diff --git a/.github/workflows/builds.yml b/.github/workflows/builds.yml index 23063767..6fd6b911 100644 --- a/.github/workflows/builds.yml +++ b/.github/workflows/builds.yml @@ -4,7 +4,7 @@ name: Builds env: ARTIFACTS_FOLDER: '${{ github.workspace }}/artifacts' - DSS_CAPI_TAG: '0.14.5' + DSS_CAPI_TAG: '0.15.0b4' on: # release: @@ -13,204 +13,282 @@ on: jobs: build_linux_x64: + continue-on-error: true name: 'Linux x64' runs-on: ubuntu-latest strategy: matrix: - container-image: [ - 'quay.io/pypa/manylinux_2_28_x86_64', - 'quay.io/pypa/manylinux2014_x86_64' - ] + include: + - container_image: 'quay.io/pypa/manylinux_2_28_x86_64' + SKIP_SCIPY: 0 + # - container_image: 'quay.io/pypa/manylinux2014_x86_64' + # SKIP_SCIPY: 1 container: - image: ${{ matrix.container-image }} + image: ${{ matrix.container_image }} env: - CONDA_SUBDIR: 'linux-64' - CONDA: "/opt/miniconda/" + DSS_PYTHON_TEST_LINUX: '1' + SKIP_SCIPY: "${{ matrix.SKIP_SCIPY }}" steps: - - name: 'Checkout' + - name: 'Checkout DSS-Python' run: | git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY dss_python cd dss_python git checkout $GITHUB_SHA + + - name: 'Get electricdss-tst' + run: | + git clone --depth=1 https://github.com/dss-extensions/electricdss-tst.git + - name: 'Download/extract message catalogs' run: | curl -s -L https://github.com/dss-extensions/dss_capi/releases/download/${DSS_CAPI_TAG}/messages.tar.gz -o messages.tar.gz cd dss_python/dss tar zxf ../../messages.tar.gz + - name: Build wheel run: | mkdir -p artifacts mkdir -p artifacts_raw bash dss_python/ci/build_linux.sh x64 - # - name: Build conda packages - # continue-on-error: true - # run: | - # bash dss_python/ci/build_conda.sh - - name: Try installing the wheel - run: bash dss_python/ci/test_wheel.sh + + - name: Install wheel + run: | + bash dss_python/ci/test_wheel.sh + - name: 'Upload artifacts' - uses: "actions/upload-artifact@v3" + uses: "actions/upload-artifact@v4" + if: ${{ matrix.container_image == 'quay.io/pypa/manylinux_2_28_x86_64' }} with: - name: 'packages' + name: 'dss_python-wheel' path: '${{ github.workspace }}/artifacts' - # build_linux_x86: - # name: 'Linux x86' - # runs-on: ubuntu-latest - # env: - # CONDA_SUBDIR: 'linux-32' - # DOCKER_IMAGE: 'pmeira/manylinux_wheel_fpc322_i686' - # steps: - # - name: 'Checkout' - # run: | - # git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY dss_python - # cd dss_python - # git checkout $GITHUB_SHA - # - name: 'Setup Docker' - # run: | - # docker pull $DOCKER_IMAGE - # - name: 'Download/extract message catalogs' - # run: | - # curl -s -L https://github.com/dss-extensions/dss_capi/releases/download/${DSS_CAPI_TAG}/messages.tar.gz -o messages.tar.gz - # cd dss_python/dss - # tar zxf ../../messages.tar.gz - # - name: Build wheel - # run: | - # mkdir -p artifacts - # mkdir -p artifacts_raw - # docker run -e GITHUB_SHA -e GITHUB_REF -v "${PWD}:/build" -w /build $DOCKER_IMAGE bash /build/dss_python/ci/build_linux.sh x86 - # - name: 'Upload artifacts' - # uses: "actions/upload-artifact@v3" - # with: - # name: 'packages' - # path: '${{ github.workspace }}/artifacts' + - name: Run tests (FastDSS) + shell: bash + run: | + export PATH=/opt/python/cp313-cp313/bin/:$PATH + cd dss_python + pip install cffi numpy pytest + DSS_EXTENSIONS_FASTDSS=1 python -m pytest + + - name: Run tests (CFFI) + shell: bash + run: | + export PATH=/opt/python/cp313-cp313/bin/:$PATH + cd dss_python + pip install cffi numpy pytest + DSS_EXTENSIONS_FASTDSS=0 python -m pytest build_macos_x64: + continue-on-error: true name: 'macOS x64' - runs-on: 'macos-11' + runs-on: 'macos-15-intel' env: - SDKROOT: '${{ github.workspace }}/MacOSX10.13.sdk' PYTHON: python3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 path: 'dss_python' + + - name: "Get electricdss-tst" + uses: actions/checkout@v6 + with: + fetch-depth: 1 + repository: 'dss-extensions/electricdss-tst' + path: 'electricdss-tst' + + - name: 'Prepare Python' + uses: actions/setup-python@v6 + with: + python-version: '3.11' + cache: 'pip' + - name: 'Download/extract message catalogs' run: | curl -s -L https://github.com/dss-extensions/dss_capi/releases/download/${DSS_CAPI_TAG}/messages.tar.gz -o messages.tar.gz cd dss_python/dss tar zxf ../../messages.tar.gz + - name: Build wheel run: | bash dss_python/ci/build_wheel.sh - # - name: Build conda packages - # continue-on-error: true - # run: | - # sudo chown -R $UID $CONDA - # bash dss_python/ci/build_conda.sh - - name: 'Upload artifacts' - uses: "actions/upload-artifact@v3" - with: - name: 'packages' - path: '${{ github.workspace }}/artifacts' + + - name: Install wheel + shell: bash + run: | + bash dss_python/ci/test_wheel.sh + + - name: Run tests (FastDSS) + shell: bash + run: | + cd dss_python + DSS_EXTENSIONS_FASTDSS=1 python -m pytest + + - name: Run tests (CFFI) + shell: bash + run: | + cd dss_python + DSS_EXTENSIONS_FASTDSS=0 python -m pytest build_macos_arm64: + continue-on-error: true name: 'macOS ARM64' - runs-on: 'macos-11' + runs-on: 'macos-15' env: PYTHON: python3 - _PYTHON_HOST_PLATFORM: macosx-11.0-arm64 ARCHFLAGS: '-arch arm64' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 path: 'dss_python' + + - name: "Get electricdss-tst" + uses: actions/checkout@v6 + with: + fetch-depth: 1 + repository: 'dss-extensions/electricdss-tst' + path: 'electricdss-tst' + + - name: 'Prepare Python' + uses: actions/setup-python@v6 + with: + python-version: '3.11' + cache: 'pip' + - name: 'Download/extract message catalogs' run: | curl -s -L https://github.com/dss-extensions/dss_capi/releases/download/${DSS_CAPI_TAG}/messages.tar.gz -o messages.tar.gz cd dss_python/dss tar zxf ../../messages.tar.gz - # - name: 'Download macOS SDK 10.13' - # run: | - # curl -s -L https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX10.13.sdk.tar.xz -o macOSsdk.tar.xz - # tar xf macOSsdk.tar.xz + - name: Build wheel run: | bash dss_python/ci/build_wheel.sh - # - name: Build conda packages - # continue-on-error: true - # run: | - # sudo chown -R $UID $CONDA - # bash dss_python/ci/build_conda.sh - - name: 'Upload artifacts' - uses: "actions/upload-artifact@v3" - with: - name: 'packages' - path: '${{ github.workspace }}/artifacts' + + - name: Install wheel + shell: bash + run: | + bash dss_python/ci/test_wheel.sh + + - name: Run tests (FastDSS) + shell: bash + run: | + cd dss_python + DSS_EXTENSIONS_FASTDSS=1 python -m pytest + + - name: Run tests (CFFI) + shell: bash + run: | + cd dss_python + DSS_EXTENSIONS_FASTDSS=0 python -m pytest build_win_x64: + continue-on-error: true name: 'Windows x64' - runs-on: windows-2019 + runs-on: windows-latest env: - CONDA_SUBDIR: 'win-64' PYTHON: python steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 path: 'dss_python' + + - name: "Get electricdss-tst" + uses: actions/checkout@v6 + with: + fetch-depth: 1 + repository: 'dss-extensions/electricdss-tst' + path: 'electricdss-tst' + - name: 'Download/extract message catalogs' shell: cmd run: | "c:\Program Files\Git\mingw64\bin\curl" -s -L https://github.com/dss-extensions/dss_capi/releases/download/%DSS_CAPI_TAG%/messages.zip -o messages.zip cd dss_python\dss tar zxf ..\..\messages.zip + - name: Build wheel shell: bash run: | bash dss_python/ci/build_wheel.sh - # - name: Build conda packages - # continue-on-error: true - # shell: bash - # run: | - # bash dss_python/ci/build_conda.sh - - name: 'Upload artifacts' - uses: "actions/upload-artifact@v3" - with: - name: 'packages' - path: '${{ github.workspace }}/artifacts' + + - name: Install wheel + shell: bash + run: | + bash dss_python/ci/test_wheel.sh + + - name: Run tests (FastDSS) + shell: cmd + run: | + set DSS_EXTENSIONS_FASTDSS=1 + cd dss_python + python -m pytest + + - name: Run tests (CFFI) + shell: cmd + run: | + set DSS_EXTENSIONS_FASTDSS=0 + cd dss_python + python -m pytest + build_win_x86: + continue-on-error: true name: 'Windows x86' - runs-on: windows-2019 + runs-on: windows-latest env: - CONDA_SUBDIR: 'win-32' PYTHON: python + SKIP_SCIPY: '1' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 path: 'dss_python' - - uses: actions/setup-python@v3 + + - name: "Get electricdss-tst" + uses: actions/checkout@v6 + with: + fetch-depth: 1 + repository: 'dss-extensions/electricdss-tst' + path: 'electricdss-tst' + + - name: 'Prepare Python' + uses: actions/setup-python@v6 with: - python-version: '3.7' + python-version: '3.11' + cache: 'pip' architecture: 'x86' + - name: 'Download/extract message catalogs' shell: cmd run: | "c:\Program Files\Git\mingw64\bin\curl" -s -L https://github.com/dss-extensions/dss_capi/releases/download/%DSS_CAPI_TAG%/messages.zip -o messages.zip cd dss_python\dss tar zxf ..\..\messages.zip + - name: Build wheel shell: bash run: | bash dss_python/ci/build_wheel.sh - - name: 'Upload artifacts' - uses: "actions/upload-artifact@v3" - with: - name: 'packages' - path: '${{ github.workspace }}/artifacts' + - name: Install wheel + shell: bash + run: | + bash dss_python/ci/test_wheel.sh + + - name: Run tests (FastDSS) + shell: cmd + run: | + set DSS_EXTENSIONS_FASTDSS=1 + cd dss_python + python -m pytest + + - name: Run tests (CFFI) + shell: cmd + run: | + set DSS_EXTENSIONS_FASTDSS=0 + cd dss_python + python -m pytest diff --git a/.gitignore b/.gitignore index 6db77324..c5d67d65 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,4 @@ dss/messages/* docs/apidocs tests/result*.zip tmp/ +electricdss-tst \ No newline at end of file diff --git a/README.md b/README.md index c677bb60..b273ee79 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # DSS-Python: Extended bindings for an alternative implementation of EPRI's OpenDSS -Python bindings and misc tools for using our to [our customized/alternative implementation](https://github.com/dss-extensions/dss_capi) of [OpenDSS](http://smartgrid.epri.com/SimulationTool.aspx), AltDSS/DSS C-API library. OpenDSS is an open-source electric power distribution system simulator [distributed by EPRI](https://sourceforge.net/p/electricdss/). Based on DSS C-API, CFFI and NumPy, aiming for enhanced performance and full compatibility with the official COM object API on Windows, Linux and macOS. Support includes Intel-based (x86 and x64) processors, as well as ARM processors for Linux (including Raspberry Pi devices) and macOS (including Apple M1 and later). +Python bindings and misc tools for using our to [our customized/alternative implementation](https://github.com/dss-extensions/dss_capi) of [OpenDSS](http://smartgrid.epri.com/SimulationTool.aspx), AltDSS/DSS C-API library. OpenDSS is an open-source electric power distribution system simulator [distributed by EPRI](https://sourceforge.net/p/electricdss/). Based on DSS C-API, CFFI and NumPy, aiming for enhanced performance and full compatibility with EPRI's OpenDSS COM object API on Windows, Linux and macOS. Support includes Intel-based (x86 and x64) processors, as well as ARM processors for Linux (including Raspberry Pi devices) and macOS (including Apple M1 and later). More context about this project and its components (including alternatives in [Julia](https://dss-extensions.org/OpenDSSDirect.jl/latest/), [MATLAB](https://github.com/dss-extensions/dss_matlab/), C++, [C#/.NET](https://github.com/dss-extensions/dss_sharp/), [Go](https://github.com/dss-extensions/AltDSS-Go/), and [Rust](https://github.com/dss-extensions/AltDSS-Rust/)), please check [https://dss-extensions.org/](https://dss-extensions.org/) and our hub repository at [dss-extensions/dss-extensions](https://github.com/dss-extensions/dss-extensions) for more documentation, discussions and the [FAQ](https://dss-extensions.org/faq.html). @@ -13,7 +13,7 @@ This package can be used as a companion to [OpenDSSDirect.py](http://github.com/ While we plan to add a lot more functionality into DSS-Python, the main goal of creating a COM-compatible API has been reached in 2018. If you find an unexpected missing feature, please report it! Currently missing features that will be implemented eventually are interactive features and diakoptics (planned for a future version). -This module mimics the COM structure (as exposed via `win32com` or `comtypes`) — see [The DSS instance](https://dss-extensions.org/DSS-Python/#the-dss-instance) as well as [OpenDSS COM/classic APIs](https://dss-extensions.org/classic_api.html) for some docs — effectively enabling multi-platform compatibility at Python level. Compared to other options, it provides easier migration from code that uses the official OpenDSS through COM. See also [OpenDSS: Python APIs](https://dss-extensions.org/python_apis.html). +This module mimics the COM structure (as exposed via `win32com` or `comtypes`) — see [The DSS instance](https://dss-extensions.org/DSS-Python/#the-dss-instance) as well as [OpenDSS COM/classic APIs](https://dss-extensions.org/classic_api.html) for some docs — effectively enabling multi-platform compatibility at Python level. Compared to other options, it provides easier migration from code that uses EPRI's OpenDSS through COM. See also [OpenDSS: Python APIs](https://dss-extensions.org/python_apis.html). Most of the COM documentation can be used as-is, but instead of returning tuples or lists, this module returns/accepts NumPy arrays for numeric data exchange, which is usually preferred by the users. By toggle `DSS.AdvancedTypes`, complex numbers and matrices (shaped arrays) are also used to provide a more modern experience. The module depends mostly on CFFI, NumPy, typing_extensions and, optionally, SciPy.Sparse for reading the sparse system admittance matrix. Pandas and matplotlib are optional dependencies [to enable plotting](https://github.com/dss-extensions/dss_python/blob/master/docs/examples/Plotting.ipynb) and other features. @@ -103,7 +103,7 @@ import win32com.client dss_engine = win32com.client.gencache.EnsureDispatch("OpenDSSEngine.DSS") ``` -or `comtypes` (incidentally, `comtypes` is usually faster than `win32com`, so we recommend it if you need the official OpenDSS COM module): +or `comtypes` (incidentally, `comtypes` is usually faster than `win32com`, so we recommend it if you need EPRI's OpenDSS COM module): ```python import comtypes.client @@ -132,18 +132,18 @@ for i in range(len(voltages) // 2): ## Testing -Since the DLL is built using the Free Pascal compiler, which is not officially supported by EPRI, the results are validated running sample networks provided in the official OpenDSS distribution. The only modifications are done directly by the script, removing interactive features and some other minor issues. Most of the sample files from the official OpenDSS repository are used for validation. +Since the DLL is built using the Free Pascal compiler, which is not officially supported by EPRI, the results are validated running sample networks provided in EPRI's OpenDSS distribution. The only modifications are done directly by the script, removing interactive features and some other minor issues. Most of the sample files from EPRI's OpenDSS repository are used for validation. The validation scripts is `tests/validation.py` and requires the same folder structure as the building process. You need `win32com` to run it on Windows. -As of version 0.11, the full validation suite can be run on the three supported platforms. This is possible by saving the official COM DLL output and loading it on macOS and Linux. We hope to fully automate this validation in the future. +As of version 0.11, the full validation suite can be run on the three supported platforms. This is possible by saving EPRI's OpenDSS COM DLL output and loading it on macOS and Linux. We hope to fully automate this validation in the future. ## Roadmap: docs and interactive features Besides bug fixes, the main functionality of this library is mostly done. Notable desirable features that may be implemented are: - More examples, especially for the extra features. There is a growing documentation hosted at [https://dss-extensions.org/Python/](https://dss-extensions.org/DSS-Python/) and [https://dss-extensions.org/docs.html](https://dss-extensions.org/docs.html); watch also https://github.com/dss-extensions/dss-extensions for more. -- Reports integrated in Python and interactive features on plots. Since most of the plot types from the official OpenDSS are optionally available since DSS-Python 0.14.2, advanced integration and interactive features are planned for a future feature. +- Reports integrated in Python and interactive features on plots. Since most of the plot types from EPRI's OpenDSS are optionally available since DSS-Python 0.14.2, advanced integration and interactive features are planned for a future feature. Expect news about these items by version 1.0. diff --git a/ci/build_linux.sh b/ci/build_linux.sh index 3029e32e..dc503efb 100644 --- a/ci/build_linux.sh +++ b/ci/build_linux.sh @@ -3,5 +3,6 @@ export PATH=/opt/python/cp39-cp39/bin/:$PATH cd dss_python python3 -m pip install --upgrade pip wheel hatch +python3 -m pip install 'virtualenv<21' python3 -m hatch build "../artifacts" cd .. diff --git a/ci/build_wheel.sh b/ci/build_wheel.sh index a67d1fde..87279e94 100644 --- a/ci/build_wheel.sh +++ b/ci/build_wheel.sh @@ -1,5 +1,5 @@ mkdir -p artifacts cd dss_python $PYTHON -m pip install --upgrade pip wheel hatch -$PYTHON -m pip install cffi +$PYTHON -m pip install cffi numpy pytest 'virtualenv<21' $PYTHON -m hatch build "$ARTIFACTS_FOLDER" diff --git a/ci/test_wheel.sh b/ci/test_wheel.sh index ff1bd5fc..e02c1a9e 100644 --- a/ci/test_wheel.sh +++ b/ci/test_wheel.sh @@ -1,15 +1,28 @@ -# Currently only for Linux - set -e -x -ORIGINAL_PATH=$PATH -PYTHON_DIRS="cp37-cp37m cp38-cp38 cp39-cp39 cp310-cp310 cp311-cp311" - -for pydir in $PYTHON_DIRS -do - echo Installing for CPython $pydir - export PATH=/opt/python/${pydir}/bin/:$ORIGINAL_PATH - python -m pip install scipy matplotlib - python -m pip install artifacts/dss_python-*.whl - python -c 'from dss import DSS; DSS.Plotting.enable(); DSS("new circuit.test123")' -done +if [[ "x${DSS_PYTHON_TEST_LINUX}" == "x1" ]]; then + ORIGINAL_PATH=$PATH + PYTHON_DIRS="cp311-cp311 cp312-cp312 cp313-cp313 cp314-cp314" + for pydir in $PYTHON_DIRS + do + echo Installing for CPython $pydir + export PATH=/opt/python/${pydir}/bin/:$ORIGINAL_PATH + if [[ "x${SKIP_SCIPY}" != "x1" ]]; then + python -m pip install scipy matplotlib + python -m pip install artifacts/dss_python-*.whl + python -c 'from dss import DSS; DSS.Plotting.enable(); DSS("new circuit.test123")' + else + python -m pip install artifacts/dss_python-*.whl + python -c 'from dss import DSS; DSS("new circuit.test123")' + fi + done +else + if [[ "x${SKIP_SCIPY}" != "x1" ]]; then + python -m pip install scipy matplotlib + python -m pip install artifacts/dss_python-*.whl + python -c 'from dss import DSS; DSS.Plotting.enable(); DSS("new circuit.test123")' + else + python -m pip install artifacts/dss_python-*.whl + python -c 'from dss import DSS; DSS("new circuit.test123")' + fi +fi \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index 1330ca8d..7050adc3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,6 +3,44 @@ Remember that changes in our alternative OpenDSS engine, currently known as DSS C-API, are always relevant. See [DSS C-API's repository](https://github.com/dss-extensions/dss_capi/) for more information. +## 0.16.x + +### 0.16.0 + +Released on 2024-12-1x. + +- See the extensive backend changes in the next subsection. +- `UseExceptions` and `AdvancedTypes` now only affect the local bindings. That is, a second instance for the same or different DSS engine context can use different settings. This also decouples the option between DSS-Python and OpenDSSDirect.py. It should make it easier to mix and match our Python packages without having to worry too much about this settings. + +#### Backend changes and support for EPRI's distribution + +- DSS-Python-Backend now provides a custom backend which integrates directly with NumPy, called internally "FastDSS", besides the legacy CFFI backend. The CFFI backend is now lighter and faster to compile. + - Since the new FastDSS is tighly integrated with CPython and NumPy, we added the option to disable it using the environment variable `DSS_EXTENSIONS_FASTDSS`. Set it to 0 to disable the backend, using only the CFFI backend. + - As the name implies, FastDSS is fast, even compared to CFFI. If removes most of the overhead of running a few interpreted lines of Python code, interacting directly with the CPython C-API and the NumPy C-API. That is, we do not expect to change it a lot in the future. + - Reading numeric arrays and strings from the API with FastDSS gets a speed boost compared to the old backend. + - Note that the original CFFI backend was already faster for many functions vs. the alternatives. + - The performance of each function varies across all the engines now supported (AltDSS, OpenDSS, OpenDSS-C). AltDSS and its DSS C-API implementation have specific optimizations implemented throughout the years. + - For PyPy and other Python implementations, the CFFI backend is still preferred. We would like to experiment with a backend based on [HPy](https://hpyproject.org/) in the future, if time permits. + +- Although we do not expect users to explore this, the backend now allows loading external DSS C-API libraries in the new struct-style initialization. + +- DSS-Extensions, including DSS-Python and OpenDSSDirect.py, now have good support for EPRI's OpenDSS binaries, including the Delphi version (mainline) and the new OpenDSS-C. + - The integration started in 2023, after EPRI's Direct DLL API was updated to migrate from Delphi variants, moving closer to our own DSS C-API in some aspects. + - Contributors from DSS-Extensions have collaborated with EPRI on the maintainance and general development of OpenDSS-C in 2024. A few extras from our AltDSS engine have been integrated into OpenDSS-C, still under testing by the time this document was updated. + - The integration is done by wrapping EPRI's binaries with a very thin layer that exposes it with the DSS-Extensions API. Our compatibility layer is called Oddie (originally for OpenDSSDirect.DLL Interface Extender). Functions that are not or cannot be implemented return errors. + - The Delphi version of EPRI's OpenDSS, in OpenDSSDirect.DLL, is copied from the https://github.com/dss-extensions/opendss-svn-mirror -- the OpenDSS versions tracked on Git tags. + - Initially, since EPRI does not distribute OpenDSS-C binaries yet, DSS-Extensions builds shared libraries/DLLs combining Oddie and OpenDSS-C. With the new-style initialization, Oddie adds a single function to the public API. + - Users can build their own binaries for OpenDSS-C (or the Delphi OpenDSS) and use that through the Oddie interface. That is, if a new OpenDSS version is released, users can install EPRI's distribution and load the binaries installed with that. Breaking API/ABI changes may crash though. + **DSS-Extensions will not patch the official EPRI's OpenDSS(-C) beyond build scripts.** That is, if a bug is not in our code, we cannot fix it on EPRI's distribution. We can adjust things in our engine, AltDSS (the main code in DSS C-API's repository). + - On Windows, users will have three alternatives of engine: AltDSS, EPRI's mainline OpenDSS written in Delphi, and EPRI's new OpenDSS-C written in C++. + +- We are open to suggestions on if and how to adjust DSS-Python and OpenDSSDirect.py to better signal that certain functions and objects are not available with EPRI's engine and vice-versa. + +To clear any potential confusion, our custom engine and API will continue to be developed since it has extra features and we are free to experiment and improve it without requiring external approval. + +When using DSS-Extensions or OpenDSS in general, please try to cite the engine version. It will make it much easier to reproduce your work. + + ## 0.15.x ### 0.15.6 @@ -47,7 +85,7 @@ Released on 2024-02-12. Released on 2024-02-09. -- Upgrade the backend to [**AltDSS/DSS C-API 0.14.0**](https://github.com/dss-extensions/dss_capi/releases/tag/0.14.0). **A lot** of changes there, please check the changelog. Includes many small bugfixes, improvements, and ports of a few changes from the official OpenDSS codebase, matching OpenDSS v9.8.0.1. +- Upgrade the backend to [**AltDSS/DSS C-API 0.14.0**](https://github.com/dss-extensions/dss_capi/releases/tag/0.14.0). **A lot** of changes there, please check the changelog. Includes many small bugfixes, improvements, and ports of a few changes from EPRI's OpenDSS codebase, matching OpenDSS v9.8.0.1. - Enums: - Move to DSS-Python-Backend to allow easier sharing among all Python packages from DSS-Extensions. @@ -134,7 +172,7 @@ Released on 2023-03-28. - Engine updated to [**DSS C-API 0.13.0**](https://github.com/dss-extensions/dss_capi/releases/tag/0.13.0), which is very compatible with OpenDSS 9.6.1.1 (plus some official SVN commits up to rev 3595, plus our own changes. - **New test suite,** which runs many more files and validates more of the API. We now use `pytest` for some more complex tests, while the numeric validation is done with the new `compare_outputs.py`. - **New `DSS.AdvancedTypes`:** toggle matrix shapes and complex numbers in many of the properties and functions of the API. This is disabled by default. -- **New `DSS.CompatFlags`:** to address some concerns about compatibility, we added a few toggles to toggle behavior that matches the official OpenDSS more closely. This flags will be refined in later releases. +- **New `DSS.CompatFlags`:** to address some concerns about compatibility, we added a few toggles to toggle behavior that matches EPRI's OpenDSS more closely. This flags will be refined in later releases. - Drop deprecated IR classes and a few undocumented functions. - Use **more enums** throughout the code, which helps both readability and documentation. Some enums were complemented. - **`DSS.AdvancedTypes`** toggle: enabling `AdvancedTypes` allows using **array/matrix shapes and complex numbers** as results from properties/functions in the API. @@ -155,25 +193,25 @@ Released on 2023-03-28. - Clean-up several files to ease the transition from Pascal to C++; more enum usage, remove redundant internal properties, rename some class members, etc. Some final steps still remain (that work is done in private branches). - Fixes a couple of minor memory leaks. -- Removed our old *Legacy Models* mechanism. Right now, the API functions still exist, but will have no effect when setting and will throw an error. For a future version, the functions will be removed. This toggle was introduced in 2020, some time after the removal of the legacy models in the official OpenDSS. We believe users had enough time to fully migrate and the extra maintenance burden is not justified anymore. +- Removed our old *Legacy Models* mechanism. Right now, the API functions still exist, but will have no effect when setting and will throw an error. For a future version, the functions will be removed. This toggle was introduced in 2020, some time after the removal of the legacy models in EPRI's OpenDSS. We believe users had enough time to fully migrate and the extra maintenance burden is not justified anymore. - Transition some deprecated and buggy properties to throw specific errors, instead of generic messages. Issue: https://github.com/dss-extensions/dss_capi/issues/118 - `Export` command: When the user provides a filename, use it as-is, otherwise could be an invalid path in case-sensitive file systems (e.g. Linux, most likely). - `Dump` and `Save` commands: in some cases, our internal "hybrid enums" were not being converted correctly for dumps. A few classes had incomplete dump implementations since v0.12.0; some strings needed to be escaped for correct output. - CtrlQueue: adjust string formatting of items; although this doesn't affect the numeric results, the strings from the queue had some truncated numbers. - Property system: For compatibility with the official version, allow autoresizing some arrays when given conflicting number of elements through the text interface or scripts. -- `Like` property: Although not recommended and [deprecated in the official OpenDSS](https://sourceforge.net/p/electricdss/discussion/861977/thread/8b59d21eb6/?limit=25#b57c/f668), the sequence of properties filled in the original copy is also copied. If you use `Like`, remember to check if the copy actually worked since some classes are known to not copy every property correctly. +- `Like` property: Although not recommended and [deprecated in EPRI's OpenDSS](https://sourceforge.net/p/electricdss/discussion/861977/thread/8b59d21eb6/?limit=25#b57c/f668), the sequence of properties filled in the original copy is also copied. If you use `Like`, remember to check if the copy actually worked since some classes are known to not copy every property correctly. - Plotting and UI: The engine side plotting callback system is now complete. There are fixes for `DaisyPlot` and `GeneralDataPlot`, especially multi-platform handling. Changed how some properties are exposed in the JSON interchange to the callbacks. Implement argument handling and callback dispatch for `DI_Plot`, `CompareCases` and `YearlyCurves`. - `New` commands: Fix potential issue with null pointers and duplicate names when `DuplicatesAllowed=False`. - EnergyMeter: Fix error message when the metered element is not a PDElement. - CIMXML export: Fix issues present since v0.12.0; reported in https://github.com/dss-extensions/OpenDSSDirect.py/issues/121 - Parser: properly error out when given excessive number of elements for matrices; implemented due to the report in https://github.com/dss-extensions/OpenDSSDirect.py/issues/122 -- Port most changes from the official OpenDSS up to SVN revision 3595 (OpenDSS v9.6.1.1 + a couple of CIMXML updates); check [OpenDSS v9.6.1.1 README.txt](https://sourceforge.net/p/electricdss/code/3595/tree/trunk/Version8/README.txt) for some complementary info to the list below. +- Port most changes from EPRI's OpenDSS up to SVN revision 3595 (OpenDSS v9.6.1.1 + a couple of CIMXML updates); check [OpenDSS v9.6.1.1 README.txt](https://sourceforge.net/p/electricdss/code/3595/tree/trunk/Version8/README.txt) for some complementary info to the list below. - Relay, UPFC, UPFCControl changes ported. - CIMXML exports: Various updates. - RegControl: More log and debug trace entries. - LoadMult: Set `SystemYChanged` when changing `LoadMult` **through a DSS script or DSS command** (doesn't affect `Solution_Set_LoadMult`) - Port PVSystem, Storage, InvControl, and StorageController changes, including the new grid-forming mode (GFM). For DSS-Extensions, we added a new class InvBasedPCE to avoid some redundancy and make things clearer. - - Port DynamicExp and related functionality. In our implementation, we also add a new class DynEqPCE to avoid some redundant code (could still be improved). the Generator and the new InvBasePCE derive from this new DynEqPCE. **Note**: the `DynamicEq` functionality from the upstream still seems incomplete and some things are not fully implemented or maybe buggy, so we only ported now to remove the burden of porting this down the line. If you find issues, feel free to report here on DSS-Extensions, but we recommended checking first with the official OpenDSS — if the issue is also found in the official version, prefer to report in the official OpenDSS forum first so everyone gets the fixes and our implementation doesn't diverge too much. + - Port DynamicExp and related functionality. In our implementation, we also add a new class DynEqPCE to avoid some redundant code (could still be improved). the Generator and the new InvBasePCE derive from this new DynEqPCE. **Note**: the `DynamicEq` functionality from the upstream still seems incomplete and some things are not fully implemented or maybe buggy, so we only ported now to remove the burden of porting this down the line. If you find issues, feel free to report here on DSS-Extensions, but we recommended checking first with EPRI's OpenDSS — if the issue is also found in the official version, prefer to report in EPRI's OpenDSS forum first so everyone gets the fixes and our implementation doesn't diverge too much. - CktElement/API: add a few new functions related to state variables. - Circuit, Line: port the `LongLineCorrection` flag now that it seems to be fixed upstream. Note that we didn't publish releases with the previous buggy version from the upstream OpenDSS (that applied the long-line correction for everything). - LineSpacing: port side-effect from upstream; changing `nconds` now reallocates and doesn't leak previously allocated memory. Not a common operation, so it's not very relevant. @@ -191,9 +229,9 @@ Released on 2023-03-28. - `Bus_Get_ZSC012Matrix`: check for nulls - `Bus_Get_AllPCEatBus`, `Bus_Get_AllPDEatBus`: faster implementations - `Meters_Get_CountBranches`: reimplemented - - `Monitors_Get_dblHour`: For harmonics solution, return empty array. Previously, it was returning a large array instead of a single element (`[0]`) array. A small issue adjusted for compatibility with the official COM API results. + - `Monitors_Get_dblHour`: For harmonics solution, return empty array. Previously, it was returning a large array instead of a single element (`[0]`) array. A small issue adjusted for compatibility with EPRI's OpenDSS COM API results. - `Reactors_Set_Bus1`: Match the side-effects of the property API for two-terminal reactors. - - New `DSS_Set_CompatFlags`/`DSS_Get_CompatFlags` function pair: introduced to address some current and potential future concerns about compatibility of results with the official OpenDSS. See the API docs for more info. + - New `DSS_Set_CompatFlags`/`DSS_Get_CompatFlags` function pair: introduced to address some current and potential future concerns about compatibility of results with EPRI's OpenDSS. See the API docs for more info. - New `DSS_Set_EnableArrayDimensions`/`DSS_Get_EnableArrayDimensions`: for Array results in the API, implement optional matrix sizes; when setting `DSS_Set_EnableArrayDimensions(true)`, the array size pointer will be filled with two extra elements to represent the matrix size (if the data is a matrix instead of a plain vector). For complex number, the dimensions are filled in relation to complex elements instead of double/float64 elements even though we currently reuse the double/float64 array interface. Issue: https://github.com/dss-extensions/dss_capi/issues/113 Note that a couple of SVN changes were ignored on purpose since they introduced potential issues, while many other changes and bug-fixes did not affect the DSS C-API version since our implementation is quite different in some places. @@ -219,12 +257,12 @@ Released on 2022-07-14. - New `ToJSON` functions to dump object properties (power flow state is not included at the moment) - Initial implementation of the new `DSS.Obj` API for direct DSS object and uniform batch manipulation, covering all DSS classes implemented in DSS C-API. The shape of this API may change for the next releases. At the moment it is intended for advanced users. For example, if you get an object handle from the engine and load a new circuit, the handle is invalid and you should not access it anymore (otherwise, crashes are expected). - Initial (work-in-progress) implementation of plotting functions. This will also be finished and polished in following releases. -- Due to some changes ported from the official OpenDSS since 0.10.7, some results may change, especially for circuits that used miles as length units. The same is observed across the official OpenDSS releases. +- Due to some changes ported from EPRI's OpenDSS since 0.10.7, some results may change, especially for circuits that used miles as length units. The same is observed across EPRI's OpenDSS releases. #### DSS C-API 0.12.0 changes -**Includes porting of most official OpenDSS features up to revision 3460.** Check the OpenDSS SVN commits for details. +**Includes porting of most features from EPRI's OpenDSS up to revision 3460.** Check the OpenDSS SVN commits for details. Since version 0.11 accumulated too many changes for too long (nearly 2 years), making it hard to keep two parallel but increasingly distinct codebases, version 0.12 is a stepping stone to the next big version (planned as 0.13) that will contain all of the 0.11 changes. As such, only some of the 0.11 features are included. The previous 0.10.8 changes are also included here. @@ -239,13 +277,13 @@ This version still maintains basic compatibility with the 0.10.x series of relea - Experimental callbacks for plotting and message output. Expect initial support in Python soon after DSS C-API v0.12 is released. - Introduce `AllowChangeDir` mechanism: defaults to enabled state for backwards compatibility. When disabled, the engine will not change the current working directory in any situation. This is exposed through a new pair of functions `DSS_Set_AllowChangeDir` and `DSS_Get_AllowChangeDir`, besides the environment variable `DSS_CAPI_ALLOW_CHANGE_DIR`. - New setting to toggle `DOScmd` command. Can be controlled through the environment variable `DSS_CAPI_ALLOW_DOSCMD` or functions `DSS_Get_AllowDOScmd`/`DSS_Set_AllowDOScmd`. -- Use `OutputDirectory` more. `OutputDirectory` is set to the current `DataPath` if `DataPath` is writable. If not, it's set to a general location (`%LOCALAPPDATA%/dss-extensions` and `/tmp/dss-extensions` since this release). This should make life easier for a user running files from a read-only location. Note that this is only an issue when running a `compile` command. If the user only uses `redirect` commands, the `DataPath` and `OutputDirectory` are left empty, meaning the files are written to the current working directory (CWD), which the user can control through the programming language driving DSS C-API. Note that the official OpenDSS COM behavior is different, since it loads the `DataPath` saved in the registry and modifies the CWD accordingly when OpenDSS is initialized. +- Use `OutputDirectory` more. `OutputDirectory` is set to the current `DataPath` if `DataPath` is writable. If not, it's set to a general location (`%LOCALAPPDATA%/dss-extensions` and `/tmp/dss-extensions` since this release). This should make life easier for a user running files from a read-only location. Note that this is only an issue when running a `compile` command. If the user only uses `redirect` commands, the `DataPath` and `OutputDirectory` are left empty, meaning the files are written to the current working directory (CWD), which the user can control through the programming language driving DSS C-API. Note that EPRI's OpenDSS COM behavior is different, since it loads the `DataPath` saved in the registry and modifies the CWD accordingly when OpenDSS is initialized. - File IO rewritten to drop deprecated Pascal functions and features. This removes some limitations related to long paths due to the legacy implementation being limited to 255 chars. - Reworked `TPowerTerminal` to achieve better memory layout. This makes simulations running `LoadsTerminalCheck=false` and `LoadsTerminalCheck=true` closer in performance, yet disabling the check is still faster. - Use `TFPHashList` where possible (replacing the custom, original THashList implementation from OpenDSS). - New LoadShape functions and internals: - - Port memory-mapped files from the official OpenDSS, used when `MemoryMapping=Yes` from a DSS script while creating a LoadShape object. + - Port memory-mapped files from EPRI's OpenDSS, used when `MemoryMapping=Yes` from a DSS script while creating a LoadShape object. - Release the `LoadShape_Set_Points` function, which can be used for faster LoadShape input, memory-mapping externally, shared memory, chunked input, etc. - Some new functions: @@ -253,11 +291,11 @@ This version still maintains basic compatibility with the 0.10.x series of relea - `Circuit_Get_ElementLosses` - `CktElement_Get_NodeRef` -- `DSS_Get_COMErrorResults`/`DSS_Set_COMErrorResults`: New compatibility setting for error/empty result. If enabled, in case of errors or empty arrays, the API returns arrays with values compatible with the official OpenDSS COM interface. +- `DSS_Get_COMErrorResults`/`DSS_Set_COMErrorResults`: New compatibility setting for error/empty result. If enabled, in case of errors or empty arrays, the API returns arrays with values compatible with EPRI's OpenDSS COM interface. For example, consider the function Loads_Get_ZIPV. If there is no active circuit or active load element: - In the disabled state (COMErrorResults=False), the function will return "[]", an array with 0 elements. - - In the enabled state (COMErrorResults=True), the function will return "[0.0]" instead. This should be compatible with the return value of the official COM interface. + - In the enabled state (COMErrorResults=True), the function will return "[0.0]" instead. This should be compatible with the return value of EPRI's OpenDSS COM interface. Defaults to True/1 (enabled state) in the v0.12.x series. This will change to false in future series. @@ -265,7 +303,7 @@ This version still maintains basic compatibility with the 0.10.x series of relea the legacy/COM behavior. The value can be toggled through the API at any time. - Drop function aliases: previously deprecated function aliases (`LoadShapes_Set_Sinterval` and `LoadShapes_Get_sInterval`) were removed to simplify the build process. Use `LoadShapes_Set_SInterval` and `LoadShapes_Get_SInterval` instead. -- Monitor headers: From the official OpenDSS, since May 2021, the monitor binary stream doesn't include the header anymore. When porting the change to DSS-Extensions, we took the opportunity to rewrite the related code, simplifying it. As such, the implementation in DSS-Extensions deviates from the official one. Extra blank chars are not included, and fields should be more consistent. As a recommendation, if your code needs to be compatible with both implementations, trimming the fields should be enough. +- Monitor headers: From EPRI's OpenDSS, since May 2021, the monitor binary stream doesn't include the header anymore. When porting the change to DSS-Extensions, we took the opportunity to rewrite the related code, simplifying it. As such, the implementation in DSS-Extensions deviates from the official one. Extra blank chars are not included, and fields should be more consistent. As a recommendation, if your code needs to be compatible with both implementations, trimming the fields should be enough. - Error messages: most messages are now more specific and, if running a DSS script from files, include the file names and line numbers. - Spectrum: To reduce overhead during object edits, now required to exist before the object that uses it. This is consistent with most of the other types in OpenDSS. - New object and batch APIs for direct manipulation of DSS objects and batches of objects @@ -287,7 +325,7 @@ Released on 2020-12-29. - Maintenance release. - Updated to DSS C-API 0.10.7, which includes most changes up to OpenDSS v9.1.3.4. - Includes an important bug fix related to the `CapRadius` DSS property. If your DSS scripts included the pattern `GMRac=... rad=...` or `GMRac=... diam=...` (in this order and without specifying `CapRadius`), you should upgrade and re-evaluate the results. -- New API properties ported from the official COM interface: `Bus.AllPCEatBus`, `Bus.AllPDEatBus`, `CktElement.TotalPowers`, `Meters.ZonePCE` +- New API properties ported from EPRI's OpenDSS COM interface: `Bus.AllPCEatBus`, `Bus.AllPDEatBus`, `CktElement.TotalPowers`, `Meters.ZonePCE` DSS C-API 0.10.7 changes: @@ -295,8 +333,8 @@ DSS C-API 0.10.7 changes: - Includes an important bug fix related to the `CapRadius` DSS property. If your DSS scripts included the pattern `GMRac=... rad=...` or `GMRac=... diam=...` (in this order and without specifying `CapRadius`), you should upgrade and re-evaluate the results. - This version should be fully API compatible with 0.10.3+. - A reference document listing the DSS commands and properties for all DSS elements is now available at https://github.com/dss-extensions/dss_capi/blob/0.10.x/docs/dss_properties.md -- New functions API ported from the official OpenDSS include: `Bus_Get_AllPCEatBus`, `Bus_Get_AllPDEatBus`, `CktElement_Get_TotalPowers`, `Meters_Get_ZonePCE`. -- The changes ported from the official OpenDSS include the following (check the repository for more details): +- New functions API ported from EPRI's OpenDSS include: `Bus_Get_AllPCEatBus`, `Bus_Get_AllPDEatBus`, `CktElement_Get_TotalPowers`, `Meters_Get_ZonePCE`. +- The changes ported from EPRI's OpenDSS include the following (check the repository for more details): - "Adds LineType property to LineCode and LineGeometry objects." - "Correcting bug found in storage device when operating in idling mode. It was preventing the solution of other test feeders (IEEE 9500)" - "Enabling fuel option for generator, fixing bug found in TotalPower command." @@ -320,15 +358,15 @@ Released on 2020-08-01. DSS C-API 0.10.6 changes: - This version should be fully API compatible with 0.10.3+. The behavior of some functions changed with the new extensions. Especially, empty strings are explicitly return as nulls instead of "\0". This conforms to the behavior already seen in arrays of strings. -- The binary releases now use Free Pascal 3.2.0. We observed the solution process is around 6% faster, and results are even closer to the official OpenDSS. +- The binary releases now use Free Pascal 3.2.0. We observed the solution process is around 6% faster, and results are even closer to EPRI's OpenDSS. - The releases now include both the optimized/default binary and a non-optimized/debug version. See the [Debugging](https://github.com/dss-extensions/dss_capi/blob/0.10.x/docs/debug.md) document for more. - Extended API validation and **Extended Errors** mechanism: - The whole API was reviewed to add basic checks for active circuit and element access. - - By default, invalid accesses now result in errors reported through the Error interface. This can be disabled to achieve the previous behavior, more compatible with the official COM implementation — that is, ignore the error, just return a default/invalid value and assume the user has handled it. + - By default, invalid accesses now result in errors reported through the Error interface. This can be disabled to achieve the previous behavior, more compatible with EPRI's OpenDSS COM implementation — that is, ignore the error, just return a default/invalid value and assume the user has handled it. - The mechanism can be toggled by API functions `DSS_Set_ExtendedErrors` and `DSS_Get_ExtendedErrors`, or environment variable `DSS_CAPI_EXTENDED_ERRORS=0` to disable (defaults to enabled state). - New **Legacy Models** mechanism: - OpenDSS 9.0+ dropped the old `PVsystem`, `Storage`, `InvControl`, and `StorageController` models, replacing with the new versions previously known as `PVsystem2`, `Storage2`, `InvControl2` and `StorageController2`. - - The behavior and parameters from the new models are different — they are better, more complete and versatile models. Check the official OpenDSS docs and examples for further information. + - The behavior and parameters from the new models are different — they are better, more complete and versatile models. Check EPRI's OpenDSS docs and examples for further information. - The implementation of the new models in DSS C-API was validated successfully with all test cases available. As such, we mirror the decision to make them the default models. - As an extension, we implemented the Legacy Models option. By toggling it, a `clear` command will be issued and the alternative models will be loaded. This should allow users to migrate to the new version but, if something that used to work with the old models stopped working somehow, the user can toggle the old models. The idea is to keep reproducibility of results while we keep updating the engine and the API. - Since EPRI dropped/deprecated the old models, we might drop them too, in a future release. Please open an issue on GitHub or send a message if those old models are important to you. @@ -340,7 +378,7 @@ DSS C-API 0.10.6 changes: - Some bugs found in DSS C-API and also reported upstream (already fixed in SVN): - `CapRadius` DSS property: if the radius was initialized using `GMRac`, `CapRadius` was left uninitialized, resulting in invalid/NaN values. - `Sensors` API: some functions edited capacitors instead of sensors. -- Updated to the official OpenDSS revision 2903, corresponding to versions 9.0.0+. Changes include: +- Updated to EPRI's OpenDSS revision 2903, corresponding to versions 9.0.0+. Changes include: - ExportCIMXML: updated. - Relay: Fix in `GetPropertyValue`. - Line: In `DumpProperties` and `MakePosSequence`, the length is handled differently for lines with `LineGeometry` or `LineSpacing`. @@ -363,7 +401,7 @@ DSS C-API 0.10.5 changes: - Disable builds and distribution of v8-only variation — the extra/missing parallel-machine will be completely merged in a mixed (v7+v8) codebase in the coming months. - This version should be fully API compatible with 0.10.3+. - `Bus` and `CktElement` API functions reworked with some more checks. -- Updated up to revision 2837 of the official OpenDSS code: +- Updated up to revision 2837 of EPRI's OpenDSS code: - Ported changes from SVN (v7 and v8) into DSS C-API v7 variation (v8 was left untouched). - 4 new API level functions (`ActiveClass_Get_ActiveClassParent`, `PVSystems_Get_Pmpp`, `PVSystems_Set_Pmpp`, `PVSystems_Get_IrradianceNow`) - 4 new components: `PVsystem2`, `Storage2`, `InvControl2`, `StorageController2` — *added for early testing, no dedicated API functions yet*. At the moment, please consider them experimental features subject to change. @@ -385,7 +423,7 @@ Released on 2019-11-16. This is a maintenance release. DSS C-API 0.10.4 changes include: -- Updated up to revision 2761 of the official OpenDSS code. The changes affect at least the following components: CIMXML export, `Capacitor`, `InvControl`, `LineGeometry`, `PVsystem`, `StorageController`, `Storage`, `Vsource`, `VCCS`. +- Updated up to revision 2761 of EPRI's OpenDSS code. The changes affect at least the following components: CIMXML export, `Capacitor`, `InvControl`, `LineGeometry`, `PVsystem`, `StorageController`, `Storage`, `Vsource`, `VCCS`. - This version should be fully compatible with 0.10.3. - Fixes issue with long paths on Linux, potentially other platforms too. @@ -424,7 +462,7 @@ Released on 2019-02-28. Released on 2019-02-17. -This is a minor DSS-Python release that contains lots of changes from DSS C-API. See also the [changes from DSS C-API](https://github.com/dss-extensions/dss_capi/blob/ed2a6b322a5e102ba61c6565e5e0eb23247b9221/docs/changelog.md#version-0101) for details, including changes from the official OpenDSS code since 0.10.0. +This is a minor DSS-Python release that contains lots of changes from DSS C-API. See also the [changes from DSS C-API](https://github.com/dss-extensions/dss_capi/blob/ed2a6b322a5e102ba61c6565e5e0eb23247b9221/docs/changelog.md#version-0101) for details, including changes from EPRI's OpenDSS code since 0.10.0. DSS-Python has recently moved from https://github.com/PMeira/dss_python/ to the new https://dss-extensions.org/ and https://github.com/dss-extensions/dss_python/ diff --git a/docs/conf.py b/docs/conf.py index 28cef554..4b41cf3f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,7 +11,7 @@ import dss project = 'DSS-Python' -copyright = '2018-2024 Paulo Meira, Dheepak Krishnamurthy, DSS-Extensions contributors' +copyright = '2018-2025 Paulo Meira, Dheepak Krishnamurthy, DSS-Extensions contributors' author = 'Paulo Meira, Dheepak Krishnamurthy, DSS-Extensions contributors' version = dss.__version__ release = dss.__version__ diff --git a/docs/examples/GettingStarted.ipynb b/docs/examples/GettingStarted.ipynb index 60288f8b..a99fbcbe 100644 --- a/docs/examples/GettingStarted.ipynb +++ b/docs/examples/GettingStarted.ipynb @@ -82,7 +82,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For a comparison of the general Python-level API, including a list of our extra functions, please check [DSS-Extensions — OpenDSS: Overview of Python APIs](https://github.com/dss-extensions/dss-extensions/blob/main/docs/python_apis.md). That documents introduces and compares DSS-Python, OpenDSSDirect.py, and the official COM implementation." + "For a comparison of the general Python-level API, including a list of our extra functions, please check [DSS-Extensions — OpenDSS: Overview of Python APIs](https://github.com/dss-extensions/dss-extensions/blob/main/docs/python_apis.md). That documents introduces and compares DSS-Python, OpenDSSDirect.py, and EPRI's OpenDSS COM implementation." ] }, { @@ -108,7 +108,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The package exposes the lower level API functions from AltDSS/DSS C-API mimicking the organization and behavior of the official COM implementation, as used in Python. This allows an easier migration, or even toggling which interface is used if the user avoids using API Extensions (which are marked as such in the documentation)." + "The package exposes the lower level API functions from AltDSS/DSS C-API mimicking the organization and behavior of EPRI's OpenDSS COM implementation, as used in Python. This allows an easier migration, or even toggling which interface is used if the user avoids using API Extensions (which are marked as such in the documentation)." ] }, { @@ -149,7 +149,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "After a DSS circuit is loaded, the interaction can be done as with the official COM module:" + "After a DSS circuit is loaded, the interaction can be done as with EPRI's OpenDSS COM module:" ] }, { diff --git a/docs/examples/JSON.ipynb b/docs/examples/JSON.ipynb index e5f05ccf..64feed16 100644 --- a/docs/examples/JSON.ipynb +++ b/docs/examples/JSON.ipynb @@ -84,7 +84,7 @@ "\n", "Our alternative OpenDSS engine used in DSS-Extensions, implemented in the DSS C-API library, extends the API to provide some JSON export functions since version 0.12.0. Provided some constraints, most classes and properties could be exported as JSON, but a few properties didn't work correctly even with these contraints. **Warning: This feature is an API Extension and is not available in the official OpenDSS.**\n", "\n", - "Note that manually creating JSON through the classic DSS API (as implemented in the official COM DLL API) is possible but cumbersome, requiring manually tracking and converting strings to the types and several other details. We expect the DSS C-API implementation, being shared across all DSS-Extensions, can achieve better performance and remove this extra processing work.\n", + "Note that manually creating JSON through the classic DSS API (as implemented in EPRI's OpenDSS COM DLL API) is possible but cumbersome, requiring manually tracking and converting strings to the types and several other details. We expect the DSS C-API implementation, being shared across all DSS-Extensions, can achieve better performance and remove this extra processing work.\n", "\n", "We will use Pandas to show tables more easily in this document, but it is not required for exporting. In the near future, we plan to release integrated dataframe support for using with Pandas and similar workloads in Python and other languages.\n", "\n", diff --git a/docs/examples/Plotting.ipynb b/docs/examples/Plotting.ipynb index 7f96718a..0e941166 100644 --- a/docs/examples/Plotting.ipynb +++ b/docs/examples/Plotting.ipynb @@ -10,9 +10,11 @@ "source": [ "# Integrated plotting in Python\n", "\n", - "*Last major update: May, 2023* \n", + "*Last major update: Feb 2026 (added how to use EPRI's Delphi OpenDSS and C++ OpenDSS-C implementations)* \n", "*Original author: Paulo Meira*\n", "\n", + "***In this document:** How to use the plotting tools from DSS-Python (DSS-Extensions), OpenDSSDirect.py and AltDSS-Python. Example gallery.*\n", + "\n", "**Due to the high number of images, this notebook is stored in the Git repository without outputs.**\n", "\n", "You can open and then run this notebook on Google Colab for a quick overview if you don't want to set up a local environment: **[Open in Colab](https://colab.research.google.com/github/dss-extensions/dss_python/blob/master/docs/examples/Plotting.ipynb)**.\n", @@ -328,7 +330,7 @@ "\n", "Besides a few missing plots, some of the implementation ones can be polished in a future release, either by default or by toggling an option. Colormaps and colorbars are one example we should use instead of the manual colormapping in general data plots, etc.\n", "\n", - "Reading a plot from `.dsv` files created by the official OpenDSS could also be added. Using our full plotting backend with the official OpenDSS is not possible, but we could expose an user-friendly API to reuse our code with the official COM implementation." + "Reading a plot from `.dsv` files created by the official OpenDSS could also be added. Using our full plotting backend with the official OpenDSS is not possible, but we could expose an user-friendly API to reuse our code with EPRI's implementation." ] }, { @@ -604,14 +606,21 @@ "\n", "// the original script already runs a scenario\n", "redirect electricdss-tst/Version8/Distrib/IEEETestCases/123Bus/Run_YearlySim.dss\n", + "Set Year=2 LoadMult=1.05\n", + "solve\n", + "closedi\n", "\n", "// we need a second scenario to compare later\n", "Set CaseName=another\n", "batchedit load..* yearly=default\n", "set mode=yearly number=720\n", - "Set Year=1\n", + "Set Year=1 LoadMult=1\n", + "solve\n", + "Set Year=2 LoadMult=1.05\n", "solve\n", - "closedi" + "closedi\n", + "! Reset LoadMult\n", + "Set LoadMult=1" ] }, { @@ -1497,6 +1506,262 @@ "# Toggle auto-showing back\n", "dss.Plotting.enable(show=True)" ] + }, + { + "cell_type": "markdown", + "id": "8e4a94e3-25cf-4c0d-aa1f-a3ff59e3dd69", + "metadata": {}, + "source": [ + "# .DSV files from EPRI's OpenDSS/OpenDSS-C\n", + "\n", + "EPRI's OpenDSS distributions, both Delphi OpenDSS and C++ OpenDSS-C, save the definition of the figure as a .DSV file, a custom format. An initial DSV parser that maps the DSV constructs to matplotlib is implemented in DSS-Python's plotting system.\n", + "\n", + "*Note: when using \"OpenDSS Viewer\", DSV files are not used.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c037c570-c2bf-4846-9f36-f47fe05c3e64", + "metadata": {}, + "outputs": [], + "source": [ + "from dss.plot import plot_dsv\n", + "\n", + "? plot_dsv" + ] + }, + { + "cell_type": "markdown", + "id": "534fccf6-decd-40a7-934c-5353dc28d5b1", + "metadata": {}, + "source": [ + "**Load the target library**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9bd4a638-911a-4105-b04a-89994eae24f2", + "metadata": {}, + "outputs": [], + "source": [ + "from dss import IOddieDSS\n", + "dss = IOddieDSS('libOpenDSSC.so') # reminder: you might need the full path to the library/DLL here" + ] + }, + { + "cell_type": "markdown", + "id": "bbc342e1-6daa-4bd7-8a19-766ba73e3fac", + "metadata": {}, + "source": [ + "**Set it as the notebook target**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fab81e6-3ae8-438f-ac2f-a0aed090a683", + "metadata": {}, + "outputs": [], + "source": [ + "from dss import plot\n", + "plot.set_nb_target(dss)" + ] + }, + { + "cell_type": "markdown", + "id": "61fae66f-d14c-4c87-9fac-b7813e2ce2ec", + "metadata": {}, + "source": [ + "**Run the plot commands**\n", + "\n", + "Plot commands return the .DSV created as a result. By wrapping and checking each line, DSS-Python can then plot the resulting .DSV. Note that the commands need be run through the notebook directly, running a .DSS script that calls plot commands would not work." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "72a18ecf-0733-4031-bfe9-726208fc0d7a", + "metadata": {}, + "outputs": [], + "source": [ + "%%dss\n", + "redirect electricdss-tst/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss\n", + "solve\n", + "visualize powers Transformer.xfm1" + ] + }, + { + "cell_type": "markdown", + "id": "2a9e7bd0-6546-4de5-8340-190acf4bec7f", + "metadata": {}, + "source": [ + "Of course, users can provide saved .DSVs instead of recreating them. For example, let's check where the result from one of the `visualize` commands:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2b9b2da2-15ec-4fee-b4e2-db558bb1cdff", + "metadata": {}, + "outputs": [], + "source": [ + "dss('visualize powers Transformer.xfm1')\n", + "dsv_fn = dss.Text.Result\n", + "print(dsv_fn)" + ] + }, + { + "cell_type": "markdown", + "id": "f0bbd70f-947e-421d-b2ea-0828d48f60fa", + "metadata": {}, + "source": [ + "If this `IEEE13Nodeckt_Transformer_xfm1_PQ.DSV` was archived, the user can them plot it directly, without running any simulation:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1839947b-a595-4651-a85b-adc0d1c42116", + "metadata": {}, + "outputs": [], + "source": [ + "plot.dsv(dsv_fn)" + ] + }, + { + "cell_type": "markdown", + "id": "ce4ce0aa-cee7-473c-a16b-078a0bde52df", + "metadata": {}, + "source": [ + "One of the reasons to load the DSV on DSS-Python would be to use matplotlib's capabilities to manipulate the figure. For this, just like with the other examples, just disable the automatic calls to `plt.show` (see [`matplotlib.pyplot.show`](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.show.html)):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c3c2a11-e8f4-4a48-b51b-3f5f6eb44570", + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import pyplot as plt\n", + "plot.dsv(dsv_fn, show=False)\n", + "plt.annotate(\n", + " \"Look here!\",\n", + " xy=(0.2, 0.68),\n", + " xycoords='figure fraction',\n", + " xytext=(0.2, 0.75),\n", + " arrowprops=dict(width=2, headwidth=8),\n", + " fontweight='bold',\n", + " ha='center',\n", + " color='red'\n", + ")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ac60c5c1-86d3-44e1-94dd-e4e06821f228", + "metadata": {}, + "source": [ + "# Experimental plotting API\n", + "\n", + "The code for the plotting tools has been refactored. A new class (`DSSPlotter`) is available and AltDSS-specific code was ported back to the classic OpenDSS API. Consequently, the new plotting tools can be used with all DSS engines (AltDSS, OpenDSS, and OpenDSS-C), either via DSS-Python/OpenDSSdirect.py **and** through EPRI's OpenDSS COM engine.\n", + "\n", + "That is, besides the previously mentioned .DSV tools, the new plotting API is an alternative. The plotting API does not use DSS commands, instead relying in Python calls instead.\n", + "\n", + "The method arguments are very close to the arguments passed to the DSS `plot` command. The arguments might change in a future release as the plotting API is finalized (i.e., exit the experimental stage)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd6ee5a1-01bc-4343-9fe2-b8bb9fafb4b6", + "metadata": {}, + "outputs": [], + "source": [ + "from dss import dss\n", + "dss('redirect electricdss-tst/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4d46c61-6d31-428f-83b0-490dd58dacef", + "metadata": {}, + "outputs": [], + "source": [ + "# Grab the default instance\n", + "plotter = dss.Plotting\n", + "\n", + "? plotter.circuit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "676488b6-dd2a-427e-a911-0c9f01f8d121", + "metadata": {}, + "outputs": [], + "source": [ + "plotter.circuit(Labels=True, Color1='red')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "514b7078-f0fb-4827-ace8-3c8b703079e4", + "metadata": {}, + "outputs": [], + "source": [ + "plotter.loadshape(ObjectName='default')" + ] + }, + { + "cell_type": "markdown", + "id": "37b39842-0625-4702-ab1f-406e981afa0c", + "metadata": {}, + "source": [ + "## Handling multiple engines and DSS contexts" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1dbf765-98b3-44cf-b8f7-dc99055beeea", + "metadata": {}, + "outputs": [], + "source": [ + "# Load the main AltDSS engine context\n", + "from dss import dss\n", + "plotter = dss.Plotting\n", + "dss.ClearAll()\n", + "dss.NewCircuit('circ1')\n", + "\n", + "# Create a new AltDSS context\n", + "dss2 = dss.NewContext()\n", + "dss2.ClearAll()\n", + "dss2.NewCircuit('circ2')\n", + "plotter2 = dss2.Plotting\n", + "dss2.ActiveCircuit.LoadShapes.Name = 'default'\n", + "dss2.ActiveCircuit.LoadShapes.Pmult = dss2.ActiveCircuit.LoadShapes.Pmult * 2\n", + "\n", + "# Create another context, this time loading EPRI's OpenDSS-C library\n", + "from dss import IOddieDSS\n", + "dss3 = IOddieDSS('libOpenDSSC.so') # reminder: you might need the full path to the library/DLL here\n", + "dss3.ClearAll()\n", + "dss3.AllowForms = False\n", + "dss3.NewCircuit('circ3')\n", + "plotter3 = dss3.Plotting\n", + "dss3.ActiveCircuit.LoadShapes.Name = 'default'\n", + "dss3.ActiveCircuit.LoadShapes.Pmult = dss3.ActiveCircuit.LoadShapes.Pmult * 3\n", + "\n", + "# Notice how each \"default\" LoadShape has different scales (1, 2, 3), as expected.\n", + "plotter.loadshape(ObjectName='default')\n", + "plotter2.loadshape(ObjectName='default')\n", + "plotter3.loadshape(ObjectName='default')" + ] } ], "metadata": { @@ -1515,7 +1780,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.13.12" } }, "nbformat": 4, diff --git a/docs/examples/UserModels/PyIndMach012/PyIndMach012.py b/docs/examples/UserModels/PyIndMach012/PyIndMach012.py index ee72464e..0cc28914 100644 --- a/docs/examples/UserModels/PyIndMach012/PyIndMach012.py +++ b/docs/examples/UserModels/PyIndMach012/PyIndMach012.py @@ -1,7 +1,7 @@ ''' A `dss_python` User-Model implementation of the IndMach012 Generator model from OpenDSS. -Based on the following files from the official OpenDSS source code: +Based on the following files from EPRI's OpenDSS source code: - Source/PCElements/IndMach012.pas - Source/IndMach012a/IndMach012Model.pas @@ -9,7 +9,7 @@ Original code by EPRI, licensed under the 3-clause BSD. See OPENDSS_LICENSE. This sample code doesn't interact with the main OpenDSS interface directly, -it only uses the user-model interface. Thus, it is compatible with the official OpenDSS +it only uses the user-model interface. Thus, it is compatible with EPRI's OpenDSS distribution as well as DSS-Python. Note that OpenDSS version 7 has a bug on 64-bit systems and user-models most likely won't run via COM. diff --git a/docs/examples/UserModels/PyIndMach012/README.ipynb b/docs/examples/UserModels/PyIndMach012/README.ipynb index 1f16a127..9c73f2f3 100644 --- a/docs/examples/UserModels/PyIndMach012/README.ipynb +++ b/docs/examples/UserModels/PyIndMach012/README.ipynb @@ -7,11 +7,17 @@ "source": [ "# PyIndMach012: an example of user-model using DSS-Python\n", "\n", + "*Now also contains example usage of a C++ user-model, CppIndMach012*\n", + "\n", "This example runs a modified example from the OpenDSS distribution for the induction machine model with a sample PyIndMach012 implementation, written in Python, and the original, built-in IndMach012.\n", "\n", - "Check the `PyIndMach012.py` file for more comments. Comparing it to [the Pascal code for IndMach012](https://github.com/dss-extensions/dss_capi/blob/master/src/PCElements/IndMach012.pas) can be useful to understand some of the inner workings of OpenDSS.\n", + "Check the file named `PyIndMach012.py` for more comments. Comparing it to [the Pascal code for IndMach012](https://github.com/dss-extensions/dss_capi/blob/master/src/PCElements/IndMach012.pas) can be useful to understand some of the inner workings of OpenDSS.\n", + "\n", + "The user-model code in DSS-Python was supposed to grow other features, but the effort hasn't been continued for many reasons. The code for generator user-models has been stable for many releases. We believe it can be used to develop new ideas before committing the final model in a traditional DLL user-model. Many users opt to use Delphi to build user-models since the original OpenDSS code uses Delphi and there is the IndMach012a user-model example in the codebase.\n", "\n", - "The user-model code in DSS-Python was supposed to grow other features, but the effort hasn't been continued for many reasons. The code for generator user-models has been stable for many releases. We believe it can be used to develop new ideas before committing the final model in a traditional DLL user-model." + "Acquiring and installing Delphi, and learning Object Pascal can be extra hurdles. In 2024, to also illustrate an alternative approach using C++ instead of Python or Delphi, a working example (\"CppIndMach012\") was added to the AltDSS/DSS C-API repository. Note that the basic C headers were added back in 2018. \"CppIndMach012\" is prepared to build DLLs for multiple DSS engines, including AltDSS and various versions of OpenDSS, since the DLLs are not backward/forward compatible across versions. The CppIndMach012 code is available at https://github.com/dss-extensions/dss_capi/tree/master/examples/UserModels\n", + "\n", + "If either DSS-Python, PyIndMach012 or CppIndMach012 was useful for your research, please cite the relevant work. All code here is available according to the BSD-3 licenses listed on the main DSS C-API and DSS-Python repositories, also listed at https://dss-extensions.org/licenses" ] }, { @@ -22,27 +28,94 @@ "The outputs for this notebook should be listed below, but you can also open and then run this notebook on Google Colab for a quick overview if you don't want to set up a local environment: **[Open in Colab](https://colab.research.google.com/github/dss-extensions/dss_python/blob/master/docs/examples/UserModels/PyIndMach012/README.ipynb)**. The next cell installs DSS-Python and dependencies, and downloads the extra files." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "----\n", + "\n", + "## \"Do I need a user-model?\"\n", + "\n", + "Since the DSS engine can be interfaced and integrated through several means, user-models is just one of the alternatives.\n", + "\n", + "If you don't need the features supported by user-models, you could consider writing a custom solution loop, for example.\n", + "\n", + "## Changes\n", + "\n", + "- 2024-07: \n", + " - `GenUserModel.dll_path` became `GenUserModel(DSS).dll_path`. Pass the target DSS instance and it will try to select the appropriate DLL according to the DSS version. It should support AltDSS (the engine from DSS-Extensions), OpenDSS versions 7, 8, 9 and 10.\n", + " - When running through Google Colab, the user-model `CppIndMach012` is added to the comparison. A few commands are executed to install the required packages and build the shared objects (DLLs).\n", + " - On Windows: the examples work with our own engine (AltDSS), the official OpenDSS COM DLL, and the official OpenDSSDirect.DLL/DCSL implementation (using our Oddie project in DSS-Python).\n", + "----" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*The following cell will run when running on Google Colab.*" + ] + }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os, subprocess\n", + "has_cpp_model = False\n", "if os.getenv(\"COLAB_RELEASE_TAG\"):\n", + " # Install DSS-Python, download the code for Python user-model, and the sample .DSS script\n", " import urllib.request\n", " print(subprocess.check_output('pip install dss-python[plot]', shell=True).decode())\n", " urllib.request.urlretrieve(\"https://raw.githubusercontent.com/dss-extensions/dss_python/master/docs/examples/UserModels/PyIndMach012/PyIndMach012.py\", \"PyIndMach012.py\")\n", - " urllib.request.urlretrieve(\"https://raw.githubusercontent.com/dss-extensions/dss_python/master/docs/examples/UserModels/PyIndMach012/master.dss\", \"master.dss\")\n" + " urllib.request.urlretrieve(\"https://raw.githubusercontent.com/dss-extensions/dss_python/master/docs/examples/UserModels/PyIndMach012/master.dss\", \"master.dss\")\n", + "\n", + " # Try building the C++ user-model too\n", + " try:\n", + " print(subprocess.check_output('git clone --depth=1 --quiet https://github.com/dss-extensions/dss_capi', shell=True).decode())\n", + " print(subprocess.check_output('apt-get -qq update', stderr=subprocess.STDOUT, shell=True).decode())\n", + " print(subprocess.check_output('apt-get -qq install -y libeigen3-dev', stderr=subprocess.STDOUT, shell=True).decode())\n", + " print(subprocess.check_output('cmake dss_capi/examples/UserModels -B build -DCMAKE_BUILD_TYPE=Release', shell=True).decode())\n", + " print(subprocess.check_output('cmake --build build --config=Release', stderr=subprocess.STDOUT, shell=True).decode())\n", + " has_cpp_model = True\n", + " cpp_model_dir = \"build\"\n", + " except subprocess.CalledProcessError as exc:\n", + " print(\"Failed to prepare CppIndMach012:\", exc.output) \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*When running on a local Python installation, it could be easier to build externally and set the path like in the commented code in the next cell.*" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# has_cpp_model = True\n", + "# cpp_model_dir = \"/tmp/dss_capi/examples/UserModels/build\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "*General imports for the example itself*" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "import os\n", + "import os, sys\n", + "from time import perf_counter\n", "import numpy as np\n", "from matplotlib import pyplot as plt\n", "from dss.UserModels import GenUserModel # used to get the DLL path\n", @@ -61,405 +134,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Run `??PyIndMach012` to see the code of the class, or open `PyIndMach012.py` in an editor." + "Run `??PyIndMach012` to see the code of the class, or open `PyIndMach012.py` in an editor. On Colab, JupyterLab and other similar environments, the file should be listed in the sidebar to the left." ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "'''\n", - "A `dss_python` User-Model implementation of the IndMach012 Generator model from OpenDSS.\n", - "\n", - "Based on the following files from the official OpenDSS source code:\n", - "- Source/PCElements/IndMach012.pas\n", - "- Source/IndMach012a/IndMach012Model.pas\n", - "\n", - "This Python version was written by Paulo Meira. \n", - "Original code by EPRI, licensed under the 3-clause BSD. See OPENDSS_LICENSE.\n", - "\n", - "This sample code doesn't interact with the main OpenDSS interface directly,\n", - "it only uses the user-model interface. Thus, it is compatible with the official OpenDSS\n", - "distribution as well as DSS-Python. Note that OpenDSS version 7 has a bug on 64-bit \n", - "systems and user-models most likely won't run via COM.\n", - "\n", - "Recent version of OpenDSS 8 also present a bug when handling the editing of \n", - "user-model parameters after the creation of the generator. You can, of course,\n", - "edit the data in Python if you desire.\n", - "\n", - "'''\n", - "from dss.UserModels import GenUserModel\n", - "from dss.enums import SolveModes\n", - "import numpy as np\n", - "\n", - "# This is the user-model we'll use as a base\n", - "Base = GenUserModel.Base\n", - "\n", - "# Symmetrical component transformation matrices\n", - "a = np.exp(1j * 2 * np.pi/3)\n", - "aa = np.exp(1j * 4 * np.pi/3)\n", - "\n", - "Ap2s = np.array([\n", - " [1, 1, 1], \n", - " [1, a, aa],\n", - " [1, aa, a]\n", - "]) / 3.0\n", - "\n", - "As2p = np.array([\n", - " [1, 1, 1], \n", - " [1, aa, a],\n", - " [1, a, aa]\n", - "])\n", - "\n", - "\n", - "@GenUserModel.register # The class needs to be registered\n", - "class PyIndMach012(Base):\n", - " '''\n", - " A Python User-Model implementation of the IndMach012 Generator model for OpenDSS \n", - " '''\n", - " def __init__(self, gen, dyn, callbacks):\n", - " '''\n", - " Initialize the model object instance\n", - " Note: OpenDSS calls `Init` from the UserModel DLL later,\n", - " which calls our `init_state_vars`\n", - " '''\n", - " \n", - " Base.__init__(self, gen, dyn, callbacks)\n", - " \n", - " # You can list the DSS model inputs and default values like this.\n", - " # The UserModel wrapper will create attributes in the instance with\n", - " # the default values and update them later when the UserModel\n", - " # `Edit` function is called.\n", - " self.add_inputs(\n", - " ('H', 0.02),\n", - " ('D', 0.02),\n", - " ('puRs', 0.0053),\n", - " ('puXs', 0.106),\n", - " ('puRr', 0.007),\n", - " ('puXr', 0.12),\n", - " ('puXm', 4.0),\n", - " ('slip', 0.007),\n", - " ('MaxSlip', 0.1),\n", - " ('slipOption', 'variable')\n", - " )\n", - " \n", - " # The outputs can be any variable or Python property, i.e. it can be an \n", - " # input, state variable, property, etc., as long as it is available in\n", - " # the model class\n", - " self.add_outputs(\n", - " 'Slip', # The current slip (`slip` is the DSS input param)\n", - " \n", - " # There don't need to be in the output (they're constant) but are listed \n", - " # in IndMach012.pas -- most likely to debug\n", - " 'puRs',\n", - " 'puXs',\n", - " 'puRr',\n", - " 'puXr',\n", - " 'puXm',\n", - " 'MaxSlip',\n", - " \n", - " # complex variables like these are exported as their absolute values\n", - " 'Is1', \n", - " 'Is2',\n", - " 'Ir1',\n", - " 'Ir2',\n", - " \n", - " # Some properties to mimic the Pascal version\n", - " 'E1_pu',\n", - " 'StatorLosses',\n", - " 'RotorLosses',\n", - " 'ShaftPower_hp',\n", - " 'PowerFactor',\n", - " 'Efficiency_pct'\n", - " )\n", - " \n", - " # These are the state variables. DSS-Python will automatically\n", - " # setup auxiliary variables such as dE1_dt, dE1_dtn, E1n used in\n", - " # the solution process\n", - " self.add_state_vars(\n", - " 'E1', 'E2'\n", - " )\n", - " \n", - " # For some advanced usage, we need some CFFI code.\n", - " # We plan to add a simple wrapper to the callback interface.\n", - " # While writing this, I noticed that there were some changes in \n", - " # OpenDSS version 8 that introduce an extra ActorID parameter in \n", - " # many of the callback functions. This means that we cannot write\n", - " # a user-model that is compatible with both versions. \n", - " # An issue ticket was created to track this at:\n", - " # \n", - " # self.char_buffer = self.ffi.new('char[1024]')\n", - " # self.callbacks.GetActiveElementName(self.char_buffer, 1024)\n", - " # self.element_name = self.ffi.string(self.char_buffer)#.decode('ascii')\n", - " \n", - " # This one is used for the Power property, left as an example\n", - " # self.double_buffer = self.ffi.new('double[2]')\n", - "\n", - " # Update other variables that depend on the input parameters\n", - " self.update()\n", - "\n", - "\n", - " def init_state_vars(self, Vabc, Iabc):\n", - " '''\n", - " Initialize state variables (dynamics mode), equivalent to\n", - " TIndMach012Obj.InitStateVars\n", - " '''\n", - "\n", - " V012 = np.dot(Ap2s, Vabc)\n", - " I012 = np.dot(Ap2s, Iabc)\n", - " \n", - " # The following is done in TIndMach012Obj.InitModel:\n", - " # Compute Voltage behind transient reactance and set derivatives to zero\n", - " self.E1 = V012[1] - I012[1] * self.Zsp\n", - " self.dE1dt = 0\n", - " self.E2 = V012[2] - I012[2] * self.Zsp\n", - " self.dE2dt = 0\n", - "\n", - " # Copy the current state to the previous state\n", - " self.copy_state()\n", - " \n", - " # Initial rotor speed\n", - " self.gen.Speed = -self.S1 * self.gen.w0\n", - " self.gen.dSpeed = 0\n", - " self.gen.Theta = np.angle(self.E1) # overwrite Theta\n", - " self.gen.dTheta = 0\n", - "\n", - "\n", - " def integrate(self):\n", - " '''\n", - " Equivalent to TIndMach012Obj.Integrate\n", - " '''\n", - " if self.dyn.IterationFlag == 0: \n", - " # First iteration of new time step, copy the previous state\n", - " # to be used in the integration process\n", - " self.copy_state()\n", - " \n", - " # Some copies to reduce `self.` spam\n", - " gen = self.gen\n", - " w0 = gen.w0\n", - " S1, S2 = self.S1, self.S2\n", - " E1, E2 = self.E1, self.E2\n", - " Is1, Is2 = self.Is1, self.Is2\n", - " T0p = self.T0p\n", - " Xopen, Xp = self.Xopen, self.Xp\n", - " \n", - " # Derivative of E\n", - " self.dE1_dt = (1j * -w0 * S1 * E1) - ((E1 - 1j * (Xopen - Xp) * Is1) / T0p)\n", - " self.dE2_dt = (1j * -w0 * S2 * E2) - ((E2 - 1j * (Xopen - Xp) * Is2) / T0p)\n", - " \n", - " # Trapezoidal Integration\n", - " Base.integrate(self)\n", - " \n", - " \n", - " def update(self): \n", - " '''\n", - " Propagate changes from the input parameters to the model.\n", - " \n", - " Equivalent to part of TIndMach012Obj.RecalcElementData\n", - " '''\n", - " \n", - " gen = self.gen\n", - " \n", - " self._set_local_slip(self.slip)\n", - " \n", - " # make generator speed agree\n", - " gen.Speed = -self.S1 * self.gen.w0\n", - " self.gen.dSpeed = 0.0\n", - " \n", - " self.fixed_slip = (self.slipOption) and (self.slipOption[0].upper() == 'F')\n", - " self.first_iteration = True\n", - "\n", - " ZBase = 1000.0 * (gen.kVGeneratorBase**2 / gen.kVArating)\n", - " \n", - " Rs = self.puRs * ZBase\n", - " Xs = self.puXs * ZBase\n", - " Rr = self.puRr * ZBase\n", - " Xr = self.puXr * ZBase\n", - " Xm = self.puXm * ZBase\n", - " \n", - " self.Zs = complex(Rs, Xs)\n", - " self.Zm = complex(0, Xm)\n", - " self.Zr = complex(Rr, Xr)\n", - " \n", - " self.Xopen = Xs + Xm\n", - " self.Xp = Xs + (Xr * Xm) / (Xr + Xm)\n", - " self.Zsp = complex(Rs, self.Xp)\n", - " self.T0p = (Xr + Xm) / (gen.w0 * Rr)\n", - " # self.Zrsc = self.Zr + (self.Zs * self.Zm) / (self.Zs + selfg.Zm)\n", - " \n", - " # Init dSdP based on rated slip and rated voltage\n", - " self.V1 = complex(gen.kVGeneratorBase * 1000.0/np.sqrt(3))\n", - " if self.S1 != 0:\n", - " self.Is1, self.Ir1 = self._pfmodel_current(self.V1, self.S1)\n", - " \n", - " self.dSdP = self.S1/(self.V1 * np.conjugate(self.Is1)).real\n", - " \n", - " self.Is1 = complex(0)\n", - " self.V1 = complex(0)\n", - " self.Is2 = complex(0)\n", - " self.V2 = complex(0)\n", - "\n", - "\n", - " def calc(self, Vabc, Iabc):\n", - " '''\n", - " Calculate the new model state. Vabc is used as an\n", - " input, while Iabc is the ouput used in OpenDSS.\n", - " '''\n", - " \n", - " # The next version of DSS-Python should have an option to \n", - " # provide the values in 012 space to simplify the model code\n", - " V012 = np.dot(Ap2s, Vabc)\n", - " I012 = np.dot(Ap2s, Iabc)\n", - " \n", - " if self.dyn.SolutionMode == SolveModes.Dynamic:\n", - " self.calc_dynamic(V012, I012)\n", - " else:\n", - " self.calc_power_flow(V012, I012)\n", - "\n", - " Iabc[:] = iabc = np.dot(As2p, I012)\n", - " \n", - " # We can get the current total power here, or we can use \n", - " # the power property below \n", - " self.Power = sum(np.asarray(Vabc) * iabc.conj())\n", - "\n", - "\n", - " # @property\n", - " # def Power(self):\n", - " # '''\n", - " # This is an example of callback usage, returning the power of the \n", - " # element. Note that we don't really need this here since we can \n", - " # calculate the power in the calc function.\n", - " # '''\n", - " # cmd = b'select %s' % (self.element_name)\n", - " # self.callbacks.DoDSSCommand(cmd, len(cmd) + 1)\n", - " # self.callbacks.GetActiveElementPower(1, self.double_buffer)\n", - " # return complex(self.double_buffer[0], self.double_buffer[1])\n", - "\n", - "\n", - " def calc_dynamic(self, V012, I012):\n", - " '''Equivalent to TIndMach012Obj.CalcDynamic'''\n", - " \n", - " # In dynamics mode, slip is allowed to vary\n", - " \n", - " # Copy some values to local variables\n", - " V1, V2 = self.V1, self.V2 = V012[1], V012[2]\n", - " E1, E2 = self.E1, self.E2\n", - " Zsp, Zm = self.Zsp, self.Zm\n", - " \n", - " # Gets slip from shaft speed\n", - " self._set_local_slip(-self.gen.Speed / self.gen.w0)\n", - " \n", - " # The stator and rotor currents from the Pascal code are \n", - " # computed in TIndMach012Obj.Get_DynamicModelCurrent\n", - "\n", - " # Stator current\n", - " self.Is1 = (V1 - E1) / self.Zsp\n", - " self.Is2 = (V2 - E2) / self.Zsp\n", - "\n", - " # Rotor current\n", - " self.Ir1 = self.Is1 - (V1 - self.Is1 * Zsp) / Zm\n", - " self.Ir2 = self.Is2 - (V2 - self.Is2 * Zsp) / Zm \n", - " \n", - " I012[:] = complex(0.0, 0.0), self.Is1, self.Is2\n", - " \n", - "\n", - " def calc_power_flow(self, V012, I012):\n", - " '''Equivalent to TIndMach012Obj.CalcPFlow'''\n", - " \n", - " self.V1, self.V2 = V012[1], V012[2]\n", - " \n", - " if self.first_iteration:\n", - " # Initialize Is1\n", - " self.Is1, self.Ir1 = self._pfmodel_current(self.V1, self.S1)\n", - "\n", - "\n", - " # If fixed slip option set, then use the value set by the user\n", - " if not self.fixed_slip:\n", - " P_Error = self.gen.PNominalPerPhase - (self.V1 * self.Is1.conjugate()).real\n", - " \n", - " # make new guess at slip\n", - " self._set_local_slip(self.S1 + self.dSdP * P_Error)\n", - "\n", - " \n", - " self.Is1, self.Ir1 = self._pfmodel_current(self.V1, self.S1)\n", - " self.Is2, self.Ir2 = self._pfmodel_current(self.V2, self.S2)\n", - " \n", - " I012[:] = complex(0.0, 0.0), self.Is1, self.Is2\n", - "\n", - " \n", - " def _pfmodel_current(self, V, s, show=False):\n", - " '''Equivalent to TIndMach012Obj.Get_PFlowModelCurrent'''\n", - " \n", - " if s != 0.0:\n", - " RL = self.Zr.real * (1 - s) / s\n", - " else:\n", - " RL = self.Zr.real * 1.0e6\n", - " \n", - " Zrotor = RL + self.Zr\n", - " Zmotor = self.Zs + (Zrotor * self.Zm) / (Zrotor + self.Zm)\n", - " Istator = V / Zmotor\n", - " Irotor = Istator - (V - self.Zs * Istator) / self.Zm\n", - " \n", - " return Istator, Irotor\n", - "\n", - "\n", - " def _set_local_slip(self, value):\n", - " '''Equivalent to TIndMach012Obj.set_Localslip'''\n", - " \n", - " self.S1 = value\n", - " if self.dyn.SolutionMode != SolveModes.Dynamic:\n", - " # Put limits on the slip unless dynamics\n", - " if abs(self.S1) > self.MaxSlip:\n", - " self.S1 = np.sign(self.S1) * self.MaxSlip \n", - " \n", - " self.S2 = 2 - self.S1\n", - "\n", - " \n", - " # The following are properties to emulate the model outputs from \n", - " # the Pascal version of built-in IndMach012 \n", - " \n", - " @property\n", - " def E1_pu(self):\n", - " return np.sqrt(3) * abs(self.E1) / (1000 * self.gen.kVGeneratorBase)\n", - "\n", - " @property\n", - " def Slip(self):\n", - " return self.S1\n", - "\n", - " @property\n", - " def RotorLosses(self):\n", - " Ir1, Ir2, Zr = self.Ir1, self.Ir2, self.Zr\n", - " return 3 * (Ir1.real**2 + Ir1.imag**2 + Ir2.real**2 + Ir2.imag**2) * Zr.real\n", - " \n", - " @property\n", - " def StatorLosses(self):\n", - " Is1, Is2, Zs = self.Is1, self.Is2, self.Zs\n", - " return 3 * (Is1.real**2 + Is1.imag**2 + Is2.real**2 + Is2.imag**2) * Zs.real\n", - " \n", - " @property\n", - " def PowerFactor(self):\n", - " power = self.Power\n", - " return np.sign(power.imag) * power.real / abs(power)\n", - " \n", - " @property\n", - " def Efficiency_pct(self):\n", - " power = self.Power\n", - " return np.clip((1 - (self.StatorLosses + self.RotorLosses) / power.real) * 100, 0, 100)\n", - " \n", - " @property\n", - " def ShaftPower_hp(self):\n", - " Ir1, Ir2, Zr, S1, S2 = self.Ir1, self.Ir2, self.Zr, self.S1, self.S2\n", - " return (3.0/746) * (abs(Ir1)**2 * (1 - S1) / S1 + abs(Ir2)**2 * (1 - S2)/S2) * Zr.real\n", - "\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "with open('PyIndMach012.py', 'r') as f:\n", " src = f.read()\n", @@ -480,43 +162,45 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "For this example, we can use either COM or DSS-Python (DSS C-API)." + "In this example, we can use a OpenDSS engine in three implementations. Set `DSS_ENGINE` accordingly (default is our AltDSS engine):\n", + "\n", + "- `ALT`: : Using DSS-Python, coupled with the AltDSS/DSS C-API engine from DSS-Extensions.\n", + "- `COM`: Using the official OpenDSS COM implementation; must be previously installed/registered. Here we use `comtypes`, which is currently the more performant alternative (win32com is slower in several functions).\n", + "- `ODD.DLL`: Using DSS-Python, coupled with the official (or a custom but compatible) `OpenDSSDirect.DLL` distribution (a.k.a. DCSL). If it is installed in `C:\\Program Files\\OpenDSS\\x64\\OpenDSSDirect.DLL`, it should be picked automatically (otherwise, adjust below). For this alternative, the DLL is wrapped using the AltDSS Oddie wrapper. More at https://github.com/dss-extensions/dss_capi/tree/master/src/altdss_oddie\n" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'DSS C-API Library version DEV revision UNKNOWN based on OpenDSS SVN UNKNOWN [FPC 3.2.2] (64-bit build) MVMULT INCREMENTAL_Y CONTEXT_API PM 20240209064406; License Status: Open \\nDSS-Python version: 0.15.0rc1'" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "original_dir = os.getcwd() # same the original working directory since the COM module messes with it\n", - "USE_COM = False # toggle this value to run with DSS C-API or COM\n", - "if USE_COM:\n", + "DSS_ENGINE = 'ALT'\n", + "# DSS_ENGINE = 'COM'\n", + "# DSS_ENGINE = 'ODD.DLL'\n", + "\n", + "original_dir = os.getcwd() # same the original working directory since the COM/ODD.DLL module messes with it\n", + "if DSS_ENGINE == 'COM':\n", " from dss import patch_dss_com\n", " import comtypes.client\n", " DSS = patch_dss_com(comtypes.client.CreateObject('OpenDSSengine.DSS'))\n", " DSS.DataPath = original_dir\n", + " DSS.AllowForms = False # disable progress form\n", " os.chdir(original_dir)\n", - "else:\n", + "elif DSS_ENGINE == 'ODD.DLL':\n", + " from dss import IOddieDSS\n", + " DSS = IOddieDSS()\n", + " os.chdir(original_dir)\n", + "elif DSS_ENGINE == 'ALT':\n", " from dss import DSS\n", - " \n", - "DSS.Version " + "\n", + "print(\"Engine selection:\", DSS_ENGINE)\n", + "print(DSS.Version)" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -540,17 +224,49 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "def run(pymodel):\n", + "def run(model):\n", " Text.Command = 'redirect \"master.dss\"'\n", "\n", - " if pymodel:\n", + " if model == 'py':\n", " # This uses our custom user-model in Python\n", " Text.Command = 'New \"Generator.Motor1\" bus1=Bg2 kW=1200 conn=delta kVA=1500.000 H=6 model=6 kv=0.48 D=0 usermodel=\"{dll_path}\" userdata=(pymodel=PyIndMach012 purs=0.048 puxs=0.075 purr=0.018 puxr=0.12 puxm=3.8 slip=0.02 SlipOption=variableslip)'.format(\n", - " dll_path=GenUserModel.dll_path,\n", + " dll_path=GenUserModel(DSS).dll_path,\n", + " )\n", + " Text.Command = 'New \"Monitor.mfr2\" element=Generator.Motor1 terminal=1 mode=3'\n", + " elif model == 'cpp':\n", + " # Select the suffix according to the engine version we're running\n", + " ver = DSS.Version\n", + " if 'DSS C-API Library' in ver:\n", + " suffix = 'AltDSS'\n", + " elif ver.startswith('Version 9.') or ver.startswith('Version 8.'):\n", + " suffix = 'OpenDSSv8v9'\n", + " elif ver.startswith('Version 7.'):\n", + " suffix = 'OpenDSSv7'\n", + " else:\n", + " if not ver.startswith('Version 10.'):\n", + " print('Assuming compatibility with OpenDSS v10.0')\n", + "\n", + " suffix = 'OpenDSSv10'\n", + "\n", + " # Try to adjust the name according to common conventions\n", + " dll_ext = 'so' # default to .so\n", + " dll_prefix = 'lib'\n", + " if sys.platform == 'win32':\n", + " dll_ext = 'dll'\n", + " dll_prefix = ''\n", + " elif sys.platform == 'darwin':\n", + " dll_ext = 'dylib'\n", + " dll_prefix = 'lib'\n", + " \n", + " # This uses the custom user-model in C++.\n", + " # If you built the C++ model yourself, remember to update the file path\n", + " # as a whole (path and basename).\n", + " Text.Command = 'New \"Generator.Motor1\" bus1=Bg2 kW=1200 conn=delta kVA=1500.000 H=6 model=6 kv=0.48 D=0 usermodel=\"{dll_path}\" userdata=(purs=0.048 puxs=0.075 purr=0.018 puxr=0.12 puxm=3.8 slip=0.02 SlipOption=variableslip)'.format(\n", + " dll_path=f'{cpp_model_dir}/{dll_prefix}CppIndMach012_{suffix}.{dll_ext}',\n", " )\n", " Text.Command = 'New \"Monitor.mfr2\" element=Generator.Motor1 terminal=1 mode=3'\n", " else:\n", @@ -565,20 +281,23 @@ " Text.Command = 'Set mode=dynamics number=1 h=0.000166667'\n", " \n", " # And finally run 5000 steps for the dynamic simulation\n", - " Text.Command = f'Solve number=5000'\n", - " " + " t0 = perf_counter() \n", + " Text.Command = 'Solve number=5000'\n", + " print(model, perf_counter() - t0)" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# There are the channels from the Pascal/built-in IndMach012\n", + "# These are the channels from the Pascal/built-in IndMach012\n", "channels_pas = ('Frequency', 'Theta (deg)', 'E1', 'dSpeed (deg/sec)', 'dTheta (deg)', 'Slip', 'Is1', 'Is2', 'Ir1', 'Ir2', 'Stator Losses', 'Rotor Losses', 'Shaft Power (hp)', 'Power Factor', 'Efficiency (%)')\n", "\n", - "# There are the channels from the Python module -- we define part of these and part come from the generator model itself\n", + "# These are the channels from the Python module.\n", + "# We define part of these and part come from the generator model itself.\n", + "# If CppIndMach012 is available, it also uses the same channel names.\n", "channels_py = ('Frequency', 'Theta (Deg)', 'E1_pu', 'dSpeed (Deg/sec)', 'dTheta (Deg)', 'Slip', 'Is1', 'Is2', 'Ir1', 'Ir2', 'StatorLosses', 'RotorLosses', 'ShaftPower_hp', 'PowerFactor', 'Efficiency_pct')" ] }, @@ -595,25 +314,35 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Let's run the Pascal/built-in version of IndMach012 and our custom Python version for comparison:" + "Let's run the Pascal/built-in version of IndMach012 and our custom Python version for comparison. The `run` function also outputs the time it takes to run the 5000 time steps. As expected, the Python version has a lot of overhead, but it doesn't require a compiler and is easier to iterate. The C++ version of the user-model should result in a runtime closer to that of the internal IndMach012 model.\n", + "\n", + "The circuit being tested here is tiny. The performance of these alternatives in real-world scenarios will depend on the number of instances of user-model being used and how big and complicated the base circuit is. That is, be sure to evaluate these according to your needs." ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "run(False)\n", + "run(model='built-in')\n", "Monitors.Name = 'mfr2'\n", "header = [x.strip() for x in Monitors.Header]\n", "outputs_pas = {channel: Monitors.Channel(header.index(channel) + 1) for channel in channels_pas}\n", "\n", - "run(True)\n", + "run(model='py')\n", "Monitors.Name = 'mfr2'\n", "header = [x.strip() for x in Monitors.Header]\n", "outputs_py = {channel: Monitors.Channel(header.index(channel) + 1) for channel in channels_py}\n", "\n", + "if has_cpp_model:\n", + " run(model='cpp')\n", + " Monitors.Name = 'mfr2'\n", + " header = [x.strip() for x in Monitors.Header]\n", + " outputs_cpp = {channel: Monitors.Channel(header.index(channel) + 1) for channel in channels_py}\n", + "else:\n", + " outputs_cpp = {}\n", + "\n", "time = np.arange(1, 5000 + 1) * 0.000166667\n", "offset = int(0.1 / 0.000166667)" ] @@ -638,165 +367,17 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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bGVlZdoLN9y/f18700kGKCsDnl4M4/59gDUlIrJqTz/vvsyC0pLgstN6y8nJQePGjS0dg4iIiIjILPLz8+Hv7//CMTYxY2FtPDw8AABXr16Fq6urhdPYvqcnw+fn52sPW6PqYz2Ni/U0PtbUuFhP42I9jY81NS5z11MIgQcPHuisFVcVNhbVIJNVnJri6urKXxAjcnFxYT2NiPU0LtbT+FhT42I9jYv1ND7W1LjMWc+X/Ue6TZy8TURERERE1o2NBRERERERGYyNRTXY29tj7ty5XNvCSFhP42I9jYv1ND7W1LhYT+NiPY2PNTUua64nrwpFREREREQG44wFEREREREZjI0FEREREREZjI0FEREREREZjI1FJVatWoWGDRvCwcEBISEhSEtLe+H41NRUhISEwMHBAY0aNcKaNWvMlNR26FPTgoICREVFITAwEDKZDFOmTDFfUBuhTz13796NN954A97e3nBxcUHnzp3x/fffmzGt9dOnnunp6QgLC4OnpyccHR0RFBSE5cuXmzGtbdD3ffSpI0eOQKFQoG3btqYNaGP0qeehQ4cgSdJz288//2zGxNZN39dnaWkpZs+ejYCAANjb26Nx48bYtGmTmdJaP33qGR0dXenrMzg42IyJrZ++r9GkpCS0adMGTk5O8PPzw3vvvYc7d+6YKe0zBOnYvn27UCqVYv369SIrK0vExsYKZ2dnceXKlUrH5+TkCCcnJxEbGyuysrLE+vXrhVKpFDt37jRzcuulb01zc3PF5MmTxd/+9jfRtm1bERsba97AVk7fesbGxorFixeLkydPiuzsbDFz5kyhVCrF2bNnzZzcOulbz7Nnz4rk5GSRmZkpcnNzxVdffSWcnJzE2rVrzZzceulb06eKi4tFo0aNRO/evUWbNm3ME9YG6FvPgwcPCgDil19+EQUFBdpNrVabObl1qs7rs1+/fqJTp04iJSVF5ObmihMnTogjR46YMbX10reexcXFOq/L/Px84eHhIebOnWve4FZM35qmpaUJmUwmvvjiC5GTkyPS0tJEcHCwGDBggJmTC8HG4jc6duwoYmJidPYFBQWJuLi4Ssd/8MEHIigoSGffuHHjRGhoqMky2hp9a/qs8PBwNha/YUg9n2rRooWIj483djSbZIx6/ulPfxLvvvuusaPZrOrWdNCgQeLDDz8Uc+fOZWPxDH3r+bSx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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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wm+//QbAwoUL2bp1Kx9++CGNGzfmtttuY+LEiUyfPt01j2T27NmcP3+eWbNmUa9ePbp06cLw4cOZNGmSfpkUEZfAwEACAwO9HYaIiEih4XFiUbFiRaZNm5bp+FtvvUXFihXzJajcKFWqFLVr1+b999/n7Nmz2O123nrrLcqWLUuTJk2AjKSoXr16REVFueq1b9+e1NRUNmzY4CrTqlUrAgIC3MocPHiQPXv2XNVrEpHCyd/fn6FDhzJ06FD1VuTROQLZ6qzEVmclNjpreDscERHJA4+Xm3311Ve55557WLBgAc2aNQNg9erV7Ny5ky+++CLfA8wpwzBYtGgRnTt3pnjx4lgsFsqWLcsPP/xAiRIlAEhKSqJs2bJu9cLDw/H39ycpKclVJjo62q3MhTpJSUlUrlw502unpqaSmprq2vdkFS0RkWvZCUs4C503APCxow2xXo5HRERyz+Mei44dO5KYmMhdd93F8ePHOXbsGJ07dyYxMdG1rnt+GjlyJIZhXPGxfv16TNNkwIABlClThmXLlrF27Vo6d+7MnXfeyaFDh1zny2p1K9M03Y7/s8yFIVCXWxlr3LhxhIWFuR7e7LkREREREfGGXN0gr2LFiowdOza/Y8nSwIED6d69+xXLREdH89NPPzFv3jxOnDjhupfG1KlTWbRoEe+99x5Dhw4lMjKSNWvWuNU9ceIE6enprl6JyMhIV+/FBYcPHwbI1NtxwbBhw4iPj3ftJycnK7kQKcLsdjvz5s0D4M4778Rm071GRUREcvVpuGzZMt566y127drFZ599Rvny5fnggw+oXLkyN910U74GGBERQURERLblUlJSALBY3DthLBYLTqcTgNjYWMaMGcOhQ4coV64ckDGhOyAgwDUPIzY2luHDh5OWluYaO71w4UKioqIyDZG6ICAgwG1OhogUbU6nk4SEBIAC6am9lmTuIfZSICIikmceD4X64osvaN++PUFBQfzyyy+uuQWnT5++ar0YWYmNjSU8PJzevXvz66+/kpiYyNNPP83u3bu54447AGjXrh116tShV69ebNy4kR9//JHBgwfTr18/Vy9Hz549CQgIIC4uji1btjB37lzGjh1LfHx8jm8SKCJFm9VqpW3btrRt2xar1ertcHxaGY7Qp/pC+lRfyEi/97wdjoiI5IHHicVLL73EtGnTmD59On5+fq7jzZs3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p+05Z84c02azmW+88Ybbsn8nT5701iUUGp625f/+9z/zm2++MRMTE83ExERzxowZZmhoqPnss8966xIKjdz8P7+UVoW6yNO2fPXVV825c+eaiYmJ5pYtW8yhQ4eagPnFF1946xIKFU/b8/Tp02aFChXMf//73+Zvv/1mLlmyxKxevbr50EMPeesSCo3c/j+///77zRtvvPFqh1uoedqWM2fONG02mzl16lRz586d5vLly82YmBizadOmVz12JRa59MYbb5jXXXed6e/vb15//fXmkiVLXM/17t3bbNWqlVv5xYsXm40bNzb9/f3N6Oho880337zKERdenrYlkOlx3XXXXd2gCzFP2rNVq1ZZtmfv3r2vfuCFkCdtOXnyZLNu3bpmcHCwGRoaajZu3NicOnWq6XA4vBB54ePp//NLKbFw50lbvvzyy2bVqlXNwMBAMzw83LzpppvM+fPneyHqwsvTv81t27aZt912mxkUFGRWqFDBjI+PN1NSUq5y1IWTp2158uRJMygoyHz77bevcqSFn6dtOXnyZLNOnTpmUFCQWa5cOfO+++4zDxw4cJWjNk3DNK92H4mIiIiIiBQ1mmMhIiIiIiJ5psRCRERERETyTImFiIiIiIjkmRILERERERHJMyUWIiIiIiKSZ0osREREREQkz5RYiIiIiIhInimxEBERERGRPFNiISIiV9XIkSNp1KiR117/+eef5+GHH85R2cGDB/P4448XcEQiIkWD7rwtIiL5xjCMKz7fu3dv/ve//5GamkqpUqWuUlQX/fXXX1SvXp1NmzYRHR2dbfnDhw9TtWpVNm3aROXKlQs+QBERH6bEQkRE8k1SUpJr+5NPPmHEiBH88ccfrmNBQUGEhYV5IzQAxo4dy5IlS1iwYEGO69xzzz1Uq1aNl19+uQAjExHxfRoKJSIi+SYyMtL1CAsLwzCMTMf+ORQqLi6Ou+++m7Fjx1K2bFlKlCjBqFGjsNvtPP3005QsWZIKFSowY8YMt9f6888/6datG+Hh4ZQqVYrOnTuzZ8+eK8b38ccfc9ddd7kd+/zzz6lfvz5BQUGUKlWK2267jbNnz7qev+uuu/joo4/y3DYiIkWdEgsREfG6n376iYMHD7J06VImTZrEyJEjufPOOwkPD2fNmjX079+f/v37s3//fgBSUlJo06YNxYoVY+nSpSxfvpxixYpx++23k5aWluVrnDhxgi1bthATE+M6dujQIXr06EGfPn3Ytm0bixcvpkuXLlzamd+0aVP279/P3r17C7YRRER8nBILERHxupIlSzJ58mRq1qxJnz59qFmzJikpKQwfPpzq1aszbNgw/P39WbFiBZDR82CxWHjnnXeoX78+tWvXZubMmezbt4/Fixdn+Rp79+7FNE2ioqJcxw4dOoTdbqdLly5ER0dTv359BgwYQLFixVxlypcvD5Btb4iIyLXO5u0ARERE6tati8Vy8beusmXLUq9ePde+1WqlVKlSHD58GIANGzawY8cOihcv7nae8+fPs3Pnzixf49y5cwAEBga6jjVs2JBbb72V+vXr0759e9q1a8e///1vwsPDXWWCgoKAjF4SERG5PCUWIiLidX5+fm77hmFkeczpdALgdDpp0qQJs2fPznSu0qVLZ/kaERERQMaQqAtlrFYrixYtYuXKlSxcuJApU6bw7LPPsmbNGtcqUMePH7/ieUVEJIOGQomIiM+5/vrr2b59O2XKlKFatWpuj8utOlW1alVCQ0PZunWr23HDMGjRogWjRo1i48aN+Pv7M3fuXNfzW7Zswc/Pj7p16xboNYmI+DolFiIi4nPuu+8+IiIi6Ny5M8uWLWP37t0sWbKEJ554ggMHDmRZx2KxcNttt7F8+XLXsTVr1jB27FjWr1/Pvn37+PLLLzly5Ai1a9d2lVm2bBk333yza0iUiIhkTYmFiIj4nODgYJYuXUqlSpXo0qULtWvXpk+fPpw7d47Q0NDL1nv44Yf5+OOPXUOqQkNDWbp0KR07dqRGjRo899xzTJw4kQ4dOrjqfPTRR/Tr16/Ar0lExNfpBnkiInLNME2TZs2aMWjQIHr06JFt+fnz5/P000+zadMmbDZNSxQRuRL1WIiIyDXDMAzefvtt7HZ7jsqfPXuWmTNnKqkQEckB9ViIiIiIiEieqcdCRERERETyTImFiIiIiIjkmRILERERERHJMyUWIiIiIiKSZ0osREREREQkz5RYiIiIiIhInimxEBERERGRPFNiISIiIiIieabEQkRERERE8kyJhYiIiIiI5Nn/AwJnAx8ULla+AAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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6KOPkuzjdrsZOXIkkZGR+Pv7U6hQIYYNG5Zkn7179/Lggw8SGBhIxYoVWb16tXfbvHnzuHjxIrNnz6ZcuXI0bdqUvn37MnbsWO93YkREBOPHj6dVq1aEhiYdxe/y87z00ktUqlSJUqVKMX36dNxuN0uXLr3q/mlBiYWICGCaJseOHePYsWO6uU0jVotBz7olmfP8vUlGhXrZ+iWfWgdwevEgus7fwLkEpw+jFJG0lCVLFhwOB23btmXWrFlJts2cOZP777+fYsWKXfP4wYMH8/TTT/Pnn3/SoEEDWrRowalTpwA4ePAgTZs2pUGDBkRFRdG+fXtef/31q56nZcuWrFixgujoaAAWLlxIREQEVapUSbLfxYsXqVq1Kt988w1btmyhY8eOtGzZkrVr13r36dOnDyNHjqR///5s27aN+fPnkzt37iTn6devH7179yYqKooSJUrw7LPP4nR6vutWr15NrVq18Pf39+5fr149jhw5cs3EKDni4+NxOBxkz377BspQYiEiAjgcDiZPnszkyZNxONTmPy09UCIn33a9n7sjslHW2EcP20Ishkk32xc8u6Mrrcd/zfajcb4OU0RS2bp165g/fz4PP/wwzz//PDt37vT2AXA4HHz44Ye0bdv2uudo06YNzz77LJGRkQwfPpzz5897zzFlyhSKFi3KO++8Q8mSJWnRogVt2rS56nly5crFo48+yuzZswFPUnO1a+fPn5/evXtTqVIlihYtSpcuXahXrx4LFiwA4OzZs4wfP55Ro0bRunVrihUrxn333Uf79u2TnKd37940bNiQEiVKMHjwYA4cOMCePXsAiImJuSIRufQ+JibmuuVxPa+//jr58+fnkUceuelzpJRGhRIR+UdgYKCvQ8h8rlGmeUIDmN+hGm9/H8qoVc/Q2/YpNsNNDes2Is93p+e7XXmsydM8dVcBjRolcjW/TYLV7954v7wVofnHSdfNbwZH/7j6/per/jLU6Hxz8f3jm2++IWvWrDidThwOB02aNGHixInkypWLhg0bMnPmTO655x6++eYbLl68yFNPPXXd81WoUMH7OigoiODgYI4dOwbA9u3bqVatWpLvjOrVq1/zXG3btqVbt24899xzrF69mgULFrBixYok+7hcLt566y0++eQTDh8+TEJCAgkJCQQFBXmvmZCQwMMPP5zsuPPmzQvAsWPHKFWq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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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t8+4fv26ByK7P127djVatmwZrz0gIMDo16+fYRiGERwcbGTIkMGYOnWqceDAAWPPnj3GvHnzjMmTJxuGYRhHjx41AGPXrl2GYRjG1KlTjaxZsxo//vijceDAAePtt982smbNajz22GNxzuF0Oo3ChQsb3t7exvvvvx/ntddff9348ccfjSNHjhg7d+40atasabz44ot3fB8tW7Y0ypcvb2zevNkIDQ01mjZtapQsWdKw2WyGYRjG/PnzDS8vL6NmzZrGtm3bjJ07dxqBgYFG7dq13cf48ccfDavVaowYMcL4888/jX379hnLly83hg8f7u4DGF9//XWcc9/L+x07dqxRpEgRY//+/ca5c+cMm81mXLt2zShVqpTRsGFD45dffjGOHDlibNy40ejfv79x8uTJeO8xLf+cpSUul8u4evWqcfXqVcPlcnk6nPTB5TKMq1djHxpTEblH56/GGL0/WW+sf7uBYYzI6n78/HY9o8UH3xj7zsT/TCb3L7HPvP+l2+PlngwaNIjg4GBOnjxJz549+fTTT1mwYAGVKlWiQYMGLFiwAH9//wT37dOnD61bt6Zt27bUqlWL8+fPJ1gXw2w2061bN5xOJ126dInzmtPppF+/fpQrV46mTZtSpkyZRJe4nT9/PgEBATzzzDMEBgZiGAZr1qyJs7pVxowZGTJkCB06dCAwMJAMGTKwfPly9+tNmjRh9erVrF+/nho1alC7dm2mTJly15mte3m/vXr1okyZMu77MLZs2ULGjBn55ZdfKFKkCK1bt6ZcuXJ0796d69evawbDg0wmE5kyZSJTpkxa9zylmEyQKVPsQ2MqIvfgjxMXGTR1AW+f7EOj26poT7E/z/JSk1jcvznl8utvpaeZDOMOF6RLoqKiosiWLRuXL1+O96Hvxo0bHD16FH9//zjX4svd9erVi3/++YdVq1Y90PMsWLCAAQMGcOnSpQd6ngdJP2ciIpLeGYbBZ1uO8vePM3nHssBd8O6CkZlBjlep26QtPev564ufByixz7z/5fE6FiIAly9f5vfff2fJkiV8++23ng5HxM3pdLprm9StW/eOixRIEjgcsHZt7HaTJnHr94iI3HQtxsHQr/ayb8/vrPWeh9UUu4DMLldJ3vF+k3e7NaamvwrepSb6bS6pQsuWLfntt994+eWXeeqppzwdjoib0+nk//7v/wCoXbu2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LFq0CIGBgaipqUFmZiYyMzPR1NQEAOjo6MDs2bPh7++PyspKVFdXw9/fH/Pnz8eXL19sbuPt27eoq6tDfHy8Mtba2orU1FRkZGSgoaEBFRUVSE5Oxo8n7ydPnoympiY8f/7ctUUgIupn2FgQEZHbDRkyBAUFBTAajcjIyIDRaERHRwe2b9+OqKgobNu2DVqtFjdv3gTw9cyDWq3GiRMnYDKZEB0djZKSErx48QIVFRU2t/H8+XOICIYPH66Mtba2oqurC8nJyQgPD4fJZML69evh7++vzBkxYgQA/PZsCBERWdP0dQAiIvr3xMTEQK3+/retkJAQjBs3Trnv4+MDg8GA9vZ2AMDdu3fx+PFjDBo0yOp1Pn/+jCdPntjcxqdPnwAAAwYMUMYmTJiAuXPnwmQyYd68efjvv/+QkpKCwMBAZY5OpwPw9SwJERH9OTYWRETkdr6+vlb3VSqVzbGenh4AQE9PD+Li4nD69OlerzV06FCb2wgKCgLwdUnUtzk+Pj4oLy/HrVu3UFZWhsOHD2PHjh2oqalRrgL15s2bX74uERHZxqVQRETk8WJjY/Ho0SMEBwcjMjLS6vazq05FRERAr9ejvr7ealylUiEhIQF5eXmwWCzQarW4fPmy8nhdXR18fX0RExPj0n0iIupv2FgQEZHHW7FiBYKCgrB48WJUVVXh6dOnMJvN2LhxI5qbm20+R61WIzExEdXV1cpYTU0N8vPzcefOHbx48QKXLl3C69evER0drcypqqrCzJkzlSVRRET0Z9hYEBGRxxs4cCAqKysxcuRIJCcnIzo6GhkZGfj06RP0ev1Pn7du3TqcPXtWWVKl1+tRWVmJhQsXYsyYMdi5cycOHDiABQsWKM8pLS3F2rVrXb5PRET9Df9BHhER9VsigqlTp2LTpk1ITU397fxr164hOzsbtbW10Gj4MUQiInvwjAUREfVbKpUKx48fR1dX1x/N//jxI0pKSthUEBE5gGcsiIiIiIjIaTxjQURERERETmNjQURERERETmNjQURERERETmNjQURERERETmNjQURERERETmNjQURERERETmNjQURERERETmNjQURERERETmNjQURERERETmNjQURERERETvsfLWVEluaovE0AAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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yz9nsYDNlrmyhgEXXhm/XNmAgKzFd5H9+/ezbNkyr6vws2fP5umnn2bmzJk0btyYHTt2MGTIEEJDQ+nfv/8lr+F2uylXrhzvvvsuJUuWZMOGDdx3332UKVOGXr16cdNNN1GlShXmz5/Po48+CoDT6WTBggU8//zzADzwwAOkpaWxZs0aQkND2bVrF0WKFLls3AMGDGDfvn188sknFCtWjMcff5yuXbuya9cuz7mkpKTw3HPP8fbbbxMQEMDw4cO58847Wb9+PQDLly/n7rvv5uWXX+bGG2/k119/5b770i8uPf300573evrpp5k0aRLTpk3DarVmeL6jRo1i9+7dJCcnM3fuXADCw8NJSUmhbdu23HjjjaxZswabzcazzz5L586d+eGHHwjI5v/LK1FiISJyEQPY7q7JSfPyXzZZMWddPJ+t/Aqo8M97GHDvbbfQq2n5HHmPgsbUerMi2TNx4uWfq14d+vb9Z//FF8Hh8F22UiUYMOCf/enTISXl0nLjx2c5xM8++4wiRYrgcrk4d+4cAFOnTvU8/8wzzzBlyhR69uwJQOXKldm1axevv/66z8TCbrczYcIEz37lypXZsGED7777Lr169QJg0KBBzJ0715NYfP7556SkpHieP3ToELfddhv169cHoEqVKpeN/3xCsX79elq1agXAwoULKV++PB999BF33HEHkD5saebMmTRv3hyAt99+m9q1a7NlyxaaNWvGc889x+jRoz3nVKVKFZ555hkee+wxr8SiT58+DBw40CuGK51vkSJFCA4OJjU1laioKE+5BQsWYLFYePPNNz0XrebOnUvx4sVZtWoVHTt2vOw5Z5cSCxERH5533uXZbmNc/RClxVsO8eeXE/ki4D1GOwfzrqstAJN7NrimkopEivOTuyIAO91VaODneEQk97Vt25ZXX32VlJQU3nzzTfbu3ctDDz0EwIkTJzh8+DCDBg1iyJAhnjpOp5OwsLDLvuZrr73Gm2++ycGDBzl79ixpaWk0atTI8/yAAQN44okn2LRpEy1atOCtt96iV69ehIaGAjBixAjuv/9+VqxYQfv27bntttto0MD3J9Lu3bux2WyehAEgIiKCmjVrsnv3bs8xm81G06ZNPfu1atWiePHi7N69m2bNmrF9+3a2bt3Kc8895ylzPtlKSUkhJCQEwOs1Mnu+vmzfvp1ffvmFokW9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dSrz5s2jRo0aPPvss3To0IE9e/ZQtGhRAGJjY/n0009ZsmQJERERjBw5km7durF9+3asVmuOxiwihYPT6eSdd94BoHfv3rpJXjYVMc9QzUj/jqhjHARa+TcgERHJcVn6poyNjaVMmTIEBAT4fD4tLS1Hgjpv48aN9OjRg5tvvhmASpUqsXjxYrZt2wak91ZMnz6dcePG0bNnTwDefvttIiMjWbRoEUOHDiUpKYk5c+Ywf/582rdvD8CCBQsoX748X331FZ06dcrRmEWkcHC73ezbt8+zLdkTZZ6gm3UTAMnWEpjc5eeIREQkp2VpKFTFihWZNm0a8fHxPh+ff/55jgZ3ww038PXXX7N3714Avv/+e9atW0fXrl0BiI+PJyEhgY4dO3rqBAYG0rp1azZs2ADA9u3bcTgcXmWio6OpV6+ep4yIyMWsViu33nort956q3o2c4BpsUAtO9SyY2qVLRGRQilLPRYxMTFs376dXr16+XzeMIwcvZvq448/TlJSErVq1cJqteJyuXjuuee46670K10JCQkAREZGetWLjIzk4MGDnjIBAQGUKFHikjLn6/uSmppKamqqZz85OTlHzklECgar1UqjRo38HUahYVosEJWeoJlOJRYiIoVRlhKL//73v6SkpFz2+Tp16hAfH5/toM575513WLBgAYsWLaJu3brExcURGxtLdHQ0/fv395S7eI150zQzXHc+ozKTJk1iwoQJ2TsBEREREZFrRJaGQtWpU4emTZte9nm73U7FihWzHdR5jz76KKNHj+bOO++kfv369OvXj//85z9MmjQJgKioKIBLeh6OHz/u6cWIiooiLS2NxMTEy5bxZcyYMSQlJXkehw8fzrHzEpH8z+12k5CQQEJCguZY5ADD7YY/XfCnCyMHe7ZFRCT/uKrlZs+ePevVc3Hw4EGmT5/OihUrciwwgJSUFCwW7xCtVqvnS75y5cpERUWxcuVKz/NpaWmsXr2aVq3SVxyJiYnBbrd7lTl27Bg7d+70lPElMDCQYsWKeT1E5NrhdDp57bXXeO2113A6nf4Op8Az3G740QE/OrC4lViIiBRGV7V+Yo8ePejZsyfDhg3j5MmTNG/eHLvdzh9//MHUqVO5//77cyS4W265heeee44KFSpQt25dduzYwdSpUxk4cCCQPgQqNjaWiRMnUr16dapXr87EiRMJCQmh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HI+IiOQ8JRYiIpLrEiyRtEub4tl/zY+xiIhI7lBiISIiuc80sbscADgs+uoRESmM9OkuIuJDWloaU6dOBeCRRx4hICDAzxEVbDaXkwc2vgvAKy17+TkaERHJDUosREQu49y5c/4OodAytdqsiEihU6CWOpk0aRKGYRAbG+s5Zpom48ePJzo6muDgYNq0acNPP/3kVS81NZWHHnqIkiVLEhoaSvfu3Tly5EgeRy8iBYndbuehhx7ioYcewm63+zucAi/C/INbLeu51bKegdYv/B2OiIjkggKTWGzdupU33niDBg0aeB1/4YUXmDp1KjNnzmTr1q1ERUXRoUMHTp065SkTGxvL0qVLWbJkCevWreP06dN069YNl8uV16chIgWEYRhEREQQERGBYWh91OwKIo1KlgQqWRKoadGFHRGRwqhAJBanT5+mb9++zJ49mxIlSniOm6bJ9OnTGTduHD179qRevXq8/fbbpKSksGjRIgCSkpKYM2cOU6ZMoX379jRu3JgFCxbw448/8tVXX/nrlERERERECpUCkVg88MAD3HzzzbRv397reHx8PAkJCXTs2NFzLDAwkNatW7NhwwYAtm/fjsPh8CoTHR1NvXr1PGV8SU1NJTk52eshItcOl8vFli1b2LJli3o3RUREMiHfT95esmQJ3333HVu3br3kuYSEBAAiIyO9jkdGRnLw4EFPmYCAAK+ejvNlztf3ZdKkSUyYMCG74YtIAeVyufjii/S5AI0aNcJqtfo5IhERkfwtX/dYHD58mIcffpgFCxYQFBR02XIXj382TTPDMdEZlRkzZgxJSUmex+HDh7MWvIgUaBaLhTp16lCnTh0slnz9UVkgmIYBpazpD0NLQomIFEb5usdi+/btHD9+nJiYGM8xl8vFmjVrmDlzJnv27AHSeyXKlCnjKXP8+HFPL0ZUVBRpaWkkJiZ69VocP36cVq1aXfa9AwMDCQwMzOlTEpECwmaz0auX7reQU1xWG9RNX13LdFlQaiEiUvjk68tw7dq148cffyQuLs7zaNq0KX379iUuLo4qVaoQFRXFypUrPXXS0tJYvXq1J2mIiYnBbrd7lTl27Bg7d+68YmIhIiI5R+tqiYgUfvm6x6Jo0aLUq1fP61hoaCgRERGe47GxsUycOJHq1atTvXp1Jk6cSEhICH369AEgLCyMQYMGMXLkSCIiIggP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Lyc+S+fMmUP79u2pWLFiboRY4FxNm7Zq1YojR47wxRdfYJomv//+O++//z4333xzXoTsxZbn75gP/fHHH7hcLiIjI72OR0ZGkpCQ4LNOQkKCz/JOp5M//viDMmXK5Fq8+d3VtKdcWU606ZQpUzhz5gy9evXKjRALlOy0Z7ly5Thx4gROp5Px48czePDg3Ay1wLiaNt23bx+jR49m7dq12Gz6OrrY1bRpmTJleOONN4iJiSE1NZX58+fTrl07Vq1axU033ZQXYedbV9Oe+/fvZ926dQQFBbF06VL++OMPhg8fzl9//aV5FmT/u+nYsWN8+eWXLFq0KLdCLHCupk1btWrFwoUL6d27N+fOncPpdNK9e3dmzJiRFyF70Sf5BQzD8No3TfOSYxmV93X8WpXV9pSMXW2bLl68mPHjx/Pxxx9TunTp3AqvwLma9ly7di2nT59m06ZNjB49mmrVqnHXXXflZpgFSmbb1OVy0adPHyZMmECNGjXyKrwCKSt/pzVr1qRmzZqe/ZYtW3L48GH+97//XfOJxXlZaU+3241hGCxcuJCwsDAApk6dyu23384rr7xCcHBwrsdbEFztd9O8efMoXrw4t956ay5FVnBlpU137drFiBEjeOqpp+jUqRPHjh3j0UcfZdiwYcyZMycvwvVQYgGULFkSq9V6SSZ4/PjxSzLG86KionyWt9lsRERE5FqsBcHVtKdcWXba9J133mHQoEG89957tG/fPjfDLDCy056VK1cGoH79+vz++++MHz9eiQVZb9NTp06xbds2duzYwYMPPgik/4gzTRObzcaKFSv417/+lSex51c59VnaokULFixYkNPhFThX055lypShbNmynqQCoHbt2pimyZEjR6hevXquxpzfZedv1DRN3nrrLfr160dAQEBuhlmgXE2bTpo0ieuvv55HH30UgAYNGhAaGsqNN97Is88+m6ejaDTHAggICCAmJoaVK1d6HV+5ciWtWrXyWadly5aXlF+xYgVNmzbFbrfnWqwFwdW0p1zZ1bbp4sWLGTBgAIsWLfLLWMv8Kqf+Rk3TJDU1NafDK5Cy2qbFihXjxx9/JC4uzvMYNmwYNWvWJC4ujubNm+dV6PlWTv2d7tix45oennve1bTn9ddfz9GjRzl9+rTn2N69e7FYLJQrVy5X4y0IsvM3unr1an755RcGDRqUmyEWOFfTpikpKVgs3j/prVYr8M9omjyT59PF86nzS3vNmTPH3LVrlxkbG2uGhoZ6VikYPXq02a9fP0/588vN/uc//zF37dplzpkzR8vNXiCr7Wmaprljxw5zx44dZkxMjNmnTx9zx44d5k8//eSP8POlrLbpokWLTJvNZr7yyiteS/udPHnSX6eQr2S1PWfOnGl+8skn5t69e829e/eab731llmsWDFz3Lhx/jqFfOdq/t1fSKtCXSqrbTpt2jRz6dKl5t69e82dO3eao0ePNgHzgw8+8Ncp5CtZbc9Tp06Z5cqVM2+//Xbzp59+MlevXm1Wr17dHDx4sL9OId+52n/3d999t9m8efO8DrdAyGqbzp0717TZbOasWbPMX3/91Vy3bp3ZtGlTs1mzZnkeuxKLC7zyyitmxYoVzYCAALNJkybm6tWrPc/179/fbN26tVf5VatWmY0bNzYDAgLMSpUqma+++moeR5y/ZbU9gUseFStWzNug87mstGnr1q19tmn//v3zPvB8Kivt+fLLL5t169Y1Q0JCzGLFipmNGzc2Z82aZbpcLj9Enn9l9d/9hZRY+JaVNp08ebJZtWpVMygoyCxRooR5ww03mJ9//rkfos6/svo3unv3brN9+/ZmcHCwWa5cOfORRx4xU1JS8jjq/C2rbXry5EkzODjYfOONN/I40oIjq2368ssvm3Xq1DGDg4PNMmXKmH379jWPHDmSx1GbpmGaed1HIiIiIiIihY3mWIiIiIiISLYpsRARERERkWxTYiEiIiIiItmmxEJERERERLJNiYWIiIiIiGSbEgsREREREck2JRYiIiIiIpJtSixERERERCTblFiIiEiuGD9+PI0aNfLb+z/55JPcd999mSo7atQoRowYkcsRiYgUbrrztoiIZJlhGFd8vn///sycOZPU1FQiIiLyKKp//P7771SvXp0ffviBSpUqZVj++PHjVK1alR9++IHKlSvnfoAiIoWQEgsREcmyhIQEz/Y777zDU089xZ49ezzHgoODCQsL80doAEycOJHVq1ezfPnyTNe57bbbqFatGpMnT87FyERECi8NhRIRkSyLioryPMLCwjAM45JjFw+FGjBgALfeeisTJ04kMjKS4sWLM2HCBJxOJ48++ijh4eGUK1eOt956y+u9fvvtN3r37k2JEiWIiIigR48eHDhw4IrxLVmyhO7du3sde//996lfvz7BwcFERETQvn17zpw543m+e/fuLF68ONttIyJyrVJiISIieeabb77h6NGjrFmzhqlTpzJ+/Hi6detGiRIl2Lx5M8OGDWPYsGEcPnwYgJSUFNq2bUuRIkVYs2YN69ato0iRInTu3Jm0tDSf75GYmMjOnTtp2rSp59ixY8e46667GDhwILt372bVqlX07NmTCzvtmzVrxuHDhzl48GDuNoKISCGlxEJERPJMeHg4L7/8MjVr1mTgwIHUrFmTlJQUxo4dS/Xq1RkzZgwBAQGsX78eSO95sFgsvPnmm9SvX5/atWszd+5cDh06xKpVq3y+x8GDBzFNk+joaM+xY8eO4XQ66dmzJ5UqVaJ+/foMHz6cIkWKeMqULVsWIMPeEBER8c3m7wBEROTaUbduXSyWf65pRUZGUq9ePc++1WolIiKC48ePA7B9+3Z++eUXihYt6vU6586d49dff/X5HmfPngUgKCjIc6xhw4a0a9eO+vXr06lTJzp27Mjtt99OiRIlPGWCg4OB9F4SERHJOiUWIiKSZ+x2u9e+YRg+j7ndbgDcbjcxMTEsXLjwktcqVaqUz/coWbIkkD4k6nwZq9XKypUr2bBhAytWrGDGjBmMGzeOzZs3e1aB+uuvv674uiIicmUaCiUiIvlWkyZN2LdvH6VLl6ZatWpej8utOlW1alWKFSvGrl27vI4bhsH111/PhAkT2LFjBwEBASxdutTz/M6dO7Hb7dStWzdXz0lEpLBSYiEiIvlW3759KVmyJD169GDt2rXEx8ezevVqHn74YY4cOeKzjsVioX379qxbt85zbPPmzUycOJFt27Zx6NAhPvzwQ06cOEHt2rU9ZdauXcuNN97oGRIlIiJZo8RCRETyrZCQENasWUOFChXo2bMntWvXZuDAgZw9e5ZixYpdtt59993HkiVLPEOqihUrxpo1a+jatSs1atTgiSeeYMqUKXTp0sVTZ/HixQwZMiTXz0lEpLDSDfJERKTQMU2TFi1aEBsby1133ZVh+c8//5xHH32UH374AZtN0w9FRK6GeixERKTQMQyDN954A6fTmanyZ86cYe7cuUoqRESyQT0WIiIiIiKSbeqxEBERERGRbFNiISIiIiIi2abEQkREREREsk2JhYiIiIiIZJsSCxERERERyTYlFiIiIiIikm1KLEREREREJNuUWIiIiIiISLYpsRARERERkWxTYiEiIiIiItn2/8H8lUkJCvGEAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAGGCAYAAADmRxfNAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB4WklEQVR4nO3dd3RU1drH8e+ZmXSSQAIkREBC78UgEFBBQZoIXFRUFESaXGyoiCIXhavCK16KglhQitKsqNdCsdAE6VylCChdCaBAAgSSzMx+/4iMDAwkIWWS+PusNWvNnLP3medswsw8Z5djGWMMIiIiIiIiuWDzdwAiIiIiIlL0KbEQEREREZFcU2IhIiIiIiK5psRCRERERERyTYmFiIiIiIjkmhILERERERHJNSUWIiIiIiKSa0osREREREQk1xz+DqCocLvd/Pbbb4SHh2NZlr/DERERERHJd8YYTpw4QVxcHDbbpfsklFhk02+//UaFChX8HYaIiIiISIHbv38/5cuXv2QZJRbZFB4eDmQ2akREhJ+jEZH85na72bNnDwCVKlXK8iqNZCE9HcaNy3z+2GMQGOjfeEREJFtSUlKoUKGC57fwpSixyKazw58iIiKUWIj8TTRs2NDfIRQf6ekQFJT5PCJCiYWISBGTnakAugQnIiIiIiK5ph4LEREf3G43P//8MwBVq1bVUCgREZEs6JtSRMQHp9PJnDlzmDNnDk6n09/hiIiIFHrqsRAR8cGyLOLi4jzPJZcsC/5sT9SeIpfF5XKRkZHh7zCkmAkICMBut+fJsSxjjMmTIxVzKSkpREZGkpycrMnbIiIiUmCMMSQlJXH8+HF/hyLFVMmSJYmNjfV5IS0nv4HVYyEiIiJSiJ1NKsqWLUtoaKh6USXPGGNITU3l8OHDAJQrVy5Xx1NiISIiIlJIuVwuT1IRHR3t73CkGAoJCQHg8OHDlC1bNlfDopRYiIj4kJGRwdtvvw1Ar169CAgI8HNERVxGBrzySubz++8HtadItpydUxEaGurnSKQ4O/v3lZGRocRCRCSvGWPYv3+/57nkkjFwdny42lMkxzT8SfJTXv19KbEQEfHB4XBwxx13eJ6flZyawcl0J+UigrHZ9EUvIiJyll/vY7Fs2TJuvvlm4uLisCyLjz/++KJl77vvPizLYuLEiV7b09LSePDBByldujRhYWF07tyZAwcOeJU5duwYPXv2JDIyksjISHr27KmVFUTkkmw2GzVr1qRmzZqem+O98/1eHhj9Em+9+DgDpy0lNV33txARyU9Z/T7Mb5UqVbrgt6dcnF8Ti1OnTtGgQQMmT558yXIff/wxq1ev9qwpf67Bgwczf/585s2bx4oVKzh58iSdOnXC5XJ5yvTo0YNNmzaxYMECFixYwKZNm+jZs2een4+IFF/7j6ay8r8zeMfxLE8HvMPde//F60t3+TssEZFCq3fv3nTt2jVPj2lZFpZl8f3333ttT0tLIzo6GsuyWLJkSZ6+Z1aycwH74YcfJiEhgaCgIBo2bHjBMZYsWUKXLl0oV64cYWFhNGzYkNmzZxfMCeQhvw6F6tChAx06dLhkmV9//ZUHHniAhQsXctNNN3ntS05O5q233uKdd96hTZs2AMyaNYsKFSrw1Vdf0a5dO7Zt28aCBQv4/vvvadq0KQBTp04lMTGR7du3U6NGjfw5OREp0txuN/v27QOgYsWKfLn5IHfZFnr2X2f/kSnr12HaVMOyLNbvPcrEj5ZiP3mQGgktGdq+NnYNlRIRyXMVKlRg+vTpNGvWzLNt/vz5lChRgqNHjxZ4PD169ODAgQMsWLAAgAEDBtCzZ0/++9//esoYY+jTpw+rV6/mhx9+uOAYK1eupH79+jzxxBPExMTw+eef06tXLyIiIrj55psL7Fxyy689Fllxu9307NmTxx9/nDp16lywf/369WRkZNC2bVvPtri4OOrWrcvKlSsBWLVqFZGRkZ6kAqBZs2ZERkZ6yviSlpZGSkqK10NE/j4OHT/F8BcmMf6VN3A6nazefoAmtp+8ylQ/8T37j57meGo646bP5Y3jA5jhGsaVK4czY+Uer7Jut+FMhgsRkcvldhv+OJnm14fbfXmLL7Rq1YqHHnqIoUOHEhUVRWxsLCNHjvQqs3PnTq677jqCg4OpXbs2ixcv9nmse+65h3nz5nH69GnPtmnTpnHPPfdcUPaJJ56gevXqhIaGUrlyZUaMGHHB3cs//fRTGjduTHBwMKVLl6Zbt25e+1NTU+nTpw/h4eFUrFiRN954w7Pv7AXsN998k8TERBITE5k6dSqfffYZ27dv95R7+eWXuf/++6lcubLPc3rqqad49tlnad68OVWqVOGhhx6iffv2zJ8/33eDFlKFevL2Cy+8gMPh4KGHHvK5PykpicDAQEqVKuW1PSYmhqSkJE+ZsmXLXlC3bNmynjK+jBkzhlGjRuUiehEpqo6eSqfbq6vY8UsqkMrNO46Q/ttmAq2/EoOH0h9gtbsmTX9N5siJM9zr+pAQezoAPRzf0n3FKvq0qIRlWSzcksSTH/7A8dMZtKsdy/jbGxAaWKg/fvOeZUGZMn89F5EcO5aaTsJzX/k1hvX/akN0iaDLqjtz5kweffRRVq9ezapVq+jduzctWrTgxhtvxO12061bN0qXLs33339PSkoKgwcP9nmchIQE4uPj+fDDD7n77rvZv38/y5Yt45VXXuHZZ5/1KhseHs6MGTOIi4vjxx9/pH///oSHhzN06FAAPv/8c7p168bw4cN55513SE9P5/PPP/c6xrhx43j22Wd56qmn+OCDD/jnP//JddddR82aNbO8gJ2bkTHJycnUqlXrsuv7Q6H9Zlu/fj0vvfQSGzZsyPESWMYYrzq+6p9f5nzDhg3j0Ucf9bxOSUmhQoUKOYpDRIqmJdsPc/BEBuFXdQKg/6xN3Gn/Bf689cLIjF586m4OwA8HjvPzr4d51bbJ6xiNTixjx6GbKRHs4KG5G0lzugFYsCWJuIUhPH1zbQCcLjcLtiRxKCWNm+uXo2xEcMGcZEELCMi8f4WI/G3Vr1+fZ555BoBq1aoxefJkvv76a2688Ua++uortm3bxp49eyhfvjwAo0ePvuiQ+XvvvZdp06Zx9913M336dDp27EiZsxcvzvGvf/3L87xSpUo89thjvPvuu57E4vnnn+eOO+7wupjcoEEDr2N07NiRQYMGAZk9IBMmTGDJkiXUrFnzsi9gZ+WDDz5g7dq1vP7665d9DH8otEOhli9fzuHDh6lYsSIOhwOHw8HevXt57LHHqFSpEgCxsbGkp6dz7Ngxr7qHDx8mJibGU+bQoUMXHP/IkSOeMr4EBQURERHh9RCRv4flO3+/YFsta5/n+Tb3lZ7nm39LJuPXTV69GQBX235izZ6jzPp+ryepOGve2n2cTHNijGHwrNUkvfcoVRf2Yuh/JrNmt/f44NPpLg6nnNG9NESkyKtfv77X63LlynH48GEgc0hRxYoVPUkFQGJi4kWPdffdd7Nq1Sp27drFjBkz6NOnj89yH3zwAddccw2xsbGUKFGCESNGeObPAWzatInWrVtnO27LsoiNjfXEfXbb+bK6gH0pS5YsoXfv3kydOtXnVIDCrNAmFj179uSHH35g06ZNnkdcXByPP/44CxdmTqBMSEggICDAawzewYMH2bx5M82bZ15NTExMJDk5mTVr1njKrF69muTkZE8ZEZFzlQ0P4iprBzfa1nGT7XtKcoKatnMSC/NX7+V3P/9B9YztFxyjsW0H63b9zpqf9lLBOgQYyvEHt9u/pZPrK777+Xe+3JxEg50v08/xJS3tPzDFGsu4D5d6xjDP33iA+56fzMsvPEmf1xZzKOWM13sYYy57vLOISEELCAjwem1ZFm535oUXXxdPLvXDPDo6mk6dOtG3b1/OnDnjs2fj+++/54477qBDhw589tlnbNy4keHDh5Oenu4pExISkqu4L/cC9sUsXbqUm2++mfHjx9OrV68c1/c3vw6FOnnyJD///LPn9e7du9m0aRNRUVFUrFiR6Ohor/IBAQHExsZ6xqtFRkbSt29fHnvsMaKjo4mKimLIkCHUq1fPs0pUrVq1aN++Pf379/d0Jw0YMIBOnTppRSgR8SkmIphX7ONYvOUPAJrVvBGADGPndyJJIYzy1mFqWfv4zZTmE1cLfjWlaWD7hRtsG6lu+5WS1il2b/8f8Rk7+TBoCkdMBGWszEUg9rrL8saOezh8LJmJ9q897xtqpdH8+Ces3XMdwQF2ln3wCm8HTIEA2H3wC4bNe523+rfCsiyW7zzCK/O/xZG8h9K1rmNkt6soGRroOZbbbUjNcFEiqJCMeM3IgLMTHgcMyBwaJSI5Uio0kPX/auP3GPJD7dq12bdvH7/99pvn9gKrVq26ZJ0+ffrQsWNHnnjiCex2+wX7v/vuO6688kqGDx/u2bZ3716vMvXr1+frr7/m3nvvvay4z72A3aRJE+DyL2AvWbKETp068cILLzBgwIDLisff/PqNs27dOq6//nrP67NzGu655x5mzJiRrWNMmDABh8NB9+7dOX36NK1bt2bGjBlef2CzZ8/moYce8qwe1blz5yzvnSEif1+WBX+YcHYdOwJAG9tBuqePxIGTshwn0baVuYHPAzDd2Y5Rznv40t2UL91NWW+rTkXrMBvdVdlmornL8QmAJ6kAuNJ2mK1b/8eJ0xnss5WllrXfs6+9bS3vbjnEoWMpjHTM8myPtx2ixt55rN/bgJBAO7Nnvsrb9okEBrjYvGM2j82awJv9r8eyLP77v9+Y8+nnlDj9G84KzXn2jhaULxXqOdae309x/HQG8aXDiAwpoB/4xsCRI389F5Ecs9msy544Xdi1adOGGjVq0KtXL8aNG0dKSopXQuBL+/btOXLkyEWHq1etWpV9+/Yxb948rr76aj7//PMLVll65plnaN26NVWqVOGOO+7A6XTy5ZdfeuZgZCW7F7B//vlnTp48SVJSEqdPn2bTpk1AZkIVGBjIkiVLuOmmm3j44Ye55ZZbPPMzAgMDiYqKylYshYFfE4tWrVrlaNzwnj17LtgWHBzMpEmTmDRp0kXrRUVFMWvWrIvuFxE53zGrJN1qZf7ojrEfBBc4cfAbpUl1//XFXtvmffVrsbux1+tE2xYA0oyDN1ydeNDxMQDVUjfxnut6OvAC5a0jzA8cQRkrhRq2A6xev4YKzv2UcXgvc93d/i2vr9vP8ROneNY+1TOvo65tD/X3vc232+sQ5LDz3XvjmO14C1ug4dekmTzy5ou8PbgrgXYbIz7ZzNI16+htX8h/7M3p1b07bevEAnDiTAbTVuwhKeU0V1UsxT8aXYHD/teIWafLzdHUdMqUCLrsscMiIr7YbDbmz59P3759adKkCZUqVeLll1+mffv2F61jWRalS5e+6P4uXbrwyCOP8MADD5CWlsZNN93EiBEjvJa5bdWqFe+//z7PPvss//d//0dERATXXXddjmLPzgXsfv36sXTpUs/rRo0aAZmjdSpVqsSMGTNITU1lzJgxjBkzxlOuZcuWBX7Dv9ywjGYEZktKSgqRkZEkJydrIrdIMTf9u91UW3AX19i3eLbVODODNDKHALStHcOoX26jnHWUZBNKg7SpQOYPbcv664J8HL+zMjhzuezv3bUYm3E7HwWNBGC+qwWPZPy1StIA+395KmAuAM9n9KCBbRed7N53lgXoljaSeCuJcYGveW0/YUL4Z5m3cTvTmHqsL2FWmmffZ65m/NpmCgBjvvyJCE7yQ/AAnMbGfa6hPPHgA5QpEcRDr7zPyWNH2GiqAXBDzbK80TMBu83irRW7Wbb4Y8q7DrAzvAn3/+MGWtXIXAnlYPJpZn+/j1+Pn6ZOXAR3NKnoNQRr56ET/HY4mUazXyUiOACeegoC82c4hUhxc+bMGXbv3k18fDzBwcV01Tjxu0v9neXkN3AhGXwrIlK4nH89vpKVxHZTkaiwQK6pVpqfdlagnP0okVYqA+yf8aW7CQcoS69mlZi5KrMXo6ltm6f+anctDobV4lRGEGFWGs1tWwDjeadF7sY8RWZicbN9FVWs34DMIVkvOO9gbMBUALrZl9PA9ovnuD+5K1DTtp9w6zS1D35EaSuFMMdfSQVAJ/v33LzgC340mTdmSiGMHe4rqG77lefsb/DY+40JC3Iw6uS/OR5Qgm7powCLb346zP99+RNlwoPIWDyKtx2fgA3STs9kyNv3k9ZjEFFhgfSZsZYTZ5w845hJ+c1H6L3yn4zv1wmH3eKZ91ZRae/7VHH/ypnv/sAWW4fENCcllFiIiBQ7hXZVKBERfzEG3Mbwa4qbX1PcuI1hYdCT3GNfSO1yEdQqF8E289eSs08FzGV50CO0iD7FtdXKEEQ6CdZ2JgS+6imzyVaHvi2rs9ZdE4AY67gneQCoUqMBO9xXAFDX2kP39KcZnXEnrzs78ZkrkT3uGKY6O7LRXY16tj0A/OCOZ1DGw7hNZnLS27GQL11NWOJqwBkTwGRnF8/xhzje8zy3YThkMm8sWs46Sruk1+m0byyVbUlcZfuZ9ra1nrJvrthN0qIJPPDnXBGAIMvJOMdkZs2ezm2vreLEGScA4dZpbrRv4KXUJxkwYS79XnyHpw4MZHjAHO5wLKGdfR1lDy2n/8x1uLSalYhIsaMeCxERH1xuw9QNmUsSPnVtEIF2+Id9OUsqPUCtchG8Zap6ld/vLkOF+Fo0rFiSTrbvvYYqnTTBOK5synXVy/D+gjq0sv8PgK+DHucVZ2f+G9WX/tdVZu7OG7jC/TuLXI35yVRkiyueiGAH/+pci1bzx5PZu2HYlVaOXo5FLHE35JpmiXy+riknTAhvum5il4mjd8YTxPE7RyhJF9tKKtiOUMu2jyhSOEoEbmwMc/ZnkW0ooVYa9zj+WrI7xYSw9ZykqYNtNc8EvON5/auJ5grrDwItF8Mcc/k+vTYZf36VBJIBwBXWH3xoH4YNQ4j117KOZ0wg693VWbf3KD8fPkmN2PC8+ccSEZFCQT0WIiI+WEDJYIuSwZZnWNQiV2NaVi9DiSAHrso3kGL+Wmnpc3cz2tcrR+kSQfxevjWnzF8TvD93NaNDo3iqlS3B5qi2pJu/Vq2rYe2na0IFGl9ZigUl/sFzzp6sMbVwkVnmH42uoHPDuMy5CX9GttFU45GM+zlV/R8MbV+Tp+yP8JSzP7tMnOe4EbHxfP7IDUx03QJAWes49zn+CxhKlwjkqR7tmOC85YLzHua8j4b1G2HhZpD9YyYHvOzZ97KzK9emvcQC19UAnCbQk1QAjMroxZY/bx4YZqV5koot7isZmtGf9wJacSAoBoPFybSMnP2DiIhIoafEQkTEB4fdxuBmQQxuFsQpWwm+djViV5WeNKxQEoCBberwhHMgv5poFrkSWBV3D9dWzVydZFD7BIY7+3HUlGCtuzqflelHp/pxWJZF3w7NGe7sy3ETxiZ3Fd4IG8Ddza7EYbfxVMdaXjFEhwVy/w1VKRHkuGBfWKCdYR1rUSLIwSNtvO/JE2C3eP4fdakeE07MNb2Z4cxcqaSXfTH1bbt5/h/16FivHBlXD2SO8wbcxiLNOBiZ0YtmN/fhpTsa0q1RHFVsB7Fbhgxj5yVnN1KbP8mO52/i0yr/5r+uZgTg9Lxn14ZxLBh+C2PKvuhJPFzG4l1nK1684mXe4waebjyQaVd3wWlXZ7mISHGkVaGySatCifx9TFuxmyoLe9Hsz8nXDdLe4NGODeiVWInggL96G344cJyPNvxKmfAgeiZeeU6vAqzdc5T31+0nNiKYga2qEBr414/pb346xEfr9xNVIphBraoSG/nXChyf/3CQ99fvp1J0GPe2qMSV0WGefZ9s+pV5a/ZTJjyI+1pWpk5cJJB5x9qP/9xXMjSAAddVJuHKKM++OWv28fnaHYQEBdL3hjo0r5KZALndhnfX7Wfx2s0EhpTgH02r0+7PpWddbsM7q/awfNM2rIAgOl1dky4NM5Mjp8vNjJV7WPbjbpwBYXRteAW3JpTHZrNIc7p4e+VeVm/9BWOz06peZe5sUpFG/17MibS/EpEP/5noiVFELk6rQklByKtVoZRYZJMSC5G/j2krdvPvz7Z6XreoGs3sfs38GFHR1+CZLzmTdgYAFzbe/ee1SixEskGJhRQELTcrIpKPjNtF6k8rAHDHd/ZzNEVfY9cW3toyAoA36v4DuNa/AYmISJ7THAsREV+Mm4yjB8g4egB17OYFAyfcmQ/UniJSMJYsWYJlWRw/ftwv779nzx4sy2LTpk1+ef+CpsRCROQ8BsCyEVK1KSFVm2LZ9FEpIpJTvXv3xrIsLMsiICCAypUrM2TIEE6dOpWt+pUqVWLixIl5GtPZRKNUqVKcOXPGa9+aNWs88Ra0H3/8kZYtWxISEsIVV1zBv//9b6+LWgcPHqRHjx7UqFEDm83G4MGDLzjG1KlTufbaaylVqhSlSpWiTZs2rFmzpgDPQomFiIhPfQMWMbb8MsaWX0agpSvseU2dQCJ/D+3bt+fgwYPs2rWL5557jilTpjBkyBB/h0V4eDjz58/32jZt2jQqVqxY4LGkpKRw4403EhcXx9q1a5k0aRL/+c9/GD9+vKdMWloaZcqUYfjw4TRo0MDncZYsWcKdd97Jt99+y6pVq6hYsSJt27bl119/LahTUWIhIuJLG9t6ejq+oqfjK2y4/R1O0VfwFwBFpBAICgoiNjaWChUq0KNHD+666y4+/vhjqlatyn/+8x+vsps3b8Zms/HLL7/4PJZlWbz55pv84x//IDQ0lGrVqvHpp596lfniiy+oXr06ISEhXH/99ezZs8fnse655x6mTZvmeX369GnmzZvHPffc41Xujz/+4M4776R8+fKEhoZSr1495s6d61XG7XbzwgsvULVqVYKCgqhYsSLPP/+8V5ldu3Zx/fXXExoaSoMGDVi1apVn3+zZszlz5gwzZsygbt26dOvWjaeeeorx48d7ei0qVarESy+9RK9evYiMjPR5TrNnz2bQoEE0bNiQmjVrMnXqVNxuN19//bXP8vlBiYWIiA/GGA6fcnP4lFtzLERE8khISAgZGRn06dOH6dOne+2bNm0a1157LVWqVLlo/VGjRtG9e3d++OEHOnbsyF133cXRo0cB2L9/P926daNjx45s2rSJfv368eSTT/o8Ts+ePVm+fDn79u0D4MMPP6RSpUpcddVVXuXOnDlDQkICn332GZs3b2bAgAH07NmT1atXe8oMGzaMF154gREjRrB161bmzJlDTEyM13GGDx/OkCFD2LRpE9WrV+fOO+/E6cxcgnvVqlW0bNmSoKC/bqzarl07fvvtt4smRtmRmppKRkYGUVEFtwKfEgsRER+cbsOUtelMWZuO2+XMuoKIiFzSmjVrmDNnDq1bt+bee+9l+/btnjkAGRkZzJo1iz59+lzyGL179+bOO++katWqjB49mlOnTnmO8eqrr1K5cmUmTJhAjRo1uOuuu+jdu7fP45QtW5YOHTowY8YMIDOp8fXeV1xxBUOGDKFhw4ZUrlyZBx98kHbt2vH+++8DcOLECV566SXGjh3LPffcQ5UqVbjmmmvo16+f13GGDBnCTTfdRPXq1Rk1ahR79+7l559/BiApKemCROTs66SkpEu2x6U8+eSTXHHFFbRp0+ayj5FTWm5WROQiQgM0fidPqT1F8s7KybDqlazLlWsAPeZ5b5tzBxz8X9Z1E++H5g9cXnx/+uyzzyhRogROp5OMjAy6dOnCpEmTKFu2LDfddBPTpk2jSZMmfPbZZ5w5c4bbbrvtkserX7++53lYWBjh4eEcPnwYgG3bttGsWTOvydeJiYkXPVafPn14+OGHufvuu1m1ahXvv/8+y5cv9yrjcrn4v//7P959911+/fVX0tLSSEtLIywszPOeaWlptG7dOttxlytXDoDDhw9Ts2ZNgAsmjJ/tKb/cieRjx45l7ty5LFmypEDvf6LEQkTEh0C7jaEtMrul73UEZFFasuKyO+DP9nQ77VmUFpEspZ2AE79lXS7yigu3pf6evbppJ3Ie13muv/56Xn31VQICAoiLiyMg4K/P0379+tGzZ08mTJjA9OnTuf322wkNDb3k8c6tD5k/vN3uzHlwOR222rFjR+677z769u3LzTffTHR09AVlxo0bx4QJE5g4cSL16tUjLCyMwYMHk56eDmQO7cqOc+M+myycjTs2NvaCnomzydL5PRnZ8Z///IfRo0fz1VdfeSU0BUGJhYiIiEhRExQO4XFZlwst7XtbduoGhec8rvOEhYVRtWpVn/s6duxIWFgYr776Kl9++SXLli3L1XvVrl2bjz/+2Gvb999/f9Hydrudnj17MnbsWL788kufZZYvX06XLl24++67gcxkYOfOndSqVQuAatWqERISwtdff33B8KfsSkxM5KmnniI9PZ3AwEAAFi1aRFxcHJUqVcrRsV588UWee+45Fi5cSOPGjS8rntxQYiEich5N1s5726lE97TMO28fJIqC/7oTKWaaP3D5w5TOHxrlJ3a7nd69ezNs2DCqVq16yWFL2TFw4EDGjRvHo48+yn333cf69es9cygu5tlnn+Xxxx/32VsBULVqVT788ENWrlxJqVKlGD9+PElJSZ7EIjg4mCeeeIKhQ4cSGBhIixYtOHLkCFu2bKFv377ZirtHjx6MGjWK3r1789RTT7Fz505Gjx7N008/7TUU6uxN9k6ePMmRI0fYtGkTgYGB1K5dG8gc/jRixAjmzJlDpUqVPL0gJUqUoESJEtmKJbc0eVtExAen2/Dh1gw+3JqB0eTtXDvj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V3+H4aEeCxERX4ybtANb/nzayr+xFANBJp0yh48CUKrKCT9HIyIFoX379kyfPh2n08nWrVvp06cPx48fZ+7cuf4OrdDLyMggIKDoDR9Vj4WIiC+WjaC4mgTF1dQcCxGRyxAUFERsbCzly5enbdu23H777SxatMirzPTp06lVqxbBwcHUrFmTKVOmXPR4LpeLvn37Eh8fT0hICDVq1OCll17y7F+2bBkBAQEkJSV51Xvssce47rrrANi7dy8333wzpUqVIiwsjDp16vDFF19c9D2PHTtGr169KFWqFKGhoXTo0IGdO3d69s+YMYOSJUvy8ccfU716dYKDg7nxxhvZv3+/13H++9//kpCQQHBwMJUrV2bUqFE4nX8tZW5ZFq+99hpdunQhLCyM5557LsvzHTlyJDNnzuSTTz7x9A4tWbIEgF9//ZXbb7+dUqVKER0dTZcuXdizZ89FzzOvqMdCRMSHDo71lK2W2WW/36ZrMCJSyKSnX3yfzQYOR/bKWhace2X8YmUDA3MW33l27drFggULvK7CT506lWeeeYbJkyfTqFEjNm7cSP/+/QkLC+Oee+654Bhut5vy5cvz3nvvUbp0aVauXMmAAQMoV64c3bt357rrrqNy5cq88847PP744wA4nU5mzZrF//3f/wFw//33k56ezrJlywgLC2Pr1q2UKFHionH37t2bnTt38umnnxIREcETTzxBx44d2bp1q+dcUlNTef7555k5cyaBgYEMGjSIO+64g++++w6AhQsXcvfdd/Pyyy9z7bXX8ssvvzBgwAAAnnnmGc97PfPMM4wZM4YJEyZgt9uzPN8hQ4awbds2UlJSmD59OgBRUVGkpqZy/fXXc+2117Js2TIcDgfPPfcc7du354cffiAwl/+Wl6LEQkTEh76OL7jatgOAe7nTz9GIiJxn9OiL76tWDe6666/XL74IGRm+y1aqBL17//V64kRITb2w3MiROQ7xs88+o0SJErhcLs6cOQPA+PHjPfufffZZxo0bR7du3QCIj49n69atvP766z4Ti4CAAEaNGuV5HR8fz8qVK3nvvffo3j1zOeu+ffsyffp0T2Lx+eefk5qa6tm/b98+brnlFurVqwdA5cqVLxr/2YTiu+++o3nz5gDMnj2bChUq8PHHH3PbbbcBmcOWJk+eTNOmTQGYOXMmtWrVYs2aNTRp0oTnn3+eJ5980nNOlStX5tlnn2Xo0KFeiUWPHj3o06ePVwyXOt8SJUoQEhJCWloasbGxnnKzZs3CZrPx5ptvenrcp0+fTsmSJVmyZAlt27a96DnnlhILEREpUBpYJvL3cP311/Pqq6+SmprKm2++yY4dO3jwwQcBOHLkCPv376dv377079/fU8fpdBIZGXnRY7722mu8+eab7N27l9OnT5Oenu61YlTv3r3517/+xffff0+zZs2YNm0a3bt3JywsDICHHnqIf/7znyxatIg2bdpwyy23UL9+fZ/vtW3bNhwOhydhAIiOjqZGjRps27bNs83hcNC4cWPP65o1a1KyZEm2bdtGkyZNWL9+PWvXruX555/3lDmbbKWmphIaGgrgdYzsnq8v69ev5+effyY8PNxr+5kzZ/jll18uWTe3lFiIiJzHABkuNyOXZV5hc/W4yJU+yTZzXjph0HqzIrny1FMX33f+8M0/r977dP4cssGDLzuk84WFhVG1alUAXn75Za6//npGjRrFs88+i9ududre1KlTvX64A9jtvpf4fu+993jkkUcYN24ciYmJhIeH8+KLL7J69WpPmbJly3LzzTczffp0KleuzBdffOGZdwDQr18/2rVrx+eff86iRYsYM2YM48aN8yQ85zIXWRfbGHPB3Dtfc/HObnO73YwaNcrTM3Ou4OBgz/OzyU9OztcXt9tNQkICs2fPvmBfmTJlLlk3t5RYiIhkQVfYRaTQyck4+fwqm0PPPPMMHTp04J///CdxcXFcccUV7Nq1i7vOHbZ1CcuXL6d58+YMGjTIs83XFfh+/fpxxx13UL58eapUqUKLFi289leoUIGBAwcycOBAhg0bxtSpU30mFrVr18bpdLJ69WrPUKg//viDHTt2UKtWLU85p9PJunXraNKkCQDbt2/n+PHj1KxZE4CrrrqK7du3e5Ks7MrO+QYGBuJyuby2XXXVVbz77ruULVuWiIiIHL1nbmlGooiIDw6bxePNg3i8eRA2u67B5JbLbofmQdA8CLcmw4v8LbVq1Yo6deow+s/5ISNHjmTMmDG89NJL7Nixgx9//JHp06d7zcM4V9WqVVm3bh0LFy5kx44djBgxgrVr115Qrl27dkRGRvLcc89x7733eu0bPHgwCxcuZPfu3WzYsIFvvvnGK0k4V7Vq1ejSpQv9+/dnxYoV/O9//+Puu+/miiuu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OVKJBTlv0xMnTrBu3To2btzIAw88AGT+iDPG4HA4WLRoETfccEOBxF5Y5dVnabNmzZg1a1Zeh1fkXE57litXjiuuuMKTVADUqlULYwwHDhygWrVq+RpzYZebv1FjDNOmTaNnz54EBgbmZ5hFyuW06ZgxY2jRogWPP/44APXr1ycsLIxrr72W5557rkBH0WiOBRAYGEhCQgKLFy/22r548WKaN2/us05iYuIF5RctWkTjxo0JCAjIt1iLgstpT7m0y23TuXPn0rt3b+bMmeOXsZaFVV79jRpjSEtLy+vwiqSctmlERAQ//vgjmzZt8jwGDhxIjRo12LRpE02bNi2o0AutvPo73bhx4996eO5Zl9OeLVq04LfffuPkyZOebTt27MBms1G+fPl8jbcoyM3f6NKlS/n555/p27dvfoZY5FxOm6ampmKzef+kt9vtwF+jaQpMgU8XL6TOLu311ltvma1bt5rBgwebsLAwzyoFTz75pOnZs6en/NnlZh955BGzdetW89Zbb2m52XPktD2NMWbjxo1m48aNJiEhwfTo0cNs3LjRbNmyxR/hF0o5bdM5c+YYh8NhXnnlFa+l/Y4fP+6vUyhUctqekydPNp9++qnZsWOH2bFjh5k2bZqJiIgww4cP99cpFDqX8//+XFoV6kI5bdMJEyaY+fPnmx07dpjNmzebJ5980gDmww8/9NcpFCo5bc8TJ06Y8uXLm1tvvdVs2bLFLF261FSrVs3069fPX6dQ6Fzu//u7777bNG3atKDDLRJy2qbTp083DofDTJkyxfzyyy9mxYoVpnHjxqZJkyYFHrsSi3O88sor5sorrzSBgYHmqquuMkuXLvXsu+eee0zLli29yi9ZssQ0atTIBAYGmkqVKplXX321gCMu3HLansAFjyuvvLJggy7kctKmLVu29Nmm99xzT8EHXkjlpD1ffvllU6dOHRMaGmoiIiJMo0aNzJQpU4zL5fJD5IVXTv/fn0uJhW85adMXXnjBVKlSxQQHB5tSpUqZa665xnz++ed+iLrwyunf6LZt20ybNm1MSEiIKV++vHn00UdNampqAUdduOW0TY8fP25CQkLMG2+8UcCRFh05bdOXX37Z1K5d24SEhJhy5cqZu+66yxw4cKCAozbGMqag+0hERERERKS40RwLERERERHJNSUWIiIiIiKSa0osREREREQk15RYiIiIiIhIrimxEBERERGRXFNiISIiIiIiuabEQkREREREck2JhYiIiIiI5JoSCxERyRcjR46kYcOGfnv/ESNGMGDAgGyVHTJkCA899FA+RyQiUrzpztsiIpJjlmVdcv8999zD5MmTSUtLIzo6uoCi+suhQ4eoVq0aP/zwA5UqVcqy/OHDh6lSpQo//PAD8fHx+R+giEgxpMRCRERyLCkpyfP83Xff5emnn2b79u2ebSEhIURGRvojNABGjx7N0qVLWbhwYbbr3HLLLVStWpUXXnghHyMTESm+NBRKRERyLDY21vOIjIzEsqwLtp0/FKp379507dqV0aNHExMTQ8mSJRk1ahROp5PHH3+cqKgoypcvz7Rp07ze69dff+X222+nVKlSREdH06VLF/bs2XPJ+ObNm0fnzp29tn3wwQfUq1ePkJAQoqOjadOmDadOnfLs79y5M3Pnzs1124iI/F0psRARkQLzzTff8Ntvv7Fs2TLGjx/PyJEj6dSpE6VKlWL16tUMHDiQgQMHsn//fgBSU1O5/vrrKVGiBMuWLWPFihWUKFGC9u3bk56e7vM9jh07xubNm2ncuLFn28GDB7nzzjvp06cP27ZtY8mSJXTr1o1zO+2bNGnC/v372bt3b/42gohIMaXEQkRECkxUVBQvv/wyNWrUoE+fPtSoUYPU1FSeeuopqlWrxrBhwwgMDOS7774DMnsebDYbb775JvXq1aNWrVpMnz6dffv2sWTJEp/vsXfvXowxxMXFebYdPHgQp9NJt27dqFSpEvXq1WPQoEGUKFHCU+aKK64AyLI3REREfHP4OwAREfn7qFOnDjbbX9e0YmJiqFu3rue13W4nOjqaw4cPA7B+/Xp+/vlnwsPDvY5z5swZfvnlF5/vcfr0aQCCg4M92xo0aEDr1q2pV68e7dq1o23bttx6662UKlXKUyYkJATI7CUREZGcU2IhIiIFJiAgwOu1ZVk+t7ndbgDcbjcJCQnMnj37gmOVKVPG53uULl0ayBwSdbaM3W5n8eLFrFy5kkWLFjFp0iSGDx/O6tWrPatAHT169JLHFRGRS9NQKBERKbSuuuoqdu7cSdmyZalatarX42KrTlWpUoWIiAi2bt3qtd2yLFq0aMGoUaPYuHEjgYGBzJ8/37N/8+bNBAQEUKdOnXw9JxGR4kqJhYiIFFp33XUXpUuXpkuXLixfvpzdu3ezdOlSHn74YQ4cOOCzjs1mo02bNqxYscKzbfXq1YwePZp169axb98+PvroI44cOUKtWrU8ZZYvX861117rGRIlIiI5o8RCREQKrdDQUJYtW0bFihXp1q0btWrVok+fPpw+fZqIiIiL1hswYADz5s3zDKmKiIhg2bJldOzYkerVq/Ovf/2LcePG0aFDB0+duXPn0r9//3w/JxGR4ko3yBMRkWLHGEOzZs0YPHgwd955Z5blP//8cx5//HF++OEHHA5NPxQRuRzqsRARkWLHsizeeOMNnE5ntsqfOnWK6dOnK6kQEckF9ViIiIiIiEiuqcdCRERERERyTYmFiIiIiIjkmhILERERERHJNSUWIiIiIiKSa0osREREREQk15RYiIiIiIhIrimxEBERERGRXFNiISIiIiIiuabEQkREREREck2JhYiIiIiI5Nr/A1Uic8ccNQ2uAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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NYUSEQnA6/WyevVqAN8M3HL6DNOE/WVfzEZL9bAQ+S348MMPiYyM9K1ffvnlvP322359IZo1a8Zf//pXbrvtNl544YXTeh+n04nD4SA8PJzk5OQTlvv55595//33+eqrr+jevTsACxYsIC0tjffee48//elPPPHEE5x//vl+sbRr1w6AvLw8nnnmGZ577jmGDx8OQIsWLbj44osrFa/L5eKFF16gY8eOABw6dIicnBwGDBhAixYtAGjbtm2lzllbKLEQEQnAarVyySWX+JZFRGqTSZMmAWC3233bLrroIrp27Vquvf69995bruwFF1zAeeedV67skR/9x5Y9vlnSqerVqxcvvviibz0iIgKA//73v0ydOpWNGzeSm5uL2+2muLiYgoICX5masGnTJmw2G126dPFti4+Pp3Xr1mzatAkoq7H405/+dMLjS0pK6N27d5XicDgcnHPOOb71uLg4RowYQb9+/bjsssvo06cPQ4YMISUlpUrvEwxqCiUiEoDVaqV379707t1biUUNMFVpIVIlDocDh8Ph11zGarXicDiw2WzVXvZ0RERE0LJlS98rJSWFHTt2cMUVV9C+fXveffdd1q5dy/PPPw/ga45ksVj8+ngcu68qjj/nsduPXG9YWNgJjz/ZPsCXpB37PoHiDgsLK9fMae7cuaxatYru3bvz1ltvcdZZZ/lqzesSJRYiIiIickZ8++23uN1unnrqKbp27cpZZ53F3r17/co0aNCAjIwMvx/oFfXzcDgcFXYyP/vss3G73axZs8a37eDBg2zZssXX9Oicc87h888/D3h8q1atCAsLO+H+IyNepaenn3Lcxzr33HOZOHEiK1eupH379rzxxhunfGxtocRCRCQA0zQDjo4ip2cnqXzqOZ9PPefzsadLxQeISL3UokUL3G43s2bNYuvWrcyfP5+XXnrJr0zPnj3Zv38/TzzxBL/++ivPP/88n3zyyUnP27RpU9asWcP27ds5cOAA3gCDRLRq1YpBgwYxatQovvzyS/73v/9xww030LBhQwYNGgSUdc7+5ptvuP322/nhhx/46aefePHFFzlw4AChoaHcf//93Hffffzf//0fv/76K6tXr+bVV18FoGXLlqSlpTF58mS2bNnCRx99xFNPPVXhPdm2bRsTJ05k1apV7NixgyVLlvglO3WJEgsRkQBcLpdv1JXqqIL/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IiIiUmWqsRARERERkSpTYiEiIiIiIlWmxEJERERERKpMiYWIiIiIiFSZEotjvPDCCzRr1ozQ0FA6d+7MF198cdLyy5cvp3PnzoSGhtK8eXNeeumlMxRp3VCZ+5mens7QoUNp3bo1FouFsWPHnrlA65DK3NN//etfXHbZZTRo0IDo6Gi6devGp59+egajrf0qcz+//PJLLrroIuLj4wkLC6NNmzbMnDnzDEZbN1T27+gRX331FTabjU6dOtVsgHVQZe7psmXLMAyj3Ounn346gxHXbpX9jJaUlPDggw/SpEkTQkJCaNGiBXPmzDlD0dYNlbmnI0aMCPgZbdeu3RmMuPar7Od0wYIFdOzYkfDwcFJSUrjppps4ePDgGYr2GKaYpmmaCxcuNO12uzl79mxz48aN5t13321GRESYO3bsCFh+69atZnh4uHn33XebGzduNGfPnm3a7XbznXfeOcOR106VvZ/btm0z77rrLvO1114zO3XqZN59991nNuA6oLL39O677zanT59ufv311+aWLVvMiRMnmna73fzuu+/OcOS1U2Xv53fffWe+8cYb5vr1681t27aZ8+fPN8PDw81//OMfZzjy2quy9/SI7Oxss3nz5mbfvn3Njh07nplg64jK3tP//ve/JmBu3rzZTE9P973cbvcZjrx2Op3P6MCBA80uXbqYS5cuNbdt22auWbPG/Oqrr85g1LVbZe9pdna232dz165dZlxcnPnoo4+e2cBrscre0y+++MK0WCzmM888Y27dutX84osvzHbt2plXXXXVGY7cNJVYHHbhhReat956q9+2Nm3amA888EDA8vfdd5/Zpk0bv21jxowxu3btWmMx1iWVvZ/H6tGjhxKLAKpyT484++yzzSlTplR3aHVSddzPP/zhD+YNN9xQ3aHVWad7T6+55hrzoYceMh999FElFsep7D09klhkZWWdgejqnsrez08++cR0Op3mwYMHz0R4dVJV/5YuWrTINAzD3L59e02EVydV9p4++eSTZvPmzf22Pfvss2ajRo1qLMYTUVMooLS0lLVr19K3b1+/7X379mXlypUBj1m1alW58v369ePbb7/F5XLVWKx1wencTzm56rinXq+XvLw84uLiaiLEOqU67uf333/PypUr6dGjR02EWOec7j2dO3cuv/76K48++mhNh1jnVOVzeu6555KSkkLv3r3573//W5Nh1hmncz/ff/99zj//fJ544gkaNmzIWWedxYQJEygqKjoTIdd61fG39NVXX6VPnz40adKkJkKsc07nnnbv3p3du3fz8ccfY5om+/bt45133uH3v//9mQjZj+2Mv2MtdODAATweD0lJSX7bk5KSyMjICHhMRkZGwPJut5sDBw6QkpJSY/HWdqdzP+XkquOePvXUUxQUFDBkyJCaCLFOqcr9bNSoEfv378ftdjN58mRuueWWmgy1zjide/rzzz/zwAMP8MUXX2Cz6evoeKdzT1NSUnj55Zfp3LkzJSUlzJ8/n969e7Ns2TIuvfTSMxF2rXU693Pr1q18+eWXhIaGsmjRIg4cOMDtt9/OoUOH1M+Cqn83paen88knn/DGG2/UVIh1zunc0+7du7NgwQKuueYaiouLcbvdDBw4kFmzZp2JkP3oL/kxDMPwWzdNs9y2isoH2v5bVdn7KRU73Xv65ptvMnnyZP7973+TmJhYU+HVOadzP7/44gvy8/NZvXo1DzzwAC1btuS6666ryTDrlFO9px6Ph6FDhzJlyhTOOuusMxVenVSZz2nr1q1p3bq1b71bt27s2rWLv//977/5xOKIytxPr9eLYRgsWLAAp9MJwIwZM/jjH//I888/T1hYWI3HWxec7nfTvHnziImJ4aqrrqqhyOquytzTjRs3ctddd/HII4/Qr18/0tPTuffee7n11lt59dVXz0S4PkosgISEBKxWa7lMMDMzs1zGeERycnLA8jabjfj4+BqLtS44nfspJ1eVe/rWW28xcuRI3n77bfr06VOTYdYZVbmfzZo1A6BDhw7s27ePyZMnK7Gg8vc0Ly+Pb7/9lu+//54///nPQNmPONM0sdlsLFmyhN/97ndnJPbaqrr+lnbt2pXXX3+9usOrc07nfqakpNCwYUNfUgHQtm1bTNNk9+7dtGrVqkZjru2q8hk1TZM5c+YwbNgwHA5HTYZZp5zOPZ02bRoXXXQR9957LwDnnHMOERERXHLJJTz22GNntBWN+lgADoeDzp07s3TpUr/tS5cupXv37gGP6datW7nyS5Ys4fzzz8dut9dYrHXB6dxPObnTvadvvvkmI0aM4I033ghKW8vaqro+o6ZpUlJSUt3h1UmVvafR0dH8+OOPrFu3zve69dZbad26NevWraNLly5nKvRaq7o+p99///1vunnuEadzPy+66CL27t1Lfn6+b9uWLVuwWCw0atSoRuOtC6ryGV2+fDm//PILI0eOrMkQ65zTuaeFhYVYLP4/6a1WK3C0Nc0Zc8a7i9dSR4b2evXVV82NGzeaY8eONSMiInyjFDzwwAPmsGHDfOWPDDd7zz33mBs3bjRfffVVDTd7jMreT9M0ze+//978/vvvzc6dO5tDhw41v//+e3PDhg3BCL9Wquw9feONN0ybzWY+//zzfkP7ZWdnB+sSapXK3s/nnnvOfP/9980tW7aYW7ZsMefMmWNGR0ebDz74YLAuodY5nX/3x9KoUOVV9p7OnDnTXLRokbllyxZz/fr15gMPPGAC5rvvvhusS6hVKns/8/LyzEaNGpl//OMfzQ0bNpjLly83W7VqZd5yyy3BuoRa53T/3d9www1mly5dznS4dUJl7+ncuXNNm81mvvDCC+avv/5qfvnll+b5559vXnjhhWc8diUWx3j++efNJk2amA6HwzzvvPPM5cuX+/YNHz7c7NGjh1/5ZcuWmeeee67pcDjMpk2bmi+++OIZjrh2q+z9BMq9mjRpcmaDruUqc0979OgR8J4OHz78zAdeS1Xmfj777LNmu3btzPDwcDM6Oto899xzzRdeeMH0eDxBiLz2quy/+2MpsQisMvd0+vTpZosWLczQ0FAzNjbWvPjii82PPvooCFHXXpX9jG7atMns06ePGRYWZjZq1MgcN26cWVhYeIajrt0qe0+zs7PNsLAw8+WXXz7DkdYdlb2nzz77rHn22WebYWFhZkpKinn99debu3fvPsNRm6Zhmme6jkREREREROob9bEQEREREZEqU2IhIiIiIiJVpsRCRERERESqTImFiIiIiIhUmRILERERERGpMiUWIiIiIiJSZUosRERERESkypRYiIiIiIhIlSmxEBGRGjF58mQ6deoUtPd/+OGHGT169CmVnTBhAnfddVcNRyQiUr9p5m0REak0wzBOun/48OE899xzlJSUEB8ff4aiOmrfvn20atWKH374gaZNm1ZYPjMzkxYtWvDDDz/QrFmzmg9QRKQeUmIhIiKVlpGR4Vt+6623eOSRR9i8ebNvW1hYGE6nMxihATB16lSWL1/Op59+esrHXH311bRs2ZLp06fXYGQiIvWXmkKJiEilJScn+15OpxPDMMptO74p1IgRI7jqqquYOnUqSUlJxMTEMGXKFNxuN/feey9xcXE0atSIOXPm+L3Xnj17uOaaa4iNjSU+Pp5Bgwaxffv2k8a3cOFCBg4c6LftnXfeoUOHDoSFhREfH0+fPn0oKCjw7R84cCBvvvlmle+NiMhvlRILERE5Y/7zn/+wd+9eVqxYwYwZM5g8eTIDBgwgNjaWNWvWcOutt3Lrrbeya9cuAAoLC+nVqxeRkZGsWLGCL7/8ksjISPr3709paWnA98jKymL9+vWcf/75vm3p6elcd9113HzzzWzatIlly5YxePBgjq20v/DCC9m1axc7duyo2ZsgIlJPKbEQEZEzJi4ujmeffZbWrVtz880307p1awoLC5k0aRKtWrVi4sSJOBwOvvrqK6Cs5sFisfDKK6/QoUMH2rZty9y5c9m5cyfLli0L+B47duzANE1SU1N929LT03G73QwePJimTZvSoUMHbr/9diIjI31lGjZsCFBhbYiIiARmC3YAIiLy29GuXTsslqPPtJKSkmjfvr1v3Wq1Eh8fT2ZmJgBr167ll19+ISoqyu88xcXF/PrrrwHfo6ioCIDQ0FDfto4dO9K7d286dOhAv3796Nu3L3/84x+JjY31lQkLCwPKaklERKTylFiIiMgZY7fb/dYNwwi4zev1AuD1euncuTMLFiwod64GDRoEfI+EhASgrEnUkTJWq5WlS5eycuVKlixZwqxZs3jwwQf5//bullWZIAzj+LVyNBg2idGiCIvNJIjNokWwiU3QqsVmsdj8AhajJk1+AHctC4IgYjEIYhNsYhF82sKB55znZTgg8v/Blpm5FyZe3LOzvu8Ht0Bdr9dv3wsA+B5HoQAALyubzepwOCgejyuVSn16vrp1KplMyrZt7ff7T+OWZSmfz6vf72uz2SgSiWg+nwfzu91O4XBYmUzmR/cEAO+KYAEAeFn1el2xWEyVSkWe5+l4PGq5XKrdbut8Pv+2JhQKqVgsarVaBWO+72swGGi9Xut0Omk2m+lyuchxnGCN53kqFArBkSgAwL8hWAAAXlY0GpXrukokEqpWq3IcR41GQ/f7XbZtf1nXarU0nU6DI1W2bct1XZXLZaXTafV6PQ2HQ5VKpaBmMpmo2Wz++J4A4F3xgzwAwNt5Pp/K5XLqdDqq1Wp/XL9YLNTtdrXdbvXxweeHAPA/6FgAAN6OZVkajUZ6PB5/tf52u2k8HhMqAMAAHQsAAAAAxuhYAAAAADBGsAAAAABgjGABAAAAwBjBAgAAAIAxggUAAAAAYwQLAAAAAMYIFgAAAACMESwAAAAAGCNYAAAAADD2C/AdRxVmoSSsAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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2BCQ17/clFiIiPhj2Qip0ZKQGi2xbPqoFBHJqf79+2NZFpZl4XQ6qVatGkOHDuXUqVPZ2r9KlSpMmDAhT2M6k2iULl2a06dP+2z76aefvPFebr/++ivt2rUjJCSEChUq8PLLL/tc1Dp8+DB9+/aldu3a2Gw2hgwZkukYU6ZM4dprr6V06dKULl2ajh078tNPP13Gs1BiISKSiQEa2PfTPiaV9jGp2C//d4yISJHQtWtXDh8+zK5du3j11VeZPHkyQ4cODXRYlChRgrlz5/qs++ijj6hUqdJljyUpKYlOnToRExPDmjVrmDhxIv/6178YN26ct0xqaiply5bl+eefp3Hjxn6Ps3jxYu68805++OEHVq1aRaVKlejcuTMHDx68XKeixEJExJ/nHTP5OOg1Pg56DSfuQIdT6MXyB+1tG2hv20BH21rUu0zkyhAcHEx0dDSxsbH07duXu+66iy+++IIaNWrwr3/9y6fspk2bsNls/P77736PZVkWH3zwAX/7298IDQ2lZs2azJs3z6fMN998Q61atQgJCeH6669nz549fo9177338tFHH3mXU1JSmDNnDvfee69PuT///JM777yTihUrEhoaSsOGDfn44499yng8HsaMGUONGjUIDg6mUqVKvPbaaz5ldu3axfXXX09oaCiNGzdm1apV3m2zZs3i9OnTTJs2jQYNGtC7d2+ee+45xo0b5221qFKlCm+++Sb33HMP4eHhfs9p1qxZDB48mCZNmlCnTh2mTJmCx+Ph+++/91s+PyixEBHxwxhD/CkP8ac8GmORB8pax2hi+50mtt9p+tedykXkyhMSEkJ6ejoDBgxg6tSpPts++ugjrr32WqpXr37B/UeOHEmfPn3YuHEj3bt356677uLYsWMA7N+/n969e9O9e3c2bNjA/fffz7PPPuv3OP369WPZsmXs27cPgM8//5wqVapw1VVX+ZQ7ffo0zZo146uvvmLTpk0MGjSIfv36sXr1am+ZYcOGMWbMGIYPH86WLVuYPXs2UVFRPsd5/vnnGTp0KBs2bKBWrVrceeeduFwZNx9dtWoV7dq1Izj47P2PunTpwqFDhy6YGGVHcnIy6enpRERcvokylFiIiPjh8hgmr0lj8po0PG5XoMMRESn0fvrpJ2bPnk2HDh2477772LZtm3cMQHp6OjNnzmTAgAEXPUb//v258847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X78+ACdOnODNN99k0qRJ3HvvvQBUr16da665JkfxpqenM3nyZBo3bgzAsWPHSExMpEePHlSvXh2AunXr5uiYBUWhSCzS09O544478Hg82XrTGWN8sjx/Gd/5Zc43evRoRo4ceWkBi0iht9FViQ9WHATAU7no9H8VkaJh1KhRADz11FOEhYUBGV14/ve//3HVVVf5jEl94403SE9PZ8iQId5eH2vWrGH+/Pk0bNiQW265xVt2woQJJCcnM3jwYO+F2Q0bNtCsWbMcx3j99dfzzjvveJfPxPnDDz8watQotmzZQlJSEi6Xi9OnT3Pq1ClvmfywdetWHA6HTy+WyMhIateuzdatW4GMc73tttsuuH9qaiodOnTIVRxBQUE0atTIuxwREUH//v3p0qULnTp1omPHjvTp04fy5cvn6nUCocB3hUpPT6dPnz7s3r2bhQsXelsrAKKjo0lLS/P2ezsjPj7e2ywVHR3NH3/8kem4R44cuWjT1bBhw0hMTPQ+9u/fn0dnJCKFwSjXXXzuuY7PPdfhtpyBDqfQO0QUP7ib8IO7CYvcOf+BIiKFT1hYGDVq1PA+ypcvz969e+nevTsNGjTg888/Z+3atbz99tsA3u5INpstU6vmmW25caGW0nMvNoeEhFxw/4ttg4y4z38df3GHhIRkurg9depUVq1aRZs2bfjkk0+oVasWP/7440VfryAq0C0WZ5KKHTt28MMPPxAZGemzvVmzZjidTu8gb4DDhw+zadMmXn/9dQBat25NYmIiP/30Ey1atABg9erVJCYmepvB/AkODiY4ODifzkxECjrL7qTUNXcBYHcoscitw85y3Nf2xUCHIVJkPPfcc0BGl6Uz2rZtS6tWrbw/cM946qmnMpW9+uqrueqqqzKVPdNN6dyy5w+kzo2ff/4Zl8vF2LFjva/96aef+pQpW7YscXFxPj/4N2zYcNHjBgUF+cwI6k+9evVwuVysXr3a+xvwzz//ZPv27d6uR40aNeL777/322ulZs2ahISE8P3333P//fdn2l62bFkg47fomW76WcV9rqZNm9K0aVOGDRtG69atmT17Nq1atcr2/gVBQBOLkydP+sxnvHv3bjZs2EBERAQxMTHceuutrFu3jq+++gq32+0dExEREUFQUBDh4eEMHDiQJ598ksjISCIiIhg6dCgNGzakY8eOQEYfta5du/LAAw/w3nvvATBo0CB69OhB7dq1L/9Ji4iIiORSUFBQpnV2u93vWIi8KJtXqlevjsvlYuLEidx0002sWLGCd99916dM+/btOXLkCK+//jq33nor8+fP59tvv/XptXK+KlWqsHr1avbs2UPx4sWJiIjIlDTVrFmTXr16eX8TlihRgmeffZYKFSrQq1cvIKPHSsOGDRk8eDAPPfQQQUFB/PDDD9x2222UKVOGZ555hqeffpqgoCDatm3LkSNH2Lx5MwMHDqRGjRrExsYyYsQIXn31VXbs2MHYsWOzrJPdu3fz/vvv07NnT2JiYti2bRvbt2/nnnvuuYQaDqyAdoX6+eefvdkZwBNPPEHTpk158cUXOXDgAPPmzePAgQM0adKE8uXLex/nzuY0fvx4br75Zvr06UPbtm0JDQ3lv//9r88/waxZs2jYsCGdO3emc+fONGrUiBkzZlz28xURERG5kjVp0oRx48YxZswYGjRowKxZsxg9erRPmbp16zJ58mTefvttGjduzE8//cTQoUMvetyhQ4dit9upV68eZcuW9d6a4HxTp06lWbNm9OjRg9atW2OM4ZtvvvG20NSqVYsFCxbwyy+/0KJFC1q3bs2XX37pHeA+fPhwnnzySV588UXq1q3L7bffTnx8PJDRyvPxxx/z22+/0bhxY8aMGcOrr76aZZ2Ehoby22+/ccstt1CrVi0GDRrEI488woMPPpjlvgWNZTQ1R7YkJSURHh5OYmLiRTNmESn8pizdRfH5j5G0ZzMAK9tPZ/qg6wIcVeHW+sUv6bRxPgBf17qGyQ91pWW1yCz2EpHTp0+ze/duqlatSrFixQIdjhRRF3uf5eQ3cIEeYyEiEggGQ6z1B4sOHcpY1vWXXKtrdvHy8Y8AqGz/E+ga2IBERCTPKbEQEfHDZllcWynjI3K3rcBPoFfoKFUTESl6lFiIiPhht1l0qJbxETnLlncDF69Uhe/+sSIiklO6DCciIiIiIrmmFgsRET+MMaS5jfe5iIiIXJwSCxERP1wew6gVqQB4KroCHI2IiEjBp65QIiIiIiKSa2qxEBHxw2GzeO7aYAAetOujMrfcdjv8VZ8ej65piYgURfq2FBE5jzFgWRZBtoy5jCxLcxrlmmWB/a96NKpPEZGiSImFiIgfs1038IPVBACPJkvNcxoPLyJZ2bNnD1WrVmX9+vU0adIk0OFINqg9WkTEjy9crRn/eyXG/14JT6CDKQJ2eirxwq9388Kvd/N+2o2BDkdE8ln//v2xLAvLsnA4HFSqVIm///3vJCQkBDq0IqV///7cfPPNgQ7DSy0WIiL+GA+pBzb/9bR9YGMpAtJwUuaPjB8Ux6sVD3A0InI5dO3alalTp+JyudiyZQsDBgzg+PHjfPzxx4EOrcBLT0/H6XQGOowcU4uFiIg/lo3gmDoEx9TRGAsRkUsQHBxMdHQ0FStWpHPnztx+++0sWLDAp8zUqVOpW7cuxYoVo06dOkyePPmCx3O73QwcOJCqVasSEhJC7dq1efPNN73bly5ditPpJC4uzme/J598kuuuuw6AvXv3ctNNN1G6dGnCwsKoX78+33zzzQVfMyEhgXvuuYfSpUsTGhpKt27d2LFjh3f7tGnTKFWqFF988QW1atWiWLFidOrUif379/sc57///S/NmjWjWLFiVKtWjZEjR+JynZ3K3LIs3n33XXr16kVYWBivvvpqluc7YsQIpk+fzpdffultHVq8eDEABw8e5Pbbb6d06dJERkbSq1cv9uzZc8HzzCtqsRAR8aOULQVntRoA2Gz2AEcjInKetLQLb7PZwOHIXlnLgnOvjF+obFBQzuI7z65du5g/f77PVfgpU6bw0ksvMWnSJJo2bcr69et54IEHCAsL49577810DI/HQ8WKFfn0008pU6YMK1euZNCgQZQvX54+ffpw3XXXUa1aNWbMmMFTTz0FgMvlYubMmfzzn/8E4OGHHyYtLY2lS5cSFhbGli1bKF78wq2o/fv3Z8eOHcybN4+SJUvyzDPP0L17d7Zs2eI9l+TkZF577TWmT59OUFAQgwcP5o477mDFihUAfPfdd9x999289dZbXHvttfz+++8MGjQIgJdeesn7Wi+99BKjR49m/Pjx2O32LM936NChbN26laSkJKZOnQpAREQEycnJXH/99Vx77bUsXboUh8PBq6++SteuXdm4cSNBufxbXowSCxERPz4I+hdX27YDcB/zAxxN4RdhEmlk7QLgKms7cG1gAxIp7EaNuvC2mjXhrrvOLr/xBqSn+y9bpQr07392ecIESE7OXG7EiByH+NVXX1G8eHHcbjenT58GYNy4cd7tr7zyCmPHjqV3794AVK1alS1btvDee+/5TSycTicjR470LletWpWVK1fy6aef0qdPHwAGDhzI1KlTvYnF119/TXJysnf7vn37uOWWW2jYsCEA1apVu2D8ZxKKFStW0KZNGwBmzZpFbGwsX3zxBbfddhuQ0W1p0qRJtGzZEoDp06dTt25dfvrpJ1q0aMFrr73Gs88+6z2natWq8corr/D000/7JBZ9+/ZlwIABPjFc7HyLFy9OSEgIqampREdHe8vNnDkTm83GBx984G1xnzp1KqVKlWLx4sV07tz5guecW0osREQk38UQzw329QDstccCAwMbkIjku+uvv5533nmH5ORkPvjgA7Zv386jjz4KwJEjR9i/fz8DBw7kgQce8O7jcrkIDw+/4DHfffddPvjgA/bu3UtKSgppaWk+M0b179+fF154gR9//JFWrVrx0Ucf0adPH8LCwgB47LHH+Pvf/86CBQvo2LEjt9xyC40aNfL7Wlu3bsXhcHgTBoDIyEhq167N1q1bvescDgfNmzf3LtepU4dSpUqxdetWWrRowdq1a1mzZg2vvfaat8yZZCs5OZnQ0FAAn2Nk93z9Wbt2LTt37qREiRI+60+fPs3vv/9+0X1zS4mFiMh5DJDu9jBiacYVNnffC1zpExEJlOeeu/A223lDaP+6eu/X+WPIhgy55JDOFxYWRo0aGV1K33rrLa6//npGjhzJK6+8gseTMd/elClTfH64A9jt/ruffvrpp/zjH/9g7NixtG7dmhIlSvDGG2+wevVqb5ly5cpx0003MXXqVKpVq8Y333zjHXcAcP/999OlSxe+/vprFixYwOjRoxk7dqw34TmXucC82MaYTGPv/I3FO7PO4/EwcuRIb8vMuYoVK+Z9fib5ycn5+uPxeGjWrBmzZs3KtK1s2bIX3Te3lFiIiMhlZ9CNLERyJSf95POrbA699NJLdOvWjb///e/ExMRQoUIFdu3axV3ndtu6iGXLltGmTRsGDx7sXefvCvz999/PHXfcQcWKFalevTpt27b12R4bG8tDDz3EQw89xLBhw5gyZYrfxKJevXq4XC5Wr17t7Qr1559/sn37durWrest53K5+Pnnn2nRogUA27Zt4/jx49SpUweAq666im3btnmTrOzKzvkGBQXhdrt91l111VV88sknlCtXjpIlS+boNXNLs0KJiPjhsFk81SaYp9oEY7frGkxuue12aBMMbYLxnH81VUSuCO3bt6d+/fqM+mt8yIgRIxg9ejRvvvkm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CArK3MVEsi/IYaNltYzxFb3vbwWqTxGRQiEpKYl/v/5s1gW5QrpCBQUF0axZMxYu9O3ztnDhQtq0aROgqESkoHM6nTh11+2843RmPEREpEi6IrpCwdnpZt99911at27N+++/z5QpU9i8eTOVK2e+Wdv5zkw3q65QIiIiInKlyMlv4CuiKxTA7bffzp9//snLL7/M4cOHadCgAd988022kgoREREREbm4K6bFIrfUYiEiIiIiVxq1WIiI5JLL5eKbb74BoHv37rpJXm65XPDJJxnPb78dVJ8iIkWOPtlFRPzweDysW5dxp+OuXbsGOJoiwOOBHTvOPhcRkSJHiYWIiB92u50bbrjB+1xEREQuTomFiIgfdrud6667LtBhiIiIFBpXxH0sREREREQkf6nFQkTED2MMycnJAISGhmJZVoAjEhERKdjUYiEi4kd6ejpvvPEGb7zxBunp6YEOR0REpMBTi0U2nbndR1JSUoAjEZHLIS0tjdTUVCDj/z4oKCjAERVyaWnwV32SlASqTxGRQuHMb9/s3PpON8jLpl27dlG9evVAhyEiIiIictnt37+fihUrXrSMWiyyKSIiAoB9+/YRHh4e4GgKv6SkJGJjY9m/f7/uZJ5HVKd5T3Wat1SfeU91mvdUp3lL9Zn3LnedGmM4ceIEMTExWZZVYpFNNlvGcJTw8HD9Y+ShkiVLqj7zmOo076lO85bqM++pTvOe6jRvqT7z3uWs0+xeVNfgbRERERERyTUlFiIiIiIikmtKLLIpODiYl156ieDg4ECHUiSoPvOe6jTvqU7zluoz76lO857qNG+pPvNeQa5TzQolIiIiIiK5phYLERERERHJNSUWIiIiIiKSa0osREREREQk15RYnGPy5MlUrVqVYsWK0axZM5YtW3bR8kuWLKFZs2YUK1aMatWq8e67716mSAuHnNTn4cOH6du3L7Vr18ZmszFkyJDLF2ghkpM6/c9//kOnTp0oW7YsJUuWpHXr1nz33XeXMdqCLyf1uXz5ctq2bUtkZCQhISHUqVOH8ePHX8ZoC4ecfo6esWLFChwOB02aNMnfAAuhnNTp4sWLsSwr0+O33367jBEXbDl9j6ampvL8889TuXJlgoODqV69Oh999NFlirZwyEmd9u/f3+97tH79+pcx4oIvp+/TWbNm0bhxY0JDQylfvjz33Xcff/7552WK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321atmx5ucMtFHJap1OnTjUOh8NMnjzZ/P7772b58uWmefPmpkWLFpc9diUW53j77bdN5cqVTVBQkLnqqqvMkiVLvNvuvfde065dO5/yixcvNk2bNjVBQUGmSpUq5p133rnMERdsOa1PINOjcuXKlzfoAi4nddquXTu/dXrvvfde/sALqJzU51tvvWXq169vQkNDTcmSJU3Tpk3N5MmTjdvtDkDkBVdO/+/PpcTCv5zU6ZgxY0z16tVNsWLFTOnSpc0111xjvv766wBEXXDl9D26detW07FjRxMSEmIqVqxonnjiCZOcnHyZoy7Yclqnx48fNyEhIeb999+/zJEWHjmt07feesvUq1fPhISEmPLly5u77rrLHDhw4DJHbYxlzOVuIxERERERkaJGYyxERERERCTXlFiIiIiIiEiuKbEQEREREZFcU2IhIiIiIiK5psRCRERERERyTYmFiIiIiIjkmhILERERERHJNSUWIiIiIiKSa0osREQkX4wYMYImTZoE7PWHDx/OoEGDslV26NChPPbYY/kckYhI0aY7b4uISI5ZlnXR7ffeey+TJk0iNTWVyMjIyxTVWX/88Qc1a9Zk48aNVKlSJcvy8fHxVK9enY0bN1K1atX8D1BEpAhSYiEiIjkWFxfnff7JJ5/w4osvsm3bNu+6kJAQwsPDAxEaAKNGjWLJkiV899132d7nlltuoUaNGowZMyYfIxMRKbrUFUpERHIsOjra+wgPD8eyrEzrzu8K1b9/f26++WZGjRpFVFQUpUqVYuTIkbhcLp566ikiIiKoWLEiH330kc9rHTx4kNtvv53SpUsTGRlJr1692LNnz0XjmzNnDj179vRZ9+9//5uGDRsSEhJCZGQkHTt25NSpU97tPXv25OOPP8513YiIXKmUWIiIyGXzv//9j0OHDrF06VLGjRvHiBEj6NGjB6VLl2b16tU89NBDPPTQQ+zfvx+A5ORkrr/+eooXL87SpUtZvnw5xYsXp2vXrqSlpfl9jYSEBDZt2kTz5s296w4fPsydd97JgAED2Lp1K4sXL6Z3796c22jfokUL9u/fz969e/O3EkREiiglFiIictlERETw1ltvUbt2bQYMGEDt2rVJTk7mueeeo2bNmgwbNoygoCBWrFgBZLQ82Gw2PvjgAxo2bEjdunWZOnUq+/btY/HixX5fY+/evRhjiImJ8a47fPgwLpeL3r17U6VKFRo2bMjgwYMpXry4t0yFChUAsmwNERER/xyBDkBERK4c9evXx2Y7e00rKiqKBg0aeJftdjuRkZHEx8cDsHbtWnbu3EmJEiV8jnP69Gl+//13v6+RkpICQLFixbzrGjduTIcOHWjYsCFdunShc+fO3HrrrZQuXdpbJiQkBMhoJRERkZxTYiEiIpeN0+n0WbYsy+86j8cDgMfjoVmzZsyaNSvTscqWLev3NcqUKQNkdIk6U8Zut7Nw4UJWrlzJggULmDhxIs8//zyrV6/2zgJ17Nixix5XREQuTl2hRESkwLrqqqvYsWMH5cqVo0aNGj6PC806Vb16dUqWLMmWLVt81luWRdu2bRk5ciTr168nKCiIuXPnerdv2rQJp9NJ/fr18/WcRESKKiUWIiJSYN11112UKVOGXr16sWzZMnbv3s2SJUt4/PHHOXDggN99bDYbHTt2ZPny5d51q1evZtSoUfz888/s27eP//znPxw5coS6det6yyxbtoxrr73W2yVKRERyRomFiIgUWKGhoSxdupRKlSrRu3dv6taty4ABA0hJSaFkyZIX3G/QoEHMmTPH26WqZMmSLF26lO7du1OrVi1eeOEFxo4dS7du3bz7fPzxxzzwwAP5fk4iIkWVbpAnIiJFjjGGVq1aMWTIEO68884sy3/99dc89dRTbNy4EYdDww9FRC6FWixERKTIsSyL999/H5fLla3yp06dYurUqUoqRERyQS0WIiIiIiKSa2qxEBERERGRXFNiISIiIiIiuabEQkREREREck2JhYiIiIiI5JoSCxERERERyTUlFiIiIiIikmtKLEREREREJNeUWIiIiIiISK4psRARERERkVxTYiEiIiIiIrn2/7EXzxVkz9hkAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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ud5dsexdht2atmr2EndNqhh7vJKKHe5iFKjelMiwwMxWlTH/tqCc+LceA7ZUq4jkZgsXLuSWW24hMTHRM/nGtbR7925iYmJYt24dtWvXvubPf60FdIyFiEhOYrVaadOmDW3atPFqEc1sb/2wjpct7xJsOIgwknnZnMD97yyjvnluVe1lZnVua9GKyc60dX+Omnl5zuzNY00qZllcIiIZ0aVLFwzDwDAMbDYbZcuWpX///pw6dcqn88uUKXPFH5+vxsKFCzEMg8jISM6cOeP12OrVqz3xXmsbN26kUaNGhISEULx4cV588UVM89wvLvHx8XTs2JFKlSphsVguOZHI5MmTuemmm4iMjCQyMpKmTZt6jS++FpRYiIj4yGq1EhsbS2xsbJYlFmccLow9y8lrnPvAW+SqxelTSdxk2QiA07RwslgDejUqy5lbXuT2sI94LGo6Ax9+kLKF82ZJXCIiV6Nly5bEx8ezc+dOXn75ZSZNmkT//v0DHRbh4eF89dVXXsemTJlCqVKlrnksx48fp1mzZkRHR7NmzRreeOMNXnvtNcaOHespk5KSQuHChRk8eDC1atW65HUWLlzIfffdxy+//MKKFSsoVaoUzZs358CBA9fqVpRYiIhkJ7/vO8Z1bPbsP5n6CIOd3chnJFPechCA9WZ5YiuWxjAMHm9SgdlPt2XGI42pWSJ/gKIWEbm04OBgoqKiKFmyJB07duT+++9n9uzZlC9f/qJxrJs2bcJisbBjx45LXsswDN577z3uuOMOQkNDqVChAt98841Xmblz51KxYkVCQkK45ZZb2L179yWv1blzZ6ZMmeLZP336NLNmzfJaRw3gn3/+4b777qNEiRKEhoZSo0YNZs6c6VXG7Xbz6quvUr58eYKDgylVqhTDhw/3KrNz505uueUWQkNDqVWrFitWrPA8Nn36dM6cOcO0adOoXr067du3Z9CgQYwdO9bTalGmTBlef/11HnzwQSIiIi55T9OnT6d3797Url2bypUrM3nyZNxuNz/99NMly2cFJRYiIj4yTZNDhw5x6NAhrybqq3XG4eLHzX/z05a/Sf13wbvVu45S17LVU2aRO+2XqaaW3zzHfnbVoXHlIn4/v4jItRYSEoLD4aBr165MnTrV67EpU6Zw0003Ua5cucueP2zYMDp06MCGDRto1aoV999/P0ePHgVg3759tG/f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5htPFykRkdwoKCiIjh07erbFP26LATVs/25rTRARkZwuw5+MjRs35siRIxw/fpzIyEjP8Z49exIaGpqpwYmIZCcWi4WKFSsGOoxcwcBIWyWvoBUAU4mFiEiOl+F38tOnT5OSkuJJKvbs2cP48ePZunUrRYoUyfQARUQk91PHMhGRnC/DLRbt2rWjffv29OrVi2PHjlGvXj1sNhtHjhxh7NixPPLII1kRp4hIwLndbnbt2gVATEwMFv3K7pfpjiZsiy8GwP7S+mFKRCSny/Cn4m+//cZNN90EwOeff07RokXZs2cPH374IRMmTMj0AEVEsgun08lHH33ERx99hNPpDHQ4Od5udxRFth6hyNYjnDZDAh2OiIj4KcMtFsnJyYSHhwMwf/582rdvj8VioX79+uzZsyfTAxQRyS4MwyAqKsqzLSIiIudkOLEoX748s2fP5o477uCHH37gySefBODQoUPky5cv0wMUEckubDYbvXr1CnQYIiIi2VKGu0K98MIL9O/fnzJlylC3bl3i4uKAtNaLOnXqZHqAIiKSO5UzDlDGSKCMkUBe81SgwxERET9luMXirrvu4sYbbyQ+Pt6zhgVAkyZNuOOOOzI1OBERyb3us/7C7dZlAHxj6vNDRCSnu6oVnqKiooiKimL//v0YhkHx4sW1OJ6I5HoOh4Pp06cDcP/992Oz2QIcUc6lISoiIrlPhrtCud1uXnzxRSIiIihdujSlSpUif/78vPTSS7jd7qyIUUQkWzBNk927d7N7925MUysviIiInC/DLRaDBw/m/fff55VXXqFhw4aYpsmyZcsYOnQoZ86cYfjw4VkRp4hIwAUFBXH33Xd7tsU/bosBVW3/bmtNEBGRnC7Dn4wffPAB7733Hm3btvUcq1WrFsWLF6d3795KLEQk17JYLFSrVi3QYeQehgFFrACYSixERHK8DL+THz16lMqVK190vHLlyhw9ejRTghIRERERkZwlw4lFrVq1mDhx4kXHJ06c6DVLlIhIbuN2u9m7dy979+7VmLLMYJpwyAWHXBiqTxGRHC/DXaFGjRrFbbfdxo8//khcXByGYbB8+XL27dvH3LlzsyJGEZFswel0MmXKFAAGDRqE3W4PcEQ5m8VtwmZH2nZhJRYiIjldhlssGjVqxLZt27jjjjs4duwYR48epX379mzdupWbbropK2IUEckWDMOg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vC0iIiIiInI5arEQEfGRy+ViyZIlANx0001aJM8PRqADEBGRTKfEQkTERy6Xi4ULFwLQoEEDJRZ+Om6GctwMBcChjyMRkRxP7+QiIj6yWCzccMMNnm3xz1vu2/mjSCkANhoVAxyNiIj4S4mFiIiPgoKCuO222wIdRq7hslj5pVxaohZuUeuPiEhOp5/cRERERETEb2qxEBGRwDBNQhxpK28THBbYWERExG9KLEREfJSamsorr7wCwIABA7Db7QGOKGe7k8X0+vVzAAY2eibA0YiIiL+UWIiIZIDb7Q50CLlGRcs+KlgOAJCfEwGORkRE/KXEQkTERzabjaeeesqzLVfP0EIWIiK5jhILEREfGYZBvnz5Ah2GiIhItqRZoURERERExG9qsRAR8ZHL5WLlypUA1K9fXytvZyYz0AGIiIi/lFiIiPjI5XKxYMECAG644QYlFiIiIudRYiEi4iOLxULt2rU92+If0zAgynpuW0REcjQlFiIiPgoKCuL2228PdBi5hmmxQOW02bXcplp/RERyOv3kJiIiIiIiflOLhYiIBIDB766yfOJqBMARW0SA4xEREX8psRAR8VFqaipjx44F4KmnnsJutwc4opztJ+d1VF7xFwD7GxcJcDQiIuIvJRYiIhlw5syZQIcgIiKSLSmxEBHxkc1m4/HHH/dsi4iIyDlKLEREfGQYBgULFgx0GLmS1scTEcn5lFiIiEhA9An6koetXwOwgTpA68AGJCIifgnodLMjR47khhtuIDw8nCJFinD77bezdetWrzKmaTJ06FCio6MJCQmhcePG/PHHH15lUlJSePzxxylUqBBhYWG0bduW/fv3e5VJTEykU6dOREREEBERQadOnTh27JhXmb1799KmTRvCwsIoVKgQffr0ITU1NUvuXURyHpfLxerVq1m9ejUulyvQ4eR4NpzYjbQ/C+5AhyMiIn4KaGKxaNEiHn30UVauXMmCBQtwOp00b96cU6dOecqMGjWKsWPHMnHiRNasWUNUVBTNmjXjxIkTnjJ9+/blq6++YtasWSxdupSTJ0/SunVrrw/+jh07sn79eubNm8e8efNYv349nTp18jzucrm47bbbOHXqFEuXLmXWrFl88cUX9OvX79pUhohkey6Xi7lz5zJ37lwlFiIiIhcIaFeoefPmee1PnTqVIkWKsHbtWm6++WZM02T8+PEMHjyY9u3bA/DBBx9QtGhRZsyYwcMPP0xSUhLvv/8+H330EU2bNgXg448/pmTJkvz444+0aNGCLVu2MG/ePFauXEm9evUAmDx5MnFxcWzdupVKlSoxf/58Nm/ezL59+4iOjgZgzJgxdOnSheHDh5MvX75rWDMikh1ZLBaqVq3q2ZarZxhgGgYUTltx2zSMAEckIiL+ylafjElJSQAUKFAAgF27dpGQkEDz5s09ZYKDg2nUqBHLly8HYO3atTgcDq8y0dHRVK9e3VNmxYoVREREeJIKgPr16xMREeFVpnr16p6kAqBFixakpKSwdu3aLLpjEclJgoKC6NChAx06dCAoSEPU/GVaLFDNBtVsuC3WQIcjIiJ+yjafjKZp8tRTT3HjjTdSvXp1ABISEgAoWrSoV9miRYuyZ88eTxm73U5kZORFZc6en5CQQJEiFy++VKRIEa8yFz5PZGQkdrvdU+ZCKSkppKSkePaPHz/u8/2KiIiIiOQm2abF4rHHHmPDhg3MnDnzoseMC5rITdO86NiFLixzqfJXU+Z8I0eO9AwGj4iIoGTJkleMSUREztEUsyIiuUu2SCwef/xxvvnmG3755RdKlCjhOR4VFQVwUYvBoUOHPK0LUVFRpKamkpiYeMUyf//990XPe/jwYa8yFz5PYmIiDofjopaMswYOHEhSUpLnb9++fRm5bRHJYRwOB2PGjGHMmDE4HI5Ah5PjWVwuWHgGFp7B6lR9iojkdAFNLEzT5LHHHuPLL7/k559/JiYmxuvxmJgYoqKiWLBggedYamoqixYtokGDBgDExsZis9m8ysTHx7Np0yZPmbi4OJKSkli9erWnzKpVq0hKSvIqs2nTJuLj4z1l5s+fT3BwMLGxsZeMPzg4mHz58nn9iUjuZZomJ06c4MSJE5imfm8XERE5X0DHWDz66KPMmDGDr7/+mvDwcE+LQUREBCEhIRiGQd++fRkxYgQVKlSgQoUKjBgxgtDQUDp27Ogp261bN/r160fBggUpUKAA/fv3p0aNGp5ZoqpUqULLli3p0aMH77zzDgA9e/akdevWVKpUCYDmzZtTtWpVOnXqxOjRozl69Cj9+/enR48eShhEBEgbvN2rVy/PtoiIiJwT0E/Gt956C4DGjRt7HZ86dSpdunQB4JlnnuH06dP07t2bxMRE6tWrx/z58wkPD/eUHzdunGe2ltOnT9OkSROmTZuG1XpulpHp06fTp08fz+xRbdu2ZeLEiZ7HrVYrc+bMoXfv3jRs2JCQkBA6duzIa6+9lkV3LyI5jcVi8XTRFP9962pASdcBAHagMWoiIjmdYao9P9McP36ciIgIkpKS1MohInIFv+1N5J43FvHoik8BmNboPtYPbxPgqERE5EIZ+X6rtnwRER+5XC42btwIQI0aNbxaRSVjtByeiEjuo8RCRMRHLpeL2bNnA1C1alUlFiIiIudRYiEi4iOLxUKFChU82+KfYsY/UCCt7SLMOB3gaERExF9KLEREfBQUFMT9998f6DByjQ72RTx23XcA/O6uE+BoRETEX/rJTURERERE/KbEQkRERERE/KauUCIiPnI4HJ71dx555BFsNluAI8rZLC4XLE8BwBrnDHA0IiLiLyUWIiI+Mk2To0ePerYlE7jT6tFA9SkiktMpsRAR8VFQUBBdu3b1bMvVMwytZCEiktvok1FExEcWi4VSpUoFOgwREZFsSYO3RURERETEb2qxEBHxkdvtZsuWLQBUqVJFi+SJiIicR5+KIiI+cjqdfPbZZ3z22Wc4nZrFKDNpLLyISM6nFgsRER8ZhkGZMmU82+Kfic472B8WCcCvVA1wNCIi4i8lFiIiPrLZbHTp0iXQYeQaydYQZtS4FYAQqzXA0YiIiL/UFUpERERERPymxEJERK45dSQTEcl91BVKRMRHDoeD999/H4Bu3bphs9kCHFHOdpO5nod//QqAEfV6BTgaERHxlxILEREfmaZJQkKCZ1v8U8+yhRtdGwEozqEARyMiIv5SYiEi4qOgoCA6derk2RYREZFz9MkoIuIji8VCuXLlAh1GrqT2HxGRnE+Dt0VERERExG9qsRAR8ZHb7eavv/4CoHz58lgs+m1GRETkLH0qioj4yOl0MmPGDGbMmIHT6Qx0OCIiItmKWixERHxkGAbR0dGebbl6adVnQPjZ37dUnyIiOZ0SCxERH9lsNnr27BnoMHINt9UCsXYAXC5rgKMRERF/qSuUiIiIiIj4TS0WIiISEAfNQvzqrgjACUIDHI2IiPhLiYWIiI8cDgcffvghAA8++CA2my3AEeVsn6feTOhvJwD4s17pAEcjIiL+UmIhIuIj0zTZt2+fZ1v8Y2CSL+Vk2o7qU0Qkx1NiISLio6CgIO69917PtoiIiJyjT0YRER9ZLBYqV64c6DBERESyJSUWIiJyzRkY3GP9hXutvwCwiusDHJGIiPhLiYWIiI/cbjd79+4FoFSpUlgsmrHbH0WNRKKMowDkNZIDHI2IiPhLn4oiIj5yOp1MmzaNadOm4XQ6Ax2OiIhItqIWCxERHxmGQeHChT3b4i8DQi3ntkVEJEdTYiEi4iObzcajjz4a6DByDbfVAnXtALhc1gBHIyIi/lJXKBERCTitYiEikvMpsRAREREREb+pK5SIiI8cDgczZ84E4L777sNmswU4opzN4nLDb6kAWGu7AhyNiIj4S4mFiIiPTNNk586dnm3xlwnJ7nPbIiKSoymxEBHxUVBQEO3bt/dsy9XTpFoiIrmPPhlFRHxksVioWbNmoMPINVa6q3C9qyoA+8yiAY5GRET8pcRCREQCYrW7CqvMKgDsR4mFiEhOp8RCRMRHbreb+Ph4AIoVK4bFoon1REREztKnooiIj5xOJ5MnT2by5Mk4nc5Ah5O7aOy2iEiOpxYLEREfGYZB/vz5PdviHzsOUoPTpuy1oulmRURyOiUWIiI+stls9O3bN9Bh5Brd7d/z5E2zAdjqrBzYYERExG/qCiUiIiIiIn5TYiEiIiIiIn5TVygRER85nU4+//xzAO666y4tkucni8sNG1IBsFbXGAsRkZxOn4oiIj5yu938+eefnm3xlwkn3Oe2RUQkR1NiISLiI6vVSps2bTzbIiIico4SCxERH1mtVmJjYwMdhoiISLakwdsiIhJwprpCiYjkeGqxEBHxkWmaHD58GIDChQtrkTwREZHzqMVCRMRHDoeDSZMmMWnSJBwOR6DDERERyVbUYiEikgGhoaGBDiFXMAz4zNWI/EYiAL+bFQIckYiI+EuJhYiIj+x2O88880ygw8g19luLMqTewwDYrOpWJiKS06krlIiIiIiI+E2JxQUmTZpETEwMefLkITY2liVLlgQ6JBERERGRbE+JxXk++eQT+vbty+DBg1m3bh033XQTt956K3v37g10aCKSDTidTr744gu++OILnE5noMPJ8Sq79/DSprd5adPbRLkOBzocERHxk8ZYnGfs2LF069aN7t27AzB+/Hh++OEH3nrrLUaOHBng6EQk0NxuNxs3bgTwrMAtV6+lZTWdTs4HYDE3sH7fscAGJCIiFzl54rjPZZVY/Cs1NZW1a9cyYMAAr+PNmzdn+fLlAYpKRLITq9VKy5YtPduSeW63LGHpO395HXNhZZzzLq9jzSy/UsuyI93r/eUuzmz3jV7Heli/I8I4le65P7piWW+W9+xHcpxuQd+nex7AZOdtJJHXs3+dsY3/Wdele16imZf3Xbd5HbvDsoRyloPpnrvOXZ6f3N4rwj8V9CkWHxYd/Mp1IzvM4p79UsbfdLAuTPc8gLHOu3Gf1/GhkeV3brD8me55e80ifOq6xetYZ+sPFDaOpXvuYldNVptVPPuhnKF30Nc+xfuBszmHifTsVzN2c6t1VbrnJZvBTHLd7nXsNstKqlj2pHvuZndp5rrrex171DqbECMl3XPnuuqx2Szj2S/KUToFLUj3PIA3ne04TR7Pfn3LZm60bEz3vL/NSD5yNfc6dq/1Z0oY6bcornJXYYm7pmc/CCd9g77wKd5ZrlvYbxbx7Jc39nO7dVm65+k94tq8R2w/E5luubOUWPzryJEjuFwuihYt6nW8aNGiJCQkXPKclJQUUlLOvTkcP+57RiciOY/VaqV+/frpF5R0GRheH2m3WVfBBTNDnTFtF31paGT5nQeCfkr3+vNdsRd9aehkXUApS/pfkBLMAqx3nfvSEGGc4jEfv7x+4mpMknnuS0N1yy6fzt3lLnrRl4bbrCtp6sMXjg+czS760vCI9Vtshivdc9e5y3slFsWNIz7f63jnnV6JRZxlM72Cvk33vOWuqhclFvdYF1LVhy/qJ80QVrvOTyxSfI53rqseh81zX5AqGXt9Ovewme+ixKKZ9Vdut6b/o+MXrhsvSiy6Bc2lgHEy3XO3u4t7JRaFjWM+3+v7zlu9EotYY5tP5250l7kosbjDupR6PiSMOPFKLKy4fY53sasm+zmXWJQ14n06V+8R1+Y9Yju+JxYaY3GBC1fSNU3zsqvrjhw5koiICM9fyZIlr0WIIiI5XplCoawxquM2Nc2siEhuYZimmX47yH9AamoqoaGhfPbZZ9xxxx2e40888QTr169n0aJFF51zqRaLkiVLkpSURL58+a5J3CJy7ZimSVJSEgARERGX/dFBfPP9b3s4+Wx/7KnH+O7G/+GyejeiuzFYTXWvYzEcoChH0712IuFspYzXsTr8STDpr5i+i2j+pqBnPw8p1GZbuucBrKMSKdg9+1EcoQzx6Z53BjvrqeR1rBK7ieREuucmUJDdRHsdq8emC9qELm0rpUnk3OdVBCepwq50zwNYRTXM836fLEUC0aT/a28SYWyhrNexmmwnlDPpnruXohw875dtGw5i8eHXdGAD5UkmxLNfiETKsz/d8xwEsZYqXsfKs5dCJKV77hHy8xfePzrGshkb6f9S/BclOUJ+z34YydQg/S4+AGupguO8TinFOURJ/k73vFOEsJHyXseqspN8pN896ACF2UeUZ9+Cm7r84VO8m4nh+HndgyI5TiXSb8HSe8S1eY84kmJl26i7fPp+q8TiPPXq1SM2NpZJkyZ5jlWtWpV27dr5NHj7+PHjREREKLEQyaVSU1MZMWIEAIMGDcJut6dzhlxRair8W58MGgSqTxGRbCcj3281xuI8Tz31FJ06deL6668nLi6Od999l71799KrV69AhyYi2YTNZgt0CLmL6lNEJNdQi8UFJk2axKhRo4iPj6d69eqMGzeOm2++2adz1WIhIiIiIrlJRr7fKrHIREosRERERCQ3ycj3W80KJSIiIiIiftMYCxERHzmdTubOnQtAq1atCArSW6hfnE745JO07XvuAdWniEiOpndxEREfud1ufvvtNwDPCtziB7cbtm8/ty0iIjmaEgsRER9ZrVb+97//ebZFRETkHCUWIiI+slqtPs8SJyIi8l+jwdsiIiIiIuI3tViIiPjINE2Sk5MBCA0NxTCMAEckIiKSfajFQkTERw6Hg9GjRzN69GgcDkegwxEREclW1GKRic6uNXj8+PEARyIiWSE1NZWUlBQg7f+53W4PcEQ5XGoq/FufHD8Oqk8RkWzn7PdaX9bU1srbmWjnzp2UK1cu0GGIiIiIiGSqffv2UaJEiSuWUYtFJipQoAAAe/fuJSIiIsDR5GzHjx+nZMmS7Nu3L93l4yV9qs/Mo7rMXKrPzKO6zFyqz8yl+sw817ouTdPkxIkTREdHp1tWiUUmsljShqxEREToP00myZcvn+oyE6k+M4/qMnOpPjOP6jJzqT4zl+oz81zLuvT1B3MN3hYREREREb8psRAREREREb8pschEwcHBDBkyhODg4ECHkuOpLjOX6jPzqC4zl+oz86guM5fqM3OpPjNPdq5LzQolIiIiIiJ+U4uFiIiIiIj4TYmFiIiIiIj4TYmFiIiIiIj4TYlFBkyaNImYmBjy5MlDbGwsS5YsuWL5RYsWERsbS548eShbtixvv/32NYo0Z8hIfcbHx9OxY0cqVaqExWKhb9++1y7QHCIj9fnll1/SrFkzChcuTL58+YiLi+OHH364htFmbxmpy6VLl9KwYUMKFixISEgIlStXZty4cdcw2uwvo++dZy1btoygoCBq166dtQHmIBmpy4ULF2IYxkV/f/755zWMOHvL6GszJSWFwYMHU7p0aYKDgylXrhxTpky5RtFmbxmpyy5dulzytVmtWrVrGHH2ltHX5vTp06lVqxahoaEUK1aMhx56iH/++ecaRXseU3wya9Ys02azmZMnTzY3b95sPvHEE2ZYWJi5Z8+eS5bfuXOnGRoaaj7xxBPm5s2bzcmTJ5s2m838/PPPr3Hk2VNG63PXrl1mnz59zA8++MCsXbu2+cQTT1zbgLO5jNbnE088Yb766qvm6tWrzW3btpkDBw40bTab+dtvv13jyLOfjNblb7/9Zs6YMcPctGmTuWvXLvOjjz4yQ0NDzXfeeecaR549ZbQ+zzp27JhZtmxZs3nz5matWrWuTbDZXEbr8pdffjEBc+vWrWZ8fLznz+l0XuPIs6ereW22bdvWrFevnrlgwQJz165d5qpVq8xly5Zdw6izp4zW5bFjx7xek/v27TMLFChgDhky5NoGnk1ltD6XLFliWiwW8/XXXzd37txpLlmyxKxWrZp5++23X+PITVOJhY/q1q1r9urVy+tY5cqVzQEDBlyy/DPPPGNWrlzZ69jDDz9s1q9fP8tizEkyWp/na9SokRKLC/hTn2dVrVrVHDZsWGaHluNkRl3ecccd5gMPPJDZoeVIV1uf99xzj/ncc8+ZQ4YMUWLxr4zW5dnEIjEx8RpEl/NktD6///57MyIiwvznn3+uRXg5ir/vm1999ZVpGIa5e/furAgvx8lofY4ePdosW7as17EJEyaYJUqUyLIYL0ddoXyQmprK2rVrad68udfx5s2bs3z58kues2LFiovKt2jRgl9//RWHw5FlseYEV1OfcnmZUZ9ut5sTJ05QoECBrAgxx8iMuly3bh3Lly+nUaNGWRFijnK19Tl16lR27NjBkCFDsjrEHMOf12adOnUoVqwYTZo04ZdffsnKMHOMq6nPb775huuvv55Ro0ZRvHhxKlasSP/+/Tl9+vS1CDnbyoz3zffff5+mTZtSunTprAgxR7ma+mzQoAH79+9n7ty5mKbJ33//zeeff85tt912LUL2EnTNnzEHOnLkCC6Xi6JFi3odL1q0KAkJCZc8JyEh4ZLlnU4nR44coVixYlkWb3Z3NfUpl5cZ9TlmzBhOnTpFhw4dsiLEHMOfuixRogSHDx/G6XQydOhQunfvnpWh5ghXU5/bt29nwIABLFmyhKAgfUSddTV1WaxYMd59911iY2NJSUnho48+okmTJixcuJCbb775WoSdbV1Nfe7cuZOlS5eSJ08evvrqK44cOULv3r05evTof3qchb+fQfHx8Xz//ffMmDEjq0LMUa6mPhs0aMD06dO55557OHPmDE6nk7Zt2/LGG29ci5C96F07AwzD8No3TfOiY+mVv9Tx/6qM1qdc2dXW58yZMxk6dChff/01RYoUyarwcpSrqcslS5Zw8uRJVq5cyYABAyhfvjz33XdfVoaZY/hany6Xi44dOzJs2DAqVqx4rcLLUTLy2qxUqRKVKlXy7MfFxbFv3z5ee+21/3xicVZG6tPtdmMYBtOnTyciIgKAsWPHctddd/Hmm28SEhKS5fFmZ1f7GTRt2jTy58/P7bffnkWR5UwZqc/NmzfTp08fXnjhBVq0aEF8fDxPP/00vXr14v33378W4XoosfBBoUKFsFqtF2WKhw4duiijPCsqKuqS5YOCgihYsGCWxZoTXE19yuX5U5+ffPIJ3bp147PPPqNp06ZZGWaO4E9dxsTEAFCjRg3+/vtvhg4d+p9PLDJanydOnODXX39l3bp1PPbYY0DalznTNAkKCmL+/Pn873//uyaxZzeZ9b5Zv359Pv7448wOL8e5mvosVqwYxYsX9yQVAFWqVME0Tfbv30+FChWyNObsyp/XpmmaTJkyhU6dOmG327MyzBzjaupz5MiRNGzYkKeffhqAmjVrEhYWxk033cTLL798TXvJaIyFD+x2O7GxsSxYsMDr+IIFC2jQoMElz4mLi7uo/Pz587n++uux2WxZFmtOcDX1KZd3tfU5c+ZMunTpwowZMwLSDzM7yqzXpmmapKSkZHZ4OU5G6zNfvnxs3LiR9evXe/569epFpUqVWL9+PfXq1btWoWc7mfXaXLdu3X+6K+5ZV1OfDRs25ODBg5w8edJzbNu2bVgsFkqUKJGl8WZn/rw2Fy1axF9//UW3bt2yMsQc5WrqMzk5GYvF+yu91WoFzvWWuWau+XDxHOrs1F/vv/++uXnzZrNv375mWFiYZwaDAQMGmJ06dfKUPzvd7JNPPmlu3rzZfP/99zXd7HkyWp+maZrr1q0z161bZ8bGxpodO3Y0161bZ/7xxx+BCD/byWh9zpgxwwwKCjLffPNNryn/jh07FqhbyDYyWpcTJ040v/nmG3Pbtm3mtm3bzClTppj58uUzBw8eHKhbyFau5v/6+TQr1DkZrctx48aZX331lblt2zZz06ZN5oABA0zA/OKLLwJ1C9lKRuvzxIkTZokSJcy77rrL/OOPP8xFixaZFSpUMLt37x6oW8g2rvb/+QMPPGDWq1fvWoeb7WW0PqdOnWoGBQWZkyZNMnfs2GEuXbrUvP766826dete89iVWGTAm2++aZYuXdq02+3mddddZy5atMjzWOfOnc1GjRp5lV+4cKFZp04d0263m2XKlDHfeuutaxxx9pbR+gQu+itduvS1DToby0h9NmrU6JL12blz52sfeDaUkbqcMGGCWa1aNTM0NNTMly+fWadOHXPSpEmmy+UKQOTZU0b/r59PiYW3jNTlq6++apYrV87MkyePGRkZad54443mnDlzAhB19pXR1+aWLVvMpk2bmiEhIWaJEiXMp556ykxOTr7GUWdPGa3LY8eOmSEhIea77757jSPNGTJanxMmTDCrVq1qhoSEmMWKFTPvv/9+c//+/dc4atM0TPNat5GIiIiIiEhuozEWIiIiIiLiNyUWIiIiIiLiNyUWIiIiIiLiNyUWIiIiIiLiNyUWIiIiIiLiNyUWIiIiIiLiNyUWIiIiIiLiNyUWIiIiIiLiNyUWIiJyzQwdOpTatWsH7Pmff/55evbs6VPZ/v3706dPnyyOSEQk99DK2yIikikMw7ji4507d2bixImkpKRQsGDBaxTVOX///TcVKlRgw4YNlClTJt3yhw4doly5cmzYsIGYmJisD1BEJIdTYiEiIpkiISHBs/3JJ5/wwgsvsHXrVs+xkJAQIiIiAhEaACNGjGDRokX88MMPPp9z5513Ur58eV599dUsjExEJHdQVygREckUUVFRnr+IiAgMw7jo2IVdobp06cLtt9/OiBEjKFq0KPnz52fYsGE4nU6efvppChQoQIkSJZgyZYrXcx04cIB77rmHyMhIChYsSLt27di9e/cV45s1axZt27b1Ovb5559To0YNQkJCKFiwIE2bNuXUqVOex9u2bcvMmTP9rhsRkf8CJRYiIhJQP//8MwcPHmTx4sWMHTuWoUOH0rp1ayIjI1m1ahW9evWiV69e7Nu3D4Dk5GRuueUW8ubNy+LFi1m6dCl58+alZcuWpKamXvI5EhMT2bRpE9dff73nWHx8PPfddx9du3Zly5YtLFy4kPbt23N+Q37dunXZt28fe/bsydpKEBHJBZRYiIhIQBUoUIAJEyZQqVIlunbtSqVKlUhOTmbQoEFUqFCBgQMHYrfbWbZsGZDW8mCxWHjvvfeoUaMGVapUYerUqezdu5eFCxde8jn27NmDaZpER0d7jsXHx+N0Omnfvj1lypShRo0a9O7dm7x583rKFC9eHCDd1hAREYGgQAcgIiL/bdWqVcNiOfc7V9GiRalevbpn32q1UrBgQQ4dOgTA2rVr+euvvwgPD/e6zpkzZ9ixY8cln+P06dMA5MmTx3OsVq1aNGnShBo1atCiRQuaN2/OXXfdRWRkpKdMSEgIkNZKIiIiV6bEQkREAspms3ntG4ZxyWNutxsAt9tNbGws06dPv+hahQsXvuRzFCpUCEjrEnW2jNVqZcGCBSxfvpz58+fzxhtvMHjwYFatWuWZBero0aNXvK6IiJyjrlAiIpKjXHfddWzfvp0iRYpQvnx5r7/LzTpVrlw58uXLx+bNm72OG4ZBw4YNGTZsGOvWrcNut/PVV195Ht+0aRM2m41q1apl6T2JiOQGSixERCRHuf/++ylUqBDt2rVjyZIl7Nq1i0WLFvHEE0+wf//+S55jsVho2rQpS5cu9RxbtWoVI0aM4Ndff2Xv3r18+eWXHD58mCpVqnjKLFmyhJtuusnTJUpERC5PiYWIiOQooaGhLF68mFKlStG+fXuqVKlC165dOX36NPny5bvseT179mTWrFmeLlX58uVj8eLFtGrViooVK/Lcc88xZswYbr31Vs85M2fOpEePHll+TyIiuYEWyBMRkf8E0zSpX78+ffv25b777ku3/Jw5c3j66afZsGEDQUEakigikh61WIiIyH+CYRi8++67OJ1On8qfOnWKqVOnKqkQEfGRWixERERERMRvarEQERERERG/KbEQERERERG/KbEQERERERG/KbEQERERERG/KbEQERERERG/KbEQERERERG/KbEQERERERG/KbEQERERERG/KbEQERERERG/KbEQERERERG//R8fwV/Pkn9LGgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAxYAAAGFCAYAAABg02VjAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACNWElEQVR4nOzdd3wUdf7H8ddsSyMsBAhJ6E2kgyAQsKB0aR6eqGgkiqCiIAI/FWx4CihIUTnLoYIFxPM4bGgE9RCRKhoFAUGlChHEkFCT3Z35/bFhYQllQ4KbxPfz8dgHU74z85lJyO5nv82wLMtCRERERESkEGzhDkBEREREREo+JRYiIiIiIlJoSixERERERKTQlFiIiIiIiEihKbEQEREREZFCU2IhIiIiIiKFpsRCREREREQKTYmFiIiIiIgUmhILEREREREpNCUWIiIiIiJSaI5wXvyFF17ghRdeYOvWrQA0atSIRx55hO7duwOQmprKa6+9FnRMmzZtWLFiRWA9JyeHUaNG8dZbb3HkyBE6duzI888/T9WqVQNlMjMzGTZsGO+//z4AvXv35rnnnqNcuXKBMtu3b+euu+7i888/Jyoqiv79+/P000/jcrlCvh/TNNm1axexsbEYhlHQxyEiIiIiUqxYlsWBAwdISkrCZjtznURYE4uqVavy5JNPUrduXQBee+01+vTpw7fffkujRo0A6NatGzNnzgwcc/IH/eHDh/PBBx8wd+5cKlSowMiRI+nZsydr1qzBbrcD0L9/f3bu3ElaWhoAgwcPJiUlhQ8++AAAn89Hjx49qFSpEkuXLmXfvn0MGDAAy7J47rnnQr6fXbt2Ua1atXN/ICIiIiIixdCOHTuCvrg/FcOyLOtPiickcXFxTJo0iYEDB5Kamsr+/ft59913T1k2KyuLSpUq8cYbb3DdddcBxz/cf/TRR3Tt2pUNGzbQsGFDVqxYQZs2bQBYsWIFycnJbNy4kfr16/Pxxx/Ts2dPduzYQVJSEgBz584lNTWVPXv2ULZs2ZBiz8rKoly5cuzYsSPkY0Sk5DBNk19++QWA2rVrn/WbGzmL3FyYPNm/PHIkFKCGWERE/hzZ2dlUq1aN/fv343a7z1g2rDUWJ/L5fLzzzjscOnSI5OTkwPbFixcTHx9PuXLluPzyyxk3bhzx8fEArFmzBo/HQ5cuXQLlk5KSaNy4McuWLaNr164sX74ct9sdSCoA2rZti9vtZtmyZdSvX5/ly5fTuHHjQFIB0LVrV3JyclizZg1XXHHFKWPOyckhJycnsH7gwAEAypYtq8RCpBTKzc0NNKkcM2ZMgZpKyink5kJEhH+5bFklFiIixVgozfzDnlisXbuW5ORkjh49SpkyZZg/fz4NGzYEoHv37lx77bXUqFGDLVu28PDDD3PllVeyZs0aIiIiyMjIwOVyUb58+aBzVq5cmYyMDAAyMjICiciJ4uPjg8pUrlw5aH/58uVxuVyBMqcyYcIEHnvssULdv4iUHIZhBL6AUD+qImAYcOwLHT1PEZESL+yJRf369UlPT2f//v3MmzePAQMG8MUXX9CwYcNA8yaAxo0b06pVK2rUqMGCBQvo27fvac9pWVbQm/6pPgCcS5mTjR49mhEjRgTWj1UViUjp5HQ6GTx4cLjDKD2cTtDzFBEpNcLeQNjlclG3bl1atWrFhAkTaNasGc8888wpyyYmJlKjRg02b94MQEJCArm5uWRmZgaV27NnT6AGIiEhgd9++y3fufbu3RtU5uSaiczMTDweT76ajBNFREQEmj2p+ZOIiIiI/JWFvcbiZJZlBfVbONG+ffvYsWMHiYmJALRs2RKn08miRYvo168fALt372bdunVMnDgRgOTkZLKysli1ahWtW7cGYOXKlWRlZdGuXbtAmXHjxrF79+7AuRcuXEhERAQtW7Y8r/crIiIicjaWZeH1evH5fOEORUoZu92Ow+Eokia+YR0VasyYMXTv3p1q1apx4MAB5s6dy5NPPklaWhrJycmMHTuWa665hsTERLZu3cqYMWPYvn07GzZsIDY2FoA777yTDz/8kFmzZhEXF8eoUaPYt29f0HCz3bt3Z9euXbz00kuAf7jZGjVqBA0327x5cypXrsykSZP4448/SE1N5eqrry7QcLPZ2dm43W6ysrJUeyFSCnk8Hl5//XUAbr75ZpxOZ5gjKuE8HvjnP/3Ld93lbxolIvnk5uaye/duDh8+HO5QpJSKjo4mMTHxlIOSFOTzbVhrLH777TdSUlLYvXs3brebpk2bkpaWRufOnTly5Ahr167l9ddfZ//+/SQmJnLFFVfw9ttvB5IKgKlTp+JwOOjXr19ggrxZs2YFkgqA2bNnM2zYsMDoUb1792b69OmB/Xa7nQULFjBkyBDat28fNEGeiMgxlmWxY8eOwLIUkmXB/v3Hl0UkH9M02bJlC3a7naSkJFwulwaPkCJjWRa5ubns3buXLVu2UK9evUINpV7s5rEoyVRjIVK6mabJpk2bALjgggs0j0Vh5ebC+PH+5TFjNNysyCkcPXqULVu2UKNGDaKjo8MdjpRShw8fZtu2bdSqVYvIyMigfSWmxkJEpCSx2WxceOGF4Q5DRP6C9EWGnE9F9ful31IRERERESk01ViIiITINE3e/HQNP+05SErnltSrrCaPIiIix6jGQkQkRG+t+IU3Hr+DvTOu5+Fn/kVG1lEAtu87zMyvtvC/H/eoU7eISBEyDIN33303bNevWbMm06ZNC9v1SxolFiIiIfpgzRaujl1P45hs3rD/g8c/XM/6Xdk89uzzNP3kWn58414mf7Ix3GGWHIYBlSr5XxrlRqTUOTZ0f1EyDAPDMFixYkXQ9pycHCpUqIBhGCxevLhIr3k2mZmZpKSk4Ha7cbvdpKSksP/YiHd57rnnHlq2bElERATNmzfPd47FixfTp08fEhMTiYmJoXnz5syePfvPuYEipKZQIiIhWvfrfua2Pj5y0YK1uzFzD/M0UylvO0hL22buXVqDjORHSHBHnuFMAvjnrbjrrnBHIVJimKZF5uHcsMZQPtqFzRbeLwKqVavGzJkzadu2bWDb/PnzKVOmDH/88cefHk///v3ZuXMnaWlpgH++tJSUlMB8aeAf1vXWW29l5cqVfP/99/nOsWzZMpo2bcr9999P5cqVWbBgATfffDNly5alV69ef9q9FJYSCxGRcxTNUayf11DecTCw7SpjOZ9t/I0b29QIY2QiUhplHs6l5ROfhjWGNQ91okKZiAIf16FDB5o2bUpkZCQvv/wyLpeLO+64g7FjxwbKbN68mYEDB7Jq1Spq167NM888c8pzDRgwgGeffZZp06YRFRUFwKuvvsqAAQN4/PHHg8ref//9zJ8/n507d5KQkMCNN97II488EjTB6fvvv88//vEP1q1bR5kyZbjsssv473//G9h/+PBhbr31Vt555x3Kly/PQw89xODBgwHYsGEDaWlprFixgjZt2gAwY8YMkpOT+fHHH6lfvz4Azz77LAB79+49ZWIxZsyYoPVhw4bxySefMH/+/BKVWKgplIjIOTKwuIjgpk8X2Tbz7bbMMEUkIlJ8vfbaa8TExLBy5UomTpzIP/7xDxYtWgT4B8fo27cvdrudFStW8OKLL3L//fef8jwtW7akVq1azJs3D4AdO3awZMkSUlJS8pWNjY1l1qxZrF+/nmeeeYYZM2YwderUwP4FCxbQt29fevTowbfffstnn31Gq1atgs4xefJkWrVqxbfffsuQIUO488472bjR/7d/+fLluN3uQFIB0LZtW9xuN8uWLSvU88rKyiIuLq5Q5/izqcZCRCRELsPk9e/8zRCqNmjBIaJYYjbF7jUZ6PgYgArGAfbs2Aw0D1+gJYXHA//6l3958GB/0ygRKbWaNm3Ko48+CkC9evWYPn06n332GZ07d+bTTz9lw4YNbN26lapVqwIwfvx4unfvfspz3XLLLbz66qvcdNNNzJw5k6uuuopKlSrlK/fQQw8FlmvWrMnIkSN5++23ue+++wAYN24c119/PY899ligXLNmzYLOcdVVVzFkyBDAXwMydepUFi9ezIUXXkhGRgbx8fH5rhsfH09GRkZBHk+Q//znP6xevZqXXnrpnM8RDkosRERClOiO4JdME4Ak/G2Ml5pNWGo2IQcnQxzvA+DK3IzPtLAZ8G76r/zv+1+oWD6OoR0voHyMZpcOsCzYu/f4soiUak2bNg1aT0xMZM+ePYC/SVH16tUDSQVAcnLyac9100038cADD/DLL78wa9asQFOjk/3nP/9h2rRp/PTTTxw8eBCv1xs0e3R6ejqDBg0KOW7DMEhISAjEfWzbySzLOuX2UCxevJjU1FRmzJhBo0aNzukc4aLEQkQkRBXKRNO3gf9b9X02A8zj+36xEgPL1a1d7Mw8zKotf/Dbuw/yrOM91vxcjxE7x/PqnZ3P+c1GRP7ayke7WPNQp7DHcK6cJ9VKGoaBafr/kJ5qqO4z/a2sUKECPXv2ZODAgRw9epTu3btz4MCBoDIrVqwI1EZ07doVt9vN3LlzmTx5cqDMsT4a5xp3QkICv/32W75j9u7dS+XKlc967pN98cUX9OrViylTpnDzzTcX+PhwU2IhIhIiy+Zkd8U2GFjssipQ1djDTstfBf6zmRQoV8fYxc97DzL/86+Y43gPgJa2zbTYNZuvt7Xi4polq82siBQPNptxTh2nS4KGDRuyfft2du3aRVKS/+/p8uXLz3jMrbfeylVXXcX999+P3W7Pt/+rr76iRo0aPPjgg4Ft27ZtCyrTtGlTPvvsM2655ZZzijs5OZmsrCxWrVpF69atAVi5ciVZWVm0a9euQOdavHgxPXv25Kmnngp0Di9plFiIiIQoxxbJXZ5hfBcxiBgjh972ZVyVM4EM4thkVeX/PIP52UziJyuJbusyaJK1GE74ousq2yre/iEjkFh8uXkvn23YQ80K0fRvUwOXQ+NpiMhfU6dOnahfvz4333wzkydPJjs7OyghOJVu3bqxd+/eoKZNJ6pbty7bt29n7ty5XHzxxSxYsID58+cHlXn00Ufp2LEjderU4frrr8fr9fLxxx8H+mCcTYMGDejWrRuDBg0K9IcYPHgwPXv2DIwIBQSaYmVkZHDkyBHS09MBf0LlcrlYvHgxPXr04J577uGaa64J9M9wuVwlqgO33sVEREJkmSZHD+xnc7YT07KIMw6yInIo30TcTiyHecfXgW+sC8imDPO++ZXNVhXe9R3/xqqubRc/btkKwPvf7SLllVXMWraVsR+sZ/jb34bprkREws9mszF//nxycnJo3bo1t912G+PGjTvjMYZhULFiRVyuUzfP6tOnD/feey933303zZs3Z9myZTz88MNBZTp06MA777zD+++/T/PmzbnyyitZuXJlgWKfPXs2TZo0oUuXLnTp0oWmTZvyxhtvBJW57bbbaNGiBS+99BKbNm2iRYsWtGjRgl27dgEwa9YsDh8+zIQJE0hMTAy8+vbtW6BYws2wTtWoTc5JdnY2brebrKys02bPIlJy/X36Ej6d+yLX2hfzz8sO4rL72/8esiJokvMK5mm+q3nI8Qa35Y0adZvvfqY/cj8dJ3/Br/uPBJV79672NK9WDo/P5J+fb+bHjWupUbs+d15xIe7oUjhiUm4ujB/vXx4zBk7z4UDkr+zo0aNs2bKFWrVqERmpiTfl/DjT71lBPt+qxkJEJESWAbaIMhyMiOfELoUbreo0rlr+tMd9Z9YJLDcyf2LOyu1UzvqO+sZ2jBN6gL/77a8ATPzoB5osuZ0X9g2k78rrGPrKJ5jm8e+APtvwG+MWrOfjtbtP2eGxxDAMKFfO/1KHdhGREk99LEREQlTGyOG79p9TzbYXTkgtlpqN6d+6Olt3LqeF7WdqG7tIN+vyrVUPgO+t2oGyjW1bGLt0C9Odb9LC9hPZVjR3eYbR1PiFbzZ1IyOrDr+vnEtHh79p1AW2X+n42ywWbWhF10YJPPfpjzgX/4M77F/w6fKWPLTxEcZd2xLwj6ry2YY9fLsjk+TaFbmkXsU/7+GcC6cThg8PdxQiIlJEVGMhIhIiAzMvqTjOtAyWuS6hT/MqtLZv5jXXUzzqfIObHIuIJAeAbVZlsq1oABrbtpK5P5Mmxi8AlDUO84brSf7P+W+qZS7nrVXb6WF8FXSNv9m/4sNvt7P190PsWPwqdzg+pIJxgOsciymb/i++3voHAE+89x0759xNn6/6su61e3jq4/WBc1iWxYpf9vGvJT/z7XbNDC4iIkVPNRYiIiE6sdXRISuC/5kt+MRsw4C/9yDKZeeouw4c9u+/xr6UPrZlbLKqcW3uI7zh64QXB2vNWrSy/YjD8DeB2mhW40LbDgDa2DYy8rPNPOCowgXmTqrnJTFljcMc3Pwlc+JiudmWFhTTLY40nlwxmByvSdmvnyHVsRDw13T8Y2l5vmv8BE2quBk1exktf5xMsu1nPvAls6br/3HbZcebaK37NYv9hz00SIwttcNZiojI+aXEQkQkRJbPx9x1HgAq1m/KzKqP8sTVjWmQ6O/MFh1fm5wtTiIMfxmHYVKOA1SIq8CkP64PnGek49+B5Zd9VzHOeJUIw8PFxkYAnvT250luINX+CWOdrwNwiW81r39ZjjERW4Niijf2k7X+M2YcaMMz9uCk407Hezzy6Q10blaD9psmcI3jSwCa2LYy+pMY1tZ+hAsTYxn11irqbXyemkYGD9KBv11/K10bJQTOcyjHS47XpHy0s2gn9/N4YOZM//Itt/ibRomISImlplAiIiGyLJONv/vY+LsPnwV9L6oSSCoAmtWowLdW3aBjvjXrktK2RtC21raNgeUvfU0Cx1S37SWRfXl7DP7ruwSv5f8zfaXtG3rYjg+B+K15/DrdrS+psOUj3MbhoOtUMrKJ/Wk+z//nI/5mWxq0b6Tj3zz94Rr++b+faL1xInc73qOnfSUv2p/iv3NfZuvvh7Asi2c+3UyzxxZy0eOL6D39K37ac7CAT+0MLAt27fK/SnIndBERAZRYiIiEzLDZ6HWBk14XOLGd4pv75DoVWOZrFLRtsdmcbo0TqFHB38ciglyaGz8BsNWsTPmEGqw0LwyUv/iEpCObMnxt+SdYqmX7jWGO/wb2jfLczkrzQsZ7bmCK5+/c6Pg0sG+s52YAvJaNGsZvDLG/h80I/uBe0cimwY63mfbpZqZ7r+YLX9PAvsdsL/PUe6t5ackvTP10E968EanW/ppFyisryTyUi2VZ/Hv1Du6YNofbJ77C5E82ctTjK8DTFBGR0kaJhYhIiAybjZZJdlom2bHbDAyCk4umVdwsj7mCHMvfynSfFcueKl2oFhdN4ypuAG6wf06E4QVgtVmf2y6tzbdGg8A5etmXB5Y7NajMp76LAEg3azPNew1veDvxqa8FWTG1uS73Ef7l60V54yAX2fzJygazOrN8XXnUM4DLcqbxrLcvzYyfAfjDKkPPnCfwWf64Bzs+JIYj7KYCAzz387OZCECCkUmzLS/z68JnudP+PnA8KdmddZQx89cyZeGPHHnvXl7cfycvHR7BJV8N4K5X/keu1993ZOUv+3hk/veMmfcdy376vWh+ACIiUqypj4WIyDmwyF9j4bDbuOuaLqS++TBtrO9YHtWBsX9rA0CbWnE0Wj+VIY73A+U/sy5mXP1KvJ90MTkZDiIML53t3/C+8SBjPQMY1rE9//drFy7ObsdegufJePuWFtz86ipyvCY/W4k87+3NIPsCZvs68vLNFzP4jeMzZHTLfYrr7Z9jYqNGk/a8v6Edf7N/RRmOUMfYxfdWHcAg1XMfn7ruI8LwcIfjg8C1Gtm2MsxzNzZM7rK/h2OjjzIbjzDAsShQpo1tIzm/jmXsexVwx0Rh+3Iyo/LO8eq33VmcPIrRVzXEMAx+2nOAJZt+J8Ly0ueolzKReisSESkNVGMhIhIiy7LYc8hkzyHztBPTdagfzwujh3DF7VN5eWT/QB+Mnk2TWGZchJlXW7DZrILjwi5UKBNB5xZ1+MhsEzhHfWMntvI1aFLFzRUtLsyXVDRMLEvrWnFc07IqAEeJYKL3errnTmBn9avp1LAy3RsnBsp7cPCGrws/Ve/Hs9e34AP3jRy2IrjLc09eUgGJ7kgG9ryCl3w98t3TTqsSAG+7Hude5zyGOt7lFscn+cpdZl9L5pr5vLD4ZzZY1Ykmh7LGEYY7/kvN5Q/y5Ecb+Md73zH32ftptfBv1Evrz3+XreX7nftD/RGIiBTI4sWLMQyD/fv3h+X6W7duxTAM0tPTw3L9P5sSCxGREPm8Hp5fncvzq3MD/Q5OpVy0i2bVyhEbeXyUo7gYF9de049U8yH+4UlhROwkHuzdDIC/X1SVN8vcykrzQrKtKB7y3sJtV7XDMAzuuLw2VcpFBc7jsBmM7d0IwzC4r2t96saXCezLjK7N2GsuBuChng2CjktyRzLp782w2wxu79uNLr5pLDJbAeCy23j62mbcnFyTVVVS+c48PqHfh762rLvwHp7r34pFvpb57vX/PIPpl/Mwh60IHvTcysdmawA+Ny/iMe/NgXL9Hf/jwhWjuGrNbTzkmE1T2xY8lp0/zGg+37iH/6zZEdoPQURKjNTUVAzDwDAMnE4ntWvXZtSoURw6dCik42vWrMm0adOKNKZjiUb58uU5evRo0L5Vq1YF4v2zrV27lssvv5yoqCiqVKnCP/7xj6AvsHbv3k3//v2pX78+NpuN4aeYXHTGjBlceumllC9fnvLly9OpUydWrVr1J96FmkKJiIQsx4hkic3/wfln35VcVcD3nj7Nq3BpvWHsPZDDmEoxOOz+73aiXHb+eUdPnvu8Pq8ezOGai6rSJW+413LRLubd2Y6ZX20h+6iXa1tV5aLq5QP73r+7PR98twufCT2aJuKO8iczie4oPht5OQu+343DbtChfnxgX5vaFfjn7Vfx1qrtOO02BrSrQd34WACmD2jPI/99kUPrP+GoEU2Nll2Y3LsREQ47y38awvRvjnCLPY0sYhjvvYmLewzkkmgnl85NYh/uoPt9w9eFTCuWZ53TsRkWf7Mfn/jPY9l5wDOYq5z+IXDfWrmdvyfXQURKl27dujFz5kw8Hg9ffvklt912G4cOHeKFF14Ia1yxsbHMnz+fG264IbDt1VdfpXr16mzfvv1PjSU7O5vOnTtzxRVXsHr1ajZt2kRqaioxMTGMHDkSgJycHCpVqsSDDz7I1KlTT3mexYsXc8MNN9CuXTsiIyOZOHEiXbp04YcffqBKlSp/zs1YUmSysrIswMrKygp3KCJyHvztn0utGvd/GHjNWbkt3CGdN9lHci2vzwzaZpqmNf+bndbAmSute976xlr+8++Bff/832ar5gPHn02zxz6xlmzaY81Zuc26b8xIy/eI27IeLWtZj5a1djxS27r6galWjfs/CJS/4un//bk3KFJCHDlyxFq/fr115MiRcIdSYAMGDLD69OkTtO22226zEhISrDp16liTJk0K2rd27VrLMAzrp59+sizLsmrUqGFNnTo1sB+wZsyYYV199dVWVFSUVbduXeu9994LOseCBQusevXqWZGRkVaHDh2smTNnWoCVmZlpWZZl/e9/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unWJiIigevXqjBs3DoDFixdjGAb79+8PnCs9PT0onlmzZlGuXDk+/PBDGjZsSEREBNu2bWPx4sW0bt2amJgYypUrR/v27dm2bVvoD7uYUGIhIhIiu93B2A6RjO0QidOuP5+FYRhg2W3QIRI6ROK1a+ZtkYIYP34848eP5/Dhw4FtX331FePHj+ejjz4KKjtp0iTGjx9PVlZWYNvq1asZP3487733XlDZadOmMX78ePbu3RvYlp6eXqSx22w2nn32WdatW8drr73G559/zn33nfvMQM888wzJyckMGjSI3bt3s3v3bqpVq3bKsqmpqXz99de8//77LF++HMuyuOqqq/B4PID/Xjt27EijRo1Yvnw5S5cupVevXoHkavTo0Tz11FM8/PDDrF+/njlz5lC5cuUCxXv48GEmTJjAyy+/zA8//EBcXBxXX301l19+Od9//z3Lly9n8ODBJbJWXE2hRERCZp2wZGi42UKy9ARFSrUPP/yQMmXKBNa7d+/OO++8E9QXolatWjz++OPceeedPP/88+d0HbfbjcvlIjo6moSEhNOW27x5M++//z5fffUV7dq1A/xznVWrVo13332Xa6+9lokTJ9KqVaugWBo1agTAgQMHeOaZZ5g+fToDBvgnS61Tpw6XXHJJgeL1eDw8//zzNGvmH778jz/+ICsri549e1KnTh3AP8FzSaTEQkRERKSEGTNmDABOpzOwrX379rRt2zZfe/3/+7//y1f24osv5qKLLspX9tiH/hPLntwsKVRXXHEFL7zwQmA9JiYGgP/973+MHz+e9evXk52djdfr5ejRoxw6dChQ5nzYsGEDDoeDNm3aBLZVqFCB+vXrs2HDBsBfY3Httdee9vicnBw6duxYqDhcLhdNmx4fVTAuLo7U1FS6du1K586d6dSpE/369SMxMbFQ1wkH1eWLiITI9PlI+8lL2k9efKZmii4swzThBw/84MF2jm24Rf6qXC4XLpcrqLmM3W7H5XLhcDiKvOy5iImJoW7duoFXYmIi27Zt46qrrqJx48bMmzePNWvW8M9//hMg0BzJZrMF9fE4cV9hnHzOE7cfu9+oqKjTHn+mfUAgSTvxOqeKOyoqKl8zp5kzZ7J8+XLatWvH22+/zQUXXMCKFSvOeL3iSImFiEiITMtkxU4vK3Z68Z36/UkKwLCAvT7Y68M4zRu+iJQuX3/9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Ex8fT9myZQt0zcLSqFAiIiGK9+5m8M5HGbzzUSY6Xgh3OCWeYQG7fLDLpzlCRP4COnToQKNGjRif1/F87NixTJgwgWeeeYZNmzaxdu1aZs6cGdQP40R169bl66+/5pNPPmHTpk08/PDDrF69Ol+5rl274na7eeKJJ7jllluC9g0fPpxPPvmELVu28M033/D5558HJQknqlevHn369GHQoEEsXbqU7777jptuuokqVarQp0+fQDmn08nQoUNZuXIl33zzDbfccgtt27YNJBqPPPIIr7/+OmPHjuWHH35gw4YNvP322zz00ENnfF6h3G/NmjX5/vvv+fHHH/n999/xeDzceOONVKxYkT59+vDll1+yZcsWvvjiC+655x527tx5xmsWlhILEZEQGYZBqyQbrZJsOAwNNysiUlAjRoxgxowZ7Nixg9tuu42XX36ZWbNm0aRJEy6//HJmzZpFrVq1TnnsHXfcQd++fbnuuuto06YN+/btO+W8GDabjdTUVHw+HzfffHPQPp/Px1133UWDBg3o1q0b9evXP+MQtzNnzqRly5b07NmT5ORkLMvio48+ChrdKjo6mvvvv5/+/fuTnJxMVFQUc+fODezv2rUrH374IYsWLeLiiy+mbdu2TJkyhRo1apzxWYVyv4MGDaJ+/fq0atWKSpUq8dVXXxEdHc2SJUuoXr06ffv2pUGDBtx6660cOXLk/NdgWGE0fvx4q1WrVlaZMmWsSpUqWX369LE2btwYVGbAgAEWEPRq06ZNUJmjR49ad999t1WhQgUrOjra6tWrl7Vjx46gMn/88Yd10003WWXLlrXKli1r3XTTTVZmZmZQmW3btlk9e/a0oqOjrQoVKlhDhw61cnJyQr6frKwsC7CysrIK9iBEpERIeXquZT1a1rIeLWvNf6ib9X76r+EOqcRa9EOGNfeBXpZ1ucuyLndZqY//K9whiRRLR44csdavX28dOXIk3KGUKLfddpvVq1ev836dmTNnWm63+7xf53w70+9ZQT7fhrXG4osvvuCuu+5ixYoVLFq0CK/XS5cuXTh06FBQuWMzNx57nTyRyfDhw5k/fz5z585l6dKlHDx4kJ49ewa1Oevfvz/p6emkpaWRlpZGeno6KSkpgf0+n48ePXpw6NAhli5dyty5c5k3bx4jR448vw9BREqOk5rraCCjoqNHKSJFISsri08/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UJUqVSI9PZ169eoFbU9PTz/lB/jCODa0LUDjxo1p1aoVNWrUYMGCBfTt2/e0xx0b+vaYU401fy5lTjZ69GhGjBgRWM/OzlZyIVKK2e32QHNPu10zRReWZRhQ2/82ZNrOqcufiIgUIwVOLAYNGsTgwYP55ZdfaNeuHYZhsHTpUp566qnz3tk5MTGRGjVqsHnzZgASEhLIzc0lMzMzqNZiz549tGvXLlDmt99+y3euvXv3BmopEhISWLlyZdD+zMxMPB5PvpqME0VERBAREVHo+xKRksFut9O+fftwh1FqWDYbVHccXxYRkRKtwInFww8/TGxsLJMnT2b06NGAv1/D2LFjgybNOx/27dvHjh07SExMBKBly5Y4nU4WLVpEv379ANi9ezfr1q1j4sSJACQnJ5OVlcWqVato3bo1ACtXriQrKyuQfCQnJzNu3Dh2794dOPfChQuJiIigZcuW5/WeRET+qhb42rLZqgrAzojTf4kjIiIlg2FZ1jn3nztw4AAAsbGx53T8wYMH+emnnwBo0aIFU6ZM4YorriAuLo64uDjGjh3LNddcQ2JiIlu3bmXMmDFs376dDRs2BK5555138uGHHzJr1izi4uIYNWoU+/btY82aNYGmCt27d2fXrl289NJLAAwePJgaNWrwwQcfAP7hZps3b07lypWZNGkSf/zxB6mpqVx99dWBWXZDkZ2djdvtJisrSyNJiZRClmUF/d07U1NJObPlP++j/7+WEX/wDwC88QmsebRrmKMSEZGTFeTzbYHrnq+88srA5HKxsbGBD/jZ2dlceeWVBTrX119/TYsWLWjRogUAI0aMoEWLFjzyyCPY7XbWrl1Lnz59uOCCCxgwYAAXXHABy5cvD0pkpk6dytVXX02/fv1o37490dHRfPDBB0Htn2fPnk2TJk3o0qULXbp0oWnTprzxxhuB/Xa7nQULFhAZGUn79u3p168fV199NU8//XRBH4+IlGIej4cpU6YwZcoUPB5PuMMp8Rymjxu++4QbvvsEu+k7+wEiIlKsFbgp1OLFi085I/XRo0f58ssvC3SuDh06cKYKk08++eSs54iMjOS55547Y81CXFwcb7755hnPU716dT788MOzXk9E/tps6gtQJFTZIyJS+oScWHz//feB5fXr1wcNw+rz+UhLS6NKlSpFG52ISDHicrl45JFHwh1GqVGRLOLw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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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GEjlypV58803r1KkhUNO7ueRI0fo168f1atXx2KxMHLkyKsXaCGSk3v6ySef0KFDB0qXLk1YWBjNmzfn66+/vorRFnw5uZ/r1q2jZcuWlCxZkqCgIGrUqMG0adOuYrSFQ05/j571/fffY7PZaNCgQf4GWAjl5J6uWrUKwzAyvX755ZerGHHBltOf0bS0NJ544gmuu+46AgICuP7663n33XevUrSFQ07u6aBBg7L8Ga1du/ZVjLjgy+nP6YIFC6hfvz7BwcGULVuWe++9l2PHjl2laM9jimmaprl48WLTbrebs2bNMnfu3Gk+8sgjZkhIiHngwIEsy+/du9cMDg42H3nkEXPnzp3mrFmzTLvdbn700UdXOfKCKaf3c9++feaIESPM9957z2zQoIH5yCOPXN2AC4Gc3tNHHnnEfPHFF83Nmzebv/76qzlu3DjTbrebP/zww1WOvGDK6f384YcfzIULF5o7duww9+3bZ86bN88MDg4233rrrascecGV03t61okTJ8zKlSubHTt2NOvXr391gi0kcnpPv/vuOxMwd+/ebR45csT7crlcVznygulKfkZ79OhhNm3a1FyxYoW5b98+c9OmTeb3339/FaMu2HJ6T0+cOOHzs3no0CGzRIkS5jPPPHN1Ay/AcnpP165da1osFvOVV14x9+7da65du9asXbu22atXr6scuWkqsTijSZMm5oMPPuhzrEaNGubYsWOzLP/YY4+ZNWrU8Dn2wAMPmM2aNcu3GAuTnN7P87Vu3VqJRRZyc0/PqlWrljlx4sS8Dq1Qyov7edttt5l33313XodWaF3pPb3zzjvNJ5980nzmmWeUWFwgp/f0bGKRkJBwFaIrfHJ6P7/66iszPDzcPHbs2NUIr1DK7e/SJUuWmIZhmPv378+P8AqlnN7Tl156yaxcubLPsVdffdUsX758vsV4MeoKBaSnp7Nt2zY6duzoc7xjx46sX78+yzobNmzIVL5Tp05s3boVp9OZb7EWBldyP+XS8uKeejweTp48SYkSJfIjxEIlL+7n9u3bWb9+Pa1bt86PEAudK72nc+bM4ffff+eZZ57J7xALndz8nDZs2JCyZcvSrl07vvvuu/wMs9C4kvv52WefccMNNzBlyhTKlStHtWrVGDNmDCkpKVcj5AIvL36Xzp49m/bt23PdddflR4iFzpXc0xYtWnD48GGW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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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LjnZvq/5r7/+mh49elCvXj27+RERESXWFY4NGzaMYcOG+TsMISqMqtNzd3trr1D/Nalu/R61UXZSQ8kB4IR2pRejCzwmnZ55bfrYnlea33fhc+WOY+HiQHjn1y+tnOLj0arLjNXha5Z/wuxOyEGUc5UrEPfVFyOJl19OcfMzem65G3EFxKW3VatWERsba0sqADp37kxsbCwrV650ahtHjhzh+++/Z/jw4SWWzZ07l8TERJo3b87YsWM5fdrzgeWEEMElR4njbdO1vG26lhWWlm5t4179t7wXMpX3QqYSpUlvX0IIIYJLQNyxyM7OpkaNGiXm16hRo8TAZGX54IMPiI6O5oYbbrCbf/vtt1O3bl2Sk5P5+++/GTduHBs3bmTx4sVlbuv86M3n5eXlObknQohAdVyJ52XTrbbntzpYVwghhKiK/JpYPPvss0yYMMHhOr///jtQel07TdOcrm85e/Zsbr/9dsLCwuzmjxw50jbdokULGjZsSIcOHfjjjz9o165dqduaNGlSuXELIYKLxWzi9MZFAES1+Jefowl8erOJoX98D8CH7a7xczRCCCG8wa+JxQMPPFBuD0zp6els2rSJI0eOlFh27Ngxp3ojWrFiBdu2bWP+/PnlrtuuXTsMBgM7duwoM7EYN26c3RgFeXl5pKamlrttIUQA0zTMp4+df+LXUIKBgkZMUb5tWgghKpp883ifXxOLxMREEhMTy10vIyOD3Nxc1q5dS8eOHQFYs2YNubm5dOnSpdzys2bNon379rRu3brcdf/55x+MRiMpKSllrhMaGkpoaGi52xJCBA9VUYht2hUAnZut0+QEWgghAks5Q1CJSwRE4+2mTZvSp08fRo4cyerVq1m9ejUjR46kf//+dj1CNWnShC+//NKubF5eHp999hkjRowosd1du3bx3HPPsW7dOvbu3cuCBQu4+eabadu2LV27dvX5fgkhAkcD5QD7aj3JvlpP8rzhQ5fLy2+TYwHYyYsQoooob4BbXwq0HrACIrEAa89NLVu2pFevXvTq1YtWrVrx3//+126dbdu2kZubazfvk08+QdM0br21ZFPLkJAQlixZQu/evWncuDEPPvggvXr14qeffkKn0/l0f6q6YcOGcf311/s7DCFEJSGJV9XhcCSGcs6iKuM5lrv74+oJo3UQX/fiCDbujAFSGmcSBm+d2Lv73jnz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eP7553nsscd45plnbK932223cdddd9nF4Gh/o6KiCA8Pp6ioiOTkZNt6H330Eaqq8v7779u+EObMmUNcXBxLly6lV69eZe6z8J37/tWEnccf4os/D9nmpZ7eyGPvHuKlu2+kfvWq3X2sq4w6A+92usnfYQghhPAiSSxEmXr06MHbb79NQUEB77//Ptu3b+f//u//ADh27BgHDhxg+PDhjBw50lbGZDIRGxtb5jbfeecd3n//ffbt28fZs2cpLi6mTZs2tuXDhg1j/PjxrF69ms6dOzN79mwGDhxIZGQkAA8++CD33XcfixYtomfPntx44420atWq1NfasmULer3eljAAJCQk0LhxY7Zs2WKbp9fr6dChg+15kyZNiIuLY8uWLXTs2JH169fz+++/292hOJ9sFRQUEBERAWC3DWf3tzTr169n586dREdH280vLCxk165dDssK31EUhUk3tuRATgG/783hMmUrmSEvc6Y4nAffKuCB22/m8oaJ/g5TCCGE8JuASSxefPFFvv/+ezZs2EBISAinTp0qt4ymaUyYMIH33nuPnJwcOnXqxFtvvUXz5s1t6xQVFTF27FjmzZvH2bNnueqqq5gxYwa1a9f24d4ATz5Z9jL1khpq567el+rS+1SjR7sd0qUiIyNp0MA6UvDrr79Ojx49mDBhAs8//zwWi3Xk4ZkzZ9qduAPodLpSt/fpp5/y8MMP89prr5GRkUF0dDSvvPIKa9assa1To0YNrr32WubMmUO9evVYsGCBrd0BwIgRI+jduzfff/89ixYtYtKkSbz22mu2hOdiWhmVIjVNK3FrsLRbhefnWSwWJkyYYLszc7GwsDDb9Pnkx5X9LY3FYqF9+/bMnTu3xLLq1as7LCt8K1Sv4/2hlzFk1mrGHvuUSKWISIrI1J5mfOZelmUM5cGejYgOq9qjagshhKiaAiaxKC4u5uabbyYjI4NZs2Y5VWby5MlMmTKFzMxMGjVqxAsvvMDVV1/Ntm3bbFeDR48ezbfffssnn3xCQkICjzzyCP3792f9+vVlniB7RUiI/9d10TPPPEPfvn257777qFmzJrVq1WL37t3cfnG1LQdWrFhBly5d7HqWKu0K/IgRI7jllluoXbs29evXp2vXrnbLU1NTuffe/2/v3uOirPM9gH/mys1muMgdFQUEBLxhirqGJYprrRj5yiNamVly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bm6uyM3NFdHR0SIpKUnk5uaKkydPWiN8m2NoPrds2SLkcrlYs2aNzlR/FRUV1joEm2JoPt9//33x5Zdfivz8fJGfny82btwoVCqVWLRokbUOwaZ05P1+L84KpcvQfK5cuVLs3LlT5OfnixMnTogFCxYIAOKzzz6z1iHYDENzeePGDREQECDGjx8vTp48KTIzM0VISIiYNm2atQ7BpnT0vT558mQxaNAgS4dr8wzNZ3p6upDL5WLt2rXi3LlzIjs7WwwYMEAMHDjQ4rGzsGinNWvWiG7dugmlUin69+8vMjMztfuee+45ERsbq9P+wIEDol+/fkKpVIrAwEDxwQcfWDhi22VoLgE0u3Xr1s2yQdswQ/IZGxurN5/PPfec5QO3UYbkc9WqVSIiIkI4OzsLlUol+vXrJ9auXSvq6+utELltMvT9fi8WFs0Zks8VK1aIoKAg4ejoKNzc3MQf/vAH8dVXX1khattk6Ll56tQpERcXJ5ycnERAQICYM2eOqK6utnDUtsvQfFZUVAgnJyexbt06C0dqHwzN56pVq0SvXr2Ek5OT8PX1FZMmTRLFxcUWjloIiRCWvkZCRERERET3G46xICIiIiIio7GwICIiIiIio7GwICIiIiIio7GwICIiIiIio7GwICIiIiIio7GwICIiIiIio7GwICIiIiIio7GwICIiIiIio7GwICIii0hNTUXfvn2t9vqvv/46XnrppXa1nTt3LmbOnGnmiIiI7i9ceZuIiIwmkUha3f/cc8/h/fffR21tLTw8PCwU1V2XL19GSEgIjh07hsDAwDbbl5WVISgoCMeOHUP37t3NHyAR0X2AhQURERmttLRU+/P27duxZMkSnD59WrvNyckJarXaGqEBAJYtW4bMzEzs2bOn3Y956qmnEBwcjBUrVpgxMiKi+we7QhERkdF8fHy0N7VaDYlE0mzb77tCTZkyBePGjcOyZcvg7e0NV1dXpKWlQaPRYN68eXB3d0dAQAA2btyo81q//vorJkyYADc3N3h4eCAhIQHnz59vNb5t27Zh7NixOtt27NiBqKgoODk5wcPDA3Fxcbh165Z2/9ixY7F161ajc0NE9KBgYUFERFbzzTff4NKlSzh48CDeffddpKam4oknnoCbmxuOHDmC5ORkJCcno6ioCABQXV2NRx99FJ06dcLBgweRnZ2NTp06YfTo0airq9P7GtevX8eJEycwYMAA7baSkhJMnDgRU6dOxalTp3DgwAEkJibi3ov4AwcORFFRES5cuGDeJBAR3SdYWBARkdW4u7tj1apVCA0NxdSpUxEaGorq6mq89tprCAkJwcKFC6FUKnHo0CEAjVcepFIpPvroI0RFRSE8PBzp6em4ePEiDhw4oPc1Lly4ACEE/Pz8tNtKSkqg0WiQmJiIwMBAREVFYcaMGejUqZO2jb+/PwC0eTWEiIgaya0dABERPbgiIiIgld79G5e3tzciIyO192UyGTw8PFBWVgYA+PHHH3H27Fk89NBDOs9z+/ZtnDt3Tu9r1NTUAAAcHR212/r06YMRI0YgKioK8fHxGDVqFMaPHw83NzdtGycnJwCNV0mIiKhtLCyIiMhqFAqFzn2JRKJ3W0NDAwCgoaEB0dHR2Lx5c7Pn8vT01PsanTt3BtDYJaqpjUwmw759+3D48GHs3bsXq1evxqJFi3DkyBHtLFDXrl1r9XmJiEgXu0IREZHd6N+/P86cOQMvLy8EBwfr3FqadSooKAgqlQp5eXk62yUSCYYOHYq0tDTk5uZCqVRi586d2v0nTpyAQqFARESEWY+JiOh+wcKCiIjsxqRJk9C5c2ckJCQgKysLhYWFyMzMxKxZs1BcXKz3MVKpFHFxccjOztZuO3LkCJYtW4acnBxcvHgRn3/+Oa5cuYLw8HBtm6ysLAwbNkzbJYqIiFrHwoKIiOyGs7MzDh48iK5duyIxMRHh4eGYOnUqampqoFKpWnzcSy+9hG3btmm7VKlUKhw8eBBjxoxBz549sXjxYrzzzjv44x//qH3M1q1b8eKLL5r9mIiI7hdcII+IiO57QgjExMQgJSUFEydObLP9V199hXnz5uHYsWOQyzkckYioPXjFgoiI7nsSiQTr1q2DRqNpV/tbt24hPT2dRQURkQF4xYKIiIiIiIzGKxZERERERGQ0FhZERERERGQ0FhZERERERGQ0FhZERERERGQ0FhZERERERGQ0FhZERERERGQ0FhZERERERGQ0FhZERERERGQ0FhZERERERGQ0FhZERERERGS0/wfLZToFJGx5fgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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eQE+pMJCRET8S5eY+Ubmmbf3LM+9X2Q5aDvM86ipeyyk5KlatSozZ87Msm7evHmUKlUqIHn69+/P+vXr+f777/n+++9Zv349AwYMyNLHNE0GDhxIv379cnyOpUuX0rRpUz799FN+++03Bg4cyB133MFXX31VGC/BZ1RYiEhQczgc/Oc//+E///mPZrT1JZcJK9M9D4duivcJI9NILTWuzr1fyn745C7PY/XM3PtJseN2mxw9lRbQh9t9cTPAd+zYkQcffJARI0ZQtmxZ4uPjGTNmTJY+W7du5eqrryYsLIyGDRuyYMGCHJ/rzjvv5IMPPuDMmTPedTNmzODOO+/M1veJJ56gbt26REREULNmTZ5++uls7/VffvklLVu2JCwsjHLlytG3b98s21NTUxk4cCBRUVFUq1aNt956y7vtzz//5Pvvv+ftt9+mbdu2tG3blunTp/P111+zefNmb7/XXnuNYcOGUbNmzRxf06hRo3juuedo164dtWrV4sEHH6R79+7Mmzcv5wNaROnmbREJaqZpsn37dm9bfCjV7fmvjquITxxPTafF8/8NaIY1T3UmtlToRe07e/ZsHnnkEVasWMGyZctITEykffv2dOnSBbfbTd++fSlXrhzLly8nJSWF4cOH5/g8LVq0oEaNGnz66afcfvvt7Nmzh8WLF/PGG2/w3HPPZekbFRXFrFmzqFSpEr///juDBw8mKiqKESNGAPDNN9/Qt29fRo8ezbvvvkt6ejrffPNNlud45ZVXeO655xg1ahSffPIJ9913H1dffTX169dn2bJlxMTE0Lp1a2//Nm3aEBMTw9KlS6lXr95FHSuA5ORkGjRocNH7B4IKi0wWbEgiotTpQMcQkULkdrup0OQqEmIjsdn0ligi4i9Nmzbl2WefBaBOnTpMmTKFH3/8kS5duvDf//6XP//8k507d1KlShUAxo0bx7XXXpvjc911113MmDGD22+/nZkzZ9KjRw/vCH+ZPfXUU952QkICjz76KB9++KG3sHjhhRe45ZZbGDt2rLffZZddluU5evTowdChQwHPGZBJkyaxcOFC6tevT1JSEhUqVMj2cytUqEBSUlJBDk8Wn3zyCatWreLf//73RT9HIOhTNJPUeQ9A6IVHhZnt7MpvZi3vcg3jAPfbPs/X849yDCKNjNFRultW0sW6Js/9drjjmeL6vyzrhts+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KLfccgu//PILAD/88AO33347r732GldddRXbtm3jnnvuAeDZZ5/1/qxnn32W8ePHM2nSJKxWa56v97HHHuPPP/8kJSWFmTNnAlC2bFlSU1O55ppruOqqq1i8eDE2m43nn3+e7t2789tvvxFyif+WF6J3exER8bnnnQO87X62mAAmESmhxo3LfVudOnDbbRnLL78MuV1ak5AAiYkZy5MnQ2pq9n5jxhQ44tdff02pUqVwuVycPXsWgIkTJ3q3P/fcc7zyyiv07dsXgBo1arBx40b+/e9/51hY2O12xo4d612uUaMGS5cu5aOPPuLmm28GYNCgQcycOdNbWHzzzTekpqZ6t+/evZsbbriBJk08c7zUrFkz1/znC4pffvmFdu3aATBnzhyqVq3K559/zk033QR4LluaMmUKrVu3BmD27Nk0aNCAlStX0qpVK1544QWefPJJ72uqWbMmzz33HCNGjMhSWPTv35+BAwdmyXCh11uqVCnCw8NJS0sjPj7e2++9997DYrHw9ttvYxgG4DkzVLp0aRYuXEjXrv4bKU6FhYgENZfL5f1WqX379pp520esbhdX71gLgKV5xQCnEZFAuOaaa5g2bRqpqam8/fbbbNmyhQceeACAw4cPs2fPHgYNGsTgwYO9+zidTmJicv8y4s033+Ttt99m165dnDlzhvT0dJo1a+bdnpiYyFNPPcXy5ctp06YNM2bM4Oabb/aemX7wwQe57777mD9/Pp07d+aGG26gadOmOf6sP//8E5vN5i0YAGJjY6lXrx5//vmnd53NZqNly5be5fr161O6dGn+/PNPWrVqxZo1a1i1ahUvvPCCt8/5Yis1NZWIiAiALM+R39ebkzVr1vDXX38RFRWVZf3Zs2fZtm3bBfe9VCosRCSouVwu/ve//wHQpk0bFRY+YjHdXHZgCwCH3GYevUWkwEaNyn2b5W+30J779j5H577R9ho+/KIj/V1kZCS1a9cG4LXXXuOaa65h7NixPPfcc7jPzSQ/ffr0LH+4A7m+D3/00Uc8/PDDvPLKK7Rt25aoqChefvllVqxY4e1ToUIFrr/+embOnEnNmjX59ttvvfcdANx9991069aNb775hvnz5zN+/HheeeUVb8GTmWnm/N5lmqb3TMB5f1/OvM7tdjN27FjvmZnMwsLCvO2/X5abn9ebE7fbTYsWLZgzZ062beXLl7/gvpdKhYWIBDWLxcLll1/ubYsUWaHRGe1S8bn3s2oQgqBQkOvk/dW3gJ599lmuvfZa7rvvPipVqkTlypXZvn07t2W+bOsClixZQrt27bKMLJXTN/B33303t9xyC1WqVKFWrVq0b98+y/aqVasyZMgQhgwZwsiRI5k+fXqOhUXDhg1xOp2sWLHCeynU0aNH2bJlCw0aNPD2czqdrF69mlatWgGwefNmTpw4Qf369QG4/PLL2bx5s7fIyq/8vN6QkBBcLleWdZdffjkffvghFSpUIDo6msKkT1ERCWo2m41evXrRq1cvbDZ91+Irw22fcKN1MTdaFxPuSg50nJLBYgXOfSsaUzn3fjFVM9qth/g1kkhBdOzYkUaNGjHu3P0hY8aMYfz48bz66qts2bKF33//nZkzZ2a5DyOz2rVrs3r1an744Qe2bNnC008/zapVq7L169atGzExMTz//PPcddddWbYNHz6cH374gR07drB27Vr+97//ZSkSMqtTpw69e/dm8ODB/Pzzz/z666/cfvvtVK5cmd69e3v72e12HnjgAVasWMHatWu56667aNOmjbfQeOaZZ/jPf/7DmDFj2LBhA3/++ScffvghTz311AWPV35eb0JCAr/99hubN2/myJEjOBwObrvtNsqVK0fv3r1ZsmQJO3bsYNGiRTz00EPs3XuBEeV8QIWFiIj4XB1jH1WMw1QxDmM1NfypiHg88sgjTJ8+nT179nD33Xfz9ttvM2vWLJo0aUKHDh2YNWsWNWrUyHHfIUOG0LdvX/r160fr1q05evRojvNiWCwWEhMTcblc3HHHHVm2uVwuhg0bRoMGDejevTv16tW74BC3M2fOpEWLFvTs2ZO2bdtimibffvttltGtIiIieOKJJ+jfvz9t27YlPDycDz74wLu9W7dufP311yxYsIArrriCNm3aMHHiRKpXr37BY5Wf1zt48GDq1atHy5YtKV++PL/88gsREREsXryYatWq0bdvXxo0aMDAgQM5c+aM389gGGZuF5AFkZSUFGJiYkhOTi70U0YiIiXNP7/aSJtlw+i6dCkA4wZ9yagB3QKcqoQYUxowoXILGPy/nPvsXQNv/8PTbj0Erp1QWOnED86ePcuOHTuoUaNGluvx5cIGDx7MwYMH+fLLL/36c2bNmsXw4cM5ceKEX3+Ov13o96wgfyfrvL+IBLX09HRefvllAB5//HG/ju8tckmcaXgnydu3Jvd+pavB9a952hVyvsRDpKRKTk5m1apVzJkzhy+++CLQcYKOCgsRCXqO3MZ3FylK0k9ntKu1zb2f6YIN8zzt1KNQtZV/c4kUIb1792blypXce++9dOnSJdBxgo4KCxEJana7neHnhlfMfM2sXBrTYkCbUADcVn3U+FxoVO7bHGdg+0+edmS5wskjUkRkHlq2MCQmJpKYeYLBIKd3exEJaoZhULp06UDHKHFMwwJh50YwymF8dxERKXk0KpSIiEhJ4jiT0T51MHA5RCTo6IyFiAQ1l8vlHRf8iiuu0MzbvuI2YacTAKOOK4/OUmBb5+e+7eCGjPaOxf7PIoXi/EzVIv7gq98vFRYiEtRcLhfff/894JmtVIWFbximCXvOFRb6g0jkooWEhGCxWNi/fz/ly5cnJCQEQ5cXio+Ypkl6ejqHDx/GYrFc8siIKixEJKhZLBaaNGnibYtvzHe1pKz7CAAOS3iA04gUXxaLhRo1anDgwAH2798f6DhSQkVERFCtWrVL/hxUYSEiQc1ms3HDDTcEOkaJ86W7HVXdewFIs0QGOI1I8RYSEkK1atVwOp24XLq0UHzLarVis9l8ciZMhYWIiPiVeX5SNxG5aIZhYLfbNSy2FGk67y8iIj6ly7/9xNBHtogUbTpjISJBLT09ncmTJwMwfPjwS75xTTxsOLFy/qZtnbHwifDSYFg9M2tXap57v7DojHZErN9jiYicp8JCRIJeampqoCOUOJPsU+lu+wWAcc7EwIYJNuFlM9pN+wUuh4gEHRUWIhLU7HY7Q4cO9bbFN0yLAVd4zv64rfqoEREJBnq3F5GgZhgGFSpUCHSMkscwINKS0RYRkRJPhYWIiEhxcDbFc38FwP51ufer2BSG/+Fph5byfy4RkXNUWIhIUHO5XKxfvx6AZs2aaeZtX3Fnmnm7jsbd9wmXI6Ndt3vu/U4egFcv87Qb3wA3zvBvLhGRc1RYiEhQc7lcfPXVVwA0adJEhYWPGKYJO88VFm53Hr1FRKQkUGEhIkHNYrFQv359b1tEREQujgoLEQlqNpuNW265JdAxRHznZFJGe+cvgcshIkFHX8+JiIgUN1u+z33bid0Z7VNJufcTEfExFRYiIiIiInLJdCmUiAQ1h8PBG2+8AcCwYcM0SZ6PvObsyymX50b4VGt0gNMEG80bIiKBocJCRIKaaZqcOHHC2xbf2GpWYa9ZHgCXERLgNCIiUhhUWIhIULPZbAwePNjbFt9wWqy8f1k3ADpqCF8RkaCgT1ERCWoWi4XKlSsHOkaJYxoWDkaV87Q1jK9v2HTmR0SKNhUWIiLic02NbVQwTgBgc5cPbJiSIjQKLDZwO6Fis9z7lcp0vGt29HcqERGvAhcWaWlprFy5kp07d5Kamkr58uVp3rw5NWrU8Ec+ERG/crvd/PHHHwA0btxYk+T5yL2WL7nuwDIAxjlaBThNkLGFZ7TjGgcuh4gEnXwXFkuXLuX111/n888/Jz09ndKlSxMeHs6xY8dIS0ujZs2a3HPPPQwZMoSoqCh/ZhYR8Rmn08lnn30GQP369QkJ0eUmvmCYJmx3etrXuAOcRkRECkO+vprr3bs3N954I5UrV+aHH37g5MmTHD16lL1795KamsrWrVt56qmn+PHHH6lbty4LFizwd24REZ8wDIOaNWtSs2ZNDEPDdEoJYA+DcvU8j0hdhiYihSdfZyy6du3Kxx9/nOs3eec/lO+88042bNjA/v37fRpSRMRf7HY7d9xxR6BjiOTt9FHP/RUAB9bn3q9cXeg2ztMuVcHvsUREzstXYTFs2LB8P2GjRo1o1KjRRQcSERGRnGSaZ6Xutbl3O30Y5tzgaTfqCzfN9G8sEZFzLmlUqD/++INFixbhcrlo164dLVu29FUuEREREREpRi56+JM33niDTp06sWjRIn766Sc6derECy+84MtsIiJ+53A4eOONN3jjjTdwOByBjiMiIlJs5fuMxd69e6lSpYp3ecqUKWzYsIFy5TwTIC1btoxevXoxevRo36cUEfET0zQ5fPiwty1SLDhSc992aFNGe8NnuhRKRApNvs9YdOrUiVdffdX7wRsbG8sPP/xAWloaJ0+e5L///S/ly2v0CREpXmw2G4mJiSQmJmKzac5QXzEtBjQLgWYhuC3WQMcpGTIXvjsW5d7vbLL/s4iI5CDfhcWqVavYtGkTrVu3Zt26dbz11ltMnDiR8PBwSpcuzYcffsjs2bN9HnDfvn3cfvvtxMbGEhERQbNmzVizZo13u2majBkzhkqVKhEeHk7Hjh3ZsGGDz3OISMlksVhISEggISFBk+P5kNOwkxYTQlpMCOi4iogEhXx/PRcdHc20adP45ZdfSExMpHPnzixZsgSXy4XL5aJ06dI+D3f8+HHat2/PNddcw3fffUeFChXYtm1blp/10ksvMXHiRGbNmkXdunV5/vnn6dKlC5s3b9ZEfSIiAfKQ435v+2a7hjwVEQkGBf4aqX379qxevZqYmBiaN2/O4sWL/VJUAEyYMIGqVasyc+ZMWrVqRUJCAp06daJWrVqA52zF5MmTGT16NH379qVx48bMnj2b1NRU5s6d65dMIlKyuN1uNm3axKZNm3C7NUO0r1jcLi7bv5nL9m/GcLkCHUdERApBvgsLp9PJtGnTeOCBB5g9ezajR4/mq6++4l//+hc33XQTSUlJPg/35Zdf0rJlS2666SYqVKhA8+bNmT59unf7jh07SEpKomvXrt51oaGhdOjQgaVLl/o8j4iUPE6nkw8++IAPPvgAp9MZ6DglhtV0c8321VyzfTUWFWwiIkEh34XF4MGDef3114mMjGTmzJk8/PDD1K1bl59++olu3brRtm1bpk2b5tNw27dvZ9q0adSpU4cffviBIUOG8OCDD/Kf//wHwFvMxMXFZdkvLi7ugoVOWloaKSkpWR4iEpwMw6Bq1apUrVoVwzACHUckd6G6vFdEirZ832Px+eefs3TpUho0aMCZM2do3Lgxr732GgB33303vXr1Yvjw4dx3330+C+d2u2nZsiXjxo0DoHnz5mzYsIFp06Zxxx13ePv9/Y8B0zQv+AfC+PHjGTt2rM9yikjxZbfbGTRoUKBjlDg3WhdxjWU9AF+49OWNT9jDwBoCrnSIb5p7v9JVM9pt78+9n4iIj+X7jEWFChWYP38+6enp/Pjjj8TGxmbb7uv7GipWrEjDhg2zrGvQoAG7d+8GID4+HiDb2YlDhw5lO4uR2ciRI0lOTvY+9uzZ49PcIiLB7krL71xm2cZllm2EuM8EOo6IiBSCfBcWU6ZMYdy4cYSHhzNkyBAmT57sx1ge7du3Z/PmzVnWbdmyherVqwNQo0YN4uPjWbBggXd7eno6ixYtol27drk+b2hoKNHR0VkeIiIiIiJy8fJ9KVSXLl1ISkriyJEjhTYR3sMPP0y7du0YN24cN998MytXruStt97irbfeAjyXQA0fPpxx48ZRp04d6tSpw7hx44iIiKB///6FklFEijeHw8HMmZ6Zie+66y7sdnuAE4nkwpnuuQwK4PDm3PtFxUOboZ529dy/ZBMR8bUCTTNrGEahzq59xRVXMG/ePEaOHMk///lPatSoweTJk7ntttu8fUaMGMGZM2cYOnQox48fp3Xr1syfP19zWIhIvpimyf79+71tkSLr7ImMdqncL/clLCbTPpqFW0QKT74Ki+7du/PMM89c8PIigJMnTzJ16lRKlSrFsGHDfBKwZ8+e9OzZM9fthmEwZswYxowZ45OfJyLBxWazec9w2mwF+q5FLsC0GNDEc/bHbbEGOE0JFN8k923pp2H5VE+7YR9opjP4IlI48vUpetNNN3HzzTcTFRVFr169aNmyJZUqVSIsLIzjx4+zceNGfv75Z7799lt69uzJyy+/7O/cIiI+YbFYqFu3bqBjlDyGBWLPFRSWAs/FKiIixVC+CotBgwYxYMAAPvnkEz788EOmT5/OiRMnAM8Zg4YNG9KtWzfWrFlDvXr1/JlXRERERESKoHyf9w8JCaF///7eSwaSk5M5c+YMsbGxutlRRIott9vNjh07AM9IcxZ9u+4bbhMOuwAwarsCHKYE2r4w9227lmW0N37u7yQiIl4X/QkaExNDfHy8igoRKdacTifvvvsu7777Lk6nM9BxSgzDNGGTAzY5MNzuQMcpeRynA51ARCQb3akoIkHNMAzvZJuGYQQ4TcnxhzuB+u6tADgsIQFOIyIihUGFhYgENbvdzpAhQwIdo8R523UdoW7Pt+qp1jIBTiMiIoVBFxOLiIiIiMglU2EhIiIiIiKXrMCFRWJiIosXL/ZHFhGRQudwOJg1axazZs3C4XAEOo5I7iLLZ7Rj6+Tezxbq/ywiIjkocGFx8uRJunbtSp06dRg3bhz79u3zRy4RkUJhmiY7d+5k586dmKYZ6DglxlO290i0fk+i9XuinIcDHadkMAywhXna5/+bk8hyGe12D/o3k4hIJgUuLD799FP27dvH/fffz8cff0xCQgLXXnstn3zyib7tE5Fix2azcdNNN3HTTTdhs2k8C18pa02hdKN0SjdKBw22JSISFC7qHovY2Fgeeugh1q1bx8qVK6lduzYDBgygUqVKPPzww2zdutXXOUVE/MJisdCoUSMaNWqkyfF8yTCggtXz0HEVEQkKl/Ruf+DAAebPn8/8+fOxWq306NGDDRs20LBhQyZNmuSrjCIiIpJ2EpxnPe2Dv+fer3x9uO1Tz+PyOwonm4gIFzGPhcPh4Msvv2TmzJnMnz+fpk2b8vDDD3PbbbcRFRUFwAcffMB9993Hww8/7PPAIiK+5Ha72bt3LwBVqlTRWQtfMU045PK0a2vmbZ9IzzTbdv2eufdzOeCHUZ52rX/AtS/6N5eIyDkFLiwqVqyI2+3m1ltvZeXKlTRr1ixbn27dulG6dGkfxBMR8S+n08mMGTMAGDVqFCEhmiXaFwy3CRs9991Z2rgCnCbIuJ1wZLOnXb5eYLOISFApcGExadIkbrrpJsLCch+RokyZMuzYseOSgomIFAbDMChbtqy3LSIiIhenwIVFr169SE1NzVZYHDt2DJvNRnR0tM/CiYj4m91u58EHNSSnlCBnjme0j24LXA4RCToFvpj4lltu4YMPPsi2/qOPPuKWW27xSSgRERG5gE1f577t0MZM7Q3+zyIick6BC4sVK1ZwzTXXZFvfsWNHVqxY4ZNQIiJScmjeQRGR4FDgS6HS0tJwOp3Z1jscDs6cOeOTUCIihcXpdPLhhx8C0K9fP02S5wMG8KnrKiJcKQCctUYFNlDQ0b1CIhIYBT5jccUVV/DWW29lW//mm2/SokULn4QSESksbrebrVu3snXrVtxuDYvqKz+7m/KbWYvfzFqkWSMDHUdERApBgb+ae+GFF+jcuTO//vornTp1AuDHH39k1apVzJ8/3+cBRUT8yWq10qdPH29bfMNlWJhfpy0AjTU3iIhIUChwYdG+fXuWLVvGyy+/zEcffUR4eDhNmzblnXfeoU6dOv7IKCLiN1arNcf5eOTSuC1WNsbVBKCRRQWbT1jsgU4gInJBF3UxcbNmzZgzZ46vs4iISAlRnhNEGGcBsJjxAU5TQkTGgi0cnGcgrnHu/cIyDftevr7/c4mInHNRhYXb7eavv/7i0KFD2a5Jvvrqq30STESkMLjdbg4dOgRAhQoVsOiyHZ94yvYuvZN/BmBc2ocBThNkQjLd01K3e+ByiEjQKXBhsXz5cvr378+uXbsw/zaGoGEYuFwun4UTEfE3p9PJm2++CcCoUaMICQkJcKKSwXCb8LsDAEtLfS6IiASDAhcWQ4YMoWXLlnzzzTdUrFgRw9CwdiJSfBmGQVRUlLctIiIiF6fAhcXWrVv55JNPqF27tj/yiIgUKrvdzqOPPhroGCJ5O33Ec38FwME/cu9XtQ0MP7fdHu7/XCIi5xT4YuLWrVvz119/+SOLiIiI5MaVntFucH3u/U4fhsmNPY+vh/s9lojIeQU+Y/HAAw/w6KOPkpSURJMmTbDbsw5/17RpU5+FExERERGR4qHAhcUNN9wAwMCBA73rDMPANE3dvC0ixY7T6eSzzz4DoG/fvthsFzVYnoiISNAr8Cfojh07/JFDRCQg3G43GzduBPDOwC1SrB3bntHeMj9wOUQk6BS4sKhevbo/coiIBITVaqVHjx7etviGaRhQx3OprKm5QXzvz69y35ayP6PtSvN/FhGRcy7q3f7dd9+lffv2VKpUiV27dgEwefJkvvjiC5+GExHxN6vVSqtWrWjVqpUKC1+yGFDZCpWtmBYdVxGRYFDgwmLatGk88sgj9OjRgxMnTnjvqShdujSTJ0/2dT4RESmGnnMM4Oq0SVydNolke4VAxxERkUJQ4MLi9ddfZ/r06YwePTrLt3stW7bk999/92k4ERF/M02To0ePcvToUUzTDHScEuOoGYX7BLhPgGnqUigRkWBwUTdvN2/ePNv60NBQTp8+7ZNQIiKFxeFw8PrrrwMwatQoQkJCApyoZLC5Xdz4+38BSLpaw5CLiASDAn+NVKNGDdavX59t/XfffUfDhg19kUlEpFCFhYURFhYW6BgiFxZSKtAJREQuqMBnLB5//HGGDRvG2bNnMU2TlStX8v777zN+/Hjefvttf2QUEfGbkJAQnnzyyUDHKHHaWf6gqbENgBOuUwFOU0KERYM9EhynocIFvsiLjM1oN7nZ/7lERM4pcGFx11134XQ6GTFiBKmpqfTv35/KlSvz6quvcsstt/gjo4iIFDP/Z/2Zf1jXA7DalRzYMMHGYs9ox1QOXA4RCToXNcXs4MGDGTx4MEeOHMHtdlOhgkb8EBEREREJZhdVWJxXrlw5X+UQEQkIp9PJ119/DUDPnj2x2S7pbVHEf9xuz2VQAOkXuLwsvDTU+oenXbaW32OJiJyXr0/Qyy+/nB9//JEyZcrQvHlzDMPIte/atWt9Fk5ExN/cbrd3QIrzM3CLFEknD2S0T+zOvV/ZWtCwj6etS6FEpBDlq7Do3bs3oaGhAPTp08efeURECpXVaqVLly7etviGaRhQ0/MRY1o0j4XPNeyd+7a0k/DVg552/Z4ZZy9ERPwsX4XFs88+m2NbRKS4s1qttG/fPtAxSh6LAdXOFxYq2EREgkGBv0ZatWoVK1asyLZ+xYoVrF692iehRERERESkeClwYTFs2DD27NmTbf2+ffsYNmyYT0KJiBQW0zRJSUkhJSUF0zQDHafkME1IcXsebneg05Q8x3fmvm1fpi/5Nn3t9ygiIucVuLDYuHEjl19+ebb1zZs3Z+PGjT4JJSJSWBwOBxMnTmTixIk4HI5AxykxDLcJa9NhbToWtyvQcUqeA7/mvi09tfByiIhkUuDCIjQ0lIMHD2Zbf+DAAQ3TKCLFksViwaIbjH3qqBnNCbMUJ8xSuNA9FiIiwaDAlUCXLl0YOXIkX3zxBTExMQCcOHGCUaNGeUdWEREpLkJCQnjmmWcCHaPEedHZn5Muz0dMckh8gNOIiEhhKHBh8corr3D11VdTvXp1mjdvDsD69euJi4vj3Xff9XlAEREREREp+gpcWFSuXJnffvuNOXPm8OuvvxIeHs5dd93Frbfeit1u90dGEREREREp4i7qpojIyEjuueceX2cRESl0TqeTH374AYBu3brpXjEpuiLLZ7QtF/giT/cLiUiA5OsT9Msvv+Taa6/Fbrfz5ZdfXrBvr169fBJMRKQwuN1uVq1aBaD7xHwo0fod11uWAvCB444ApykhbCEQEgXpJyG2Vu79IspltK961P+5RETOyVdh0adPH5KSkqhQoQJ9+vTJtZ9hGLhc/htWcPz48YwaNYqHHnqIyZMnA54x6MeOHctbb73F8ePHad26NW+88QaNGjXyWw4RKTmsVisdO3b0tsU3Glj3UKvmYQBspAc4jYiIFIZ8FRbuTJMbuQM00dGqVat46623aNq0aZb1L730EhMnTmTWrFnUrVuX559/ni5durB582aioqICklVEio/MhYX4kMWABM9HjGlRwSYiEgzydSFm2bJlOXLkCAADBw7k5MmTfg31d6dOneK2225j+vTplClTxrveNE0mT57M6NGj6du3L40bN2b27NmkpqYyd+7cQs0oIiLiV+mnPZdBARzelHu/sjWh6wueR21d3icihSdfhUV6ejopKSkAzJ49m7Nnz/o11N8NGzaM6667js6dO2dZv2PHDpKSkujatat3XWhoKB06dGDp0qW5Pl9aWhopKSlZHiISnEzT5OzZs5w9exbTNAMdp+QwTTjt9jx0XH0j9VhGu1rb3PuFRsGeFecey/2fS0TknHxdCtW2bVv69OlDixYtME2TBx98kPDw8Bz7zpgxw6cBP/jgA9auXeu9uTKzpKQkAOLi4rKsj4uLY9euXbk+5/jx4xk7dqxPc4pI8eRwOHjxxRcBGDVqFCEhIQFOVDIYbhNWee6tsDRyBjhNCRR1gUkHXenw57mBVtw69iJSePJ1xuK9996jR48enDp1CoDk5GSOHz+e48OX9uzZw0MPPcR7771HWFhYrv0Mw8iybJpmtnWZjRw5kuTkZO9jz549PsssIiIiIhKM8nXGIi4uzvuNXo0aNXj33XeJjY31azCANWvWcOjQIVq0aOFd53K5WLx4MVOmTGHz5s2A58xFxYoVvX0OHTqU7SxGZqGhoYSGhvovuIgUG3a7naeffhoAi8b/l5LAmely5bO61FdECk+Bb96+5pprCu1SgU6dOvH777+zfv1676Nly5bcdtttrF+/npo1axIfH8+CBQu8+6Snp7No0SLatWtXKBlFpHgzDAOr1YrVar3gmU6RImXDvNy37c50X8Wun/2fRUTknHydsTh/83a5cuWYPXs2EyZMKJShXKOiomjcuHGWdZGRkcTGxnrXDx8+nHHjxlGnTh3q1KnDuHHjiIiIoH///n7PJyIiIiIiHkX+5u28jBgxgjNnzjB06FDvBHnz58/XHBYiki8ul4sff/wR8Jwl1SR5vrHM1Yiabs8gGmnWyACnCTY68yYigZGvwuK9995j0qRJbNu2DcMwSE5OLvQhZ89buHBhlmXDMBgzZgxjxowJSB4RKd5cLpd3eOqOHTuqsPCRee4rqeTeD8BpW5k8eouISElQpG/eFhHxN6vV6r0nS0WF77gMC2sqNwCgom6KFxEJCvkqLDLbsWOHP3KIiASE1WrNMsmm+IbbYmVJjcsBuMmigs0nNLiAiBRx+f4aqUePHiQnJ3uXX3jhBU6cOOFdPnr0KA0bNvRpOBERETknpgqERnva5erl3s+WaTj10Bj/ZhIRySTfhcUPP/xAWlqad3nChAkcO3bMu+x0Or3zSoiIFBemaeJyuXC5XJimGeg4JcYE67/ZbN7OZvN2yp3dHeg4wSU00+Alre8JXA4RCTr5vhTq7x+4+gAWkZLA4XAwbtw4AEaNGlVo8/SUdHbTSeiKVAAsDZwBTiMiIoVBd9SJiIiIiMgly/cZC8Mwss1Kq1lqRaS4s9vtPPnkk962SJF1+iikpXjaRy5w6XGVK2Doudm3IzSCo4gUngJdCpWYmEhoqOemsLNnzzJkyBAiIz0TH2W+/0JEpLgwDIOwsLBAxxDJW/qpjHajvrn3c6bB25097Tpd4KZZfo0lInJevguLO++8M8vy7bffnq3PHXfccemJRERE5BKYGUWIIzCT2YpIcMp3YTFz5kx/5hARCQiXy8WSJUsAuOqqqzRJnh9oqA8RkeBQ4AnyRERKEpfLxcKFCwFo166dCgsf0O13AZayP6N9YH3AYohI8FFhISJBzWKxcMUVV3jb4iMGUMlTpJmGjqvPbfgMbsrlSoLDmW7sPnmgcPKIiKDCQkSCnM1m47rrrgt0jBLHtFigrmeULVNngUREgoIKCxER8bkZzmv5ztUKgAo2DXkqIhIMVFiIiIjPbXBXZ7sjHoDrLOEBTiMiIoVBhYWIBLX09HRefPFFAJ588klCQkICnKhksLud3LvyUwAOXPFIgNOIiEhhUGEhIkHP7XYHOoJI3kIiA51AROSCVFiISFCz2+088sgj3rb4Rk1jP1WMwwAccWuSNp+ILAdhMXA2GWLr5N4vNCqjXeNq/+cSETlHhYWIBDXDMIiOjg50jBJnsPUbbrQuBmCH42CA0wQZW6bL+aq2CVwOEQk6GlxcREREREQumc5YiEhQc7lcLF++HIA2bdpo5m0p/qyhEFXJ0858WZSIiJ+psBCRoOZyuViwYAEAV1xxhQoLKbpO7PHcXwFwdGvu/aq2gptmedrhpf2dSkTES4WFiAQ1i8VCs2bNvG3xEQOI9xRppqHj6hOmK6Pd+Mbc+6WdhBldPe063eC2j/ybS0TkHBUWIhLUbDYbffr0CXSMEse0WKC+Z5QtU2eBRESCgr5GEhERERGRS6YzFiIi4numCS4zoy2FJ+n3jPbWHwKXQ0SCjs5YiEhQS09P58UXX+TFF18kPT090HFKDMNtwpI0WJKGxeUMdJyS549Pct926lDh5RARyURnLEQk6J09q5mhRURELpUKCxEJana7nQceeMDbFt942nkXB5yeORSOhlQNcBoRESkMKixEJKgZhkFsbGygY5Q4Tmy4zl1tq+FmRUSCg97tRURERETkkumMhYgENZfLxZo1awBo0aKFZt6WoitCZ9ZEpGhTYSEiQc3lcvHtt98C0KxZMxUWPtLNsor2lj8A+N55PMBpSojQKAgvA2eOQ9laufcLL5PRbveg/3OJiJyjwkJEgprFYqFhw4betvhGB+uvXBH3FwAL3ScDnCbIGJna9oiAxRCR4KPCQkSCms1m4+abbw50jBLHtFigkWeULVNngUREgoIKCxERkeLAmea5DAogeW/u/aIqQfPbPe2KTf2fS0TkHBUWIiIixUHKvoz2hW7kLlsDoit72mmn/JtJRCQTFRYiEtQcDgevvfYaAA8++KAmyfMRi8sNP3tmNLdWdwQ4TQmUcGXu25xnYdEET7t2F7isX+FkEpGgp8JCRIKaaZqcPHnS2xYREZGLo8JCRIKazWZjyJAh3raIiIhcHH2KikhQs1gsxMfHBzqGSMHsXp77tm0/ZbT/WuD/LCIi52jQdhERkeImeXfu29yuwsshIpKJzliISFBzuVz8/vvvADRp0kQzb/vIdjOevWZ5ANItoQFOIyIihUGFhYgENZfLxeeffw5Aw4YNVVj4yHRXT0JcqQCcCKkY4DQiIlIYVFiISFCzWCzUqVPH2xbfcBsWdpSpBECoYQQ4jYiIFAYVFiIS1Gw2G7fddlugY5Q4LouVLxpdA8CNVn3UiIgEA309JyIiUhzEVMtoh5fJvZ9Fl/OJSGDoayQREfG5B62f0dW6GoCv0scDlwU2UElgtUF4WThzDMJK594vNCqj3XGU32OJiJynwkJEgprD4WDatGkA3Hfffdjt9gAnKhmquA/ReNlmAH6ocTrAaUREpDCosBCRoGaaJseOHfO2xYfcOp4iIsFEhYWIBDWbzcbAgQO9bZEi68xxz2VQAMd35N4vvgncONPTrtDA/7lERM7Rp6iIBDWLxUK1atXy7igSaKnHMtqNb8y9X0gkLHvD067aCrqP928uEZFzVFiIiIgUNxca+cntgn2eG+cJL10ocUREoIgPNzt+/HiuuOIKoqKiqFChAn369GHz5s1Z+pimyZgxY6hUqRLh4eF07NiRDRs2BCixiBQ3brebDRs2sGHDBtxud6DjiIiIFFtFurBYtGgRw4YNY/ny5SxYsACn00nXrl05fTpjhJGXXnqJiRMnMmXKFFatWkV8fDxdunTh5MmTAUwuIsWF0+nk448/5uOPP8bpdAY6jsilO3sio52yP2AxRCT4FOlLob7//vssyzNnzqRChQqsWbOGq6++GtM0mTx5MqNHj6Zv374AzJ49m7i4OObOncu9994biNgiUowYhkFCQoK3Lb5hApQ+/92VjqvP/fYh9H0r5217VmW0D20snDwiIhTxwuLvkpOTAShbtiwAO3bsICkpia5du3r7hIaG0qFDB5YuXZprYZGWlkZaWpp3OSUlxY+pRaQos9vtJCYmBjpGiWNaLdAsBAC3RtsSEQkKxebd3jRNHnnkEa688koaN24MQFJSEgBxcXFZ+sbFxbFr165cn2v8+PGMHTvWf2FFRILc9+4r2O3wvDeftpUJcBoRESkMxaawuP/++/ntt9/4+eefs237++ULpmle8JKGkSNH8sgjj3iXU1JSqFq1qu/CiogEuYXu5iykOQA32koHNoyIiBSKYlFYPPDAA3z55ZcsXryYKlWqeNfHx8cDnjMXFStW9K4/dOhQtrMYmYWGhhIaGuq/wCJSbDgcDt555x0ABg0ahN1uD3CiksHucjBw9RcAnLhsWIDTiIhIYSjSo0KZpsn999/PZ599xv/+9z9q1KiRZXuNGjWIj49nwYIF3nXp6eksWrSIdu3aFXZcESmGTNMkKSmJpKQkTNMMdJwSJdyRRrgjLe+Okj9WFb0iUrQV6TMWw4YNY+7cuXzxxRdERUV576mIiYkhPDwcwzAYPnw448aNo06dOtSpU4dx48YRERFB//79A5xeRIoDm83GgAEDvG3xjUjOEI6nqLCYGsbXJ0pXg4hykHoEyiTk3s+W6Yx8bG2/xxIROa9If4pOmzYNgI4dO2ZZP3PmTO8oLiNGjODMmTMMHTqU48eP07p1a+bPn09UVFQhpxWR4shisVCrVq1AxyhxnrS9zy22HwCYmHYz0CawgYKJLSyj3bRf4HKISNAp0oVFfi5LMAyDMWPGMGbMGP8HEhERERGRHBXpwkJExN/cbjd//fUXALVr18ZiKdK3nomIiBRZKixEJKg5nU7mzp0LwKhRowgJCQlwopJH98T7SPI+z/0VAMd35t6vZkcY/oenHRLp71QiIl4qLEQkqBmGQaVKlbxtuXSGYWACRJ0/+6Pj6hOOMxnty27NvZ/zLEz2TCRLrX/AgHn+zSUico4KCxEJana7nXvuuSfQMUoc02qBFp6zP26NtiUiEhR0MbGIiIiIiFwyFRYiIiIlydG/Mtq7lgYuh4gEHRUWIhLUHA4H77zzDu+88w4OhyPQcUoMw+WG5WmwPA2LUxPk+dyv7+e+7ei2jLbzrP+ziIicowtfRSSomabJnj17vG3xDQPg7PnjqeMqIhIMVFiISFCz2Wzccsst3rb4xlRnb1znzlQcDakS4DQiIlIY9CkqIkHNYrFQv379QMcocQ4Qy2FKA+C0hAY2jIiIFArdYyEiIiIiIpdMZyxEJKi53W52794NQLVq1bBY9H2LFFHhpQOdQETkgvQJKiJBzel0MmvWLGbNmoVToxf5TDPjL+obu6lv7CbcdTLQcUqGyHIQWcHTLl0t934hERntJjf7N5OISCY6YyEiQc0wDMqXL+9ti2/0tS6he9QaADY6Dgc4TZAxrBnt8nUDl0NEgo4KCxEJana7nWHDhgU6RoljWi3QKgQAt0bbEhEJCnq3FxERKQ5ME0xXRjs3YTFQuaWnHVXR/7lERM5RYSEiIlIcHNkKqUc97eQ9ufer0hLanjsLV6qC/3OJiJyjwkJEgprD4eD9998H4NZbb8Vutwc4UclguNywNh0ASzVHgNOUQM1uy32bMw0+ucvTrtkREq4slEgiIiosRCSomabJ9u3bvW3xDQMg1X1uScdVRCQYqLAQkaBms9no27evty0iIiIXR5+iIhLULBYLTZs2DXQMkYI5dSj3bbuXZbS3L/R7FBGR8zRBnoiISHHz14Lct505UWgxREQy0xkLEQlqbrebAwcOAFCxYkUsFn3f4gunCCPVDAXA1HdYIiJBQYWFiAQ1p9PJ9OnTARg1ahQhISEBTlQyTHDeyimXZwbog2E1A5xGREQKgwoLEQlqhmFQunRpb1t8w8QgJbTUubaIiAQDFRYiEtTsdjvDhw8PdIwSx2m1MeOK3gDcYNPcICIiwUAXvoqIiBQHMZXz109n3kQkQHTGQkREfO4m60LaWTYAsCH9IeCygOYpEUIioVQcnDoIMVVz72ePyGh3esb/uUREzlFhISJBzel08sknnwBw4403apI8H2nm3sr//fYTADtr3BHgNCIiUhj0CSoiQc3tdrNp0yZvW3zDADh5/njq9m0RkWCgwkJEgprVauX666/3tkWKrLRTnsugAJL35N4vtjZ0eMLTrtbW/7lERM5RYSEiQc1qtdKiRYtAxxDJW/LejHaVVrn3K1MdUvZ52ntXQ/V2/s0lInKORoUSEREpbsrVzX2b2wnr3vM8/vpv4WUSkaCnMxYiEtRM0+Tw4cMAlC9fXpPkiYiIXCSdsRCRoOZwOJg6dSpTp07F4XAEOo7IpXNl+j12pQcuh4gEHRUWIhL0IiIiiIiIyLujFIzd8DzE9zbMy33b1gUZ7d3L/J9FROQcXQolIkEtJCSEESNGBDpGieO2WqB9qKdtswc4TQnkOB3oBCIi2aiwEBERn/vVrEWEKw2AM5aoAKcREZHCoMJCRER87mNXRz52dQTghpD4wIYREZFCocJCRIKa0+nkiy++AKB3797YbHpb9AWby0mfjQsBMJreFtgwIiJSKHTztogENbfbze+//87vv/+O2+0OdJwSw8CkSvJBqiQfRLdvi4gEB301JyJBzWq10r17d29bpMiqUB+iKsLJAxBdJfd+lky/x7Zw/+cSETlHhYWIBDWr1UqbNm0CHaPEedz2IbdbvwHgzbO9gZaBDRRMbKEZ7Q4a8UxECo8KCxER8bkI0ogwPKNCWUxXgNOIiEhhUGEhIkHNNE2Sk5MBiImJwTB0R4CIiMjFUGEhIkHN4XAwefJkAEaNGkVISEhgA4nk5mSS5/4KgJS9ufer1hYGnZt9O+YC92KIiPiYCgsRCXp2u2aG9guLzv741JnjGe3mA3LvZw+Hj+70tKu2gptn+zeXiMg5KixEJKiFhIQwevToQMcocdxWC1ztuYnYrblBCt/J/Z7/ph4NbA4RCSqax0JERERERC6ZCgsREZGSJGV/RvvI1sDlEJGgo8JCRIKa0+nkyy+/5Msvv8TpdAY6TolhuN3wmwN+c2C4NNysz617N/dt+9ZmtE8l+T+LiMg5KixEJKi53W7Wrl3L2rVrcbvdgY5TYhgmcMwFx1wYphnoOCIiUgh0R52IBDWr1co//vEPb1t8Y56rPeVchwA4YY8LcBoRESkMJeaMxdSpU6lRowZhYWG0aNGCJUuWBDqSiBQDVquVq6++mquvvlqFhQ/9ZtZik1mNTWY1Um0xgY4jIiKFoEQUFh9++CHDhw9n9OjRrFu3jquuuoprr72W3bt3BzqaiIiIiEhQKBGFxcSJExk0aBB33303DRo0YPLkyVStWpVp06YFOpqIFHGmaXL69GlOnz6NqXsBpCgLiQx0AhGRCyr291ikp6ezZs0annzyySzru3btytKlSwOUSkSKC4fDwcsvvwzAqFGjCAkJCXCikiGeY8RzjCutf5Cy+zB8VDH3zl1fgNJVM5a3L4LV7+T9Q8LLwPWvZl237A3YsyLvfROuglaDs66bNwQcqXnv22YoVGuTsXx0G/w4Nu/9APpMy1ogbJjneeSlbE3oPAaiK0PKPrCFwUd3QItEqPWPjH4p+2H51Izl6ld6/vvbR7Dp67x/TvkGcM3IrOvmPwUn8nEFQJOboMH1GctnTsBXD+a9H0CXf0KZhIzlnT/Dyrfy3i80GnpPybpu+TTYvSzvfau3h9b3Zl33+TBIP5n3vq2HQPV2GcvHtsN/x+S9H0CvKRAWnbG88Uv445O89yuT4DlOmf00Dg5vynvf+tdD05sylp3p8Nnd+YpLx5FQoUHG8t41sPTV3PufZ7HDjX/7/3jNLNj2v7z3rXQ5XDk867pvHoXTh/Pe9/I7oHbnjOWTSfDdiLz3A+jxLyhVIWN564ILj8B2Xqk46PFy1nVLJsKB9XnvW7uzJ3NmnwwEdz5GKWw/HCpfnrF8cCMsejHv/QBueAes9ozlC71HpKbn7zkpAYXFkSNHcLlcxMVlvTkwLi6OpKSch9lLS0sjLS3Nu5ycnAxASkqK/4KKSJGUnp7ufT9ISUlRYeEDZ1NP0d/1BT34H7gg+lgyKclG7jtcfj9YMt2HsXczrPs87x9UKg46PJd13ZZf8vcHtDMU6vfLum7915Cej8+Bal2gdMOM5UN78pcXoMPzEJ5p+N2dv+Vv34rNoNUjkA6kmZB2xrNfhdZQvmVGv6MHYceajOUqHSElBbavzd/PqXYQWgzLuu6P/8LhjXnvG10fKnfIWD51NP/HpdkQsJbNWM7v70BEObjmb/9mW5fBxnzsm26FBrdmXffr13D2eN77VvkHlGmcsXx4X/5f61X/hMwnn3bl83cgrjG0fizruj9/yl8hHVoZErplLDvO5j9vg9sgrHLG8oFt+dvXEgJdJ2Vdt21l/vY9eQaaDsy67vfvIXlP3vuWvwIqtMpYPpaU/9fa6jFwh2Us79mYv33LJMCVT2ddt/ln2PbfvPc1YqB2n6zr1n4OZj4Ki1q9Iap2xvLBXfl/rZ0ngi3T590F3iNS0jxn8/N1Vt8s5vbt22cC5tKlS7Osf/7558169erluM+zzz5rAnrooYceeuihhx566KFHPh579uzJ8+/yYn/Goly5clit1mxnJw4dOpTtLMZ5I0eO5JFHHvEunzhxgurVq7N7925iYmL8mjeYpKSkULVqVfbs2UN0dHTeO0iedEz9Q8fVP3RcfU/H1D90XP1Dx9X3AnFMTdPk5MmTVKpUKc++xb6wCAkJoUWLFixYsID/+7//865fsGABvXv3znGf0NBQQkNDs62PiYnRL74fREdH67j6mI6pf+i4+oeOq+/pmPqHjqt/6Lj6XmEf0/x+8V7sCwuARx55hAEDBtCyZUvatm3LW2+9xe7duxkyZEigo4mIiIiIBIUSUVj069ePo0eP8s9//pMDBw7QuHFjvv32W6pXrx7oaCIiIiIiQaFEFBYAQ4cOZejQoRe1b2hoKM8++2yOl0fJxdNx9T0dU//QcfUPHVff0zH1Dx1X/9Bx9b2ifkwN09SMUCIiIiIicmlKxMzbIiIiIiISWCosRERERETkkqmwEBERERGRSxY0hcXUqVOpUaMGYWFhtGjRgiVLllyw/6JFi2jRogVhYWHUrFmTN998s5CSFh8FOaYHDhygf//+1KtXD4vFwvDhwwsvaDFTkOP62Wef0aVLF8qXL090dDRt27blhx9+KMS0xUdBjuvPP/9M+/btiY2NJTw8nPr16zNp0qRCTFs8FPR99bxffvkFm81Gs2bN/BuwmCrIcV24cCGGYWR7bNq0qRATFw8F/X1NS0tj9OjRVK9endDQUGrVqsWMGTMKKW3xUZDjmpiYmOPva6NGjQoxcdFX0N/VOXPmcNlllxEREUHFihW56667OHr0aCGl/Zs85+YuAT744APTbreb06dPNzdu3Gg+9NBDZmRkpLlr164c+2/fvt2MiIgwH3roIXPjxo3m9OnTTbvdbn7yySeFnLzoKugx3bFjh/nggw+as2fPNps1a2Y+9NBDhRu4mCjocX3ooYfMCRMmmCtXrjS3bNlijhw50rTb7ebatWsLOXnRVtDjunbtWnPu3LnmH3/8Ye7YscN89913zYiICPPf//53IScvugp6TM87ceKEWbNmTbNr167mZZddVjhhi5GCHteffvrJBMzNmzebBw4c8D6cTmchJy/aLub3tVevXmbr1q3NBQsWmDt27DBXrFhh/vLLL4WYuugr6HE9ceJElt/TPXv2mGXLljWfffbZwg1ehBX0mC5ZssS0WCzmq6++am7fvt1csmSJ2ahRI7NPnz6FnNwjKAqLVq1amUOGDMmyrn79+uaTTz6ZY/8RI0aY9evXz7Lu3nvvNdu0aeO3jMVNQY9pZh06dFBhkYtLOa7nNWzY0Bw7dqyvoxVrvjiu//d//2fefvvtvo5WbF3sMe3Xr5/51FNPmc8++6wKixwU9LieLyyOHz9eCOmKr4Ie1++++86MiYkxjx49Whjxiq1LfW+dN2+eaRiGuXPnTn/EK5YKekxffvlls2bNmlnWvfbaa2aVKlX8lvFCSvylUOnp6axZs4auXbtmWd+1a1eWLl2a4z7Lli3L1r9bt26sXr0ah8Pht6zFxcUcU8mbL46r2+3m5MmTlC1b1h8RiyVfHNd169axdOlSOnTo4I+Ixc7FHtOZM2eybds2nn32WX9HLJYu5Xe1efPmVKxYkU6dOvHTTz/5M2axczHH9csvv6Rly5a89NJLVK5cmbp16/LYY49x5syZwohcLPjivfWdd96hc+fOmtD4nIs5pu3atWPv3r18++23mKbJwYMH+eSTT7juuusKI3I2JWaCvNwcOXIEl8tFXFxclvVxcXEkJSXluE9SUlKO/Z1OJ0eOHKFixYp+y1scXMwxlbz54ri+8sornD59mptvvtkfEYulSzmuVapU4fDhwzidTsaMGcPdd9/tz6jFxsUc061bt/Lkk0+yZMkSbLYS/9FzUS7muFasWJG33nqLFi1akJaWxrvvvkunTp1YuHAhV199dWHELvIu5rhu376dn3/+mbCwMObNm8eRI0cYOnQox44d030W51zqZ9aBAwf47rvvmDt3rr8iFjsXc0zbtWvHnDlz6NevH2fPnsXpdNKrVy9ef/31woicTdC8uxuGkWXZNM1s6/Lqn9P6YFbQYyr5c7HH9f3332fMmDF88cUXVKhQwV/xiq2LOa5Llizh1KlTLF++nCeffJLatWtz6623+jNmsZLfY+pyuejfvz9jx46lbt26hRWv2CrI72q9evWoV6+ed7lt27bs2bOHf/3rXyos/qYgx9XtdmMYBnPmzCEmJgaAiRMncuONN/LGG28QHh7u97zFxcV+Zs2aNYvSpUvTp08fPyUrvgpyTDdu3MiDDz7IM888Q7du3Thw4ACPP/44Q4YM4Z133imMuFmU+MKiXLlyWK3WbJXeoUOHslWE58XHx+fY32azERsb67esxcXFHFPJ26Uc1w8//JBBgwbx8ccf07lzZ3/GLHYu5bjWqFEDgCZNmnDw4EHGjBmjwoKCH9OTJ0+yevVq1q1bx/333w94/nAzTRObzcb8+fP5xz/+USjZizJfvbe2adOG9957z9fxiq2LOa4VK1akcuXK3qICoEGDBpimyd69e6lTp45fMxcHl/L7apomM2bMYMCAAYSEhPgzZrFyMcd0/PjxtG/fnscffxyApk2bEhkZyVVXXcXzzz9f6FfZlPh7LEJCQmjRogULFizIsn7BggW0a9cux33atm2brf/8+fNp2bIldrvdb1mLi4s5ppK3iz2u77//PomJicydOzdg11QWZb76fTVNk7S0NF/HK5YKekyjo6P5/fffWb9+vfcxZMgQ6tWrx/r162ndunVhRS/SfPW7um7duqC/ZDezizmu7du3Z//+/Zw6dcq7bsuWLVgsFqpUqeLXvMXFpfy+Llq0iL/++otBgwb5M2KxczHHNDU1FYsl65/zVqsVyLjaplAV/v3ihe/80F3vvPOOuXHjRnP48OFmZGSkdxSCJ5980hwwYIC3//nhZh9++GFz48aN5jvvvKPhZv+moMfUNE1z3bp15rp168wWLVqY/fv3N9etW2du2LAhEPGLrIIe17lz55o2m8184403sgzhd+LEiUC9hCKpoMd1ypQp5pdffmlu2bLF3LJlizljxgwzOjraHD16dKBeQpFzMe8BmWlUqJwV9LhOmjTJnDdvnrllyxbzjz/+MJ988kkTMD/99NNAvYQiqaDH9eTJk2aVKlXMG2+80dywYYO5aNEis06dOubdd98dqJdQJF3s+8Dtt99utm7durDjFgsFPaYzZ840bTabOXXqVHPbtm3mzz//bLZs2dJs1apVQPIHRWFhmqb5xhtvmNWrVzdDQkLMyy+/3Fy0aJF325133ml26NAhS/+FCxeazZs3N0NCQsyEhARz2rRphZy46CvoMQWyPapXr164oYuBghzXDh065Hhc77zzzsIPXsQV5Li+9tprZqNGjcyIiAgzOjrabN68uTl16lTT5XIFIHnRVdD3gMxUWOSuIMd1woQJZq1atcywsDCzTJky5pVXXml+8803AUhd9BX09/XPP/80O3fubIaHh5tVqlQxH3nkETM1NbWQUxd9BT2uJ06cMMPDw8233nqrkJMWHwU9pq+99prZsGFDMzw83KxYsaJ52223mXv37i3k1B6GaQbiPImIiIiIiJQkJf4eCxERERER8T8VFiIiIiIicslUWIiIiIiIyCVTYSEiIiIiIpdMhYWIiIiIiFwyFRYiIiIiInLJVFiIiIiIiMglU2EhIiIiIiKXTIWFiIj4xJgxY2jWrFnAfv7TTz/NPffck6++jz32GA8++KCfE4mIBBfNvC0iInkyDOOC2++8806mTJlCWloasbGxhZQqw8GDB6lTpw6//fYbCQkJefY/dOgQtWrV4rfffqNGjRr+DygiEgRUWIiISJ6SkpK87Q8//JBnnnmGzZs3e9eFh4cTExMTiGgAjBs3jkWLFvHDDz/ke58bbriB2rVrM2HCBD8mExEJHroUSkRE8hQfH+99xMTEYBhGtnV/vxQqMTGRPn36MG7cOOLi4ihdujRjx47F6XTy+OOPU7ZsWapUqcKMGTOy/Kx9+/bRr18/ypQpQ2xsLL1792bnzp0XzPfBBx/Qq1evLOs++eQTmjRpQnh4OLGxsXTu3JnTp097t/fq1Yv333//ko+NiIh4qLAQERG/+d///sf+/ftZvHgxEydOZMyYMfTs2ZMyZcqwYsUKhgwZwpAhQ9izZw8AqampXHPNNZQqVYrFixfz888/U6pUKbp37056enqOP+P48eP88ccftGzZ0rvuwIED3HrrrQwcOJA///yThQsX0rdvXzKfpG/VqhV79uxh165d/j0IIiJBQoWFiIj4TdmyZXnttdeoV68eAwcOpF69eqSmpjJq1Cjq1KnDyJEjCQkJ4ZdffgE8Zx4sFgtvv/02TZo0oUGDBsycOZPdu3ezcOHCHH/Grl27ME2TSpUqedcdOHAAp9NJ3759SUhIoEmTJgwdOpRSpUp5+1SuXBkgz7MhIiKSP7ZABxARkZKrUaNGWCwZ32HFxcXRuHFj77LVaiU2NpZDhw4BsGbNGv766y+ioqKyPM/Zs2fZtm1bjj/jzJkzAISFhXnXXXbZZXTq1IkmTZrQrVs3unbtyo033kiZMmW8fcLDwwHPWRIREbl0KixERMRv7HZ7lmXDMHJc53a7AXC73bRo0YI5c+Zke67y5cvn+DPKlSsHeC6JOt/HarWyYMECli5dyvz583n99dcZPXo0K1as8I4CdezYsQs+r4iIFIwuhRIRkSLj8ssvZ+vWrVSoUIHatWtneeQ26lStWrWIjo5m48aNWdYbhkH79u0ZO3Ys69atIyQkhHnz5nm3//HHH9jtdho1auTX1yQiEixUWIiISJFx2223Ua5cOXr37s2SJUvYsWMHixYt4qGHHmLv3r057mOxWOjcuTM///yzd92KFSsYN24cq1evZvfu3Xz22WccPnyYBg0aePssWbKEq666yntJlIiIXBoVFiIiUmRERESwePFiqlWrRt++fWnQoAEDBw7kzJkzREdH57rfPffcwwcffOC9pCo6OprFixfTo0cP6taty1NPPcUrr7zCtdde693n/fffZ/DgwX5/TSIiwUIT5ImISLFnmiZt2rRh+PDh3HrrrXn2/+abb3j88cf57bffsNl0u6GIiC/ojIWIiBR7hmHw1ltv4XQ689X/9OnTzJw5U0WFiIgP6YyFiIiIiIhcMp2xEBERERGRS6bCQkRERERELpkKCxERERERuWQqLERERERE5JKpsBARERERkUumwkJERERERC6ZCgsREREREblkKixEREREROSSqbAQEREREZFLpsJCREREREQu2f8DXwrB20xCe1cAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "for ch_pas, ch_py in zip(channels_pas, channels_py):\n", " plt.figure(figsize=(8,4))\n", " plt.plot(time, outputs_pas[ch_pas], label='IndMach012', lw=3)\n", " plt.plot(time, outputs_py[ch_py], label='PyIndMach012', ls='--', lw=2)\n", + " if outputs_cpp:\n", + " plt.plot(time, outputs_cpp[ch_py], label='CppIndMach012', ls='-.', lw=2)\n", + "\n", " plt.axvline(0.3, linestyle=':', color='k', alpha=0.5, label='Fault occurs')\n", " plt.axvline(0.4, linestyle='--', color='r', alpha=0.5, label='Relays operate')\n", " plt.legend()\n", @@ -829,7 +410,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.2" } }, "nbformat": 4, diff --git a/docs/index.md b/docs/index.md index 9e8a90b3..c239ebc8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,7 +26,7 @@ flowchart TD -DSS-Python is one of three Python projects under DSS-Extensions. See [DSS-Extensions — OpenDSS: Overview of Python APIs](https://dss-extensions.org/python_apis.html) for a brief comparison between these and the official COM API. Both OpenDSSDirect.py and DSS-Python expose the classic OpenDSS API (closer to the COM implementation). For an alternative API which exposes all OpenDSS objects, batch operations, and a more intuitive API, check [AltDSS-Python](https://dss-extensions.org/AltDSS-Python/). If required, users can mix all three packages in the same project to access some of their unique features. +DSS-Python is one of three Python projects under DSS-Extensions. See [DSS-Extensions — OpenDSS: Overview of Python APIs](https://dss-extensions.org/python_apis.html) for a brief comparison between these and EPRI's OpenDSS COM API. Both OpenDSSDirect.py and DSS-Python expose the classic OpenDSS API (closer to the COM implementation). For an alternative API which exposes all OpenDSS objects, batch operations, and a more intuitive API, check [AltDSS-Python](https://dss-extensions.org/AltDSS-Python/). If required, users can mix all three packages in the same project to access some of their unique features. ## Brief introduction @@ -48,7 +48,7 @@ In this documentation, since many features from DSS-Python are not available in Independent of which OpenDSS implementation you use, it is good practice to list the specific implementation begin used (e.g. from EPRI or from DSS-Extensions). At the moment we do not generate DOIs for our packages, but users can always cite a specific version on PyPI, e.g. https://pypi.org/project/dss-python/0.12.1/ -Check http://dss-extensions.org and https://github.com/dss-extensions/dss-extensions for links to more documentation and examples. Besides our own documentation, the official OpenDSS documentation is extensive and covers various topics. +Check http://dss-extensions.org and https://github.com/dss-extensions/dss-extensions for links to more documentation and examples. Besides our own documentation, EPRI's OpenDSS documentation is extensive and covers various topics. We recommend looking especially in the following resources: @@ -83,7 +83,7 @@ While OpenDSS relies on windows/forms to report errors, or require the user to c Although there are many classes and modules in DSS-Python, the main usage is typically through the default DSS instance, and that is the most interest aspect for most users. -DSS-Python tries to be a drop-in replacement for the official OpenDSS COM implementation, within reasonable limits. +DSS-Python tries to be a drop-in replacement for EPRI's OpenDSS COM implementation, within reasonable limits. There are two main Python packages that allow instantiating COM objects, `win32com` and `comtypes`. For a quick look into some Python APIs (COM, DSS-Python, OpenDSSDirect.py) for the OpenDSS (official or our alternative @@ -110,7 +110,7 @@ or with `comtypes`: DSS = comtypes.client.CreateObject("OpenDSSEngine.DSS") ``` -Either way, to use DSS-Python and effectively migrate from the official OpenDSS COM interface, you can replace that fragment with: +Either way, to use DSS-Python and effectively migrate from EPRI's OpenDSS COM interface, you can replace that fragment with: ```python from dss import DSS @@ -141,7 +141,6 @@ For a quick overview of DSS-Python, the main DSS class is organized as follows. - {class}`DSS.ActiveCircuit.CapControls ` - {class}`DSS.ActiveCircuit.CNData ` **(API Extension)** - {class}`DSS.ActiveCircuit.CtrlQueue ` - - {class}`DSS.ActiveCircuit.DSSim_Coms ` - {class}`DSS.ActiveCircuit.Fuses ` - {class}`DSS.ActiveCircuit.Generators ` - {class}`DSS.ActiveCircuit.GICSources ` @@ -172,6 +171,7 @@ For a quick overview of DSS-Python, the main DSS class is organized as follows. - {class}`DSS.ActiveCircuit.TSData ` **(API Extension)** - {class}`DSS.ActiveCircuit.Vsources ` - {class}`DSS.ActiveCircuit.WireData ` **(API Extension)** + - {class}`DSS.ActiveCircuit.WindGens ` - {class}`DSS.ActiveCircuit.XYCurves ` @@ -189,7 +189,7 @@ To enable: ``` After that, running the plot commands from the text interface or compile/redirect scripts will try to use matplotlib to -reproduce most of the plot options from the official OpenDSS. +reproduce most of the plot options from EPRI's OpenDSS. ```python dss.Text.Command = 'compile some_circuit/Master.dss' diff --git a/dss/IActiveClass.py b/dss/IActiveClass.py index 75bce1a8..82900f32 100644 --- a/dss/IActiveClass.py +++ b/dss/IActiveClass.py @@ -1,10 +1,10 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from __future__ import annotations from ._cffi_api_util import Base from .enums import DSSJSONFlags -from typing import AnyStr, List, Iterator +from typing import AnyStr, List, Iterator, Optional class IActiveClass(Base): __slots__ = [] @@ -23,7 +23,7 @@ def ActiveClassName(self) -> str: Original COM help: https://opendss.epri.com/ActiveClassName.html ''' - return self._get_string(self._check_for_error(self._lib.ActiveClass_Get_ActiveClassName())) + return self._lib.ActiveClass_Get_ActiveClassName() @property def AllNames(self) -> List[str]: @@ -32,7 +32,7 @@ def AllNames(self) -> List[str]: Original COM help: https://opendss.epri.com/AllNames.html ''' - return self._check_for_error(self._get_string_array(self._lib.ActiveClass_Get_AllNames)) + return self._lib.ActiveClass_Get_AllNames() @property def Count(self) -> int: @@ -41,10 +41,10 @@ def Count(self) -> int: Original COM help: https://opendss.epri.com/Count.html ''' - return self._check_for_error(self._lib.ActiveClass_Get_Count()) + return self._lib.ActiveClass_Get_Count() def __len__(self) -> int: - return self._check_for_error(self._lib.ActiveClass_Get_Count()) + return self._lib.ActiveClass_Get_Count() def __iter__(self) -> Iterator[IActiveClass]: n = self.First @@ -56,13 +56,13 @@ def __iter__(self) -> Iterator[IActiveClass]: def First(self) -> int: ''' Sets first element in the active class to be the active DSS object. - If the object is a CktElement, ActiveCktELement also points to this element. + If the object is a CktElement, ActiveCktElement also points to this element. Returns 0 if none. Original COM help: https://opendss.epri.com/First.html ''' - return self._check_for_error(self._lib.ActiveClass_Get_First()) + return self._lib.ActiveClass_Get_First() @property def Name(self) -> str: @@ -71,14 +71,11 @@ def Name(self) -> str: Original COM help: https://opendss.epri.com/Name.html ''' - return self._get_string(self._check_for_error(self._lib.ActiveClass_Get_Name())) + return self._lib.ActiveClass_Get_Name() @Name.setter def Name(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.ActiveClass_Set_Name(Value)) + self._lib.ActiveClass_Set_Name(Value) @property def Next(self) -> int: @@ -90,7 +87,7 @@ def Next(self) -> int: Original COM help: https://opendss.epri.com/Next.html ''' - return self._check_for_error(self._lib.ActiveClass_Get_Next()) + return self._lib.ActiveClass_Get_Next() @property def NumElements(self) -> int: @@ -99,7 +96,7 @@ def NumElements(self) -> int: Original COM help: https://opendss.epri.com/NumElements.html ''' - return self._check_for_error(self._lib.ActiveClass_Get_NumElements()) + return self._lib.ActiveClass_Get_NumElements() @property def ActiveClassParent(self) -> str: @@ -108,7 +105,7 @@ def ActiveClassParent(self) -> str: Original COM help: https://opendss.epri.com/ActiveClassParent.html ''' - return self._get_string(self._check_for_error(self._lib.ActiveClass_Get_ActiveClassParent())) + return self._lib.ActiveClass_Get_ActiveClassParent() def ToJSON(self, options: DSSJSONFlags = 0) -> str: ''' @@ -121,4 +118,17 @@ def ToJSON(self, options: DSSJSONFlags = 0) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.ActiveClass_ToJSON(options))) + return self._lib.ActiveClass_ToJSON(options) + + def to_altdss(self) -> Optional[DSSObject]: + ''' + Returns a Python object for the current active DSS object in this interface. + + Requires AltDSS-Python. + + *Available only for the AltDSS engine.* + + **(API Extension)** + ''' + ptr = self._lib.ActiveClass_Get_Pointer() + return self._api_util.get_dss_obj(ptr) diff --git a/dss/IBus.py b/dss/IBus.py index 3f1b6104..202e1bce 100644 --- a/dss/IBus.py +++ b/dss/IBus.py @@ -1,10 +1,17 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from __future__ import annotations from ._cffi_api_util import Base -from ._types import Float64Array, Float64ArrayOrComplexArray, Float64ArrayOrSimpleComplex, Int32Array -from typing import List, Union, Iterator +from ._types import Float64Array, ComplexArray, Complex, Int32Array +from typing import List, Union, Iterator, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + try: + from altdss import Bus as AltBus + except: + pass + class IBus(Base): __slots__ = [] @@ -52,7 +59,7 @@ def GetUniqueNodeNumber(self, StartNumber: int) -> int: Original COM help: https://opendss.epri.com/GetUniqueNodeNumber.html ''' - return self._check_for_error(self._lib.Bus_GetUniqueNodeNumber(StartNumber)) + return self._lib.Bus_GetUniqueNodeNumber(StartNumber) def ZscRefresh(self) -> bool: ''' @@ -60,7 +67,7 @@ def ZscRefresh(self) -> bool: Original COM help: https://opendss.epri.com/ZscRefresh.html ''' - return self._check_for_error(self._lib.Bus_ZscRefresh()) != 0 + return self._lib.Bus_ZscRefresh() @property def Coorddefined(self) -> bool: @@ -69,17 +76,16 @@ def Coorddefined(self) -> bool: Original COM help: https://opendss.epri.com/Coorddefined.html ''' - return self._check_for_error(self._lib.Bus_Get_Coorddefined()) != 0 + return self._lib.Bus_Get_Coorddefined() @property - def CplxSeqVoltages(self) -> Float64ArrayOrComplexArray: + def CplxSeqVoltages(self) -> ComplexArray: ''' Complex array of Sequence Voltages (0, 1, 2) at this Bus. Original COM help: https://opendss.epri.com/CplxSeqVoltages.html ''' - self._check_for_error(self._lib.Bus_Get_CplxSeqVoltages_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_CplxSeqVoltages_GR() @property def Cust_Duration(self) -> float: @@ -90,7 +96,7 @@ def Cust_Duration(self) -> float: Original COM help: https://opendss.epri.com/Cust_Duration.html ''' - return self._check_for_error(self._lib.Bus_Get_Cust_Duration()) + return self._lib.Bus_Get_Cust_Duration() @property def Cust_Interrupts(self) -> float: @@ -101,7 +107,7 @@ def Cust_Interrupts(self) -> float: Original COM help: https://opendss.epri.com/Cust_Interrupts.html ''' - return self._check_for_error(self._lib.Bus_Get_Cust_Interrupts()) + return self._lib.Bus_Get_Cust_Interrupts() @property def Distance(self) -> float: @@ -112,7 +118,7 @@ def Distance(self) -> float: Original COM help: https://opendss.epri.com/Distance.html ''' - return self._check_for_error(self._lib.Bus_Get_Distance()) + return self._lib.Bus_Get_Distance() @property def Int_Duration(self) -> float: @@ -123,10 +129,10 @@ def Int_Duration(self) -> float: Original COM help: https://opendss.epri.com/Int_Duration.html ''' - return self._check_for_error(self._lib.Bus_Get_Int_Duration()) + return self._lib.Bus_Get_Int_Duration() @property - def Isc(self) -> Float64ArrayOrComplexArray: + def Isc(self) -> ComplexArray: ''' Short circuit currents at bus; Complex Array. @@ -134,8 +140,7 @@ def Isc(self) -> Float64ArrayOrComplexArray: Original COM help: https://opendss.epri.com/Isc.html ''' - self._check_for_error(self._lib.Bus_Get_Isc_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_Isc_GR() @property def Lambda(self) -> float: @@ -146,7 +151,7 @@ def Lambda(self) -> float: Original COM help: https://opendss.epri.com/Lambda.html ''' - return self._check_for_error(self._lib.Bus_Get_Lambda()) + return self._lib.Bus_Get_Lambda() @property def N_Customers(self) -> int: @@ -157,7 +162,7 @@ def N_Customers(self) -> int: Original COM help: https://opendss.epri.com/N_Customers.html ''' - return self._check_for_error(self._lib.Bus_Get_N_Customers()) + return self._lib.Bus_Get_N_Customers() @property def N_interrupts(self) -> float: @@ -168,16 +173,16 @@ def N_interrupts(self) -> float: Original COM help: https://opendss.epri.com/N_interrupts.html ''' - return self._check_for_error(self._lib.Bus_Get_N_interrupts()) + return self._lib.Bus_Get_N_interrupts() @property def Name(self) -> str: ''' - Name of Bus + Name of the active Bus Original COM help: https://opendss.epri.com/Name1.html ''' - return self._get_string(self._check_for_error(self._lib.Bus_Get_Name())) + return self._lib.Bus_Get_Name() @property def Nodes(self) -> Int32Array: @@ -186,8 +191,7 @@ def Nodes(self) -> Int32Array: Original COM help: https://opendss.epri.com/Nodes.html ''' - self._check_for_error(self._lib.Bus_Get_Nodes_GR()) - return self._get_int32_gr_array() + return self._lib.Bus_Get_Nodes_GR() @property def NumNodes(self) -> int: @@ -196,7 +200,7 @@ def NumNodes(self) -> int: Original COM help: https://opendss.epri.com/NumNodes.html ''' - return self._check_for_error(self._lib.Bus_Get_NumNodes()) + return self._lib.Bus_Get_NumNodes() @property def SectionID(self) -> int: @@ -207,7 +211,7 @@ def SectionID(self) -> int: Original COM help: https://opendss.epri.com/SectionID.html ''' - return self._check_for_error(self._lib.Bus_Get_SectionID()) + return self._lib.Bus_Get_SectionID() @property def SeqVoltages(self) -> Float64Array: @@ -216,8 +220,7 @@ def SeqVoltages(self) -> Float64Array: Original COM help: https://opendss.epri.com/SeqVoltages.html ''' - self._check_for_error(self._lib.Bus_Get_SeqVoltages_GR()) - return self._get_float64_gr_array() + return self._lib.Bus_Get_SeqVoltages_GR() @property def TotalMiles(self) -> float: @@ -228,17 +231,16 @@ def TotalMiles(self) -> float: Original COM help: https://opendss.epri.com/TotalMiles.html ''' - return self._check_for_error(self._lib.Bus_Get_TotalMiles()) + return self._lib.Bus_Get_TotalMiles() @property - def VLL(self) -> Float64ArrayOrComplexArray: + def VLL(self) -> ComplexArray: ''' For 2- and 3-phase buses, returns array of complex numbers representing L-L voltages in volts. Returns -1.0 for 1-phase bus. If more than 3 phases, returns only first 3. Original COM help: https://opendss.epri.com/VLL.html ''' - self._check_for_error(self._lib.Bus_Get_VLL_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_VLL_GR() @property def VMagAngle(self) -> Float64Array: @@ -247,11 +249,10 @@ def VMagAngle(self) -> Float64Array: Original COM help: https://opendss.epri.com/VMagAngle.html ''' - self._check_for_error(self._lib.Bus_Get_VMagAngle_GR()) - return self._get_float64_gr_array() + return self._lib.Bus_Get_VMagAngle_GR() @property - def Voc(self) -> Float64ArrayOrComplexArray: + def Voc(self) -> ComplexArray: ''' Open circuit voltage; Complex array. @@ -259,21 +260,19 @@ def Voc(self) -> Float64ArrayOrComplexArray: Original COM help: https://opendss.epri.com/Voc.html ''' - self._check_for_error(self._lib.Bus_Get_Voc_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_Voc_GR() @property - def Voltages(self) -> Float64ArrayOrComplexArray: + def Voltages(self) -> ComplexArray: ''' Complex array of voltages at this bus. Original COM help: https://opendss.epri.com/Voltages.html ''' - self._check_for_error(self._lib.Bus_Get_Voltages_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_Voltages_GR() @property - def YscMatrix(self) -> Float64ArrayOrComplexArray: + def YscMatrix(self) -> ComplexArray: ''' Complex array of Ysc matrix at bus. Column by column. @@ -281,11 +280,10 @@ def YscMatrix(self) -> Float64ArrayOrComplexArray: Original COM help: https://opendss.epri.com/YscMatrix.html ''' - self._check_for_error(self._lib.Bus_Get_YscMatrix_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_YscMatrix_GR() @property - def Zsc0(self) -> Float64ArrayOrSimpleComplex: + def Zsc0(self) -> Complex: ''' Complex Zero-Sequence short circuit impedance at bus. @@ -293,11 +291,10 @@ def Zsc0(self) -> Float64ArrayOrSimpleComplex: Original COM help: https://opendss.epri.com/Zsc0.html ''' - self._check_for_error(self._lib.Bus_Get_Zsc0_GR()) - return self._get_complex128_gr_simple() + return self._lib.Bus_Get_Zsc0_GR() @property - def Zsc1(self) -> Float64ArrayOrSimpleComplex: + def Zsc1(self) -> Complex: ''' Complex Positive-Sequence short circuit impedance at bus. @@ -305,11 +302,10 @@ def Zsc1(self) -> Float64ArrayOrSimpleComplex: Original COM help: https://opendss.epri.com/Zsc1.html ''' - self._check_for_error(self._lib.Bus_Get_Zsc1_GR()) - return self._get_complex128_gr_simple() + return self._lib.Bus_Get_Zsc1_GR() @property - def ZscMatrix(self) -> Float64ArrayOrComplexArray: + def ZscMatrix(self) -> ComplexArray: ''' Complex array of Zsc matrix at bus. Column by column. @@ -317,8 +313,7 @@ def ZscMatrix(self) -> Float64ArrayOrComplexArray: Original COM help: https://opendss.epri.com/ZscMatrix.html ''' - self._check_for_error(self._lib.Bus_Get_ZscMatrix_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_ZscMatrix_GR() @property def kVBase(self) -> float: @@ -327,17 +322,16 @@ def kVBase(self) -> float: Original COM help: https://opendss.epri.com/kVBase.html ''' - return self._check_for_error(self._lib.Bus_Get_kVBase()) + return self._lib.Bus_Get_kVBase() @property - def puVLL(self) -> Float64ArrayOrComplexArray: + def puVLL(self) -> ComplexArray: ''' Returns Complex array of pu L-L voltages for 2- and 3-phase buses. Returns -1.0 for 1-phase bus. If more than 3 phases, returns only 3 phases. Original COM help: https://opendss.epri.com/puVLL.html ''' - self._check_for_error(self._lib.Bus_Get_puVLL_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_puVLL_GR() @property def puVmagAngle(self) -> Float64Array: @@ -346,23 +340,21 @@ def puVmagAngle(self) -> Float64Array: Original COM help: https://opendss.epri.com/puVmagAngle.html ''' - self._check_for_error(self._lib.Bus_Get_puVmagAngle_GR()) - return self._get_float64_gr_array() + return self._lib.Bus_Get_puVmagAngle_GR() @property - def puVoltages(self) -> Float64ArrayOrComplexArray: + def puVoltages(self) -> ComplexArray: ''' Complex Array of pu voltages at the bus. Original COM help: https://opendss.epri.com/puVoltages.html ''' - self._check_for_error(self._lib.Bus_Get_puVoltages_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_puVoltages_GR() @property - def ZSC012Matrix(self) -> Float64ArrayOrComplexArray: + def ZSC012Matrix(self) -> ComplexArray: ''' - Array of doubles (complex) containing the complete 012 Zsc matrix. + Complex array containing the complete 012 Zsc matrix. Only available after Zsc is computed, either through the "ZscRefresh" command, or running a "FaultStudy" solution. Only available for buses with 3 nodes. @@ -370,8 +362,7 @@ def ZSC012Matrix(self) -> Float64ArrayOrComplexArray: Original COM help: https://opendss.epri.com/ZSC012Matrix.html ''' - self._check_for_error(self._lib.Bus_Get_ZSC012Matrix_GR()) - return self._get_complex128_gr_array() + return self._lib.Bus_Get_ZSC012Matrix_GR() @property def x(self) -> float: @@ -380,11 +371,11 @@ def x(self) -> float: Original COM help: https://opendss.epri.com/x.html ''' - return self._check_for_error(self._lib.Bus_Get_x()) + return self._lib.Bus_Get_x() @x.setter def x(self, Value: float): - self._check_for_error(self._lib.Bus_Set_x(Value)) + self._lib.Bus_Set_x(Value) @property def y(self) -> float: @@ -393,11 +384,11 @@ def y(self) -> float: Original COM help: https://opendss.epri.com/y.html ''' - return self._check_for_error(self._lib.Bus_Get_y()) + return self._lib.Bus_Get_y() @y.setter def y(self, Value: float): - self._check_for_error(self._lib.Bus_Set_y(Value)) + self._lib.Bus_Set_y(Value) @property def LoadList(self) -> List[str]: @@ -406,7 +397,7 @@ def LoadList(self) -> List[str]: Original COM help: https://opendss.epri.com/LoadList.html ''' - return self._check_for_error(self._get_string_array(self._lib.Bus_Get_LoadList)) + return self._lib.Bus_Get_LoadList() @property def LineList(self) -> List[str]: @@ -415,16 +406,18 @@ def LineList(self) -> List[str]: Original COM help: https://opendss.epri.com/LineList.html ''' - return self._check_for_error(self._get_string_array(self._lib.Bus_Get_LineList)) + return self._lib.Bus_Get_LineList() @property def AllPCEatBus(self) -> List[str]: ''' Returns an array with the names of all PCE connected to the active bus + This also includes shunt Capacitors/Reactors. + Original COM help: https://opendss.epri.com/AllPCEatBus.html ''' - result = self._check_for_error(self._get_string_array(self._lib.Bus_Get_AllPCEatBus)) + result = self._lib.Bus_Get_AllPCEatBus() if result: result.append('') #TODO: remove this -- added for full compatibility with COM else: @@ -437,9 +430,11 @@ def AllPDEatBus(self) -> List[str]: ''' Returns an array with the names of all PDE connected to the active bus + This excludes shunt Capacitors/Reactors. + Original COM help: https://opendss.epri.com/AllPDEatBus1.html ''' - result = self._check_for_error(self._get_string_array(self._lib.Bus_Get_AllPDEatBus)) + result = self._lib.Bus_Get_AllPDEatBus() if result: result.append('') #TODO: remove this -- added for full compatibility with COM else: @@ -450,12 +445,9 @@ def AllPDEatBus(self) -> List[str]: def __getitem__(self, index: Union[int, str]) -> IBus: if isinstance(index, int): # bus index is zero based, pass it directly - self._check_for_error(self._lib.Circuit_SetActiveBusi(index)) + self._lib.Circuit_SetActiveBusi(index) else: - if not isinstance(index, bytes): - index = index.encode(self._api_util.codec) - - self._check_for_error(self._lib.Circuit_SetActiveBus(index)) + self._lib.Circuit_SetActiveBus(index) return self @@ -463,18 +455,35 @@ def __call__(self, index: Union[int, str]) -> IBus: return self.__getitem__(index) def __iter__(self) -> Iterator[IBus]: - if self._api_util._is_odd: + if self._api_util._is_oddie: for i in range(self._lib.Circuit_Get_NumBuses()): - self._check_for_error(self._lib.Circuit_SetActiveBusi(i)) + self._lib.Circuit_SetActiveBusi(i) yield self return - n = self._check_for_error(self._lib.Circuit_SetActiveBusi(0)) + n = self._lib.Circuit_SetActiveBusi(0) while n == 0: yield self - n = self._check_for_error(self._lib.Bus_Get_Next()) + n = self._lib.Bus_Get_Next() def __len__(self) -> int: '''Total number of Buses in the circuit.''' - return self._check_for_error(self._lib.Circuit_Get_NumBuses()) + return self._lib.Circuit_Get_NumBuses() + + def to_altdss(self) -> Optional[AltBus]: + ''' + Returns a Python object for the current active bus in the circuit (if any). + + Requires AltDSS-Python. + + *Available only for the AltDSS engine.* + + **(API Extension)** + + ''' + idx = self.lib.Bus_Get_idx() + if idx < 0: + return None + + return self._api_util.get_bus_obj(self.lib.Alt_Bus_GetByIndex(idx)) diff --git a/dss/ICNData.py b/dss/ICNData.py index 61f35511..e435e228 100644 --- a/dss/ICNData.py +++ b/dss/ICNData.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import Union from .enums import LineUnits @@ -39,147 +39,147 @@ class ICNData(Iterable): @property def EmergAmps(self) -> float: '''Emergency ampere rating''' - return self._check_for_error(self._lib.CNData_Get_EmergAmps()) + return self._lib.CNData_Get_EmergAmps() @EmergAmps.setter def EmergAmps(self, Value: float): - self._check_for_error(self._lib.CNData_Set_EmergAmps(Value)) + self._lib.CNData_Set_EmergAmps(Value) @property def NormAmps(self) -> float: '''Normal Ampere rating''' - return self._check_for_error(self._lib.CNData_Get_NormAmps()) + return self._lib.CNData_Get_NormAmps() @NormAmps.setter def NormAmps(self, Value: float): - self._check_for_error(self._lib.CNData_Set_NormAmps(Value)) + self._lib.CNData_Set_NormAmps(Value) @property def Rdc(self) -> float: - return self._check_for_error(self._lib.CNData_Get_Rdc()) + return self._lib.CNData_Get_Rdc() @Rdc.setter def Rdc(self, Value: float): - self._check_for_error(self._lib.CNData_Set_Rdc(Value)) + self._lib.CNData_Set_Rdc(Value) @property def Rac(self) -> float: - return self._check_for_error(self._lib.CNData_Get_Rac()) + return self._lib.CNData_Get_Rac() @Rac.setter def Rac(self, Value: float): - self._check_for_error(self._lib.CNData_Set_Rac(Value)) + self._lib.CNData_Set_Rac(Value) @property def GMRac(self) -> float: - return self._check_for_error(self._lib.CNData_Get_GMRac()) + return self._lib.CNData_Get_GMRac() @GMRac.setter def GMRac(self, Value: float): - self._check_for_error(self._lib.CNData_Set_GMRac(Value)) + self._lib.CNData_Set_GMRac(Value) @property def GMRUnits(self) -> LineUnits: - return LineUnits(self._check_for_error(self._lib.CNData_Get_GMRUnits())) + return LineUnits(self._lib.CNData_Get_GMRUnits()) @GMRUnits.setter def GMRUnits(self, Value: int): - self._check_for_error(self._lib.CNData_Set_GMRUnits(Value)) + self._lib.CNData_Set_GMRUnits(Value) @property def Radius(self) -> float: - return self._check_for_error(self._lib.CNData_Get_Radius()) + return self._lib.CNData_Get_Radius() @Radius.setter def Radius(self, Value: float): - self._check_for_error(self._lib.CNData_Set_Radius(Value)) + self._lib.CNData_Set_Radius(Value) @property def RadiusUnits(self) -> LineUnits: - return LineUnits(self._check_for_error(self._lib.CNData_Get_RadiusUnits())) + return LineUnits(self._lib.CNData_Get_RadiusUnits()) @RadiusUnits.setter def RadiusUnits(self, Value: Union[int, LineUnits]): - self._check_for_error(self._lib.CNData_Set_RadiusUnits(Value)) + self._lib.CNData_Set_RadiusUnits(Value) @property def ResistanceUnits(self) -> LineUnits: - return LineUnits(self._check_for_error(self._lib.CNData_Get_ResistanceUnits())) + return LineUnits(self._lib.CNData_Get_ResistanceUnits()) @ResistanceUnits.setter def ResistanceUnits(self, Value: Union[int, LineUnits]): - self._check_for_error(self._lib.CNData_Set_ResistanceUnits(Value)) + self._lib.CNData_Set_ResistanceUnits(Value) @property def Diameter(self) -> float: - return self._check_for_error(self._lib.CNData_Get_Diameter()) + return self._lib.CNData_Get_Diameter() @Diameter.setter def Diameter(self, Value: float): - self._check_for_error(self._lib.CNData_Set_Diameter(Value)) + self._lib.CNData_Set_Diameter(Value) @property def EpsR(self) -> float: - return self._check_for_error(self._lib.CNData_Get_EpsR()) + return self._lib.CNData_Get_EpsR() @EpsR.setter def EpsR(self, Value: float): - self._check_for_error(self._lib.CNData_Set_EpsR(Value)) + self._lib.CNData_Set_EpsR(Value) @property def InsLayer(self) -> float: - return self._check_for_error(self._lib.CNData_Get_InsLayer()) + return self._lib.CNData_Get_InsLayer() @InsLayer.setter def InsLayer(self, Value: float): - self._check_for_error(self._lib.CNData_Set_InsLayer(Value)) + self._lib.CNData_Set_InsLayer(Value) @property def DiaIns(self) -> float: - return self._check_for_error(self._lib.CNData_Get_DiaIns()) + return self._lib.CNData_Get_DiaIns() @DiaIns.setter def DiaIns(self, Value: float): - self._check_for_error(self._lib.CNData_Set_DiaIns(Value)) + self._lib.CNData_Set_DiaIns(Value) @property def DiaCable(self) -> float: - return self._check_for_error(self._lib.CNData_Get_DiaCable()) + return self._lib.CNData_Get_DiaCable() @DiaCable.setter def DiaCable(self, Value: float): - self._check_for_error(self._lib.CNData_Set_DiaCable(Value)) + self._lib.CNData_Set_DiaCable(Value) @property def k(self) -> int: - return self._check_for_error(self._lib.CNData_Get_k()) + return self._lib.CNData_Get_k() @k.setter def k(self, Value: int): - self._check_for_error(self._lib.CNData_Set_k(Value)) + self._lib.CNData_Set_k(Value) @property def DiaStrand(self) -> float: - return self._check_for_error(self._lib.CNData_Get_DiaStrand()) + return self._lib.CNData_Get_DiaStrand() @DiaStrand.setter def DiaStrand(self, Value: float): - self._check_for_error(self._lib.CNData_Set_DiaStrand(Value)) + self._lib.CNData_Set_DiaStrand(Value) @property def GmrStrand(self) -> float: - return self._check_for_error(self._lib.CNData_Get_GmrStrand()) + return self._lib.CNData_Get_GmrStrand() @GmrStrand.setter def GmrStrand(self, Value: float): - self._check_for_error(self._lib.CNData_Set_GmrStrand(Value)) + self._lib.CNData_Set_GmrStrand(Value) @property def RStrand(self) -> float: - return self._check_for_error(self._lib.CNData_Get_RStrand()) + return self._lib.CNData_Get_RStrand() @RStrand.setter def RStrand(self, Value: float): - self._check_for_error(self._lib.CNData_Set_RStrand(Value)) + self._lib.CNData_Set_RStrand(Value) diff --git a/dss/ICapControls.py b/dss/ICapControls.py index 2d8f4f47..68db4c48 100644 --- a/dss/ICapControls.py +++ b/dss/ICapControls.py @@ -1,12 +1,13 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import AnyStr, Union from .enums import CapControlModes class ICapControls(Iterable): __slots__ = [] + _is_circuit_element = True _columns = [ 'Name', @@ -33,7 +34,7 @@ def Reset(self): Original COM help: https://opendss.epri.com/Reset.html ''' - self._check_for_error(self._lib.CapControls_Reset()) + self._lib.CapControls_Reset() @property def CTratio(self) -> float: @@ -42,11 +43,11 @@ def CTratio(self) -> float: Original COM help: https://opendss.epri.com/CTratio.html ''' - return self._check_for_error(self._lib.CapControls_Get_CTratio()) + return self._lib.CapControls_Get_CTratio() @CTratio.setter def CTratio(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_CTratio(Value)) + self._lib.CapControls_Set_CTratio(Value) @property def Capacitor(self) -> str: @@ -55,14 +56,11 @@ def Capacitor(self) -> str: Original COM help: https://opendss.epri.com/Capacitor.html ''' - return self._get_string(self._check_for_error(self._lib.CapControls_Get_Capacitor())) + return self._lib.CapControls_Get_Capacitor() @Capacitor.setter def Capacitor(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.CapControls_Set_Capacitor(Value)) + self._lib.CapControls_Set_Capacitor(Value) @property def DeadTime(self) -> float: @@ -73,11 +71,11 @@ def DeadTime(self) -> float: Original COM help: https://opendss.epri.com/DeadTime.html ''' - return self._check_for_error(self._lib.CapControls_Get_DeadTime()) + return self._lib.CapControls_Get_DeadTime() @DeadTime.setter def DeadTime(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_DeadTime(Value)) + self._lib.CapControls_Set_DeadTime(Value) @property def Delay(self) -> float: @@ -86,11 +84,11 @@ def Delay(self) -> float: Original COM help: https://opendss.epri.com/Delay.html ''' - return self._check_for_error(self._lib.CapControls_Get_Delay()) + return self._lib.CapControls_Get_Delay() @Delay.setter def Delay(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_Delay(Value)) + self._lib.CapControls_Set_Delay(Value) @property def DelayOff(self) -> float: @@ -99,11 +97,11 @@ def DelayOff(self) -> float: Original COM help: https://opendss.epri.com/DelayOff.html ''' - return self._check_for_error(self._lib.CapControls_Get_DelayOff()) + return self._lib.CapControls_Get_DelayOff() @DelayOff.setter def DelayOff(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_DelayOff(Value)) + self._lib.CapControls_Set_DelayOff(Value) @property def Mode(self) -> CapControlModes: @@ -112,11 +110,11 @@ def Mode(self) -> CapControlModes: Original COM help: https://opendss.epri.com/Mode.html ''' - return CapControlModes(self._check_for_error(self._lib.CapControls_Get_Mode())) + return CapControlModes(self._lib.CapControls_Get_Mode()) @Mode.setter def Mode(self, Value: Union[CapControlModes, int]): - self._check_for_error(self._lib.CapControls_Set_Mode(Value)) + self._lib.CapControls_Set_Mode(Value) @property def MonitoredObj(self) -> int: @@ -125,14 +123,11 @@ def MonitoredObj(self) -> int: Original COM help: https://opendss.epri.com/MonitoredObj.html ''' - return self._get_string(self._check_for_error(self._lib.CapControls_Get_MonitoredObj())) + return self._lib.CapControls_Get_MonitoredObj() @MonitoredObj.setter def MonitoredObj(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.CapControls_Set_MonitoredObj(Value)) + self._lib.CapControls_Set_MonitoredObj(Value) @property def MonitoredTerm(self) -> int: @@ -141,11 +136,11 @@ def MonitoredTerm(self) -> int: Original COM help: https://opendss.epri.com/MonitoredTerm.html ''' - return self._check_for_error(self._lib.CapControls_Get_MonitoredTerm()) + return self._lib.CapControls_Get_MonitoredTerm() @MonitoredTerm.setter def MonitoredTerm(self, Value: int): - self._check_for_error(self._lib.CapControls_Set_MonitoredTerm(Value)) + self._lib.CapControls_Set_MonitoredTerm(Value) @property def OFFSetting(self) -> float: @@ -154,11 +149,11 @@ def OFFSetting(self) -> float: Original COM help: https://opendss.epri.com/OFFSetting.html ''' - return self._check_for_error(self._lib.CapControls_Get_OFFSetting()) + return self._lib.CapControls_Get_OFFSetting() @OFFSetting.setter def OFFSetting(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_OFFSetting(Value)) + self._lib.CapControls_Set_OFFSetting(Value) @property def ONSetting(self) -> float: @@ -167,11 +162,11 @@ def ONSetting(self) -> float: Original COM help: https://opendss.epri.com/ONSetting.html ''' - return self._check_for_error(self._lib.CapControls_Get_ONSetting()) + return self._lib.CapControls_Get_ONSetting() @ONSetting.setter def ONSetting(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_ONSetting(Value)) + self._lib.CapControls_Set_ONSetting(Value) @property def PTratio(self) -> float: @@ -180,11 +175,11 @@ def PTratio(self) -> float: Original COM help: https://opendss.epri.com/PTratio.html ''' - return self._check_for_error(self._lib.CapControls_Get_PTratio()) + return self._lib.CapControls_Get_PTratio() @PTratio.setter def PTratio(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_PTratio(Value)) + self._lib.CapControls_Set_PTratio(Value) @property def UseVoltOverride(self) -> float: @@ -193,11 +188,11 @@ def UseVoltOverride(self) -> float: Original COM help: https://opendss.epri.com/UseVoltOverride.html ''' - return self._check_for_error(self._lib.CapControls_Get_UseVoltOverride()) != 0 + return self._lib.CapControls_Get_UseVoltOverride() @UseVoltOverride.setter def UseVoltOverride(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_UseVoltOverride(Value)) + self._lib.CapControls_Set_UseVoltOverride(Value) @property def Vmax(self) -> float: @@ -206,11 +201,11 @@ def Vmax(self) -> float: Original COM help: https://opendss.epri.com/Vmax.html ''' - return self._check_for_error(self._lib.CapControls_Get_Vmax()) + return self._lib.CapControls_Get_Vmax() @Vmax.setter def Vmax(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_Vmax(Value)) + self._lib.CapControls_Set_Vmax(Value) @property def Vmin(self) -> float: @@ -219,8 +214,8 @@ def Vmin(self) -> float: Original COM help: https://opendss.epri.com/Vmin.html ''' - return self._check_for_error(self._lib.CapControls_Get_Vmin()) + return self._lib.CapControls_Get_Vmin() @Vmin.setter def Vmin(self, Value: float): - self._check_for_error(self._lib.CapControls_Set_Vmin(Value)) + self._lib.CapControls_Set_Vmin(Value) diff --git a/dss/ICapacitors.py b/dss/ICapacitors.py index 0a5201de..c47dff34 100644 --- a/dss/ICapacitors.py +++ b/dss/ICapacitors.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Int32Array @@ -20,16 +20,16 @@ class ICapacitors(Iterable): ] def AddStep(self) -> bool: - return self._check_for_error(self._lib.Capacitors_AddStep()) != 0 + return self._lib.Capacitors_AddStep() def Close(self): - self._check_for_error(self._lib.Capacitors_Close()) + self._lib.Capacitors_Close() def Open(self): - self._check_for_error(self._lib.Capacitors_Open()) + self._lib.Capacitors_Open() def SubtractStep(self) -> bool: - return self._check_for_error(self._lib.Capacitors_SubtractStep()) != 0 + return self._lib.Capacitors_SubtractStep() @property def AvailableSteps(self) -> int: @@ -38,7 +38,7 @@ def AvailableSteps(self) -> int: Original COM help: https://opendss.epri.com/AvailableSteps.html ''' - return self._check_for_error(self._lib.Capacitors_Get_AvailableSteps()) + return self._lib.Capacitors_Get_AvailableSteps() @property def IsDelta(self) -> bool: @@ -47,11 +47,11 @@ def IsDelta(self) -> bool: Original COM help: https://opendss.epri.com/IsDelta.html ''' - return self._check_for_error(self._lib.Capacitors_Get_IsDelta()) != 0 + return self._lib.Capacitors_Get_IsDelta() @IsDelta.setter def IsDelta(self, Value: bool): - self._check_for_error(self._lib.Capacitors_Set_IsDelta(Value)) + self._lib.Capacitors_Set_IsDelta(Value) @property def NumSteps(self) -> int: @@ -60,11 +60,11 @@ def NumSteps(self) -> int: Original COM help: https://opendss.epri.com/NumSteps.html ''' - return self._check_for_error(self._lib.Capacitors_Get_NumSteps()) + return self._lib.Capacitors_Get_NumSteps() @NumSteps.setter def NumSteps(self, Value: int): - self._check_for_error(self._lib.Capacitors_Set_NumSteps(Value)) + self._lib.Capacitors_Set_NumSteps(Value) @property def States(self) -> Int32Array: @@ -73,13 +73,12 @@ def States(self) -> Int32Array: Original COM help: https://opendss.epri.com/States.html ''' - self._check_for_error(self._lib.Capacitors_Get_States_GR()) - return self._get_int32_gr_array() + return self._lib.Capacitors_Get_States_GR() @States.setter def States(self, Value: Int32Array): Value, ValuePtr, ValueCount = self._prepare_int32_array(Value) - self._check_for_error(self._lib.Capacitors_Set_States(ValuePtr, ValueCount)) + self._lib.Capacitors_Set_States(ValuePtr, ValueCount) @property def kV(self) -> float: @@ -88,17 +87,17 @@ def kV(self) -> float: Original COM help: https://opendss.epri.com/kV.html ''' - return self._check_for_error(self._lib.Capacitors_Get_kV()) + return self._lib.Capacitors_Get_kV() @kV.setter def kV(self, Value): - self._check_for_error(self._lib.Capacitors_Set_kV(Value)) + self._lib.Capacitors_Set_kV(Value) @property def kvar(self) -> float: '''Total bank KVAR, distributed equally among phases and steps.''' - return self._check_for_error(self._lib.Capacitors_Get_kvar()) + return self._lib.Capacitors_Get_kvar() @kvar.setter def kvar(self, Value: float): - self._check_for_error(self._lib.Capacitors_Set_kvar(Value)) + self._lib.Capacitors_Set_kvar(Value) diff --git a/dss/ICircuit.py b/dss/ICircuit.py index 0f2e1811..1c47e0a1 100644 --- a/dss/ICircuit.py +++ b/dss/ICircuit.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from typing import List, AnyStr, Union import json from ._cffi_api_util import Base @@ -31,7 +31,6 @@ from .ILoadShapes import ILoadShapes from .IFuses import IFuses from .IISources import IISources -from .IDSSimComs import IDSSimComs from .IPVSystems import IPVSystems from .IVsources import IVsources from .ILineCodes import ILineCodes @@ -45,8 +44,9 @@ from .IReduceCkt import IReduceCkt from .IStorages import IStorages from .IGICSources import IGICSources +from .IWindGens import IWindGens -from ._types import Float64Array, Int32Array, Float64ArrayOrComplexArray, Float64ArrayOrSimpleComplex +from ._types import Float64Array, Int32Array, ComplexArray, ComplexMatrix, Complex from .enums import DSSJSONFlags, DSSSaveFlags class ICircuit(Base): @@ -81,7 +81,6 @@ class ICircuit(Base): 'Fuses', 'Isources', 'ISources', - 'DSSim_Coms', 'PVSystems', 'Vsources', 'LineCodes', @@ -95,6 +94,7 @@ class ICircuit(Base): 'ReduceCkt', 'Storages', 'GICSources', + 'WindGens', ] _columns = [ @@ -153,7 +153,6 @@ class ICircuit(Base): Fuses: IFuses Isources: IISources ISources: IISources - DSSim_Coms: IDSSimComs PVSystems: IPVSystems Vsources: IVsources LineCodes: ILineCodes @@ -165,6 +164,7 @@ class ICircuit(Base): Reactors: IReactors ReduceCkt: IReduceCkt Storages: IStorages + WindGens: IWindGens GICSources: IGICSources Parallel: IParallel @@ -202,19 +202,19 @@ def __init__(self, api_util): object.__setattr__(self, 'Isources', Isources) object.__setattr__(self, 'ISources', Isources) - self.DSSim_Coms = IDSSimComs(api_util) self.PVSystems = IPVSystems(api_util) self.Vsources = IVsources(api_util) self.LineCodes = ILineCodes(api_util) - self.LineGeometries = ILineGeometries(api_util) if not api_util._is_odd else None - self.LineSpacings = ILineSpacings(api_util) if not api_util._is_odd else None - self.WireData = IWireData(api_util) if not api_util._is_odd else None - self.CNData = ICNData(api_util) if not api_util._is_odd else None - self.TSData = ITSData(api_util) if not api_util._is_odd else None - self.Reactors = IReactors(api_util) if not api_util._is_odd else None + self.LineGeometries = ILineGeometries(api_util) if not api_util._is_oddie else None + self.LineSpacings = ILineSpacings(api_util) if not api_util._is_oddie else None + self.WireData = IWireData(api_util) if not api_util._is_oddie else None + self.CNData = ICNData(api_util) if not api_util._is_oddie else None + self.TSData = ITSData(api_util) if not api_util._is_oddie else None + self.Reactors = IReactors(api_util) self.ReduceCkt = IReduceCkt(api_util) #: Circuit Reduction Interface - self.Storages = IStorages(api_util) if not api_util._is_odd else None + self.Storages = IStorages(api_util) self.GICSources = IGICSources(api_util) + self.WindGens = IWindGens(api_util) if hasattr(api_util.lib, 'Parallel_CreateActor'): self.Parallel = IParallel(api_util) @@ -239,7 +239,7 @@ def Capacity(self, Start: float, Increment: float) -> float: Original COM help: https://opendss.epri.com/Capacity1.html ''' - return self._check_for_error(self._lib.Circuit_Capacity(Start, Increment)) + return self._lib.Circuit_Capacity(Start, Increment) def Disable(self, Name: AnyStr): ''' @@ -247,10 +247,7 @@ def Disable(self, Name: AnyStr): Original COM help: https://opendss.epri.com/Disable.html ''' - if not isinstance(Name, bytes): - Name = Name.encode(self._api_util.codec) - - self._check_for_error(self._lib.Circuit_Disable(Name)) + self._lib.Circuit_Disable(Name) def Enable(self, Name: AnyStr): ''' @@ -258,10 +255,7 @@ def Enable(self, Name: AnyStr): Original COM help: https://opendss.epri.com/Enable.html ''' - if not isinstance(Name, bytes): - Name = Name.encode(self._api_util.codec) - - self._check_for_error(self._lib.Circuit_Enable(Name)) + self._lib.Circuit_Enable(Name) def EndOfTimeStepUpdate(self): ''' @@ -269,7 +263,7 @@ def EndOfTimeStepUpdate(self): Original COM help: https://opendss.epri.com/EndOfTimeStepUpdate.html ''' - self._check_for_error(self._lib.Circuit_EndOfTimeStepUpdate()) + self._lib.Circuit_EndOfTimeStepUpdate() def FirstElement(self) -> int: ''' @@ -279,7 +273,7 @@ def FirstElement(self) -> int: Original COM help: https://opendss.epri.com/FirstElement.html ''' - return self._check_for_error(self._lib.Circuit_FirstElement()) + return self._lib.Circuit_FirstElement() def FirstPCElement(self) -> int: ''' @@ -289,7 +283,7 @@ def FirstPCElement(self) -> int: Original COM help: https://opendss.epri.com/FirstPCElement.html ''' - return self._check_for_error(self._lib.Circuit_FirstPCElement()) + return self._lib.Circuit_FirstPCElement() def FirstPDElement(self) -> int: ''' @@ -299,26 +293,23 @@ def FirstPDElement(self) -> int: Original COM help: https://opendss.epri.com/FirstPDElement.html ''' - return self._check_for_error(self._lib.Circuit_FirstPDElement()) + return self._lib.Circuit_FirstPDElement() def AllNodeDistancesByPhase(self, Phase: int) -> Float64Array: '''Returns an array of doubles representing the distances to parent EnergyMeter. Sequence of array corresponds to other node ByPhase properties.''' - self._check_for_error(self._lib.Circuit_Get_AllNodeDistancesByPhase_GR(Phase)) - return self._get_float64_gr_array() + return self._lib.Circuit_Get_AllNodeDistancesByPhase_GR(Phase) def AllNodeNamesByPhase(self, Phase: int) -> List[str]: '''Return array of strings of the node names for the By Phase criteria. Sequence corresponds to other ByPhase properties.''' - return self._check_for_error(self._get_string_array(self._lib.Circuit_Get_AllNodeNamesByPhase, Phase)) + return self._lib.Circuit_Get_AllNodeNamesByPhase(Phase) def AllNodeVmagByPhase(self, Phase: int) -> Float64Array: '''Returns Array of doubles represent voltage magnitudes for nodes on the specified phase.''' - self._check_for_error(self._lib.Circuit_Get_AllNodeVmagByPhase_GR(Phase)) - return self._get_float64_gr_array() + return self._lib.Circuit_Get_AllNodeVmagByPhase_GR(Phase) def AllNodeVmagPUByPhase(self, Phase: int) -> Float64Array: '''Returns array of per unit voltage magnitudes for each node by phase''' - self._check_for_error(self._lib.Circuit_Get_AllNodeVmagPUByPhase_GR(Phase)) - return self._get_float64_gr_array() + return self._lib.Circuit_Get_AllNodeVmagPUByPhase_GR(Phase) def NextElement(self) -> int: ''' @@ -327,7 +318,7 @@ def NextElement(self) -> int: Original COM help: https://opendss.epri.com/NextElement.html ''' - return self._check_for_error(self._lib.Circuit_NextElement()) + return self._lib.Circuit_NextElement() def NextPCElement(self) -> int: ''' @@ -335,7 +326,7 @@ def NextPCElement(self) -> int: Original COM help: https://opendss.epri.com/NextPCElement.html ''' - return self._check_for_error(self._lib.Circuit_NextPCElement()) + return self._lib.Circuit_NextPCElement() def NextPDElement(self) -> int: ''' @@ -343,7 +334,7 @@ def NextPDElement(self) -> int: Original COM help: https://opendss.epri.com/NextPDElement.html ''' - return self._check_for_error(self._lib.Circuit_NextPDElement()) + return self._lib.Circuit_NextPDElement() def Sample(self): ''' @@ -351,7 +342,7 @@ def Sample(self): Original COM help: https://opendss.epri.com/Sample.html ''' - self._check_for_error(self._lib.Circuit_Sample()) + self._lib.Circuit_Sample() def SaveSample(self): ''' @@ -359,7 +350,7 @@ def SaveSample(self): Original COM help: https://opendss.epri.com/SaveSample.html ''' - self._check_for_error(self._lib.Circuit_SaveSample()) + self._lib.Circuit_SaveSample() def SetActiveBus(self, BusName: AnyStr) -> int: ''' @@ -369,10 +360,7 @@ def SetActiveBus(self, BusName: AnyStr) -> int: Original COM help: https://opendss.epri.com/SetActiveBus.html ''' - if not isinstance(BusName, bytes): - BusName = BusName.encode(self._api_util.codec) - - return self._check_for_error(self._lib.Circuit_SetActiveBus(BusName)) + return self._lib.Circuit_SetActiveBus(BusName) def SetActiveBusi(self, BusIndex: int) -> int: ''' @@ -383,7 +371,7 @@ def SetActiveBusi(self, BusIndex: int) -> int: Original COM help: https://opendss.epri.com/SetActiveBusi.html ''' - return self._check_for_error(self._lib.Circuit_SetActiveBusi(BusIndex)) + return self._lib.Circuit_SetActiveBusi(BusIndex) def SetActiveClass(self, ClassName: AnyStr) -> int: ''' @@ -393,10 +381,7 @@ def SetActiveClass(self, ClassName: AnyStr) -> int: Original COM help: https://opendss.epri.com/SetActiveClass.html ''' - if not isinstance(ClassName, bytes): - ClassName = ClassName.encode(self._api_util.codec) - - return self._check_for_error(self._lib.Circuit_SetActiveClass(ClassName)) + return self._lib.Circuit_SetActiveClass(ClassName) def SetActiveElement(self, FullName: AnyStr) -> int: ''' @@ -406,10 +391,7 @@ def SetActiveElement(self, FullName: AnyStr) -> int: Original COM help: https://opendss.epri.com/SetActiveElement.html ''' - if not isinstance(FullName, bytes): - FullName = FullName.encode(self._api_util.codec) - - return self._check_for_error(self._lib.Circuit_SetActiveElement(FullName)) + return self._lib.Circuit_SetActiveElement(FullName) def UpdateStorage(self): ''' @@ -419,7 +401,7 @@ def UpdateStorage(self): Original COM help: https://opendss.epri.com/UpdateStorage.html ''' - self._check_for_error(self._lib.Circuit_UpdateStorage()) + self._lib.Circuit_UpdateStorage() @property def AllBusDistances(self) -> Float64Array: @@ -428,8 +410,7 @@ def AllBusDistances(self) -> Float64Array: Original COM help: https://opendss.epri.com/AllBusDistances.html ''' - self._check_for_error(self._lib.Circuit_Get_AllBusDistances_GR()) - return self._get_float64_gr_array() + return self._lib.Circuit_Get_AllBusDistances_GR() @property def AllBusNames(self) -> List[str]: @@ -438,7 +419,7 @@ def AllBusNames(self) -> List[str]: Original COM help: https://opendss.epri.com/AllBusNames.html ''' - return self._check_for_error(self._get_string_array(self._lib.Circuit_Get_AllBusNames)) + return self._lib.Circuit_Get_AllBusNames() @property def AllBusVmag(self) -> Float64Array: @@ -447,38 +428,34 @@ def AllBusVmag(self) -> Float64Array: Original COM help: https://opendss.epri.com/AllBusVmag.html ''' - self._check_for_error(self._lib.Circuit_Get_AllBusVmag_GR()) - return self._get_float64_gr_array() + return self._lib.Circuit_Get_AllBusVmag_GR() @property def AllBusVmagPu(self) -> Float64Array: ''' - Double Array of all bus voltages (each node) magnitudes in Per unit + Array of all bus voltages (each node) magnitudes in Per unit Original COM help: https://opendss.epri.com/AllBusVmagPu.html ''' - self._check_for_error(self._lib.Circuit_Get_AllBusVmagPu_GR()) - return self._get_float64_gr_array() + return self._lib.Circuit_Get_AllBusVmagPu_GR() @property - def AllBusVolts(self) -> Float64ArrayOrComplexArray: + def AllBusVolts(self) -> ComplexArray: ''' Complex array of all bus, node voltages from most recent solution Original COM help: https://opendss.epri.com/AllBusVolts.html ''' - self._check_for_error(self._lib.Circuit_Get_AllBusVolts_GR()) - return self._get_complex128_gr_array() + return self._lib.Circuit_Get_AllBusVolts_GR() @property - def AllElementLosses(self) -> Float64ArrayOrComplexArray: + def AllElementLosses(self) -> ComplexArray: ''' Array of total losses (complex) in each circuit element Original COM help: https://opendss.epri.com/AllElementLosses.html ''' - self._check_for_error(self._lib.Circuit_Get_AllElementLosses_GR()) - return self._get_complex128_gr_array() + return self._lib.Circuit_Get_AllElementLosses_GR() @property def AllElementNames(self) -> List[str]: @@ -487,7 +464,7 @@ def AllElementNames(self) -> List[str]: Original COM help: https://opendss.epri.com/AllElementNames.html ''' - return self._check_for_error(self._get_string_array(self._lib.Circuit_Get_AllElementNames)) + return self._lib.Circuit_Get_AllElementNames() @property def AllNodeDistances(self) -> Float64Array: @@ -496,8 +473,7 @@ def AllNodeDistances(self) -> Float64Array: Original COM help: https://opendss.epri.com/AllNodeDistances.html ''' - self._check_for_error(self._lib.Circuit_Get_AllNodeDistances_GR()) - return self._get_float64_gr_array() + return self._lib.Circuit_Get_AllNodeDistances_GR() @property def AllNodeNames(self) -> List[str]: @@ -506,32 +482,30 @@ def AllNodeNames(self) -> List[str]: Original COM help: https://opendss.epri.com/AllNodeNames.html ''' - return self._check_for_error(self._get_string_array(self._lib.Circuit_Get_AllNodeNames)) + return self._lib.Circuit_Get_AllNodeNames() @property - def LineLosses(self) -> Float64ArrayOrSimpleComplex: + def LineLosses(self) -> Complex: ''' Complex total line losses in the circuit Original COM help: https://opendss.epri.com/LineLosses.html ''' - self._check_for_error(self._lib.Circuit_Get_LineLosses_GR()) - return self._get_complex128_gr_simple() + return self._lib.Circuit_Get_LineLosses_GR() @property - def Losses(self) -> Float64ArrayOrSimpleComplex: + def Losses(self) -> Complex: ''' Total losses in active circuit, complex number (two-element array of double). Original COM help: https://opendss.epri.com/Losses.html ''' - self._check_for_error(self._lib.Circuit_Get_Losses_GR()) - return self._get_complex128_gr_simple() + return self._lib.Circuit_Get_Losses_GR() @property def Name(self) -> str: '''Name of the active circuit.''' - return self._get_string(self._check_for_error(self._lib.Circuit_Get_Name())) + return self._lib.Circuit_Get_Name() @property def NumBuses(self) -> int: @@ -540,7 +514,7 @@ def NumBuses(self) -> int: Original COM help: https://opendss.epri.com/NumBuses.html ''' - return self._check_for_error(self._lib.Circuit_Get_NumBuses()) + return self._lib.Circuit_Get_NumBuses() @property def NumCktElements(self) -> int: @@ -549,7 +523,7 @@ def NumCktElements(self) -> int: Original COM help: https://opendss.epri.com/NumCktElements.html ''' - return self._check_for_error(self._lib.Circuit_Get_NumCktElements()) + return self._lib.Circuit_Get_NumCktElements() @property def NumNodes(self) -> int: @@ -558,7 +532,7 @@ def NumNodes(self) -> int: Original COM help: https://opendss.epri.com/NumNodes1.html ''' - return self._check_for_error(self._lib.Circuit_Get_NumNodes()) + return self._lib.Circuit_Get_NumNodes() @property def ParentPDElement(self) -> int: @@ -567,20 +541,19 @@ def ParentPDElement(self) -> int: Original COM help: https://opendss.epri.com/ParentPDElement.html ''' - return self._check_for_error(self._lib.Circuit_Get_ParentPDElement()) + return self._lib.Circuit_Get_ParentPDElement() @property - def SubstationLosses(self) -> Float64ArrayOrSimpleComplex: + def SubstationLosses(self) -> Complex: ''' Complex losses in all transformers designated to substations. Original COM help: https://opendss.epri.com/SubstationLosses.html ''' - self._check_for_error(self._lib.Circuit_Get_SubstationLosses_GR()) - return self._get_complex128_gr_simple() + return self._lib.Circuit_Get_SubstationLosses_GR() @property - def SystemY(self) -> Float64ArrayOrComplexArray: + def SystemY(self) -> ComplexMatrix: ''' (read-only) System Y matrix (after a solution has been performed). This is deprecated as it returns a dense matrix. Only use it for small systems. @@ -588,28 +561,25 @@ def SystemY(self) -> Float64ArrayOrComplexArray: Original COM help: https://opendss.epri.com/SystemY.html ''' - self._check_for_error(self._lib.Circuit_Get_SystemY_GR()) - return self._get_complex128_gr_array() + return self._lib.Circuit_Get_SystemY_GR() @property - def TotalPower(self) -> Float64ArrayOrSimpleComplex: + def TotalPower(self) -> Complex: ''' Total power (complex), kVA delivered to the circuit Original COM help: https://opendss.epri.com/TotalPower.html ''' - self._check_for_error(self._lib.Circuit_Get_TotalPower_GR()) - return self._get_complex128_gr_simple() + return self._lib.Circuit_Get_TotalPower_GR() @property - def YCurrents(self) -> Float64ArrayOrComplexArray: + def YCurrents(self) -> ComplexArray: ''' Array of doubles containing complex injection currents for the present solution. It is the "I" vector of I=YV Original COM help: https://opendss.epri.com/YCurrents.html ''' - self._check_for_error(self._lib.Circuit_Get_YCurrents_GR()) - return self._get_complex128_gr_array() + return self._lib.Circuit_Get_YCurrents_GR() @property def YNodeOrder(self) -> List[str]: @@ -618,19 +588,18 @@ def YNodeOrder(self) -> List[str]: Original COM help: https://opendss.epri.com/YNodeOrder.html ''' - return self._check_for_error(self._get_string_array(self._lib.Circuit_Get_YNodeOrder)) + return self._lib.Circuit_Get_YNodeOrder() @property - def YNodeVarray(self) -> Float64ArrayOrComplexArray: + def YNodeVarray(self) -> ComplexArray: ''' Complex array of actual node voltages in same order as SystemY matrix. Original COM help: https://opendss.epri.com/YNodeVarray.html ''' - self._check_for_error(self._lib.Circuit_Get_YNodeVarray_GR()) - return self._get_complex128_gr_array() + return self._lib.Circuit_Get_YNodeVarray_GR() - def ElementLosses(self, Value: Int32Array) -> Float64ArrayOrComplexArray: + def ElementLosses(self, Value: Int32Array) -> ComplexArray: ''' Array of total losses (complex) in a selection of elements. Use the element indices (starting at 1) as parameter. @@ -638,8 +607,7 @@ def ElementLosses(self, Value: Int32Array) -> Float64ArrayOrComplexArray: **(API Extension)** ''' Value, ValuePtr, ValueCount = self._prepare_int32_array(Value) - self._check_for_error(self._lib.Circuit_Get_ElementLosses_GR(ValuePtr, ValueCount)) - return self._get_complex128_gr_array() + return self._lib.Circuit_Get_ElementLosses_GR(ValuePtr, ValueCount) def ToJSON(self, options: DSSJSONFlags = 0) -> str: ''' @@ -653,7 +621,7 @@ def ToJSON(self, options: DSSJSONFlags = 0) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.Circuit_ToJSON(options))) + return self._lib.Circuit_ToJSON(options) def FromJSON(self, data: Union[AnyStr, dict], options: DSSJSONFlags = 0): ''' @@ -675,9 +643,8 @@ def FromJSON(self, data: Union[AnyStr, dict], options: DSSJSONFlags = 0): self._lib.Circuit_FromJSON(data, options) - self._check_for_error() - def Save(self, dirOrFilePath: AnyStr, options: DSSSaveFlags) -> str: + def Save(self, dirOrFilePath: AnyStr, saveFlags: Union[DSSSaveFlags, List[DSSSaveFlags]]) -> str: ''' Equivalent of the "save circuit" DSS command, but allows customization through the `saveFlags` argument, which is a set of bit flags. @@ -694,7 +661,7 @@ def Save(self, dirOrFilePath: AnyStr, options: DSSSaveFlags) -> str: - `IsOpen`: Export commands to open terminals of elements. - `ToString`: to the result string. Requires "SingleFile" flag. - If `SingleFile` is enabled, the first argument (`dirOrFilePath`) is the file path, + If `SingleFile` is enabled, the path argument (`dirOrFilePath`) is the file path, otherwise it is the folder path. For string output, the argument is not used. **(API Extension)** @@ -702,6 +669,39 @@ def Save(self, dirOrFilePath: AnyStr, options: DSSSaveFlags) -> str: if not isinstance(dirOrFilePath, bytes): dirOrFilePath = dirOrFilePath.encode() - return self._check_for_error(self._get_string(self._lib.Circuit_Save(dirOrFilePath, options))) + if isinstance(saveFlags, (list, tuple)): + mask = 0 + for v in saveFlags: + mask = mask | v + + saveFlags = mask + + return self._api_util.get_string(self._lib.Circuit_Save(dirOrFilePath, saveFlags)) + + def Flatten(self) -> None: + ''' + Flatten the circuit + + Flatten the circuit structures, removing any object of the following types: + + - XfmrCode + - LineCode + - LineSpacing + - LineGeometry + - WireData + - CNData + - TSData + + The general data from those objects is propagated to the referencing Line and Transformer objects, + and the properties on the latter are updated to remove any references to the removed objects. + This is useful for some converting the DSS circuit to another format, without requiring the user to handle all + the types listed above. This, of course, results in some limitations since a lot of detail is removed. Numerically, + a normal snapshot or daily solution should be the same before and after the flatten operation. + + Available only on AltDSS. + + **(API Extension)** + ''' + self._lib.Circuit_Flatten() diff --git a/dss/ICktElement.py b/dss/ICktElement.py index d38a5edc..fcca200f 100644 --- a/dss/ICktElement.py +++ b/dss/ICktElement.py @@ -1,14 +1,27 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from __future__ import annotations from ._cffi_api_util import Base from .IDSSProperty import IDSSProperty -from ._types import Float64Array, Int32Array, Float64ArrayOrComplexArray, Float64ArrayOrSimpleComplex +from ._types import Float64Array, Int32Array, Int32Matrix, ComplexArray, Complex from typing import List, AnyStr, Tuple, Iterator from .enums import OCPDevType as OCPDevTypeEnum class ICktElement(Base): + ''' + The (Active)CktElement interface allows accessing some common properties and + methods shared across circuit elements in the DSS engine. + + Users can enable specific elements by name or use the dedicated interface + (e.g. use `Loads.Name`, `Transformers.First/Next`) and access the properties here. + + If you are new to OpenDSS/AltDSS and this classic interface, please read the following document + for an overview of the "active element" paradigm used by COM and the classic APIs: + + https://dss-extensions.org/classic_api.html#the-active-paradigm + ''' + __slots__ = [ 'Properties' ] @@ -68,11 +81,11 @@ def Close(self, Term: int, Phs: int): Original COM help: https://opendss.epri.com/Close1.html ''' - self._check_for_error(self._lib.CktElement_Close(Term, Phs)) + self._lib.CktElement_Close(Term, Phs) def Controller(self, idx: int) -> str: '''Full name of the i-th controller attached to this element. Ex: str = Controller(2). See NumControls to determine valid index range''' - return self._get_string(self._check_for_error(self._lib.CktElement_Get_Controller(idx))) + return self._lib.CktElement_Get_Controller(idx) def Variable(self, MyVarName: AnyStr) -> Tuple[float, int]: ''' @@ -84,7 +97,7 @@ def Variable(self, MyVarName: AnyStr) -> Tuple[float, int]: MyVarName = MyVarName.encode(self._api_util.codec) Code = self._api_util.ffi.new('int32_t*') - result = self._check_for_error(self._lib.CktElement_Get_Variable(MyVarName, Code)) + result = self._lib.CktElement_Get_Variable(MyVarName, Code) # if Code[0] == 1: # raise DssException('No variable by this name or not a PCelement') return result, Code[0] @@ -97,7 +110,7 @@ def Variablei(self, Idx: int) -> Tuple[float, int]: Original COM help: https://opendss.epri.com/Variablei.html ''' Code = self._api_util.ffi.new('int32_t*') - result = self._check_for_error(self._lib.CktElement_Get_Variablei(Idx, Code)) + result = self._lib.CktElement_Get_Variablei(Idx, Code) # if Code[0] == 1: # raise DssException('Invalid variable index or not a PCelement') return result, Code[0] @@ -108,20 +121,29 @@ def Variablei(self, Idx: int) -> Tuple[float, int]: def setVariableByIndex(self, Idx: int, Value: float) -> int: Code = self._api_util.ffi.new('int32_t*') - self._check_for_error(self._lib.CktElement_Set_Variablei(Idx, Code, Value)) + self._lib.CktElement_Set_Variablei(Idx, Code, Value) # if Code[0] == 1: # raise DSSException('Invalid variable index or not a PCelement') return Code[0] def setVariableByName(self, Idx: AnyStr, Value: float) -> int: Code = self._api_util.ffi.new('int32_t*') - self._check_for_error(self._lib.CktElement_Set_Variable(Idx, Code, Value)) + self._lib.CktElement_Set_Variable(Idx, Code, Value) # if Code[0] == 1: # raise DSSException('Invalid variable index or not a PCelement') return Code[0] - def IsOpen(self, Term: int, Phs: int) -> bool: - return self._check_for_error(self._lib.CktElement_IsOpen(Term, Phs)) != 0 + def IsOpen(self, Term: int, Phs: int = 0) -> bool: + ''' + Indicates if the specified terminal and, optionally, a specific phase conductor is open. + + Provide zero in the `Phs` argument to check if any conductor of the terminal `Term` is open. + + Provide a non-zero phase number in `Phs` to check if a specific phase conductor is open. + + Original COM help: https://opendss.epri.com/IsOpen.html + ''' + return self._lib.CktElement_IsOpen(Term, Phs) def Open(self, Term: int, Phs: int): ''' @@ -129,7 +151,7 @@ def Open(self, Term: int, Phs: int): Original COM help: https://opendss.epri.com/Open1.html ''' - self._check_for_error(self._lib.CktElement_Open(Term, Phs)) + self._lib.CktElement_Open(Term, Phs) @property def AllPropertyNames(self) -> List[str]: @@ -138,7 +160,7 @@ def AllPropertyNames(self) -> List[str]: Original COM help: https://opendss.epri.com/AllPropertyNames.html ''' - return self._check_for_error(self._get_string_array(self._lib.CktElement_Get_AllPropertyNames)) + return self._lib.CktElement_Get_AllPropertyNames() @property def AllVariableNames(self) -> List[str]: @@ -148,7 +170,7 @@ def AllVariableNames(self) -> List[str]: Original COM help: https://opendss.epri.com/AllVariableNames.html ''' - return self._check_for_error(self._get_string_array(self._lib.CktElement_Get_AllVariableNames)) + return self._lib.CktElement_Get_AllVariableNames() @property def AllVariableValues(self) -> Float64Array: @@ -158,61 +180,67 @@ def AllVariableValues(self) -> Float64Array: Original COM help: https://opendss.epri.com/AllVariableValues.html ''' - self._check_for_error(self._lib.CktElement_Get_AllVariableValues_GR()) - return self._get_float64_gr_array() + return self._lib.CktElement_Get_AllVariableValues_GR() - @property - def BusNames(self) -> List[str]: + def _get_BusNames(self, removeNodes: bool = False) -> List[str]: ''' Bus definitions to which each terminal is connected. + The `removeNodes` argument is an **API Extension**. Use it to get only the bus names, + without the connection/node specification, if present. + Original COM help: https://opendss.epri.com/BusNames.html ''' - return self._check_for_error(self._get_string_array(self._lib.CktElement_Get_BusNames)) + return self._lib.CktElement_Get_BusNames(removeNodes) + + def _set_BusNames(self, Value: List[AnyStr]): + self._set_string_array(self._lib.CktElement_Set_BusNames, Value) + + BusNames = property(_get_BusNames, _set_BusNames) # type: List[str] + ''' + Bus definitions to which each terminal is connected. + + In the getter function (`_get_BusNames`), the `removeNodes` argument is an **API Extension**. + Use it to get only the bus names, without the connection/node specification, if present. - @BusNames.setter - def BusNames(self, Value: List[AnyStr]): - self._check_for_error(self._set_string_array(self._lib.CktElement_Set_BusNames, Value)) + Original COM help: https://opendss.epri.com/BusNames.html + ''' @property - def CplxSeqCurrents(self) -> Float64ArrayOrComplexArray: + def CplxSeqCurrents(self) -> ComplexMatrix: ''' Complex double array of Sequence Currents for all conductors of all terminals of active circuit element. Original COM help: https://opendss.epri.com/CplxSeqCurrents.html ''' - self._check_for_error(self._lib.CktElement_Get_CplxSeqCurrents_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_CplxSeqCurrents_GR() @property - def CplxSeqVoltages(self) -> Float64ArrayOrComplexArray: + def CplxSeqVoltages(self) -> ComplexMatrix: ''' Complex double array of Sequence Voltage for all terminals of active circuit element. Original COM help: https://opendss.epri.com/CplxSeqVoltages1.html ''' - self._check_for_error(self._lib.CktElement_Get_CplxSeqVoltages_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_CplxSeqVoltages_GR() @property - def Currents(self) -> Float64ArrayOrComplexArray: + def Currents(self) -> ComplexMatrix: ''' Complex array of currents into each conductor of each terminal Original COM help: https://opendss.epri.com/Currents1.html ''' - self._check_for_error(self._lib.CktElement_Get_Currents_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_Currents_GR() @property - def CurrentsMagAng(self) -> Float64Array: + def CurrentsMagAng(self) -> Float64Matrix: ''' - Currents in magnitude, angle (degrees) format as a array of doubles. + Currents in magnitude, angle (degrees) format as an array of doubles. Original COM help: https://opendss.epri.com/CurrentsMagAng.html ''' - self._check_for_error(self._lib.CktElement_Get_CurrentsMagAng_GR()) - return self._get_float64_gr_array() + return self._lib.CktElement_Get_CurrentsMagAng_GR() @property def DisplayName(self) -> str: @@ -221,14 +249,11 @@ def DisplayName(self) -> str: Original COM help: https://opendss.epri.com/DisplayName.html ''' - return self._get_string(self._check_for_error(self._lib.CktElement_Get_DisplayName())) + return self._lib.CktElement_Get_DisplayName() @DisplayName.setter def DisplayName(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.CktElement_Set_DisplayName(Value)) + self._lib.CktElement_Set_DisplayName(Value) @property def EmergAmps(self) -> float: @@ -237,11 +262,11 @@ def EmergAmps(self) -> float: Original COM help: https://opendss.epri.com/EmergAmps.html ''' - return self._check_for_error(self._lib.CktElement_Get_EmergAmps()) + return self._lib.CktElement_Get_EmergAmps() @EmergAmps.setter def EmergAmps(self, Value: float): - self._check_for_error(self._lib.CktElement_Set_EmergAmps(Value)) + self._lib.CktElement_Set_EmergAmps(Value) @property def Enabled(self) -> bool: @@ -250,11 +275,11 @@ def Enabled(self) -> bool: Original COM help: https://opendss.epri.com/Enabled.html ''' - return self._check_for_error(self._lib.CktElement_Get_Enabled()) != 0 + return self._lib.CktElement_Get_Enabled() @Enabled.setter def Enabled(self, Value: bool): - self._check_for_error(self._lib.CktElement_Set_Enabled(Value)) + self._lib.CktElement_Set_Enabled(Value) @property def EnergyMeter(self) -> str: @@ -265,7 +290,7 @@ def EnergyMeter(self) -> str: Original COM help: https://opendss.epri.com/EnergyMeter.html ''' - return self._get_string(self._check_for_error(self._lib.CktElement_Get_EnergyMeter())) + return self._lib.CktElement_Get_EnergyMeter() @property def GUID(self) -> str: @@ -274,7 +299,7 @@ def GUID(self) -> str: Original COM help: https://opendss.epri.com/GUID.html ''' - return self._get_string(self._check_for_error(self._lib.CktElement_Get_GUID())) + return self._lib.CktElement_Get_GUID() @property def Handle(self) -> int: @@ -283,7 +308,7 @@ def Handle(self) -> int: Original COM help: https://opendss.epri.com/Handle.html ''' - return self._check_for_error(self._lib.CktElement_Get_Handle()) + return self._lib.CktElement_Get_Handle() @property def HasOCPDevice(self) -> bool: @@ -292,7 +317,7 @@ def HasOCPDevice(self) -> bool: Original COM help: https://opendss.epri.com/HasOCPDevice.html ''' - return self._check_for_error(self._lib.CktElement_Get_HasOCPDevice()) != 0 + return self._lib.CktElement_Get_HasOCPDevice() @property def HasSwitchControl(self) -> bool: @@ -301,7 +326,7 @@ def HasSwitchControl(self) -> bool: Original COM help: https://opendss.epri.com/HasSwitchControl.html ''' - return self._check_for_error(self._lib.CktElement_Get_HasSwitchControl()) != 0 + return self._lib.CktElement_Get_HasSwitchControl() @property def HasVoltControl(self) -> bool: @@ -310,17 +335,25 @@ def HasVoltControl(self) -> bool: Original COM help: https://opendss.epri.com/HasVoltControl.html ''' - return self._check_for_error(self._lib.CktElement_Get_HasVoltControl()) != 0 + return self._lib.CktElement_Get_HasVoltControl() @property - def Losses(self) -> Float64ArrayOrSimpleComplex: + def Losses(self) -> Complex: ''' Total losses in the element: two-element double array (complex), in VA (watts, vars) Original COM help: https://opendss.epri.com/Losses1.html ''' - self._check_for_error(self._lib.CktElement_Get_Losses_GR()) - return self._get_complex128_gr_simple() + return self._lib.CktElement_Get_Losses_GR() + + @property + def AllLosses(self) -> Complex: + ''' + Complex array with the losses by type (total losses, load losses, no-load losses), in VA, for the active circuit element. + + Added in May 2025. Same as `LossesByType` introduced for Transformers in AltDSS/DSS C-API in May 2019. + ''' + return self._lib.CktElement_Get_AllLosses_GR() @property def Name(self) -> str: @@ -329,10 +362,10 @@ def Name(self) -> str: Original COM help: https://opendss.epri.com/Name4.html ''' - return self._get_string(self._check_for_error(self._lib.CktElement_Get_Name())) + return self._lib.CktElement_Get_Name() @property - def NodeOrder(self) -> Int32Array: + def NodeOrder(self) -> Int32Matrix: ''' Array of integer containing the node numbers (representing phases, for example) for each conductor of each terminal. @@ -340,8 +373,7 @@ def NodeOrder(self) -> Int32Array: Original COM help: https://opendss.epri.com/NodeOrder.html ''' - self._check_for_error(self._lib.CktElement_Get_NodeOrder_GR()) - return self._get_int32_gr_array() + return self._lib.CktElement_Get_NodeOrder_GR() @property def NormalAmps(self) -> float: @@ -350,11 +382,11 @@ def NormalAmps(self) -> float: Original COM help: https://opendss.epri.com/NormalAmps.html ''' - return self._check_for_error(self._lib.CktElement_Get_NormalAmps()) + return self._lib.CktElement_Get_NormalAmps() @NormalAmps.setter def NormalAmps(self, Value: float): - self._check_for_error(self._lib.CktElement_Set_NormalAmps(Value)) + self._lib.CktElement_Set_NormalAmps(Value) @property def NumConductors(self) -> int: @@ -363,7 +395,7 @@ def NumConductors(self) -> int: Original COM help: https://opendss.epri.com/NumConductors.html ''' - return self._check_for_error(self._lib.CktElement_Get_NumConductors()) + return self._lib.CktElement_Get_NumConductors() @property def NumControls(self) -> int: @@ -373,7 +405,7 @@ def NumControls(self) -> int: Original COM help: https://opendss.epri.com/NumControls.html ''' - return self._check_for_error(self._lib.CktElement_Get_NumControls()) + return self._lib.CktElement_Get_NumControls() @property def NumPhases(self) -> int: @@ -382,7 +414,7 @@ def NumPhases(self) -> int: Original COM help: https://opendss.epri.com/NumPhases.html ''' - return self._check_for_error(self._lib.CktElement_Get_NumPhases()) + return self._lib.CktElement_Get_NumPhases() @property def NumProperties(self) -> int: @@ -391,7 +423,7 @@ def NumProperties(self) -> int: Original COM help: https://opendss.epri.com/NumProperties.html ''' - return self._check_for_error(self._lib.CktElement_Get_NumProperties()) + return self._lib.CktElement_Get_NumProperties() @property def NumTerminals(self) -> int: @@ -400,7 +432,7 @@ def NumTerminals(self) -> int: Original COM help: https://opendss.epri.com/NumTerminals.html ''' - return self._check_for_error(self._lib.CktElement_Get_NumTerminals()) + return self._lib.CktElement_Get_NumTerminals() @property def OCPDevIndex(self) -> int: @@ -409,7 +441,7 @@ def OCPDevIndex(self) -> int: Original COM help: https://opendss.epri.com/OCPDevIndex.html ''' - return self._check_for_error(self._lib.CktElement_Get_OCPDevIndex()) + return self._lib.CktElement_Get_OCPDevIndex() @property def OCPDevType(self) -> OCPDevTypeEnum: @@ -418,97 +450,97 @@ def OCPDevType(self) -> OCPDevTypeEnum: Original COM help: https://opendss.epri.com/OCPDevType.html ''' - return OCPDevTypeEnum(self._check_for_error(self._lib.CktElement_Get_OCPDevType())) + return OCPDevTypeEnum(self._lib.CktElement_Get_OCPDevType()) @property - def PhaseLosses(self) -> Float64ArrayOrComplexArray: + def PhaseLosses(self) -> ComplexArray: ''' Complex array of losses (kVA) by phase Original COM help: https://opendss.epri.com/PhaseLosses.html ''' - self._check_for_error(self._lib.CktElement_Get_PhaseLosses_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_PhaseLosses_GR() @property - def Powers(self) -> Float64ArrayOrComplexArray: + def Powers(self) -> ComplexArray: ''' Complex array of powers (kVA) into each conductor of each terminal Original COM help: https://opendss.epri.com/Powers.html ''' - self._check_for_error(self._lib.CktElement_Get_Powers_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_Powers_GR() @property - def Residuals(self) -> Float64Array: + def Residuals(self) -> Float64Matrix: ''' Residual currents for each terminal: (magnitude, angle in degrees) Original COM help: https://opendss.epri.com/Residuals.html ''' - self._check_for_error(self._lib.CktElement_Get_Residuals_GR()) - return self._get_float64_gr_array() + return self._lib.CktElement_Get_Residuals_GR() @property - def SeqCurrents(self) -> Float64Array: + def SeqCurrents(self) -> Float64Matrix: ''' Double array of symmetrical component currents (magnitudes only) into each 3-phase terminal Original COM help: https://opendss.epri.com/SeqCurrents.html ''' - self._check_for_error(self._lib.CktElement_Get_SeqCurrents_GR()) - return self._get_float64_gr_array() + return self._lib.CktElement_Get_SeqCurrents_GR() @property - def SeqPowers(self) -> Float64ArrayOrComplexArray: + def SeqPowers(self) -> ComplexMatrix: ''' Complex array of sequence powers (kW, kvar) into each 3-phase terminal Original COM help: https://opendss.epri.com/SeqPowers.html ''' - self._check_for_error(self._lib.CktElement_Get_SeqPowers_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_SeqPowers_GR() @property - def SeqVoltages(self) -> Float64Array: + def SeqVoltages(self) -> Float64Matrix: ''' Double array of symmetrical component voltages (magnitudes only) at each 3-phase terminal Original COM help: https://opendss.epri.com/SeqVoltages1.html ''' - self._check_for_error(self._lib.CktElement_Get_SeqVoltages_GR()) - return self._get_float64_gr_array() + return self._lib.CktElement_Get_SeqVoltages_GR() @property - def Voltages(self) -> Float64ArrayOrComplexArray: + def Voltages(self) -> ComplexMatrix: ''' Complex array of voltages at terminals Original COM help: https://opendss.epri.com/Voltages1.html ''' - self._check_for_error(self._lib.CktElement_Get_Voltages_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_Voltages_GR() @property - def VoltagesMagAng(self) -> Float64Array: + def VoltagesMagAng(self) -> Float64Matrix: ''' Voltages at each conductor in magnitude, angle form as array of doubles. Original COM help: https://opendss.epri.com/VoltagesMagAng.html ''' - self._check_for_error(self._lib.CktElement_Get_VoltagesMagAng_GR()) - return self._get_float64_gr_array() + return self._lib.CktElement_Get_VoltagesMagAng_GR() @property - def Yprim(self) -> Float64ArrayOrComplexArray: + def Yprim(self) -> ComplexMatrix: ''' YPrim matrix, column order, complex numbers Original COM help: https://opendss.epri.com/Yprim.html ''' - self._check_for_error(self._lib.CktElement_Get_Yprim_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_Yprim_GR() + + @property + def YprimOrder(self) -> int: + ''' + Order (size) of the active circuit element's primite Y matrix (Yprim), typically `NumConductors * NumTerminals` + + **(API Extension)** + ''' + return self._lib.CktElement_Get_YprimOrder() @property def IsIsolated(self) -> bool: @@ -518,17 +550,16 @@ def IsIsolated(self) -> bool: **(API Extension)** ''' - return self._check_for_error(self._lib.CktElement_Get_IsIsolated()) != 0 + return self._lib.CktElement_Get_IsIsolated() @property - def TotalPowers(self) -> Float64ArrayOrComplexArray: + def TotalPowers(self) -> ComplexArray: ''' Returns an array with the total powers (complex, kVA) at ALL terminals of the active circuit element. Original COM help: https://opendss.epri.com/TotalPowers.html ''' - self._check_for_error(self._lib.CktElement_Get_TotalPowers_GR()) - return self._get_complex128_gr_array() + return self._lib.CktElement_Get_TotalPowers_GR() @property def NodeRef(self) -> Int32Array: @@ -539,23 +570,19 @@ def NodeRef(self) -> Int32Array: **(API Extension)** ''' - self._lib.CktElement_Get_NodeRef_GR() - return self._get_int32_gr_array() + return self._lib.CktElement_Get_NodeRef_GR() def __iter__(self) -> Iterator[ICktElement]: - for index in range(self._check_for_error(self._lib.Circuit_Get_NumCktElements())): - self._check_for_error(self._lib.Circuit_SetCktElementIndex(index)) + for index in range(self._lib.Circuit_Get_NumCktElements()): + self._lib.Circuit_SetCktElementIndex(index) yield self def __getitem__(self, index) -> ICktElement: if isinstance(index, int): # index is zero based, pass it directly - self._check_for_error(self._lib.Circuit_SetCktElementIndex(index)) + self._lib.Circuit_SetCktElementIndex(index) else: - if not isinstance(index, bytes): - index = index.encode(self._api_util.codec) - - self._check_for_error(self._lib.Circuit_SetCktElementName(index)) + self._lib.Circuit_SetCktElementName(index) return self diff --git a/dss/ICtrlQueue.py b/dss/ICtrlQueue.py index 583aa5ad..f3f8992b 100644 --- a/dss/ICtrlQueue.py +++ b/dss/ICtrlQueue.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base from typing import List @@ -21,7 +21,7 @@ def ClearActions(self): Original COM help: https://opendss.epri.com/ClearActions.html ''' - self._check_for_error(self._lib.CtrlQueue_ClearActions()) + self._lib.CtrlQueue_ClearActions() def ClearQueue(self): ''' @@ -29,9 +29,9 @@ def ClearQueue(self): Original COM help: https://opendss.epri.com/ClearQueue.html ''' - self._check_for_error(self._lib.CtrlQueue_ClearQueue()) + self._lib.CtrlQueue_ClearQueue() - def Delete(self, ActionHandle): + def Delete(self, ActionHandle: int): ''' Delete an Action from the DSS Control Queue by the handle that is returned when the action is added. @@ -39,7 +39,7 @@ def Delete(self, ActionHandle): Original COM help: https://opendss.epri.com/Delete.html ''' - self._check_for_error(self._lib.CtrlQueue_Delete(ActionHandle)) + self._lib.CtrlQueue_Delete(ActionHandle) def DoAllQueue(self): ''' @@ -49,7 +49,7 @@ def DoAllQueue(self): Original COM help: https://opendss.epri.com/DoAllQueue.html ''' - self._check_for_error(self._lib.CtrlQueue_DoAllQueue()) + self._lib.CtrlQueue_DoAllQueue() def Show(self): ''' @@ -57,7 +57,7 @@ def Show(self): Original COM help: https://opendss.epri.com/Show.html ''' - self._check_for_error(self._lib.CtrlQueue_Show()) + self._lib.CtrlQueue_Show() @property def ActionCode(self) -> int: @@ -69,7 +69,7 @@ def ActionCode(self) -> int: Original COM help: https://opendss.epri.com/ActionCode.html ''' - return self._check_for_error(self._lib.CtrlQueue_Get_ActionCode()) + return self._lib.CtrlQueue_Get_ActionCode() @property def DeviceHandle(self) -> int: @@ -83,7 +83,7 @@ def DeviceHandle(self) -> int: Original COM help: https://opendss.epri.com/DeviceHandle.html ''' - return self._check_for_error(self._lib.CtrlQueue_Get_DeviceHandle()) + return self._lib.CtrlQueue_Get_DeviceHandle() @property def NumActions(self) -> int: @@ -92,15 +92,15 @@ def NumActions(self) -> int: Original COM help: https://opendss.epri.com/NumActions.html ''' - return self._check_for_error(self._lib.CtrlQueue_Get_NumActions()) + return self._lib.CtrlQueue_Get_NumActions() - def Push(self, Hour: int, Seconds: float, ActionCode: int, DeviceHandle: int): + def Push(self, Hour: int, Seconds: float, ActionCode: int, DeviceHandle: int) -> int: ''' Push a control action onto the DSS control queue by time, action code, and device handle (user defined). Returns Control Queue handle. Original COM help: https://opendss.epri.com/Push.html ''' - return self._check_for_error(self._lib.CtrlQueue_Push(Hour, Seconds, ActionCode, DeviceHandle)) + return self._lib.CtrlQueue_Push(Hour, Seconds, ActionCode, DeviceHandle) @property def PopAction(self) -> int: @@ -109,7 +109,7 @@ def PopAction(self) -> int: Original COM help: https://opendss.epri.com/PopAction.html ''' - return self._check_for_error(self._lib.CtrlQueue_Get_PopAction()) + return self._lib.CtrlQueue_Get_PopAction() @property def Queue(self) -> List[str]: @@ -118,7 +118,7 @@ def Queue(self) -> List[str]: Original COM help: https://opendss.epri.com/Queue.html ''' - return self._check_for_error(self._get_string_array(self._lib.CtrlQueue_Get_Queue)) + return self._lib.CtrlQueue_Get_Queue() @property def QueueSize(self) -> int: @@ -127,7 +127,7 @@ def QueueSize(self) -> int: Original COM help: https://opendss.epri.com/QueueSize.html ''' - return self._check_for_error(self._lib.CtrlQueue_Get_QueueSize()) + return self._lib.CtrlQueue_Get_QueueSize() @property def Action(self) -> int: @@ -140,5 +140,5 @@ def Action(self) -> int: @Action.setter def Action(self, Param1: int): - self._check_for_error(self._lib.CtrlQueue_Set_Action(Param1)) + self._lib.CtrlQueue_Set_Action(Param1) diff --git a/dss/IDSS.py b/dss/IDSS.py index 201a4826..c65d92c9 100644 --- a/dss/IDSS.py +++ b/dss/IDSS.py @@ -1,11 +1,11 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from __future__ import annotations import warnings from weakref import WeakKeyDictionary from typing import Any, List, Union, AnyStr, TYPE_CHECKING -from ._cffi_api_util import Base, CffiApiUtil, DSSException +from ._cffi_api_util import Base, AltDSSAPIUtil, DSSException from .ICircuit import ICircuit from .IError import IError from .IText import IText @@ -14,7 +14,7 @@ from .IDSS_Executive import IDSS_Executive from .IDSSEvents import IDSSEvents from .IParser import IParser -from .IDSSimComs import IDSSimComs +from .ISettings import ISettings from .IYMatrix import IYMatrix from .IZIP import IZIP @@ -22,19 +22,20 @@ try: from altdss import AltDSS except: - AltDSS = None + pass try: from opendssdirect.OpenDSSDirect import OpenDSSDirect except: - OpenDSSDirect = None + pass class IDSS(Base): ''' Main OpenDSS interface. Organizes the subclasses trying to mimic the `OpenDSSengine.DSS` object as seen from `win32com.client` or `comtypes.client`. - This main class also includes some global settings. See more settings in `ActiveCircuit.Settings`. + This main class also includes some global settings. Most settings at being moved to the dediced + `Settings` interface, exposed in the shortcut `Settings` of this object, or `ActiveCircuit.Settings`. ''' __slots__ = [ 'ActiveCircuit', @@ -46,11 +47,11 @@ class IDSS(Base): 'Executive', 'Events', 'Parser', - 'DSSim_Coms', 'YMatrix', 'ZIP', '_version', '_altdss', + '_plotter', ] _columns = [ @@ -75,12 +76,11 @@ class IDSS(Base): Executive: IDSS_Executive Events: IDSSEvents Parser: IParser - DSSim_Coms: IDSSimComs YMatrix: IYMatrix ZIP: IZIP @classmethod - def _get_instance(cls: IDSS, api_util: CffiApiUtil = None, ctx=None) -> IDSS: + def _get_instance(cls: IDSS, api_util: AltDSSAPIUtil = None, ctx=None) -> IDSS: ''' If there is an existing instance for a DSSContext, returns it. Otherwise, tries to wrap the context into a new DSS-Python API instance. @@ -88,7 +88,7 @@ def _get_instance(cls: IDSS, api_util: CffiApiUtil = None, ctx=None) -> IDSS: if api_util is None: # If none exists, something is probably wrong elsewhere, # so let's allow the IndexError to propagate - api_util = CffiApiUtil._ctx_to_util[ctx] + api_util = AltDSSAPIUtil._ctx_to_util[ctx] dss = cls._ctx_to_dss.get(api_util.ctx) if dss is None: @@ -101,12 +101,19 @@ def __init__(self, api_util): Wrap a new DSS context with the DSS-Python API. This is not typically used directly. Refer to `IDSS.NewContext` or `IDSS._get_instance`. + + For Oddie-wrapped libraries (EPRI's OpenDSS and OpenDSS-C), prefer the dedicated constructors and classes + (e.g. `IOddieDSS`, `EPRIOpenDSSC`, `EPRIOpenDSS`). ''' if api_util.ctx not in IDSS._ctx_to_dss: IDSS._ctx_to_dss[api_util.ctx] = self + if api_util._dss_python is None: + api_util._dss_python = self + self._version = None + self._plotter = None #: Provides access to the circuit attributes and objects in general. self.ActiveCircuit = ICircuit(api_util) @@ -134,15 +141,11 @@ def __init__(self, api_util): self.Executive = IDSS_Executive(api_util) #: Kept for compatibility. - self.Events = IDSSEvents(api_util) if not api_util._is_odd else None + self.Events = IDSSEvents(api_util) if not api_util._is_oddie else None #: Kept for compatibility. self.Parser = IParser(api_util) - #: Kept for compatibility. Apparently was used for DSSim-PC (now OpenDSS-G), a - #: closed-source software developed by EPRI using LabView. - self.DSSim_Coms = IDSSimComs(api_util) if not api_util._is_odd else None - #: The YMatrix interface provides advanced access to the internals of #: the DSS engine. The sparse admittance matrix of the system is also #: available here. @@ -157,7 +160,7 @@ def __init__(self, api_util): #: and run scripts inside the ZIP, without creating extra files on disk. #: #: **(API Extension)** - self.ZIP = IZIP(api_util) if not api_util._is_odd else None + self.ZIP = IZIP(api_util) if not api_util._is_oddie else None Base.__init__(self, api_util) @@ -189,8 +192,18 @@ def to_opendssdirect(self) -> OpenDSSDirect: from opendssdirect.OpenDSSDirect import OpenDSSDirect return OpenDSSDirect._get_instance(ctx=self._api_util.ctx, api_util=self._api_util) + def is_oddie(self) -> bool: + """ + Returns True if this instance is based on the Oddie compatibility layer for + EPRI's OpenDSS Direct API (a.k.a. DCSL). + + Note that the default engine in DSS-Python has been based on AltDSS since + 2018, even though it was not called AltDSS then. + """ + return self._api_util._is_oddie + def ClearAll(self): - self._check_for_error(self._lib.DSS_ClearAll()) + self._lib.DSS_ClearAll() def Reset(self): ''' @@ -198,13 +211,10 @@ def Reset(self): Original COM help: https://opendss.epri.com/Reset1.html ''' - self._check_for_error(self._lib.DSS_Reset()) + self._lib.DSS_Reset() def SetActiveClass(self, ClassName: AnyStr) -> int: - if not isinstance(ClassName, bytes): - ClassName = ClassName.encode(self._api_util.codec) - - return self._check_for_error(self._lib.DSS_SetActiveClass(ClassName)) + return self._lib.DSS_SetActiveClass(ClassName) def Start(self, code: int) -> bool: ''' @@ -214,12 +224,13 @@ def Start(self, code: int) -> bool: handled automatically, so the users do not need to call it manually, unless using AltDSS/DSS C-API directly without further tools. - On the official OpenDSS, `Start` also does nothing at all in the current - versions. + On EPRI's OpenDSS, `Start` also does nothing at all in the current + Delphi versions. It is required for OpenDSS-C, but also handled behind + the scenes on DSS-Extensions. Original COM help: https://opendss.epri.com/Start.html ''' - return self._check_for_error(self._lib.DSS_Start(code)) != 0 + return self._lib.DSS_Start(code) @property def Classes(self) -> List[str]: @@ -228,7 +239,7 @@ def Classes(self) -> List[str]: Original COM help: https://opendss.epri.com/Classes1.html ''' - return self._check_for_error(self._get_string_array(self._lib.DSS_Get_Classes)) + return self._lib.DSS_Get_Classes() @property def DataPath(self) -> str: @@ -237,14 +248,11 @@ def DataPath(self) -> str: Original COM help: https://opendss.epri.com/DataPath.html ''' - return self._get_string(self._check_for_error(self._lib.DSS_Get_DataPath())) + return self._lib.DSS_Get_DataPath() @DataPath.setter def DataPath(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.DSS_Set_DataPath(Value)) + self._lib.DSS_Set_DataPath(Value) @property def DefaultEditor(self) -> str: @@ -253,7 +261,7 @@ def DefaultEditor(self) -> str: Original COM help: https://opendss.epri.com/DefaultEditor.html ''' - return self._get_string(self._check_for_error(self._lib.DSS_Get_DefaultEditor())) + return self._lib.DSS_Get_DefaultEditor() @property def NumCircuits(self) -> int: @@ -262,7 +270,7 @@ def NumCircuits(self) -> int: Original COM help: https://opendss.epri.com/NumCircuits.html ''' - return self._check_for_error(self._lib.DSS_Get_NumCircuits()) + return self._lib.DSS_Get_NumCircuits() @property def NumClasses(self) -> int: @@ -271,7 +279,7 @@ def NumClasses(self) -> int: Original COM help: https://opendss.epri.com/NumClasses.html ''' - return self._check_for_error(self._lib.DSS_Get_NumClasses()) + return self._lib.DSS_Get_NumClasses() @property def NumUserClasses(self) -> int: @@ -280,7 +288,7 @@ def NumUserClasses(self) -> int: Original COM help: https://opendss.epri.com/NumUserClasses.html ''' - return self._check_for_error(self._lib.DSS_Get_NumUserClasses()) + return self._lib.DSS_Get_NumUserClasses() @property def UserClasses(self) -> List[str]: @@ -289,7 +297,7 @@ def UserClasses(self) -> List[str]: Original COM help: https://opendss.epri.com/UserClasses.html ''' - return self._check_for_error(self._get_string_array(self._lib.DSS_Get_UserClasses)) + return self._lib.DSS_Get_UserClasses() @property def Version(self) -> str: @@ -303,20 +311,22 @@ def Version(self) -> str: from . import __version__ as dss_python_version self._version = dss_python_version - return self._get_string(self._check_for_error(self._lib.DSS_Get_Version())) + f'\nDSS-Python version: {self._version}' + return self._lib.DSS_Get_Version() + f'\nDSS-Python version: {self._version}' @property def AllowForms(self) -> bool: ''' - Gets/sets whether text output is allowed (DSS-Extensions) or general forms/windows are shown (official OpenDSS). + Indicates whether text output is allowed or forms are used. Disable to silence most output. + + Currently, forms/windows are only used for EPRI's OpenDSS distribution on Windows. Original COM help: https://opendss.epri.com/AllowForms.html ''' - return self._check_for_error(self._lib.DSS_Get_AllowForms()) != 0 + return self._lib.DSS_Get_AllowForms() @AllowForms.setter def AllowForms(self, value: bool): - self._check_for_error(self._lib.DSS_Set_AllowForms(value)) + self._lib.DSS_Set_AllowForms(value) @property def AllowEditor(self) -> bool: @@ -327,16 +337,20 @@ def AllowEditor(self) -> bool: If you set to 0 (false), the editor is not executed. Note that other side effects, such as the creation of files, are not affected. + **Deprecated:** Use `Settings.AllowEditor` instead (same behavior, the setting was just moved there for better organization). + **(API Extension)** ''' - return self._check_for_error(self._lib.DSS_Get_AllowEditor()) != 0 + warnings.warn('"AllowEditor" was moved to the Settings interface. This property still works, but will be removed in a future release. Please use `...Settings.AllowEditor` instead.', DeprecationWarning, stacklevel=2) + return self._lib.DSS_Get_AllowEditor() @AllowEditor.setter def AllowEditor(self, value: bool): - self._check_for_error(self._lib.DSS_Set_AllowEditor(value)) + self._lib.DSS_Set_AllowEditor(value) def ShowPanel(self): - pass + if api_util._is_oddie: + self._lib.Text_Set_Command('panel') def NewCircuit(self, name) -> ICircuit: ''' @@ -344,10 +358,7 @@ def NewCircuit(self, name) -> ICircuit: Original COM help: https://opendss.epri.com/NewCircuit.html ''' - if not isinstance(name, bytes): - name = name.encode(self._api_util.codec) - - self._check_for_error(self._lib.DSS_NewCircuit(name)) + self._lib.DSS_NewCircuit(name) return self.ActiveCircuit @@ -356,18 +367,18 @@ def LegacyModels(self) -> bool: ''' LegacyModels was a flag used to toggle legacy (pre-2019) models for PVSystem, InvControl, Storage and StorageControl. - In the official OpenDSS version 9.0, the old models were removed. They were temporarily present here + In EPRI's OpenDSS version 9.0, the old models were removed. They were temporarily present here but were also removed in DSS C-API v0.13.0. **NOTE**: this property will be removed for v1.0. It is left to avoid breaking the current API too soon. **(API Extension)** ''' - return self._check_for_error(self._lib.DSS_Get_LegacyModels()) != 0 + return self._lib.DSS_Get_LegacyModels() @LegacyModels.setter def LegacyModels(self, Value: bool): - self._check_for_error(self._lib.DSS_Set_LegacyModels(Value)) + self._lib.DSS_Set_LegacyModels(Value) @property def AllowChangeDir(self) -> bool: @@ -383,13 +394,16 @@ def AllowChangeDir(self) -> bool: This can also be set through the environment variable DSS_CAPI_ALLOW_CHANGE_DIR. Set it to 0 to disallow changing the active working directory. + **Deprecated:** Use `Settings.AllowChangeDir` instead (same behavior, the setting was just moved there for better organization). + **(API Extension)** ''' - return self._check_for_error(self._lib.DSS_Get_AllowChangeDir()) != 0 + warnings.warn('"AllowChangeDir" was moved to the Settings interface. This property still works, but will be removed in a future release. Please use `...Settings.AllowChangeDir` instead.', DeprecationWarning, stacklevel=2) + return self._lib.DSS_Get_AllowChangeDir() @AllowChangeDir.setter def AllowChangeDir(self, Value: bool): - self._check_for_error(self._lib.DSS_Set_AllowChangeDir(Value)) + self._lib.DSS_Set_AllowChangeDir(Value) @property def AllowDOScmd(self) -> bool: @@ -398,41 +412,50 @@ def AllowDOScmd(self) -> bool: Defaults to False/0 (disabled state). Users should consider DOScmd deprecated on DSS-Extensions. - This can also be set through the environment variable DSS_CAPI_ALLOW_DOSCMD. Setting it to 1 enables + This can also be set through the environment variable `DSS_CAPI_ALLOW_DOSCMD`. Setting it to 1 enables the command. + **Deprecated:** Use `Settings.AllowDOScmd` instead (same behavior, the setting was just moved there for better organization). + **(API Extension)** ''' - return self._check_for_error(self._lib.DSS_Get_AllowDOScmd()) != 0 + warnings.warn('"AllowDOScmd" was moved to the Settings interface. This property still works, but will be removed in a future release. Please use `...Settings.AllowDOScmd` instead.', DeprecationWarning, stacklevel=2) + return self._lib.DSS_Get_AllowDOScmd() @AllowDOScmd.setter def AllowDOScmd(self, Value: bool): - self._check_for_error(self._lib.DSS_Set_AllowDOScmd(Value)) + self._lib.DSS_Set_AllowDOScmd(Value) @property def COMErrorResults(self) -> bool: ''' - If enabled, in case of errors or empty arrays, the API returns arrays with values compatible with the - official OpenDSS COM interface. + If enabled, in case of errors or empty arrays, the API returns arrays with values compatible with + EPRI's OpenDSS COM interface. For example, consider the function `Loads_Get_ZIPV`. If there is no active circuit or active load element: - - In the disabled state (COMErrorResults=False), the function will return "[]", an array with 0 elements. - - In the enabled state (COMErrorResults=True), the function will return "[0.0]" instead. This should - be compatible with the return value of the official COM interface. + - In the disabled state (`COMErrorResults`=False), the function will return "[]", an array with 0 elements. + - In the enabled state (`COMErrorResults`=True), the function will return "[0.0]" instead. This should + be compatible with the return value of EPRI's OpenDSS COM interface. + + Defaults to false (disabled state) in AltDSS since the v0.15.x series. - Defaults to True/1 (enabled state) in the v0.12.x series. This will change to false in future series. + This does not affect the results when using EPRI's OpenDSS distribution through Oddie. - This can also be set through the environment variable `DSS_CAPI_COM_DEFAULTS`. Setting it to 0 disables + This can also be set through the environment variable `DSS_CAPI_COM_DEFAULTS`. Setting it to 1 enables the legacy/COM behavior. The value can be toggled through the API at any time. + **Deprecated:** Use `Settings.COMErrorResults` instead (same behavior, the setting was just moved there for better organization). + **(API Extension)** ''' - return self._check_for_error(self._lib.DSS_Get_COMErrorResults()) != 0 + warnings.warn('"COMErrorResults" was moved to the Settings interface. This property still works, but will be removed in a future release. Please use `...Settings.COMErrorResults` instead.', DeprecationWarning, stacklevel=2) + return self._lib.DSS_Get_COMErrorResults() @COMErrorResults.setter def COMErrorResults(self, Value: bool): - self._check_for_error(self._lib.DSS_Set_COMErrorResults(Value)) + warnings.warn('"COMErrorResults" was moved to the Settings interface. This property still works, but will be removed in a future release. Please use `...Settings.COMErrorResults` instead.', DeprecationWarning, stacklevel=2) + self._lib.DSS_Set_COMErrorResults(Value) def NewContext(self) -> IDSS: ''' @@ -445,14 +468,13 @@ def NewContext(self) -> IDSS: **(API Extension)** ''' - if self._api_util._is_odd: - raise NotImplementedError("NewContext is not supported for the official OpenDSS engine.") + if self._api_util._is_oddie: + raise NotImplementedError("NewContext is not supported for the EPRI's OpenDSS engines.") ffi = self._api_util.ffi lib = self._api_util.lib_unpatched new_ctx = ffi.gc(lib.ctx_New(), lib.ctx_Dispose) - new_api_util = CffiApiUtil(ffi, lib, new_ctx) - new_api_util._allow_complex = self._api_util._allow_complex + new_api_util = AltDSSAPIUtil(ffi, lib, new_ctx, parent=self._api_util) return IDSS(new_api_util) def __call__(self, cmds: Union[AnyStr, List[AnyStr]]): @@ -485,21 +507,26 @@ def __call__(self, cmds: Union[AnyStr, List[AnyStr]]): @property def Plotting(self): ''' - Shortcut for the plotting module. This property is equivalent to: + Shortcut for the plotting tools for the current DSS engine. + + *Previously, this was just a shortcut to the plotting module. Since + the plotting tools were extended and refactored, an instance + of the DSS plotter is return, allowing the user to plot from + multiple instances and different engines.* - ``` - from dss import plot - return plot - ``` + Gives access to the `enable()`/`disable()` functions, and the new + experimental plotting API in Python. - Gives access to the `enable()` and `disable()` functions. - Requires matplotlib and SciPy to be installed, hence it is an - optional feature. + Requires matplotlib and SciPy to be installed, hence it is an optional + feature, lazily imported. **(API Extension)** ''' - from dss import plot - return plot + if self._plotter is None: + from dss.plot import get_plotter + self._plotter = get_plotter(self) + + return self._plotter @property def AdvancedTypes(self) -> bool: @@ -515,24 +542,26 @@ def AdvancedTypes(self) -> bool: When disabled, the legacy plain arrays are used and complex numbers cannot be consumed by the Python API. *Defaults to **False** for backwards compatibility.* + + **Deprecated:** Use `Settings.AdvancedTypes` instead (same behavior, the setting was just moved there for better organization). **(API Extension)** ''' - arr_dim = self._check_for_error(self._lib.DSS_Get_EnableArrayDimensions()) != 0 - allow_complex = self._api_util._allow_complex - return arr_dim and allow_complex + return self._lib.advanced_types @AdvancedTypes.setter def AdvancedTypes(self, Value: bool): - self._check_for_error(self._lib.DSS_Set_EnableArrayDimensions(Value)) - self._api_util._allow_complex = bool(Value) + warnings.warn('"AdvancedTypes" was moved to the Settings interface. This property still works, but will be removed in a future release. Please use `...Settings.AdvancedTypes` instead.', DeprecationWarning, stacklevel=2) + self._lib.advanced_types = bool(Value) @property def CompatFlags(self) -> int: ''' - Controls some compatibility flags introduced to toggle some behavior from the official OpenDSS. + Controls some compatibility flags introduced to toggle some behavior from EPRI's OpenDSS. - **THE FLAGS ARE GLOBAL, affecting all DSS engines in the process.** + **THE FLAGS ARE GLOBAL, affecting all AltDSS engines in the process.** + CompatFlags for Oddie-loaded instances (OpenDSS and OpenDSS-C engines) are handled by the Oddie code itself, + so it is global for each Oddie library. These flags may change for each version of DSS C-API, but the same value will not be reused. That is, when we remove a compatibility flag, it will have no effect but will also not affect anything else @@ -544,10 +573,48 @@ def CompatFlags(self) -> int: See the enumeration `DSSCompatFlags` for available flags, including description. + **Deprecated:** Use `Settings.CompatFlags` instead (same behavior, the setting was just moved there for better organization). + **(API Extension)** ''' - return self._check_for_error(self._lib.DSS_Get_CompatFlags()) + return self._lib.DSS_Get_CompatFlags() @CompatFlags.setter def CompatFlags(self, Value: int): - self._check_for_error(self._lib.DSS_Set_CompatFlags(Value)) + warnings.warn('"CompatFlags" was moved to the Settings interface. This property still works, but will be removed in a future release. Please use `...Settings.CompatFlags` instead.', DeprecationWarning, stacklevel=2) + self._lib.DSS_Set_CompatFlags(Value) + + + def ShareGeneral(self, otherContext: IDSS, skip_cmds: Optional[List[str]] = None, skip_file_regexp: str = None): + ''' + Share general DSS objects from this AltDSS context to another. + + **WARNING:** currently, the pointers are not tracked! The user must ensure this context + and its objects are kept alive while other contexts require it. + + Optionally, as a shortcut, the user can provide `skip_cmds` to be passed to the `Settings.SkipCommands` + and `skip_file_regexp` to be passed to `Settings.SkipFileRegExp`, in the second DSS context. + + *Note*: If the `clear` command is included in `Settings.SkipCommands`, the `DSS.ClearAll()` method can still be called + and it will reset both skip settings. + + ***EXPERIMENTAL*** + + **(API Extension)** + ''' + if self._api_util._is_oddie or otherContext._api_util._is_oddie: + raise ValueError("Only AltDSS engine contexts can share data.") + + self._lib.ctx_ShareGeneral(otherContext._api_util.ctx) + if skip_cmds is not None: + otherContext.ActiveCircuit.Settings.SkipCommands = skip_cmds + + if skip_file_regexp is not None: + otherContext.ActiveCircuit.Settings.SkipFileRegExp = skip_file_regexp + + @property + def Settings(self): + ''' + For convenience, a shortcut to `ActiveCircuit.Settings`. + ''' + return self.ActiveCircuit.Settings diff --git a/dss/IDSSElement.py b/dss/IDSSElement.py index a123f7db..b69dd233 100644 --- a/dss/IDSSElement.py +++ b/dss/IDSSElement.py @@ -1,9 +1,10 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors +from __future__ import annotations from ._cffi_api_util import Base from .IDSSProperty import IDSSProperty -from typing import List +from typing import List, Optional from .enums import DSSJSONFlags class IDSSElement(Base): @@ -28,7 +29,7 @@ def AllPropertyNames(self) -> List[str]: Original COM help: https://opendss.epri.com/AllPropertyNames1.html ''' - return self._check_for_error(self._get_string_array(self._lib.DSSElement_Get_AllPropertyNames)) + return self._lib.DSSElement_Get_AllPropertyNames() @property def Name(self) -> str: @@ -37,7 +38,7 @@ def Name(self) -> str: Original COM help: https://opendss.epri.com/Name5.html ''' - return self._get_string(self._check_for_error(self._lib.DSSElement_Get_Name())) + return self._lib.DSSElement_Get_Name() @property def NumProperties(self) -> int: @@ -46,7 +47,7 @@ def NumProperties(self) -> int: Original COM help: https://opendss.epri.com/NumProperties1.html ''' - return self._check_for_error(self._lib.DSSElement_Get_NumProperties()) + return self._lib.DSSElement_Get_NumProperties() def ToJSON(self, options: DSSJSONFlags = 0) -> str: ''' @@ -57,4 +58,18 @@ def ToJSON(self, options: DSSJSONFlags = 0) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.DSSElement_ToJSON(options))) + return self._lib.DSSElement_ToJSON(options) + + + def to_altdss(self) -> Optional[DSSObject]: + ''' + Returns a Python object for the current active DSS object in this interface. + + Requires AltDSS-Python. + + *Available only for the AltDSS engine.* + + **(API Extension)** + ''' + ptr = self._api_util._lib.DSSElement_Get_Pointer() + return self._api_util.get_dss_obj(ptr) diff --git a/dss/IDSSEvents.py b/dss/IDSSEvents.py index 90a03bf2..235ebfe1 100644 --- a/dss/IDSSEvents.py +++ b/dss/IDSSEvents.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base, DSSException from .enums import AltDSSEvent from dss_python_backend.events import get_manager_for_ctx @@ -63,7 +63,7 @@ def disconnect(self): class IDSSEvents(Base): """ This interface provides connection to classic the OpenDSS Events - API. For official OpenDSS documentation about this feature, see + API. For EPRI's OpenDSS documentation about this feature, see the document titled "Evaluation of Distribution Reconfiguration Functions in Advanced Distribution Management Systems, Example Assessments of Distribution Automation Using Open Distribution Systems Simulator" (2011), which is available from @@ -73,7 +73,7 @@ class IDSSEvents(Base): VBA/Excel examples of the classic COM usage are found in the folder ["Examples/civinlar model/"](https://sourceforge.net/p/electricdss/code/HEAD/tree/trunk/Version8/Distrib/Examples/civinlar%20model/) ([mirrored here](https://github.com/dss-extensions/electricdss-tst/tree/master/Version8/Distrib/Examples/civinlar%20model), - with minor changes), which is distributed along with the official OpenDSS. + with minor changes), which is distributed along with the EPRI's OpenDSS distribution. For a quick intro, this interface allows connecting an object (event handler) that runs custom actions are three points of the solution process diff --git a/dss/IDSSProgress.py b/dss/IDSSProgress.py index 8a102b50..51464cc4 100644 --- a/dss/IDSSProgress.py +++ b/dss/IDSSProgress.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base from typing import AnyStr @@ -8,15 +8,27 @@ class IDSSProgress(Base): __slots__ = [] def Close(self): - self._check_for_error(self._lib.DSSProgress_Close()) + ''' + Close progress form + + Typically used with EPRI's OpenDSS, on Windows. Otherwise, it could be a no-op. + ''' + self._lib.DSSProgress_Close() def Show(self): - self._check_for_error(self._lib.DSSProgress_Show()) + ''' + Show progress form + + Typically used with EPRI's OpenDSS, on Windows. Otherwise, it could be a no-op. + ''' + self._lib.DSSProgress_Show() @property def Caption(self) -> str: ''' - (write-only) Caption to appear on the bottom of the DSS Progress form. + Set the caption to appear on the bottom of the DSS Progress form. + + Typically used with EPRI's OpenDSS, on Windows. Otherwise, it could be a no-op. Original COM help: https://opendss.epri.com/Caption.html ''' @@ -24,15 +36,14 @@ def Caption(self) -> str: @Caption.setter def Caption(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.DSSProgress_Set_Caption(Value)) + self._lib.DSSProgress_Set_Caption(Value) @property def PctProgress(self) -> int: ''' - (write-only) Percent progress to indicate [0..100] + Set the percent progress to indicate [0..100] on the progress form. + + Typically used with EPRI's OpenDSS, on Windows. Otherwise, it could be a no-op. Original COM help: https://opendss.epri.com/PctProgress.html ''' @@ -40,6 +51,6 @@ def PctProgress(self) -> int: @PctProgress.setter def PctProgress(self, Value: int): - self._check_for_error(self._lib.DSSProgress_Set_PctProgress(Value)) + self._lib.DSSProgress_Set_PctProgress(Value) diff --git a/dss/IDSSProperty.py b/dss/IDSSProperty.py index cecda5ed..a581a53d 100644 --- a/dss/IDSSProperty.py +++ b/dss/IDSSProperty.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from __future__ import annotations from ._cffi_api_util import Base from typing import AnyStr, Union @@ -21,7 +21,7 @@ def Description(self) -> str: Original COM help: https://opendss.epri.com/Description.html ''' - return self._get_string(self._check_for_error(self._lib.DSSProperty_Get_Description())) + return self._lib.DSSProperty_Get_Description() @property def Name(self) -> str: @@ -30,7 +30,7 @@ def Name(self) -> str: Original COM help: https://opendss.epri.com/Name6.html ''' - return self._get_string(self._check_for_error(self._lib.DSSProperty_Get_Name())) + return self._lib.DSSProperty_Get_Name() @property def Val(self) -> str: @@ -39,23 +39,17 @@ def Val(self) -> str: Original COM help: https://opendss.epri.com/Val.html ''' - return self._get_string(self._check_for_error(self._lib.DSSProperty_Get_Val())) + return self._lib.DSSProperty_Get_Val() @Val.setter def Val(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = str(Value).encode(self._api_util.codec) - - self._check_for_error(self._lib.DSSProperty_Set_Val(Value)) + self._lib.DSSProperty_Set_Val(Value) def __getitem__(self, propname_index: Union[AnyStr, int]) -> IDSSProperty: if isinstance(propname_index, int): - self._check_for_error(self._lib.DSSProperty_Set_Index(propname_index)) + self._lib.DSSProperty_Set_Index(propname_index) else: - if not isinstance(propname_index, bytes): - propname_index = propname_index.encode(self._api_util.codec) - - self._check_for_error(self._lib.DSSProperty_Set_Name(propname_index)) + self._lib.DSSProperty_Set_Name(propname_index) return self diff --git a/dss/IDSS_Executive.py b/dss/IDSS_Executive.py index 882fa79f..5c3f5bc7 100644 --- a/dss/IDSS_Executive.py +++ b/dss/IDSS_Executive.py @@ -1,13 +1,13 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base class IDSS_Executive(Base): __slots__ = [] _columns = [ - 'NumCommands', + 'NumCommands', 'NumOptions', ] @@ -17,7 +17,7 @@ def Command(self, i: int) -> str: Original COM help: https://opendss.epri.com/Command.html ''' - return self._get_string(self._check_for_error(self._lib.DSS_Executive_Get_Command(i))) + return self._lib.DSS_Executive_Get_Command(i) def CommandHelp(self, i: int) -> str: ''' @@ -25,7 +25,7 @@ def CommandHelp(self, i: int) -> str: Original COM help: https://opendss.epri.com/CommandHelp.html ''' - return self._get_string(self._check_for_error(self._lib.DSS_Executive_Get_CommandHelp(i))) + return self._lib.DSS_Executive_Get_CommandHelp(i) def Option(self, i: int) -> str: ''' @@ -33,7 +33,7 @@ def Option(self, i: int) -> str: Original COM help: https://opendss.epri.com/Option.html ''' - return self._get_string(self._check_for_error(self._lib.DSS_Executive_Get_Option(i))) + return self._lib.DSS_Executive_Get_Option(i) def OptionHelp(self, i: int) -> str: ''' @@ -41,7 +41,7 @@ def OptionHelp(self, i: int) -> str: Original COM help: https://opendss.epri.com/OptionHelp.html ''' - return self._get_string(self._check_for_error(self._lib.DSS_Executive_Get_OptionHelp(i))) + return self._lib.DSS_Executive_Get_OptionHelp(i) def OptionValue(self, i: int) -> str: ''' @@ -49,7 +49,7 @@ def OptionValue(self, i: int) -> str: Original COM help: https://opendss.epri.com/OptionValue.html ''' - return self._get_string(self._check_for_error(self._lib.DSS_Executive_Get_OptionValue(i))) + return self._lib.DSS_Executive_Get_OptionValue(i) @property def NumCommands(self) -> int: @@ -58,7 +58,7 @@ def NumCommands(self) -> int: Original COM help: https://opendss.epri.com/NumCommands.html ''' - return self._check_for_error(self._lib.DSS_Executive_Get_NumCommands()) + return self._lib.DSS_Executive_Get_NumCommands() @property def NumOptions(self) -> int: @@ -67,5 +67,5 @@ def NumOptions(self) -> int: Original COM help: https://opendss.epri.com/NumOptions.html ''' - return self._check_for_error(self._lib.DSS_Executive_Get_NumOptions()) + return self._lib.DSS_Executive_Get_NumOptions() diff --git a/dss/IDSSimComs.py b/dss/IDSSimComs.py deleted file mode 100644 index abcf2533..00000000 --- a/dss/IDSSimComs.py +++ /dev/null @@ -1,18 +0,0 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors -from ._cffi_api_util import Base -from ._types import Float64Array - -class IDSSimComs(Base): - __slots__ = [] - - def BusVoltage(self, Index: int) -> Float64Array: - self._check_for_error(self._lib.DSSimComs_BusVoltage_GR(Index)) - return self._get_float64_gr_array() - - def BusVoltagepu(self, Index: int) -> Float64Array: - self._check_for_error(self._lib.DSSimComs_BusVoltagepu_GR(Index)) - return self._get_float64_gr_array() - - diff --git a/dss/IError.py b/dss/IError.py index 97b1b227..49a4d5f5 100644 --- a/dss/IError.py +++ b/dss/IError.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base class IError(Base): @@ -19,7 +19,7 @@ def Description(self) -> str: Original COM help: https://opendss.epri.com/Description1.html ''' - return self._get_string(self._lib.Error_Get_Description()) + return self._lib.Error_Get_Description() @property def Number(self) -> int: @@ -37,7 +37,7 @@ def EarlyAbort(self) -> bool: **(API Extension)** ''' - return self._lib.Error_Get_EarlyAbort() != 0 + return self._lib.Error_Get_EarlyAbort() @EarlyAbort.setter def EarlyAbort(self, Value: bool): @@ -50,7 +50,7 @@ def ExtendedErrors(self) -> bool: Extended errors are errors derived from checks across the API to ensure a valid state. Although many of these checks are already present in the - original/official COM interface, the checks do not produce any error + original/EPRI's COM interface, the checks do not produce any error message. An error value can be returned by a function but this value can, for many of the functions, be a valid value. As such, the user has no means to detect an invalid API call. @@ -66,7 +66,7 @@ def ExtendedErrors(self) -> bool: **(API Extension)** ''' - return self._lib.Error_Get_ExtendedErrors() != 0 + return self._lib.Error_Get_ExtendedErrors() @ExtendedErrors.setter def ExtendedErrors(self, Value: bool): @@ -77,23 +77,28 @@ def UseExceptions(self) -> bool: """ Controls whether the automatic error checking mechanism is enable, i.e., if the DSS engine errors (from the `Error` interface) are mapped exception when - detected. - + detected. + **When disabled, the user takes responsibility for checking for errors.** This can be done through the `Error` interface. When `Error.Number` is not zero, there should be an error message in `Error.Description`. This is compatible - with the behavior on the official OpenDSS (Windows-only COM implementation) when + with the behavior on EPRI's OpenDSS (Windows-only COM implementation) when `AllowForms` is disabled. Users can also use the DSS command `Export ErrorLog` to inspect for errors. - **WARNING:** This is a global setting, affects all DSS instances from DSS-Python, - OpenDSSDirect.py and AltDSS. + With EPRI's OpenDSS engines, in contrast to our main AltDSS engine, users are + also required to set `AllowForms` to `False`, otherwise the engine does not + populate the Error.Number API and the error is consumed by the popup form or + terminal message. + + **NOTE:** this used to be a global settings. Since DSS-Python v0.16.0, + it only affects the target instance. **(API Extension)** """ - return Base._use_exceptions - + return self._lib.using_exceptions + @UseExceptions.setter def UseExceptions(self, value: bool): - Base._enable_exceptions(value) + self._lib.using_exceptions = value diff --git a/dss/IFuses.py b/dss/IFuses.py index f2f3a26e..9452074b 100644 --- a/dss/IFuses.py +++ b/dss/IFuses.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import List, AnyStr @@ -31,7 +31,7 @@ def Close(self): Original COM help: https://opendss.epri.com/Close3.html ''' - self._check_for_error(self._lib.Fuses_Close()) + self._lib.Fuses_Close() def IsBlown(self) -> bool: ''' @@ -39,7 +39,7 @@ def IsBlown(self) -> bool: Original COM help: https://opendss.epri.com/IsBlown.html ''' - return self._check_for_error(self._lib.Fuses_IsBlown()) != 0 + return self._lib.Fuses_IsBlown() def Open(self): ''' @@ -47,7 +47,7 @@ def Open(self): Original COM help: https://opendss.epri.com/Open2.html ''' - self._check_for_error(self._lib.Fuses_Open()) + self._lib.Fuses_Open() def Reset(self): ''' @@ -55,7 +55,7 @@ def Reset(self): Original COM help: https://opendss.epri.com/Reset7.html ''' - self._check_for_error(self._lib.Fuses_Reset()) + self._lib.Fuses_Reset() @property def Delay(self) -> float: @@ -65,11 +65,11 @@ def Delay(self) -> float: Original COM help: https://opendss.epri.com/Delay1.html ''' - return self._check_for_error(self._lib.Fuses_Get_Delay()) + return self._lib.Fuses_Get_Delay() @Delay.setter def Delay(self, Value: float): - self._check_for_error(self._lib.Fuses_Set_Delay(Value)) + self._lib.Fuses_Set_Delay(Value) @property def MonitoredObj(self) -> str: @@ -78,14 +78,11 @@ def MonitoredObj(self) -> str: Original COM help: https://opendss.epri.com/MonitoredObj1.html ''' - return self._get_string(self._check_for_error(self._lib.Fuses_Get_MonitoredObj())) + return self._lib.Fuses_Get_MonitoredObj() @MonitoredObj.setter def MonitoredObj(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Fuses_Set_MonitoredObj(Value)) + self._lib.Fuses_Set_MonitoredObj(Value) @property def MonitoredTerm(self) -> int: @@ -94,11 +91,11 @@ def MonitoredTerm(self) -> int: Original COM help: https://opendss.epri.com/MonitoredTerm1.html ''' - return self._check_for_error(self._lib.Fuses_Get_MonitoredTerm()) + return self._lib.Fuses_Get_MonitoredTerm() @MonitoredTerm.setter def MonitoredTerm(self, Value: int): - self._check_for_error(self._lib.Fuses_Set_MonitoredTerm(Value)) + self._lib.Fuses_Set_MonitoredTerm(Value) @property def NumPhases(self) -> int: @@ -107,7 +104,7 @@ def NumPhases(self) -> int: Original COM help: https://opendss.epri.com/NumPhases1.html ''' - return self._check_for_error(self._lib.Fuses_Get_NumPhases()) + return self._lib.Fuses_Get_NumPhases() @property def RatedCurrent(self) -> float: @@ -118,11 +115,11 @@ def RatedCurrent(self) -> float: Original COM help: https://opendss.epri.com/RatedCurrent.html ''' - return self._check_for_error(self._lib.Fuses_Get_RatedCurrent()) + return self._lib.Fuses_Get_RatedCurrent() @RatedCurrent.setter def RatedCurrent(self, Value: float): - self._check_for_error(self._lib.Fuses_Set_RatedCurrent(Value)) + self._lib.Fuses_Set_RatedCurrent(Value) @property def SwitchedObj(self) -> str: @@ -132,14 +129,11 @@ def SwitchedObj(self) -> str: Original COM help: https://opendss.epri.com/SwitchedObj.html ''' - return self._get_string(self._check_for_error(self._lib.Fuses_Get_SwitchedObj())) + return self._lib.Fuses_Get_SwitchedObj() @SwitchedObj.setter def SwitchedObj(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Fuses_Set_SwitchedObj(Value)) + self._lib.Fuses_Set_SwitchedObj(Value) @property def SwitchedTerm(self) -> int: @@ -148,11 +142,11 @@ def SwitchedTerm(self) -> int: Original COM help: https://opendss.epri.com/SwitchedTerm.html ''' - return self._check_for_error(self._lib.Fuses_Get_SwitchedTerm()) + return self._lib.Fuses_Get_SwitchedTerm() @SwitchedTerm.setter def SwitchedTerm(self, Value: int): - self._check_for_error(self._lib.Fuses_Set_SwitchedTerm(Value)) + self._lib.Fuses_Set_SwitchedTerm(Value) @property def TCCcurve(self) -> str: @@ -161,14 +155,11 @@ def TCCcurve(self) -> str: Original COM help: https://opendss.epri.com/TCCcurve.html ''' - return self._get_string(self._check_for_error(self._lib.Fuses_Get_TCCcurve())) + return self._lib.Fuses_Get_TCCcurve() @TCCcurve.setter def TCCcurve(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Fuses_Set_TCCcurve(Value)) + self._lib.Fuses_Set_TCCcurve(Value) @property def State(self) -> List[str]: @@ -177,11 +168,11 @@ def State(self) -> List[str]: Original COM help: https://opendss.epri.com/State2.html ''' - return self._check_for_error(self._get_string_array(self._lib.Fuses_Get_State)) + return self._lib.Fuses_Get_State() @State.setter def State(self, Value: List[AnyStr]): - self._check_for_error(self._set_string_array(self._lib.Fuses_Set_State, Value)) + self._set_string_array(self._lib.Fuses_Set_State, Value) @property def NormalState(self) -> List[str]: @@ -190,8 +181,8 @@ def NormalState(self) -> List[str]: Original COM help: https://opendss.epri.com/NormalState2.html ''' - return self._check_for_error(self._get_string_array(self._lib.Fuses_Get_NormalState)) + return self._lib.Fuses_Get_NormalState() @NormalState.setter def NormalState(self, Value: List[AnyStr]): - self._check_for_error(self._set_string_array(self._lib.Fuses_Set_NormalState, Value)) + self._set_string_array(self._lib.Fuses_Set_NormalState, Value) diff --git a/dss/IGICSources.py b/dss/IGICSources.py index 44f2e3a4..1742355b 100644 --- a/dss/IGICSources.py +++ b/dss/IGICSources.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2023-2024 Paulo Meira -# Copyright (c) 2023-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2023-2025 Paulo Meira +# Copyright (c) 2023-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable class IGICSources(Iterable): @@ -25,81 +25,81 @@ class IGICSources(Iterable): @property def Bus1(self) -> str: '''First bus name of GICSource (Created name)''' - return self._get_string(self._check_for_error(self._lib.GICSources_Get_Bus1())) + return self._lib.GICSources_Get_Bus1() @property def Bus2(self) -> str: '''Second bus name''' - return self._get_string(self._check_for_error(self._lib.GICSources_Get_Bus2())) + return self._lib.GICSources_Get_Bus2() @property def Phases(self) -> int: '''Number of Phases, this GICSource element.''' - return self._check_for_error(self._lib.GICSources_Get_Phases()) + return self._lib.GICSources_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.GICSources_Set_Phases(Value)) + self._lib.GICSources_Set_Phases(Value) @property def EN(self) -> float: '''Northward E Field V/km''' - return self._check_for_error(self._lib.GICSources_Get_EN()) + return self._lib.GICSources_Get_EN() @EN.setter def EN(self, Value: float): - self._check_for_error(self._lib.GICSources_Set_EN(Value)) + self._lib.GICSources_Set_EN(Value) @property def EE(self) -> float: '''Eastward E Field, V/km''' - return self._check_for_error(self._lib.GICSources_Get_EE()) + return self._lib.GICSources_Get_EE() @EE.setter def EE(self, Value: float): - self._check_for_error(self._lib.GICSources_Set_EE(Value)) + self._lib.GICSources_Set_EE(Value) @property def Lat1(self) -> float: '''Latitude of Bus1 (degrees)''' - return self._check_for_error(self._lib.GICSources_Get_Lat1()) + return self._lib.GICSources_Get_Lat1() @Lat1.setter def Lat1(self, Value: float): - self._check_for_error(self._lib.GICSources_Set_Lat1(Value)) + self._lib.GICSources_Set_Lat1(Value) @property def Lat2(self) -> float: '''Latitude of Bus2 (degrees)''' - return self._check_for_error(self._lib.GICSources_Get_Lat2()) + return self._lib.GICSources_Get_Lat2() @Lat2.setter def Lat2(self, Value: float): - self._check_for_error(self._lib.GICSources_Set_Lat2(Value)) + self._lib.GICSources_Set_Lat2(Value) @property def Lon1(self) -> float: '''Longitude of Bus1 (Degrees)''' - return self._check_for_error(self._lib.GICSources_Get_Lon1()) + return self._lib.GICSources_Get_Lon1() @Lon1.setter def Lon1(self, Value: float): - self._check_for_error(self._lib.GICSources_Set_Lon1(Value)) + self._lib.GICSources_Set_Lon1(Value) @property def Lon2(self) -> float: '''Longitude of Bus2 (Degrees)''' - return self._check_for_error(self._lib.GICSources_Get_Lon2()) + return self._lib.GICSources_Get_Lon2() @Lon2.setter def Lon2(self, Value: float): - self._check_for_error(self._lib.GICSources_Set_Lon2(Value)) + self._lib.GICSources_Set_Lon2(Value) @property def Volts(self) -> float: '''Specify dc voltage directly''' - return self._check_for_error(self._lib.GICSources_Get_Volts()) + return self._lib.GICSources_Get_Volts() @Volts.setter def Volts(self, Value: float): - self._check_for_error(self._lib.GICSources_Set_Volts(Value)) + self._lib.GICSources_Set_Volts(Value) diff --git a/dss/IGenerators.py b/dss/IGenerators.py index ae1fabd5..15a24666 100644 --- a/dss/IGenerators.py +++ b/dss/IGenerators.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import List, AnyStr, Union from ._types import Float64Array @@ -42,11 +42,11 @@ def ForcedON(self) -> bool: Original COM help: https://opendss.epri.com/ForcedON.html ''' - return self._check_for_error(self._lib.Generators_Get_ForcedON()) != 0 + return self._lib.Generators_Get_ForcedON() @ForcedON.setter def ForcedON(self, Value: bool): - self._check_for_error(self._lib.Generators_Set_ForcedON(Value)) + self._lib.Generators_Set_ForcedON(Value) @property def Model(self) -> int: @@ -55,11 +55,11 @@ def Model(self) -> int: Original COM help: https://opendss.epri.com/Model.html ''' - return self._check_for_error(self._lib.Generators_Get_Model()) #TODO: use enum + return self._lib.Generators_Get_Model() #TODO: use enum @Model.setter def Model(self, Value: int): - self._check_for_error(self._lib.Generators_Set_Model(Value)) + self._lib.Generators_Set_Model(Value) @property def PF(self) -> float: @@ -68,11 +68,11 @@ def PF(self) -> float: Original COM help: https://opendss.epri.com/PF.html ''' - return self._check_for_error(self._lib.Generators_Get_PF()) + return self._lib.Generators_Get_PF() @PF.setter def PF(self, Value: float): - self._check_for_error(self._lib.Generators_Set_PF(Value)) + self._lib.Generators_Set_PF(Value) @property def Phases(self) -> int: @@ -81,11 +81,11 @@ def Phases(self) -> int: Original COM help: https://opendss.epri.com/Phases.html ''' - return self._check_for_error(self._lib.Generators_Get_Phases()) + return self._lib.Generators_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.Generators_Set_Phases(Value)) + self._lib.Generators_Set_Phases(Value) @property def RegisterNames(self) -> List[str]: @@ -94,7 +94,7 @@ def RegisterNames(self) -> List[str]: See also the enum `GeneratorRegisters`. ''' - return self._check_for_error(self._get_string_array(self._lib.Generators_Get_RegisterNames)) + return self._lib.Generators_Get_RegisterNames() @property def RegisterValues(self) -> Float64Array: @@ -103,8 +103,7 @@ def RegisterValues(self) -> Float64Array: Original COM help: https://opendss.epri.com/RegisterValues.html ''' - self._check_for_error(self._lib.Generators_Get_RegisterValues_GR()) - return self._get_float64_gr_array() + return self._lib.Generators_Get_RegisterValues_GR() @property def Vmaxpu(self) -> float: @@ -113,11 +112,11 @@ def Vmaxpu(self) -> float: Original COM help: https://opendss.epri.com/Vmaxpu.html ''' - return self._check_for_error(self._lib.Generators_Get_Vmaxpu()) + return self._lib.Generators_Get_Vmaxpu() @Vmaxpu.setter def Vmaxpu(self, Value: float): - self._check_for_error(self._lib.Generators_Set_Vmaxpu(Value)) + self._lib.Generators_Set_Vmaxpu(Value) @property def Vminpu(self) -> float: @@ -126,11 +125,11 @@ def Vminpu(self) -> float: Original COM help: https://opendss.epri.com/Vminpu.html ''' - return self._check_for_error(self._lib.Generators_Get_Vminpu()) + return self._lib.Generators_Get_Vminpu() @Vminpu.setter def Vminpu(self, Value: float): - self._check_for_error(self._lib.Generators_Set_Vminpu(Value)) + self._lib.Generators_Set_Vminpu(Value) @property def kV(self) -> float: @@ -139,11 +138,11 @@ def kV(self) -> float: Original COM help: https://opendss.epri.com/kV1.html ''' - return self._check_for_error(self._lib.Generators_Get_kV()) + return self._lib.Generators_Get_kV() @kV.setter def kV(self, Value: float): - self._check_for_error(self._lib.Generators_Set_kV(Value)) + self._lib.Generators_Set_kV(Value) @property def kVArated(self) -> float: @@ -152,11 +151,11 @@ def kVArated(self) -> float: Original COM help: https://opendss.epri.com/kVArated.html ''' - return self._check_for_error(self._lib.Generators_Get_kVArated()) + return self._lib.Generators_Get_kVArated() @kVArated.setter def kVArated(self, Value: float): - self._check_for_error(self._lib.Generators_Set_kVArated(Value)) + self._lib.Generators_Set_kVArated(Value) @property def kW(self) -> float: @@ -165,11 +164,11 @@ def kW(self) -> float: Original COM help: https://opendss.epri.com/kW.html ''' - return self._check_for_error(self._lib.Generators_Get_kW()) + return self._lib.Generators_Get_kW() @kW.setter def kW(self, Value: float): - self._check_for_error(self._lib.Generators_Set_kW(Value)) + self._lib.Generators_Set_kW(Value) @property def kvar(self) -> float: @@ -178,11 +177,11 @@ def kvar(self) -> float: Original COM help: https://opendss.epri.com/kvar.html ''' - return self._check_for_error(self._lib.Generators_Get_kvar()) + return self._lib.Generators_Get_kvar() @kvar.setter def kvar(self, Value: float): - self._check_for_error(self._lib.Generators_Set_kvar(Value)) + self._lib.Generators_Set_kvar(Value) @property def daily(self) -> str: @@ -191,14 +190,11 @@ def daily(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.Generators_Get_daily())) + return self._lib.Generators_Get_daily() @daily.setter def daily(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Generators_Set_daily(Value)) + self._lib.Generators_Set_daily(Value) @property def duty(self) -> str: @@ -207,14 +203,11 @@ def duty(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.Generators_Get_duty())) + return self._lib.Generators_Get_duty() @duty.setter def duty(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Generators_Set_duty(Value)) + self._lib.Generators_Set_duty(Value) @property def Yearly(self) -> str: @@ -223,14 +216,11 @@ def Yearly(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.Generators_Get_Yearly())) + return self._lib.Generators_Get_Yearly() @Yearly.setter def Yearly(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Generators_Set_Yearly(Value)) + self._lib.Generators_Set_Yearly(Value) @property def Status(self) -> GeneratorStatus: @@ -241,11 +231,11 @@ def Status(self) -> GeneratorStatus: **(API Extension)** ''' - return GeneratorStatus(self._check_for_error(self._lib.Generators_Get_Status())) + return GeneratorStatus(self._lib.Generators_Get_Status()) @Status.setter def Status(self, Value: Union[int, GeneratorStatus]): - self._check_for_error(self._lib.Generators_Set_Status(Value)) + self._lib.Generators_Set_Status(Value) @property def IsDelta(self) -> bool: @@ -254,11 +244,11 @@ def IsDelta(self) -> bool: **(API Extension)** ''' - return self._check_for_error(self._lib.Generators_Get_IsDelta()) != 0 + return self._lib.Generators_Get_IsDelta() @IsDelta.setter def IsDelta(self, Value: bool): - self._check_for_error(self._lib.Generators_Set_IsDelta(Value)) + self._lib.Generators_Set_IsDelta(Value) @property def kva(self) -> float: @@ -267,24 +257,25 @@ def kva(self) -> float: **(API Extension)** ''' - return self._check_for_error(self._lib.Generators_Get_kva()) + return self._lib.Generators_Get_kva() @kva.setter def kva(self, Value: float): - self._check_for_error(self._lib.Generators_Set_kva(Value)) + self._lib.Generators_Set_kva(Value) @property def Class(self) -> int: ''' An arbitrary integer number representing the class of Generator so that Generator values may be segregated by class. + No effect on the solution. **(API Extension)** ''' - return self._check_for_error(self._lib.Generators_Get_Class_()) + return self._lib.Generators_Get_Class_() @Class.setter def Class(self, Value: int): - self._check_for_error(self._lib.Generators_Set_Class_(Value)) + self._lib.Generators_Set_Class_(Value) @property def Bus1(self) -> str: @@ -293,12 +284,9 @@ def Bus1(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.Generators_Get_Bus1())) + return self._lib.Generators_Get_Bus1() @Bus1.setter def Bus1(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Generators_Set_Bus1(Value)) + self._lib.Generators_Set_Bus1(Value) diff --git a/dss/IISources.py b/dss/IISources.py index 52fa7159..f4e9c699 100644 --- a/dss/IISources.py +++ b/dss/IISources.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable class IISources(Iterable): @@ -22,11 +22,11 @@ def Amps(self) -> float: Original COM help: https://opendss.epri.com/Amps.html ''' - return self._check_for_error(self._lib.ISources_Get_Amps()) + return self._lib.ISources_Get_Amps() @Amps.setter def Amps(self, Value: float): - self._check_for_error(self._lib.ISources_Set_Amps(Value)) + self._lib.ISources_Set_Amps(Value) @property def AngleDeg(self) -> float: @@ -35,11 +35,11 @@ def AngleDeg(self) -> float: Original COM help: https://opendss.epri.com/AngleDeg.html ''' - return self._check_for_error(self._lib.ISources_Get_AngleDeg()) + return self._lib.ISources_Get_AngleDeg() @AngleDeg.setter def AngleDeg(self, Value: float): - self._check_for_error(self._lib.ISources_Set_AngleDeg(Value)) + self._lib.ISources_Set_AngleDeg(Value) @property def Frequency(self) -> float: @@ -48,9 +48,9 @@ def Frequency(self) -> float: Original COM help: https://opendss.epri.com/Frequency.html ''' - return self._check_for_error(self._lib.ISources_Get_Frequency()) + return self._lib.ISources_Get_Frequency() @Frequency.setter def Frequency(self, Value: float): - self._check_for_error(self._lib.ISources_Set_Frequency(Value)) + self._lib.ISources_Set_Frequency(Value) \ No newline at end of file diff --git a/dss/ILineCodes.py b/dss/ILineCodes.py index ee126443..c60654e6 100644 --- a/dss/ILineCodes.py +++ b/dss/ILineCodes.py @@ -1,8 +1,8 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable -from ._types import Float64Array +from ._types import Float64Matrix from typing import Union from .enums import LineUnits @@ -35,11 +35,11 @@ def C0(self): Original COM help: https://opendss.epri.com/C2.html ''' - return self._check_for_error(self._lib.LineCodes_Get_C0()) + return self._lib.LineCodes_Get_C0() @C0.setter def C0(self, Value): - self._check_for_error(self._lib.LineCodes_Set_C0(Value)) + self._lib.LineCodes_Set_C0(Value) @property def C1(self): @@ -48,26 +48,25 @@ def C1(self): Original COM help: https://opendss.epri.com/C3.html ''' - return self._check_for_error(self._lib.LineCodes_Get_C1()) + return self._lib.LineCodes_Get_C1() @C1.setter def C1(self, Value): - self._check_for_error(self._lib.LineCodes_Set_C1(Value)) + self._lib.LineCodes_Set_C1(Value) @property - def Cmatrix(self) -> Float64Array: + def Cmatrix(self) -> Float64Matrix: ''' Capacitance matrix, nF per unit length Original COM help: https://opendss.epri.com/Cmatrix1.html ''' - self._check_for_error(self._lib.LineCodes_Get_Cmatrix_GR()) - return self._get_float64_gr_array() + return self._lib.LineCodes_Get_Cmatrix_GR() @Cmatrix.setter - def Cmatrix(self, Value: Float64Array): + def Cmatrix(self, Value: Float64Matrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LineCodes_Set_Cmatrix(ValuePtr, ValueCount)) + self._lib.LineCodes_Set_Cmatrix(ValuePtr, ValueCount) @property def EmergAmps(self) -> float: @@ -76,11 +75,11 @@ def EmergAmps(self) -> float: Original COM help: https://opendss.epri.com/EmergAmps2.html ''' - return self._check_for_error(self._lib.LineCodes_Get_EmergAmps()) + return self._lib.LineCodes_Get_EmergAmps() @EmergAmps.setter def EmergAmps(self, Value: float): - self._check_for_error(self._lib.LineCodes_Set_EmergAmps(Value)) + self._lib.LineCodes_Set_EmergAmps(Value) @property def IsZ1Z0(self) -> bool: @@ -89,7 +88,7 @@ def IsZ1Z0(self) -> bool: Original COM help: https://opendss.epri.com/IsZ1Z0.html ''' - return self._check_for_error(self._lib.LineCodes_Get_IsZ1Z0()) != 0 + return self._lib.LineCodes_Get_IsZ1Z0() @property def NormAmps(self) -> float: @@ -98,11 +97,11 @@ def NormAmps(self) -> float: Original COM help: https://opendss.epri.com/NormAmps1.html ''' - return self._check_for_error(self._lib.LineCodes_Get_NormAmps()) + return self._lib.LineCodes_Get_NormAmps() @NormAmps.setter def NormAmps(self, Value: float): - self._check_for_error(self._lib.LineCodes_Set_NormAmps(Value)) + self._lib.LineCodes_Set_NormAmps(Value) @property def Phases(self) -> int: @@ -111,11 +110,11 @@ def Phases(self) -> int: Original COM help: https://opendss.epri.com/Phases2.html ''' - return self._check_for_error(self._lib.LineCodes_Get_Phases()) + return self._lib.LineCodes_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.LineCodes_Set_Phases(Value)) + self._lib.LineCodes_Set_Phases(Value) @property def R0(self) -> float: @@ -124,11 +123,11 @@ def R0(self) -> float: Original COM help: https://opendss.epri.com/R2.html ''' - return self._check_for_error(self._lib.LineCodes_Get_R0()) + return self._lib.LineCodes_Get_R0() @R0.setter def R0(self, Value: float): - self._check_for_error(self._lib.LineCodes_Set_R0(Value)) + self._lib.LineCodes_Set_R0(Value) @property def R1(self) -> float: @@ -137,34 +136,33 @@ def R1(self) -> float: Original COM help: https://opendss.epri.com/R3.html ''' - return self._check_for_error(self._lib.LineCodes_Get_R1()) + return self._lib.LineCodes_Get_R1() @R1.setter def R1(self, Value: float): - self._check_for_error(self._lib.LineCodes_Set_R1(Value)) + self._lib.LineCodes_Set_R1(Value) @property - def Rmatrix(self) -> Float64Array: + def Rmatrix(self) -> Float64Matrix: ''' Resistance matrix, ohms per unit length Original COM help: https://opendss.epri.com/Rmatrix1.html ''' - self._check_for_error(self._lib.LineCodes_Get_Rmatrix_GR()) - return self._get_float64_gr_array() + return self._lib.LineCodes_Get_Rmatrix_GR() @Rmatrix.setter - def Rmatrix(self, Value: Float64Array): + def Rmatrix(self, Value: Float64Matrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LineCodes_Set_Rmatrix(ValuePtr, ValueCount)) + self._lib.LineCodes_Set_Rmatrix(ValuePtr, ValueCount) @property def Units(self) -> LineUnits: - return LineUnits(self._check_for_error(self._lib.LineCodes_Get_Units())) + return LineUnits(self._lib.LineCodes_Get_Units()) @Units.setter def Units(self, Value: Union[int, LineUnits]): - self._check_for_error(self._lib.LineCodes_Set_Units(Value)) + self._lib.LineCodes_Set_Units(Value) @property def X0(self) -> float: @@ -173,11 +171,11 @@ def X0(self) -> float: Original COM help: https://opendss.epri.com/X2.html ''' - return self._check_for_error(self._lib.LineCodes_Get_X0()) + return self._lib.LineCodes_Get_X0() @X0.setter def X0(self, Value: float): - self._check_for_error(self._lib.LineCodes_Set_X0(Value)) + self._lib.LineCodes_Set_X0(Value) @property def X1(self) -> float: @@ -186,23 +184,22 @@ def X1(self) -> float: Original COM help: https://opendss.epri.com/X3.html ''' - return self._check_for_error(self._lib.LineCodes_Get_X1()) + return self._lib.LineCodes_Get_X1() @X1.setter def X1(self, Value: float): - self._check_for_error(self._lib.LineCodes_Set_X1(Value)) + self._lib.LineCodes_Set_X1(Value) @property - def Xmatrix(self) -> Float64Array: + def Xmatrix(self) -> Float64Matrix: ''' Reactance matrix, ohms per unit length Original COM help: https://opendss.epri.com/Xmatrix1.html ''' - self._check_for_error(self._lib.LineCodes_Get_Xmatrix_GR()) - return self._get_float64_gr_array() + return self._lib.LineCodes_Get_Xmatrix_GR() @Xmatrix.setter - def Xmatrix(self, Value: Float64Array): + def Xmatrix(self, Value: Float64Matrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LineCodes_Set_Xmatrix(ValuePtr, ValueCount)) + self._lib.LineCodes_Set_Xmatrix(ValuePtr, ValueCount) diff --git a/dss/ILineGeometries.py b/dss/ILineGeometries.py index 5208c176..1e8901fb 100644 --- a/dss/ILineGeometries.py +++ b/dss/ILineGeometries.py @@ -1,9 +1,9 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import List, Union -from ._types import Float64Array, Int32Array, Float64ArrayOrComplexArray +from ._types import Float64Array, Float64Matrix, Int32Array, ComplexMatrix from .enums import LineUnits class ILineGeometries(Iterable): @@ -36,108 +36,101 @@ class ILineGeometries(Iterable): @property def Conductors(self) -> List[str]: '''Array of strings with names of all conductors in the active LineGeometry object''' - return self._check_for_error(self._get_string_array(self._lib.LineGeometries_Get_Conductors)) + return self._lib.LineGeometries_Get_Conductors() @property def EmergAmps(self) -> float: '''Emergency ampere rating''' - return self._check_for_error(self._lib.LineGeometries_Get_EmergAmps()) + return self._lib.LineGeometries_Get_EmergAmps() @EmergAmps.setter def EmergAmps(self, Value: float): - self._check_for_error(self._lib.LineGeometries_Set_EmergAmps(Value)) + self._lib.LineGeometries_Set_EmergAmps(Value) @property def NormAmps(self) -> float: '''Normal ampere rating''' - return self._check_for_error(self._lib.LineGeometries_Get_NormAmps()) + return self._lib.LineGeometries_Get_NormAmps() @NormAmps.setter def NormAmps(self, Value: float): - self._check_for_error(self._lib.LineGeometries_Set_NormAmps(Value)) + self._lib.LineGeometries_Set_NormAmps(Value) @property def RhoEarth(self) -> float: - return self._check_for_error(self._lib.LineGeometries_Get_RhoEarth()) + return self._lib.LineGeometries_Get_RhoEarth() @RhoEarth.setter def RhoEarth(self, Value: float): - self._check_for_error(self._lib.LineGeometries_Set_RhoEarth(Value)) + self._lib.LineGeometries_Set_RhoEarth(Value) @property def Reduce(self) -> bool: - return self._check_for_error(self._lib.LineGeometries_Get_Reduce()) != 0 + return self._lib.LineGeometries_Get_Reduce() @Reduce.setter def Reduce(self, Value: bool): - self._check_for_error(self._lib.LineGeometries_Set_Reduce(Value)) + self._lib.LineGeometries_Set_Reduce(Value) @property def Phases(self) -> int: '''Number of Phases''' - return self._check_for_error(self._lib.LineGeometries_Get_Phases()) + return self._lib.LineGeometries_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.LineGeometries_Set_Phases(Value)) + self._lib.LineGeometries_Set_Phases(Value) - def Rmatrix(self, Frequency: float, Length: float, Units: int) -> Float64Array: + def Rmatrix(self, Frequency: float, Length: float, Units: int) -> Float64Matrix: '''Resistance matrix, ohms''' - self._check_for_error(self._lib.LineGeometries_Get_Rmatrix_GR(Frequency, Length, Units)) - return self._get_float64_gr_array() + return self._lib.LineGeometries_Get_Rmatrix_GR(Frequency, Length, Units) - def Xmatrix(self, Frequency: float, Length: float, Units: int) -> Float64Array: + def Xmatrix(self, Frequency: float, Length: float, Units: int) -> Float64Matrix: '''Reactance matrix, ohms''' - self._check_for_error(self._lib.LineGeometries_Get_Xmatrix_GR(Frequency, Length, Units)) - return self._get_float64_gr_array() + return self._lib.LineGeometries_Get_Xmatrix_GR(Frequency, Length, Units) - def Zmatrix(self, Frequency: float, Length: float, Units: int) -> Float64ArrayOrComplexArray: + def Zmatrix(self, Frequency: float, Length: float, Units: int) -> ComplexMatrix: '''Complex impedance matrix, ohms''' - self._check_for_error(self._lib.LineGeometries_Get_Zmatrix_GR(Frequency, Length, Units)) - return self._get_complex128_gr_array() + return self._lib.LineGeometries_Get_Zmatrix_GR(Frequency, Length, Units) def Cmatrix(self, Frequency: float, Length: float, Units: int) -> Float64Array: '''Capacitance matrix, nF''' - self._check_for_error(self._lib.LineGeometries_Get_Cmatrix_GR(Frequency, Length, Units)) - return self._get_float64_gr_array() + return self._lib.LineGeometries_Get_Cmatrix_GR(Frequency, Length, Units) @property def Units(self) -> List[LineUnits]: - self._check_for_error(self._lib.LineGeometries_Get_Units_GR()) - return [LineUnits(unit) for unit in self._get_int32_gr_array()] + return [LineUnits(unit) for unit in self._lib.LineGeometries_Get_Units_GR()] @Units.setter def Units(self, Value: Union[Int32Array, List[LineUnits]]): Value, ValuePtr, ValueCount = self._prepare_int32_array(Value) - self._check_for_error(self._lib.LineGeometries_Set_Units(ValuePtr, ValueCount)) + self._lib.LineGeometries_Set_Units(ValuePtr, ValueCount) @property def Xcoords(self) -> Float64Array: '''Get/Set the X (horizontal) coordinates of the conductors''' - self._check_for_error(self._lib.LineGeometries_Get_Xcoords_GR()) - return self._get_float64_gr_array() + return self._lib.LineGeometries_Get_Xcoords_GR() @Xcoords.setter def Xcoords(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LineGeometries_Set_Xcoords(ValuePtr, ValueCount)) + self._lib.LineGeometries_Set_Xcoords(ValuePtr, ValueCount) @property def Ycoords(self) -> Float64Array: '''Get/Set the Y (vertical/height) coordinates of the conductors''' - self._check_for_error(self._lib.LineGeometries_Get_Ycoords_GR()) - return self._get_float64_gr_array() + return self._lib.LineGeometries_Get_Ycoords_GR() @Ycoords.setter def Ycoords(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LineGeometries_Set_Ycoords(ValuePtr, ValueCount)) + self._lib.LineGeometries_Set_Ycoords(ValuePtr, ValueCount) @property def Nconds(self) -> int: '''Number of conductors in this geometry. Default is 3. Triggers memory allocations. Define first!''' - return self._check_for_error(self._lib.LineGeometries_Get_Nconds()) + return self._lib.LineGeometries_Get_Nconds() @Nconds.setter def Nconds(self, Value: int): - self._check_for_error(self._lib.LineGeometries_Set_Nconds(Value)) + self._lib.LineGeometries_Set_Nconds(Value) diff --git a/dss/ILineSpacings.py b/dss/ILineSpacings.py index 83995488..fad005ef 100644 --- a/dss/ILineSpacings.py +++ b/dss/ILineSpacings.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Float64Array from typing import Union @@ -28,46 +28,44 @@ class ILineSpacings(Iterable): @property def Phases(self) -> int: '''Number of Phases''' - return self._check_for_error(self._lib.LineSpacings_Get_Phases()) + return self._lib.LineSpacings_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.LineSpacings_Set_Phases(Value)) + self._lib.LineSpacings_Set_Phases(Value) @property def Nconds(self) -> int: - return self._check_for_error(self._lib.LineSpacings_Get_Nconds()) + return self._lib.LineSpacings_Get_Nconds() @Nconds.setter def Nconds(self, Value: int): - self._check_for_error(self._lib.LineSpacings_Set_Nconds(Value)) + self._lib.LineSpacings_Set_Nconds(Value) @property def Units(self) -> LineUnits: - return LineUnits(self._check_for_error(self._lib.LineSpacings_Get_Units())) + return LineUnits(self._lib.LineSpacings_Get_Units()) @Units.setter def Units(self, Value: Union[int, LineUnits]): - self._check_for_error(self._lib.LineSpacings_Set_Units(Value)) + self._lib.LineSpacings_Set_Units(Value) @property def Xcoords(self) -> Float64Array: '''Get/Set the X (horizontal) coordinates of the conductors''' - self._check_for_error(self._lib.LineSpacings_Get_Xcoords_GR()) - return self._get_float64_gr_array() + return self._lib.LineSpacings_Get_Xcoords_GR() @Xcoords.setter def Xcoords(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LineSpacings_Set_Xcoords(ValuePtr, ValueCount)) + self._lib.LineSpacings_Set_Xcoords(ValuePtr, ValueCount) @property def Ycoords(self) -> Float64Array: '''Get/Set the Y (vertical/height) coordinates of the conductors''' - self._check_for_error(self._lib.LineSpacings_Get_Ycoords_GR()) - return self._get_float64_gr_array() + return self._lib.LineSpacings_Get_Ycoords_GR() @Ycoords.setter def Ycoords(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LineSpacings_Set_Ycoords(ValuePtr, ValueCount)) + self._lib.LineSpacings_Set_Ycoords(ValuePtr, ValueCount) diff --git a/dss/ILines.py b/dss/ILines.py index 142eb011..9f3d3f58 100644 --- a/dss/ILines.py +++ b/dss/ILines.py @@ -1,8 +1,8 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable -from ._types import Float64Array, Float64ArrayOrComplexArray +from ._types import Float64Matrix, ComplexMatrix from typing import AnyStr, Union from .enums import LineUnits @@ -42,11 +42,9 @@ class ILines(Iterable): 'Units', ] - def New(self, Name): - if not isinstance(Name, bytes): - Name = Name.encode(self._api_util.codec) - - return self._check_for_error(self._lib.Lines_New(Name)) + def New(self, Name: str): + '''Create new Line object with the given `Name`''' + return self._lib.Lines_New(Name) @property def Bus1(self) -> str: @@ -55,14 +53,11 @@ def Bus1(self) -> str: Original COM help: https://opendss.epri.com/Bus1.html ''' - return self._get_string(self._check_for_error(self._lib.Lines_Get_Bus1())) + return self._lib.Lines_Get_Bus1() @Bus1.setter def Bus1(self, Value): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Lines_Set_Bus1(Value)) + self._lib.Lines_Set_Bus1(Value) @property def Bus2(self) -> str: @@ -71,14 +66,11 @@ def Bus2(self) -> str: Original COM help: https://opendss.epri.com/Bus2.html ''' - return self._get_string(self._check_for_error(self._lib.Lines_Get_Bus2())) + return self._lib.Lines_Get_Bus2() @Bus2.setter def Bus2(self, Value): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Lines_Set_Bus2(Value)) + self._lib.Lines_Set_Bus2(Value) @property def C0(self) -> float: @@ -87,11 +79,11 @@ def C0(self) -> float: Original COM help: https://opendss.epri.com/C0.html ''' - return self._check_for_error(self._lib.Lines_Get_C0()) + return self._lib.Lines_Get_C0() @C0.setter def C0(self, Value: float): - self._check_for_error(self._lib.Lines_Set_C0(Value)) + self._lib.Lines_Set_C0(Value) @property def C1(self) -> float: @@ -100,21 +92,20 @@ def C1(self) -> float: Original COM help: https://opendss.epri.com/C1.html ''' - return self._check_for_error(self._lib.Lines_Get_C1()) + return self._lib.Lines_Get_C1() @C1.setter def C1(self, Value: float): - self._check_for_error(self._lib.Lines_Set_C1(Value)) + self._lib.Lines_Set_C1(Value) @property - def Cmatrix(self) -> Float64Array: - self._check_for_error(self._lib.Lines_Get_Cmatrix_GR()) - return self._get_float64_gr_array() + def Cmatrix(self) -> Float64Matrix: + return self._lib.Lines_Get_Cmatrix_GR() @Cmatrix.setter - def Cmatrix(self, Value: Float64Array): + def Cmatrix(self, Value: Float64Matrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Lines_Set_Cmatrix(ValuePtr, ValueCount)) + self._lib.Lines_Set_Cmatrix(ValuePtr, ValueCount) @property def EmergAmps(self) -> float: @@ -123,11 +114,11 @@ def EmergAmps(self) -> float: Original COM help: https://opendss.epri.com/EmergAmps1.html ''' - return self._check_for_error(self._lib.Lines_Get_EmergAmps()) + return self._lib.Lines_Get_EmergAmps() @EmergAmps.setter def EmergAmps(self, Value: float): - self._check_for_error(self._lib.Lines_Set_EmergAmps(Value)) + self._lib.Lines_Set_EmergAmps(Value) @property def Geometry(self) -> str: @@ -136,27 +127,24 @@ def Geometry(self) -> str: Original COM help: https://opendss.epri.com/Geometry.html ''' - return self._get_string(self._check_for_error(self._lib.Lines_Get_Geometry())) + return self._lib.Lines_Get_Geometry() @Geometry.setter def Geometry(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Lines_Set_Geometry(Value)) + self._lib.Lines_Set_Geometry(Value) @property def Length(self) -> float: ''' - Length of line section in units compatible with the LineCode definition. + Length of line in units compatible with the LineCode definition. Original COM help: https://opendss.epri.com/Length.html ''' - return self._check_for_error(self._lib.Lines_Get_Length()) + return self._lib.Lines_Get_Length() @Length.setter def Length(self, Value: float): - self._check_for_error(self._lib.Lines_Set_Length(Value)) + self._lib.Lines_Set_Length(Value) @property def LineCode(self) -> str: @@ -165,14 +153,11 @@ def LineCode(self) -> str: Original COM help: https://opendss.epri.com/LineCode.html ''' - return self._get_string(self._check_for_error(self._lib.Lines_Get_LineCode())) + return self._lib.Lines_Get_LineCode() @LineCode.setter def LineCode(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Lines_Set_LineCode(Value)) + self._lib.Lines_Set_LineCode(Value) @property def NormAmps(self) -> float: @@ -181,22 +166,22 @@ def NormAmps(self) -> float: Original COM help: https://opendss.epri.com/NormAmps.html ''' - return self._check_for_error(self._lib.Lines_Get_NormAmps()) + return self._lib.Lines_Get_NormAmps() @NormAmps.setter def NormAmps(self, Value: float): - self._check_for_error(self._lib.Lines_Set_NormAmps(Value)) + self._lib.Lines_Set_NormAmps(Value) @property def NumCust(self) -> int: ''' - Number of customers on this line section. + Number of customers on this line. *Requires an energy meter with an updated zone.* Original COM help: https://opendss.epri.com/NumCust.html ''' - return self._check_for_error(self._lib.Lines_Get_NumCust()) + return self._lib.Lines_Get_NumCust() @property def Parent(self) -> int: @@ -207,7 +192,7 @@ def Parent(self) -> int: Original COM help: https://opendss.epri.com/Parent.html ''' - return self._check_for_error(self._lib.Lines_Get_Parent()) + return self._lib.Lines_Get_Parent() @property def Phases(self) -> int: @@ -216,11 +201,11 @@ def Phases(self) -> int: Original COM help: https://opendss.epri.com/Phases1.html ''' - return self._check_for_error(self._lib.Lines_Get_Phases()) + return self._lib.Lines_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.Lines_Set_Phases(Value)) + self._lib.Lines_Set_Phases(Value) @property def R0(self) -> float: @@ -229,11 +214,11 @@ def R0(self) -> float: Original COM help: https://opendss.epri.com/R0.html ''' - return self._check_for_error(self._lib.Lines_Get_R0()) + return self._lib.Lines_Get_R0() @R0.setter def R0(self, Value: float): - self._check_for_error(self._lib.Lines_Set_R0(Value)) + self._lib.Lines_Set_R0(Value) @property def R1(self) -> float: @@ -242,11 +227,11 @@ def R1(self) -> float: Original COM help: https://opendss.epri.com/R1.html ''' - return self._check_for_error(self._lib.Lines_Get_R1()) + return self._lib.Lines_Get_R1() @R1.setter def R1(self, Value: float): - self._check_for_error(self._lib.Lines_Set_R1(Value)) + self._lib.Lines_Set_R1(Value) @property def Rg(self) -> float: @@ -255,11 +240,11 @@ def Rg(self) -> float: Original COM help: https://opendss.epri.com/Rg.html ''' - return self._check_for_error(self._lib.Lines_Get_Rg()) + return self._lib.Lines_Get_Rg() @Rg.setter def Rg(self, Value: float): - self._check_for_error(self._lib.Lines_Set_Rg(Value)) + self._lib.Lines_Set_Rg(Value) @property def Rho(self) -> float: @@ -268,26 +253,25 @@ def Rho(self) -> float: Original COM help: https://opendss.epri.com/Rho.html ''' - return self._check_for_error(self._lib.Lines_Get_Rho()) + return self._lib.Lines_Get_Rho() @Rho.setter def Rho(self, Value: float): - self._check_for_error(self._lib.Lines_Set_Rho(Value)) + self._lib.Lines_Set_Rho(Value) @property - def Rmatrix(self) -> Float64Array: + def Rmatrix(self) -> Float64Matrix: ''' Resistance matrix (full), ohms per unit length. Array of doubles. Original COM help: https://opendss.epri.com/Rmatrix.html ''' - self._check_for_error(self._lib.Lines_Get_Rmatrix_GR()) - return self._get_float64_gr_array() + return self._lib.Lines_Get_Rmatrix_GR() @Rmatrix.setter - def Rmatrix(self, Value: Float64Array): + def Rmatrix(self, Value: Float64Matrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Lines_Set_Rmatrix(ValuePtr, ValueCount)) + self._lib.Lines_Set_Rmatrix(ValuePtr, ValueCount) @property def Spacing(self) -> str: @@ -296,31 +280,33 @@ def Spacing(self) -> str: Original COM help: https://opendss.epri.com/Spacing.html ''' - return self._get_string(self._check_for_error(self._lib.Lines_Get_Spacing())) + return self._lib.Lines_Get_Spacing() @Spacing.setter def Spacing(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Lines_Set_Spacing(Value)) + self._lib.Lines_Set_Spacing(Value) @property def TotalCust(self) -> int: ''' - Total Number of customers served from this line section. + Total Number of customers served from this line. Original COM help: https://opendss.epri.com/TotalCust.html ''' - return self._check_for_error(self._lib.Lines_Get_TotalCust()) + return self._lib.Lines_Get_TotalCust() @property def Units(self) -> LineUnits: - return LineUnits(self._check_for_error(self._lib.Lines_Get_Units())) + ''' + Length units for the active line. + + Original COM help: https://opendss.epri.com/Units.html + ''' + return LineUnits(self._lib.Lines_Get_Units()) @Units.setter def Units(self, Value: Union[int, LineUnits]): - self._check_for_error(self._lib.Lines_Set_Units(Value)) + self._lib.Lines_Set_Units(Value) @property def X0(self) -> float: @@ -329,11 +315,11 @@ def X0(self) -> float: Original COM help: https://opendss.epri.com/X0.html ''' - return self._check_for_error(self._lib.Lines_Get_X0()) + return self._lib.Lines_Get_X0() @X0.setter def X0(self, Value: float): - self._check_for_error(self._lib.Lines_Set_X0(Value)) + self._lib.Lines_Set_X0(Value) @property def X1(self) -> float: @@ -342,11 +328,11 @@ def X1(self) -> float: Original COM help: https://opendss.epri.com/X1.html ''' - return self._check_for_error(self._lib.Lines_Get_X1()) + return self._lib.Lines_Get_X1() @X1.setter def X1(self, Value: float): - self._check_for_error(self._lib.Lines_Set_X1(Value)) + self._lib.Lines_Set_X1(Value) @property def Xg(self) -> float: @@ -355,41 +341,39 @@ def Xg(self) -> float: Original COM help: https://opendss.epri.com/Xg.html ''' - return self._check_for_error(self._lib.Lines_Get_Xg()) + return self._lib.Lines_Get_Xg() @Xg.setter def Xg(self, Value: float): - self._check_for_error(self._lib.Lines_Set_Xg(Value)) + self._lib.Lines_Set_Xg(Value) @property - def Xmatrix(self) -> Float64Array: + def Xmatrix(self) -> Float64Matrix: ''' Reactance matrix (full), ohms per unit length. Array of doubles. Original COM help: https://opendss.epri.com/Xmatrix.html ''' - self._check_for_error(self._lib.Lines_Get_Xmatrix_GR()) - return self._get_float64_gr_array() + return self._lib.Lines_Get_Xmatrix_GR() @Xmatrix.setter - def Xmatrix(self, Value: Float64Array): + def Xmatrix(self, Value: Float64Matrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Lines_Set_Xmatrix(ValuePtr, ValueCount)) + self._lib.Lines_Set_Xmatrix(ValuePtr, ValueCount) @property - def Yprim(self) -> Float64ArrayOrComplexArray: + def Yprim(self) -> ComplexMatrix: ''' Yprimitive for the active line object (complex array). Original COM help: https://opendss.epri.com/Yprim1.html ''' - self._check_for_error(self._lib.Lines_Get_Yprim_GR()) - return self._get_complex128_gr_array() + return self._lib.Lines_Get_Yprim_GR() @Yprim.setter - def Yprim(self, Value: Float64ArrayOrComplexArray): + def Yprim(self, Value: ComplexMatrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Lines_Set_Yprim(ValuePtr, ValueCount)) + self._lib.Lines_Set_Yprim(ValuePtr, ValueCount) @property def SeasonRating(self) -> float: @@ -398,18 +382,18 @@ def SeasonRating(self) -> float: Original COM help: https://opendss.epri.com/SeasonRating.html ''' - return self._check_for_error(self._lib.Lines_Get_SeasonRating()) + return self._lib.Lines_Get_SeasonRating() @property def IsSwitch(self) -> bool: ''' - Sets/gets the Line element switch status. Setting it has side-effects to the line parameters. + Line element switch status. Setting it has side-effects to the line parameters. **(API Extension)** ''' - return self._check_for_error(self._lib.Lines_Get_IsSwitch()) != 0 + return self._lib.Lines_Get_IsSwitch() @IsSwitch.setter def IsSwitch(self, Value: bool): - self._check_for_error(self._lib.Lines_Set_IsSwitch(Value)) + self._lib.Lines_Set_IsSwitch(Value) diff --git a/dss/ILoadShapes.py b/dss/ILoadShapes.py index 5c9ab89d..4c8ab277 100644 --- a/dss/ILoadShapes.py +++ b/dss/ILoadShapes.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Float64Array from typing import AnyStr @@ -25,14 +25,11 @@ class ILoadShapes(Iterable): def New(self, Name: AnyStr): '''Create a new LoadShape, with default parameters''' - if not isinstance(Name, bytes): - Name = Name.encode(self._api_util.codec) - - return self._check_for_error(self._lib.LoadShapes_New(Name)) + return self._lib.LoadShapes_New(Name) def Normalize(self): '''Normalize the LoadShape data inplace''' - self._check_for_error(self._lib.LoadShapes_Normalize()) + self._lib.LoadShapes_Normalize() @property def HrInterval(self) -> float: @@ -41,11 +38,11 @@ def HrInterval(self) -> float: Original COM help: https://opendss.epri.com/HrInterval.html ''' - return self._check_for_error(self._lib.LoadShapes_Get_HrInterval()) + return self._lib.LoadShapes_Get_HrInterval() @HrInterval.setter def HrInterval(self, Value: float): - self._check_for_error(self._lib.LoadShapes_Set_HrInterval(Value)) + self._lib.LoadShapes_Set_HrInterval(Value) @property def MinInterval(self) -> float: @@ -54,11 +51,11 @@ def MinInterval(self) -> float: Original COM help: https://opendss.epri.com/MinInterval.html ''' - return self._check_for_error(self._lib.LoadShapes_Get_MinInterval()) + return self._lib.LoadShapes_Get_MinInterval() @MinInterval.setter def MinInterval(self, Value: float): - self._check_for_error(self._lib.LoadShapes_Set_MinInterval(Value)) + self._lib.LoadShapes_Set_MinInterval(Value) @property def Npts(self) -> int: @@ -67,11 +64,11 @@ def Npts(self) -> int: Original COM help: https://opendss.epri.com/Npts.html ''' - return self._check_for_error(self._lib.LoadShapes_Get_Npts()) + return self._lib.LoadShapes_Get_Npts() @Npts.setter def Npts(self, Value: int): - self._check_for_error(self._lib.LoadShapes_Set_Npts(Value)) + self._lib.LoadShapes_Set_Npts(Value) @property def PBase(self) -> float: @@ -80,11 +77,11 @@ def PBase(self) -> float: Original COM help: https://opendss.epri.com/Pbase.html ''' - return self._check_for_error(self._lib.LoadShapes_Get_PBase()) + return self._lib.LoadShapes_Get_PBase() @PBase.setter def PBase(self, Value: float): - self._check_for_error(self._lib.LoadShapes_Set_PBase(Value)) + self._lib.LoadShapes_Set_PBase(Value) Pbase = PBase @@ -95,13 +92,12 @@ def Pmult(self) -> Float64Array: Original COM help: https://opendss.epri.com/Pmult.html ''' - self._check_for_error(self._lib.LoadShapes_Get_Pmult_GR()) - return self._get_float64_gr_array() + return self._lib.LoadShapes_Get_Pmult_GR() @Pmult.setter def Pmult(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LoadShapes_Set_Pmult(ValuePtr, ValueCount)) + self._lib.LoadShapes_Set_Pmult(ValuePtr, ValueCount) @property def QBase(self) -> float: @@ -110,11 +106,11 @@ def QBase(self) -> float: Original COM help: https://opendss.epri.com/Qbase.html ''' - return self._check_for_error(self._lib.LoadShapes_Get_Qbase()) + return self._lib.LoadShapes_Get_Qbase() @QBase.setter def QBase(self, Value: float): - self._check_for_error(self._lib.LoadShapes_Set_Qbase(Value)) + self._lib.LoadShapes_Set_Qbase(Value) Qbase = QBase @@ -125,13 +121,12 @@ def Qmult(self) -> Float64Array: Original COM help: https://opendss.epri.com/Qmult.html ''' - self._check_for_error(self._lib.LoadShapes_Get_Qmult_GR()) - return self._get_float64_gr_array() + return self._lib.LoadShapes_Get_Qmult_GR() @Qmult.setter def Qmult(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LoadShapes_Set_Qmult(ValuePtr, ValueCount)) + self._lib.LoadShapes_Set_Qmult(ValuePtr, ValueCount) @property def TimeArray(self) -> Float64Array: @@ -140,13 +135,12 @@ def TimeArray(self) -> Float64Array: Original COM help: https://opendss.epri.com/TimeArray.html ''' - self._check_for_error(self._lib.LoadShapes_Get_TimeArray_GR()) - return self._get_float64_gr_array() + return self._lib.LoadShapes_Get_TimeArray_GR() @TimeArray.setter def TimeArray(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.LoadShapes_Set_TimeArray(ValuePtr, ValueCount)) + self._lib.LoadShapes_Set_TimeArray(ValuePtr, ValueCount) @property def UseActual(self) -> bool: @@ -155,11 +149,11 @@ def UseActual(self) -> bool: Original COM help: https://opendss.epri.com/UseActual.html ''' - return self._check_for_error(self._lib.LoadShapes_Get_UseActual()) != 0 + return self._lib.LoadShapes_Get_UseActual() @UseActual.setter def UseActual(self, Value: bool): - self._check_for_error(self._lib.LoadShapes_Set_UseActual(Value)) + self._lib.LoadShapes_Set_UseActual(Value) @property def sInterval(self) -> float: @@ -168,11 +162,11 @@ def sInterval(self) -> float: Original COM help: https://opendss.epri.com/Sinterval.html ''' - return self._check_for_error(self._lib.LoadShapes_Get_SInterval()) + return self._lib.LoadShapes_Get_SInterval() @sInterval.setter def sInterval(self, Value: float): - self._check_for_error(self._lib.LoadShapes_Set_SInterval(Value)) + self._lib.LoadShapes_Set_SInterval(Value) Sinterval = sInterval SInterval = sInterval @@ -184,7 +178,7 @@ def UseFloat32(self): **(API Extension)** ''' - self._check_for_error(self._lib.LoadShapes_UseFloat32()) + self._lib.LoadShapes_UseFloat32() def UseFloat64(self): ''' @@ -193,4 +187,4 @@ def UseFloat64(self): **(API Extension)** ''' - self._check_for_error(self._lib.LoadShapes_UseFloat64()) + self._lib.LoadShapes_UseFloat64() diff --git a/dss/ILoads.py b/dss/ILoads.py index 4a855605..cec02ae1 100644 --- a/dss/ILoads.py +++ b/dss/ILoads.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Float64Array from typing import AnyStr, Union @@ -58,11 +58,11 @@ def AllocationFactor(self) -> float: Original COM help: https://opendss.epri.com/AllocationFactor.html ''' - return self._check_for_error(self._lib.Loads_Get_AllocationFactor()) + return self._lib.Loads_Get_AllocationFactor() @AllocationFactor.setter def AllocationFactor(self, Value: float): - self._check_for_error(self._lib.Loads_Set_AllocationFactor(Value)) + self._lib.Loads_Set_AllocationFactor(Value) @property def CVRcurve(self) -> str: @@ -71,14 +71,11 @@ def CVRcurve(self) -> str: Original COM help: https://opendss.epri.com/CVRcurve.html ''' - return self._get_string(self._check_for_error(self._lib.Loads_Get_CVRcurve())) + return self._lib.Loads_Get_CVRcurve() @CVRcurve.setter def CVRcurve(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Loads_Set_CVRcurve(Value)) + self._lib.Loads_Set_CVRcurve(Value) @property def CVRvars(self) -> float: @@ -87,11 +84,11 @@ def CVRvars(self) -> float: Original COM help: https://opendss.epri.com/CVRvars.html ''' - return self._check_for_error(self._lib.Loads_Get_CVRvars()) + return self._lib.Loads_Get_CVRvars() @CVRvars.setter def CVRvars(self, Value: float): - self._check_for_error(self._lib.Loads_Set_CVRvars(Value)) + self._lib.Loads_Set_CVRvars(Value) @property def CVRwatts(self) -> float: @@ -100,24 +97,24 @@ def CVRwatts(self) -> float: Original COM help: https://opendss.epri.com/CVRwatts.html ''' - return self._check_for_error(self._lib.Loads_Get_CVRwatts()) + return self._lib.Loads_Get_CVRwatts() @CVRwatts.setter def CVRwatts(self, Value: float): - self._check_for_error(self._lib.Loads_Set_CVRwatts(Value)) + self._lib.Loads_Set_CVRwatts(Value) @property def Cfactor(self) -> float: ''' - Factor relates average to peak kw. Used for allocation with kwh and kwhdays + CFactor relates average to peak kw. Used for allocation with kwh and kwhdays Original COM help: https://opendss.epri.com/Cfactor.html ''' - return self._check_for_error(self._lib.Loads_Get_Cfactor()) + return self._lib.Loads_Get_Cfactor() @Cfactor.setter def Cfactor(self, Value: float): - self._check_for_error(self._lib.Loads_Set_Cfactor(Value)) + self._lib.Loads_Set_Cfactor(Value) @property def Class(self) -> int: @@ -126,11 +123,11 @@ def Class(self) -> int: Original COM help: https://opendss.epri.com/Class.html ''' - return self._check_for_error(self._lib.Loads_Get_Class_()) + return self._lib.Loads_Get_Class_() @Class.setter def Class(self, Value: int): - self._check_for_error(self._lib.Loads_Set_Class_(Value)) + self._lib.Loads_Set_Class_(Value) @property def Growth(self) -> str: @@ -139,14 +136,11 @@ def Growth(self) -> str: Original COM help: https://opendss.epri.com/Growth.html ''' - return self._get_string(self._check_for_error(self._lib.Loads_Get_Growth())) + return self._lib.Loads_Get_Growth() @Growth.setter def Growth(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Loads_Set_Growth(Value)) + self._lib.Loads_Set_Growth(Value) @property def IsDelta(self) -> bool: @@ -155,11 +149,11 @@ def IsDelta(self) -> bool: Original COM help: https://opendss.epri.com/IsDelta1.html ''' - return self._check_for_error(self._lib.Loads_Get_IsDelta()) != 0 + return self._lib.Loads_Get_IsDelta() @IsDelta.setter def IsDelta(self, Value: bool): - self._check_for_error(self._lib.Loads_Set_IsDelta(Value)) + self._lib.Loads_Set_IsDelta(Value) @property def Model(self) -> LoadModels: @@ -168,11 +162,11 @@ def Model(self) -> LoadModels: Original COM help: https://opendss.epri.com/Model1.html ''' - return self._check_for_error(LoadModels(self._lib.Loads_Get_Model())) + return LoadModels(self._lib.Loads_Get_Model()) @Model.setter def Model(self, Value: Union[int, LoadModels]): - self._check_for_error(self._lib.Loads_Set_Model(Value)) + self._lib.Loads_Set_Model(Value) @property def NumCust(self) -> int: @@ -181,11 +175,11 @@ def NumCust(self) -> int: Original COM help: https://opendss.epri.com/NumCust1.html ''' - return self._check_for_error(self._lib.Loads_Get_NumCust()) + return self._lib.Loads_Get_NumCust() @NumCust.setter def NumCust(self, Value: int): - self._check_for_error(self._lib.Loads_Set_NumCust(Value)) + self._lib.Loads_Set_NumCust(Value) @property def PF(self) -> float: @@ -194,11 +188,11 @@ def PF(self) -> float: Original COM help: https://opendss.epri.com/PF1.html ''' - return self._check_for_error(self._lib.Loads_Get_PF()) + return self._lib.Loads_Get_PF() @PF.setter def PF(self, Value: float): - self._check_for_error(self._lib.Loads_Set_PF(Value)) + self._lib.Loads_Set_PF(Value) @property def PctMean(self) -> float: @@ -207,11 +201,11 @@ def PctMean(self) -> float: Original COM help: https://opendss.epri.com/PctMean.html ''' - return self._check_for_error(self._lib.Loads_Get_PctMean()) + return self._lib.Loads_Get_PctMean() @PctMean.setter def PctMean(self, Value: float): - self._check_for_error(self._lib.Loads_Set_PctMean(Value)) + self._lib.Loads_Set_PctMean(Value) @property def PctStdDev(self) -> float: @@ -220,24 +214,26 @@ def PctStdDev(self) -> float: Original COM help: https://opendss.epri.com/PctStdDev.html ''' - return self._check_for_error(self._lib.Loads_Get_PctStdDev()) + return self._lib.Loads_Get_PctStdDev() @PctStdDev.setter def PctStdDev(self, Value: float): - self._check_for_error(self._lib.Loads_Set_PctStdDev(Value)) + self._lib.Loads_Set_PctStdDev(Value) @property def RelWeight(self) -> float: ''' - Relative Weighting factor for the active LOAD + Relative Weighting factor for the active load. + + This value is used in reliability methods. Original COM help: https://opendss.epri.com/RelWeight.html ''' - return self._check_for_error(self._lib.Loads_Get_RelWeight()) + return self._lib.Loads_Get_RelWeight() @RelWeight.setter def RelWeight(self, Value: float): - self._check_for_error(self._lib.Loads_Set_RelWeight(Value)) + self._lib.Loads_Set_RelWeight(Value) @property def Rneut(self) -> float: @@ -246,11 +242,11 @@ def Rneut(self) -> float: Original COM help: https://opendss.epri.com/Rneut.html ''' - return self._check_for_error(self._lib.Loads_Get_Rneut()) + return self._lib.Loads_Get_Rneut() @Rneut.setter def Rneut(self, Value: float): - self._check_for_error(self._lib.Loads_Set_Rneut(Value)) + self._lib.Loads_Set_Rneut(Value) @property def Spectrum(self) -> str: @@ -259,14 +255,11 @@ def Spectrum(self) -> str: Original COM help: https://opendss.epri.com/Spectrum.html ''' - return self._get_string(self._check_for_error(self._lib.Loads_Get_Spectrum())) + return self._lib.Loads_Get_Spectrum() @Spectrum.setter def Spectrum(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Loads_Set_Spectrum(Value)) + self._lib.Loads_Set_Spectrum(Value) @property def Status(self) -> LoadStatus: @@ -275,11 +268,11 @@ def Status(self) -> LoadStatus: Original COM help: https://opendss.epri.com/Status.html ''' - return LoadStatus(self._check_for_error(self._lib.Loads_Get_Status())) + return LoadStatus(self._lib.Loads_Get_Status()) @Status.setter def Status(self, Value: Union[int, LoadStatus]): - self._check_for_error(self._lib.Loads_Set_Status(Value)) + self._lib.Loads_Set_Status(Value) @property def Vmaxpu(self) -> float: @@ -288,11 +281,11 @@ def Vmaxpu(self) -> float: Original COM help: https://opendss.epri.com/Vmaxpu1.html ''' - return self._check_for_error(self._lib.Loads_Get_Vmaxpu()) + return self._lib.Loads_Get_Vmaxpu() @Vmaxpu.setter def Vmaxpu(self, Value: float): - self._check_for_error(self._lib.Loads_Set_Vmaxpu(Value)) + self._lib.Loads_Set_Vmaxpu(Value) @property def Vminemerg(self) -> float: @@ -301,11 +294,11 @@ def Vminemerg(self) -> float: Original COM help: https://opendss.epri.com/Vminemerg.html ''' - return self._check_for_error(self._lib.Loads_Get_Vminemerg()) + return self._lib.Loads_Get_Vminemerg() @Vminemerg.setter def Vminemerg(self, Value: float): - self._check_for_error(self._lib.Loads_Set_Vminemerg(Value)) + self._lib.Loads_Set_Vminemerg(Value) @property def Vminnorm(self) -> float: @@ -314,11 +307,11 @@ def Vminnorm(self) -> float: Original COM help: https://opendss.epri.com/Vminnorm.html ''' - return self._check_for_error(self._lib.Loads_Get_Vminnorm()) + return self._lib.Loads_Get_Vminnorm() @Vminnorm.setter def Vminnorm(self, Value: float): - self._check_for_error(self._lib.Loads_Set_Vminnorm(Value)) + self._lib.Loads_Set_Vminnorm(Value) @property def Vminpu(self) -> float: @@ -327,11 +320,11 @@ def Vminpu(self) -> float: Original COM help: https://opendss.epri.com/Vminpu1.html ''' - return self._check_for_error(self._lib.Loads_Get_Vminpu()) + return self._lib.Loads_Get_Vminpu() @Vminpu.setter def Vminpu(self, Value: float): - self._check_for_error(self._lib.Loads_Set_Vminpu(Value)) + self._lib.Loads_Set_Vminpu(Value) @property def Xneut(self) -> float: @@ -340,11 +333,11 @@ def Xneut(self) -> float: Original COM help: https://opendss.epri.com/Xneut.html ''' - return self._check_for_error(self._lib.Loads_Get_Xneut()) + return self._lib.Loads_Get_Xneut() @Xneut.setter def Xneut(self, Value: float): - self._check_for_error(self._lib.Loads_Set_Xneut(Value)) + self._lib.Loads_Set_Xneut(Value) @property def Yearly(self) -> str: @@ -353,14 +346,11 @@ def Yearly(self) -> str: Original COM help: https://opendss.epri.com/Yearly.html ''' - return self._get_string(self._check_for_error(self._lib.Loads_Get_Yearly())) + return self._lib.Loads_Get_Yearly() @Yearly.setter def Yearly(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Loads_Set_Yearly(Value)) + self._lib.Loads_Set_Yearly(Value) @property def ZIPV(self) -> Float64Array: @@ -369,13 +359,12 @@ def ZIPV(self) -> Float64Array: Original COM help: https://opendss.epri.com/ZIPV.html ''' - self._check_for_error(self._lib.Loads_Get_ZIPV_GR()) - return self._get_float64_gr_array() + return self._lib.Loads_Get_ZIPV_GR() @ZIPV.setter def ZIPV(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Loads_Set_ZIPV(ValuePtr, ValueCount)) + self._lib.Loads_Set_ZIPV(ValuePtr, ValueCount) @property def daily(self) -> str: @@ -384,14 +373,11 @@ def daily(self) -> str: Original COM help: https://opendss.epri.com/daily.html ''' - return self._get_string(self._check_for_error(self._lib.Loads_Get_daily())) + return self._lib.Loads_Get_daily() @daily.setter def daily(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Loads_Set_daily(Value)) + self._lib.Loads_Set_daily(Value) @property def duty(self) -> str: @@ -400,14 +386,11 @@ def duty(self) -> str: Original COM help: https://opendss.epri.com/duty.html ''' - return self._get_string(self._check_for_error(self._lib.Loads_Get_duty())) + return self._lib.Loads_Get_duty() @duty.setter def duty(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Loads_Set_duty(Value)) + self._lib.Loads_Set_duty(Value) @property def kV(self) -> float: @@ -416,11 +399,11 @@ def kV(self) -> float: Original COM help: https://opendss.epri.com/kV2.html ''' - return self._check_for_error(self._lib.Loads_Get_kV()) + return self._lib.Loads_Get_kV() @kV.setter def kV(self, Value: float): - self._check_for_error(self._lib.Loads_Set_kV(Value)) + self._lib.Loads_Set_kV(Value) @property def kW(self) -> float: @@ -429,11 +412,11 @@ def kW(self) -> float: Original COM help: https://opendss.epri.com/kW1.html ''' - return self._check_for_error(self._lib.Loads_Get_kW()) + return self._lib.Loads_Get_kW() @kW.setter def kW(self, Value: float): - self._check_for_error(self._lib.Loads_Set_kW(Value)) + self._lib.Loads_Set_kW(Value) @property def kva(self) -> float: @@ -442,11 +425,11 @@ def kva(self) -> float: Original COM help: https://opendss.epri.com/kva.html ''' - return self._check_for_error(self._lib.Loads_Get_kva()) + return self._lib.Loads_Get_kva() @kva.setter def kva(self, Value: float): - self._check_for_error(self._lib.Loads_Set_kva(Value)) + self._lib.Loads_Set_kva(Value) @property def kvar(self) -> float: @@ -455,11 +438,11 @@ def kvar(self) -> float: Original COM help: https://opendss.epri.com/kvar1.html ''' - return self._check_for_error(self._lib.Loads_Get_kvar()) + return self._lib.Loads_Get_kvar() @kvar.setter def kvar(self, Value: float): - self._check_for_error(self._lib.Loads_Set_kvar(Value)) + self._lib.Loads_Set_kvar(Value) @property def kwh(self) -> float: @@ -468,11 +451,11 @@ def kwh(self) -> float: Original COM help: https://opendss.epri.com/kwh.html ''' - return self._check_for_error(self._lib.Loads_Get_kwh()) + return self._lib.Loads_Get_kwh() @kwh.setter def kwh(self, Value: float): - self._check_for_error(self._lib.Loads_Set_kwh(Value)) + self._lib.Loads_Set_kwh(Value) @property def kwhdays(self) -> float: @@ -481,11 +464,11 @@ def kwhdays(self) -> float: Original COM help: https://opendss.epri.com/kwhdays.html ''' - return self._check_for_error(self._lib.Loads_Get_kwhdays()) + return self._lib.Loads_Get_kwhdays() @kwhdays.setter def kwhdays(self, Value: float): - self._check_for_error(self._lib.Loads_Set_kwhdays(Value)) + self._lib.Loads_Set_kwhdays(Value) @property def pctSeriesRL(self) -> float: @@ -494,11 +477,11 @@ def pctSeriesRL(self) -> float: Original COM help: https://opendss.epri.com/pctSeriesRL.html ''' - return self._check_for_error(self._lib.Loads_Get_pctSeriesRL()) + return self._lib.Loads_Get_pctSeriesRL() @pctSeriesRL.setter def pctSeriesRL(self, Value: float): - self._check_for_error(self._lib.Loads_Set_pctSeriesRL(Value)) + self._lib.Loads_Set_pctSeriesRL(Value) @property def xfkVA(self) -> float: @@ -507,11 +490,11 @@ def xfkVA(self) -> float: Original COM help: https://opendss.epri.com/xfkVA.html ''' - return self._check_for_error(self._lib.Loads_Get_xfkVA()) + return self._lib.Loads_Get_xfkVA() @xfkVA.setter def xfkVA(self, Value: float): - self._check_for_error(self._lib.Loads_Set_xfkVA(Value)) + self._lib.Loads_Set_xfkVA(Value) @property def Sensor(self) -> str: @@ -520,7 +503,7 @@ def Sensor(self) -> str: Original COM help: https://opendss.epri.com/Sensor.html ''' - return self._get_string(self._check_for_error(self._lib.Loads_Get_Sensor())) + return self._lib.Loads_Get_Sensor() # API extensions @property @@ -530,8 +513,8 @@ def Phases(self) -> int: **(API Extension)** ''' - return self._check_for_error(self._lib.Loads_Get_Phases()) + return self._lib.Loads_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.Loads_Set_Phases(Value)) + self._lib.Loads_Set_Phases(Value) diff --git a/dss/IMeters.py b/dss/IMeters.py index 6d3b3bdf..d17917a3 100644 --- a/dss/IMeters.py +++ b/dss/IMeters.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import List, AnyStr from ._types import Float64Array @@ -47,7 +47,7 @@ def CloseAllDIFiles(self): Original COM help: https://opendss.epri.com/CloseAllDIFiles.html ''' - self._check_for_error(self._lib.Meters_CloseAllDIFiles()) + self._lib.Meters_CloseAllDIFiles() def DoReliabilityCalc(self, AssumeRestoration: bool): ''' @@ -55,7 +55,7 @@ def DoReliabilityCalc(self, AssumeRestoration: bool): Original COM help: https://opendss.epri.com/DoReliabilityCalc.html ''' - self._check_for_error(self._lib.Meters_DoReliabilityCalc(AssumeRestoration)) + self._lib.Meters_DoReliabilityCalc(AssumeRestoration) def OpenAllDIFiles(self): ''' @@ -63,7 +63,7 @@ def OpenAllDIFiles(self): Original COM help: https://opendss.epri.com/OpenAllDIFiles.html ''' - self._check_for_error(self._lib.Meters_OpenAllDIFiles()) + self._lib.Meters_OpenAllDIFiles() def Reset(self): ''' @@ -71,7 +71,7 @@ def Reset(self): Original COM help: https://opendss.epri.com/Reset2.html ''' - self._check_for_error(self._lib.Meters_Reset()) + self._lib.Meters_Reset() def ResetAll(self): ''' @@ -79,7 +79,7 @@ def ResetAll(self): Original COM help: https://opendss.epri.com/ResetAll.html ''' - self._check_for_error(self._lib.Meters_ResetAll()) + self._lib.Meters_ResetAll() def Sample(self): ''' @@ -87,7 +87,7 @@ def Sample(self): Original COM help: https://opendss.epri.com/Sample1.html ''' - self._check_for_error(self._lib.Meters_Sample()) + self._lib.Meters_Sample() def SampleAll(self): ''' @@ -95,7 +95,7 @@ def SampleAll(self): Original COM help: https://opendss.epri.com/SampleAll.html ''' - self._check_for_error(self._lib.Meters_SampleAll()) + self._lib.Meters_SampleAll() def Save(self): ''' @@ -103,7 +103,7 @@ def Save(self): Original COM help: https://opendss.epri.com/Save.html ''' - self._check_for_error(self._lib.Meters_Save()) + self._lib.Meters_Save() def SaveAll(self): ''' @@ -111,19 +111,19 @@ def SaveAll(self): Original COM help: https://opendss.epri.com/SaveAll.html ''' - self._check_for_error(self._lib.Meters_SaveAll()) + self._lib.Meters_SaveAll() def SetActiveSection(self, SectIdx: int): - self._check_for_error(self._lib.Meters_SetActiveSection(SectIdx)) + self._lib.Meters_SetActiveSection(SectIdx) @property def AllBranchesInZone(self) -> List[str]: ''' - Wide string list of all branches in zone of the active EnergyMeter object. + List (strings) of all branches in zone of the active EnergyMeter object. Original COM help: https://opendss.epri.com/AllBranchesInZone.html ''' - return self._check_for_error(self._get_string_array(self._lib.Meters_Get_AllBranchesInZone)) + return self._lib.Meters_Get_AllBranchesInZone() @property def AllEndElements(self) -> List[str]: @@ -132,7 +132,7 @@ def AllEndElements(self) -> List[str]: Original COM help: https://opendss.epri.com/AllEndElements.html ''' - return self._check_for_error(self._get_string_array(self._lib.Meters_Get_AllEndElements)) + return self._lib.Meters_Get_AllEndElements() @property def AllocFactors(self) -> Float64Array: @@ -141,13 +141,12 @@ def AllocFactors(self) -> Float64Array: Original COM help: https://opendss.epri.com/AllocFactors.html ''' - self._check_for_error(self._lib.Meters_Get_AllocFactors_GR()) - return self._get_float64_gr_array() + return self._lib.Meters_Get_AllocFactors_GR() @AllocFactors.setter def AllocFactors(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Meters_Set_AllocFactors(ValuePtr, ValueCount)) + self._lib.Meters_Set_AllocFactors(ValuePtr, ValueCount) @property def AvgRepairTime(self) -> float: @@ -156,7 +155,7 @@ def AvgRepairTime(self) -> float: Original COM help: https://opendss.epri.com/AvgRepairTime.html ''' - return self._check_for_error(self._lib.Meters_Get_AvgRepairTime()) + return self._lib.Meters_Get_AvgRepairTime() @property def CalcCurrent(self) -> Float64Array: @@ -165,13 +164,12 @@ def CalcCurrent(self) -> Float64Array: Original COM help: https://opendss.epri.com/CalcCurrent.html ''' - self._check_for_error(self._lib.Meters_Get_CalcCurrent_GR()) - return self._get_float64_gr_array() + return self._lib.Meters_Get_CalcCurrent_GR() @CalcCurrent.setter def CalcCurrent(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Meters_Set_CalcCurrent(ValuePtr, ValueCount)) + self._lib.Meters_Set_CalcCurrent(ValuePtr, ValueCount) @property def CountBranches(self) -> int: @@ -180,7 +178,7 @@ def CountBranches(self) -> int: Original COM help: https://opendss.epri.com/CountBranches.html ''' - return self._check_for_error(self._lib.Meters_Get_CountBranches()) + return self._lib.Meters_Get_CountBranches() @property def CountEndElements(self) -> int: @@ -189,7 +187,7 @@ def CountEndElements(self) -> int: Original COM help: https://opendss.epri.com/CountEndElements.html ''' - return self._check_for_error(self._lib.Meters_Get_CountEndElements()) + return self._lib.Meters_Get_CountEndElements() @property def CustInterrupts(self) -> float: @@ -198,7 +196,7 @@ def CustInterrupts(self) -> float: Original COM help: https://opendss.epri.com/CustInterrupts.html ''' - return self._check_for_error(self._lib.Meters_Get_CustInterrupts()) + return self._lib.Meters_Get_CustInterrupts() @property def DIFilesAreOpen(self) -> bool: @@ -207,7 +205,7 @@ def DIFilesAreOpen(self) -> bool: Original COM help: https://opendss.epri.com/DIFilesAreOpen.html ''' - return self._check_for_error(self._lib.Meters_Get_DIFilesAreOpen()) != 0 + return self._lib.Meters_Get_DIFilesAreOpen() @property def FaultRateXRepairHrs(self) -> float: @@ -216,7 +214,7 @@ def FaultRateXRepairHrs(self) -> float: Original COM help: https://opendss.epri.com/FaultRateXRepairHrs.html ''' - return self._check_for_error(self._lib.Meters_Get_FaultRateXRepairHrs()) + return self._lib.Meters_Get_FaultRateXRepairHrs() @property def MeteredElement(self) -> str: @@ -225,14 +223,11 @@ def MeteredElement(self) -> str: Original COM help: https://opendss.epri.com/MeteredElement.html ''' - return self._get_string(self._check_for_error(self._lib.Meters_Get_MeteredElement())) + return self._lib.Meters_Get_MeteredElement() @MeteredElement.setter def MeteredElement(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Meters_Set_MeteredElement(Value)) + self._lib.Meters_Set_MeteredElement(Value) @property def MeteredTerminal(self) -> int: @@ -241,11 +236,11 @@ def MeteredTerminal(self) -> int: Original COM help: https://opendss.epri.com/MeteredTerminal.html ''' - return self._check_for_error(self._lib.Meters_Get_MeteredTerminal()) + return self._lib.Meters_Get_MeteredTerminal() @MeteredTerminal.setter def MeteredTerminal(self, Value: int): - self._check_for_error(self._lib.Meters_Set_MeteredTerminal(Value)) + self._lib.Meters_Set_MeteredTerminal(Value) @property def NumSectionBranches(self) -> int: @@ -254,7 +249,7 @@ def NumSectionBranches(self) -> int: Original COM help: https://opendss.epri.com/NumSectionBranches.html ''' - return self._check_for_error(self._lib.Meters_Get_NumSectionBranches()) + return self._lib.Meters_Get_NumSectionBranches() @property def NumSectionCustomers(self) -> int: @@ -263,7 +258,7 @@ def NumSectionCustomers(self) -> int: Original COM help: https://opendss.epri.com/NumSectionCustomers.html ''' - return self._check_for_error(self._lib.Meters_Get_NumSectionCustomers()) + return self._lib.Meters_Get_NumSectionCustomers() @property def NumSections(self) -> int: @@ -272,7 +267,7 @@ def NumSections(self) -> int: Original COM help: https://opendss.epri.com/NumSections.html ''' - return self._check_for_error(self._lib.Meters_Get_NumSections()) + return self._lib.Meters_Get_NumSections() @property def OCPDeviceType(self) -> OCPDevTypeEnum: @@ -281,7 +276,7 @@ def OCPDeviceType(self) -> OCPDevTypeEnum: Original COM help: https://opendss.epri.com/OCPDeviceType.html ''' - return OCPDevTypeEnum(self._check_for_error(self._lib.Meters_Get_OCPDeviceType())) + return OCPDevTypeEnum(self._lib.Meters_Get_OCPDeviceType()) @property def Peakcurrent(self) -> Float64Array: @@ -290,13 +285,12 @@ def Peakcurrent(self) -> Float64Array: Original COM help: https://opendss.epri.com/Peakcurrent.html ''' - self._check_for_error(self._lib.Meters_Get_Peakcurrent_GR()) - return self._get_float64_gr_array() + return self._lib.Meters_Get_Peakcurrent_GR() @Peakcurrent.setter def Peakcurrent(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Meters_Set_Peakcurrent(ValuePtr, ValueCount)) + self._lib.Meters_Set_Peakcurrent(ValuePtr, ValueCount) @property def RegisterNames(self) -> List[str]: @@ -309,7 +303,7 @@ def RegisterNames(self) -> List[str]: Original COM help: https://opendss.epri.com/RegisterNames1.html ''' - return self._check_for_error(self._get_string_array(self._lib.Meters_Get_RegisterNames)) + return self._lib.Meters_Get_RegisterNames() @property def RegisterValues(self) -> Float64Array: @@ -318,8 +312,7 @@ def RegisterValues(self) -> Float64Array: Original COM help: https://opendss.epri.com/RegisterValues1.html ''' - self._check_for_error(self._lib.Meters_Get_RegisterValues_GR()) - return self._get_float64_gr_array() + return self._lib.Meters_Get_RegisterValues_GR() @property def SAIDI(self) -> float: @@ -328,7 +321,7 @@ def SAIDI(self) -> float: Original COM help: https://opendss.epri.com/SAIDI.html ''' - return self._check_for_error(self._lib.Meters_Get_SAIDI()) + return self._lib.Meters_Get_SAIDI() @property def SAIFI(self) -> float: @@ -337,7 +330,7 @@ def SAIFI(self) -> float: Original COM help: https://opendss.epri.com/SAIFI.html ''' - return self._check_for_error(self._lib.Meters_Get_SAIFI()) + return self._lib.Meters_Get_SAIFI() @property def SAIFIKW(self) -> float: @@ -346,7 +339,7 @@ def SAIFIKW(self) -> float: Original COM help: https://opendss.epri.com/SAIFIKW.html ''' - return self._check_for_error(self._lib.Meters_Get_SAIFIKW()) + return self._lib.Meters_Get_SAIFIKW() @property def SectSeqIdx(self) -> int: @@ -355,7 +348,7 @@ def SectSeqIdx(self) -> int: Original COM help: https://opendss.epri.com/SectSeqIdx.html ''' - return self._check_for_error(self._lib.Meters_Get_SectSeqIdx()) + return self._lib.Meters_Get_SectSeqIdx() @property def SectTotalCust(self) -> int: @@ -364,7 +357,7 @@ def SectTotalCust(self) -> int: Original COM help: https://opendss.epri.com/SectTotalCust.html ''' - return self._check_for_error(self._lib.Meters_Get_SectTotalCust()) + return self._lib.Meters_Get_SectTotalCust() @property def SeqListSize(self) -> int: @@ -373,7 +366,7 @@ def SeqListSize(self) -> int: Original COM help: https://opendss.epri.com/SeqListSize.html ''' - return self._check_for_error(self._lib.Meters_Get_SeqListSize()) + return self._lib.Meters_Get_SeqListSize() @property def SequenceIndex(self) -> int: @@ -383,11 +376,11 @@ def SequenceIndex(self) -> int: Original COM help: https://opendss.epri.com/SequenceIndex.html ''' - return self._check_for_error(self._lib.Meters_Get_SequenceIndex()) + return self._lib.Meters_Get_SequenceIndex() @SequenceIndex.setter def SequenceIndex(self, Value: int): - self._check_for_error(self._lib.Meters_Set_SequenceIndex(Value)) + self._lib.Meters_Set_SequenceIndex(Value) @property def SumBranchFltRates(self) -> float: @@ -396,7 +389,7 @@ def SumBranchFltRates(self) -> float: Original COM help: https://opendss.epri.com/SumBranchFltRates.html ''' - return self._check_for_error(self._lib.Meters_Get_SumBranchFltRates()) + return self._lib.Meters_Get_SumBranchFltRates() @property def TotalCustomers(self) -> int: @@ -405,7 +398,7 @@ def TotalCustomers(self) -> int: Original COM help: https://opendss.epri.com/TotalCustomers.html ''' - return self._check_for_error(self._lib.Meters_Get_TotalCustomers()) + return self._lib.Meters_Get_TotalCustomers() @property def Totals(self) -> Float64Array: @@ -414,8 +407,7 @@ def Totals(self) -> Float64Array: Original COM help: https://opendss.epri.com/Totals.html ''' - self._check_for_error(self._lib.Meters_Get_Totals_GR()) - return self._get_float64_gr_array() + return self._lib.Meters_Get_Totals_GR() @property def ZonePCE(self) -> List[str]: @@ -424,7 +416,7 @@ def ZonePCE(self) -> List[str]: Original COM help: https://opendss.epri.com/ZonePCE.html ''' - result = self._check_for_error(self._get_string_array(self._lib.Meters_Get_ZonePCE)) + result = self._lib.Meters_Get_ZonePCE() if not result: result = ['NONE'] #TODO: remove diff --git a/dss/IMonitors.py b/dss/IMonitors.py index 6b14f7f6..12638334 100644 --- a/dss/IMonitors.py +++ b/dss/IMonitors.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import DSSException, Iterable import numpy as np from typing import List, AnyStr @@ -34,7 +34,7 @@ def Channel(self, Index: int) -> Float32Array: Original COM help: https://opendss.epri.com/Channel.html ''' - num_channels = self._check_for_error(self._lib.Monitors_Get_NumChannels()) + num_channels = self._lib.Monitors_Get_NumChannels() if Index < 1 or Index > num_channels: raise DSSException( 0, @@ -43,8 +43,10 @@ def Channel(self, Index: int) -> Float32Array: )) ffi = self._api_util.ffi - self._check_for_error(self._lib.Monitors_Get_ByteStream_GR()) - ptr, cnt = self._api_util.gr_int8_pointers + api_util = self._api_util + api_util.lib_unpatched.Monitors_Get_ByteStream_GR(api_util.ctx) + api_util._check_for_error() + ptr, cnt = api_util.gr_int8_pointers cnt = cnt[0] if cnt == 272: return np.zeros((1,), dtype=np.float32) @@ -64,8 +66,10 @@ def AsMatrix(self) -> Float64Array: ''' ffi = self._api_util.ffi - self._check_for_error(self._lib.Monitors_Get_ByteStream_GR()) - ptr, cnt = self._api_util.gr_int8_pointers + api_util = self._api_util + api_util.lib_unpatched.Monitors_Get_ByteStream_GR(api_util.ctx) + api_util._check_for_error() + ptr, cnt = api_util.gr_int8_pointers cnt = cnt[0] if cnt == 272: return None #np.zeros((0,), dtype=np.float32) @@ -82,7 +86,7 @@ def Process(self): Original COM help: https://opendss.epri.com/Process.html ''' - self._check_for_error(self._lib.Monitors_Process()) + self._lib.Monitors_Process() def ProcessAll(self): ''' @@ -90,7 +94,7 @@ def ProcessAll(self): Original COM help: https://opendss.epri.com/ProcessAll.html ''' - self._check_for_error(self._lib.Monitors_ProcessAll()) + self._lib.Monitors_ProcessAll() def Reset(self): ''' @@ -98,7 +102,7 @@ def Reset(self): Original COM help: https://opendss.epri.com/Reset3.html ''' - self._check_for_error(self._lib.Monitors_Reset()) + self._lib.Monitors_Reset() def ResetAll(self): ''' @@ -106,7 +110,7 @@ def ResetAll(self): Original COM help: https://opendss.epri.com/ResetAll1.html ''' - self._check_for_error(self._lib.Monitors_ResetAll()) + self._lib.Monitors_ResetAll() def Sample(self): ''' @@ -114,7 +118,7 @@ def Sample(self): Original COM help: https://opendss.epri.com/Sample2.html ''' - self._check_for_error(self._lib.Monitors_Sample()) + self._lib.Monitors_Sample() def SampleAll(self): ''' @@ -122,7 +126,7 @@ def SampleAll(self): Original COM help: https://opendss.epri.com/SampleAll1.html ''' - self._check_for_error(self._lib.Monitors_SampleAll()) + self._lib.Monitors_SampleAll() def Save(self): ''' @@ -134,7 +138,7 @@ def Save(self): Original COM help: https://opendss.epri.com/Save1.html ''' - self._check_for_error(self._lib.Monitors_Save()) + self._lib.Monitors_Save() def SaveAll(self): ''' @@ -144,7 +148,7 @@ def SaveAll(self): Original COM help: https://opendss.epri.com/SaveAll1.html ''' - self._check_for_error(self._lib.Monitors_SaveAll()) + self._lib.Monitors_SaveAll() def Show(self): ''' @@ -152,7 +156,7 @@ def Show(self): Original COM help: https://opendss.epri.com/Show3.html ''' - self._check_for_error(self._lib.Monitors_Show()) + self._lib.Monitors_Show() @property def ByteStream(self) -> Int8Array: @@ -161,8 +165,7 @@ def ByteStream(self) -> Int8Array: Original COM help: https://opendss.epri.com/ByteStream.html ''' - self._check_for_error(self._lib.Monitors_Get_ByteStream_GR()) - return self._get_int8_gr_array() + return self._lib.Monitors_Get_ByteStream_GR() @property def Element(self) -> str: @@ -171,14 +174,11 @@ def Element(self) -> str: Original COM help: https://opendss.epri.com/Element.html ''' - return self._get_string(self._check_for_error(self._lib.Monitors_Get_Element())) + return self._lib.Monitors_Get_Element() @Element.setter def Element(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Monitors_Set_Element(Value)) + self._lib.Monitors_Set_Element(Value) @property def FileName(self) -> str: @@ -187,7 +187,7 @@ def FileName(self) -> str: Original COM help: https://opendss.epri.com/FileName.html ''' - return self._get_string(self._check_for_error(self._lib.Monitors_Get_FileName())) + return self._lib.Monitors_Get_FileName() @property def FileVersion(self) -> int: @@ -196,7 +196,7 @@ def FileVersion(self) -> int: Original COM help: https://opendss.epri.com/FileVersion.html ''' - return self._check_for_error(self._lib.Monitors_Get_FileVersion()) + return self._lib.Monitors_Get_FileVersion() @property def Header(self) -> List[str]: @@ -205,20 +205,20 @@ def Header(self) -> List[str]: Original COM help: https://opendss.epri.com/Header.html ''' - return self._check_for_error(self._get_string_array(self._lib.Monitors_Get_Header)) + return self._lib.Monitors_Get_Header() @property def Mode(self) -> int: ''' - Set Monitor mode (bitmask integer - see DSS Help) + Monitor mode (bitmask integer - see DSS Help) Original COM help: https://opendss.epri.com/Mode1.html ''' - return self._check_for_error(self._lib.Monitors_Get_Mode()) # TODO: expose this better + return self._lib.Monitors_Get_Mode() # TODO: expose this better @Mode.setter def Mode(self, Value: int): - self._check_for_error(self._lib.Monitors_Set_Mode(Value)) + self._lib.Monitors_Set_Mode(Value) @property def NumChannels(self) -> int: @@ -227,7 +227,7 @@ def NumChannels(self) -> int: Original COM help: https://opendss.epri.com/NumChannels.html ''' - return self._check_for_error(self._lib.Monitors_Get_NumChannels()) + return self._lib.Monitors_Get_NumChannels() @property def RecordSize(self) -> int: @@ -236,7 +236,7 @@ def RecordSize(self) -> int: Original COM help: https://opendss.epri.com/RecordSize.html ''' - return self._check_for_error(self._lib.Monitors_Get_RecordSize()) + return self._lib.Monitors_Get_RecordSize() @property def SampleCount(self) -> int: @@ -245,7 +245,7 @@ def SampleCount(self) -> int: Original COM help: https://opendss.epri.com/SampleCount.html ''' - return self._check_for_error(self._lib.Monitors_Get_SampleCount()) + return self._lib.Monitors_Get_SampleCount() @property def Terminal(self) -> int: @@ -254,11 +254,11 @@ def Terminal(self) -> int: Original COM help: https://opendss.epri.com/Terminal.html ''' - return self._check_for_error(self._lib.Monitors_Get_Terminal()) + return self._lib.Monitors_Get_Terminal() @Terminal.setter def Terminal(self, Value: int): - self._check_for_error(self._lib.Monitors_Set_Terminal(Value)) + self._lib.Monitors_Set_Terminal(Value) @property def dblFreq(self) -> Float64Array: @@ -267,8 +267,7 @@ def dblFreq(self) -> Float64Array: Original COM help: https://opendss.epri.com/dblFreq.html ''' - self._check_for_error(self._lib.Monitors_Get_dblFreq_GR()) - return self._get_float64_gr_array() + return self._lib.Monitors_Get_dblFreq_GR() @property def dblHour(self) -> Float64Array: @@ -277,5 +276,4 @@ def dblHour(self) -> Float64Array: Original COM help: https://opendss.epri.com/dblHour.html ''' - self._check_for_error(self._lib.Monitors_Get_dblHour_GR()) - return self._get_float64_gr_array() + return self._lib.Monitors_Get_dblHour_GR() diff --git a/dss/IPDElements.py b/dss/IPDElements.py index 2bb32178..c5e5ce05 100644 --- a/dss/IPDElements.py +++ b/dss/IPDElements.py @@ -1,12 +1,26 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from __future__ import annotations from ._cffi_api_util import Base from typing import List, AnyStr, Iterator -from ._types import Float64Array, Int32Array, Float64ArrayOrComplexArray +from ._types import Float64Array, Int32Array, ComplexArray class IPDElements(Base): + ''' + The PDElements interface allows accessing some common properties and + methods shared across power delivery elements in the DSS engine. + + Users can iterate on all PD elements directly through this interface, + or enable a PD element through a dedicated interface (e.g. use `Lines.Name`, `Transformers.First/Next`) + and access the properties here. + + If you are new to OpenDSS/AltDSS and this classic interface, please read the following document + for an overview of the "active element" paradigm used by COM and the classic APIs: + + https://dss-extensions.org/classic_api.html#the-active-paradigm + ''' + __slots__ = [] _columns = [ @@ -34,7 +48,7 @@ def AccumulatedL(self) -> float: Original COM help: https://opendss.epri.com/AccumulatedL.html ''' - return self._check_for_error(self._lib.PDElements_Get_AccumulatedL()) + return self._lib.PDElements_Get_AccumulatedL() @property def Count(self) -> int: @@ -43,10 +57,10 @@ def Count(self) -> int: Original COM help: https://opendss.epri.com/Count12.html ''' - return self._check_for_error(self._lib.PDElements_Get_Count()) + return self._lib.PDElements_Get_Count() def __len__(self) -> int: - return self._check_for_error(self._lib.PDElements_Get_Count()) + return self._lib.PDElements_Get_Count() @property def FaultRate(self) -> float: @@ -54,11 +68,11 @@ def FaultRate(self) -> float: Get/Set Number of failures per year. For LINE elements: Number of failures per unit length per year. ''' - return self._check_for_error(self._lib.PDElements_Get_FaultRate()) + return self._lib.PDElements_Get_FaultRate() @FaultRate.setter def FaultRate(self, Value: float): - self._check_for_error(self._lib.PDElements_Set_FaultRate(Value)) + self._lib.PDElements_Set_FaultRate(Value) @property def First(self) -> int: @@ -66,7 +80,7 @@ def First(self) -> int: (read-only) Set the first enabled PD element to be the active element. Returns 0 if none found. ''' - return self._check_for_error(self._lib.PDElements_Get_First()) + return self._lib.PDElements_Get_First() @property def FromTerminal(self) -> int: @@ -76,7 +90,7 @@ def FromTerminal(self) -> int: *Requires an energy meter with an updated zone.* ''' - return self._check_for_error(self._lib.PDElements_Get_FromTerminal()) + return self._lib.PDElements_Get_FromTerminal() @property def IsShunt(self) -> bool: @@ -85,7 +99,7 @@ def IsShunt(self) -> bool: element rather than a series element. Applies to Capacitor and Reactor elements in particular. ''' - return self._check_for_error(self._lib.PDElements_Get_IsShunt()) != 0 + return self._lib.PDElements_Get_IsShunt() @property def Lambda(self) -> float: @@ -96,7 +110,7 @@ def Lambda(self) -> float: Original COM help: https://opendss.epri.com/Lambda1.html ''' - return self._check_for_error(self._lib.PDElements_Get_Lambda()) + return self._lib.PDElements_Get_Lambda() @property def Name(self) -> str: @@ -104,14 +118,11 @@ def Name(self) -> str: Get/Set name of active PD Element. Returns null string if active element is not PDElement type. ''' - return self._get_string(self._check_for_error(self._lib.PDElements_Get_Name())) + return self._lib.PDElements_Get_Name() @Name.setter def Name(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.PDElements_Set_Name(Value)) + self._lib.PDElements_Set_Name(Value) @property def Next(self) -> int: @@ -119,7 +130,7 @@ def Next(self) -> int: (read-only) Advance to the next PD element in the circuit. Enabled elements only. Returns 0 when no more elements. ''' - return self._check_for_error(self._lib.PDElements_Get_Next()) + return self._lib.PDElements_Get_Next() @property def Numcustomers(self) -> int: @@ -130,7 +141,7 @@ def Numcustomers(self) -> int: Original COM help: https://opendss.epri.com/Numcustomers.html ''' - return self._check_for_error(self._lib.PDElements_Get_Numcustomers()) + return self._lib.PDElements_Get_Numcustomers() @property def ParentPDElement(self) -> int: @@ -140,7 +151,7 @@ def ParentPDElement(self) -> int: *Requires an energy meter with an updated zone.* ''' - return self._check_for_error(self._lib.PDElements_Get_ParentPDElement()) + return self._lib.PDElements_Get_ParentPDElement() @property def RepairTime(self) -> float: @@ -149,11 +160,11 @@ def RepairTime(self) -> float: Original COM help: https://opendss.epri.com/RepairTime.html ''' - return self._check_for_error(self._lib.PDElements_Get_RepairTime()) + return self._lib.PDElements_Get_RepairTime() @RepairTime.setter def RepairTime(self, Value: float): - self._check_for_error(self._lib.PDElements_Set_RepairTime(Value)) + self._lib.PDElements_Set_RepairTime(Value) @property def SectionID(self) -> int: @@ -164,7 +175,7 @@ def SectionID(self) -> int: Original COM help: https://opendss.epri.com/SectionID1.html ''' - return self._check_for_error(self._lib.PDElements_Get_SectionID()) + return self._lib.PDElements_Get_SectionID() @property def TotalMiles(self) -> float: @@ -175,7 +186,7 @@ def TotalMiles(self) -> float: Original COM help: https://opendss.epri.com/TotalMiles1.html ''' - return self._check_for_error(self._lib.PDElements_Get_TotalMiles()) + return self._lib.PDElements_Get_TotalMiles() @property def Totalcustomers(self) -> int: @@ -186,7 +197,7 @@ def Totalcustomers(self) -> int: Original COM help: https://opendss.epri.com/TotalCustomers1.html ''' - return self._check_for_error(self._lib.PDElements_Get_Totalcustomers()) + return self._lib.PDElements_Get_Totalcustomers() @property def pctPermanent(self) -> float: @@ -195,11 +206,11 @@ def pctPermanent(self) -> float: Original COM help: https://opendss.epri.com/pctPermanent.html ''' - return self._check_for_error(self._lib.PDElements_Get_pctPermanent()) + return self._lib.PDElements_Get_pctPermanent() @pctPermanent.setter def pctPermanent(self, Value: float): - self._check_for_error(self._lib.PDElements_Set_pctPermanent(Value)) + self._lib.PDElements_Set_pctPermanent(Value) def __iter__(self) -> Iterator[IPDElements]: idx = self.First @@ -214,7 +225,7 @@ def AllNames(self) -> List[str]: **(API Extension)** ''' - return self._check_for_error(self._get_string_array(self._lib.PDElements_Get_AllNames)) + return self._lib.PDElements_Get_AllNames() def AllMaxCurrents(self, AllNodes: bool = False) -> Float64Array: ''' @@ -230,8 +241,7 @@ def AllMaxCurrents(self, AllNodes: bool = False) -> Float64Array: **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllMaxCurrents_GR(AllNodes)) - return self._get_float64_gr_array() + return self._lib.PDElements_Get_AllMaxCurrents_GR(AllNodes) def AllPctNorm(self, AllNodes: bool = False) -> Float64Array: ''' @@ -247,9 +257,8 @@ def AllPctNorm(self, AllNodes: bool = False) -> Float64Array: **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllPctNorm_GR(AllNodes)) - return self._get_float64_gr_array() - + return self._lib.PDElements_Get_AllPctNorm_GR(AllNodes) + def AllPctEmerg(self, AllNodes: bool = False) -> Float64Array: ''' Array of doubles with the maximum current across the conductors as a percentage @@ -264,18 +273,16 @@ def AllPctEmerg(self, AllNodes: bool = False) -> Float64Array: **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllPctEmerg_GR(AllNodes)) - return self._get_float64_gr_array() + return self._lib.PDElements_Get_AllPctEmerg_GR(AllNodes) @property - def AllCurrents(self) -> Float64ArrayOrComplexArray: + def AllCurrents(self) -> ComplexArray: ''' Complex array of currents for all conductors, all terminals, for each PD element. **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllCurrents_GR()) - return self._get_complex128_gr_array() + return self._lib.PDElements_Get_AllCurrents_GR() @property def AllCurrentsMagAng(self) -> Float64Array: @@ -284,18 +291,16 @@ def AllCurrentsMagAng(self) -> Float64Array: **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllCurrentsMagAng_GR()) - return self._get_float64_gr_array() + return self._lib.PDElements_Get_AllCurrentsMagAng_GR() @property - def AllCplxSeqCurrents(self) -> Float64ArrayOrComplexArray: + def AllCplxSeqCurrents(self) -> ComplexArray: ''' Complex double array of Sequence Currents for all conductors of all terminals, for each PD elements. **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllCplxSeqCurrents_GR()) - return self._get_complex128_gr_array() + return self._lib.PDElements_Get_AllCplxSeqCurrents_GR() @property def AllSeqCurrents(self) -> Float64Array: @@ -304,28 +309,25 @@ def AllSeqCurrents(self) -> Float64Array: **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllSeqCurrents_GR()) - return self._get_float64_gr_array() + return self._lib.PDElements_Get_AllSeqCurrents_GR() @property - def AllPowers(self) -> Float64ArrayOrComplexArray: + def AllPowers(self) -> ComplexArray: ''' Complex array of powers into each conductor of each terminal, for each PD element. **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllPowers_GR()) - return self._get_complex128_gr_array() + return self._lib.PDElements_Get_AllPowers_GR() @property - def AllSeqPowers(self) -> Float64ArrayOrComplexArray: + def AllSeqPowers(self) -> ComplexArray: ''' Complex array of sequence powers into each 3-phase terminal, for each PD element **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllSeqPowers_GR()) - return self._get_complex128_gr_array() + return self._lib.PDElements_Get_AllSeqPowers_GR() @property def AllNumPhases(self) -> Int32Array: @@ -334,8 +336,7 @@ def AllNumPhases(self) -> Int32Array: **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllNumPhases_GR()) - return self._get_int32_gr_array() + return self._lib.PDElements_Get_AllNumPhases_GR() @property def AllNumConductors(self) -> Int32Array: @@ -344,9 +345,7 @@ def AllNumConductors(self) -> Int32Array: **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllNumConductors_GR()) - return self._get_int32_gr_array() - + return self._lib.PDElements_Get_AllNumConductors_GR() @property def AllNumTerminals(self) -> Int32Array: @@ -355,7 +354,6 @@ def AllNumTerminals(self) -> Int32Array: **(API Extension)** ''' - self._check_for_error(self._lib.PDElements_Get_AllNumTerminals_GR()) - return self._get_int32_gr_array() + return self._lib.PDElements_Get_AllNumTerminals_GR() diff --git a/dss/IPVSystems.py b/dss/IPVSystems.py index 644d57ee..b121664f 100644 --- a/dss/IPVSystems.py +++ b/dss/IPVSystems.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Float64Array from typing import List, AnyStr @@ -37,11 +37,11 @@ def Irradiance(self) -> float: Original COM help: https://opendss.epri.com/Irradiance.html ''' - return self._check_for_error(self._lib.PVSystems_Get_Irradiance()) + return self._lib.PVSystems_Get_Irradiance() @Irradiance.setter def Irradiance(self, Value: float): - self._check_for_error(self._lib.PVSystems_Set_Irradiance(Value)) + self._lib.PVSystems_Set_Irradiance(Value) @property def PF(self) -> float: @@ -50,11 +50,11 @@ def PF(self) -> float: Original COM help: https://opendss.epri.com/PF2.html ''' - return self._check_for_error(self._lib.PVSystems_Get_PF()) + return self._lib.PVSystems_Get_PF() @PF.setter def PF(self, Value: float): - self._check_for_error(self._lib.PVSystems_Set_PF(Value)) + self._lib.PVSystems_Set_PF(Value) @property def RegisterNames(self) -> List[str]: @@ -65,7 +65,7 @@ def RegisterNames(self) -> List[str]: Original COM help: https://opendss.epri.com/RegisterNames2.html ''' - return self._check_for_error(self._get_string_array(self._lib.PVSystems_Get_RegisterNames)) + return self._lib.PVSystems_Get_RegisterNames() @property def RegisterValues(self) -> Float64Array: @@ -74,8 +74,7 @@ def RegisterValues(self) -> Float64Array: Original COM help: https://opendss.epri.com/RegisterValues2.html ''' - self._check_for_error(self._lib.PVSystems_Get_RegisterValues_GR()) - return self._get_float64_gr_array() + return self._lib.PVSystems_Get_RegisterValues_GR() @property def kVArated(self) -> float: @@ -84,11 +83,11 @@ def kVArated(self) -> float: Original COM help: https://opendss.epri.com/kVArated1.html ''' - return self._check_for_error(self._lib.PVSystems_Get_kVArated()) + return self._lib.PVSystems_Get_kVArated() @kVArated.setter def kVArated(self, Value: float): - self._check_for_error(self._lib.PVSystems_Set_kVArated(Value)) + self._lib.PVSystems_Set_kVArated(Value) @property def kW(self) -> float: @@ -97,7 +96,7 @@ def kW(self) -> float: Original COM help: https://opendss.epri.com/kW2.html ''' - return self._check_for_error(self._lib.PVSystems_Get_kW()) + return self._lib.PVSystems_Get_kW() @property def kvar(self) -> float: @@ -106,11 +105,11 @@ def kvar(self) -> float: Original COM help: https://opendss.epri.com/kvar2.html ''' - return self._check_for_error(self._lib.PVSystems_Get_kvar()) + return self._lib.PVSystems_Get_kvar() @kvar.setter def kvar(self, Value: float): - self._check_for_error(self._lib.PVSystems_Set_kvar(Value)) + self._lib.PVSystems_Set_kvar(Value) @property def daily(self) -> str: @@ -121,14 +120,11 @@ def daily(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.PVSystems_Get_daily())) + return self._lib.PVSystems_Get_daily() @daily.setter def daily(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.PVSystems_Set_daily(Value)) + self._lib.PVSystems_Set_daily(Value) @property def duty(self) -> str: @@ -139,14 +135,11 @@ def duty(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.PVSystems_Get_duty())) + return self._lib.PVSystems_Get_duty() @duty.setter def duty(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.PVSystems_Set_duty(Value)) + self._lib.PVSystems_Set_duty(Value) @property def yearly(self) -> str: @@ -158,14 +151,11 @@ def yearly(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.PVSystems_Get_yearly())) + return self._lib.PVSystems_Get_yearly() @yearly.setter def yearly(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.PVSystems_Set_yearly(Value)) + self._lib.PVSystems_Set_yearly(Value) @property def Tdaily(self) -> str: @@ -177,14 +167,11 @@ def Tdaily(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.PVSystems_Get_Tdaily())) + return self._lib.PVSystems_Get_Tdaily() @Tdaily.setter def Tdaily(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.PVSystems_Set_Tdaily(Value)) + self._lib.PVSystems_Set_Tdaily(Value) @property def Tduty(self) -> str: @@ -199,14 +186,11 @@ def Tduty(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.PVSystems_Get_Tduty())) + return self._lib.PVSystems_Get_Tduty() @Tduty.setter def Tduty(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.PVSystems_Set_Tduty(Value)) + self._lib.PVSystems_Set_Tduty(Value) @property def Tyearly(self) -> str: @@ -219,14 +203,11 @@ def Tyearly(self) -> str: **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.PVSystems_Get_Tyearly())) + return self._lib.PVSystems_Get_Tyearly() @Tyearly.setter def Tyearly(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.PVSystems_Set_Tyearly(Value)) + self._lib.PVSystems_Set_Tyearly(Value) @property def IrradianceNow(self) -> float: @@ -236,7 +217,7 @@ def IrradianceNow(self) -> float: Original COM help: https://opendss.epri.com/IrradianceNow.html ''' - return self._check_for_error(self._lib.PVSystems_Get_IrradianceNow()) + return self._lib.PVSystems_Get_IrradianceNow() @property def Pmpp(self) -> float: @@ -246,11 +227,11 @@ def Pmpp(self) -> float: Original COM help: https://opendss.epri.com/Pmpp.html ''' - return self._check_for_error(self._lib.PVSystems_Get_Pmpp()) + return self._lib.PVSystems_Get_Pmpp() @Pmpp.setter def Pmpp(self, Value: float): - self._check_for_error(self._lib.PVSystems_Set_Pmpp(Value)) + self._lib.PVSystems_Set_Pmpp(Value) @property def Sensor(self) -> str: @@ -259,4 +240,4 @@ def Sensor(self) -> str: Original COM help: https://opendss.epri.com/Sensor1.html ''' - return self._get_string(self._check_for_error(self._lib.PVSystems_Get_Sensor())) + return self._lib.PVSystems_Get_Sensor() diff --git a/dss/IParallel.py b/dss/IParallel.py index 89a529d9..7e717782 100644 --- a/dss/IParallel.py +++ b/dss/IParallel.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base from ._types import Int32Array @@ -18,7 +18,7 @@ def CreateActor(self): ''' Create a new actor, if there are still cores available. ''' - self._check_for_error(self._lib.Parallel_CreateActor()) + self._lib.Parallel_CreateActor() def Wait(self): ''' @@ -26,7 +26,7 @@ def Wait(self): Original COM help: https://opendss.epri.com/Wait.html ''' - self._check_for_error(self._lib.Parallel_Wait()) + self._lib.Parallel_Wait() @property def ActiveActor(self) -> int: @@ -35,11 +35,11 @@ def ActiveActor(self) -> int: Original COM help: https://opendss.epri.com/ActiveActor.html ''' - return self._check_for_error(self._lib.Parallel_Get_ActiveActor()) + return self._lib.Parallel_Get_ActiveActor() @ActiveActor.setter def ActiveActor(self, Value: int): - self._check_for_error(self._lib.Parallel_Set_ActiveActor(Value)) + self._lib.Parallel_Set_ActiveActor(Value) @property def ActiveParallel(self) -> int: @@ -49,11 +49,11 @@ def ActiveParallel(self) -> int: Original COM help: https://opendss.epri.com/ActiveParallel.html ''' - return self._check_for_error(self._lib.Parallel_Get_ActiveParallel()) #TODO: use boolean for consistency + return self._lib.Parallel_Get_ActiveParallel() #TODO: use boolean for consistency @ActiveParallel.setter def ActiveParallel(self, Value: int): - self._check_for_error(self._lib.Parallel_Set_ActiveParallel(Value)) + self._lib.Parallel_Set_ActiveParallel(Value) @property def ActorCPU(self) -> int: @@ -62,11 +62,11 @@ def ActorCPU(self) -> int: Original COM help: https://opendss.epri.com/ActorCPU.html ''' - return self._check_for_error(self._lib.Parallel_Get_ActorCPU()) + return self._lib.Parallel_Get_ActorCPU() @ActorCPU.setter def ActorCPU(self, Value: int): - self._check_for_error(self._lib.Parallel_Set_ActorCPU(Value)) + self._lib.Parallel_Set_ActorCPU(Value) @property def ActorProgress(self) -> Int32Array: @@ -75,8 +75,7 @@ def ActorProgress(self) -> Int32Array: Original COM help: https://opendss.epri.com/ActorProgress.html ''' - self._check_for_error(self._lib.Parallel_Get_ActorProgress_GR()) - return self._get_int32_gr_array() + return self._lib.Parallel_Get_ActorProgress_GR() @property def ActorStatus(self) -> Int32Array: @@ -85,8 +84,7 @@ def ActorStatus(self) -> Int32Array: Original COM help: https://opendss.epri.com/ActorStatus.html ''' - self._check_for_error(self._lib.Parallel_Get_ActorStatus_GR()) - return self._get_int32_gr_array() + return self._lib.Parallel_Get_ActorStatus_GR() @property def ConcatenateReports(self) -> int: @@ -96,11 +94,11 @@ def ConcatenateReports(self) -> int: Original COM help: https://opendss.epri.com/ConcatenateReports.html ''' - return self._check_for_error(self._lib.Parallel_Get_ConcatenateReports()) #TODO: use boolean for consistency + return self._lib.Parallel_Get_ConcatenateReports() #TODO: use boolean for consistency @ConcatenateReports.setter def ConcatenateReports(self, Value: int): - self._check_for_error(self._lib.Parallel_Set_ConcatenateReports(Value)) + self._lib.Parallel_Set_ConcatenateReports(Value) @property def NumCPUs(self) -> int: @@ -109,7 +107,7 @@ def NumCPUs(self) -> int: Original COM help: https://opendss.epri.com/NumCPUs.html ''' - return self._check_for_error(self._lib.Parallel_Get_NumCPUs()) + return self._lib.Parallel_Get_NumCPUs() @property def NumCores(self) -> int: @@ -118,7 +116,7 @@ def NumCores(self) -> int: Original COM help: https://opendss.epri.com/NumCores.html ''' - return self._check_for_error(self._lib.Parallel_Get_NumCores()) + return self._lib.Parallel_Get_NumCores() @property def NumOfActors(self) -> int: @@ -127,6 +125,6 @@ def NumOfActors(self) -> int: Original COM help: https://opendss.epri.com/NumOfActors.html ''' - return self._check_for_error(self._lib.Parallel_Get_NumOfActors()) + return self._lib.Parallel_Get_NumOfActors() diff --git a/dss/IParser.py b/dss/IParser.py index 8afc2c34..c844490b 100644 --- a/dss/IParser.py +++ b/dss/IParser.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base from ._types import Float64Array from typing import AnyStr @@ -14,18 +14,15 @@ class IParser(Base): def Matrix(self, ExpectedOrder: int) -> Float64Array: '''Use this property to parse a Matrix token in OpenDSS format. Returns square matrix of order specified. Order same as default Fortran order: column by column.''' - self._check_for_error(self._lib.Parser_Get_Matrix_GR(ExpectedOrder)) - return self._get_float64_gr_array() + return self._lib.Parser_Get_Matrix_GR(ExpectedOrder) def SymMatrix(self, ExpectedOrder: int) -> Float64Array: '''Use this property to parse a matrix token specified in lower triangle form. Symmetry is forced.''' - self._check_for_error(self._lib.Parser_Get_SymMatrix_GR(ExpectedOrder)) - return self._get_float64_gr_array() + return self._lib.Parser_Get_SymMatrix_GR(ExpectedOrder) def Vector(self, ExpectedSize: int) -> Float64Array: '''Returns token as array of doubles. For parsing quoted array syntax.''' - self._check_for_error(self._lib.Parser_Get_Vector_GR(ExpectedSize)) - return self._get_float64_gr_array() + return self._lib.Parser_Get_Vector_GR(ExpectedSize) def ResetDelimiters(self): ''' @@ -33,7 +30,7 @@ def ResetDelimiters(self): Original COM help: https://opendss.epri.com/ResetDelimiters.html ''' - self._check_for_error(self._lib.Parser_ResetDelimiters()) + self._lib.Parser_ResetDelimiters() @property def AutoIncrement(self) -> bool: @@ -42,11 +39,11 @@ def AutoIncrement(self) -> bool: Original COM help: https://opendss.epri.com/AutoIncrement.html ''' - return self._check_for_error(self._lib.Parser_Get_AutoIncrement()) != 0 + return self._lib.Parser_Get_AutoIncrement() @AutoIncrement.setter def AutoIncrement(self, Value: bool): - self._check_for_error(self._lib.Parser_Set_AutoIncrement(Value)) + self._lib.Parser_Set_AutoIncrement(Value) @property def BeginQuote(self) -> str: @@ -55,14 +52,11 @@ def BeginQuote(self) -> str: Original COM help: https://opendss.epri.com/BeginQuote.html ''' - return self._get_string(self._check_for_error(self._lib.Parser_Get_BeginQuote())) + return self._lib.Parser_Get_BeginQuote() @BeginQuote.setter def BeginQuote(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Parser_Set_BeginQuote(Value)) + self._lib.Parser_Set_BeginQuote(Value) @property def CmdString(self) -> str: @@ -71,14 +65,11 @@ def CmdString(self) -> str: Original COM help: https://opendss.epri.com/CmdString.html ''' - return self._get_string(self._check_for_error(self._lib.Parser_Get_CmdString())) + return self._lib.Parser_Get_CmdString() @CmdString.setter def CmdString(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Parser_Set_CmdString(Value)) + self._lib.Parser_Set_CmdString(Value) @property def DblValue(self) -> float: @@ -87,7 +78,7 @@ def DblValue(self) -> float: Original COM help: https://opendss.epri.com/DblValue.html ''' - return self._check_for_error(self._lib.Parser_Get_DblValue()) + return self._lib.Parser_Get_DblValue() @property def Delimiters(self) -> str: @@ -96,14 +87,11 @@ def Delimiters(self) -> str: Original COM help: https://opendss.epri.com/Delimiters.html ''' - return self._get_string(self._check_for_error(self._lib.Parser_Get_Delimiters())) + return self._lib.Parser_Get_Delimiters() @Delimiters.setter def Delimiters(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Parser_Set_Delimiters(Value)) + self._lib.Parser_Set_Delimiters(Value) @property def EndQuote(self) -> str: @@ -112,14 +100,11 @@ def EndQuote(self) -> str: Original COM help: https://opendss.epri.com/EndQuote.html ''' - return self._get_string(self._check_for_error(self._lib.Parser_Get_EndQuote())) + return self._lib.Parser_Get_EndQuote() @EndQuote.setter def EndQuote(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Parser_Set_EndQuote(Value)) + self._lib.Parser_Set_EndQuote(Value) @property def IntValue(self) -> int: @@ -128,7 +113,7 @@ def IntValue(self) -> int: Original COM help: https://opendss.epri.com/IntValue.html ''' - return self._check_for_error(self._lib.Parser_Get_IntValue()) + return self._lib.Parser_Get_IntValue() @property def NextParam(self) -> str: @@ -137,7 +122,7 @@ def NextParam(self) -> str: Original COM help: https://opendss.epri.com/NextParam.html ''' - return self._get_string(self._check_for_error(self._lib.Parser_Get_NextParam())) + return self._lib.Parser_Get_NextParam() @property def StrValue(self) -> str: @@ -146,7 +131,7 @@ def StrValue(self) -> str: Original COM help: https://opendss.epri.com/StrValue.html ''' - return self._get_string(self._check_for_error(self._lib.Parser_Get_StrValue())) + return self._lib.Parser_Get_StrValue() @property def WhiteSpace(self) -> str: @@ -155,12 +140,9 @@ def WhiteSpace(self) -> str: Original COM help: https://opendss.epri.com/WhiteSpace.html ''' - return self._get_string(self._check_for_error(self._lib.Parser_Get_WhiteSpace())) + return self._lib.Parser_Get_WhiteSpace() @WhiteSpace.setter def WhiteSpace(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Parser_Set_WhiteSpace(Value)) + self._lib.Parser_Set_WhiteSpace(Value) diff --git a/dss/IReactors.py b/dss/IReactors.py index 5a1963a4..e0604c21 100644 --- a/dss/IReactors.py +++ b/dss/IReactors.py @@ -1,15 +1,15 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from typing import AnyStr -from ._types import Float64Array, Float64ArrayOrSimpleComplex +from ._types import Float64Matrix, Complex from ._cffi_api_util import Iterable class IReactors(Iterable): ''' Reactor objects - (API Extension) + API Status: **(API Extension)** before 2024-10-04. Since then, functions partially marked as extensions (see each property/function documentation). ''' __slots__ = [] @@ -44,62 +44,74 @@ def SpecType(self) -> int: ''' How the reactor data was provided: 1=kvar, 2=R+jX, 3=R and X matrices, 4=sym components. Depending on this value, only some properties are filled or make sense in the context. + + **(API Extension)** ''' - return self._check_for_error(self._lib.Reactors_Get_SpecType()) #TODO: use enum + return self._lib.Reactors_Get_SpecType() #TODO: use enum @property def IsDelta(self) -> bool: - '''Delta connection or wye?''' - return self._check_for_error(self._lib.Reactors_Get_IsDelta()) != 0 + ''' + Delta connection or wye? + + **(API Extension)** + ''' + return self._lib.Reactors_Get_IsDelta() @IsDelta.setter def IsDelta(self, Value: bool): - self._check_for_error(self._lib.Reactors_Set_IsDelta(Value)) + self._lib.Reactors_Set_IsDelta(Value) @property def Parallel(self) -> bool: '''Indicates whether Rmatrix and Xmatrix are to be considered in parallel.''' - return self._check_for_error(self._lib.Reactors_Get_Parallel()) != 0 + return self._lib.Reactors_Get_Parallel() @Parallel.setter def Parallel(self, Value: bool): - self._check_for_error(self._lib.Reactors_Set_Parallel(Value)) + self._lib.Reactors_Set_Parallel(Value) @property def LmH(self) -> float: '''Inductance, mH. Alternate way to define the reactance, X, property.''' - return self._check_for_error(self._lib.Reactors_Get_LmH()) + return self._lib.Reactors_Get_LmH() @LmH.setter def LmH(self, Value: float): - self._check_for_error(self._lib.Reactors_Set_LmH(Value)) + self._lib.Reactors_Set_LmH(Value) + + lmH = LmH # Compatibility for the new @property def kV(self) -> float: '''For 2, 3-phase, kV phase-phase. Otherwise specify actual coil rating.''' - return self._check_for_error(self._lib.Reactors_Get_kV()) + return self._lib.Reactors_Get_kV() @kV.setter def kV(self, Value: float): - self._check_for_error(self._lib.Reactors_Set_kV(Value)) + self._lib.Reactors_Set_kV(Value) @property def kvar(self) -> float: '''Total kvar, all phases. Evenly divided among phases. Only determines X. Specify R separately''' - return self._check_for_error(self._lib.Reactors_Get_kvar()) + return self._lib.Reactors_Get_kvar() @kvar.setter def kvar(self, Value: float): - self._check_for_error(self._lib.Reactors_Set_kvar(Value)) + self._lib.Reactors_Set_kvar(Value) @property def Phases(self) -> int: - '''Number of phases.''' - return self._check_for_error(self._lib.Reactors_Get_Phases()) + ''' + Number of phases. + + **(API Extension)** + ''' + return self._lib.Reactors_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.Reactors_Set_Phases(Value)) + self._lib.Reactors_Set_Phases(Value) @property def Bus1(self) -> str: @@ -107,117 +119,106 @@ def Bus1(self) -> str: Name of first bus. Bus2 property will default to this bus, node 0, unless previously specified. Only Bus1 need be specified for a Yg shunt reactor. + + **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.Reactors_Get_Bus1())) + return self._lib.Reactors_Get_Bus1() @Bus1.setter def Bus1(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Reactors_Set_Bus1(Value)) + self._lib.Reactors_Set_Bus1(Value) @property def Bus2(self) -> str: ''' Name of 2nd bus. Defaults to all phases connected to first bus, node 0, (Shunt Wye Connection) except when Bus2 is specifically defined. - Not necessary to specify for delta (LL) connection + Not necessary to specify for delta (LL) connection. + + **(API Extension)** ''' - return self._get_string(self._check_for_error(self._lib.Reactors_Get_Bus2())) + return self._lib.Reactors_Get_Bus2() @Bus2.setter def Bus2(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Reactors_Set_Bus2(Value)) + self._lib.Reactors_Set_Bus2(Value) @property def LCurve(self) -> str: '''Name of XYCurve object, previously defined, describing per-unit variation of phase inductance, L=X/w, vs. frequency. Applies to reactance specified by X, LmH, Z, or kvar property. L generally decreases somewhat with frequency above the base frequency, approaching a limit at a few kHz.''' - return self._get_string(self._check_for_error(self._lib.Reactors_Get_LCurve())) + return self._lib.Reactors_Get_LCurve() @LCurve.setter def LCurve(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Reactors_Set_LCurve(Value)) + self._lib.Reactors_Set_LCurve(Value) @property def RCurve(self) -> str: '''Name of XYCurve object, previously defined, describing per-unit variation of phase resistance, R, vs. frequency. Applies to resistance specified by R or Z property. If actual values are not known, R often increases by approximately the square root of frequency.''' - return self._get_string(self._check_for_error(self._lib.Reactors_Get_RCurve())) + return self._lib.Reactors_Get_RCurve() @RCurve.setter def RCurve(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Reactors_Set_RCurve(Value)) + self._lib.Reactors_Set_RCurve(Value) @property def R(self) -> float: '''Resistance (in series with reactance), each phase, ohms. This property applies to REACTOR specified by either kvar or X. See also help on Z.''' - return self._check_for_error(self._lib.Reactors_Get_R()) + return self._lib.Reactors_Get_R() @R.setter def R(self, Value: float): - self._check_for_error(self._lib.Reactors_Set_R(Value)) + self._lib.Reactors_Set_R(Value) @property def X(self) -> float: '''Reactance, each phase, ohms at base frequency. See also help on Z and LmH properties.''' - return self._check_for_error(self._lib.Reactors_Get_X()) + return self._lib.Reactors_Get_X() @X.setter def X(self, Value: float): - self._check_for_error(self._lib.Reactors_Set_X(Value)) + self._lib.Reactors_Set_X(Value) @property def Rp(self) -> float: '''Resistance in parallel with R and X (the entire branch). Assumed infinite if not specified.''' - return self._check_for_error(self._lib.Reactors_Get_Rp()) + return self._lib.Reactors_Get_Rp() @Rp.setter def Rp(self, Value: float): - self._check_for_error(self._lib.Reactors_Set_Rp(Value)) + self._lib.Reactors_Set_Rp(Value) @property - def Rmatrix(self) -> Float64Array: + def Rmatrix(self) -> Float64Matrix: '''Resistance matrix, ohms at base frequency. Order of the matrix is the number of phases. Mutually exclusive to specifying parameters by kvar or X.''' - self._check_for_error(self._lib.Reactors_Get_Rmatrix_GR()) - return self._get_float64_gr_array() + return self._lib.Reactors_Get_Rmatrix_GR() @Rmatrix.setter - def Rmatrix(self, Value: Float64Array): + def Rmatrix(self, Value: Float64Matrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Reactors_Set_Rmatrix(ValuePtr, ValueCount)) + self._lib.Reactors_Set_Rmatrix(ValuePtr, ValueCount) @property - def Xmatrix(self) -> Float64Array: + def Xmatrix(self) -> Float64Matrix: '''Reactance matrix, ohms at base frequency. Order of the matrix is the number of phases. Mutually exclusive to specifying parameters by kvar or X.''' - self._check_for_error(self._lib.Reactors_Get_Xmatrix_GR()) - return self._get_float64_gr_array() + return self._lib.Reactors_Get_Xmatrix_GR() @Xmatrix.setter - def Xmatrix(self, Value: Float64Array): + def Xmatrix(self, Value: Float64Matrix): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Reactors_Set_Xmatrix(ValuePtr, ValueCount)) + self._lib.Reactors_Set_Xmatrix(ValuePtr, ValueCount) @property - def Z(self) -> Float64ArrayOrSimpleComplex: + def Z(self) -> Complex: '''Alternative way of defining R and X properties. Enter a 2-element array representing R +jX in ohms.''' - self._check_for_error(self._lib.Reactors_Get_Z_GR()) - return self._get_complex128_gr_simple() + return self._lib.Reactors_Get_Z_GR() @Z.setter - def Z(self, Value: Float64ArrayOrSimpleComplex): + def Z(self, Value: Complex): Value, ValuePtr, ValueCount = self._prepare_complex128_simple(Value) - self._check_for_error(self._lib.Reactors_Set_Z(ValuePtr, ValueCount)) + self._lib.Reactors_Set_Z(ValuePtr, ValueCount) @property - def Z1(self) -> Float64ArrayOrSimpleComplex: + def Z1(self) -> Complex: ''' Positive-sequence impedance, ohms, as a 2-element array representing a complex number. @@ -227,16 +228,15 @@ def Z1(self) -> Float64ArrayOrSimpleComplex: Side Effect: Sets Z2 and Z0 to same values unless they were previously defined. ''' - self._check_for_error(self._lib.Reactors_Get_Z1_GR()) - return self._get_complex128_gr_simple() + return self._lib.Reactors_Get_Z1_GR() @Z1.setter - def Z1(self, Value: Float64ArrayOrSimpleComplex): + def Z1(self, Value: Complex): Value, ValuePtr, ValueCount = self._prepare_complex128_simple(Value) - self._check_for_error(self._lib.Reactors_Set_Z1(ValuePtr, ValueCount)) + self._lib.Reactors_Set_Z1(ValuePtr, ValueCount) @property - def Z2(self) -> Float64ArrayOrSimpleComplex: + def Z2(self) -> Complex: ''' Negative-sequence impedance, ohms, as a 2-element array representing a complex number. @@ -244,16 +244,15 @@ def Z2(self) -> Float64ArrayOrSimpleComplex: Note: Z2 defaults to Z1 if it is not specifically defined. If Z2 is not equal to Z1, the impedance matrix is asymmetrical. ''' - self._check_for_error(self._lib.Reactors_Get_Z2_GR()) - return self._get_complex128_gr_simple() + return self._lib.Reactors_Get_Z2_GR() @Z2.setter - def Z2(self, Value: Float64ArrayOrSimpleComplex): + def Z2(self, Value: Complex): Value, ValuePtr, ValueCount = self._prepare_complex128_simple(Value) - self._check_for_error(self._lib.Reactors_Set_Z2(ValuePtr, ValueCount)) + self._lib.Reactors_Set_Z2(ValuePtr, ValueCount) @property - def Z0(self) -> Float64ArrayOrSimpleComplex: + def Z0(self) -> Complex: ''' Zero-sequence impedance, ohms, as a 2-element array representing a complex number. @@ -261,11 +260,10 @@ def Z0(self) -> Float64ArrayOrSimpleComplex: Note: Z0 defaults to Z1 if it is not specifically defined. ''' - self._check_for_error(self._lib.Reactors_Get_Z0_GR()) - return self._get_complex128_gr_simple() + return self._lib.Reactors_Get_Z0_GR() @Z0.setter - def Z0(self, Value: Float64ArrayOrSimpleComplex): + def Z0(self, Value: Complex): Value, ValuePtr, ValueCount = self._prepare_complex128_simple(Value) - self._check_for_error(self._lib.Reactors_Set_Z0(ValuePtr, ValueCount)) + self._lib.Reactors_Set_Z0(ValuePtr, ValueCount) diff --git a/dss/IReclosers.py b/dss/IReclosers.py index c8d442cd..831f07ae 100644 --- a/dss/IReclosers.py +++ b/dss/IReclosers.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Float64Array from typing import AnyStr @@ -28,10 +28,10 @@ class IReclosers(Iterable): ] def Close(self): - self._check_for_error(self._lib.Reclosers_Close()) + self._lib.Reclosers_Close() def Open(self): - self._check_for_error(self._lib.Reclosers_Open()) + self._lib.Reclosers_Open() @property def GroundInst(self) -> float: @@ -40,11 +40,11 @@ def GroundInst(self) -> float: Original COM help: https://opendss.epri.com/GroundInst.html ''' - return self._check_for_error(self._lib.Reclosers_Get_GroundInst()) + return self._lib.Reclosers_Get_GroundInst() @GroundInst.setter def GroundInst(self, Value: float): - self._check_for_error(self._lib.Reclosers_Set_GroundInst(Value)) + self._lib.Reclosers_Set_GroundInst(Value) @property def GroundTrip(self) -> float: @@ -53,11 +53,11 @@ def GroundTrip(self) -> float: Original COM help: https://opendss.epri.com/GroundTrip.html ''' - return self._check_for_error(self._lib.Reclosers_Get_GroundTrip()) + return self._lib.Reclosers_Get_GroundTrip() @GroundTrip.setter def GroundTrip(self, Value: float): - self._check_for_error(self._lib.Reclosers_Set_GroundTrip(Value)) + self._lib.Reclosers_Set_GroundTrip(Value) @property def MonitoredObj(self) -> str: @@ -66,14 +66,11 @@ def MonitoredObj(self) -> str: Original COM help: https://opendss.epri.com/MonitoredObj2.html ''' - return self._get_string(self._check_for_error(self._lib.Reclosers_Get_MonitoredObj())) + return self._lib.Reclosers_Get_MonitoredObj() @MonitoredObj.setter def MonitoredObj(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Reclosers_Set_MonitoredObj(Value)) + self._lib.Reclosers_Set_MonitoredObj(Value) @property def MonitoredTerm(self) -> int: @@ -82,11 +79,11 @@ def MonitoredTerm(self) -> int: Original COM help: https://opendss.epri.com/MonitoredTerm2.html ''' - return self._check_for_error(self._lib.Reclosers_Get_MonitoredTerm()) + return self._lib.Reclosers_Get_MonitoredTerm() @MonitoredTerm.setter def MonitoredTerm(self, Value: int): - self._check_for_error(self._lib.Reclosers_Set_MonitoredTerm(Value)) + self._lib.Reclosers_Set_MonitoredTerm(Value) @property def NumFast(self) -> int: @@ -95,11 +92,11 @@ def NumFast(self) -> int: Original COM help: https://opendss.epri.com/NumFast.html ''' - return self._check_for_error(self._lib.Reclosers_Get_NumFast()) + return self._lib.Reclosers_Get_NumFast() @NumFast.setter def NumFast(self, Value: int): - self._check_for_error(self._lib.Reclosers_Set_NumFast(Value)) + self._lib.Reclosers_Set_NumFast(Value) @property def PhaseInst(self) -> float: @@ -108,11 +105,11 @@ def PhaseInst(self) -> float: Original COM help: https://opendss.epri.com/PhaseInst.html ''' - return self._check_for_error(self._lib.Reclosers_Get_PhaseInst()) + return self._lib.Reclosers_Get_PhaseInst() @PhaseInst.setter def PhaseInst(self, Value: float): - self._check_for_error(self._lib.Reclosers_Set_PhaseInst(Value)) + self._lib.Reclosers_Set_PhaseInst(Value) @property def PhaseTrip(self) -> float: @@ -121,11 +118,11 @@ def PhaseTrip(self) -> float: Original COM help: https://opendss.epri.com/PhaseTrip.html ''' - return self._check_for_error(self._lib.Reclosers_Get_PhaseTrip()) + return self._lib.Reclosers_Get_PhaseTrip() @PhaseTrip.setter def PhaseTrip(self, Value: float): - self._check_for_error(self._lib.Reclosers_Set_PhaseTrip(Value)) + self._lib.Reclosers_Set_PhaseTrip(Value) @property def RecloseIntervals(self) -> Float64Array: @@ -134,8 +131,7 @@ def RecloseIntervals(self) -> Float64Array: Original COM help: https://opendss.epri.com/RecloseIntervals.html ''' - self._check_for_error(self._lib.Reclosers_Get_RecloseIntervals_GR()) - return self._get_float64_gr_array() + return self._lib.Reclosers_Get_RecloseIntervals_GR() @property def Shots(self) -> int: @@ -144,11 +140,11 @@ def Shots(self) -> int: Original COM help: https://opendss.epri.com/Shots.html ''' - return self._check_for_error(self._lib.Reclosers_Get_Shots()) + return self._lib.Reclosers_Get_Shots() @Shots.setter def Shots(self, Value: int): - self._check_for_error(self._lib.Reclosers_Set_Shots(Value)) + self._lib.Reclosers_Set_Shots(Value) @property def SwitchedObj(self) -> str: @@ -157,14 +153,11 @@ def SwitchedObj(self) -> str: Original COM help: https://opendss.epri.com/SwitchedObj1.html ''' - return self._get_string(self._check_for_error(self._lib.Reclosers_Get_SwitchedObj())) + return self._lib.Reclosers_Get_SwitchedObj() @SwitchedObj.setter def SwitchedObj(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Reclosers_Set_SwitchedObj(Value)) + self._lib.Reclosers_Set_SwitchedObj(Value) @property def SwitchedTerm(self) -> int: @@ -173,11 +166,11 @@ def SwitchedTerm(self) -> int: Original COM help: https://opendss.epri.com/SwitchedTerm1.html ''' - return self._check_for_error(self._lib.Reclosers_Get_SwitchedTerm()) + return self._lib.Reclosers_Get_SwitchedTerm() @SwitchedTerm.setter def SwitchedTerm(self, Value: int): - self._check_for_error(self._lib.Reclosers_Set_SwitchedTerm(Value)) + self._lib.Reclosers_Set_SwitchedTerm(Value) def Reset(self): @@ -186,20 +179,20 @@ def Reset(self): If open, lock out the recloser. If closed, resets recloser to first operation. ''' - self._check_for_error(self._lib.Reclosers_Reset()) + self._lib.Reclosers_Reset() @property def State(self) -> int: ''' - Get/Set present state of recloser. + Present state of recloser. If set to open (ActionCodes.Open=1), open recloser's controlled element and lock out the recloser. If set to close (ActionCodes.Close=2), close recloser's controlled element and resets recloser to first operation. ''' - return self._check_for_error(self._lib.Reclosers_Get_State()) + return self._lib.Reclosers_Get_State() @State.setter def State(self, Value: int): - self._check_for_error(self._lib.Reclosers_Set_State(Value)) + self._lib.Reclosers_Set_State(Value) @property def NormalState(self) -> int: @@ -208,8 +201,8 @@ def NormalState(self) -> int: Original COM help: https://opendss.epri.com/NormalState1.html ''' - return self._check_for_error(self._lib.Reclosers_Get_NormalState()) + return self._lib.Reclosers_Get_NormalState() @NormalState.setter def NormalState(self, Value: int): - self._check_for_error(self._lib.Reclosers_Set_NormalState(Value)) + self._lib.Reclosers_Set_NormalState(Value) diff --git a/dss/IReduceCkt.py b/dss/IReduceCkt.py index dfd8cc86..c84fb6f5 100644 --- a/dss/IReduceCkt.py +++ b/dss/IReduceCkt.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2019-2024 Paulo Meira -# Copyright (c) 2019-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2019-2025 Paulo Meira +# Copyright (c) 2019-2025 DSS-Extensions contributors from ._cffi_api_util import Base from typing import AnyStr @@ -16,11 +16,11 @@ def Zmag(self) -> float: Original COM help: https://opendss.epri.com/Zmag.html ''' - return self._check_for_error(self._lib.ReduceCkt_Get_Zmag()) + return self._lib.ReduceCkt_Get_Zmag() @Zmag.setter def Zmag(self, Value: float): - self._check_for_error(self._lib.ReduceCkt_Set_Zmag(Value)) + self._lib.ReduceCkt_Set_Zmag(Value) @property def KeepLoad(self) -> bool: @@ -29,11 +29,11 @@ def KeepLoad(self) -> bool: Original COM help: https://opendss.epri.com/KeepLoad.html ''' - return self._check_for_error(self._lib.ReduceCkt_Get_KeepLoad()) != 0 + return self._lib.ReduceCkt_Get_KeepLoad() @KeepLoad.setter def KeepLoad(self, Value: bool): - self._check_for_error(self._lib.ReduceCkt_Set_KeepLoad(bool(Value))) + self._lib.ReduceCkt_Set_KeepLoad(Value) @property def EditString(self) -> str: @@ -42,14 +42,11 @@ def EditString(self) -> str: Original COM help: https://opendss.epri.com/EditString.html ''' - return self._get_string(self._check_for_error(self._lib.ReduceCkt_Get_EditString())) + return self._lib.ReduceCkt_Get_EditString() @EditString.setter def EditString(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.ReduceCkt_Set_EditString(Value)) + self._lib.ReduceCkt_Set_EditString(Value) @property def StartPDElement(self) -> str: @@ -58,14 +55,11 @@ def StartPDElement(self) -> str: Original COM help: https://opendss.epri.com/StartPDElement.html ''' - return self._get_string(self._check_for_error(self._lib.ReduceCkt_Get_StartPDElement())) + return self._lib.ReduceCkt_Get_StartPDElement() @StartPDElement.setter def StartPDElement(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.ReduceCkt_Set_StartPDElement(Value)) + self._lib.ReduceCkt_Set_StartPDElement(Value) @property def EnergyMeter(self) -> str: @@ -74,24 +68,18 @@ def EnergyMeter(self) -> str: Original COM help: https://opendss.epri.com/EnergyMeter1.html ''' - return self._get_string(self._check_for_error(self._lib.ReduceCkt_Get_EnergyMeter())) + return self._lib.ReduceCkt_Get_EnergyMeter() @EnergyMeter.setter def EnergyMeter(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.ReduceCkt_Set_EnergyMeter(Value)) + self._lib.ReduceCkt_Set_EnergyMeter(Value) def SaveCircuit(self, CktName: AnyStr): ''' Save present (reduced) circuit Filename is listed in the Text Result interface ''' - if not isinstance(CktName, bytes): - CktName = CktName.encode(self._api_util.codec) - - self._check_for_error(self._lib.ReduceCkt_SaveCircuit(CktName)) + self._lib.ReduceCkt_SaveCircuit(CktName) def DoDefault(self): ''' @@ -99,7 +87,7 @@ def DoDefault(self): Original COM help: https://opendss.epri.com/DoDefault.html ''' - self._check_for_error(self._lib.ReduceCkt_DoDefault()) + self._lib.ReduceCkt_DoDefault() def DoShortLines(self): ''' @@ -107,7 +95,7 @@ def DoShortLines(self): Original COM help: https://opendss.epri.com/DoShortLines.html ''' - self._check_for_error(self._lib.ReduceCkt_DoShortLines()) + self._lib.ReduceCkt_DoShortLines() def DoDangling(self): ''' @@ -115,7 +103,7 @@ def DoDangling(self): Original COM help: https://opendss.epri.com/DoDangling.html ''' - self._check_for_error(self._lib.ReduceCkt_DoDangling()) + self._lib.ReduceCkt_DoDangling() def DoLoopBreak(self): ''' @@ -123,19 +111,19 @@ def DoLoopBreak(self): Disables one of the Line objects at the head of a loop to force the circuit to be radial. ''' - self._check_for_error(self._lib.ReduceCkt_DoLoopBreak()) + self._lib.ReduceCkt_DoLoopBreak() def DoParallelLines(self): ''' Merge all parallel lines found in the circuit to facilitate its reduction. ''' - self._check_for_error(self._lib.ReduceCkt_DoParallelLines()) + self._lib.ReduceCkt_DoParallelLines() def DoSwitches(self): ''' Merge Line objects in which the IsSwitch property is true with the down-line Line object. ''' - self._check_for_error(self._lib.ReduceCkt_DoSwitches()) + self._lib.ReduceCkt_DoSwitches() def Do1phLaterals(self): ''' @@ -143,7 +131,7 @@ def Do1phLaterals(self): Loads and other shunt elements are moved to the parent 3-phase bus. ''' - self._check_for_error(self._lib.ReduceCkt_Do1phLaterals()) + self._lib.ReduceCkt_Do1phLaterals() def DoBranchRemove(self): ''' @@ -153,4 +141,4 @@ def DoBranchRemove(self): If KeepLoad=Y (default), a new Load element is defined and kW, kvar are set to present power flow solution for the first element eliminated. The EditString is applied to each new Load element defined. ''' - self._check_for_error(self._lib.ReduceCkt_DoBranchRemove()) + self._lib.ReduceCkt_DoBranchRemove() diff --git a/dss/IRegControls.py b/dss/IRegControls.py index d080891d..d548b6b7 100644 --- a/dss/IRegControls.py +++ b/dss/IRegControls.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import AnyStr @@ -35,7 +35,7 @@ class IRegControls(Iterable): ] def Reset(self): - self._check_for_error(self._lib.RegControls_Reset()) + self._lib.RegControls_Reset() @property def CTPrimary(self) -> float: @@ -44,11 +44,11 @@ def CTPrimary(self) -> float: Original COM help: https://opendss.epri.com/CTPrimary.html ''' - return self._check_for_error(self._lib.RegControls_Get_CTPrimary()) + return self._lib.RegControls_Get_CTPrimary() @CTPrimary.setter def CTPrimary(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_CTPrimary(Value)) + self._lib.RegControls_Set_CTPrimary(Value) @property def Delay(self) -> float: @@ -57,11 +57,11 @@ def Delay(self) -> float: Original COM help: https://opendss.epri.com/Delay2.html ''' - return self._check_for_error(self._lib.RegControls_Get_Delay()) + return self._lib.RegControls_Get_Delay() @Delay.setter def Delay(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_Delay(Value)) + self._lib.RegControls_Set_Delay(Value) @property def ForwardBand(self) -> float: @@ -70,11 +70,11 @@ def ForwardBand(self) -> float: Original COM help: https://opendss.epri.com/ForwardBand.html ''' - return self._check_for_error(self._lib.RegControls_Get_ForwardBand()) + return self._lib.RegControls_Get_ForwardBand() @ForwardBand.setter def ForwardBand(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_ForwardBand(Value)) + self._lib.RegControls_Set_ForwardBand(Value) @property def ForwardR(self) -> float: @@ -83,11 +83,11 @@ def ForwardR(self) -> float: Original COM help: https://opendss.epri.com/ForwardR.html ''' - return self._check_for_error(self._lib.RegControls_Get_ForwardR()) + return self._lib.RegControls_Get_ForwardR() @ForwardR.setter def ForwardR(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_ForwardR(Value)) + self._lib.RegControls_Set_ForwardR(Value) @property def ForwardVreg(self) -> float: @@ -96,11 +96,11 @@ def ForwardVreg(self) -> float: Original COM help: https://opendss.epri.com/ForwardVreg.html ''' - return self._check_for_error(self._lib.RegControls_Get_ForwardVreg()) + return self._lib.RegControls_Get_ForwardVreg() @ForwardVreg.setter def ForwardVreg(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_ForwardVreg(Value)) + self._lib.RegControls_Set_ForwardVreg(Value) @property def ForwardX(self) -> float: @@ -109,11 +109,11 @@ def ForwardX(self) -> float: Original COM help: https://opendss.epri.com/ForwardX.html ''' - return self._check_for_error(self._lib.RegControls_Get_ForwardX()) + return self._lib.RegControls_Get_ForwardX() @ForwardX.setter def ForwardX(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_ForwardX(Value)) + self._lib.RegControls_Set_ForwardX(Value) @property def IsInverseTime(self) -> bool: @@ -122,11 +122,11 @@ def IsInverseTime(self) -> bool: Original COM help: https://opendss.epri.com/IsInverseTime.html ''' - return self._check_for_error(self._lib.RegControls_Get_IsInverseTime()) != 0 + return self._lib.RegControls_Get_IsInverseTime() @IsInverseTime.setter def IsInverseTime(self, Value: bool): - self._check_for_error(self._lib.RegControls_Set_IsInverseTime(Value)) + self._lib.RegControls_Set_IsInverseTime(Value) @property def IsReversible(self) -> bool: @@ -135,11 +135,11 @@ def IsReversible(self) -> bool: Original COM help: https://opendss.epri.com/IsReversible.html ''' - return self._check_for_error(self._lib.RegControls_Get_IsReversible()) != 0 + return self._lib.RegControls_Get_IsReversible() @IsReversible.setter def IsReversible(self, Value: bool): - self._check_for_error(self._lib.RegControls_Set_IsReversible(Value)) + self._lib.RegControls_Set_IsReversible(Value) @property def MaxTapChange(self) -> int: @@ -148,11 +148,11 @@ def MaxTapChange(self) -> int: Original COM help: https://opendss.epri.com/MaxTapChange.html ''' - return self._check_for_error(self._lib.RegControls_Get_MaxTapChange()) + return self._lib.RegControls_Get_MaxTapChange() @MaxTapChange.setter def MaxTapChange(self, Value: int): - self._check_for_error(self._lib.RegControls_Set_MaxTapChange(Value)) + self._lib.RegControls_Set_MaxTapChange(Value) @property def MonitoredBus(self) -> str: @@ -161,14 +161,11 @@ def MonitoredBus(self) -> str: Original COM help: https://opendss.epri.com/MonitoredBus.html ''' - return self._get_string(self._check_for_error(self._lib.RegControls_Get_MonitoredBus())) + return self._lib.RegControls_Get_MonitoredBus() @MonitoredBus.setter def MonitoredBus(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.RegControls_Set_MonitoredBus(Value)) + self._lib.RegControls_Set_MonitoredBus(Value) @property def PTratio(self) -> float: @@ -177,11 +174,11 @@ def PTratio(self) -> float: Original COM help: https://opendss.epri.com/PTratio1.html ''' - return self._check_for_error(self._lib.RegControls_Get_PTratio()) + return self._lib.RegControls_Get_PTratio() @PTratio.setter def PTratio(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_PTratio(Value)) + self._lib.RegControls_Set_PTratio(Value) @property def ReverseBand(self) -> float: @@ -190,11 +187,11 @@ def ReverseBand(self) -> float: Original COM help: https://opendss.epri.com/ReverseBand.html ''' - return self._check_for_error(self._lib.RegControls_Get_ReverseBand()) + return self._lib.RegControls_Get_ReverseBand() @ReverseBand.setter def ReverseBand(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_ReverseBand(Value)) + self._lib.RegControls_Set_ReverseBand(Value) @property def ReverseR(self) -> float: @@ -203,11 +200,11 @@ def ReverseR(self) -> float: Original COM help: https://opendss.epri.com/ReverseR.html ''' - return self._check_for_error(self._lib.RegControls_Get_ReverseR()) + return self._lib.RegControls_Get_ReverseR() @ReverseR.setter def ReverseR(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_ReverseR(Value)) + self._lib.RegControls_Set_ReverseR(Value) @property def ReverseVreg(self) -> float: @@ -216,11 +213,11 @@ def ReverseVreg(self) -> float: Original COM help: https://opendss.epri.com/ReverseVreg.html ''' - return self._check_for_error(self._lib.RegControls_Get_ReverseVreg()) + return self._lib.RegControls_Get_ReverseVreg() @ReverseVreg.setter def ReverseVreg(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_ReverseVreg(Value)) + self._lib.RegControls_Set_ReverseVreg(Value) @property def ReverseX(self) -> float: @@ -229,11 +226,11 @@ def ReverseX(self) -> float: Original COM help: https://opendss.epri.com/ReverseX.html ''' - return self._check_for_error(self._lib.RegControls_Get_ReverseX()) + return self._lib.RegControls_Get_ReverseX() @ReverseX.setter def ReverseX(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_ReverseX(Value)) + self._lib.RegControls_Set_ReverseX(Value) @property def TapDelay(self) -> float: @@ -242,11 +239,11 @@ def TapDelay(self) -> float: Original COM help: https://opendss.epri.com/TapDelay.html ''' - return self._check_for_error(self._lib.RegControls_Get_TapDelay()) + return self._lib.RegControls_Get_TapDelay() @TapDelay.setter def TapDelay(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_TapDelay(Value)) + self._lib.RegControls_Set_TapDelay(Value) @property def TapNumber(self) -> int: @@ -255,11 +252,11 @@ def TapNumber(self) -> int: Original COM help: https://opendss.epri.com/TapNumber.html ''' - return self._check_for_error(self._lib.RegControls_Get_TapNumber()) + return self._lib.RegControls_Get_TapNumber() @TapNumber.setter def TapNumber(self, Value: int): - self._check_for_error(self._lib.RegControls_Set_TapNumber(Value)) + self._lib.RegControls_Set_TapNumber(Value) @property def TapWinding(self) -> int: @@ -268,11 +265,11 @@ def TapWinding(self) -> int: Original COM help: https://opendss.epri.com/TapWinding.html ''' - return self._check_for_error(self._lib.RegControls_Get_TapWinding()) + return self._lib.RegControls_Get_TapWinding() @TapWinding.setter def TapWinding(self, Value: int): - self._check_for_error(self._lib.RegControls_Set_TapWinding(Value)) + self._lib.RegControls_Set_TapWinding(Value) @property def Transformer(self) -> str: @@ -281,14 +278,11 @@ def Transformer(self) -> str: Original COM help: https://opendss.epri.com/Transformer.html ''' - return self._get_string(self._check_for_error(self._lib.RegControls_Get_Transformer())) + return self._lib.RegControls_Get_Transformer() @Transformer.setter def Transformer(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.RegControls_Set_Transformer(Value)) + self._lib.RegControls_Set_Transformer(Value) @property def VoltageLimit(self) -> float: @@ -297,11 +291,11 @@ def VoltageLimit(self) -> float: Original COM help: https://opendss.epri.com/VoltageLimit.html ''' - return self._check_for_error(self._lib.RegControls_Get_VoltageLimit()) + return self._lib.RegControls_Get_VoltageLimit() @VoltageLimit.setter def VoltageLimit(self, Value: float): - self._check_for_error(self._lib.RegControls_Set_VoltageLimit(Value)) + self._lib.RegControls_Set_VoltageLimit(Value) @property def Winding(self) -> int: @@ -310,10 +304,10 @@ def Winding(self) -> int: Original COM help: https://opendss.epri.com/Winding.html ''' - return self._check_for_error(self._lib.RegControls_Get_Winding()) + return self._lib.RegControls_Get_Winding() @Winding.setter def Winding(self, Value: int): - self._check_for_error(self._lib.RegControls_Set_Winding(Value)) + self._lib.RegControls_Set_Winding(Value) diff --git a/dss/IRelays.py b/dss/IRelays.py index 4bdf7f54..35688519 100644 --- a/dss/IRelays.py +++ b/dss/IRelays.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import AnyStr @@ -26,14 +26,11 @@ def MonitoredObj(self) -> str: Original COM help: https://opendss.epri.com/MonitoredObj3.html ''' - return self._get_string(self._check_for_error(self._lib.Relays_Get_MonitoredObj())) + return self._lib.Relays_Get_MonitoredObj() @MonitoredObj.setter def MonitoredObj(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Relays_Set_MonitoredObj(Value)) + self._lib.Relays_Set_MonitoredObj(Value) @property def MonitoredTerm(self) -> int: @@ -42,11 +39,11 @@ def MonitoredTerm(self) -> int: Original COM help: https://opendss.epri.com/MonitoredTerm3.html ''' - return self._check_for_error(self._lib.Relays_Get_MonitoredTerm()) + return self._lib.Relays_Get_MonitoredTerm() @MonitoredTerm.setter def MonitoredTerm(self, Value: int): - self._check_for_error(self._lib.Relays_Set_MonitoredTerm(Value)) + self._lib.Relays_Set_MonitoredTerm(Value) @property def SwitchedObj(self) -> str: @@ -55,14 +52,11 @@ def SwitchedObj(self) -> str: Original COM help: https://opendss.epri.com/SwitchedObj2.html ''' - return self._get_string(self._check_for_error(self._lib.Relays_Get_SwitchedObj())) + return self._lib.Relays_Get_SwitchedObj() @SwitchedObj.setter def SwitchedObj(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Relays_Set_SwitchedObj(Value)) + self._lib.Relays_Set_SwitchedObj(Value) @property def SwitchedTerm(self) -> int: @@ -71,11 +65,11 @@ def SwitchedTerm(self) -> int: Original COM help: https://opendss.epri.com/SwitchedTerm2.html ''' - return self._check_for_error(self._lib.Relays_Get_SwitchedTerm()) + return self._lib.Relays_Get_SwitchedTerm() @SwitchedTerm.setter def SwitchedTerm(self, Value: int): - self._check_for_error(self._lib.Relays_Set_SwitchedTerm(Value)) + self._lib.Relays_Set_SwitchedTerm(Value) def Open(self): ''' @@ -83,7 +77,7 @@ def Open(self): Original COM help: https://opendss.epri.com/Open4.html ''' - self._check_for_error(self._lib.Relays_Open()) + self._lib.Relays_Open() def Close(self): ''' @@ -91,7 +85,7 @@ def Close(self): Original COM help: https://opendss.epri.com/Close5.html ''' - self._check_for_error(self._lib.Relays_Close()) + self._lib.Relays_Close() def Reset(self): ''' @@ -99,20 +93,20 @@ def Reset(self): If open, lock out the relay. If closed, resets relay to first operation. ''' - self._check_for_error(self._lib.Relays_Reset()) + self._lib.Relays_Reset() @property def State(self) -> int: ''' - Get/Set present state of relay. + Present state of relay. If set to open, open relay's controlled element and lock out the relay. If set to close, close relay's controlled element and resets relay to first operation. ''' - return self._check_for_error(self._lib.Relays_Get_State()) + return self._lib.Relays_Get_State() @State.setter def State(self, Value: int): - self._check_for_error(self._lib.Relays_Set_State(Value)) + self._lib.Relays_Set_State(Value) @property def NormalState(self) -> int: @@ -121,8 +115,8 @@ def NormalState(self) -> int: Original COM help: https://opendss.epri.com/NormalState3.html ''' - return self._check_for_error(self._lib.Relays_Get_NormalState()) + return self._lib.Relays_Get_NormalState() @NormalState.setter def NormalState(self, Value: int): - self._check_for_error(self._lib.Relays_Set_NormalState(Value)) + self._lib.Relays_Set_NormalState(Value) diff --git a/dss/ISensors.py b/dss/ISensors.py index 7ae06774..87233f6e 100644 --- a/dss/ISensors.py +++ b/dss/ISensors.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Float64Array from typing import AnyStr @@ -27,10 +27,10 @@ class ISensors(Iterable): ] def Reset(self): - self._check_for_error(self._lib.Sensors_Reset()) + self._lib.Sensors_Reset() def ResetAll(self): - self._check_for_error(self._lib.Sensors_ResetAll()) + self._lib.Sensors_ResetAll() @property def Currents(self) -> Float64Array: @@ -39,13 +39,12 @@ def Currents(self) -> Float64Array: Original COM help: https://opendss.epri.com/Currents2.html ''' - self._check_for_error(self._lib.Sensors_Get_Currents_GR()) - return self._get_float64_gr_array() + return self._lib.Sensors_Get_Currents_GR() @Currents.setter def Currents(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Sensors_Set_Currents(ValuePtr, ValueCount)) + self._lib.Sensors_Set_Currents(ValuePtr, ValueCount) @property def IsDelta(self) -> bool: @@ -54,11 +53,11 @@ def IsDelta(self) -> bool: Original COM help: https://opendss.epri.com/IsDelta2.html ''' - return self._check_for_error(self._lib.Sensors_Get_IsDelta()) != 0 + return self._lib.Sensors_Get_IsDelta() @IsDelta.setter def IsDelta(self, Value: bool): - self._check_for_error(self._lib.Sensors_Set_IsDelta(Value)) + self._lib.Sensors_Set_IsDelta(Value) @property def MeteredElement(self) -> str: @@ -67,14 +66,11 @@ def MeteredElement(self) -> str: Original COM help: https://opendss.epri.com/MeteredElement1.html ''' - return self._get_string(self._check_for_error(self._lib.Sensors_Get_MeteredElement())) + return self._lib.Sensors_Get_MeteredElement() @MeteredElement.setter def MeteredElement(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Sensors_Set_MeteredElement(Value)) + self._lib.Sensors_Set_MeteredElement(Value) @property def MeteredTerminal(self) -> int: @@ -83,11 +79,11 @@ def MeteredTerminal(self) -> int: Original COM help: https://opendss.epri.com/MeteredTerminal1.html ''' - return self._check_for_error(self._lib.Sensors_Get_MeteredTerminal()) + return self._lib.Sensors_Get_MeteredTerminal() @MeteredTerminal.setter def MeteredTerminal(self, Value: int): - self._check_for_error(self._lib.Sensors_Set_MeteredTerminal(Value)) + self._lib.Sensors_Set_MeteredTerminal(Value) @property def PctError(self) -> float: @@ -96,11 +92,11 @@ def PctError(self) -> float: Original COM help: https://opendss.epri.com/PctError.html ''' - return self._check_for_error(self._lib.Sensors_Get_PctError()) + return self._lib.Sensors_Get_PctError() @PctError.setter def PctError(self, Value: float): - self._check_for_error(self._lib.Sensors_Set_PctError(Value)) + self._lib.Sensors_Set_PctError(Value) @property def ReverseDelta(self) -> bool: @@ -109,11 +105,11 @@ def ReverseDelta(self) -> bool: Original COM help: https://opendss.epri.com/ReverseDelta.html ''' - return self._check_for_error(self._lib.Sensors_Get_ReverseDelta()) != 0 + return self._lib.Sensors_Get_ReverseDelta() @ReverseDelta.setter def ReverseDelta(self, Value: bool): - self._check_for_error(self._lib.Sensors_Set_ReverseDelta(Value)) + self._lib.Sensors_Set_ReverseDelta(Value) @property def Weight(self) -> float: @@ -122,11 +118,11 @@ def Weight(self) -> float: Original COM help: https://opendss.epri.com/Weight.html ''' - return self._check_for_error(self._lib.Sensors_Get_Weight()) + return self._lib.Sensors_Get_Weight() @Weight.setter def Weight(self, Value: float): - self._check_for_error(self._lib.Sensors_Set_Weight(Value)) + self._lib.Sensors_Set_Weight(Value) @property def kVARS(self) -> Float64Array: @@ -135,13 +131,12 @@ def kVARS(self) -> Float64Array: Original COM help: https://opendss.epri.com/kVARS.html ''' - self._check_for_error(self._lib.Sensors_Get_kVARS_GR()) - return self._get_float64_gr_array() + return self._lib.Sensors_Get_kVARS_GR() @kVARS.setter def kVARS(self, Value): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Sensors_Set_kVARS(ValuePtr, ValueCount)) + self._lib.Sensors_Set_kVARS(ValuePtr, ValueCount) @property def kVS(self) -> Float64Array: @@ -150,13 +145,12 @@ def kVS(self) -> Float64Array: Original COM help: https://opendss.epri.com/kVS.html ''' - self._check_for_error(self._lib.Sensors_Get_kVS_GR()) - return self._get_float64_gr_array() + return self._lib.Sensors_Get_kVS_GR() @kVS.setter def kVS(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Sensors_Set_kVS(ValuePtr, ValueCount)) + self._lib.Sensors_Set_kVS(ValuePtr, ValueCount) @property def kVbase(self) -> float: @@ -165,11 +159,11 @@ def kVbase(self) -> float: Original COM help: https://opendss.epri.com/kVBase1.html ''' - return self._check_for_error(self._lib.Sensors_Get_kVbase()) + return self._lib.Sensors_Get_kVbase() @kVbase.setter def kVbase(self, Value: float): - self._check_for_error(self._lib.Sensors_Set_kVbase(Value)) + self._lib.Sensors_Set_kVbase(Value) @property def kWS(self) -> Float64Array: @@ -178,13 +172,12 @@ def kWS(self) -> Float64Array: Original COM help: https://opendss.epri.com/kWS.html ''' - self._check_for_error(self._lib.Sensors_Get_kWS_GR()) - return self._get_float64_gr_array() + return self._lib.Sensors_Get_kWS_GR() @kWS.setter def kWS(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Sensors_Set_kWS(ValuePtr, ValueCount)) + self._lib.Sensors_Set_kWS(ValuePtr, ValueCount) @property def AllocationFactor(self): @@ -193,5 +186,4 @@ def AllocationFactor(self): Original COM help: https://opendss.epri.com/AllocationFactor1.html ''' - self._check_for_error(self._lib.Sensors_Get_AllocationFactor_GR()) - return self._get_float64_gr_array() + return self._lib.Sensors_Get_AllocationFactor_GR() diff --git a/dss/ISettings.py b/dss/ISettings.py index bcda2b85..a613af4d 100644 --- a/dss/ISettings.py +++ b/dss/ISettings.py @@ -1,13 +1,97 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base from ._types import Float64Array, Int32Array -from typing import AnyStr, Union +from typing import AnyStr, Union, List from .enums import DSSPropertyNameStyle, CktModels + +class SettingsContext: + def __init__(self, settings): + self._settings = settings + + def __enter__(self): + # Using try...except since the official engine doesn't implement these. + # Only a few are (and can be) implemented through Oddie. + try: + self._AdvancedTypes = self._settings.AdvancedTypes + except: + pass + + try: + self._CompatFlags = self._settings.CompatFlags + except: + pass + + try: + self._IterateDisabled = self._settings.IterateDisabled + except: + pass + + try: + self._PreferLists = self._settings.PreferLists + except: + pass + + try: + self._SkipCommands = self._settings.SkipCommands + except: + pass + + try: + self._SkipFileRegExp = self._settings.SkipFileRegExp + except: + pass + + try: + self._AllowDOScmd = self._settings.AllowDOScmd + except: + pass + + return self._settings + + def __exit__(self, exc_type, exc_val, exc_tb): + try: + self._settings.AdvancedTypes = self._AdvancedTypes + except: + pass + + try: + self._settings.CompatFlags = self._CompatFlags + except: + pass + + try: + self._settings.IterateDisabled = self._IterateDisabled + except: + pass + + try: + self._settings.PreferLists = self._PreferLists + except: + pass + + try: + self._settings.SkipCommands = self._SkipCommands + except: + pass + + try: + self._settings.SkipFileRegExp = self._SkipFileRegExp + except: + pass + + try: + self._settings.AllowDOScmd = self._AllowDOScmd + except: + pass + + class ISettings(Base): - __slots__ = [] + __slots__ = [ + '_command_dict', + ] _columns = [ 'Trapezoidal', @@ -27,10 +111,47 @@ class ISettings(Base): 'NormVmaxpu', 'AllowDuplicates', 'ControlTrace', - 'LoadsTerminalCheck', - 'IterateDisabled', + + # Commented since we don't have these on Oddie with the official engine + # 'LoadsTerminalCheck', + # 'IterateDisabled', + # 'SkipCommands', + # 'PreferLists', + # 'SkipFileRegExp', + # 'CompatFlags', + # 'PreserveCase', ] + + def __init__(self, api_util): + Base.__init__(self, api_util) + num_commands = self._lib.DSS_Executive_Get_NumCommands() + self._command_dict = { + self._lib.DSS_Executive_Get_Command(i).lower(): i + for i in range(1, num_commands + 1) + } + + + def Context(self) -> SettingsContext: + ''' + Returns a Settings context manager. + The context manager saves the values of the tracker settings on enter, + restoring them on exit. This allows code to change the settings within + the context block and they are restored to the initial values automatically. + + Note: this context manager target DSS-Python settings. Use the equivalent for OpenDSSDirect.py. + A few settings are shared at engine level. + + Settings tracked: + - AdvancedTypes + - CompatFlags + - IterateDisabled + - PreferLists + - SkipCommands + - SkipFileRegExp + ''' + return SettingsContext(self) + @property def AllowDuplicates(self) -> bool: ''' @@ -41,11 +162,11 @@ def AllowDuplicates(self) -> bool: **NOTE**: for DSS-Extensions, we are considering removing this option in a future release since it has performance impacts even when not used. ''' - return self._check_for_error(self._lib.Settings_Get_AllowDuplicates()) != 0 + return self._lib.Settings_Get_AllowDuplicates() @AllowDuplicates.setter def AllowDuplicates(self, Value: bool): - self._check_for_error(self._lib.Settings_Set_AllowDuplicates(Value)) + self._lib.Settings_Set_AllowDuplicates(Value) @property def AutoBusList(self) -> str: @@ -54,14 +175,11 @@ def AutoBusList(self) -> str: Original COM help: https://opendss.epri.com/AutoBusList.html ''' - return self._get_string(self._check_for_error(self._lib.Settings_Get_AutoBusList())) + return self._lib.Settings_Get_AutoBusList() @AutoBusList.setter def AutoBusList(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Settings_Set_AutoBusList(Value)) + self._lib.Settings_Set_AutoBusList(Value) @property def CktModel(self) -> CktModels: @@ -70,11 +188,11 @@ def CktModel(self) -> CktModels: Original COM help: https://opendss.epri.com/CktModel.html ''' - return self._check_for_error(CktModels(self._lib.Settings_Get_CktModel())) + return CktModels(self._lib.Settings_Get_CktModel()) @CktModel.setter def CktModel(self, Value: Union[int, CktModels]): - self._check_for_error(self._lib.Settings_Set_CktModel(Value)) + self._lib.Settings_Set_CktModel(Value) @property def ControlTrace(self) -> bool: @@ -83,11 +201,11 @@ def ControlTrace(self) -> bool: Original COM help: https://opendss.epri.com/ControlTrace.html ''' - return self._check_for_error(self._lib.Settings_Get_ControlTrace()) != 0 + return self._lib.Settings_Get_ControlTrace() @ControlTrace.setter def ControlTrace(self, Value: bool): - self._check_for_error(self._lib.Settings_Set_ControlTrace(Value)) + self._lib.Settings_Set_ControlTrace(Value) @property def EmergVmaxpu(self) -> float: @@ -96,11 +214,11 @@ def EmergVmaxpu(self) -> float: Original COM help: https://opendss.epri.com/EmergVmaxpu.html ''' - return self._check_for_error(self._lib.Settings_Get_EmergVmaxpu()) + return self._lib.Settings_Get_EmergVmaxpu() @EmergVmaxpu.setter def EmergVmaxpu(self, Value: float): - self._check_for_error(self._lib.Settings_Set_EmergVmaxpu(Value)) + self._lib.Settings_Set_EmergVmaxpu(Value) @property def EmergVminpu(self) -> float: @@ -109,11 +227,11 @@ def EmergVminpu(self) -> float: Original COM help: https://opendss.epri.com/EmergVminpu.html ''' - return self._check_for_error(self._lib.Settings_Get_EmergVminpu()) + return self._lib.Settings_Get_EmergVminpu() @EmergVminpu.setter def EmergVminpu(self, Value: float): - self._check_for_error(self._lib.Settings_Set_EmergVminpu(Value)) + self._lib.Settings_Set_EmergVminpu(Value) @property def LossRegs(self) -> Int32Array: @@ -122,13 +240,12 @@ def LossRegs(self) -> Int32Array: Original COM help: https://opendss.epri.com/LossRegs.html ''' - self._check_for_error(self._lib.Settings_Get_LossRegs_GR()) - return self._get_int32_gr_array() + return self._lib.Settings_Get_LossRegs_GR() @LossRegs.setter def LossRegs(self, Value: Int32Array): Value, ValuePtr, ValueCount = self._prepare_int32_array(Value) - self._check_for_error(self._lib.Settings_Set_LossRegs(ValuePtr, ValueCount)) + self._lib.Settings_Set_LossRegs(ValuePtr, ValueCount) @property def LossWeight(self) -> float: @@ -137,11 +254,11 @@ def LossWeight(self) -> float: Original COM help: https://opendss.epri.com/LossWeight.html ''' - return self._check_for_error(self._lib.Settings_Get_LossWeight()) + return self._lib.Settings_Get_LossWeight() @LossWeight.setter def LossWeight(self, Value: float): - self._check_for_error(self._lib.Settings_Set_LossWeight(Value)) + self._lib.Settings_Set_LossWeight(Value) @property def NormVmaxpu(self) -> float: @@ -150,11 +267,11 @@ def NormVmaxpu(self) -> float: Original COM help: https://opendss.epri.com/NormVmaxpu.html ''' - return self._check_for_error(self._lib.Settings_Get_NormVmaxpu()) + return self._lib.Settings_Get_NormVmaxpu() @NormVmaxpu.setter def NormVmaxpu(self, Value: float): - self._check_for_error(self._lib.Settings_Set_NormVmaxpu(Value)) + self._lib.Settings_Set_NormVmaxpu(Value) @property def NormVminpu(self) -> float: @@ -163,11 +280,11 @@ def NormVminpu(self) -> float: Original COM help: https://opendss.epri.com/NormVminpu.html ''' - return self._check_for_error(self._lib.Settings_Get_NormVminpu()) + return self._lib.Settings_Get_NormVminpu() @NormVminpu.setter def NormVminpu(self, Value: float): - self._check_for_error(self._lib.Settings_Set_NormVminpu(Value)) + self._lib.Settings_Set_NormVminpu(Value) @property def PriceCurve(self) -> str: @@ -176,14 +293,11 @@ def PriceCurve(self) -> str: Original COM help: https://opendss.epri.com/PriceCurve.html ''' - return self._get_string(self._check_for_error(self._lib.Settings_Get_PriceCurve())) + return self._lib.Settings_Get_PriceCurve() @PriceCurve.setter def PriceCurve(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Settings_Set_PriceCurve(Value)) + self._lib.Settings_Set_PriceCurve(Value) @property def PriceSignal(self) -> float: @@ -192,11 +306,11 @@ def PriceSignal(self) -> float: Original COM help: https://opendss.epri.com/PriceSignal.html ''' - return self._check_for_error(self._lib.Settings_Get_PriceSignal()) + return self._lib.Settings_Get_PriceSignal() @PriceSignal.setter def PriceSignal(self, Value: float): - self._check_for_error(self._lib.Settings_Set_PriceSignal(Value)) + self._lib.Settings_Set_PriceSignal(Value) @property def Trapezoidal(self) -> bool: @@ -205,11 +319,11 @@ def Trapezoidal(self) -> bool: Original COM help: https://opendss.epri.com/Trapezoidal.html ''' - return self._check_for_error(self._lib.Settings_Get_Trapezoidal()) != 0 + return self._lib.Settings_Get_Trapezoidal() @Trapezoidal.setter def Trapezoidal(self, Value: bool): - self._check_for_error(self._lib.Settings_Set_Trapezoidal(Value)) + self._lib.Settings_Set_Trapezoidal(Value) @property def UEregs(self) -> Int32Array: @@ -218,13 +332,12 @@ def UEregs(self) -> Int32Array: Original COM help: https://opendss.epri.com/UEregs.html ''' - self._check_for_error(self._lib.Settings_Get_UEregs_GR()) - return self._get_int32_gr_array() + return self._lib.Settings_Get_UEregs_GR() @UEregs.setter def UEregs(self, Value: Int32Array): Value, ValuePtr, ValueCount = self._prepare_int32_array(Value) - self._check_for_error(self._lib.Settings_Set_UEregs(ValuePtr, ValueCount)) + self._lib.Settings_Set_UEregs(ValuePtr, ValueCount) @property def UEweight(self) -> float: @@ -233,11 +346,11 @@ def UEweight(self) -> float: Original COM help: https://opendss.epri.com/UEweight.html ''' - return self._check_for_error(self._lib.Settings_Get_UEweight()) + return self._lib.Settings_Get_UEweight() @UEweight.setter def UEweight(self, Value: float): - self._check_for_error(self._lib.Settings_Set_UEweight(Value)) + self._lib.Settings_Set_UEweight(Value) @property def VoltageBases(self) -> Float64Array: @@ -246,13 +359,12 @@ def VoltageBases(self) -> Float64Array: Original COM help: https://opendss.epri.com/VoltageBases.html ''' - self._check_for_error(self._lib.Settings_Get_VoltageBases_GR()) - return self._get_float64_gr_array() + return self._lib.Settings_Get_VoltageBases_GR() @VoltageBases.setter def VoltageBases(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.Settings_Set_VoltageBases(ValuePtr, ValueCount)) + self._lib.Settings_Set_VoltageBases(ValuePtr, ValueCount) @property def ZoneLock(self) -> bool: @@ -261,11 +373,11 @@ def ZoneLock(self) -> bool: Original COM help: https://opendss.epri.com/ZoneLock.html ''' - return self._check_for_error(self._lib.Settings_Get_ZoneLock()) != 0 + return self._lib.Settings_Get_ZoneLock() @ZoneLock.setter def ZoneLock(self, Value: bool): - self._check_for_error(self._lib.Settings_Set_ZoneLock(Value)) + self._lib.Settings_Set_ZoneLock(Value) @property def AllocationFactors(self): @@ -274,7 +386,7 @@ def AllocationFactors(self): @AllocationFactors.setter def AllocationFactors(self, Value: float): - self._check_for_error(self._lib.Settings_Set_AllocationFactors(Value)) + self._lib.Settings_Set_AllocationFactors(Value) @property def LoadsTerminalCheck(self) -> bool: @@ -284,11 +396,11 @@ def LoadsTerminalCheck(self) -> bool: **(API Extension)** ''' - return self._check_for_error(self._lib.Settings_Get_LoadsTerminalCheck()) != 0 + return self._lib.Settings_Get_LoadsTerminalCheck() @LoadsTerminalCheck.setter def LoadsTerminalCheck(self, Value: bool): - self._check_for_error(self._lib.Settings_Set_LoadsTerminalCheck(Value)) + self._lib.Settings_Set_LoadsTerminalCheck(Value) @property def IterateDisabled(self) -> int: @@ -302,11 +414,11 @@ def IterateDisabled(self) -> int: **(API Extension)** ''' - return self._check_for_error(self._lib.Settings_Get_IterateDisabled()) + return self._lib.Settings_Get_IterateDisabled() @IterateDisabled.setter def IterateDisabled(self, Value: int): - self._check_for_error(self._lib.Settings_Set_IterateDisabled(Value)) + self._lib.Settings_Set_IterateDisabled(Value) def SetPropertyNameStyle(self, value: DSSPropertyNameStyle): ''' @@ -318,4 +430,232 @@ def SetPropertyNameStyle(self, value: DSSPropertyNameStyle): **(API Extension)** ''' - self._check_for_error(self._lib.Settings_SetPropertyNameStyle(value)) + self._lib.Settings_SetPropertyNameStyle(value) + + @property + def SkipFileRegExp(self) -> str: + ''' + Regular expression pattern to skip files. + + If a file name as provided in the input for the `Redirect` and `Compile` commands + matches the regular expression pattern, it is skipped (the file is not read nor + commands contained in the file are executed). + + Set to an empty string to reset/disable the filter. + + Case-insensitive. + See https://regex.sorokin.engineer/en/latest/regular_expressions.html for information on + the expression syntax and options. + + Even if the `clear` command is included in `Settings.SkipCommands`, the `DSS.ClearAll()` method can + still be called. It resets both skip settings, `SkipCommands` and `SkipFileRegExp`. + + **(API Extension)** + ''' + return self._lib.Settings_Get_SkipFileRegExp() + + @SkipFileRegExp.setter + def SkipFileRegExp(self, Value: Union[AnyStr, None]): + self._lib.Settings_Set_SkipFileRegExp(Value or '') + + @property + def SkipCommands(self) -> List[str]: + ''' + List of commands to skip + + List of strings representing the command names to skip when processing DSS text commands or files. + + If the `clear` command is included in `Settings.SkipCommands`, the `DSS.ClearAll()` method can + still be called and it will reset both skip settings, `SkipCommands` and `SkipFileRegExp`. + + **(API Extension)** + ''' + + return [ + self._lib.DSS_Executive_Get_Command(i) + for i in self._lib.Settings_Get_SkipCommands_GR() + ] + + @SkipCommands.setter + def SkipCommands(self, Value: List[str]): + if len(Value) != 0 and isinstance(Value[0], str): + # map command names to integer codes + Value = [self._command_dict[cmd_name] for cmd_name in Value] + + Value, ValuePtr, ValueCount = self._prepare_int32_array(Value) + self._lib.Settings_Set_SkipCommands(ValuePtr, ValueCount) + + + @property + def AdvancedTypes(self) -> bool: + ''' + When enabled, there are **two side-effects**: + + - **Per DSS Context:** Complex arrays and complex numbers can be returned and consumed by the Python API. + - **Global effect:** The low-level API provides matrix dimensions when available (`EnableArrayDimensions` is enabled). + + As a result, for example, `DSS.ActiveCircuit.ActiveCktElement.Yprim` is returned as a complex matrix instead + of a plain array. + + When disabled, the legacy plain arrays are used and complex numbers cannot be consumed by the Python API. + + *Defaults to **False** for backwards compatibility.* + + **(API Extension)** + ''' + return self._lib.advanced_types + + @AdvancedTypes.setter + def AdvancedTypes(self, Value: bool): + self._lib.advanced_types = bool(Value) + + + @property + def CompatFlags(self) -> int: + ''' + Controls some compatibility flags introduced to toggle some behavior from EPRI's OpenDSS. + + **THE FLAGS ARE GLOBAL, affecting all AltDSS engines in the process.** + CompatFlags for Oddie-loaded instances (OpenDSS and OpenDSS-C engines) are handled by the Oddie code itself, + so it is global for each Oddie library. + + These flags may change for each version of DSS C-API, but the same value will not be reused. That is, + when we remove a compatibility flag, it will have no effect but will also not affect anything else + besides raising an error if the user tries to toggle a flag that was available in a previous version. + + We expect to keep a very limited number of flags. Since the flags are more transient than the other + options/flags, it was preferred to add this generic function instead of a separate function per + flag. + + See the enumeration `DSSCompatFlags` for available flags, including description. + + **(API Extension)** + ''' + return self._lib.DSS_Get_CompatFlags() + + @CompatFlags.setter + def CompatFlags(self, Value: int): + self._lib.DSS_Set_CompatFlags(Value) + + + @property + def PreferLists(self) -> bool: + ''' + Enable this setting to use lists instead of NumPy arrays, where it makes sense. + + This was added for better for compatibility with the COM packages (`comtypes` and `win32com` use lists + and tuples by default) and the original OpenDSSDirect.py releases. + + Current releases of OpenDSSDirect.py, DSS-Python, and AltDSS(-Python) all use NumPy arrays by default. + Users can also activate the related `AdvancedTypes` for a richer experience. + + **(API Extension)** + ''' + return self._lib.prefer_lists + + @PreferLists.setter + def PreferLists(self, value: bool): + self._lib.prefer_lists = value + + @property + def COMErrorResults(self) -> bool: + ''' + If enabled, in case of errors or empty arrays, the API returns arrays with values compatible with + EPRI's OpenDSS COM interface. + + For example, consider the property `Loads.ZIPV`. If there is no active circuit or active load element: + + - In the disabled state (`COMErrorResults`=False), the function will return "[]", an array with 0 elements. + - In the enabled state (`COMErrorResults`=True), the function will return "[0.0]" instead. This should + be compatible with the return value of EPRI's OpenDSS COM interface. + + Defaults to false (disabled state) in AltDSS since the v0.15.x series. + + This does not affect the results when using EPRI's OpenDSS distribution through Oddie. + + This can also be set through the environment variable `DSS_CAPI_COM_DEFAULTS`. Setting it to 1 enables + the legacy/COM behavior. The value can be toggled through the API at any time. + + **(API Extension)** + ''' + return self._lib.DSS_Get_COMErrorResults() + + @COMErrorResults.setter + def COMErrorResults(self, Value: bool): + self._lib.DSS_Set_COMErrorResults(Value) + + @property + def AllowDOScmd(self) -> bool: + ''' + If enabled, the `DOScmd` command is allowed. Otherwise, an error is reported if the user tries to use it. + + Defaults to False/0 (disabled state). Users should consider DOScmd deprecated on DSS-Extensions. + + This can also be set through the environment variable `DSS_CAPI_ALLOW_DOSCMD`. Setting it to 1 enables + the command. + + **(API Extension)** + ''' + return self._lib.DSS_Get_AllowDOScmd() + + @AllowDOScmd.setter + def AllowDOScmd(self, Value: bool): + self._lib.DSS_Set_AllowDOScmd(Value) + + @property + def AllowChangeDir(self) -> bool: + ''' + If disabled, the engine will not change the active working directory during execution. E.g. a "compile" + command will not "chdir" to the file path. + + If you have issues with long paths, enabling this might help in some scenarios. + + Defaults to True (allow changes, backwards compatible) in the 0.10.x versions of DSS C-API. + This might change to False in future versions. + + This can also be set through the environment variable `DSS_CAPI_ALLOW_CHANGE_DIR`. Set it to 0 to + disallow changing the active working directory. + + **(API Extension)** + ''' + return self._lib.DSS_Get_AllowChangeDir() + + @AllowChangeDir.setter + def AllowChangeDir(self, Value: bool): + self._lib.DSS_Set_AllowChangeDir(Value) + + @property + def AllowEditor(self) -> bool: + ''' + Gets/sets whether running the external editor for "Show" is allowed + + AllowEditor controls whether the external editor is used in commands like "Show". + If you set to 0 (false), the editor is not executed. Note that other side effects, + such as the creation of files, are not affected. + + **(API Extension)** + ''' + return self._lib.DSS_Get_AllowEditor() + + @AllowEditor.setter + def AllowEditor(self, value: bool): + self._lib.DSS_Set_AllowEditor(value) + + @property + def PreserveCase(self) -> bool: + ''' + Gets/sets whether the engine tries to preserve original names + + When enabled, bus and element names in many of the API functions, reports and + exports are kept as provided by the user, without applying lower or upper case + transformations. + + Note that, even when enabled, the engine is still case-insensitive. + + **(API Extension)** + ''' + return self._lib.Settings_Get_Flag(1) + + @PreserveCase.setter + def PreserveCase(self, value: bool): + self._lib.Settings_Set_Flag(1, value) diff --git a/dss/ISolution.py b/dss/ISolution.py index e6e5fdd7..1f10a44f 100644 --- a/dss/ISolution.py +++ b/dss/ISolution.py @@ -1,10 +1,11 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base from ._types import Int32Array from typing import Union, AnyStr, List from .enums import SolveModes, ControlModes, SolutionAlgorithms +import numpy as np class ISolution(Base): __slots__ = [] @@ -52,49 +53,49 @@ class ISolution(Base): ] def BuildYMatrix(self, BuildOption: int, AllocateVI: bool): - self._check_for_error(self._lib.Solution_BuildYMatrix(BuildOption, AllocateVI)) + self._lib.Solution_BuildYMatrix(BuildOption, AllocateVI) def CheckControls(self): - self._check_for_error(self._lib.Solution_CheckControls()) + self._lib.Solution_CheckControls() def CheckFaultStatus(self): - self._check_for_error(self._lib.Solution_CheckFaultStatus()) + self._lib.Solution_CheckFaultStatus() def Cleanup(self): - self._check_for_error(self._lib.Solution_Cleanup()) + self._lib.Solution_Cleanup() def DoControlActions(self): - self._check_for_error(self._lib.Solution_DoControlActions()) + self._lib.Solution_DoControlActions() def FinishTimeStep(self): - self._check_for_error(self._lib.Solution_FinishTimeStep()) + self._lib.Solution_FinishTimeStep() def InitSnap(self): - self._check_for_error(self._lib.Solution_InitSnap()) + self._lib.Solution_InitSnap() def SampleControlDevices(self): - self._check_for_error(self._lib.Solution_SampleControlDevices()) + self._lib.Solution_SampleControlDevices() def Sample_DoControlActions(self): - self._check_for_error(self._lib.Solution_Sample_DoControlActions()) + self._lib.Solution_Sample_DoControlActions() def Solve(self): - self._check_for_error(self._lib.Solution_Solve()) + self._lib.Solution_Solve() def SolveDirect(self): - self._check_for_error(self._lib.Solution_SolveDirect()) + self._lib.Solution_SolveDirect() def SolveNoControl(self): - self._check_for_error(self._lib.Solution_SolveNoControl()) + self._lib.Solution_SolveNoControl() def SolvePflow(self): - self._check_for_error(self._lib.Solution_SolvePflow()) + self._lib.Solution_SolvePflow() def SolvePlusControl(self): - self._check_for_error(self._lib.Solution_SolvePlusControl()) + self._lib.Solution_SolvePlusControl() def SolveSnap(self): - self._check_for_error(self._lib.Solution_SolveSnap()) + self._lib.Solution_SolveSnap() @property def AddType(self) -> int: @@ -103,11 +104,11 @@ def AddType(self) -> int: Original COM help: https://opendss.epri.com/AddType.html ''' - return self._check_for_error(self._lib.Solution_Get_AddType()) + return self._lib.Solution_Get_AddType() @AddType.setter def AddType(self, Value: int): - self._check_for_error(self._lib.Solution_Set_AddType(Value)) + self._lib.Solution_Set_AddType(Value) @property def Algorithm(self) -> SolutionAlgorithms: @@ -116,11 +117,11 @@ def Algorithm(self) -> SolutionAlgorithms: Original COM help: https://opendss.epri.com/Algorithm.html ''' - return SolutionAlgorithms(self._check_for_error(self._lib.Solution_Get_Algorithm())) + return SolutionAlgorithms(self._lib.Solution_Get_Algorithm()) @Algorithm.setter def Algorithm(self, Value: Union[int, SolutionAlgorithms]): - self._check_for_error(self._lib.Solution_Set_Algorithm(Value)) + self._lib.Solution_Set_Algorithm(Value) @property def Capkvar(self) -> float: @@ -129,11 +130,11 @@ def Capkvar(self) -> float: Original COM help: https://opendss.epri.com/Capkvar.html ''' - return self._check_for_error(self._lib.Solution_Get_Capkvar()) + return self._lib.Solution_Get_Capkvar() @Capkvar.setter def Capkvar(self, Value: float): - self._check_for_error(self._lib.Solution_Set_Capkvar(Value)) + self._lib.Solution_Set_Capkvar(Value) @property def ControlActionsDone(self) -> bool: @@ -142,11 +143,11 @@ def ControlActionsDone(self) -> bool: Original COM help: https://opendss.epri.com/ControlActionsDone.html ''' - return self._check_for_error(self._lib.Solution_Get_ControlActionsDone()) != 0 + return self._lib.Solution_Get_ControlActionsDone() @ControlActionsDone.setter def ControlActionsDone(self, Value: bool): - self._check_for_error(self._lib.Solution_Set_ControlActionsDone(Value)) + self._lib.Solution_Set_ControlActionsDone(Value) @property def ControlIterations(self) -> int: @@ -155,11 +156,11 @@ def ControlIterations(self) -> int: Original COM help: https://opendss.epri.com/ControlIterations.html ''' - return self._check_for_error(self._lib.Solution_Get_ControlIterations()) + return self._lib.Solution_Get_ControlIterations() @ControlIterations.setter def ControlIterations(self, Value: int): - self._check_for_error(self._lib.Solution_Set_ControlIterations(Value)) + self._lib.Solution_Set_ControlIterations(Value) @property def ControlMode(self) -> ControlModes: @@ -168,11 +169,11 @@ def ControlMode(self) -> ControlModes: Original COM help: https://opendss.epri.com/ControlMode.html ''' - return ControlModes(self._check_for_error(self._lib.Solution_Get_ControlMode())) + return ControlModes(self._lib.Solution_Get_ControlMode()) @ControlMode.setter def ControlMode(self, Value: Union[int, ControlModes]): - self._check_for_error(self._lib.Solution_Set_ControlMode(Value)) + self._lib.Solution_Set_ControlMode(Value) @property def Converged(self) -> bool: @@ -181,11 +182,11 @@ def Converged(self) -> bool: Original COM help: https://opendss.epri.com/Converged.html ''' - return self._check_for_error(self._lib.Solution_Get_Converged()) != 0 + return self._lib.Solution_Get_Converged() @Converged.setter def Converged(self, Value: bool): - self._check_for_error(self._lib.Solution_Set_Converged(Value)) + self._lib.Solution_Set_Converged(Value) @property def DefaultDaily(self) -> str: @@ -194,14 +195,11 @@ def DefaultDaily(self) -> str: Original COM help: https://opendss.epri.com/DefaultDaily.html ''' - return self._get_string(self._check_for_error(self._lib.Solution_Get_DefaultDaily())) + return self._lib.Solution_Get_DefaultDaily() @DefaultDaily.setter def DefaultDaily(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Solution_Set_DefaultDaily(Value)) + self._lib.Solution_Set_DefaultDaily(Value) @property def DefaultYearly(self) -> str: @@ -210,14 +208,11 @@ def DefaultYearly(self) -> str: Original COM help: https://opendss.epri.com/DefaultYearly.html ''' - return self._get_string(self._check_for_error(self._lib.Solution_Get_DefaultYearly())) + return self._lib.Solution_Get_DefaultYearly() @DefaultYearly.setter def DefaultYearly(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Solution_Set_DefaultYearly(Value)) + self._lib.Solution_Set_DefaultYearly(Value) @property def EventLog(self) -> List[str]: @@ -226,7 +221,7 @@ def EventLog(self) -> List[str]: Original COM help: https://opendss.epri.com/EventLog.html ''' - return self._check_for_error(self._get_string_array(self._lib.Solution_Get_EventLog)) + return self._lib.Solution_Get_EventLog() @property def Frequency(self) -> float: @@ -235,11 +230,11 @@ def Frequency(self) -> float: Original COM help: https://opendss.epri.com/Frequency1.html ''' - return self._check_for_error(self._lib.Solution_Get_Frequency()) + return self._lib.Solution_Get_Frequency() @Frequency.setter def Frequency(self, Value: float): - self._check_for_error(self._lib.Solution_Set_Frequency(Value)) + self._lib.Solution_Set_Frequency(Value) @property def GenMult(self) -> float: @@ -248,11 +243,11 @@ def GenMult(self) -> float: Original COM help: https://opendss.epri.com/GenMult.html ''' - return self._check_for_error(self._lib.Solution_Get_GenMult()) + return self._lib.Solution_Get_GenMult() @GenMult.setter def GenMult(self, Value: float): - self._check_for_error(self._lib.Solution_Set_GenMult(Value)) + self._lib.Solution_Set_GenMult(Value) @property def GenPF(self) -> float: @@ -261,11 +256,11 @@ def GenPF(self) -> float: Original COM help: https://opendss.epri.com/GenPF.html ''' - return self._check_for_error(self._lib.Solution_Get_GenPF()) + return self._lib.Solution_Get_GenPF() @GenPF.setter def GenPF(self, Value: float): - self._check_for_error(self._lib.Solution_Set_GenPF(Value)) + self._lib.Solution_Set_GenPF(Value) @property def GenkW(self) -> float: @@ -274,11 +269,11 @@ def GenkW(self) -> float: Original COM help: https://opendss.epri.com/GenkW.html ''' - return self._check_for_error(self._lib.Solution_Get_GenkW()) + return self._lib.Solution_Get_GenkW() @GenkW.setter def GenkW(self, Value: float): - self._check_for_error(self._lib.Solution_Set_GenkW(Value)) + self._lib.Solution_Set_GenkW(Value) @property def Hour(self) -> int: @@ -287,22 +282,22 @@ def Hour(self) -> int: Original COM help: https://opendss.epri.com/Hour.html ''' - return self._check_for_error(self._lib.Solution_Get_Hour()) + return self._lib.Solution_Get_Hour() @Hour.setter def Hour(self, Value: int): - self._check_for_error(self._lib.Solution_Set_Hour(Value)) + self._lib.Solution_Set_Hour(Value) @property def IntervalHrs(self) -> float: ''' Get/Set the Solution.IntervalHrs variable used for devices that integrate / custom solution algorithms ''' - return self._check_for_error(self._lib.Solution_Get_IntervalHrs()) + return self._lib.Solution_Get_IntervalHrs() @IntervalHrs.setter def IntervalHrs(self, Value: float): - self._check_for_error(self._lib.Solution_Set_IntervalHrs(Value)) + self._lib.Solution_Set_IntervalHrs(Value) @property def Iterations(self) -> int: @@ -311,7 +306,7 @@ def Iterations(self) -> int: Original COM help: https://opendss.epri.com/Iterations.html ''' - return self._check_for_error(self._lib.Solution_Get_Iterations()) + return self._lib.Solution_Get_Iterations() @property def LDCurve(self) -> str: @@ -320,14 +315,11 @@ def LDCurve(self) -> str: Original COM help: https://opendss.epri.com/LDCurve.html ''' - return self._get_string(self._check_for_error(self._lib.Solution_Get_LDCurve())) + return self._lib.Solution_Get_LDCurve() @LDCurve.setter def LDCurve(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Solution_Set_LDCurve(Value)) + self._lib.Solution_Set_LDCurve(Value) @property def LoadModel(self) -> int: @@ -336,11 +328,11 @@ def LoadModel(self) -> int: Original COM help: https://opendss.epri.com/LoadModel.html ''' - return self._check_for_error(self._lib.Solution_Get_LoadModel()) + return self._lib.Solution_Get_LoadModel() @LoadModel.setter def LoadModel(self, Value: int): - self._check_for_error(self._lib.Solution_Set_LoadModel(Value)) + self._lib.Solution_Set_LoadModel(Value) @property def LoadMult(self) -> float: @@ -349,11 +341,11 @@ def LoadMult(self) -> float: Original COM help: https://opendss.epri.com/LoadMult.html ''' - return self._check_for_error(self._lib.Solution_Get_LoadMult()) + return self._lib.Solution_Get_LoadMult() @LoadMult.setter def LoadMult(self, Value: float): - self._check_for_error(self._lib.Solution_Set_LoadMult(Value)) + self._lib.Solution_Set_LoadMult(Value) @property def MaxControlIterations(self) -> int: @@ -362,11 +354,11 @@ def MaxControlIterations(self) -> int: Original COM help: https://opendss.epri.com/MaxControlIterations.html ''' - return self._check_for_error(self._lib.Solution_Get_MaxControlIterations()) + return self._lib.Solution_Get_MaxControlIterations() @MaxControlIterations.setter def MaxControlIterations(self, Value): - self._check_for_error(self._lib.Solution_Set_MaxControlIterations(Value)) + self._lib.Solution_Set_MaxControlIterations(Value) @property def MaxIterations(self) -> int: @@ -375,11 +367,11 @@ def MaxIterations(self) -> int: Original COM help: https://opendss.epri.com/MaxIterations.html ''' - return self._check_for_error(self._lib.Solution_Get_MaxIterations()) + return self._lib.Solution_Get_MaxIterations() @MaxIterations.setter def MaxIterations(self, Value: int): - self._check_for_error(self._lib.Solution_Set_MaxIterations(Value)) + self._lib.Solution_Set_MaxIterations(Value) @property def MinIterations(self) -> int: @@ -388,11 +380,11 @@ def MinIterations(self) -> int: Original COM help: https://opendss.epri.com/MinIterations.html ''' - return self._check_for_error(self._lib.Solution_Get_MinIterations()) + return self._lib.Solution_Get_MinIterations() @MinIterations.setter def MinIterations(self, Value: int): - self._check_for_error(self._lib.Solution_Set_MinIterations(Value)) + self._lib.Solution_Set_MinIterations(Value) @property def Mode(self) -> SolveModes: @@ -401,11 +393,11 @@ def Mode(self) -> SolveModes: Original COM help: https://opendss.epri.com/Mode2.html ''' - return SolveModes(self._check_for_error(self._lib.Solution_Get_Mode())) + return SolveModes(self._lib.Solution_Get_Mode()) @Mode.setter def Mode(self, Value: Union[int, SolveModes]): - self._check_for_error(self._lib.Solution_Set_Mode(Value)) + self._lib.Solution_Set_Mode(Value) @property def ModeID(self) -> str: @@ -414,7 +406,7 @@ def ModeID(self) -> str: Original COM help: https://opendss.epri.com/ModeID.html ''' - return self._get_string(self._check_for_error(self._lib.Solution_Get_ModeID())) + return self._lib.Solution_Get_ModeID() @property def MostIterationsDone(self) -> int: @@ -423,7 +415,7 @@ def MostIterationsDone(self) -> int: Original COM help: https://opendss.epri.com/MostIterationsDone.html ''' - return self._check_for_error(self._lib.Solution_Get_MostIterationsDone()) + return self._lib.Solution_Get_MostIterationsDone() @property def Number(self) -> int: @@ -432,11 +424,11 @@ def Number(self) -> int: Original COM help: https://opendss.epri.com/Number1.html ''' - return self._check_for_error(self._lib.Solution_Get_Number()) + return self._lib.Solution_Get_Number() @Number.setter def Number(self, Value: int): - self._check_for_error(self._lib.Solution_Set_Number(Value)) + self._lib.Solution_Set_Number(Value) @property def Process_Time(self) -> float: @@ -445,7 +437,7 @@ def Process_Time(self) -> float: Original COM help: https://opendss.epri.com/Process_Time.html ''' - return self._check_for_error(self._lib.Solution_Get_Process_Time()) + return self._lib.Solution_Get_Process_Time() @property def Random(self) -> int: @@ -454,11 +446,11 @@ def Random(self) -> int: Original COM help: https://opendss.epri.com/Random.html ''' - return self._check_for_error(self._lib.Solution_Get_Random()) + return self._lib.Solution_Get_Random() @Random.setter def Random(self, Value: int): - self._check_for_error(self._lib.Solution_Set_Random(Value)) + self._lib.Solution_Set_Random(Value) @property def Seconds(self) -> float: @@ -467,11 +459,11 @@ def Seconds(self) -> float: Original COM help: https://opendss.epri.com/Seconds.html ''' - return self._check_for_error(self._lib.Solution_Get_Seconds()) + return self._lib.Solution_Get_Seconds() @Seconds.setter def Seconds(self, Value: float): - self._check_for_error(self._lib.Solution_Set_Seconds(Value)) + self._lib.Solution_Set_Seconds(Value) @property def StepSize(self) -> float: @@ -480,11 +472,11 @@ def StepSize(self) -> float: Original COM help: https://opendss.epri.com/StepSize.html ''' - return self._check_for_error(self._lib.Solution_Get_StepSize()) + return self._lib.Solution_Get_StepSize() @StepSize.setter def StepSize(self, Value: float): - self._check_for_error(self._lib.Solution_Set_StepSize(Value)) + self._lib.Solution_Set_StepSize(Value) @property def SystemYChanged(self) -> bool: @@ -493,7 +485,7 @@ def SystemYChanged(self) -> bool: Original COM help: https://opendss.epri.com/SystemYChanged.html ''' - return self._check_for_error(self._lib.Solution_Get_SystemYChanged() != 0) + return self._lib.Solution_Get_SystemYChanged() @property def Time_of_Step(self) -> float: @@ -502,7 +494,7 @@ def Time_of_Step(self) -> float: Original COM help: https://opendss.epri.com/Time_of_Step.html ''' - return self._check_for_error(self._lib.Solution_Get_Time_of_Step()) + return self._lib.Solution_Get_Time_of_Step() @property def Tolerance(self) -> float: @@ -511,11 +503,11 @@ def Tolerance(self) -> float: Original COM help: https://opendss.epri.com/Tolerance.html ''' - return self._check_for_error(self._lib.Solution_Get_Tolerance()) + return self._lib.Solution_Get_Tolerance() @Tolerance.setter def Tolerance(self, Value: float): - self._check_for_error(self._lib.Solution_Set_Tolerance(Value)) + self._lib.Solution_Set_Tolerance(Value) @property def Total_Time(self) -> float: @@ -526,11 +518,11 @@ def Total_Time(self) -> float: Original COM help: https://opendss.epri.com/Total_Time.html ''' - return self._check_for_error(self._lib.Solution_Get_Total_Time()) + return self._lib.Solution_Get_Total_Time() @Total_Time.setter def Total_Time(self, Value: float): - self._check_for_error(self._lib.Solution_Set_Total_Time(Value)) + self._lib.Solution_Set_Total_Time(Value) @property def Totaliterations(self) -> int: @@ -539,7 +531,7 @@ def Totaliterations(self) -> int: Original COM help: https://opendss.epri.com/Totaliterations.html ''' - return self._check_for_error(self._lib.Solution_Get_Totaliterations()) + return self._lib.Solution_Get_Totaliterations() @property def Year(self) -> int: @@ -548,11 +540,11 @@ def Year(self) -> int: Original COM help: https://opendss.epri.com/Year.html ''' - return self._check_for_error(self._lib.Solution_Get_Year()) + return self._lib.Solution_Get_Year() @Year.setter def Year(self, Value: int): - self._check_for_error(self._lib.Solution_Set_Year(Value)) + self._lib.Solution_Set_Year(Value) @property def dblHour(self) -> float: @@ -561,11 +553,11 @@ def dblHour(self) -> float: Original COM help: https://opendss.epri.com/dblHour1.html ''' - return self._check_for_error(self._lib.Solution_Get_dblHour()) + return self._lib.Solution_Get_dblHour() @dblHour.setter def dblHour(self, Value: float): - self._check_for_error(self._lib.Solution_Set_dblHour(Value)) + self._lib.Solution_Set_dblHour(Value) @property def pctGrowth(self) -> float: @@ -574,11 +566,11 @@ def pctGrowth(self) -> float: Original COM help: https://opendss.epri.com/pctGrowth.html ''' - return self._check_for_error(self._lib.Solution_Get_pctGrowth()) + return self._lib.Solution_Get_pctGrowth() @pctGrowth.setter def pctGrowth(self, Value: float): - self._check_for_error(self._lib.Solution_Set_pctGrowth(Value)) + self._lib.Solution_Set_pctGrowth(Value) @property def StepsizeHr(self) -> float: @@ -587,7 +579,7 @@ def StepsizeHr(self) -> float: @StepsizeHr.setter def StepsizeHr(self, Value: float): - self._check_for_error(self._lib.Solution_Set_StepsizeHr(Value)) + self._lib.Solution_Set_StepsizeHr(Value) @property def StepsizeMin(self) -> float: @@ -596,7 +588,7 @@ def StepsizeMin(self) -> float: @StepsizeMin.setter def StepsizeMin(self, Value: float): - self._check_for_error(self._lib.Solution_Set_StepsizeMin(Value)) + self._lib.Solution_Set_StepsizeMin(Value) # The following are officially available only in v8 @property @@ -611,8 +603,7 @@ def BusLevels(self) -> Int32Array: Original COM help: https://opendss.epri.com/BusLevels.html ''' - self._check_for_error(self._lib.Solution_Get_BusLevels_GR()) - return self._get_int32_gr_array() + return self._lib.Solution_Get_BusLevels_GR() @property def IncMatrix(self) -> Int32Array: @@ -628,8 +619,16 @@ def IncMatrix(self) -> Int32Array: Original COM help: https://opendss.epri.com/IncMatrix.html ''' #TODO: expose as sparse matrix - self._check_for_error(self._lib.Solution_Get_IncMatrix_GR()) - return self._get_int32_gr_array() + result = self._lib.Solution_Get_IncMatrix_GR() + n = len(result) + if n >= 3 and (n % 3) == 0: # Compatibility with COM + if isinstance(result, np.ndarray): + result = np.resize(result, n + 1) + result[-1] = 0 + else: + result.append(0) + + return result @property def IncMatrixCols(self) -> List[str]: @@ -638,7 +637,7 @@ def IncMatrixCols(self) -> List[str]: Original COM help: https://opendss.epri.com/IncMatrixCols.html ''' - return self._check_for_error(self._get_string_array(self._lib.Solution_Get_IncMatrixCols)) + return self._lib.Solution_Get_IncMatrixCols() @property def IncMatrixRows(self) -> List[str]: @@ -647,7 +646,7 @@ def IncMatrixRows(self) -> List[str]: Original COM help: https://opendss.epri.com/IncMatrixRows.html ''' - return self._check_for_error(self._get_string_array(self._lib.Solution_Get_IncMatrixRows)) + return self._lib.Solution_Get_IncMatrixRows() @property def Laplacian(self) -> Int32Array: @@ -664,8 +663,16 @@ def Laplacian(self) -> Int32Array: Original COM help: https://opendss.epri.com/Laplacian.html ''' #TODO: expose as sparse matrix - self._check_for_error(self._lib.Solution_Get_Laplacian_GR()) - return self._get_int32_gr_array() + result = self._lib.Solution_Get_Laplacian_GR() + n = len(result) + if n >= 3 and (n % 3) == 0: # Compatibility with COM + if isinstance(result, np.ndarray): + result = np.resize(result, n + 1) + result[-1] = 0 + else: + result.append(0) + + return result def SolveAll(self): ''' @@ -673,5 +680,5 @@ def SolveAll(self): Original COM help: https://opendss.epri.com/SolveAll.html ''' - self._check_for_error(self._lib.Solution_SolveAll()) + self._lib.Solution_SolveAll() diff --git a/dss/IStorages.py b/dss/IStorages.py index c7752e69..ba28fd0e 100644 --- a/dss/IStorages.py +++ b/dss/IStorages.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2023-2024 Paulo Meira -# Copyright (c) 2023-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2023-2025 Paulo Meira +# Copyright (c) 2023-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Float64Array from typing import List, Union @@ -19,28 +19,50 @@ class IStorages(Iterable): 'RegisterValues', 'puSOC', 'State', + 'AmpLimit', + 'AmpLimitGain', + 'ChargeTrigger', + 'ControlMode', + 'DischargeTrigger', + 'EffCharge', + 'EffDischarge', + 'Kp', + 'kV', + 'kVA', + 'kvar', + 'kVDC', + 'kW', + 'kWhRated', + 'kWRated', + 'LimitCurrent', + 'PF', + 'PITol', + 'SafeMode', + 'SafeVoltage', + 'TimeChargeTrig', + 'VarFollowInverter', ] @property def puSOC(self) -> float: '''Per unit state of charge''' - return self._check_for_error(self._lib.Storages_Get_puSOC()) + return self._lib.Storages_Get_puSOC() @puSOC.setter def puSOC(self, Value: float): - self._check_for_error(self._lib.Storages_Set_puSOC(Value)) + self._lib.Storages_Set_puSOC(Value) @property def State(self) -> StorageStates: ''' Get/set state: 0=Idling; 1=Discharging; -1=Charging; ''' - return StorageStates(self._check_for_error(self._lib.Storages_Get_State())) + return StorageStates(self._lib.Storages_Get_State()) @State.setter def State(self, Value: Union[int, StorageStates]): - self._check_for_error(self._lib.Storages_Set_State(Value)) + self._lib.Storages_Set_State(Value) @property def RegisterNames(self) -> List[str]: @@ -49,11 +71,253 @@ def RegisterNames(self) -> List[str]: See also the enum `GeneratorRegisters`. ''' - return self._check_for_error(self._get_string_array(self._lib.Storages_Get_RegisterNames)) + return self._lib.Storages_Get_RegisterNames() @property def RegisterValues(self) -> Float64Array: '''Array of values in Storage registers.''' - self._check_for_error(self._lib.Storages_Get_RegisterValues_GR()) - return self._get_float64_gr_array() + return self._lib.Storages_Get_RegisterValues_GR() + + @property + def AmpLimit(self) -> float: + ''' + Current limit per phase for the IBR when operating in GFM mode. + ''' + return self._lib.Storages_Get_AmpLimit() + + @AmpLimit.setter + def AmpLimit(self, Value: float) -> None: + self._lib.Storages_Set_AmpLimit(Value) + + @property + def AmpLimitGain(self) -> float: + ''' + Use it for fine tuning the current limiter when active. + ''' + return self._lib.Storages_Get_AmpLimitGain() + + @AmpLimitGain.setter + def AmpLimitGain(self, Value: float) -> None: + self._lib.Storages_Set_AmpLimitGain(Value) + + @property + def ChargeTrigger(self) -> float: + ''' + Dispatch trigger value for charging the Storage. + ''' + return self._lib.Storages_Get_ChargeTrigger() + + @ChargeTrigger.setter + def ChargeTrigger(self, Value: float) -> None: + self._lib.Storages_Set_ChargeTrigger(Value) + + @property + def ControlMode(self) -> int: + ''' + Control mode for the inverter. It can be one of {GFM = 1 | GFL* = 0}. + ''' + return self._lib.Storages_Get_ControlMode() + + @ControlMode.setter + def ControlMode(self, Value: int) -> None: + self._lib.Storages_Set_ControlMode(Value) + + @property + def DischargeTrigger(self) -> float: + ''' + Dispatch trigger value for discharging the Storage. + ''' + return self._lib.Storages_Get_DischargeTrigger() + + @DischargeTrigger.setter + def DischargeTrigger(self, Value: float) -> None: + self._lib.Storages_Set_DischargeTrigger(Value) + + @property + def EffCharge(self) -> float: + ''' + Percentage efficiency for CHARGING the Storage element. + ''' + return self._lib.Storages_Get_EffCharge() + + @EffCharge.setter + def EffCharge(self, Value: float) -> None: + self._lib.Storages_Set_EffCharge(Value) + + @property + def EffDischarge(self) -> float: + ''' + Percentage efficiency for DISCHARGING the Storage element. + ''' + return self._lib.Storages_Get_EffDischarge() + + @EffDischarge.setter + def EffDischarge(self, Value: float) -> None: + self._lib.Storages_Set_EffDischarge(Value) + + @property + def Kp(self) -> float: + ''' + Proportional gain for the PI controller within the inverter. + Use it to modify the controller response in dynamics simulation mode. + ''' + return self._lib.Storages_Get_Kp() + + @Kp.setter + def Kp(self, Value: float) -> None: + self._lib.Storages_Set_Kp(Value) + + @property + def kV(self) -> float: + ''' + Nominal rated (1.0 per unit) voltage, kV, for Storage element. + ''' + return self._lib.Storages_Get_kV() + + @kV.setter + def kV(self, Value: float) -> None: + self._lib.Storages_Set_kV(Value) + + @property + def kVA(self) -> float: + ''' + Inverter nameplate capability (in kVA). Used as the base for Dynamics mode and Harmonics mode values. + ''' + return self._lib.Storages_Get_kVA() + + @kVA.setter + def kVA(self, Value: float) -> None: + self._lib.Storages_Set_kVA(Value) + + @property + def kvar(self) -> float: + ''' + Get/set the requested kvar value. Final kvar is subjected to the inverter ratings. Sets inverter to operate in constant kvar mode. + + **Note:** reading the DSS property `kvar` returns the adjusted value, while this returns the original input value. + ''' + return self._lib.Storages_Get_kvar() + + @kvar.setter + def kvar(self, Value: float) -> None: + self._lib.Storages_Set_kvar(Value) + + @property + def kVDC(self) -> float: + ''' + Rated voltage (kV) at the input of the inverter while the storage is discharging + ''' + return self._lib.Storages_Get_kVDC() + + @kVDC.setter + def kVDC(self, Value: float) -> None: + self._lib.Storages_Set_kVDC(Value) + + @property + def kW(self) -> float: + ''' + Get/set the requested kW value. Final kW is subjected to the inverter ratings. + ''' + return self._lib.Storages_Get_kW() + + @kW.setter + def kW(self, Value: float) -> None: + self._lib.Storages_Set_kW(Value) + + @property + def kWhRated(self) -> float: + ''' + Rated Storage capacity in kWh. + ''' + return self._lib.Storages_Get_kWhRated() + + @kWhRated.setter + def kWhRated(self, Value: float) -> None: + self._lib.Storages_Set_kWhRated(Value) + + @property + def kWRated(self) -> float: + ''' + kW rating of power output. Base for Loadshapes when DispMode=Follow. Sets kVA property if it has not been specified yet. + ''' + return self._lib.Storages_Get_kWRated() + + @kWRated.setter + def kWRated(self, Value: float) -> None: + self._lib.Storages_Set_kWRated(Value) + + @property + def LimitCurrent(self) -> bool: + ''' + Limits current magnitude to Vminpu value for both 1-phase and 3-phase Storage similar to Generator Model 7. + For 3-phase, limits the positive-sequence current but not the negative-sequence." + ''' + return self._lib.Storages_Get_LimitCurrent() + + @LimitCurrent.setter + def LimitCurrent(self, Value: bool) -> None: + self._lib.Storages_Set_LimitCurrent(Value) + + @property + def PF(self) -> float: + ''' + Get/set the requested PF value. + ''' + return self._lib.Storages_Get_PF() + + @PF.setter + def PF(self, Value: float) -> None: + self._lib.Storages_Set_PF(Value) + + @property + def PITol(self) -> float: + ''' + Tolerance (%) for the closed loop controller of the inverter + ''' + return self._lib.Storages_Get_PITol() + + @PITol.setter + def PITol(self, Value: float) -> None: + self._lib.Storages_Set_PITol(Value) + + @property + def SafeMode(self) -> int: + ''' + (Read only) Indicates whether the inverter entered (Yes) or not (No) into Safe Mode. + ''' + return self._lib.Storages_Get_SafeMode() + + @property + def SafeVoltage(self) -> float: + ''' + Indicates the voltage level (%) respect to the base voltage level for which the Inverter will operate. + ''' + return self._lib.Storages_Get_SafeVoltage() + + @SafeVoltage.setter + def SafeVoltage(self, Value: float) -> None: + self._lib.Storages_Set_SafeVoltage(Value) + + @property + def TimeChargeTrig(self) -> float: + ''' + Time of day in fractional hours (0230 = 2.5) at which Storage element will automatically go into charge state. + ''' + return self._lib.Storages_Get_TimeChargeTrig() + + @TimeChargeTrig.setter + def TimeChargeTrig(self, Value: float) -> None: + self._lib.Storages_Set_TimeChargeTrig(Value) + + @property + def VarFollowInverter(self) -> int: + ''' + Indicates if the reactive power generation/absorption does not respect the inverter status + ''' + return self._lib.Storages_Get_VarFollowInverter() + + @VarFollowInverter.setter + def VarFollowInverter(self, Value: int) -> None: + self._lib.Storages_Set_VarFollowInverter(Value) + diff --git a/dss/ISwtControls.py b/dss/ISwtControls.py index b1e6a689..16bb1066 100644 --- a/dss/ISwtControls.py +++ b/dss/ISwtControls.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import AnyStr, Union from .enums import ActionCodes @@ -22,7 +22,7 @@ class ISwtControls(Iterable): ] def Reset(self): - self._check_for_error(self._lib.SwtControls_Reset()) + self._lib.SwtControls_Reset() @property def Action(self) -> int: @@ -31,11 +31,11 @@ def Action(self) -> int: Original COM help: https://opendss.epri.com/Action1.html ''' - return self._check_for_error(self._lib.SwtControls_Get_Action()) + return self._lib.SwtControls_Get_Action() @Action.setter def Action(self, Value: int): - self._check_for_error(self._lib.SwtControls_Set_Action(Value)) + self._lib.SwtControls_Set_Action(Value) @property def Delay(self) -> float: @@ -44,11 +44,11 @@ def Delay(self) -> float: Original COM help: https://opendss.epri.com/Delay3.html ''' - return self._check_for_error(self._lib.SwtControls_Get_Delay()) + return self._lib.SwtControls_Get_Delay() @Delay.setter def Delay(self, Value: float): - self._check_for_error(self._lib.SwtControls_Set_Delay(Value)) + self._lib.SwtControls_Set_Delay(Value) @property def IsLocked(self) -> bool: @@ -57,22 +57,22 @@ def IsLocked(self) -> bool: Original COM help: https://opendss.epri.com/IsLocked.html ''' - return self._check_for_error(self._lib.SwtControls_Get_IsLocked()) != 0 + return self._lib.SwtControls_Get_IsLocked() @IsLocked.setter def IsLocked(self, Value: bool): - self._check_for_error(self._lib.SwtControls_Set_IsLocked(Value)) + self._lib.SwtControls_Set_IsLocked(Value) @property def NormalState(self) -> ActionCodes: ''' Get/set Normal state of switch (see ActionCodes) dssActionOpen or dssActionClose ''' - return ActionCodes(self._check_for_error(self._lib.SwtControls_Get_NormalState())) + return ActionCodes(self._lib.SwtControls_Get_NormalState()) @NormalState.setter def NormalState(self, Value: Union[int, ActionCodes]): - self._check_for_error(self._lib.SwtControls_Set_NormalState(Value)) + self._lib.SwtControls_Set_NormalState(Value) @property def State(self) -> int: @@ -81,11 +81,11 @@ def State(self) -> int: Original COM help: https://opendss.epri.com/State.html ''' - return self._check_for_error(self._lib.SwtControls_Get_State()) + return self._lib.SwtControls_Get_State() @State.setter def State(self, Value: int): - self._check_for_error(self._lib.SwtControls_Set_State(Value)) + self._lib.SwtControls_Set_State(Value) @property def SwitchedObj(self) -> str: @@ -94,14 +94,11 @@ def SwitchedObj(self) -> str: Original COM help: https://opendss.epri.com/SwitchedObj3.html ''' - return self._get_string(self._check_for_error(self._lib.SwtControls_Get_SwitchedObj())) + return self._lib.SwtControls_Get_SwitchedObj() @SwitchedObj.setter def SwitchedObj(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.SwtControls_Set_SwitchedObj(Value)) + self._lib.SwtControls_Set_SwitchedObj(Value) @property def SwitchedTerm(self) -> int: @@ -110,9 +107,9 @@ def SwitchedTerm(self) -> int: Original COM help: https://opendss.epri.com/SwitchedTerm3.html ''' - return self._check_for_error(self._lib.SwtControls_Get_SwitchedTerm()) + return self._lib.SwtControls_Get_SwitchedTerm() @SwitchedTerm.setter def SwitchedTerm(self, Value: int): - self._check_for_error(self._lib.SwtControls_Set_SwitchedTerm(Value)) + self._lib.SwtControls_Set_SwitchedTerm(Value) diff --git a/dss/ITSData.py b/dss/ITSData.py index d3ca9799..bc50e674 100644 --- a/dss/ITSData.py +++ b/dss/ITSData.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable class ITSData(Iterable): @@ -37,137 +37,137 @@ class ITSData(Iterable): @property def EmergAmps(self) -> float: '''Emergency ampere rating''' - return self._check_for_error(self._lib.TSData_Get_EmergAmps()) + return self._lib.TSData_Get_EmergAmps() @EmergAmps.setter def EmergAmps(self, Value: float): - self._check_for_error(self._lib.TSData_Set_EmergAmps(Value)) + self._lib.TSData_Set_EmergAmps(Value) @property def NormAmps(self) -> float: '''Normal Ampere rating''' - return self._check_for_error(self._lib.TSData_Get_NormAmps()) + return self._lib.TSData_Get_NormAmps() @NormAmps.setter def NormAmps(self, Value: float): - self._check_for_error(self._lib.TSData_Set_NormAmps(Value)) + self._lib.TSData_Set_NormAmps(Value) @property def Rdc(self) -> float: - return self._check_for_error(self._lib.TSData_Get_Rdc()) + return self._lib.TSData_Get_Rdc() @Rdc.setter def Rdc(self, Value: float): - self._check_for_error(self._lib.TSData_Set_Rdc(Value)) + self._lib.TSData_Set_Rdc(Value) @property def Rac(self) -> float: - return self._check_for_error(self._lib.TSData_Get_Rac()) + return self._lib.TSData_Get_Rac() @Rac.setter def Rac(self, Value: float): - self._check_for_error(self._lib.TSData_Set_Rac(Value)) + self._lib.TSData_Set_Rac(Value) @property def GMRac(self) -> float: - return self._check_for_error(self._lib.TSData_Get_GMRac()) + return self._lib.TSData_Get_GMRac() @GMRac.setter def GMRac(self, Value: float): - self._check_for_error(self._lib.TSData_Set_GMRac(Value)) + self._lib.TSData_Set_GMRac(Value) @property def GMRUnits(self) -> int: - return self._check_for_error(self._lib.TSData_Get_GMRUnits()) + return self._lib.TSData_Get_GMRUnits() @GMRUnits.setter def GMRUnits(self, Value: int): - self._check_for_error(self._lib.TSData_Set_GMRUnits(Value)) + self._lib.TSData_Set_GMRUnits(Value) @property def Radius(self) -> float: - return self._check_for_error(self._lib.TSData_Get_Radius()) + return self._lib.TSData_Get_Radius() @Radius.setter def Radius(self, Value: float): - self._check_for_error(self._lib.TSData_Set_Radius(Value)) + self._lib.TSData_Set_Radius(Value) @property def RadiusUnits(self) -> int: - return self._check_for_error(self._lib.TSData_Get_RadiusUnits()) + return self._lib.TSData_Get_RadiusUnits() @RadiusUnits.setter def RadiusUnits(self, Value: int): - self._check_for_error(self._lib.TSData_Set_RadiusUnits(Value)) + self._lib.TSData_Set_RadiusUnits(Value) @property def ResistanceUnits(self) -> int: - return self._check_for_error(self._lib.TSData_Get_ResistanceUnits()) + return self._lib.TSData_Get_ResistanceUnits() @ResistanceUnits.setter def ResistanceUnits(self, Value: int): - self._check_for_error(self._lib.TSData_Set_ResistanceUnits(Value)) + self._lib.TSData_Set_ResistanceUnits(Value) @property def Diameter(self) -> float: - return self._check_for_error(self._lib.TSData_Get_Diameter()) + return self._lib.TSData_Get_Diameter() @Diameter.setter def Diameter(self, Value: float): - self._check_for_error(self._lib.TSData_Set_Diameter(Value)) + self._lib.TSData_Set_Diameter(Value) @property def EpsR(self) -> float: - return self._check_for_error(self._lib.TSData_Get_EpsR()) + return self._lib.TSData_Get_EpsR() @EpsR.setter def EpsR(self, Value: float): - self._check_for_error(self._lib.TSData_Set_EpsR(Value)) + self._lib.TSData_Set_EpsR(Value) @property def InsLayer(self) -> float: - return self._check_for_error(self._lib.TSData_Get_InsLayer()) + return self._lib.TSData_Get_InsLayer() @InsLayer.setter def InsLayer(self, Value: float): - self._check_for_error(self._lib.TSData_Set_InsLayer(Value)) + self._lib.TSData_Set_InsLayer(Value) @property def DiaIns(self) -> float: - return self._check_for_error(self._lib.TSData_Get_DiaIns()) + return self._lib.TSData_Get_DiaIns() @DiaIns.setter def DiaIns(self, Value: float): - self._check_for_error(self._lib.TSData_Set_DiaIns(Value)) + self._lib.TSData_Set_DiaIns(Value) @property def DiaCable(self) -> float: - return self._check_for_error(self._lib.TSData_Get_DiaCable()) + return self._lib.TSData_Get_DiaCable() @DiaCable.setter def DiaCable(self, Value: float): - self._check_for_error(self._lib.TSData_Set_DiaCable(Value)) + self._lib.TSData_Set_DiaCable(Value) @property def DiaShield(self) -> float: - return self._check_for_error(self._lib.TSData_Get_DiaShield()) + return self._lib.TSData_Get_DiaShield() @DiaShield.setter def DiaShield(self, Value: float): - self._check_for_error(self._lib.TSData_Set_DiaShield(Value)) + self._lib.TSData_Set_DiaShield(Value) @property def TapeLayer(self) -> float: - return self._check_for_error(self._lib.TSData_Get_TapeLayer()) + return self._lib.TSData_Get_TapeLayer() @TapeLayer.setter def TapeLayer(self, Value: float): - self._check_for_error(self._lib.TSData_Set_TapeLayer(Value)) + self._lib.TSData_Set_TapeLayer(Value) @property def TapeLap(self) -> float: - return self._check_for_error(self._lib.TSData_Get_TapeLap()) + return self._lib.TSData_Get_TapeLap() @TapeLap.setter def TapeLap(self, Value: float): - self._check_for_error(self._lib.TSData_Set_TapeLap(Value)) + self._lib.TSData_Set_TapeLap(Value) diff --git a/dss/IText.py b/dss/IText.py index 8faee582..518ece8d 100644 --- a/dss/IText.py +++ b/dss/IText.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base from typing import AnyStr, List, Union @@ -14,14 +14,11 @@ def Command(self) -> str: Original COM help: https://opendss.epri.com/Command1.html ''' - return self._get_string(self._check_for_error(self._lib.Text_Get_Command())) + return self._lib.Text_Get_Command() @Command.setter def Command(self, Value: str): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Text_Set_Command(Value)) + self._lib.Text_Set_Command(Value) @property def Result(self) -> str: @@ -30,7 +27,7 @@ def Result(self) -> str: Original COM help: https://opendss.epri.com/Result.html ''' - return self._get_string(self._check_for_error(self._lib.Text_Get_Result())) + return self._lib.Text_Get_Result() def Commands(self, Value: Union[AnyStr, List[AnyStr]]): ''' @@ -41,11 +38,8 @@ def Commands(self, Value: Union[AnyStr, List[AnyStr]]): **(API Extension)** ''' - if isinstance(Value, str) or isinstance(Value, bytes): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Text_CommandBlock(Value)) + if isinstance(Value, (str, bytes)): + self._lib.Text_CommandBlock(Value) else: - self._check_for_error(self._set_string_array(self._lib.Text_CommandArray, Value)) + self._set_string_array(self._lib.Text_CommandArray, Value) diff --git a/dss/ITopology.py b/dss/ITopology.py index c85f2d80..dca1e4a3 100644 --- a/dss/ITopology.py +++ b/dss/ITopology.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base from typing import List, AnyStr @@ -26,7 +26,7 @@ def ActiveBranch(self) -> int: Original COM help: https://opendss.epri.com/ActiveBranch.html ''' - return self._check_for_error(self._lib.Topology_Get_ActiveBranch()) + return self._lib.Topology_Get_ActiveBranch() @property def ActiveLevel(self) -> int: @@ -35,7 +35,7 @@ def ActiveLevel(self) -> int: Original COM help: https://opendss.epri.com/ActiveLevel.html ''' - return self._check_for_error(self._lib.Topology_Get_ActiveLevel()) + return self._lib.Topology_Get_ActiveLevel() @property def AllIsolatedBranches(self) -> List[str]: @@ -44,7 +44,7 @@ def AllIsolatedBranches(self) -> List[str]: Original COM help: https://opendss.epri.com/AllIsolatedBranches.html ''' - return self._check_for_error(self._get_string_array(self._lib.Topology_Get_AllIsolatedBranches)) + return self._lib.Topology_Get_AllIsolatedBranches() @property def AllIsolatedLoads(self) -> List[str]: @@ -53,7 +53,7 @@ def AllIsolatedLoads(self) -> List[str]: Original COM help: https://opendss.epri.com/AllIsolatedLoads.html ''' - return self._check_for_error(self._get_string_array(self._lib.Topology_Get_AllIsolatedLoads)) + return self._lib.Topology_Get_AllIsolatedLoads() @property def AllLoopedPairs(self) -> List[str]: @@ -62,7 +62,7 @@ def AllLoopedPairs(self) -> List[str]: Original COM help: https://opendss.epri.com/AllLoopedPairs.html ''' - return self._check_for_error(self._get_string_array(self._lib.Topology_Get_AllLoopedPairs)) + return self._lib.Topology_Get_AllLoopedPairs() @property def BackwardBranch(self) -> int: @@ -71,7 +71,7 @@ def BackwardBranch(self) -> int: Original COM help: https://opendss.epri.com/BackwardBranch.html ''' - return self._check_for_error(self._lib.Topology_Get_BackwardBranch()) + return self._lib.Topology_Get_BackwardBranch() @property def BranchName(self) -> str: @@ -80,14 +80,11 @@ def BranchName(self) -> str: Original COM help: https://opendss.epri.com/BranchName.html ''' - return self._get_string(self._check_for_error(self._lib.Topology_Get_BranchName())) + return self._lib.Topology_Get_BranchName() @BranchName.setter def BranchName(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Topology_Set_BranchName(Value)) + self._lib.Topology_Set_BranchName(Value) @property def BusName(self) -> str: @@ -96,14 +93,11 @@ def BusName(self) -> str: Original COM help: https://opendss.epri.com/BusName.html ''' - return self._get_string(self._check_for_error(self._lib.Topology_Get_BusName())) + return self._lib.Topology_Get_BusName() @BusName.setter def BusName(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Topology_Set_BusName(Value)) + self._lib.Topology_Set_BusName(Value) @property def First(self) -> int: @@ -112,7 +106,7 @@ def First(self) -> int: Original COM help: https://opendss.epri.com/First19.html ''' - return self._check_for_error(self._lib.Topology_Get_First()) + return self._lib.Topology_Get_First() @property def FirstLoad(self) -> int: @@ -121,7 +115,7 @@ def FirstLoad(self) -> int: Original COM help: https://opendss.epri.com/FirstLoad.html ''' - return self._check_for_error(self._lib.Topology_Get_FirstLoad()) + return self._lib.Topology_Get_FirstLoad() @property def ForwardBranch(self) -> int: @@ -130,7 +124,7 @@ def ForwardBranch(self) -> int: Original COM help: https://opendss.epri.com/ForwardBranch.html ''' - return self._check_for_error(self._lib.Topology_Get_ForwardBranch()) + return self._lib.Topology_Get_ForwardBranch() @property def LoopedBranch(self) -> int: @@ -139,7 +133,7 @@ def LoopedBranch(self) -> int: Original COM help: https://opendss.epri.com/LoopedBranch.html ''' - return self._check_for_error(self._lib.Topology_Get_LoopedBranch()) + return self._lib.Topology_Get_LoopedBranch() @property def Next(self) -> int: @@ -148,7 +142,7 @@ def Next(self) -> int: Original COM help: https://opendss.epri.com/Next18.html ''' - return self._check_for_error(self._lib.Topology_Get_Next()) + return self._lib.Topology_Get_Next() @property def NextLoad(self) -> int: @@ -157,7 +151,7 @@ def NextLoad(self) -> int: Original COM help: https://opendss.epri.com/NextLoad.html ''' - return self._check_for_error(self._lib.Topology_Get_NextLoad()) + return self._lib.Topology_Get_NextLoad() @property def NumIsolatedBranches(self) -> int: @@ -166,7 +160,7 @@ def NumIsolatedBranches(self) -> int: Original COM help: https://opendss.epri.com/NumIsolatedBranches.html ''' - return self._check_for_error(self._lib.Topology_Get_NumIsolatedBranches()) + return self._lib.Topology_Get_NumIsolatedBranches() @property def NumIsolatedLoads(self) -> int: @@ -175,7 +169,7 @@ def NumIsolatedLoads(self) -> int: Original COM help: https://opendss.epri.com/NumIsolatedLoads.html ''' - return self._check_for_error(self._lib.Topology_Get_NumIsolatedLoads()) + return self._lib.Topology_Get_NumIsolatedLoads() @property def NumLoops(self) -> int: @@ -184,7 +178,7 @@ def NumLoops(self) -> int: Original COM help: https://opendss.epri.com/NumLoops.html ''' - return self._check_for_error(self._lib.Topology_Get_NumLoops()) + return self._lib.Topology_Get_NumLoops() @property def ParallelBranch(self) -> int: @@ -193,5 +187,5 @@ def ParallelBranch(self) -> int: Original COM help: https://opendss.epri.com/ParallelBranch.html ''' - return self._check_for_error(self._lib.Topology_Get_ParallelBranch()) + return self._lib.Topology_Get_ParallelBranch() diff --git a/dss/ITransformers.py b/dss/ITransformers.py index c07055ea..46f2f5e1 100644 --- a/dss/ITransformers.py +++ b/dss/ITransformers.py @@ -1,8 +1,8 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable -from ._types import Float64ArrayOrComplexArray +from ._types import ComplexArray, ComplexMatrix from typing import AnyStr, Union from .enums import CoreType as TransformerCoreType @@ -43,11 +43,11 @@ def IsDelta(self) -> bool: Original COM help: https://opendss.epri.com/IsDelta3.html ''' - return self._check_for_error(self._lib.Transformers_Get_IsDelta()) != 0 + return self._lib.Transformers_Get_IsDelta() @IsDelta.setter def IsDelta(self, Value: bool): - self._check_for_error(self._lib.Transformers_Set_IsDelta(Value)) + self._lib.Transformers_Set_IsDelta(Value) @property def MaxTap(self) -> float: @@ -56,11 +56,11 @@ def MaxTap(self) -> float: Original COM help: https://opendss.epri.com/MaxTap.html ''' - return self._check_for_error(self._lib.Transformers_Get_MaxTap()) + return self._lib.Transformers_Get_MaxTap() @MaxTap.setter def MaxTap(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_MaxTap(Value)) + self._lib.Transformers_Set_MaxTap(Value) @property def MinTap(self) -> float: @@ -69,11 +69,11 @@ def MinTap(self) -> float: Original COM help: https://opendss.epri.com/MinTap.html ''' - return self._check_for_error(self._lib.Transformers_Get_MinTap()) + return self._lib.Transformers_Get_MinTap() @MinTap.setter def MinTap(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_MinTap(Value)) + self._lib.Transformers_Set_MinTap(Value) @property def NumTaps(self) -> int: @@ -82,11 +82,11 @@ def NumTaps(self) -> int: Original COM help: https://opendss.epri.com/NumTaps.html ''' - return self._check_for_error(self._lib.Transformers_Get_NumTaps()) + return self._lib.Transformers_Get_NumTaps() @NumTaps.setter def NumTaps(self, Value: int): - self._check_for_error(self._lib.Transformers_Set_NumTaps(Value)) + self._lib.Transformers_Set_NumTaps(Value) @property def NumWindings(self) -> int: @@ -95,11 +95,11 @@ def NumWindings(self) -> int: Original COM help: https://opendss.epri.com/NumWindings.html ''' - return self._check_for_error(self._lib.Transformers_Get_NumWindings()) + return self._lib.Transformers_Get_NumWindings() @NumWindings.setter def NumWindings(self, Value: int): - self._check_for_error(self._lib.Transformers_Set_NumWindings(Value)) + self._lib.Transformers_Set_NumWindings(Value) @property def R(self) -> float: @@ -108,11 +108,11 @@ def R(self) -> float: Original COM help: https://opendss.epri.com/R.html ''' - return self._check_for_error(self._lib.Transformers_Get_R()) + return self._lib.Transformers_Get_R() @R.setter def R(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_R(Value)) + self._lib.Transformers_Set_R(Value) @property def Rneut(self) -> float: @@ -121,11 +121,11 @@ def Rneut(self) -> float: Original COM help: https://opendss.epri.com/Rneut1.html ''' - return self._check_for_error(self._lib.Transformers_Get_Rneut()) + return self._lib.Transformers_Get_Rneut() @Rneut.setter def Rneut(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_Rneut(Value)) + self._lib.Transformers_Set_Rneut(Value) @property def Tap(self) -> float: @@ -134,11 +134,11 @@ def Tap(self) -> float: Original COM help: https://opendss.epri.com/Tap.html ''' - return self._check_for_error(self._lib.Transformers_Get_Tap()) + return self._lib.Transformers_Get_Tap() @Tap.setter def Tap(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_Tap(Value)) + self._lib.Transformers_Set_Tap(Value) @property def Wdg(self) -> int: @@ -147,11 +147,11 @@ def Wdg(self) -> int: Original COM help: https://opendss.epri.com/Wdg.html ''' - return self._check_for_error(self._lib.Transformers_Get_Wdg()) + return self._lib.Transformers_Get_Wdg() @Wdg.setter def Wdg(self, Value: int): - self._check_for_error(self._lib.Transformers_Set_Wdg(Value)) + self._lib.Transformers_Set_Wdg(Value) @property def XfmrCode(self) -> str: @@ -160,14 +160,11 @@ def XfmrCode(self) -> str: Original COM help: https://opendss.epri.com/XfmrCode1.html ''' - return self._get_string(self._check_for_error(self._lib.Transformers_Get_XfmrCode())) + return self._lib.Transformers_Get_XfmrCode() @XfmrCode.setter def XfmrCode(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._lib.Transformers_Set_XfmrCode(Value)) + self._lib.Transformers_Set_XfmrCode(Value) @property def Xhl(self) -> float: @@ -176,11 +173,11 @@ def Xhl(self) -> float: Original COM help: https://opendss.epri.com/Xhl.html ''' - return self._check_for_error(self._lib.Transformers_Get_Xhl()) + return self._lib.Transformers_Get_Xhl() @Xhl.setter def Xhl(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_Xhl(Value)) + self._lib.Transformers_Set_Xhl(Value) @property def Xht(self) -> float: @@ -189,11 +186,11 @@ def Xht(self) -> float: Original COM help: https://opendss.epri.com/Xht.html ''' - return self._check_for_error(self._lib.Transformers_Get_Xht()) + return self._lib.Transformers_Get_Xht() @Xht.setter def Xht(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_Xht(Value)) + self._lib.Transformers_Set_Xht(Value) @property def Xlt(self) -> float: @@ -202,11 +199,11 @@ def Xlt(self) -> float: Original COM help: https://opendss.epri.com/Xlt.html ''' - return self._check_for_error(self._lib.Transformers_Get_Xlt()) + return self._lib.Transformers_Get_Xlt() @Xlt.setter def Xlt(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_Xlt(Value)) + self._lib.Transformers_Set_Xlt(Value) @property def Xneut(self) -> float: @@ -215,11 +212,11 @@ def Xneut(self) -> float: Original COM help: https://opendss.epri.com/Xneut1.html ''' - return self._check_for_error(self._lib.Transformers_Get_Xneut()) + return self._lib.Transformers_Get_Xneut() @Xneut.setter def Xneut(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_Xneut(Value)) + self._lib.Transformers_Set_Xneut(Value) @property def kV(self) -> float: @@ -228,11 +225,11 @@ def kV(self) -> float: Original COM help: https://opendss.epri.com/kV3.html ''' - return self._check_for_error(self._lib.Transformers_Get_kV()) + return self._lib.Transformers_Get_kV() @kV.setter def kV(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_kV(Value)) + self._lib.Transformers_Set_kV(Value) @property def kVA(self) -> float: @@ -241,16 +238,16 @@ def kVA(self) -> float: Original COM help: https://opendss.epri.com/kva1.html ''' - return self._check_for_error(self._lib.Transformers_Get_kVA()) + return self._lib.Transformers_Get_kVA() @kVA.setter def kVA(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_kVA(Value)) + self._lib.Transformers_Set_kVA(Value) kva = kVA @property - def WdgVoltages(self) -> Float64ArrayOrComplexArray: + def WdgVoltages(self) -> ComplexArray: ''' Complex array of voltages for active winding @@ -259,11 +256,10 @@ def WdgVoltages(self) -> Float64ArrayOrComplexArray: Original COM help: https://opendss.epri.com/WdgVoltages.html ''' - self._check_for_error(self._lib.Transformers_Get_WdgVoltages_GR()) - return self._get_complex128_gr_array() + return self._lib.Transformers_Get_WdgVoltages_GR() @property - def WdgCurrents(self) -> Float64ArrayOrComplexArray: + def WdgCurrents(self) -> ComplexArray: ''' All Winding currents (ph1, wdg1, wdg2,... ph2, wdg1, wdg2 ...) @@ -272,8 +268,7 @@ def WdgCurrents(self) -> Float64ArrayOrComplexArray: Original COM help: https://opendss.epri.com/WdgCurrents.html ''' - self._check_for_error(self._lib.Transformers_Get_WdgCurrents_GR()) - return self._get_complex128_gr_array() + return self._lib.Transformers_Get_WdgCurrents_GR() @property def strWdgCurrents(self) -> str: @@ -283,7 +278,7 @@ def strWdgCurrents(self) -> str: **WARNING:** If the transformer has open terminal(s), results may be wrong, i.e. avoid using this in those situations. For more information, see https://github.com/dss-extensions/dss-extensions/issues/24 ''' - return self._get_string(self._check_for_error(self._lib.Transformers_Get_strWdgCurrents())) + return self._lib.Transformers_Get_strWdgCurrents() @property def CoreType(self) -> TransformerCoreType: @@ -292,11 +287,11 @@ def CoreType(self) -> TransformerCoreType: Original COM help: https://opendss.epri.com/CoreType.html ''' - return TransformerCoreType(self._check_for_error(self._lib.Transformers_Get_CoreType())) + return TransformerCoreType(self._lib.Transformers_Get_CoreType()) @CoreType.setter def CoreType(self, Value: Union[int, TransformerCoreType]): - self._check_for_error(self._lib.Transformers_Set_CoreType(Value)) + self._lib.Transformers_Set_CoreType(Value) @property def RdcOhms(self) -> float: @@ -305,28 +300,26 @@ def RdcOhms(self) -> float: Original COM help: https://opendss.epri.com/RdcOhms.html ''' - return self._check_for_error(self._lib.Transformers_Get_RdcOhms()) + return self._lib.Transformers_Get_RdcOhms() @RdcOhms.setter def RdcOhms(self, Value: float): - self._check_for_error(self._lib.Transformers_Set_RdcOhms(Value)) + self._lib.Transformers_Set_RdcOhms(Value) @property - def LossesByType(self) -> Float64ArrayOrComplexArray: + def LossesByType(self) -> ComplexArray: ''' - Complex array with the losses by type (total losses, load losses, no-load losses), in VA + Complex array with the losses by type (total losses, load losses, no-load losses), in VA, for the current active transformer **(API Extension)** ''' - self._check_for_error(self._lib.Transformers_Get_LossesByType_GR()) - return self._get_complex128_gr_array() + return self._lib.Transformers_Get_LossesByType_GR() @property - def AllLossesByType(self) -> Float64ArrayOrComplexArray: + def AllLossesByType(self) -> ComplexMatrix: ''' Complex array with the losses by type (total losses, load losses, no-load losses), in VA, concatenated for ALL transformers **(API Extension)** ''' - self._check_for_error(self._lib.Transformers_Get_AllLossesByType_GR()) - return self._get_complex128_gr_array() + return self._lib.Transformers_Get_AllLossesByType_GR() diff --git a/dss/IVsources.py b/dss/IVsources.py index d2e9c6a9..33bf0cd8 100644 --- a/dss/IVsources.py +++ b/dss/IVsources.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable class IVsources(Iterable): @@ -24,11 +24,11 @@ def AngleDeg(self) -> float: Original COM help: https://opendss.epri.com/AngleDeg1.html ''' - return self._check_for_error(self._lib.Vsources_Get_AngleDeg()) + return self._lib.Vsources_Get_AngleDeg() @AngleDeg.setter def AngleDeg(self, Value: float): - self._check_for_error(self._lib.Vsources_Set_AngleDeg(Value)) + self._lib.Vsources_Set_AngleDeg(Value) @property def BasekV(self) -> float: @@ -37,11 +37,11 @@ def BasekV(self) -> float: Original COM help: https://opendss.epri.com/BasekV.html ''' - return self._check_for_error(self._lib.Vsources_Get_BasekV()) + return self._lib.Vsources_Get_BasekV() @BasekV.setter def BasekV(self, Value: float): - self._check_for_error(self._lib.Vsources_Set_BasekV(Value)) + self._lib.Vsources_Set_BasekV(Value) @property def Frequency(self) -> float: @@ -50,11 +50,11 @@ def Frequency(self) -> float: Original COM help: https://opendss.epri.com/Frequency2.html ''' - return self._check_for_error(self._lib.Vsources_Get_Frequency()) + return self._lib.Vsources_Get_Frequency() @Frequency.setter def Frequency(self, Value: float): - self._check_for_error(self._lib.Vsources_Set_Frequency(Value)) + self._lib.Vsources_Set_Frequency(Value) @property def Phases(self) -> int: @@ -63,11 +63,11 @@ def Phases(self) -> int: Original COM help: https://opendss.epri.com/Phases3.html ''' - return self._check_for_error(self._lib.Vsources_Get_Phases()) + return self._lib.Vsources_Get_Phases() @Phases.setter def Phases(self, Value: int): - self._check_for_error(self._lib.Vsources_Set_Phases(Value)) + self._lib.Vsources_Set_Phases(Value) @property def pu(self) -> float: @@ -76,8 +76,8 @@ def pu(self) -> float: Original COM help: https://opendss.epri.com/pu.html ''' - return self._check_for_error(self._lib.Vsources_Get_pu()) + return self._lib.Vsources_Get_pu() @pu.setter def pu(self, Value: float): - self._check_for_error(self._lib.Vsources_Set_pu(Value)) + self._lib.Vsources_Set_pu(Value) diff --git a/dss/IWindGens.py b/dss/IWindGens.py new file mode 100644 index 00000000..7a2896ec --- /dev/null +++ b/dss/IWindGens.py @@ -0,0 +1,395 @@ +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2024-2025 Paulo Meira +# Copyright (c) 2024-2025 DSS-Extensions contributors +from ._cffi_api_util import Iterable +from ._types import Float64Array +from typing import List, Union, AnyStr + +class IWindGens(Iterable): + '''WindGen objects''' + + __slots__ = [] + _is_circuit_element = True + + _columns = [ + 'Name', + 'idx', + 'RegisterNames', + 'RegisterValues', + 'Ag', + 'Bus1', + 'Class', + 'Cp', + 'daily', + 'duty', + 'IsDelta', + 'kV', + 'kVA', + 'kvar', + 'kW', + 'Lamda', + 'N_WTG', + 'NPoles', + 'pd', + 'PF', + 'Phases', + 'PSS', + 'QFlag', + 'QMode', + 'QSS', + 'Rad', + 'RThev', + 'VCutIn', + 'VCutOut', + 'Vss', + 'WindSpeed', + 'XThev', + 'Yearly', + ] + + @property + def RegisterNames(self) -> List[str]: + ''' + Array of WindGen energy meter register names + + See also the enum `GeneratorRegisters`. + ''' + return self._lib.WindGens_Get_RegisterNames() + + @property + def RegisterValues(self) -> Float64Array: + '''Array of values in WindGen registers.''' + return self._lib.WindGens_Get_RegisterValues_GR() + + @property + def kV(self) -> float: + ''' + Nominal rated (1.0 per unit) voltage for the active WindGen, in kV. + ''' + return self._lib.WindGens_Get_kV() + + @kV.setter + def kV(self, Value: float) -> None: + self._lib.WindGens_Set_kV(Value) + + @property + def kvar(self) -> float: + ''' + Base kvar for the active WindGen. + ''' + return self._lib.WindGens_Get_kvar() + + @kvar.setter + def kvar(self, Value: float) -> None: + self._lib.WindGens_Set_kvar(Value) + + @property + def kW(self) -> float: + ''' + Total base kW for the active WindGen. + ''' + return self._lib.WindGens_Get_kW() + + @kW.setter + def kW(self, Value: float) -> None: + self._lib.WindGens_Set_kW(Value) + + @property + def PF(self) -> float: + ''' + WindGen power factor. Power factor (pos. = producing vars). + ''' + return self._lib.WindGens_Get_PF() + + @PF.setter + def PF(self, Value: float) -> None: + self._lib.WindGens_Set_PF(Value) + + @property + def kVA(self) -> float: + ''' + KVA rating of the electrical machine in the WindGen. + ''' + return self._lib.WindGens_Get_kVA() + + @kVA.setter + def kVA(self, Value: float) -> None: + self._lib.WindGens_Set_kVA(Value) + + @property + def Ag(self) -> float: + ''' + Gearbox ratio + ''' + return self._lib.WindGens_Get_Ag() + + @Ag.setter + def Ag(self, Value: float) -> None: + self._lib.WindGens_Set_Ag(Value) + + @property + def Cp(self) -> float: + ''' + Turbine performance coefficient. + ''' + return self._lib.WindGens_Get_Cp() + + @Cp.setter + def Cp(self, Value: float) -> None: + self._lib.WindGens_Set_Cp(Value) + + @property + def Lamda(self) -> float: + ''' + Tip speed ratio + ''' + return self._lib.WindGens_Get_Lamda() + + @Lamda.setter + def Lamda(self, Value: float) -> None: + self._lib.WindGens_Set_Lamda(Value) + + @property + def N_WTG(self) -> int: + ''' + Number of WTG in aggregation + ''' + return self._lib.WindGens_Get_N_WTG() + + @N_WTG.setter + def N_WTG(self, Value: int) -> None: + self._lib.WindGens_Set_N_WTG(Value) + + @property + def NPoles(self) -> int: + ''' + Number of pole pairs of the induction generator + ''' + return self._lib.WindGens_Get_NPoles() + + @NPoles.setter + def NPoles(self, Value: int) -> None: + self._lib.WindGens_Set_NPoles(Value) + + @property + def pd(self) -> float: + ''' + Air density in kg/m3 + ''' + return self._lib.WindGens_Get_pd() + + @pd.setter + def pd(self, Value: float) -> None: + self._lib.WindGens_Set_pd(Value) + + @property + def PSS(self) -> float: + ''' + Steady state output real power. + ''' + return self._lib.WindGens_Get_PSS() + + @PSS.setter + def PSS(self, Value: float) -> None: + self._lib.WindGens_Set_PSS(Value) + + @property + def QFlag(self) -> int: + ''' + Non-zero values enable reactive power and voltage control in the dynamic model. + ''' + return self._lib.WindGens_Get_QFlag() + + @QFlag.setter + def QFlag(self, Value: int) -> None: + self._lib.WindGens_Set_QFlag(Value) + + @property + def QMode(self) -> int: + ''' + Q control mode (0:Q, 1:PF, 2:VV). + ''' + return self._lib.WindGens_Get_QMode() + + @QMode.setter + def QMode(self, Value: int) -> None: + self._lib.WindGens_Set_QMode(Value) + + @property + def QSS(self) -> float: + ''' + Steady state output reactive power. + ''' + return self._lib.WindGens_Get_QSS() + + @QSS.setter + def QSS(self, Value: float) -> None: + self._lib.WindGens_Set_QSS(Value) + + @property + def Rad(self) -> float: + ''' + Rotor radius in meters + ''' + return self._lib.WindGens_Get_Rad() + + @Rad.setter + def Rad(self, Value: float) -> None: + self._lib.WindGens_Set_Rad(Value) + + @property + def RThev(self) -> float: + ''' + Per unit Thevenin equivalent resistance (R). + ''' + return self._lib.WindGens_Get_RThev() + + @RThev.setter + def RThev(self, Value: float) -> None: + self._lib.WindGens_Set_RThev(Value) + + @property + def VCutIn(self) -> float: + ''' + Cut-in speed for the wind generator + ''' + return self._lib.WindGens_Get_VCutIn() + + @VCutIn.setter + def VCutIn(self, Value: float) -> None: + self._lib.WindGens_Set_VCutIn(Value) + + @property + def VCutOut(self) -> float: + ''' + Cut-out speed for the wind generator + ''' + return self._lib.WindGens_Get_VCutOut() + + @VCutOut.setter + def VCutOut(self, Value: float) -> None: + self._lib.WindGens_Set_VCutOut(Value) + + @property + def Vss(self) -> float: + ''' + Steady state voltage magnitude. + ''' + return self._lib.WindGens_Get_Vss() + + @Vss.setter + def Vss(self, Value: float) -> None: + self._lib.WindGens_Set_Vss(Value) + + @property + def WindSpeed(self) -> float: + ''' + Wind speed in m/s + ''' + return self._lib.WindGens_Get_WindSpeed() + + @WindSpeed.setter + def WindSpeed(self, Value: float) -> None: + self._lib.WindGens_Set_WindSpeed(Value) + + @property + def XThev(self) -> float: + ''' + Per unit Thevenin equivalent reactance (X). + ''' + return self._lib.WindGens_Get_XThev() + + @XThev.setter + def XThev(self, Value: float) -> None: + self._lib.WindGens_Set_XThev(Value) + + @property + def Phases(self) -> int: + ''' + Number of phases + + (API Extension) + ''' + return self._lib.WindGens_Get_Phases() + + @Phases.setter + def Phases(self, Value: int) -> None: + self._lib.WindGens_Set_Phases(Value) + + @property + def daily(self) -> str: + ''' + Name of the loadshape for daily wind speed + + (API Extension) + ''' + return self._lib.WindGens_Get_daily() + + @daily.setter + def daily(self, Value: AnyStr) -> None: + self._lib.WindGens_Set_daily(Value) + + @property + def duty(self) -> str: + ''' + Name of the loadshape for a duty cycle simulation. + + (API Extension) + ''' + return self._lib.WindGens_Get_duty() + + @duty.setter + def duty(self, Value: AnyStr) -> None: + self._lib.WindGens_Set_duty(Value) + + @property + def Yearly(self) -> str: + ''' + Name of yearly loadshape + + (API Extension) + ''' + return self._lib.WindGens_Get_Yearly() + + @Yearly.setter + def Yearly(self, Value: AnyStr) -> None: + self._lib.WindGens_Set_Yearly(Value) + + @property + def IsDelta(self) -> bool: + ''' + WindGen connection. True/1 if delta connection, False/0 if wye. + + (API Extension) + ''' + return self._lib.WindGens_Get_IsDelta() + + @IsDelta.setter + def IsDelta(self, Value: bool) -> None: + self._lib.WindGens_Set_IsDelta(Value) + + @property + def Class(self) -> int: + ''' + An arbitrary integer number representing the class of WindGen so that WindGen values may be segregated by class. + + (API Extension) + ''' + return self._lib.WindGens_Get_Class_() + + @Class.setter + def Class(self, Value: int) -> None: + self._lib.WindGens_Set_Class_(Value) + + @property + def Bus1(self) -> str: + ''' + Bus to which the WindGen is connected. May include specific node specification. + + (API Extension) + ''' + return self._lib.WindGens_Get_Bus1() + + @Bus1.setter + def Bus1(self, Value: AnyStr): + self._lib.WindGens_Set_Bus1(Value) diff --git a/dss/IWireData.py b/dss/IWireData.py index 36e3e5e7..4d7d0b9e 100644 --- a/dss/IWireData.py +++ b/dss/IWireData.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from typing import Union from .enums import LineUnits @@ -33,90 +33,90 @@ class IWireData(Iterable): @property def EmergAmps(self) -> float: '''Emergency ampere rating''' - return self._check_for_error(self._lib.WireData_Get_EmergAmps()) + return self._lib.WireData_Get_EmergAmps() @EmergAmps.setter def EmergAmps(self, Value: float): - self._check_for_error(self._lib.WireData_Set_EmergAmps(Value)) + self._lib.WireData_Set_EmergAmps(Value) @property def NormAmps(self) -> float: '''Normal Ampere rating''' - return self._check_for_error(self._lib.WireData_Get_NormAmps()) + return self._lib.WireData_Get_NormAmps() @NormAmps.setter def NormAmps(self, Value: float): - self._check_for_error(self._lib.WireData_Set_NormAmps(Value)) + self._lib.WireData_Set_NormAmps(Value) @property def Rdc(self) -> float: - return self._check_for_error(self._lib.WireData_Get_Rdc()) + return self._lib.WireData_Get_Rdc() @Rdc.setter def Rdc(self, Value: float): - self._check_for_error(self._lib.WireData_Set_Rdc(Value)) + self._lib.WireData_Set_Rdc(Value) @property def Rac(self) -> float: - return self._check_for_error(self._lib.WireData_Get_Rac()) + return self._lib.WireData_Get_Rac() @Rac.setter def Rac(self, Value: float): - self._check_for_error(self._lib.WireData_Set_Rac(Value)) + self._lib.WireData_Set_Rac(Value) @property def GMRac(self) -> float: - return self._check_for_error(self._lib.WireData_Get_GMRac()) + return self._lib.WireData_Get_GMRac() @GMRac.setter def GMRac(self, Value: float): - self._check_for_error(self._lib.WireData_Set_GMRac(Value)) + self._lib.WireData_Set_GMRac(Value) @property def GMRUnits(self) -> LineUnits: - return LineUnits(self._check_for_error(self._lib.WireData_Get_GMRUnits())) + return LineUnits(self._lib.WireData_Get_GMRUnits()) @GMRUnits.setter def GMRUnits(self, Value: Union[int, LineUnits]): - self._check_for_error(self._lib.WireData_Set_GMRUnits(Value)) + self._lib.WireData_Set_GMRUnits(Value) @property def Radius(self) -> float: - return self._check_for_error(self._lib.WireData_Get_Radius()) + return self._lib.WireData_Get_Radius() @Radius.setter def Radius(self, Value: float): - self._check_for_error(self._lib.WireData_Set_Radius(Value)) + self._lib.WireData_Set_Radius(Value) @property def RadiusUnits(self) -> int: - return self._check_for_error(self._lib.WireData_Get_RadiusUnits()) + return self._lib.WireData_Get_RadiusUnits() @RadiusUnits.setter def RadiusUnits(self, Value: int): - self._check_for_error(self._lib.WireData_Set_RadiusUnits(Value)) + self._lib.WireData_Set_RadiusUnits(Value) @property def ResistanceUnits(self) -> LineUnits: - return LineUnits(self._check_for_error(self._lib.WireData_Get_ResistanceUnits())) + return LineUnits(self._lib.WireData_Get_ResistanceUnits()) @ResistanceUnits.setter def ResistanceUnits(self, Value: Union[int, LineUnits]): - self._check_for_error(self._lib.WireData_Set_ResistanceUnits(Value)) + self._lib.WireData_Set_ResistanceUnits(Value) @property def Diameter(self) -> float: - return self._check_for_error(self._lib.WireData_Get_Diameter()) + return self._lib.WireData_Get_Diameter() @Diameter.setter def Diameter(self, Value: float): - self._check_for_error(self._lib.WireData_Set_Diameter(Value)) + self._lib.WireData_Set_Diameter(Value) @property def CapRadius(self) -> float: '''Equivalent conductor radius for capacitance calcs. Specify this for bundled conductors. Defaults to same value as radius.''' - return self._check_for_error(self._lib.WireData_Get_CapRadius()) + return self._lib.WireData_Get_CapRadius() @CapRadius.setter def CapRadius(self, Value: float): - self._check_for_error(self._lib.WireData_Set_CapRadius(Value)) + self._lib.WireData_Set_CapRadius(Value) diff --git a/dss/IXYCurves.py b/dss/IXYCurves.py index 6b350461..eeb21dea 100644 --- a/dss/IXYCurves.py +++ b/dss/IXYCurves.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Iterable from ._types import Float64Array @@ -28,26 +28,25 @@ def Npts(self) -> int: Original COM help: https://opendss.epri.com/Npts1.html ''' - return self._check_for_error(self._lib.XYCurves_Get_Npts()) + return self._lib.XYCurves_Get_Npts() @Npts.setter def Npts(self, Value: int): - self._check_for_error(self._lib.XYCurves_Set_Npts(Value)) + self._lib.XYCurves_Set_Npts(Value) @property def Xarray(self) -> Float64Array: ''' - Get/set X values as a Array of doubles. Set Npts to max number expected if setting + Get/set X values as an array of doubles. When setting, remember to set Npts to max number expected values. Original COM help: https://opendss.epri.com/Xarray.html ''' - self._check_for_error(self._lib.XYCurves_Get_Xarray_GR()) - return self._get_float64_gr_array() + return self._lib.XYCurves_Get_Xarray_GR() @Xarray.setter def Xarray(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.XYCurves_Set_Xarray(ValuePtr, ValueCount)) + self._lib.XYCurves_Set_Xarray(ValuePtr, ValueCount) @property def Xscale(self) -> float: @@ -56,11 +55,11 @@ def Xscale(self) -> float: Original COM help: https://opendss.epri.com/Xscale.html ''' - return self._check_for_error(self._lib.XYCurves_Get_Xscale()) + return self._lib.XYCurves_Get_Xscale() @Xscale.setter def Xscale(self, Value: float): - self._check_for_error(self._lib.XYCurves_Set_Xscale(Value)) + self._lib.XYCurves_Set_Xscale(Value) @property def Xshift(self) -> float: @@ -69,26 +68,25 @@ def Xshift(self) -> float: Original COM help: https://opendss.epri.com/Xshift.html ''' - return self._check_for_error(self._lib.XYCurves_Get_Xshift()) + return self._lib.XYCurves_Get_Xshift() @Xshift.setter def Xshift(self, Value: float): - self._check_for_error(self._lib.XYCurves_Set_Xshift(Value)) + self._lib.XYCurves_Set_Xshift(Value) @property def Yarray(self) -> Float64Array: ''' - Get/Set Y values in curve; Set Npts to max number expected if setting + Get/set Y values as an array of doubles. When setting, remember to set Npts to max number expected values. Original COM help: https://opendss.epri.com/Yarray.html ''' - self._check_for_error(self._lib.XYCurves_Get_Yarray_GR()) - return self._get_float64_gr_array() + return self._lib.XYCurves_Get_Yarray_GR() @Yarray.setter def Yarray(self, Value: Float64Array): Value, ValuePtr, ValueCount = self._prepare_float64_array(Value) - self._check_for_error(self._lib.XYCurves_Set_Yarray(ValuePtr, ValueCount)) + self._lib.XYCurves_Set_Yarray(ValuePtr, ValueCount) @property def Yscale(self) -> float: @@ -97,11 +95,11 @@ def Yscale(self) -> float: Original COM help: https://opendss.epri.com/Yscale.html ''' - return self._check_for_error(self._lib.XYCurves_Get_Yscale()) + return self._lib.XYCurves_Get_Yscale() @Yscale.setter def Yscale(self, Value: float): - self._check_for_error(self._lib.XYCurves_Set_Yscale(Value)) + self._lib.XYCurves_Set_Yscale(Value) @property def Yshift(self) -> float: @@ -110,11 +108,11 @@ def Yshift(self) -> float: Original COM help: https://opendss.epri.com/Yshift.html ''' - return self._check_for_error(self._lib.XYCurves_Get_Yshift()) + return self._lib.XYCurves_Get_Yshift() @Yshift.setter def Yshift(self, Value: float): - self._check_for_error(self._lib.XYCurves_Set_Yshift(Value)) + self._lib.XYCurves_Set_Yshift(Value) @property def x(self) -> float: @@ -123,11 +121,11 @@ def x(self) -> float: Original COM help: https://opendss.epri.com/x4.html ''' - return self._check_for_error(self._lib.XYCurves_Get_x()) + return self._lib.XYCurves_Get_x() @x.setter def x(self, Value: float): - self._check_for_error(self._lib.XYCurves_Set_x(Value)) + self._lib.XYCurves_Set_x(Value) @property def y(self) -> float: @@ -136,8 +134,8 @@ def y(self) -> float: Original COM help: https://opendss.epri.com/y1.html ''' - return self._check_for_error(self._lib.XYCurves_Get_y()) + return self._lib.XYCurves_Get_y() @y.setter def y(self, Value: float): - self._check_for_error(self._lib.XYCurves_Set_y(Value)) + self._lib.XYCurves_Set_y(Value) diff --git a/dss/IYMatrix.py b/dss/IYMatrix.py index ca0fd23c..4babc70e 100644 --- a/dss/IYMatrix.py +++ b/dss/IYMatrix.py @@ -1,6 +1,6 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2016-2024 Paulo Meira -# Copyright (c) 2018-2024 DSS-Extensions contributors +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2016-2025 Paulo Meira +# Copyright (c) 2018-2025 DSS-Extensions contributors from ._cffi_api_util import Base import numpy as np from ._types import Int32Array, ComplexArray @@ -11,14 +11,14 @@ class IYMatrix(Base): YMatrix provides access to some lower-level solution aspects. Part of this class is ported from the original OpenDSSDirect.DLL back in 2017, but - part is new. Since this is not exposed in the official COM API, it is marked as an extension. + part is new. Since this is not exposed in EPRI's OpenDSS COM API, it is marked as an extension. (**API Extension**) ''' __slots__ = [] - def GetCompressedYMatrix(self, factor: bool = True) -> Tuple[ComplexArray, Int32Array, Int32Array]: + def GetCompressedYMatrix(self) -> Tuple[ComplexArray, Int32Array, Int32Array]: '''Return as (data, indices, indptr) that can fed into `scipy.sparse.csc_matrix`''' ffi = self._api_util.ffi @@ -32,7 +32,8 @@ def GetCompressedYMatrix(self, factor: bool = True) -> Tuple[ComplexArray, Int32 RowIdxPtr = ffi.new('int32_t**') cValsPtr = ffi.new('double**') - self._lib.YMatrix_GetCompressedYMatrix(factor, nBus, nNz, ColPtr, RowIdxPtr, cValsPtr) + lib = self._api_util.lib_unpatched # use the raw CFFI version + lib.YMatrix_GetCompressedYMatrix(self._api_util.ctx, True, nBus, nNz, ColPtr, RowIdxPtr, cValsPtr) if not nBus[0] or not nNz[0]: res = None @@ -44,39 +45,39 @@ def GetCompressedYMatrix(self, factor: bool = True) -> Tuple[ComplexArray, Int32 np.frombuffer(ffi.buffer(ColPtr[0], (nBus[0] + 1) * 4), dtype=np.int32).copy() ) - self._lib.DSS_Dispose_PInteger(ColPtr) - self._lib.DSS_Dispose_PInteger(RowIdxPtr) - self._lib.DSS_Dispose_PDouble(cValsPtr) + lib.DSS_Dispose_PInteger(ColPtr) + lib.DSS_Dispose_PInteger(RowIdxPtr) + lib.DSS_Dispose_PDouble(cValsPtr) - self._check_for_error() + self._api_util._check_for_error() return res def ZeroInjCurr(self): - self._check_for_error(self._lib.YMatrix_ZeroInjCurr()) + self._lib.YMatrix_ZeroInjCurr() def GetSourceInjCurrents(self): - self._check_for_error(self._lib.YMatrix_GetSourceInjCurrents()) + self._lib.YMatrix_GetSourceInjCurrents() def GetPCInjCurr(self): - self._check_for_error(self._lib.YMatrix_GetPCInjCurr()) + self._lib.YMatrix_GetPCInjCurr() def BuildYMatrixD(self, BuildOps: int, AllocateVI: bool): - self._check_for_error(self._lib.YMatrix_BuildYMatrixD(BuildOps, AllocateVI)) + self._lib.YMatrix_BuildYMatrixD(BuildOps, AllocateVI) def AddInAuxCurrents(self, SType): - self._check_for_error(self._lib.YMatrix_AddInAuxCurrents(SType)) + self._lib.YMatrix_AddInAuxCurrents(SType) def GetIPointer(self): '''Get access to the internal Current pointer''' IvectorPtr = self._api_util.ffi.new('double**') - self._check_for_error(self._lib.YMatrix_getIpointer(IvectorPtr)) + self._lib.YMatrix_getIpointer(IvectorPtr) return IvectorPtr[0] def GetVPointer(self): '''Get access to the internal Voltage pointer''' VvectorPtr = self._api_util.ffi.new('double**') - self._check_for_error(self._lib.YMatrix_getVpointer(VvectorPtr)) + self._lib.YMatrix_getVpointer(VvectorPtr) return VvectorPtr[0] def SolveSystem(self, NodeV=None) -> int: @@ -88,24 +89,23 @@ def SolveSystem(self, NodeV=None) -> int: else: NodeVPtr = self._api_util.ffi.cast("double *", NodeV.ctypes.data) - result = self._check_for_error(self._lib.YMatrix_SolveSystem(NodeVPtr)) - return result + return self._lib.YMatrix_SolveSystem(NodeVPtr) @property def SystemYChanged(self) -> bool: - return self._check_for_error(self._lib.YMatrix_Get_SystemYChanged() != 0) + return self._lib.YMatrix_Get_SystemYChanged() @SystemYChanged.setter def SystemYChanged(self, value: bool): - self._check_for_error(self._lib.YMatrix_Set_SystemYChanged(value)) + self._lib.YMatrix_Set_SystemYChanged(value) @property def UseAuxCurrents(self) -> bool: - return self._check_for_error(self._lib.YMatrix_Get_UseAuxCurrents() != 0) + return self._lib.YMatrix_Get_UseAuxCurrents() @UseAuxCurrents.setter def UseAuxCurrents(self, value: bool): - self._check_for_error(self._lib.YMatrix_Set_UseAuxCurrents(value)) + self._lib.YMatrix_Set_UseAuxCurrents(value) # for better compatibility with OpenDSSDirect.py getYSparse = GetCompressedYMatrix @@ -122,39 +122,39 @@ def SolverOptions(self, Value: int): def getI(self) -> List[float]: '''Get the data from the internal Current pointer''' IvectorPtr = self.GetIPointer() - return self._api_util.ffi.unpack(IvectorPtr, 2 * (self._check_for_error(self._lib.Circuit_Get_NumNodes() + 1))) + return self._api_util.ffi.unpack(IvectorPtr, 2 * (self._lib.Circuit_Get_NumNodes() + 1)) def getV(self) -> List[float]: '''Get the data from the internal Voltage pointer''' VvectorPtr = self.GetVPointer() - return self._api_util.ffi.unpack(VvectorPtr, 2 * (self._check_for_error(self._lib.Circuit_Get_NumNodes() + 1))) + return self._api_util.ffi.unpack(VvectorPtr, 2 * (self._lib.Circuit_Get_NumNodes() + 1)) def CheckConvergence(self) -> bool: - return self._check_for_error(self._lib.YMatrix_CheckConvergence() != 0) + return self._lib.YMatrix_CheckConvergence() def SetGeneratordQdV(self): - self._check_for_error(self._lib.YMatrix_SetGeneratordQdV()) + self._lib.YMatrix_SetGeneratordQdV() @property def LoadsNeedUpdating(self) -> bool: - return self._check_for_error(self._lib.YMatrix_Get_LoadsNeedUpdating() != 0) + return self._lib.YMatrix_Get_LoadsNeedUpdating() @LoadsNeedUpdating.setter def LoadsNeedUpdating(self, value: bool): - self._check_for_error(self._lib.YMatrix_Set_LoadsNeedUpdating(value)) + self._lib.YMatrix_Set_LoadsNeedUpdating(value) @property def SolutionInitialized(self) -> bool: - return self._check_for_error(self._lib.YMatrix_Get_SolutionInitialized() != 0) + return self._lib.YMatrix_Get_SolutionInitialized() @SolutionInitialized.setter def SolutionInitialized(self, value: bool): - self._check_for_error(self._lib.YMatrix_Set_SolutionInitialized(value)) + self._lib.YMatrix_Set_SolutionInitialized(value) @property def Iteration(self) -> int: - return self._check_for_error(self._lib.YMatrix_Get_Iteration()) + return self._lib.YMatrix_Get_Iteration() @Iteration.setter def Iteration(self, value: int): - self._check_for_error(self._lib.YMatrix_Set_Iteration(value)) + self._lib.YMatrix_Set_Iteration(value) diff --git a/dss/IZIP.py b/dss/IZIP.py index e61f0389..6ad8ba0e 100644 --- a/dss/IZIP.py +++ b/dss/IZIP.py @@ -1,5 +1,5 @@ -# A compatibility layer for DSS C-API that mimics the official OpenDSS COM interface. -# Copyright (c) 2021-2024 Paulo Meira +# A compatibility layer for DSS C-API that mimics EPRI's OpenDSS COM interface. +# Copyright (c) 2021-2025 Paulo Meira from ._cffi_api_util import Base from typing import AnyStr, Optional, List @@ -12,6 +12,8 @@ class IZIP(Base): The implementation provides a specialization which allows more efficient access if the ZIP file is open and reused for many circuits. Doing so reduces the overhead of the initial opening and indexing of the file contents. + *Not available when using EPRI's OpenDSS distribution.* + (**API Extension**) ''' @@ -28,10 +30,7 @@ def Open(self, FileName: AnyStr): **(API Extension)** ''' - if not isinstance(FileName, bytes): - FileName = FileName.encode(self._api_util.codec) - - self._check_for_error(self._lib.ZIP_Open(FileName)) + self._lib.ZIP_Open(FileName) def Close(self): ''' @@ -39,7 +38,7 @@ def Close(self): **(API Extension)** ''' - self._check_for_error(self._lib.ZIP_Close()) + self._lib.ZIP_Close() def Redirect(self, FileInZip: AnyStr): ''' @@ -50,10 +49,7 @@ def Redirect(self, FileInZip: AnyStr): **(API Extension)** ''' - if not isinstance(FileInZip, bytes): - FileInZip = FileInZip.encode(self._api_util.codec) - - self._check_for_error(self._lib.ZIP_Redirect(FileInZip)) + self._lib.ZIP_Redirect(FileInZip) def Extract(self, FileName: AnyStr) -> bytes: ''' @@ -66,11 +62,13 @@ def Extract(self, FileName: AnyStr) -> bytes: if not isinstance(FileName, bytes): FileName = FileName.encode(api_util.codec) - self._check_for_error(self._lib.ZIP_Extract_GR(FileName)) + api_util.lib_unpatched.ZIP_Extract_GR(FileName) + api_util._check_for_error() ptr, cnt = api_util.gr_int8_pointers return bytes(api_util.ffi.buffer(ptr[0], cnt[0])) - def List(self, regexp: Optional[AnyStr]=None) -> List[str]: + + def List(self, regexp: AnyStr='') -> List[str]: ''' List of strings consisting of all names match the regular expression provided in regexp. If no expression is provided, all names in the current open ZIP are returned. @@ -81,12 +79,9 @@ def List(self, regexp: Optional[AnyStr]=None) -> List[str]: **(API Extension)** ''' if regexp is None or not regexp: - regexp = self._api_util.ffi.NULL - else: - if not isinstance(regexp, bytes): - regexp = regexp.encode(self._api_util.codec) + regexp = b'' - return self._check_for_error(self._get_string_array(self._lib.ZIP_List, regexp)) + return self._lib.ZIP_List(regexp) def Contains(self, Name: AnyStr) -> bool: ''' @@ -94,10 +89,7 @@ def Contains(self, Name: AnyStr) -> bool: **(API Extension)** ''' - if not isinstance(Name, bytes): - Name = Name.encode(self._api_util.codec) - - return self._check_for_error(self._lib.ZIP_Contains(Name)) != 0 + return self._lib.ZIP_Contains(Name) def __getitem__(self, FileName) -> bytes: return self.Extract(FileName) diff --git a/dss/Oddie.py b/dss/Oddie.py index af6d2c93..34340798 100644 --- a/dss/Oddie.py +++ b/dss/Oddie.py @@ -1,6 +1,7 @@ from __future__ import annotations +import sys, platform, ctypes, os from typing import Optional -from ._cffi_api_util import CffiApiUtil +from ._cffi_api_util import AltDSSAPIUtil from .IDSS import IDSS from enum import Flag @@ -12,14 +13,14 @@ class IOddieDSS(IDSS): r''' The OddieDSS class exposes the official OpenDSSDirect.DLL binary, as distributed by EPRI, with the same API as the DSS-Python and - the official COM interface object on Windows. It uses AltDSS Oddie + EPRI's OpenDSS COM interface object on Windows. It uses AltDSS Oddie to achieve this. **Note:** This class requires the backend for Oddie to be included in the `dss_python_backend` package. If it is not available, an import error should occur when trying to use this. - AltDSS Oddie wraps OpenDSSDirect.DLL, providing a minimal compatiliby layer + AltDSS Oddie wraps OpenDSSDirect.DLL, providing a minimal compatibility layer to expose it with the same API as AltDSS/DSS C-API. With it, we can just reuse most of the tools from the other projects on DSS-Extensions without too much extra work. @@ -31,7 +32,7 @@ class IOddieDSS(IDSS): more information. :param library_path: The name or full path of the target dynamic library to - load. Defaults to trying to load "OpenDSSDirect" from `c:\Program Files\OpenDSS\x64`, + load. Defaults to trying to load "OpenDSSDirect" from `C:\Program Files\OpenDSS\x64`, followed by trying to load it from the current path. :param load_flags: Optional, flags to feed the [`LoadLibrary`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexa) @@ -41,10 +42,55 @@ class IOddieDSS(IDSS): the default settings (recommended) will be used. For advanced users. ''' + def _handle_load_lib_error(self): + ''' + This function is used to try to provide a more helpful message when the + library cannot be loaded. + ''' + try: + if sys.platform == 'win32': + error = ctypes.GetLastError() + if error: + raise ctypes.WinError(error) + + return + + if sys.platform == 'linux': + # ld = ctypes.cdll.LoadLibrary("ld-linux-x86-64.so.2") + # ld.dlerror.argtypes = [] + # ld.dlerror.restype = ctypes.c_char_p + # error = ld.dlerror() + # if error: + # raise RuntimeError(error.decode()) + return + except: + # We can ignore if something fails since the generic + # check will still work outside. + pass + + + def is_oddie(self) -> bool: + """ + Returns True if this instance is based on the Oddie compatibility layer for + EPRI's OpenDSS Direct API (a.k.a. DCSL). + + Note that the default instance in OpenDSSDirect.py is based on AltDSS since 2018. + """ + return True + def __init__(self, library_path: str = '', load_flags: Optional[int] = None, oddie_options: Optional[OddieOptions] = None): - from dss_python_backend import _altdss_oddie_capi - lib = _altdss_oddie_capi.lib - ffi = _altdss_oddie_capi.ffi + if sys.platform == 'cygwin': + raise NotImplementedError("Cygwin support is not implemented") + elif sys.platform == 'wasi': + raise NotImplementedError("WASI support is not implemented") + elif sys.platform == 'win32': + not64bits = (platform.architecture()[0] != '64bit') + if not64bits: + raise NotImplementedError("On Windows, only 64-bit (x64) environments are supported with Oddie. If you need support for further architectures, please open an issue at https://github.com/dss-extensions/") + + from dss_python_backend import oddie as oddie_capi + lib = oddie_capi.lib + ffi = oddie_capi.ffi NULL = ffi.NULL c_load_flags = NULL @@ -52,31 +98,46 @@ def __init__(self, library_path: str = '', load_flags: Optional[int] = None, odd c_load_flags = ffi.new('uint32_t*', load_flags) if library_path: - library_path = library_path.encode() - lib.Oddie_SetLibOptions(library_path, c_load_flags) - ctx = lib.ctx_New() - else: - # Try the default install folder - library_path = rb'C:\Program Files\OpenDSS\x64\OpenDSSDirect.dll' + if not isinstance(library_path, bytes): + library_path = str(library_path).encode() + lib.Oddie_SetLibOptions(library_path, c_load_flags) ctx = lib.ctx_New() if ctx == NULL: - # Try from the general path, let the system resolve it - library_path = rb'OpenDSSDirect.dll' + self._handle_load_lib_error() + + elif sys.platform == 'win32': + _win32_lib_paths = [ + rb'C:\Program Files\OpenDSS\x64\OpenDSSDirect.dll', # Try the default install folder + rb'OpenDSSDirect.dll', # Try from the general path, let the system resolve it + ] + + for library_path in _win32_lib_paths: lib.Oddie_SetLibOptions(library_path, c_load_flags) ctx = lib.ctx_New() + if ctx != NULL: + break + else: + self._handle_load_lib_error() + + elif sys.platform == 'linux': + library_path = b'libOpenDSSC.so' #TODO: add proper version extensions (e.g. libOpenDSSC.so.10) + lib.Oddie_SetLibOptions(library_path, c_load_flags) + ctx = lib.ctx_New() + if ctx == NULL: + self._handle_load_lib_error() if ctx == NULL: raise RuntimeError("Could not load the target library.") - if lib.ctx_DSS_Start(ctx, 0) != 1: + if lib.DSS_Start(ctx, 0) == 0: raise RuntimeError("DSS_Start call was not successful.") if oddie_options is not None: lib.Oddie_SetOptions(oddie_options) ctx = ffi.gc(ctx, lib.ctx_Dispose) - api_util = CffiApiUtil(ffi, lib, ctx, is_odd=True) + api_util = AltDSSAPIUtil(ffi, lib, ctx, is_oddie=True) api_util._library_path = library_path IDSS.__init__(self, api_util) diff --git a/dss/UserModels/wrappers.py b/dss/UserModels/wrappers.py index 7b06b3e6..a97ef6ad 100644 --- a/dss/UserModels/wrappers.py +++ b/dss/UserModels/wrappers.py @@ -1,7 +1,10 @@ import re from dss_python_backend import ( # _dss_CapUserControl, - _dss_GenUserModel, + _dss_GenUserModel_AltDSS, + _dss_GenUserModel_OpenDSS_v7, + _dss_GenUserModel_OpenDSS_v8v9, + _dss_GenUserModel_OpenDSS_v10, # _dss_PVSystemUserModel, # _dss_StoreDynaModel, # _dss_StoreUserModel @@ -17,7 +20,7 @@ def __init__(self): self.ffi = self.cffi_module.ffi self.lib = self.cffi_module.lib self.models = [] - self.model_classes = {} + # self.model_classes = {} -- this is now initialized in the subclasses prefix = self.function_prefix for fname in self.function_names: @@ -26,10 +29,10 @@ def __init__(self): if self.Base is not None: self.Base.ffi = self.ffi - - def register(self, cls): - self.model_classes[cls.__name__.lower()] = cls - return cls + @classmethod + def register(this_class, model_cls): + this_class.model_classes[model_cls.__name__.lower()] = model_cls + return model_cls def Delete(self, ID): ID = ID[0] @@ -167,6 +170,7 @@ def Restore(self): # class CapUserControlWrapper(CommonWrapper): # Base = bases.CapUserControlBase +# model_classes = {} # cffi_module = _dss_CapUserControl # function_prefix = 'pyCapUserControl' # function_names = ( @@ -190,7 +194,8 @@ def Restore(self): class GenUserModelWrapper(DynamicsWrapper, SaveRestoreMixin): Base = bases.GenUserModelBase - cffi_module = _dss_GenUserModel + model_classes = {} + function_prefix = 'pyGenUserModel' function_names = ( 'New', @@ -210,6 +215,10 @@ class GenUserModelWrapper(DynamicsWrapper, SaveRestoreMixin): 'Restore' ) + def __init__(self, cffi_module): + self.cffi_module = cffi_module + CommonWrapper.__init__(self) + def New(self, GenData, DynaData, CallBacks): # Create a base instance to be replaced in Edit self.active_instance = self.Base(GenData, DynaData, CallBacks) @@ -219,6 +228,7 @@ def New(self, GenData, DynaData, CallBacks): # class PVSystemUserModelWrapper(DynamicsWrapper, SaveRestoreMixin): # Base = bases.PVSystemUserModelBase +# model_classes = {} # cffi_module = _dss_PVSystemUserModel # function_prefix = 'pyPVSystemUserModel' # function_names = ( @@ -248,6 +258,7 @@ def New(self, GenData, DynaData, CallBacks): # class StoreUserModelWrapper(DynamicsWrapper, SaveRestoreMixin): # Base = bases.StoreUserModelBase +# model_classes = {} # cffi_module = _dss_StoreUserModel # function_prefix = 'pyStoreUserModel' # function_names = ( @@ -277,6 +288,7 @@ def New(self, GenData, DynaData, CallBacks): # class StoreDynaModelWrapper(DynamicsWrapper): # Base = bases.StoreDynaModelBase +# model_classes = {} # cffi_module = _dss_StoreDynaModel # function_prefix = 'pyStoreDynaModel' # function_names = ( @@ -304,7 +316,34 @@ def New(self, GenData, DynaData, CallBacks): # Instantiate the wrappers to link the DLLs to the the Python code # CapUserControl = CapUserControlWrapper() -GenUserModel = GenUserModelWrapper() +GenUserModels = { + 'AltDSS': GenUserModelWrapper(_dss_GenUserModel_AltDSS), + 'OpenDSS_v7': GenUserModelWrapper(_dss_GenUserModel_OpenDSS_v7), + 'OpenDSS_v8v9': GenUserModelWrapper(_dss_GenUserModel_OpenDSS_v8v9), + 'OpenDSS_v10': GenUserModelWrapper(_dss_GenUserModel_OpenDSS_v10), +} + +def GenUserModel(DSS): + ''' + Select the approapriate GenUserModel instance according to the DSS engine provided. + + If the version is not recognized, defaults to OpenDSS v10.0. + ''' + ver = DSS.Version + if 'DSS C-API Library' in ver: + return GenUserModels['AltDSS'] + elif ver.startswith('Version 9.') or ver.startswith('Version 8.'): + return GenUserModels['OpenDSS_v8v9'] + elif ver.startswith('Version 7.'): + return GenUserModels['OpenDSS_v7'] + else: + # Assuming compatibility with OpenDSS v10.0 + return GenUserModels['OpenDSS_v10'] + +# To keep backwards compatibility, add `Base` and register` to GenUserModel +GenUserModel.Base = GenUserModelWrapper.Base +GenUserModel.register = GenUserModelWrapper.register + # PVSystemUserModel = PVSystemUserModelWrapper() # StoreDynaModel = StoreDynaModelWrapper() # StoreUserModel = StoreUserModelWrapper() diff --git a/dss/__init__.py b/dss/__init__.py index 07934c52..e14f51d8 100644 --- a/dss/__init__.py +++ b/dss/__init__.py @@ -1,4 +1,4 @@ -'''``dss`` is the main package for DSS-Python. DSS-Python is a compatibility layer for the DSS C-API library that mimics the official OpenDSS COM interface, with many extensions and a few limitations. +'''``dss`` is the main package for DSS-Python. DSS-Python is a compatibility layer for the DSS C-API library that mimics EPRI's OpenDSS COM interface, with many extensions and a few limitations. This module used to provide instances for the OpenDSS Version 7 implementation. As of 2022, most of the parallel-machine functions of EPRI's OpenDSS have been reimplemented using a different approach. Therefore the PM functions are available in the instances of this module too. @@ -16,7 +16,7 @@ if os.path.exists(_properties_mo): lib.DSS_SetPropertiesMO(_properties_mo.encode()) -from ._cffi_api_util import CffiApiUtil, DSSException, set_case_insensitive_attributes +from ._cffi_api_util import AltDSSAPIUtil, DSSException, set_case_insensitive_attributes from .IDSS import IDSS from .Oddie import IOddieDSS, OddieOptions from .enums import * @@ -25,11 +25,11 @@ if not hasattr(lib, 'ctx_New'): # Module was built without the context API - api_util: CffiApiUtil = CffiApiUtil(ffi, lib) #: API utility functions and low-level access to the classic API + api_util: AltDSSAPIUtil = AltDSSAPIUtil(ffi, lib) #: API utility functions and low-level access to the classic API prime_api_util = None DSS_GR: IDSS = IDSS(api_util) #: GR (Global Result) interface else: - api_util = prime_api_util = CffiApiUtil(ffi, lib, lib.ctx_Get_Prime()) #: API utility functions and low-level access for DSSContext API + api_util = prime_api_util = AltDSSAPIUtil(ffi, lib, lib.ctx_Get_Prime()) #: API utility functions and low-level access for DSSContext API DSS_GR: IDSS = IDSS(prime_api_util) #: GR (Global Result) interface using the new DSSContext API DSS_IR: IDSS = DSS_GR #: IR was removed in DSS-Python v0.13.x, we'll keep mapping it to DSS_GR for this version diff --git a/dss/_cffi_api_util.py b/dss/_cffi_api_util.py index 2e906aa1..52125b7d 100644 --- a/dss/_cffi_api_util.py +++ b/dss/_cffi_api_util.py @@ -1,20 +1,45 @@ from __future__ import annotations -import warnings -from functools import partial +import os, warnings +from functools import partial, wraps from weakref import ref, WeakKeyDictionary import numpy as np -from ._types import Float64Array, Int32Array, Int8Array, ComplexArray, Float64ArrayOrComplexArray, Float64ArrayOrSimpleComplex -from typing import Any, AnyStr, Callable, List, Union, Iterator +from ._types import Float64Array, Int32Array, Int8Array, ComplexArray, ComplexArray, Complex +from typing import Any, AnyStr, Callable, List, Union, Iterator, Optional, TYPE_CHECKING from .enums import AltDSSEvent from dss_python_backend.events import get_manager_for_ctx - -# UTF8 under testing +from .error import DSSException + +if TYPE_CHECKING: + try: + from altdss import DSSObject, Bus as AltBus, AltDSS + except: + pass + +AltDSS_PyContext = None +try: + if os.environ.get('DSS_EXTENSIONS_FASTDSS', '') != '0': + # Try to import the fast backend + from dss_python_backend._fastdss import AltDSS_PyContext + else: + warnings.warn("DSS-Extensions: DSS_EXTENSIONS_FASTDSS environment variable is set to 0; using the legacy full CFFI backend.") +except: + warnings.warn("DSS-Extensions: Could not import the FastDSS backend; using the legacy full CFFI backend.") + + +if AltDSS_PyContext is None: + # Import the prepared function info if the fast implementation from + # AltDSS_PyContext is not available. + import dss_python_backend._func_info as _func_info + +# Assumed UTF8; unless the fast C extension is not used, this now has no effect, +# but was left to avoid breaking it for downstream users. codec = 'UTF8' interface_classes = set() warn_wrong_case = False + def set_case_insensitive_attributes(use: bool = True, warn: bool = False): ''' This function is provided to allow easier migration from `win32com.client`. @@ -36,8 +61,8 @@ def set_case_insensitive_attributes(use: bool = True, warn: bool = False): - AltDSS-Python (`altdss` package): done to allow users to employ the case-insensitive mechanism to address DSS properties in Python code. - Since there is a small performance overhead, users are recommended to use this - mechanism as a transition before adjusting the code. + Since there is a small performance overhead, users are recommended to enable this + mechanism during a transition period, before adjusting the code. ''' if use: global warn_wrong_case @@ -58,11 +83,6 @@ def _is_case_insensitive() -> bool: return (getattr(Base, '__getattr__', None) == Base._getattr or getattr(Base, '__getattr__', None) == Base._getattr_case_check) -class DSSException(Exception): - def __str__(self): - return f'(#{self.args[0]}) {self.args[1]}' - - # For backwards compatibility, will be removed for version 1.0 DssException = DSSException use_com_compat = set_case_insensitive_attributes @@ -70,58 +90,605 @@ def __str__(self): class CtxLib: ''' Exposes a CFFI Lib object pre-binding the DSSContext (`ctx`) object to the - `ctx_*` functions. + `ctx_*` functions, suppressing the `ctx_` prefix. This allows much simpler + backwards compatibility. ''' - def _get_string(self, b) -> str: - if b != self._ffi.NULL: - return self._ffi.string(b).decode() + _CtxSettings_UseExceptions = 1 << 0 + _CtxSettings_AdvancedTypes = 1 << 1 + _CtxSettings_ODDPyStrings = 1 << 2 # TODO: check if we still need this with the new defaults + _CtxSettings_UseLists = 1 << 3 + + def get_float64_array(self, func, *args) -> Float64Array: + ptr = self._ffi.new('double**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + res = np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=np.float64).copy() + self.DSS_Dispose_PDouble(ptr) + + if cnt[3] and (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + # If the last element is filled, we have a matrix. Otherwise, the + # matrix feature is disabled or the result is indeed a vector + return res.reshape((cnt[2], cnt[3]), order='F') + + return res + + def get_complex128_array(self, func, *args) -> ComplexArray: + if not (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + return self.get_float64_array(func, *args) + + # Currently we use the same as API as get_float64_array, may change later + ptr = self._ffi.new('double**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + res = np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy() + self.DSS_Dispose_PDouble(ptr) + + if cnt[3]: + # If the last element is filled, we have a matrix. Otherwise, the + # matrix feature is disabled or the result is indeed a vector + return res.reshape((cnt[2], cnt[3]), order='F') + + return res + + def get_fcomplex128_array(self, func, *args) -> Union[ComplexArray, None]: + # Currently we use the same as API as get_float64_array, may change later + ptr = self._ffi.new('double**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + if cnt[0] == 1: # empty + res = None + else: + res = np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy() + self.DSS_Dispose_PDouble(ptr) + + if cnt[3]: + # If the last element is filled, we have a matrix. Otherwise, the + # matrix feature is disabled or the result is indeed a vector + return res.reshape((cnt[2], cnt[3]), order='F') + + return res + + # def get_complex128_array2(self, func, *args) -> ComplexArray: + # if not (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + # return self.get_float64_array2(func, *args) + + # # Currently we use the same as API as get_float64_array, may change later + # ptr = self._ffi.new('double**') + # cnt = self._ffi.new('int32_t[4]') + # func(ptr, cnt, *args) + # ptr = self._ffi.cast('double _Complex **', ptr) + # res = self._unpack(ptr[0], cnt[0] >> 1) + # self.DSS_Dispose_PDouble(ptr) + # return res + + + def get_complex128_simple(self, func, *args) -> Complex: + if not (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + return self.get_float64_array(func, *args) + + # Currently we use the same as API as get_float64_array, may change later + ptr = self._ffi.new('double**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + try: + assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) + return self._ffi.cast('double _Complex**', ptr)[0][0] + finally: + self.DSS_Dispose_PDouble(ptr) + + def get_fcomplex128_simple(self, func, *args) -> Complex: + # Currently we use the same as API as get_float64_array, may change later + ptr = self._ffi.new('double**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + try: + assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) + return self._ffi.cast('double _Complex**', ptr)[0][0] + finally: + self.DSS_Dispose_PDouble(ptr) + + + # def get_complex128_simple2(self, func, *args) -> List[Union[complex, float]]: + # if not (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + # return self.get_float64_array2(func, *args) + + # # Currently we use the same as API as get_float64_array, may change later + # ptr = self._ffi.new('double**') + # cnt = self._ffi.new('int32_t[4]') + # func(ptr, cnt, *args) + # try: + # assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) + # return self._ffi.cast('double _Complex**', ptr)[0][0] + # finally: + # self.DSS_Dispose_PDouble(ptr) + + + def get_float64_gr_array(self) -> Float64Array: + ptr, cnt = self.gr_float64_pointers + settings = self.settings_ptr[0] + if (settings & (1 << 3)): # self.prefer_lists: + return self._unpack(ptr[0], cnt[0]) + if cnt[3] and (settings & (1 << 1)): # self.advanced_types: + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=np.float64).copy().reshape((cnt[2], cnt[3]), order='F') + + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=np.float64).copy() + + + def get_complex128_gr_array(self) -> ComplexArray: + settings = self.settings_ptr[0] + if not (settings & (1 << 1)): # self.advanced_types: + return self.get_float64_gr_array() + + # Currently we use the same as API as get_float64_array, may change later + ptr, cnt = self.gr_float64_pointers + if (settings & (1 << 3)): # self.prefer_lists: + ptr = self._ffi.cast('double _Complex **', ptr) + return self._unpack(ptr[0], cnt[0] >> 1) + + if cnt[3] and (settings & (1 << 1)): # self.advanced_types: + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy().reshape((cnt[2], cnt[3]), order='F') + + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy() + + + def get_fcomplex128_gr_array(self) -> ComplexArray: + # This one does not need to check "prefer_lists" + # Currently we use the same as API as get_float64_array, may change later + ptr, cnt = self.gr_float64_pointers + if cnt[3] and (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy().reshape((cnt[2], cnt[3]), order='F') + + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy() + + + def get_complex128_gr_simple(self) -> Complex: + if not (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + return self.get_float64_gr_array() + + # Currently we use the same as API as get_float64_array, may change later + ptr, cnt = self.gr_cfloat64_pointers + assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) + return ptr[0][0] + + + def get_fcomplex128_gr_simple(self) -> Complex: + # Currently we use the same as API as get_float64_array, may change later + ptr, cnt = self.gr_cfloat64_pointers + assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) + return ptr[0][0] + + + def get_int32_array(self, func: Callable, *args) -> Int32Array: + ptr = self._ffi.new('int32_t**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + res = np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 4), dtype=np.int32).copy() + self.DSS_Dispose_PInteger(ptr) + + if cnt[3] and (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + # If the last element is filled, we have a matrix. Otherwise, the + # matrix feature is disabled or the result is indeed a vector + return res.reshape((cnt[2], cnt[3])) + + return res + + + def get_int32_gr_array(self) -> Int32Array: + ptr, cnt = self.gr_int32_pointers + settings = self.settings_ptr[0] + if (settings & (1 << 3)): # self.prefer_lists: + return self._unpack(ptr[0], cnt[0]) + if cnt[3] and (settings & (1 << 1)): # self.advanced_types: + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 4), dtype=np.int32).copy().reshape((cnt[2], cnt[3])) + + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 4), dtype=np.int32).copy() + + + def get_int8_array(self, func: Callable, *args: Any) -> Int8Array: + ptr = self._ffi.new('int8_t**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + res = np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 1), dtype=np.int8).copy() + self.DSS_Dispose_PByte(ptr) + + if cnt[3] and (self.settings_ptr[0] & (1 << 1)): # self.advanced_types: + # If the last element is filled, we have a matrix. Otherwise, the + # matrix feature is disabled or the result is indeed a vector + return res.reshape((cnt[2], cnt[3])) + + return res + + + def get_int8_gr_array(self) -> Int8Array: + ptr, cnt = self.gr_int8_pointers + settings = self.settings_ptr[0] + if (settings & (1 << 3)): # self.prefer_lists: + return self._unpack(ptr[0], cnt[0]) + if cnt[3] and (settings & (1 << 1)): # self.advanced_types: + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 1), dtype=np.int8).copy().reshape((cnt[2], cnt[3]), order='F') + + return np.frombuffer(self._ffi.buffer(ptr[0], cnt[0] * 1), dtype=np.int8).copy() + + + def get_string_array(self, func: Callable, *args: Any) -> List[str]: + ptr = self._ffi.new('char***') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + if not cnt[0]: + res = [] + else: + actual_ptr = ptr[0] + if actual_ptr == self._ffi.NULL: + res = [] + else: + codec = self._api_util.codec + str_ptrs = self._unpack(actual_ptr, cnt[0]) + #res = [(str(self._ffi.string(str_ptr).decode(codec)) if (str_ptr != self._ffi.NULL) else None) for str_ptr in str_ptrs] + res = [(self._ffi.string(str_ptr).decode(codec) if (str_ptr != self._ffi.NULL) else u'') for str_ptr in str_ptrs] + + self.DSS_Dispose_PPAnsiChar(ptr, cnt[1]) + return res + + + # def get_string_array2(self, func, *args): # for compatibility with OpenDSSDirect.py + # ptr = self._ffi.new('char***') + # cnt = self._ffi.new('int32_t[4]') + # func(ptr, cnt, *args) + + # if not cnt[0]: + # res = [] + # else: + # actual_ptr = ptr[0] + # if actual_ptr == self._ffi.NULL: + # res = [] + # else: + # codec = self._api_util.codec + # res = [(str(self._ffi.string(actual_ptr[i]).decode(codec)) if (actual_ptr[i] != self._ffi.NULL) else '') for i in range(cnt[0])] + # if res == [u'']: + # # most COM methods return an empty array as an + # # array with an empty string + # res = [] + + # if len(res) == 1 and res[0].lower() == 'none': + # res = [] + + # self.DSS_Dispose_PPAnsiChar(ptr, cnt[1]) + # return res + + + def get_float64_array2(self, func, *args): + ptr = self._ffi.new('double**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + if not cnt[0]: + res = [] + else: + res = self._unpack(ptr[0], cnt[0]) + + self.DSS_Dispose_PDouble(ptr) + return res + + def get_int32_array2(self, func, *args): + ptr = self._ffi.new('int32_t**') + cnt = self._ffi.new('int32_t[4]') + func(ptr, cnt, *args) + if not cnt[0]: + res = None + else: + res = self._unpack(ptr[0], cnt[0]) + + self.DSS_Dispose_PInteger(ptr) + return res + + # def get_int8_array2(self, func, *args): + # ptr = self._ffi.new('int8_t**') + # cnt = self._ffi.new('int32_t[4]') + # func(ptr, cnt, *args) + # if not cnt[0]: + # res = None + # else: + # res = self._unpack(ptr[0], cnt[0]) + + # self.DSS_Dispose_PByte(ptr) + # return res + + def _get_strs_ctx(self, errorPtr, ctx, func: Callable, *args: Any) -> List[str]: + ffi = self._ffi + codec = self._api_util.codec + settings = self.settings_ptr[0] + ptr = ffi.new('char***') + cnt = ffi.new('int32_t[4]') + func(ctx, ptr, cnt, *args) + if errorPtr[0] and (settings & 1): # self.using_exceptions: + error_num = errorPtr[0] + errorPtr[0] = 0 + self.DSS_Dispose_PPAnsiChar(ptr, cnt[1]) + raise DSSException(error_num, self.Error_Get_Description()) + + if not cnt[0]: + res = [] + else: + actual_ptr = ptr[0] + if actual_ptr == ffi.NULL: + res = [] + else: + str_ptrs = ffi.unpack(actual_ptr, cnt[0]) + res = [(ffi.string(str_ptr).decode(codec) if (str_ptr) else '') for str_ptr in str_ptrs] + + if (settings & (1 << 2)): # self.oddpy_strs: + # originally on get_string_array2, for compatibility with OpenDSSDirect.py + if res == ['']: + # most COM methods return an empty array as an + # array with an empty string + res = [] + + if len(res) == 1 and res[0].lower() == 'none': + res = [] + + self.DSS_Dispose_PPAnsiChar(ptr, cnt[1]) + return res + + + def _get_bool_ctx(self, errorPtr, ctx, func: Callable, *args): + result = bool(func(ctx, *args)) + if errorPtr[0] and self.using_exceptions: + error_num = errorPtr[0] + errorPtr[0] = 0 + raise DSSException(error_num, self.Error_Get_Description()) + + return result != 0 + + + def _get_str_ctx(self, errorPtr, ctx, func: Callable, *args): + codec = self._api_util.codec + ffi = self._ffi + result = func(ctx, *args) + if errorPtr[0] and self.using_exceptions: + error_num = errorPtr[0] + errorPtr[0] = 0 + raise DSSException(error_num, self.Error_Get_Description()) + + if result: + return ffi.string(result).decode(codec) + return '' + + def _str_arg_wrapper(self, f: Callable) -> Callable: + @wraps(f) + def f_wrapper(s, *args): + if not isinstance(s, bytes): + s = s.encode(self._api_util.codec) + + return f(s, *args) + + return f_wrapper + + + def _prepare_api_functions_slow(self, done): + ''' + Wrap the C functions with a Python-level function to converter + strings and lists of strings from C. + (slow in CPython) + ''' + ctx = self._ctx + lib = self._lib + errorPtr = self._errorPtr + t = _func_info.t + api_util = self._api_util + is_oddie = api_util._is_oddie + wrappers = { + t.fastdss_types_u16: ('', self._get_bool_ctx,), + t.fastdss_types_str: ('', self._get_str_ctx,), + t.fastdss_types_strs: ('', self._get_strs_ctx,), + t.fastdss_types_gr_f64s: ('_GR', self._error_checked_ctx_gr, self.get_float64_gr_array), + t.fastdss_types_gr_i32s: ('_GR', self._error_checked_ctx_gr, self.get_int32_gr_array), + t.fastdss_types_gr_i8s: ('_GR', self._error_checked_ctx_gr, self.get_int8_gr_array), + t.fastdss_types_gr_z128: ('_GR', self._error_checked_ctx_gr, self.get_complex128_gr_simple), + t.fastdss_types_gr_z128s: ('_GR', self._error_checked_ctx_gr, self.get_complex128_gr_array), + } + + arg_no_wrapper = lambda f: f + default_wrapper = ('', self._error_checked_ctx, ) + + for res_type, arg_type, ctx_names in _func_info.funcs: + arg_wrapper = arg_no_wrapper + if arg_type == t.fastdss_types_str: + arg_wrapper = self._str_arg_wrapper + + suffix, wrapper, *wrapper_args = wrappers.get(res_type, default_wrapper) + for name in ctx_names: + if name in done: + continue + + name += suffix + if name in done: + continue + + try: + func = getattr(lib, name) + except AttributeError: + if is_oddie: + continue + + raise + + prepared_func = arg_wrapper(partial(wrapper, errorPtr, ctx, func, *wrapper_args)) + setattr(self, name, prepared_func) + + done.update(vars(self).keys()) + + + def _prepare_api_functions(self, done, settings_ptr): + self._settings_ptr = settings_ptr + if AltDSS_PyContext is None: + self._prepare_api_functions_slow(done) + return + + ctx = self._ctx + ffi = self._ffi + ctx_int = int(ffi.cast('uintptr_t', ctx)) + lib_int = int(ffi.cast('uintptr_t', self._api_util.lib_unpatched)) + settings_ptr_int = int(ffi.cast('uintptr_t', self._settings_ptr)) + self._fast = AltDSS_PyContext(ctx_int, lib_int, settings_ptr_int, DSSException, done, self) + def _error_checked(self, _errorPtr, f, *args): result = f(*args) - if _errorPtr[0] and Base._use_exceptions: + if _errorPtr[0] and self.using_exceptions: + error_num = _errorPtr[0] + _errorPtr[0] = 0 + raise DSSException(error_num, self.Error_Get_Description()) + + return result + + def _error_checked_ctx(self, _errorPtr, ctx, f, *args): + result = f(ctx, *args) + if _errorPtr[0] and self.using_exceptions: error_num = _errorPtr[0] _errorPtr[0] = 0 - raise DSSException(error_num, self._get_string(self.Error_Get_Description())) + raise DSSException(error_num, self.Error_Get_Description()) return result - def __init__(self, ctx, ffi, lib): - self._ctx = ctx - self._ffi = ffi - self._errorPtr = _errorPtr = lib.ctx_Error_Get_NumberPtr(ctx) + def _error_checked_ctx_gr(self, _errorPtr, ctx, f, _res_func, *args): + f(ctx, *args) + if _errorPtr[0] and self.using_exceptions: + error_num = _errorPtr[0] + _errorPtr[0] = 0 + raise DSSException(error_num, self.Error_Get_Description()) + + return _res_func() + + + @property + def using_exceptions(self) -> bool: + return (self.settings_ptr[0] & CtxLib._CtxSettings_UseExceptions) != 0 + + @using_exceptions.setter + def using_exceptions(self, do_enable: bool): + if do_enable: + self.settings_ptr[0] = self.settings_ptr[0] | CtxLib._CtxSettings_UseExceptions + else: + self.settings_ptr[0] = self.settings_ptr[0] & ~CtxLib._CtxSettings_UseExceptions + - done = set() + @property + def prefer_lists(self) -> bool: + return (self.settings_ptr[0] & CtxLib._CtxSettings_UseLists) != 0 + + @prefer_lists.setter + def prefer_lists(self, value: bool): + settings_ptr = self.settings_ptr + if value: + settings_ptr[0] = settings_ptr[0] | CtxLib._CtxSettings_UseLists + else: + settings_ptr[0] = settings_ptr[0] & ~CtxLib._CtxSettings_UseLists + + + @property + def oddpy_strs(self) -> bool: + return (self.settings_ptr[0] & CtxLib._CtxSettings_ODDPyStrings) != 0 + + @oddpy_strs.setter + def oddpy_strs(self, value: bool): + settings_ptr = self.settings_ptr + if value: + settings_ptr[0] = settings_ptr[0] | CtxLib._CtxSettings_ODDPyStrings + else: + settings_ptr[0] = settings_ptr[0] & ~CtxLib._CtxSettings_ODDPyStrings + + + + @property + def advanced_types(self) -> bool: + return (self.settings_ptr[0] & CtxLib._CtxSettings_AdvancedTypes) != 0 + + @advanced_types.setter + def advanced_types(self, value: bool): + settings_ptr = self.settings_ptr + if value: + settings_ptr[0] = settings_ptr[0] | CtxLib._CtxSettings_AdvancedTypes + else: + settings_ptr[0] = settings_ptr[0] & ~CtxLib._CtxSettings_AdvancedTypes + + def __init__(self, api_util, settings_ptr): + self._api_util = api_util # this is not ready, don't use it yet + lib = self._lib = api_util.lib_unpatched + ctx = self._ctx = api_util.ctx + ffi = self._ffi = api_util.ffi + self._unpack = ffi.unpack + self.settings_ptr = settings_ptr + self.gr_float64_pointers = api_util.gr_float64_pointers + self.gr_int32_pointers = api_util.gr_int32_pointers + self.gr_int8_pointers = api_util.gr_int8_pointers + self.gr_cfloat64_pointers = api_util.gr_cfloat64_pointers + + self._errorPtr = _errorPtr = lib.Error_Get_NumberPtr(ctx) + #TODO: test if a pointer is better than keeping this + self._prepared_funcs = [] + + # Wrap most of the API to provide simpler Python access + done = set(('Error_Get_Description', 'Error_Get_Number',)) + + self._prepare_api_functions(done, settings_ptr) + self.Error_Get_Description = lambda: self._api_util.get_string(lib.Error_Get_Description(ctx)) + self.Error_Get_Number = lambda: lib.Error_Get_Number(ctx) + + skip_funcs = { + 'ctx_New', 'ctx_Dispose', 'ctx_Get_Prime', 'ctx_Set_Prime', 'Error_Set_Description', 'Error_Get_NumberPtr', 'ctx_ZIP_Extract_GR', + 'DSS_BeginPascalThread', 'DSS_WaitPascalThread', 'DSS_SetPropertiesMO', 'DSS_SetMessagesMO', + 'engineName', 'isAltDSS', 'libHandle', 'versionSignature', + } - # First, process all `ctx_*`` functions - for name, value in vars(lib).items(): - is_ctx = name.startswith('ctx_') - if not is_ctx and not name.startswith(('Batch_Create', 'Batch_Filter', )): + skip_prefixes = ( + 'Oddie_', 'DSS_Dispose_', 'CmathLib_', 'DSSimComs_', 'Alt_', 'Obj_', 'Batch_', + ) + + force_include_prefixes = ( + 'Batch_Create', 'Batch_Filter', + ) + + # First, process all `ctx_*` functions + + for name in dir(lib): + if name in done: + continue + + if name.startswith(skip_prefixes) and not name.startswith(force_include_prefixes): continue + # Note: NULL function pointers here are fine since CFFI v1.13 (released in 2019). + value = getattr(lib, name) + # Keep the basic management functions alone - if name in {'ctx_New', 'ctx_Dispose', 'ctx_Get_Prime', 'ctx_Set_Prime', 'ctx_Error_Set_Description'}: - if name == 'ctx_Error_Set_Description': - name = name[4:] + if name in skip_funcs: + if name.startswith('DSSEvents_') or name == 'Error_Set_Description': setattr(self, name, partial(value, ctx)) else: setattr(self, name, value) - elif is_ctx: - name = name[4:] - setattr(self, name, partial(value, ctx)) - # setattr(self, name, partial(self._error_checked, _errorPtr, partial(value, ctx))) - else: - setattr(self, name, partial(value, ctx)) - # setattr(self, name, partial(self._error_checked, _errorPtr, partial(value, ctx))) + done.add(name) + continue + + if name.endswith('_GR'): + # A few GR functions that don't have dedicated low-level mapping + wrapper_func, res_func = self._error_checked_ctx_gr, self.get_float64_gr_array + setattr(self, name, partial(wrapper_func, _errorPtr, ctx, value, res_func)) + done.add(name) + continue + + # General functions and array setters are only error checked, no special handling yet + setattr(self, name, partial(self._error_checked, _errorPtr, partial(value, ctx))) done.add(name) # Then the new Alt_* family - for name, value in vars(lib).items(): - if (not name.startswith('Alt_')) or name in done: + for name in dir(lib): + if (not name.startswith('Alt_')) or name in done: #TODO: What about Obj_ and Batch_? continue + value = getattr(lib, name) + if name.startswith('Alt_Bus'): setattr(self, name, partial(self._error_checked, _errorPtr, partial(value, ctx))) else: @@ -130,11 +697,11 @@ def __init__(self, ctx, ffi, lib): done.add(name) # Finally the remaining fields - for name, value in vars(lib).items(): + for name in dir(lib): if name.startswith('ctx_') or name in done: continue - setattr(self, name, value) + setattr(self, name, getattr(lib, name)) # if isinstance(value, int): # setattr(self, name, value) # else: @@ -145,72 +712,23 @@ class Base: __slots__ = [ '_lib', '_api_util', - '_get_string', - '_get_float64_array', - '_get_float64_gr_array', - '_get_int32_array', - '_get_int32_gr_array', - '_get_int8_array', - '_get_int8_gr_array', - '_get_string_array', '_set_string_array', '_prepare_float64_array', '_prepare_int32_array', '_prepare_string_array', - '_get_complex128_array', - '_get_complex128_simple', - '_get_fcomplex128_simple', - '_get_complex128_gr_array', - '_get_complex128_gr_simple', - '_get_fcomplex128_gr_array', - '_get_fcomplex128_array', - '_get_fcomplex128_gr_simple', '_prepare_complex128_array', '_prepare_complex128_simple', '_errorPtr', '_frozen_attrs', ] - _use_exceptions = True + using_exceptions = True + _oddpy = False - def __init__(self, api_util, prefer_lists=False): + def __init__(self, api_util): object.__setattr__(self, '_frozen_attrs', False) - self._lib = api_util.lib self._api_util = api_util - self._get_string = api_util.get_string - - self._get_fcomplex128_gr_array = api_util.get_fcomplex128_gr_array - self._get_fcomplex128_array = api_util.get_fcomplex128_array - self._get_fcomplex128_simple = api_util.get_fcomplex128_simple - self._get_fcomplex128_gr_simple = api_util.get_fcomplex128_gr_simple - if not prefer_lists: - # Use NumPy arrays for most functions - self._get_float64_array = api_util.get_float64_array - self._get_float64_gr_array = api_util.get_float64_gr_array - self._get_int32_array = api_util.get_int32_array - self._get_int32_gr_array = api_util.get_int32_gr_array - self._get_int8_array = api_util.get_int8_array - self._get_int8_gr_array = api_util.get_int8_gr_array - self._get_string_array = api_util.get_string_array - - self._get_complex128_array = api_util.get_complex128_array - self._get_complex128_simple = api_util.get_complex128_simple - self._get_complex128_gr_array = api_util.get_complex128_gr_array - self._get_complex128_gr_simple = api_util.get_complex128_gr_simple - else: - # Classic OpenDSSDirect.py style, using mostly lists - self._get_float64_array = api_util.get_float64_array2 - self._get_float64_gr_array = api_util.get_float64_gr_array2 - self._get_int32_array = api_util.get_int32_array2 - self._get_int32_gr_array = api_util.get_int32_gr_array2 - self._get_int8_array = api_util.get_int8_array2 - self._get_int8_gr_array = api_util.get_int8_gr_array2 - self._get_string_array = api_util.get_string_array2 - - self._get_complex128_array = api_util.get_complex128_array2 - self._get_complex128_simple = api_util.get_complex128_simple2 - self._get_complex128_gr_array = api_util.get_complex128_gr_array2 - self._get_complex128_gr_simple = api_util.get_complex128_gr_simple2 + self._lib = api_util._get_lib(self._oddpy) self._prepare_complex128_array = api_util.prepare_complex128_array self._prepare_complex128_simple = api_util.prepare_complex128_simple @@ -218,8 +736,7 @@ def __init__(self, api_util, prefer_lists=False): self._prepare_float64_array = api_util.prepare_float64_array self._prepare_int32_array = api_util.prepare_int32_array self._prepare_string_array = api_util.prepare_string_array - self._errorPtr = self._lib.Error_Get_NumberPtr() - + self._errorPtr = self._api_util._errorPtr cls = type(self) if cls not in interface_classes: @@ -229,26 +746,6 @@ def __init__(self, api_util, prefer_lists=False): cls._dss_attributes = lowercase_map - @staticmethod - def _enable_exceptions(do_enable: bool): - """ - Controls whether the automatic error checking mechanism is enable, i.e., if - the DSS engine errors (from the `Error` interface) are mapped exception when - detected. - - **When disabled, the user takes responsibility for checking for errors.** - This can be done through the `Error` interface. When `Error.Number` is not - zero, there should be an error message in `Error.Description`. This is compatible - with the behavior on the official OpenDSS (Windows-only COM implementation) when - `AllowForms` is disabled. - - Users can also use the DSS command `Export ErrorLog` to inspect for errors. - - **WARNING:** This is a global setting, affects all DSS instances from DSS-Python - and OpenDSSDirect.py. - """ - Base._use_exceptions = bool(do_enable) - def _check_for_error(self, result=None): """ Checks for a DSS engine error (on the default configuration). @@ -258,13 +755,13 @@ def _check_for_error(self, result=None): If the user disabled exceptions, any error is simply ignored. Note that, in this case, manually calling this function would have no purpose/effects. - Note that, **in the future**, we may try showing a popup form like the official OpenDSS does on Windows + Note that, **in the future**, we may try showing a popup form like EPRI's OpenDSS does on Windows if AllowForms is True. This behavior is not very portable though and not adequate for automated scripts. """ - if self._errorPtr[0] and Base._use_exceptions: + if self._errorPtr[0] and Base.using_exceptions: error_num = self._errorPtr[0] self._errorPtr[0] = 0 - raise DSSException(error_num, self._get_string(self._lib.Error_Get_Description())) + raise DSSException(error_num, self._lib.Error_Get_Description()) return result @@ -317,7 +814,7 @@ def _decode_and_free_string(self, s) -> str: def altdss_python_util_callback(ctx, event_code, step, ptr): # print(ctx_util.ctx, AltDSSEvent(event_code), step, ptr) - ctx_util = CffiApiUtil._ctx_to_util[ctx] + ctx_util = AltDSSAPIUtil._ctx_to_util[ctx] if event_code == AltDSSEvent.ReprocessBuses: ctx_util.reprocess_buses_callback(step) @@ -328,14 +825,19 @@ def altdss_python_util_callback(ctx, event_code, step, ptr): return -class CffiApiUtil(object): +class AltDSSAPIUtil: ''' An internal class with various API and DSSContext management functions and structures. ''' _ctx_to_util = WeakKeyDictionary() - def __init__(self, ffi, lib, ctx=None, is_odd=False): - self._is_odd = is_odd + _altdss: AltDSS + + def __init__(self, ffi, lib, ctx=None, is_oddie=False, parent: Optional[AltDSSAPIUtil] = None): + self._opendssdirect = None + self._dss_python = None + self._altdss = None + self._is_oddie = is_oddie self.owns_ctx = True self.codec = codec self.ctx = ctx @@ -346,19 +848,68 @@ def __init__(self, ffi, lib, ctx=None, is_odd=False): self._obj_refs = [] self._bus_ref_to_name = None self._is_clearing = False + self._map_objs = True + self._parent = parent if ctx is None: self.lib = lib ctx = lib.ctx_Get_Prime() self.ctx = ctx + + self.init_buffers() + self.settings_ptr = settings_ptr_dsspy = ffi.new('int32_t*') + + # If a parent is provided, copy the settings + if self._parent is None: + settings_ptr_dsspy[0] = CtxLib._CtxSettings_UseExceptions else: - self.lib = CtxLib(ctx, ffi, lib) + settings_ptr_dsspy[0] = self._parent.settings_ptr[0] - CffiApiUtil._ctx_to_util[ctx] = self + self.lib = CtxLib(self, settings_ptr_dsspy) + self.lib_odd = None + if ctx not in AltDSSAPIUtil._ctx_to_util: + AltDSSAPIUtil._ctx_to_util[ctx] = self - self._allow_complex = False self.track_objects = True - self.init_buffers() self.register_callbacks() + if self._parent is None : + self.lib_odd = None + elif self._parent.lib_odd is not None: + self.lib_odd = self._get_lib(True) + + + def _get_lib(self, oddpy: bool): + ''' + Returns a context lib, optionally prepared for OpenDSSDirect.py + + This should be removed as we unify settings across the modules later. + ''' + if not oddpy: # and (AltDSS_PyContext is None): + if self.lib is None: + if self.settings_ptr is None: + self.settings_ptr = self.ffi.new('int32_t*') + if self._parent is not None: + self.settings_ptr[0] = self._parent.settings_ptr[0] + else: + self.settings_ptr[0] = self.settings_ptr[0] & ~CtxLib._CtxSettings_ODDPyStrings + + self.lib = CtxLib(self, self.settings_ptr) + + return self.lib + + # Check it the current lib is OK + if self.lib_odd is not None: + return self.lib_odd + + # Return a new lib object with the correct settings + + self.settings_oddpy_ptr = self.ffi.new('int32_t*') + if self._parent is not None: + self.settings_oddpy_ptr[0] = self._parent.settings_oddpy_ptr[0] + else: + self.settings_oddpy_ptr[0] = self.settings_ptr[0] | CtxLib._CtxSettings_ODDPyStrings + + self.lib_odd = CtxLib(self, self.settings_oddpy_ptr) + return self.lib_odd def _check_for_error(self, result=None): @@ -370,13 +921,13 @@ def _check_for_error(self, result=None): If the user disabled exceptions, any error is simply ignored. Note that, in this case, manually calling this function would have no purpose/effects. - Note that, **in the future**, we may try showing a popup form like the official OpenDSS does on Windows + Note that, **in the future**, we may try showing a popup form like EPRI's OpenDSS does on Windows if AllowForms is True. This behavior is not very portable though and not adequate for automated scripts. """ - if self._errorPtr[0] and Base._use_exceptions: + if self._errorPtr[0] and Base.using_exceptions: error_num = self._errorPtr[0] self._errorPtr[0] = 0 - raise DSSException(error_num, self.get_string(self.lib.Error_Get_Description())) + raise DSSException(error_num, self.lib.Error_Get_Description()) return result @@ -405,7 +956,7 @@ def reprocess_buses_callback(self, step: int): # Now try to remap the objects; on exception, just invalidate everything try: ptrs = self.lib.Alt_Bus_GetListPtr() - names = self._check_for_error(self.get_string_array(self.lib.Circuit_Get_AllBusNames)) + names = self.lib.Circuit_Get_AllBusNames() except: for bus_ref in self._bus_refs: bus_ref()._invalidate_ptr() @@ -467,7 +1018,7 @@ def clear_callback(self, step: int): def register_callbacks(self): - if self._is_odd: + if self._is_oddie: return mgr = get_manager_for_ctx(self.ctx) @@ -476,15 +1027,15 @@ def register_callbacks(self): mgr.register_func(AltDSSEvent.ReprocessBuses, altdss_python_util_callback) def unregister_callbacks(self): - if self._is_odd: + if self._is_oddie: return mgr = get_manager_for_ctx(self.ctx) mgr.unregister_func(AltDSSEvent.Clear, altdss_python_util_callback) mgr.unregister_func(AltDSSEvent.ReprocessBuses, altdss_python_util_callback) - # The context will die, no need to do anything else currently. def __del__(self): - if self._is_odd: + # The base context itself will die, no need to do anything else currently. Callbacks for AltDSS need to be cleared. + if self._is_oddie: return self.clear_callback(0) @@ -512,7 +1063,7 @@ def track_obj(self, obj): self._obj_refs.append(ref(obj)) def init_buffers(self): - tmp_string_pointers = (self.ffi.new('char****'), self.ffi.new('int32_t**')) + lib = self.lib_unpatched tmp_float64_pointers = (self.ffi.new('double***'), self.ffi.new('int32_t**')) tmp_int32_pointers = (self.ffi.new('int32_t***'), self.ffi.new('int32_t**')) tmp_int8_pointers = (self.ffi.new('int8_t***'), self.ffi.new('int32_t**')) @@ -520,13 +1071,12 @@ def init_buffers(self): # reorder pointers so data pointers are first, count pointers last ptr_args = [ ptr - for ptrs in zip(tmp_string_pointers, tmp_float64_pointers, tmp_int32_pointers, tmp_int8_pointers) + for ptrs in zip(tmp_float64_pointers, tmp_int32_pointers, tmp_int8_pointers) for ptr in ptrs ] - self.lib.DSS_GetGRPointers(*ptr_args) + lib.DSS_GetGRPointers(self.ctx, *ptr_args) # we don't need to keep the extra indirections - self.gr_string_pointers = (tmp_string_pointers[0][0], tmp_string_pointers[1][0]) self.gr_float64_pointers = (tmp_float64_pointers[0][0], tmp_float64_pointers[1][0]) self.gr_int32_pointers = (tmp_int32_pointers[0][0], tmp_int32_pointers[1][0]) self.gr_int8_pointers = (tmp_int8_pointers[0][0], tmp_int8_pointers[1][0]) @@ -534,7 +1084,7 @@ def init_buffers(self): # also keep a casted version for complex floats self.gr_cfloat64_pointers = (self.ffi.cast('double _Complex**', tmp_float64_pointers[0][0]), tmp_float64_pointers[1][0]) - self._errorPtr = self.lib.Error_Get_NumberPtr() + self._errorPtr = lib.Error_Get_NumberPtr(self.ctx) def clear_buffers(self): @@ -543,195 +1093,9 @@ def clear_buffers(self): self.init_buffers() def get_string(self, b) -> str: - if b != self.ffi.NULL: + if b: return self.ffi.string(b).decode(self.codec) - return u'' - - def get_float64_array(self, func, *args) -> Float64Array: - ptr = self.ffi.new('double**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - res = np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=np.float64).copy() - self.lib.DSS_Dispose_PDouble(ptr) - - if self._allow_complex and cnt[3]: - # If the last element is filled, we have a matrix. Otherwise, the - # matrix feature is disabled or the result is indeed a vector - return res.reshape((cnt[2], cnt[3]), order='F') - - return res - - def get_complex128_array(self, func, *args) -> Float64ArrayOrComplexArray: - if not self._allow_complex: - return self.get_float64_array(func, *args) - - # Currently we use the same as API as get_float64_array, may change later - ptr = self.ffi.new('double**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - res = np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy() - self.lib.DSS_Dispose_PDouble(ptr) - - if cnt[3]: - # If the last element is filled, we have a matrix. Otherwise, the - # matrix feature is disabled or the result is indeed a vector - return res.reshape((cnt[2], cnt[3]), order='F') - - return res - - def get_fcomplex128_array(self, func, *args) -> Union[ComplexArray, None]: - # Currently we use the same as API as get_float64_array, may change later - ptr = self.ffi.new('double**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - if cnt[0] == 1: # empty - res = None - else: - res = np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy() - self.lib.DSS_Dispose_PDouble(ptr) - - if cnt[3]: - # If the last element is filled, we have a matrix. Otherwise, the - # matrix feature is disabled or the result is indeed a vector - return res.reshape((cnt[2], cnt[3]), order='F') - - return res - - def get_complex128_array2(self, func, *args) -> Float64ArrayOrComplexArray: - if not self._allow_complex: - return self.get_float64_array2(func, *args) - - # Currently we use the same as API as get_float64_array, may change later - ptr = self.ffi.new('double**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - ptr = self.ffi.cast('double _Complex **', ptr) - res = self.ffi.unpack(ptr[0], cnt[0] >> 1) - self.lib.DSS_Dispose_PDouble(ptr) - return res - - - def get_complex128_simple(self, func, *args) -> Float64ArrayOrSimpleComplex: - if not self._allow_complex: - return self.get_float64_array(func, *args) - - # Currently we use the same as API as get_float64_array, may change later - ptr = self.ffi.new('double**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - try: - assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) - return self.ffi.cast('double _Complex**', ptr)[0][0] - finally: - self.lib.DSS_Dispose_PDouble(ptr) - - def get_fcomplex128_simple(self, func, *args) -> Float64ArrayOrSimpleComplex: - # Currently we use the same as API as get_float64_array, may change later - ptr = self.ffi.new('double**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - try: - assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) - return self.ffi.cast('double _Complex**', ptr)[0][0] - finally: - self.lib.DSS_Dispose_PDouble(ptr) - - - def get_complex128_simple2(self, func, *args) -> List[Union[complex, float]]: - if not self._allow_complex: - return self.get_float64_array2(func, *args) - - # Currently we use the same as API as get_float64_array, may change later - ptr = self.ffi.new('double**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - try: - assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) - return self.ffi.cast('double _Complex**', ptr)[0][0] - finally: - self.lib.DSS_Dispose_PDouble(ptr) - - - def get_float64_gr_array(self) -> Float64Array: - ptr, cnt = self.gr_float64_pointers - if self._allow_complex and cnt[3]: - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=np.float64).copy().reshape((cnt[2], cnt[3]), order='F') - - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=np.float64).copy() - - - def get_complex128_gr_array(self) -> ComplexArray: - if not self._allow_complex: - return self.get_float64_gr_array() - - # Currently we use the same as API as get_float64_array, may change later - ptr, cnt = self.gr_float64_pointers - if self._allow_complex and cnt[3]: - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy().reshape((cnt[2], cnt[3]), order='F') - - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy() - - - def get_fcomplex128_gr_array(self) -> ComplexArray: - # Currently we use the same as API as get_float64_array, may change later - ptr, cnt = self.gr_float64_pointers - if self._allow_complex and cnt[3]: - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy().reshape((cnt[2], cnt[3]), order='F') - - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 8), dtype=complex).copy() - - - def get_complex128_gr_array2(self) -> List[Union[complex, float]]: - if not self._allow_complex: - return self.get_float64_gr_array2() - - # Currently we use the same as API as get_float64_array, may change later - ptr, cnt = self.gr_float64_pointers - ptr = self.ffi.cast('double _Complex **', ptr) - return self.ffi.unpack(ptr[0], cnt[0] >> 1) - - - def get_complex128_gr_simple(self) -> Float64ArrayOrSimpleComplex: - if not self._allow_complex: - return self.get_float64_gr_array() - - # Currently we use the same as API as get_float64_array, may change later - ptr, cnt = self.gr_cfloat64_pointers - assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) - return ptr[0][0] - - - def get_fcomplex128_gr_simple(self) -> complex: - # Currently we use the same as API as get_float64_array, may change later - ptr, cnt = self.gr_cfloat64_pointers - assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) - return ptr[0][0] - - - def get_complex128_gr_simple2(self) -> List[Union[complex, float]]: - if not self._allow_complex: - return self.get_float64_gr_array2() - - # Currently we use the same as API as get_float64_array, may change later - ptr, cnt = self.gr_cfloat64_pointers - assert cnt[0] == 2, ('Unexpected number of elements returned by API', cnt[0]) - return ptr[0][0] - - - def get_int32_array(self, func: Callable, *args) -> Int32Array: - ptr = self.ffi.new('int32_t**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - res = np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 4), dtype=np.int32).copy() - self.lib.DSS_Dispose_PInteger(ptr) - - if self._allow_complex and cnt[3]: - # If the last element is filled, we have a matrix. Otherwise, the - # matrix feature is disabled or the result is indeed a vector - return res.reshape((cnt[2], cnt[3])) - - return res - + return '' def get_ptr_array(self, func: Callable, *args): ptr = self.ffi.new('void***') @@ -741,137 +1105,10 @@ def get_ptr_array(self, func: Callable, *args): self.lib.DSS_Dispose_PPointer(ptr) return res - - def get_int32_gr_array(self) -> Int32Array: - ptr, cnt = self.gr_int32_pointers - if self._allow_complex and cnt[3]: - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 4), dtype=np.int32).copy().reshape((cnt[2], cnt[3])) - - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 4), dtype=np.int32).copy() - - - def get_int8_array(self, func: Callable, *args: Any) -> Int8Array: - ptr = self.ffi.new('int8_t**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - res = np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 1), dtype=np.int8).copy() - self.lib.DSS_Dispose_PByte(ptr) - - if self._allow_complex and cnt[3]: - # If the last element is filled, we have a matrix. Otherwise, the - # matrix feature is disabled or the result is indeed a vector - return res.reshape((cnt[2], cnt[3])) - - return res - - - def get_int8_gr_array(self) -> Int8Array: - ptr, cnt = self.gr_int8_pointers - if self._allow_complex and cnt[3]: - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 1), dtype=np.int8).copy().reshape((cnt[2], cnt[3]), order='F') - - return np.frombuffer(self.ffi.buffer(ptr[0], cnt[0] * 1), dtype=np.int8).copy() - - - def get_string_array(self, func: Callable, *args: Any) -> List[str]: - ptr = self.ffi.new('char***') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - if not cnt[0]: - res = [] - else: - actual_ptr = ptr[0] - if actual_ptr == self.ffi.NULL: - res = [] - else: - codec = self.codec - str_ptrs = self.ffi.unpack(actual_ptr, cnt[0]) - #res = [(str(self.ffi.string(str_ptr).decode(codec)) if (str_ptr != self.ffi.NULL) else None) for str_ptr in str_ptrs] - res = [(self.ffi.string(str_ptr).decode(codec) if (str_ptr != self.ffi.NULL) else u'') for str_ptr in str_ptrs] - - self.lib.DSS_Dispose_PPAnsiChar(ptr, cnt[1]) - return res - - - def get_string_array2(self, func, *args): # for compatibility with OpenDSSDirect.py - ptr = self.ffi.new('char***') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - - if not cnt[0]: - res = [] - else: - actual_ptr = ptr[0] - if actual_ptr == self.ffi.NULL: - res = [] - else: - codec = self.codec - res = [(str(self.ffi.string(actual_ptr[i]).decode(codec)) if (actual_ptr[i] != self.ffi.NULL) else '') for i in range(cnt[0])] - if res == [u'']: - # most COM methods return an empty array as an - # array with an empty string - res = [] - - if len(res) == 1 and res[0].lower() == 'none': - res = [] - - self.lib.DSS_Dispose_PPAnsiChar(ptr, cnt[1]) - return res - - def set_string_array(self, func: Callable, value: List[AnyStr], *args): value, value_ptr, value_count = self.prepare_string_array(value) func(value_ptr, value_count, *args) - - def get_float64_array2(self, func, *args): - ptr = self.ffi.new('double**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - if not cnt[0]: - res = [] - else: - res = self.ffi.unpack(ptr[0], cnt[0]) - - self.lib.DSS_Dispose_PDouble(ptr) - return res - - def get_float64_gr_array2(self): - ptr, cnt = self.gr_float64_pointers - return self.ffi.unpack(ptr[0], cnt[0]) - - def get_int32_array2(self, func, *args): - ptr = self.ffi.new('int32_t**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - if not cnt[0]: - res = None - else: - res = self.ffi.unpack(ptr[0], cnt[0]) - - self.lib.DSS_Dispose_PInteger(ptr) - return res - - def get_int32_gr_array2(self): - ptr, cnt = self.gr_int32_pointers - return self.ffi.unpack(ptr[0], cnt[0]) - - def get_int8_array2(self, func, *args): - ptr = self.ffi.new('int8_t**') - cnt = self.ffi.new('int32_t[4]') - func(ptr, cnt, *args) - if not cnt[0]: - res = None - else: - res = self.ffi.unpack(ptr[0], cnt[0]) - - self.lib.DSS_Dispose_PByte(ptr) - return res - - def get_int8_gr_array2(self): - ptr, cnt = self.gr_int8_pointers - return self.ffi.unpack(ptr[0], cnt[0]) - def prepare_float64_array(self, value): if type(value) is not np.ndarray or value.dtype != np.float64: value = np.asarray(value, dtype=np.float64) @@ -893,7 +1130,7 @@ def prepare_complex128_array(self, value): return value, ptr, cnt - def prepare_complex128_simple(self, value: complex): + def prepare_complex128_simple(self, value: Complex): if isinstance(value, (np.complex128, complex)): value = np.asarray([value], dtype=np.complex128).view(dtype=np.float64) elif (isinstance(value, np.array) and value.dtype in (np.complex128, np.complex64)): @@ -940,6 +1177,43 @@ def prepare_string_array(self, value: List[AnyStr]): return value_enc or value, ptrs, len(ptrs) + def get_dss_obj(self, ptr) -> Optional[DSSObject]: + ''' + Get an AltDSS DSSObj instance. For internal use, but might be useful for advanced users. + The user must ensure the pointer is valid. + ''' + if not ptr: + return None + + from AltDSS import DSSObj + if self._altdss is not None: + altdss = self._altdss + else: + from AltDSS import AltDSS + altdss = AltDSS._get_instance(ctx=self.ctx, api_util=self) + + cls_idx = self._lib.Obj_GetClassIdx(ptr) + pycls = DSSObj._idx_to_cls[cls_idx] + return pycls(self, ptr) + + def get_bus_obj(self, ptr) -> Optional[AltBus]: + ''' + Get an AltDSS Bus instance. For internal use, but might be useful for advanced users. + The user must ensure the pointer is valid. + ''' + from AltDSS import Bus as AltBus + if self._altdss is not None: + altdss = self._altdss + else: + from AltDSS import AltDSS + altdss = AltDSS._get_instance(ctx=self.ctx, api_util=self) + + return AltBus(self, ptr) + + +def _oddie_not_impl(): + raise NotImplementedError("This API requires a function that is not implemented in EPRI's OpenDSS engine.") + class Iterable(Base): __slots__ = [ '_Get_First', @@ -949,39 +1223,41 @@ class Iterable(Base): '_Get_Name', '_Set_Name', '_Get_idx', - '_Set_idx' + '_Set_idx', + '_Get_Pointer', ] def __init__(self, api_util): Base.__init__(self, api_util) prefix = type(self).__name__[1:] - self._Get_First = getattr(self._lib, '{}_Get_First'.format(prefix)) - self._Get_Next = getattr(self._lib, '{}_Get_Next'.format(prefix)) - self._Get_Count = getattr(self._lib, '{}_Get_Count'.format(prefix)) - self._Get_AllNames = getattr(self._lib, '{}_Get_AllNames'.format(prefix)) - self._Get_Name = getattr(self._lib, '{}_Get_Name'.format(prefix)) - self._Set_Name = getattr(self._lib, '{}_Set_Name'.format(prefix)) - self._Get_idx = getattr(self._lib, '{}_Get_idx'.format(prefix)) - self._Set_idx = getattr(self._lib, '{}_Set_idx'.format(prefix)) + self._Get_First = getattr(self._lib, '{}_Get_First'.format(prefix), _oddie_not_impl) + self._Get_Next = getattr(self._lib, '{}_Get_Next'.format(prefix), _oddie_not_impl) + self._Get_Count = getattr(self._lib, '{}_Get_Count'.format(prefix), _oddie_not_impl) + self._Get_AllNames = getattr(self._lib, '{}_Get_AllNames'.format(prefix), _oddie_not_impl) + self._Get_Name = getattr(self._lib, '{}_Get_Name'.format(prefix), _oddie_not_impl) + self._Set_Name = getattr(self._lib, '{}_Set_Name'.format(prefix), _oddie_not_impl) + self._Get_idx = getattr(self._lib, '{}_Get_idx'.format(prefix), _oddie_not_impl) + self._Set_idx = getattr(self._lib, '{}_Set_idx'.format(prefix), _oddie_not_impl) + self._Get_Pointer = getattr(self._lib, '{}_Get_Pointer'.format(prefix), _oddie_not_impl) @property def First(self) -> int: '''Sets the first object of this type active. Returns 0 if none.''' - return self._check_for_error(self._Get_First()) + return self._Get_First() @property def Next(self) -> int: '''Sets next object of this type active. Returns 0 if no more.''' - return self._check_for_error(self._Get_Next()) + return self._Get_Next() @property def Count(self) -> int: '''Number of objects of this type''' - return self._check_for_error(self._Get_Count()) + return self._Get_Count() def __len__(self) -> int: - return self._check_for_error(self._Get_Count()) + return self._Get_Count() def __iter__(self) -> Iterator[Iterable]: ''' @@ -996,27 +1272,24 @@ def __iter__(self) -> Iterator[Iterable]: **(API Extension)** ''' - idx = self._check_for_error(self._Get_First()) + idx = self._Get_First() while idx != 0: yield self - idx = self._check_for_error(self._Get_Next()) + idx = self._Get_Next() @property def AllNames(self) -> List[str]: '''Array of all names of this object type''' - return self._check_for_error(self._get_string_array(self._Get_AllNames)) + return self._Get_AllNames() @property def Name(self) -> str: '''Gets the current name or sets the active object of this type by name''' - return self._get_string(self._check_for_error(self._Get_Name())) + return self._Get_Name() @Name.setter def Name(self, Value: AnyStr): - if not isinstance(Value, bytes): - Value = Value.encode(self._api_util.codec) - - self._check_for_error(self._check_for_error(self._Set_Name(Value))) + self._Set_Name(Value) @property def idx(self) -> int: @@ -1043,9 +1316,25 @@ def idx(self) -> int: **(API Extension)** ''' - return self._check_for_error(self._Get_idx()) + return self._Get_idx() @idx.setter def idx(self, Value: int): - self._check_for_error(self._Set_idx(Value)) + self._Set_idx(Value) + + def to_altdss(self) -> DSSObject: + ''' + Returns a Python object for the current active DSS object in this interface. + + Requires AltDSS-Python. + + *Available only for the AltDSS engine.* + + **(API Extension)** + ''' + ptr = self._Get_Pointer() + return self._api_util.get_dss_obj(ptr) + +# For backwards compat +CffiApiUtil = AltDSSAPIUtil \ No newline at end of file diff --git a/dss/_types.py b/dss/_types.py index c3994c11..613aa80b 100644 --- a/dss/_types.py +++ b/dss/_types.py @@ -1,7 +1,14 @@ import numpy as np -from typing import Union try: import numpy.typing as npt + # TODO: update after these are closed: + # - https://github.com/numpy/numpy/issues/16544 + # - https://github.com/python/typing/issues/516 + + ComplexMatrix = npt.NDArray[np.complex128] + Float64Matrix = npt.NDArray[np.float64] + Float32Matrix = npt.NDArray[np.float32] + Int32Matrix = npt.NDArray[np.int32] ComplexArray = npt.NDArray[np.complex128] Float64Array = npt.NDArray[np.float64] Float32Array = npt.NDArray[np.float32] @@ -10,6 +17,10 @@ BoolArray = npt.NDArray[np.bool_] except (ModuleNotFoundError, ImportError, AttributeError): from typing import List + ComplexMatrix = List[complex] + Float64Matrix = List[np.float64] + Float32Matrix = List[np.float32] + Int32Matrix = List[np.int32] ComplexArray = List[complex] Float64Array = List[np.float64] Float32Array = List[np.float32] @@ -17,5 +28,4 @@ Int8Array = List[np.int8] BoolArray = List[bool] -Float64ArrayOrComplexArray = Union[Float64Array, ComplexArray] -Float64ArrayOrSimpleComplex = Union[Float64Array, complex] +Complex = complex diff --git a/dss/error.py b/dss/error.py new file mode 100644 index 00000000..a213a1d0 --- /dev/null +++ b/dss/error.py @@ -0,0 +1,3 @@ +class DSSException(Exception): + def __str__(self): + return f'(#{self.args[0]}) {self.args[1]}' diff --git a/dss/notebook.py b/dss/notebook.py new file mode 100644 index 00000000..3d569f36 --- /dev/null +++ b/dss/notebook.py @@ -0,0 +1,162 @@ +import os +from enum import IntEnum +from .IDSS import IDSS +from . import api_util +from . import plot + +class DSSMessageType(IntEnum): + Error = -1 + General = 0 + Info = 1 + Help = 2 + Progress = 3 + ProgressCaption = 4 + ProgressFormCaption = 5 + ProgressPercent = 6 + FireOffEditor = 7 + ProgressSummary = 8 + ReportOutput = 9 + ShowOutput = 10 + ShowTreeView = 11 + + + +def set_nb_target(dss: IDSS): + if not hasattr(dss, '_api_util'): + raise ValueError("A DSS engine context compatible with DSS-Python was expected.") + + plot.dss_nb_ctx = dss + if hasattr(dss, '_api_util') and not dss._api_util._is_oddie: + #TODO: save original state? + dss.AllowChangeDir = False + +try: + from IPython import get_ipython + from IPython.display import FileLink, display, display_html, HTML + from IPython.core.magic import register_cell_magic + ipython = get_ipython() + if ipython is None: + raise ImportError + + import html + + def link_file(fn): + relfn = os.path.relpath(fn, os.getcwd()) + if relfn.startswith('..'): + # cannot show in the notebook :( + display(HTML(f'

File output ("{html.escape(relfn)}") outside current workspace.

')) + else: + display(FileLink(relfn, result_html_prefix=f'File output ("{html.escape(fn)}"): ')) + + def show(text): + display(text) + + + @register_cell_magic + def dss(line, cell): + dss_ctx = plot.dss_nb_ctx + if dss_ctx is None: + raise ValueError("Invalid DSS-Python instance registered.") + return + + plotter = plot.get_plotter(dss_ctx) + if isinstance(dss_ctx, IDSS) and not dss_ctx._api_util._is_oddie: + dss_ctx.Text.Commands(cell) + else: + for line in cell.split('\n'): + dss_ctx(line) + res = dss_ctx.Text.Result + if res.lower().endswith('.dsv'): + plotter.dsv(res) + + set_nb_target(plot.dss_nb_ctx) + +except: + def link_file(fn): + print(f'Output file: "{fn}"') + + def show(text): + print(text) + + + #FileLink('path_to_file/filename.extension') + +# import os +# import html +# import tqdm +# from tqdm.notebook import tqdm +# import IPython.display + + +# dss_progress_bar = None +# dss_progress_desc = '' + + +@api_util.ffi.def_extern() +def dss_python_cb_write(ctx, message_str, message_type: int, message_size: int, message_subtype: int): + global dss_progress_bar + global dss_progress_desc + + # DSS = _ctx2dss(ctx) + + message_str = api_util.ffi.string(message_str).decode(api_util.codec) + if message_type == DSSMessageType.Error: + #print('DSS Error:', message_str, file=sys.stderr) + pass + elif message_type in (DSSMessageType.ProgressCaption, DSSMessageType.ProgressFormCaption): + #dss_progress_desc = message_str + # print('Progress Caption:', message_str, file=sys.stderr) + pass + elif message_type == DSSMessageType.Progress: + #print('DSS Progress:', message_str, file=sys.stderr) + pass + elif message_type == DSSMessageType.FireOffEditor: + link_file(message_str) + # try: + # # print('DSSMessageType_FireOffEditor') + # with open(message_str, 'r') as f: + # text = f.read() + + # IPython.display.display({'text/plain': text}, raw=True) + # except: + # print(f'Could not display file "{message_str}"') + # return 1 + + elif message_type == DSSMessageType.ProgressPercent: + try: + pass + # n = int(message_str) + # desc = '' + # if n == 0 and dss_progress_bar is not None: + # dss_progress_bar = None + + # if dss_progress_bar is None: + # dss_progress_bar = tqdm(total=100, desc=dss_progress_desc) + + # if n < 0: + # del dss_progress_bar + # dss_progress_bar = None + # return 0 + + + # dss_progress_bar.n = n + # dss_progress_bar.refresh() +# if n == 100: +# dss_progress_bar.close() + except: + import traceback + traceback.print_exc() + print('DSS Progress:', message_str) + + # else: + # # print(message_type) + # # print(message_str) + # IPython.display.display({'text/plain': message_str}, raw=True) + else: + # do nothing for now... + pass + + return 0 + + +__all__ = ['set_nb_target', ] \ No newline at end of file diff --git a/dss/patch_dss_com.py b/dss/patch_dss_com.py index 87ee2ba1..2f00c9c1 100644 --- a/dss/patch_dss_com.py +++ b/dss/patch_dss_com.py @@ -22,6 +22,7 @@ from .IMonitors import IMonitors from .IPDElements import IPDElements from .IPVSystems import IPVSystems +from .IReactors import IReactors from .IRelays import IRelays from .IReclosers import IReclosers from .ISensors import ISensors @@ -31,8 +32,28 @@ from .ITransformers import ITransformers from .IXYCurves import IXYCurves from .IGICSources import IGICSources -# from .IStorages import IStorages +from .IStorages import IStorages +from .IWindGens import IWindGens +class IBusesWrapper: + def __init__(self, DSS, Buses): + self.DSS = DSS + self.Buses = Buses + + def __call__(self, *args, **kwargs): + return self.Buses(*args, **kwargs) + + def __call__(self, *args, **kwargs): + return self.Buses(*args, **kwargs) + + def __iter__(self): + circ = self.DSS.ActiveCircuit + for i in range(circ.NumBuses): + circ.SetActiveBusi(i) + yield circ.ActiveBus + + def __len__(self): + return self.DSS.ActiveCircuit.NumBuses def custom_iter(self): idx = self.First @@ -77,7 +98,7 @@ def Load_Set_Phases(self, value): def custom_bus_iter(self): for i in range(obj.ActiveCircuit.NumBuses): obj.ActiveCircuit.SetActiveBusi(i) - yield self + yield obj.ActiveCircuit.ActiveBus def custom_bus_len(self): return obj.ActiveCircuit.NumBuses @@ -92,22 +113,56 @@ def custom_dss_call(self, cmds): self.Text.Command = cmds + def Lines_Get_IsSwitch(self): + lines = obj.ActiveCircuit.Lines + elem = obj.ActiveCircuit.ActiveCktElement + name = lines.Name + if not name: + return False + + obj.ActiveCircuit.SetActiveElement(f'Line.{name}') + return elem.Properties['Switch'].Val.lower()[:1] in ('y', 't') + + def Lines_Set_IsSwitch(self, Value): + lines = obj.ActiveCircuit.Lines + elem = obj.ActiveCircuit.ActiveCktElement + name = lines.Name + if not name: + return + + obj.ActiveCircuit.SetActiveElement(f'Line.{name}') + elem.Properties['Switch'].Val = 'y' if Value else 'n' + + def _get_BusNames(self, removeNodes=False): + return [x.split('.', 1)[0] for x in self.BusNames] + # Callable DSS type(obj).__call__ = custom_dss_call # Monitors AsMatrix type(obj.ActiveCircuit.Monitors).AsMatrix = Monitors_AsMatrix - + + # Extended getter for CktElement.BusNames + type(obj.ActiveCircuit.ActiveCktElement)._get_BusNames = _get_BusNames + # Load Phases type(obj.ActiveCircuit.Loads).Phases = property(Load_Phases, Load_Set_Phases) - + + # Line IsSwitch + type(obj.ActiveCircuit.Lines).IsSwitch = property(Lines_Get_IsSwitch, Lines_Set_IsSwitch) + # Bus iterator and len type(obj.ActiveCircuit.ActiveBus).__iter__ = custom_bus_iter type(obj.ActiveCircuit.ActiveBus).__len__ = custom_bus_len type(obj.ActiveCircuit.ActiveBus)._columns = IBus._columns - type(obj.ActiveCircuit.Buses).__iter__ = custom_bus_iter - type(obj.ActiveCircuit.Buses).__len__ = custom_bus_len - type(obj.ActiveCircuit.Buses)._columns = IBus._columns + Buses = obj.ActiveCircuit.Buses + try: + type(Buses).__iter__ = custom_bus_iter + type(Buses).__len__ = custom_bus_len + type(Buses)._columns = IBus._columns + except: + type(obj.ActiveCircuit).Buses = IBusesWrapper(obj, Buses) + def add_dunders(cls): cls.__iter__ = custom_iter @@ -128,16 +183,19 @@ def add_dunders(cls): 'Monitors': IMonitors, 'PDElements': IPDElements, 'PVSystems': IPVSystems, + 'Reactors': IReactors, 'Relays': IRelays, 'Reclosers': IReclosers, 'Sensors': ISensors, 'RegControls': IRegControls, + 'Storages': IStorages, 'SwtControls': ISwtControls, 'Vsources': IVsources, 'Transformers': ITransformers, 'XYCurves': IXYCurves, 'GICSources': IGICSources, - # 'Storages': IStorages, + 'Storages': IStorages, + 'WindGens': IWindGens, } def filter_cols(py_cls): @@ -170,7 +228,11 @@ def filter_cols(py_cls): type(obj.ActiveCircuit.Topology)._columns = filter_cols(ITopology) for name, py_cls in com_classes_to_dsspy.items(): - cls = type(getattr(obj.ActiveCircuit, name)) + instance = getattr(obj.ActiveCircuit, name, None) + if instance is None: + continue + + cls = type(instance) add_dunders(cls) cls._py_cls = py_cls # Filter columns, removing @@ -179,8 +241,6 @@ def filter_cols(py_cls): if getattr(py_cls, '_is_circuit_element', False): cls._is_circuit_element = True - add_dunders(cls) - return obj __all__ = ['patch_dss_com'] diff --git a/dss/plot.py b/dss/plot.py index baa4d8b7..79ff1fe3 100644 --- a/dss/plot.py +++ b/dss/plot.py @@ -4,82 +4,237 @@ This is not a complete implementation and there are known limitations, but should suffice for many use-cases. We'd like to add another backend later. + +For DSS-Python v0.16, this module was refactored and improved to also allow using EPRI's +OpenDSS distributions (original Delphi OpenDSS, and OpenDSS-C), although more limited +since they do not provide the same API as the AltDSS engine. """ -import warnings -from typing import List -import os +from __future__ import annotations +import os, re, json, sys, warnings +from weakref import WeakKeyDictionary +from typing import List, TYPE_CHECKING, Optional, Tuple, Dict, Union, Iterable +from enum import Enum, IntEnum +from pathlib import Path as FilePath +from typing_extensions import TypedDict, Unpack + +import numpy as np +from numpy import asarray +from numpy.testing import suppress_warnings + +from dss_python_backend import loader_lib from . import api_util -from . import DSS as DSSPrime -from ._cffi_api_util import CffiApiUtil +from . import DSS as dss_nb_ctx +from ._cffi_api_util import AltDSSAPIUtil, Iterable as DSSIterable from .IDSS import IDSS from .IBus import IBus try: - import numpy as np from matplotlib import pyplot as plt from matplotlib.path import Path from matplotlib.collections import LineCollection from mpl_toolkits.mplot3d.art3d import Line3DCollection - from matplotlib.patches import Rectangle + import matplotlib.patches as patches import matplotlib.colors import scipy.sparse.coo as coo except: raise ImportError("SciPy and matplotlib are required to use this module.") -import re, json, sys, warnings - -try: - from IPython import get_ipython - from IPython.display import FileLink, display, display_html, HTML - from IPython.core.magic import register_cell_magic - ipython = get_ipython() - if ipython is None: - raise ImportError - - import html - - def link_file(fn): - relfn = os.path.relpath(fn, os.getcwd()) - if relfn.startswith('..'): - # cannot show in the notebook :( - display(HTML(f'

File output ("{html.escape(relfn)}") outside current workspace.

')) - else: - display(FileLink(relfn, result_html_prefix=f'File output ("{html.escape(fn)}"): ')) - - def show(text): - display(text) - - - @register_cell_magic - def dss(line, cell): - DSSPrime.Text.Commands(cell) +from .notebook import * + +if TYPE_CHECKING: + from altdss.AltDSS import IAltDSS + +class DSSPlotType(Enum): + AutoAddLog = 'AutoAddLog' + Circuit = 'Circuit' + Daisy = 'Daisy' + Energy = 'Energy' + Evolution = 'Evolution' + GeneralData = 'GeneralData' + LoadShape = 'LoadShape' + Matrix = 'Matrix' + MeterZones = 'MeterZones' + Monitor = 'Monitor' + PhaseVoltage = 'PhaseVoltage' + PriceShape = 'PriceShape' + Profile = 'Profile' + Scatter = 'Scatter' + TShape = 'TShape' - DSSPrime.AllowChangeDir = False -except: - def link_file(fn): - print(f'Output file: "{fn}"') - def show(text): - print(text) +(pqVoltage, pqCurrent, pqPower, pqLosses, pqCapacity, pqNone) = range(6) +class DSSPlotQuantity(Enum): + Capacities = 'Capacities' + Currents = 'Currents' + Losses = 'Losses' + Powers = 'Powers' + Voltages = 'Voltages' + none = 'None' - #FileLink('path_to_file/filename.extension') -# import os -# import html -# import tqdm -# from tqdm.notebook import tqdm -# import IPython.display +class ProfileScale(Enum): + pukm = 'pukm' + kft120 = '120kft' -include_3d = '2d' # '2d' (default), '3d' (prefer 3d), 'both' -PROFILE3PH = -1 # Default -PROFILEALL = -2 # All -PROFILEALLPRI = -3 # Primary -PROFILELL3PH = -4 # LL3Ph -PROFILELLALL = -5 # LLAll -PROFILELLPRI = -6 # LLPrimary +class ObjMarkers(TypedDict): + NodeMarkerCode: Optional[int] + NodeMarkerWidth: Optional[float] -(pqVoltage, pqCurrent, pqPower, pqLosses, pqCapacity, pqNone) = range(6) + MarkTransformers: Optional[bool] + TransMarkerCode: Optional[int] + TransMarkerSize: Optional[float] + + MarkCapacitors: Optional[bool] + CapMarkerCode: Optional[int] + CapMarkerSize: Optional[float] + + MarkPVSystems: Optional[bool] + PVMarkerCode: Optional[int] + PVMarkerSize: Optional[float] + + MarkFuses: Optional[bool] + FuseMarkerCode: Optional[int] + FuseMarkerSize: Optional[float] + + MarkReclosers: Optional[bool] + RecloserMarkerCode: Optional[int] + RecloserMarkerSize: Optional[float] + + MarkRegulators: Optional[bool] + RegMarkerCode: Optional[int] + RegMarkerSize: Optional[float] + + MarkRelays: Optional[bool] + RelayMarkerCode: Optional[int] + RelayMarkerSize: Optional[float] + + MarkStorage: Optional[bool] + StoreMarkerCode: Optional[int] + StoreMarkerSize: Optional[float] + + MarkSwitches: Optional[bool] + SwitchMarkerCode: Optional[int] + + +class BusMarker(TypedDict): + Name: str + Color: str + Code: int + Size: float + + +class DSSPlotPhases(IntEnum): + PROFILE3PH = -1 # Default + PROFILEALL = -2 # All + PROFILEALLPRI = -3 # Primary + PROFILELL3PH = -4 # LL3Ph + PROFILELLALL = -5 # LLAll + PROFILELLPRI = -6 # LLPrimary + + +class PlotParams(TypedDict): + PlotType: DSSPlotType + MatrixType: str + MaxScale: float + MinScale: float + Dots: bool + Labels: bool + ShowLoops: bool + ShowSubs: bool + Quantity: str + ObjectName: str + PlotId: str #TODO + ValueIndex: int + PhasesToPlot: DSSPlotPhases + ProfileScale: str + Channels: List[int] + Bases: Optional[List[float]] + SinglePhLineStyle: int + ThreePhLineStyle: int + Color1: str + Color2: str + Color3: str + TriColorMax: float + TriColorMid: float + MaxScaleIsSpecified: bool + MinScaleIsSpecified: bool + DaisyBusList: List[str] + DaisySize: float + MaxLineThickness: float + MarkerParams: Optional[ObjMarkers] + BusMarkers: Optional[List[BusMarker]] + + Registers: List[int] + PeakDay: bool + MeterName: str + CaseName: str + CaseYear: int + + +DEFAULT_MARKER_PARAMS = ObjMarkers( + MarkTransformers=False, + TransMarkerCode=None, + TransMarkerSize=None, + + MarkCapacitors=False, + CapMarkerCode=None, + CapMarkerSize=None, + + MarkPVSystems=False, + PVMarkerCode=None, + PVMarkerSize=None, + + MarkStorage=False, + StoreMarkerCode=None, + StoreMarkerSize=None, + + MarkSwitches=False, + SwitchMarkerCode=None, + + MarkFuses=False, + FuseMarkerCode=None, + FuseMarkerSize=None, + + MarkRegulators=False, + RegMarkerCode=None, + RegMarkerSize=None, + + MarkRelays=False, + RelayMarkerCode=None, + RelayMarkerSize=None, + + MarkReclosers=False, + RecloserMarkerCode=None, + RecloserMarkerSize=None, +) + +DEFAULT_PLOT_PARAMS = PlotParams( + PlotType=DSSPlotType.Circuit, + Quantity=DSSPlotQuantity.Powers, + Channels=[1, 3, 5], + MarkerParams=DEFAULT_MARKER_PARAMS, + BusMarkers=[], + Color1='#0000FF', + Color2='#008000', + Color3='#FF0000', + TriColorMax=0.85, + TriColorMid=0.50, + ThreePhLineStyle=1, + SinglePhLineStyle=1, + ProfileScale=ProfileScale.pukm, + PhasesToPlot=DSSPlotPhases.PROFILE3PH, + DaisyBusList=[], + MaxLineThickness=10, + Dots=False, + Labels=False, + ShowLoops=False, + ShowSubs=False, + MinScaleIsSpecified=False, + MaxScaleIsSpecified=False, + MinScale=0.0, + MaxScale=None, +) str_to_pq = { 'Voltages': pqVoltage, @@ -186,6 +341,44 @@ def show(text): MARKER_SEQ = (5, 15, 2, 8, 26, 36, 39, 19, 18) + +DSV_LINE_STYLES = { + 0: 'solid', + 1: 'dashed', + 2: 'dotted', + 3: 'dashdot', + 4: (0, (3, 5, 1, 5, 1, 5)), +} + +DSS_ITEMS = { + 'BoldLabel', + 'Caption', + 'Center', + 'ChartCaption', + 'Circle', + 'ClickOn', + 'Curve', + 'DataColor', + 'Draw', + 'FStyle', + 'KeepAspect', + 'KeyClass', + 'Label', + 'Line', + 'Marker', + 'Move', + 'NoScales', + 'PctRim', + 'Range', + 'Rect', + 'SetProp', + 'Text', + 'TxtAlign', + 'Width', + 'Xlabel', + 'Ylabel', +} + def get_marker_dict(dss_code): marker, size, fill = MARKER_MAP[dss_code] res = dict( @@ -206,1660 +399,2244 @@ def get_marker_dict(dss_code): def nodot(b): return b.split('.', 1)[0] -class ToggleAdvancedTypes: - def __init__(self, dss: IDSS, value: bool): - self._value = value - self._dss = dss - self._previous = self._dss.AdvancedTypes +def unquote(field: str): + field = field.strip() + if field[0] == '"' and field[-1] == '"': + return field[1:-1] - def __enter__(self): - if self._value != self._previous: - self._dss.AdvancedTypes = self._value - - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - if self._value != self._previous: - self._dss.AdvancedTypes = self._previous - + return field -def dss_monitor_plot(DSS: IDSS, params): - monitor = DSS.ActiveCircuit.Monitors - monitor.Name = params['ObjectName'] - data = monitor.AsMatrix() - if data is None or len(data) == 0: - raise ValueError("There is not data to plot in the monitor. Hint: check the solution mode, solve the circuit and retry.") - - channels = params['Channels'] - num_ch = monitor.NumChannels - channels = [ch for ch in channels if ch >= 1 and ch <= num_ch] - if len(channels) == 0: - raise IndexError("No valid channel numbers were specified.") - - bases = params['Bases'] - header = monitor.Header - if len(monitor.dblHour) < len(monitor.dblFreq): - header.insert(0, 'Frequency') - header.insert(1, 'Harmonic') - xlabel = 'Frequency (Hz)' - h = data[:, 0] - else: - header.insert(0, 'Hour') - header.insert(1, 'Seconds') - h = data[:, 0] * 3600 + data[:, 1] - total_seconds = max(h) - min(h) - if total_seconds < 7200: - xlabel = 'Time (s)' - else: - xlabel = 'Time (h)' - h /= 3600 +node_re = re.compile(r'(.*?)(\.[0-9])*$') - separate = False - if separate: - fig, axs = plt.subplots(len(channels), sharex=True)#, figsize=(8, 9)) - icolor = -1 - for ax, base, ch in zip(axs, bases, channels): - ch += 1 - icolor += 1 - ax.plot(h, data[:, ch] / base, color=Colors[icolor % len(Colors)]) - ax.grid() - ax.set_ylabel(header[ch]) +def remove_nodes(bus): + match = node_re.match(bus) + return match.group(1) - else: - fig, ax = plt.subplots(1) - icolor = -1 - for base, ch in zip(bases, channels): - ch += 1 - icolor += 1 - ax.plot(h, data[:, ch] / base, label=header[ch], color=Colors[icolor % len(Colors)]) +def _int_to_color(v: int): + return ((v & 255) / 255.0, (v >> 8 & 255) / 255.0, (v >> 16) / 255.0) - ax.grid() - ax.legend() - ax.set_ylabel('Mag') # Where "Mag" comes from? - ax.set_title(params['ObjectName']) - ax.set_xlabel(xlabel) +class DSVHandler: + def __init__(self, fn: Union[str, FilePath], show=True): + self.fn = fn + self.fig, self.ax = plt.subplots() + self.ax.get_xaxis().get_major_formatter().set_scientific(False) + self.ax.get_yaxis().get_major_formatter().set_scientific(False) + self.xy = [0.0, 0.0] + self.line_width = 1 + self.fig_caption = None + self.color = 'k' + self.key_class = None + self.no_scales = False + self.bold = True + self.txt_align = 'left' + self._do_show = show + def BoldLabel(self, param_str: str): + self.bold = int(param_str.strip()) != 0 -def dss_tshape_plot(DSS, params): - # There is no dedicated API yet but we can move to the Obj API - name = params['ObjectName'] - DSS.Text.Command = f'? tshape.{name}.temp' - p = np.fromstring(DSS.Text.Result[1:-1].strip(), dtype=float, sep=' ') - try: - DSS.Text.Command = f'? tshape.{name}.hour' - h = np.fromstring(DSS.Text.Result[1:-1].strip(), dtype=float, sep=' ') - except: - h = np.array([]) - try: - interval = f'? tshape.{name}.interval' # hours - interval = float(DSS.Text.Result) - except: - interval = 1 + def Caption(self, param_str: str): + self.fig_caption = param_str.strip().strip('"') + self.fig.canvas.manager.set_window_title(self.fig_caption) - fig, ax = plt.subplots(1)#, figsize=(8.5, 6))#, num=f"TShape.{params['ObjectName']}") - if not h.size: - h = interval * np.array(range(len(p))) + def ChartCaption(self, param_str: str): + self.ax.set_title(param_str.strip().strip('"')) - x_unit = 'h' - if h[-1] < 1: - h *= 3600 - x_unit = 's' - color1 = params['Color1'] - ax.plot(h, p, color=color1, label="Price") - ax.set_title(f"TShape = {params['ObjectName']}") - ax.set_xlabel(f'Time ({x_unit})') - ax.set_ylabel('Temperature') + def Center(self, param_str: str): + *int_params, text = param_str.split(',') + x, y, s = [int(v.strip()) for v in int_params] + text = text.strip().strip('"') + if '/_' in text: + text = text.replace('/_', '∠') + '°' - ax.grid(ls='--') - plt.tight_layout() + if '->' in text: + text = text.replace('->', '→') + s = s * 1.5 + elif '<-' in text: + text = text.replace('<-', '←') + s = s * 1.5 + elif '^' in text: + text = text.replace('^', '↑') + s = s * 1.5 + self.ax.text(x, y, text, horizontalalignment='center', fontsize=s * 8 / 13.) -def dss_priceshape_plot(DSS, params): - # There is no dedicated API yet but we can move to the Obj API - name = params['ObjectName'] - DSS.Text.Command = f'? priceshape.{name}.price' - p = np.fromstring(DSS.Text.Result[1:-1].strip(), dtype=float, sep=' ') - try: - DSS.Text.Command = f'? priceshape.{name}.hour' - h = np.fromstring(DSS.Text.Result[1:-1].strip(), dtype=float, sep=' ') - except: - h = np.array([]) + def Circle(self, param_str: str): + params = param_str.split(',') + x, y = float(params[0]), float(params[1]) + fc = _int_to_color(int(params[4])) + ec = _int_to_color(int(params[3])) + self.ax.scatter(x, y, marker='o', color=fc, edgecolors=ec, s=50, zorder=10, linewidths=0.5) - try: - interval = f'? priceshape.{name}.interval' # hours - interval = float(DSS.Text.Result) - except: - interval = 1 - fig, ax = plt.subplots(1)#, figsize=(8.5, 6))#, num=f"PriceShape.{params['ObjectName']}") + def ClickOn(self, param_str: str): + #TODO + pass - if not h.size: - h = interval * np.array(range(len(p))) - x_unit = 'h' - if h[-1] < 1: - h *= 3600 - x_unit = 's' + def Curve(self, param_str: str): + *int_params, curve_name, rest = param_str.split(',', 7) + npts, color, width, style, curve_markers, curve_marker = [int(v.strip()) for v in int_params] + if curve_markers: + marker_dict = get_marker_dict(curve_marker) + else: + marker_dict = {} + + data = np.fromstring(rest, dtype=float, sep=',') + self.ax.plot(data[:npts], data[npts:], lw=width/2.0, label=curve_name.strip().strip('"'), color=_int_to_color(color), ls=DSV_LINE_STYLES[style], **marker_dict) + # self.ax.minorticks_on() - color1 = params['Color1'] - ax.plot(h, p, color=color1, label="Price") - ax.set_title(f"PriceShape = {params['ObjectName']}") - ax.set_xlabel(f'Time ({x_unit})') - ax.set_ylabel('Price') + def DataColor(self, param_str: str): + self.color = _int_to_color(int(param_str)) - ax.grid(ls='--') - plt.tight_layout() + def Draw(self, param_str: str): + if not self.no_scales: + # Currently not used since Move/Draw is emulated with axhline + return + + x0, y0 = self.xy + x1, y1 = [float(v.strip().strip('"')) for v in param_str.split(',')] + self.ax.plot([x0, x1], [y0, y1], color=self.color, lw=self.line_width/2.0) -def dss_loadshape_plot(DSS, params): -# pprint(params) - - ls = DSS.ActiveCircuit.LoadShapes - ls.Name = params['ObjectName'] - h = ls.TimeArray - p = ls.Pmult - q = ls.Qmult - - fig, ax = plt.subplots(1)#, figsize=(8.5, 6))#, num=f"LoadShape.{params['ObjectName']}") - if not h.size or h is None or len(h) != len(p): - h = ls.HrInterval * np.array(range(len(p))) + def FStyle(self, param_str: str): + fstyle = int(param_str.strip().strip('"')) + # if fstyle != 0: + # print('Unhandled font style:', fstyle) - x_unit = 'h' - if h[-1] < 1: - h *= 3600 - x_unit = 's' - color1 = params['Color1'] - color2 = params['Color2'] + def KeepAspect(self, param_str: str): + try: + v = int(param_str.strip().strip('"')) + except: + v = 1 + + if v: + self.ax.set_aspect('equal', 'datalim') + else: + self.ax.set_aspect('auto') - ax.plot(h, p, color=color1, label="Pmult") - if q.size == p.size: - ax.plot(h, q, color=color2, label="Qmult") - ax.set_title(f"LoadShape = {params['ObjectName']}") - ax.set_xlabel(f'Time ({x_unit})') - if ls.UseActual: - if q.size == p.size: - ax.set_ylabel('kW, kvar') - else: - ax.set_ylabel('kW') - else: - ax.set_ylabel('p.u.') + def KeyClass(self, param_str: str): + self.key_class = int(param_str.strip()) - ax.grid(ls='--') - if q.size == p.size: - ax.legend() - plt.tight_layout() + def Label(self, param_str: str): + *int_params, text, _ = param_str.split(',') + x, y, color_int = [int(v.strip()) for v in int_params] + color = _int_to_color(color_int) + text = text.strip().strip('"') + self.ax.text(x, y, text, + horizontalalignment='center', + fontsize=10 * 8 / 13., + color=color, + backgroundcolor='white', + weight='bold' if self.bold else 'normal' + ) -node_re = re.compile(r'(.*?)(\.[0-9])*$') + def Line(self, param_str: str): + #TODO: use LineCollection -def remove_nodes(bus): - match = node_re.match(bus) - return match.group(1) + *str_params, rest = param_str.split(',', 3) + line_name, bus1, bus2 = [v.strip().strip('"') for v in str_params] + *int_params, rest = rest.split(',', 4) + offset, data_count, num_cust, total_cust = [int(v) for v in int_params] + *dbl_params, rest = rest.split(',', 6) + kv, dist, x1, y1, x2, y2 = [float(v) for v in dbl_params] + int_params = rest.split(',') + #TODO: markers + color, width, style, dots, mark_center, center_marker_code, node_marker_code, node_marker_size = [int(v) for v in int_params] -# def remove_nodes2(bus): - # dot_pos = bus.find('.') - # if dot_pos == -1: - # return bus + if dots: + node_marker_dict = get_marker_dict(node_marker_code) + node_marker_dict['markersize'] *= max(1, np.sqrt(node_marker_size) - 1) * node_marker_dict['markersize'] / 7.0 + else: + node_marker_dict = {} - # return bus[:dot_pos] - -def get_branch_data(DSS, branch_objects, bus_coords, do_values=pqNone, do_switches=False, idxs=None, single_ph_line_style =1, three_ph_line_style=1): - line_count = branch_objects.Count if not idxs else len(idxs) - lines = np.empty(shape=(line_count, 2, 2), dtype=np.float64) - lines.fill(np.nan) - values = np.empty(shape=(line_count, ), dtype=np.float64) - values.fill(np.nan) - lines_styles = np.zeros(shape=(line_count,), dtype=np.int8) - - element = DSS.ActiveCircuit.ActiveCktElement - - if do_switches: - switch_idxs = [] - isolated_idxs = [] - extra = [switch_idxs, isolated_idxs] - else: - extra = [] - # def get_buses_line(l): - # b1 = remove_nodes(l.Bus1) - # b2 = remove_nodes(l.Bus2) - - offset = 0 - skip = set() - - # norm_min_volts = DSS.ActiveCircuit.Settings.NormVminpu - # norm_max_volts = DSS.ActiveCircuit.Settings.NormVmaxpu - # emerg_min_volts = DSS.ActiveCircuit.Settings.EmergVminpu - # emerg_max_volts = DSS.ActiveCircuit.Settings.EmergVmaxpu - - vbs = None - if do_values == pqCurrent: - #max_currents = dict(zip(DSS.ActiveCircuit.PDElements.AllNames, DSS.ActiveCircuit.PDElements.AllMaxCurrents(True))) - max_currents = dict(zip(DSS.ActiveCircuit.PDElements.AllNames, DSS.ActiveCircuit.PDElements.AllPctNorm(True))) - elif do_values == pqCapacity: - capacities = dict(zip(DSS.ActiveCircuit.PDElements.AllNames, DSS.ActiveCircuit.PDElements.AllPctNorm(True))) - elif do_values == pqVoltage: - node_volts = dict(zip(DSS.ActiveCircuit.AllNodeNames, DSS.ActiveCircuit.AllBusVmag * 1e-3)) - vbs = np.empty(shape=(line_count, ), dtype=np.float64) - vbs.fill(0) - extra.append(vbs) - - if idxs: - l = branch_objects - for idx in idxs: - l.idx = idx - buses = element.BusNames - b1 = remove_nodes(buses[0]) - b2 = remove_nodes(buses[1]) - - fr = bus_coords.get(b1) - to = bus_coords.get(b2) - - if fr is None or to is None: - skip.add(idx) - continue - - lines[offset, 0] = fr - lines[offset, 1] = to - offset += 1 - - if do_values == pqNone: - return lines[:offset] - - offset = 0 - for idx in idxs: - if idx in skip: - continue - - l.idx = idx - - if do_values == pqPower: - values[offset] = np.abs(element.TotalPowers[0]) - elif do_values == pqLosses: - values[offset] = abs(element.Losses[0]) / l.Length - elif do_values == pqVoltage: - b2name = nodot(l.Bus2) - b = DSS.ActiveCircuit.Buses[b2name] - vb = b.kVBase - vbs[offset] = vb - value = 1e30 - if vb > 0: - for n in b.Nodes: - if n > 0 and n <= 3: - value = min(value, node_volts[f'{b2name}.{n}'] / vb) - - values[offset] = value - elif do_values == pqCurrent: - values[offset] = max_currents.get(element.Name, np.NaN) - elif do_values == pqCapacity: - values[offset] = capacities.get(element.Name, np.NaN) - - offset += 1 + self.ax.plot([x1, x2], [y1, y2], color=_int_to_color(color), lw=width / 2.0, ls=DSV_LINE_STYLES[style], solid_capstyle='round', **node_marker_dict) - return lines[:offset], values[:offset] - - else: - for i, l in enumerate(branch_objects): - buses = element.BusNames - b1 = remove_nodes(buses[0]) - b2 = remove_nodes(buses[1]) - - fr = bus_coords.get(b1) - to = bus_coords.get(b2) - - if fr is None or to is None or not element.Enabled: - skip.add(i) - continue + if mark_center: + center_marker_dict = get_marker_dict(center_marker_code) + self.ax.scatter((x1 + x2) / 2, (y1 + y2) / 2, color=_int_to_color(color), **center_marker_dict) - if do_switches: - if element.IsIsolated: - isolated_idxs.append(offset) - if l.IsSwitch: - #skip.add(i) - switch_idxs.append(offset) - #continue - lines[offset, 0] = fr - lines[offset, 1] = to + def Marker(self, param_str: str): + params = param_str.split(',') + x, y = float(params[0]), float(params[1]) + c, symbol, marker_size = [int(v) for v in params[2:]] + marker_dict = get_marker_dict(symbol) + marker_dict['markersize'] *= max(1, np.sqrt(marker_size) - 1) * marker_dict['markersize'] / 7.0 + self.ax.plot(x, y, ls=None, color=_int_to_color(c), **marker_dict) - offset += 1 - - if do_values == pqNone: - return [lines[:offset], None, None] + extra - - offset = 0 - for i, l in enumerate(branch_objects): - if i in skip: - continue - - if do_values == pqPower: - values[offset] = np.abs(element.TotalPowers[0]) - elif do_values == pqLosses: - values[offset] = abs(element.Losses[0]) / l.Length - elif do_values == pqVoltage: - b2name = nodot(l.Bus2) - b = DSS.ActiveCircuit.Buses[b2name] - vb = b.kVBase - vbs[offset] = vb - value = 1e30 - - if l.Phases < 3: - lines_styles[offset] = 1 - - if vb > 0: - for n in b.Nodes: - if n > 0 and n <= 3: - value = min(value, node_volts[f'{b2name}.{n}'] / vb) - - values[offset] = value - elif do_values == pqCurrent: - values[offset] = max_currents.get(element.Name, np.NaN) - elif do_values == pqCapacity: - values[offset] = capacities.get(element.Name, np.NaN) - - lines_styles[offset] = single_ph_line_style if l.Phases == 1 else three_ph_line_style - offset += 1 - - return [lines[:offset], values[:offset], lines_styles[:offset]] + extra - + def MarkAt(self, param_str: str): + params = param_str.split(',') + x, y = float(params[0]), float(params[1]) + symbol, marker_size = [int(v) for v in params[2:]] + marker_dict = get_marker_dict(symbol) + marker_dict['markersize'] *= max(1, np.sqrt(marker_size) - 1) * marker_dict['markersize'] / 7.0 + self.ax.plot(x, y, ls=None, color=self.color, **marker_dict) -def get_point_data(DSS: IDSS, point_objects, bus_coords, do_values=False): - if isinstance(point_objects, str): - cls = point_objects - DSS.SetActiveClass(cls) - point_objects = DSS.ActiveClass - - point_count = point_objects.Count - - points = np.empty(shape=(point_count, 2), dtype=np.float64) - values = np.empty(shape=(point_count, ), dtype=np.float64) - - offset = 0 - skip = set() - element = DSS.ActiveCircuit.ActiveCktElement - for i, _ in enumerate(point_objects): - buses = element.BusNames - all_coords = [] - buses = [remove_nodes(b) for b in buses] - all_coords = [c for c in (bus_coords.get(b) for b in buses) if c] - - if not all_coords: - skip.add(i) - continue - coords = tuple(sum(c) / len(all_coords) for c in zip(*all_coords)) - - points[offset] = coords - offset += 1 - - if not do_values: - return points[:offset] - - offset = 0 - for i, _ in enumerate(point_objects): - if i in skip: - continue - - values[offset] = np.abs(element.TotalPowers[0]) - offset += 1 - - return points[:offset], values[:offset] + def Move(self, param_str: str): + x, y = [float(v.strip().strip('"')) for v in param_str.split(',')] + if self.no_scales: + self.xy = [x, y] + else: + self.ax.axhline(y, color=self.color, lw=self.line_width / 2.0) -def dss_profile_plot(DSS, params): - if len(DSS.ActiveCircuit.Meters) == 0: - raise RuntimeError(f"An EnergyMeter is required to use 'plot profile'") + def NoScales(self, param_str: str): + self.no_scales = True + self.ax.get_xaxis().set_visible(False) + self.ax.get_yaxis().set_visible(False) - PhasesToPlot = params['PhasesToPlot'] - ProfileScale = params['ProfileScale'] - - vmin = DSS.ActiveCircuit.Settings.NormVminpu - vmax = DSS.ActiveCircuit.Settings.NormVmaxpu - if ProfileScale == '120kft': - xlabel = 'Distance (kft)' - ylabel = '120 Base Voltage' - DenomLN = 1.0 / 120.0 - # DenomLL = 1.732 / 120.0 - LenScale = 3.2809 - # RangeScale = 120.0 - else: - xlabel = 'Distance (km)' - ylabel = 'p.u. Voltage' - DenomLN = 1.0 - # DenomLL = 1.732 - LenScale = 1.0 - # RangeScale = 1.0 - - busnode_to_index = {(bn.rsplit('.', 1)[0], int(bn.rsplit('.', 1)[1])): num for (num, bn) in enumerate(DSS.ActiveCircuit.AllNodeNames)} - bus_to_kvbase = {b.Name: b.kVBase for b in DSS.ActiveCircuit.Buses} - puV = DSS.ActiveCircuit.AllBusVmagPu / DenomLN - distances = {name: d for (name, d) in zip(DSS.ActiveCircuit.AllBusNames, DSS.ActiveCircuit.AllBusDistances * LenScale)} - linewidths = [] - segments = [] - colors = [] - linestyles = [] - seg_phases = [] - pri_only = (PhasesToPlot == PROFILEALLPRI) - if PhasesToPlot in [PROFILEALL, PROFILEALLPRI, PROFILE3PH]: - phases = (1, 2, 3) - else: - phases = PhasesToPlot - try: - _ = iter(phases) - except: - phases = [phases] - - for em in DSS.ActiveCircuit.Meters: - branch_names = em.AllBranchesInZone - br: str - for br in branch_names: - if not br.startswith('Line.'): - continue - ls = '-' - lw = 2 + def PctRim(self, param_str: str): + self.ax.margins(float(param_str) / 100.0) - DSS.ActiveCircuit.Lines.Name = br[len('Line.'):] - if PROFILE3PH == PhasesToPlot and DSS.ActiveCircuit.Lines.Phases < 3: - continue + def Range(self, param_str: str): + pass - bus1 = nodot(DSS.ActiveCircuit.Lines.Bus1) - bus2 = nodot(DSS.ActiveCircuit.Lines.Bus2) - # Plot all phases present (between 1 and 3) - for iphs in phases: - try: - b1n_idx = busnode_to_index[(bus1, iphs)] - b2n_idx = busnode_to_index[(bus2, iphs)] - except: - continue + def Rect(self, param_str: str): + left, bottom, right, top = [int(v) for v in param_str.split(',')] + r = patches.Rectangle((left, bottom), right - left, top - bottom, fill=True, ec='k', fc='#c0c0c0') + self.ax.add_patch(r) - if bus_to_kvbase[bus1] < 1.0: - if pri_only: - continue - ls = ':' - lw = 1 - - segments.append(((distances[bus1], puV[b1n_idx]), (distances[bus2], puV[b2n_idx]))) - colors.append(Colors[iphs - 1]) - seg_phases.append(iphs) - linestyles.append(ls) - linewidths.append(lw) - #TODO: NodeMarkerCode, NodeMarkerWidth - - if include_3d in ('both', '2d'): - fig = plt.figure()#figsize=(9, 5)) - ax = fig.add_subplot(1, 1, 1) - ax.set_xlabel(xlabel) - ax.set_ylabel(ylabel) - if PhasesToPlot in (PROFILELL3PH, PROFILELLALL, PROFILELLPRI): - ax.set_title('L-L Voltage Profile') + + def SetProp(self, param_str: str): + if int(param_str.rsplit(',', 1)[-1]) != 0: + self.ax.grid(which='both', ls='--') else: - ax.set_title('L-N Voltage Profile') - + self.ax.grid(False) - lc = LineCollection(segments, linewidth=linewidths, colors=colors, linestyles=linestyles) - # ax.set_title('{}:{}, max: {:3g}'.format(DSS.ActiveCircuit.Name, quantity, quantity_max_value)) - ax.get_xaxis().get_major_formatter().set_scientific(False) - ax.get_yaxis().get_major_formatter().set_scientific(False) - ax.add_collection(lc) - ax.autoscale_view() - ax.axhline(vmin, color='darkred', ls='-', lw=3) - ax.axhline(vmax, color='darkred', ls='-', lw=3) - ax.grid(ls='--') - plt.tight_layout() - - if include_3d in ('both', '3d'): - fig2 = plt.figure()#figsize=(7, 7)) - ax2 = fig2.add_subplot(1, 1, 1, projection='3d') - ax2.set_xlabel(xlabel) - ax2.set_ylabel(ylabel) - if PhasesToPlot in (PROFILELL3PH, PROFILELLALL, PROFILELLPRI): - ax2.set_title('L-L Voltage Profile') - else: - ax2.set_title('L-N Voltage Profile') + def Text(self, param_str: str): + *int_params, text = param_str.split(',') + x, y, c, s = [int(v.strip()) for v in int_params] + text = text.strip().strip('"') + self.ax.text(x, y, text, ha=self.txt_align, va='center', fontsize=s * 10 / 13.) - segments_3d = [ - [(*p, ph) for p in seg] for seg, ph in zip(segments, seg_phases) - ] - rseg = np.ravel(segments) - max_x = np.max(rseg[::2]) - max_y = np.max(rseg[1::2]) - min_y = np.min(rseg[1::2]) - lc3d = Line3DCollection(segments_3d, colors=colors, linestyles=linestyles) - ax2.add_collection(lc3d) - ax2.set_xlabel(xlabel) - ax2.set_ylabel(ylabel) - ax2.set_zlabel('Phase') - xl = [0, max_x] - yl = [min(min_y, vmin) - 0.05, min(max_y, vmax) + 0.05] - maxph = np.max(seg_phases) + 1 - ax2.set_xlim(xl) - ax2.set_ylim(yl) - ax2.set_zlim(0, maxph) - ax2.plot_surface( - np.array([xl, xl]), - np.array([[vmax, vmax]] * 2), - np.array([[0, 0], [maxph, maxph]]), - color='k', - alpha=0.5 - ) - ax2.plot_surface( - np.array([xl, xl]), - np.array([[vmin, vmin]] * 2), - np.array([[0, 0], [maxph, maxph]]), - color='k', - alpha=0.5 - ) - ax2.autoscale_view() - - - -def get_gic_line_data(DSS: IDSS, bus_coords, single_ph_line_style=1, three_ph_line_style=1): - branch_objects = DSS.Obj.GICLine - line_count = len(branch_objects)# if not idxs else len(idxs) - lines = np.empty(shape=(line_count, 2, 2), dtype=np.float64) - lines.fill(np.nan) - values = np.empty(shape=(line_count, ), dtype=np.float64) - values.fill(np.nan) - lines_styles = np.zeros(shape=(line_count,), dtype=np.int8) - offset = 0 - # skip = set() - - # GIC lines are not exposed nicely in the classic API, so we'll use the new Obj API - for gic_line in DSS.Obj.GICLine: - if not gic_line.enabled: - continue - - b1 = remove_nodes(gic_line.bus1) - b2 = remove_nodes(gic_line.bus2) - fr = bus_coords.get(b1) - to = bus_coords.get(b2) - - if fr is None or to is None: - # skip.add(idx) - continue + + def TxtAlign(self, param_str: str): + v = int(param_str) + if v == 1: + self.txt_align = 'left' + return + + if v == 2: + self.txt_align = 'center' + return - lines[offset, 0] = fr - lines[offset, 1] = to - - lines_styles[offset] = single_ph_line_style if gic_line.phases == 1 else three_ph_line_style - max_current = DSS._lib.Obj_CktElement_MaxCurrent(gic_line._ptr, 1) - values[offset] = max_current - offset += 1 - - return lines[:offset], values[:offset], lines_styles[:offset] - -def dss_circuit_plot(DSS: IDSS, params={}, fig=None, ax=None, is3d=False): - quantity = str_to_pq.get(params.get('Quantity', None), pqNone) - dots = params.get('Dots', False) - color1 = params.pop('Color1', Colors[0]) - color2 = params.pop('Color2', Colors[1]) - color3 = params.pop('Color3', Colors[2]) - single_ph_line_style = params.get('SinglePhLineStyle', 1) - three_ph_line_style = params.get('ThreePhLineStyle', 1) - max_lw = params.get('MaxLineThickness', 5) - bus_markers = params.get('BusMarkers', []) - do_labels = params.get('Labels', False) - - - norm_min_volts = DSS.ActiveCircuit.Settings.NormVminpu - # norm_max_volts = DSS.ActiveCircuit.Settings.NormVmaxpu - emerg_min_volts = DSS.ActiveCircuit.Settings.EmergVminpu - # emerg_max_volts = DSS.ActiveCircuit.Settings.EmergVmaxpu + if v == 3: + self.txt_align = 'right' + return + - # bus_coords = dict((b.Name, (b.x, b.y)) for b in DSS.ActiveCircuit.Buses if (b.x, b.y) != (0.0, 0.0)) - bus_coords = dict((b.Name, (b.x, b.y)) for b in DSS.ActiveCircuit.Buses if b.Coorddefined) + def Width(self, param_str: str): + self.line_width = int(param_str.strip().strip('"')) + - if fig is None: - fig = plt.figure()#figsize=(8, 7)) - - given_ax = ax is not None - if not given_ax: - ax = plt.gca() - else: - plt.sca(ax) + def Xlabel(self, param_str: str): + self.ax.set_xlabel(param_str.strip().strip('"')) - if not is3d: - ax.set_aspect('equal', 'datalim') + + def Ylabel(self, param_str: str): + self.ax.set_ylabel(param_str.strip().strip('"')) + + def parse_and_plot(self): + with open(self.fn, 'r') as f: + for l in f: + l = l.strip() + if not l: + continue - lines_lines, lines_values, lines_styles, switch_idxs, isolated_idxs, *extra = get_branch_data( - DSS, - DSS.ActiveCircuit.Lines, - bus_coords, - do_values=quantity, - do_switches=True, - single_ph_line_style=single_ph_line_style, - three_ph_line_style=three_ph_line_style - ) - - if isolated_idxs: - line_idx = isolated_idxs - if not is3d: - ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=1, linestyle='-', color='#ff00ff', capstyle='round')) + item_name, *rest = l.split(',', 1) + item_name = item_name.strip() + if item_name not in DSS_ITEMS: + raise NotImplemented(f'"{item_name}" DSV item is not implemented') - if switch_idxs: - line_idx = switch_idxs - if not is3d: - ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=1, linestyle='-', color='#000000', capstyle='round')) + # print(item, repr(rest)[:100]) + getattr(self, item_name)(rest[0] if rest else '') # let the exception propagate on error - switch_idxs = set(switch_idxs) - isolated_idxs = set(isolated_idxs) - #lc_lines = LineCollection(lines_lines, linewidths=0.5, color=color1)# + 3 * lines_values / np.max(lines_values), linestyle='solid', color=color1) - try: - quantity_max_value = params.pop('MaxScale') - except: - quantity_max_value = 0 - - quantity_suffix = '' - - if lines_lines is not None and len(lines_lines) > 0: - if quantity in (pqVoltage,): - colors = [] - for v in lines_values: - if v > norm_min_volts or np.isnan(v): - colors.append(color1) - elif v > emerg_min_volts: - colors.append(color2) - else: - colors.append(color3) - - - for ls in set(lines_styles): - line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs] - if not is3d: - edgecolors = [colors[i] for i in line_idx] - ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=1, linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=edgecolors, capstyle='round')) - if dots: - ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=edgecolors, s=9, lw=1) - ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=edgecolors, s=9, lw=1) + if self._do_show: + plt.show(self.fig) + else: + return self.fig, self.ax + + +class DSSMPLPlotter: + _ctx_to_plotter = WeakKeyDictionary() + + def __init__(self, dss: IDSS): + if dss._api_util.ctx not in DSSMPLPlotter._ctx_to_plotter: + DSSMPLPlotter._ctx_to_plotter[dss._api_util.ctx] = self + + self.dss = dss + self._original_allow_forms = None + self._do_show = True + self._enabled = False + + + def dsv(self, fn: Union[str, FilePath], show: Optional[bool] = None): + """ + Plot a .DSV file from OpenDSS. This is the format that the classic `DSSView.exe` uses. + + `DSSView.exe` is the default plotting utility distributed with OpenDSS. As such, one + can use the OpenDSS GUI for some analysis, plot some results, and then replot then + using DSS-Python to further manipulate the resulting figures. + + *The new OpenDSS Viewer, a separate download, does not use the .DSV format.* + + If `show` is `None`, the "show" setting from the default plotter will be used as default. + Users can provide `show=True` or `show=False` directly to override the behavior. Useful + especially when only plotting DSVs, without further features from the DSS plotter. + """ + return DSVHandler(fn, show=self._do_show if show is None else show).parse_and_plot() + + def monitor(self, + *, + ObjectName: str = None, + Channels: List[int] = None, # TODO: allow channel names too + Bases: List[float] = None, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + monitor = DSS.ActiveCircuit.Monitors + monitor.Name = ObjectName + data = monitor.AsMatrix() + if data is None or len(data) == 0: + raise ValueError("There is not data to plot in the monitor. Hint: check the solution mode, solve the circuit and retry.") + + channels = Channels + num_ch = monitor.NumChannels + channels = [ch for ch in channels if ch >= 1 and ch <= num_ch] + if len(channels) == 0: + raise IndexError("No valid channel numbers were specified.") + + bases = Bases + header = list(monitor.Header) + if len(monitor.dblHour) < len(monitor.dblFreq): + header.insert(0, 'Frequency') + header.insert(1, 'Harmonic') + xlabel = 'Frequency (Hz)' + h = data[:, 0] + else: + header.insert(0, 'Hour') + header.insert(1, 'Seconds') + h = data[:, 0] * 3600 + data[:, 1] + total_seconds = max(h) - min(h) + if total_seconds < 7200: + xlabel = 'Time (s)' + else: + xlabel = 'Time (h)' + h /= 3600 + + separate = False + if separate: + fig, axs = plt.subplots(len(channels), sharex=True)#, figsize=(8, 9)) + icolor = -1 + for ax, base, ch in zip(axs, bases, channels): + ch += 1 + icolor += 1 + ax.plot(h, data[:, ch] / base, color=Colors[icolor % len(Colors)]) + ax.grid() + ax.set_ylabel(header[ch]) - # if is3d: - # ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=[colors[i] for i in line_idx], capstyle='round')) - # ax.set_xlim(np.min(lines_lines_3d[:, :, 0]), np.max(lines_lines_3d[:, :, 0])) - # ax.set_ylim(np.min(lines_lines_3d[:, :, 1]), np.max(lines_lines_3d[:, :, 1])) + else: + fig, ax = plt.subplots(1) + icolor = -1 + for base, ch in zip(bases, channels): + ch += 1 + icolor += 1 + ax.plot(h, data[:, ch] / base, label=header[ch], color=Colors[icolor % len(Colors)]) - quantity_max_value = 0 - elif quantity in (pqLosses,): - - if quantity_max_value == 0: - # quantity_max_value = max(lines_values) * 1e-3 - # For compatibility with the official version, loop through all lines instead - # of the actual plotted lines - element = DSS.ActiveCircuit.ActiveCktElement - quantity_max_value = max( - abs(element.Losses[0] / line.Length) - for line in DSS.ActiveCircuit.Lines - if element.Enabled - ) * 0.001 + ax.grid() + ax.legend() + ax.set_ylabel('Mag') # Where "Mag" comes from? - lines_values = np.clip(3 * 1e-3 * lines_values / quantity_max_value, 0.5, max_lw) - if not is3d: - for ls in set(lines_styles): - line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs] - # edgecolors = [colors[i] for i in line_idx] - ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=color1, capstyle='round')) - if dots: - ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) - ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) + ax.set_title(ObjectName) + ax.set_xlabel(xlabel) - elif quantity in (pqCurrent, pqCapacity): - line_idx = [i for i in range(lines_lines.shape[0]) if i not in isolated_idxs and i not in switch_idxs] - colors = [color3 if v > 100 and not np.isnan(v) else color1 for v in lines_values[line_idx]] - if quantity_max_value == 0: - quantity_max_value = max(lines_values) + def tshape(self, + *, + ObjectName: str = None, + Color1: str = None, + **kwargs: Unpack[PlotParams] + ): + # There is no dedicated API yet but we can move to the Obj API + name = ObjectName + DSS = self.dss + DSS.Text.Command = f'? tshape.{name}.temp' + p = np.fromstring(DSS.Text.Result[1:-1].strip(), dtype=float, sep=' ') + try: + DSS.Text.Command = f'? tshape.{name}.hour' + h = np.fromstring(DSS.Text.Result[1:-1].strip(), dtype=float, sep=' ') + except: + h = np.array([]) - lines_values = np.clip(3 * lines_values / quantity_max_value, 0.5, max_lw) - if not is3d: - ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle='-', color=colors, capstyle='round')) - if dots: - ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=colors, s=9, lw=1) - ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=colors, s=9, lw=1) + try: + interval = f'? tshape.{name}.interval' # hours + interval = float(DSS.Text.Result) + except: + interval = 1 - elif quantity != pqNone: - if quantity == pqPower: - quantity_suffix = ' kW' - if quantity_max_value == 0: - #lines_values *= 1e-3 + fig, ax = plt.subplots(1)#, figsize=(8.5, 6))#, num=f"TShape.{ObjectName}") - # For compatibility with the official version, loop through all lines instead - # of the actual plotted lines - element = DSS.ActiveCircuit.ActiveCktElement - - quantity_max_value = max( - element.TotalPowers[0] - for _ in DSS.ActiveCircuit.Lines - if element.Enabled - ) #* 0.001 - else: - #TODO:may need workaround about GeneralPlotQuantity - quantity_max_value = max(lines_values) + if not h.size: + h = interval * np.array(range(len(p))) - for ls in set(lines_styles): - line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs] - if not is3d: - ax.add_collection(LineCollection( - lines_lines[line_idx, :], - linewidths=np.clip(0.5 + 3 * lines_values[line_idx] / quantity_max_value, 0.5, max_lw), - linestyle=LINES_STYLE_CODE.get(ls, 'solid'), - color=color1, - capstyle='round' - )) - if dots: - ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) - ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) - else: - #TODO: handle 1 and 3 phase, etc.? - if not is3d: - ax.add_collection(LineCollection(lines_lines, linewidths=1, linestyle='-', color=color1, capstyle='round')) - # else: - # ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=color1, capstyle='round')) - # ax.set_xlim(np.min(lines_lines[:, :, 0]), np.max(lines_lines[:, :, 0])) - # ax.set_ylim(np.min(lines_lines[:, :, 1]), np.max(lines_lines[:, :, 1])) + x_unit = 'h' + if h[-1] < 1: + h *= 3600 + x_unit = 's' - transformers_lines, *_ = get_branch_data(DSS, DSS.ActiveCircuit.Transformers, bus_coords) + color1 = Color1 + ax.plot(h, p, color=color1, label="Price") + ax.set_title(f"TShape = {ObjectName}") + ax.set_xlabel(f'Time ({x_unit})') + ax.set_ylabel('Temperature') - if not is3d: - lc_transformers = LineCollection(transformers_lines, linewidth=3, linestyle='solid', color='gray') - ax.add_collection(lc_transformers) + ax.grid(ls='--') + fig.set_layout_engine(layout='tight') - lines_lines, lines_values, lines_styles, *_ = get_gic_line_data(DSS, bus_coords, single_ph_line_style=single_ph_line_style, three_ph_line_style=three_ph_line_style) - if len(lines_lines) != 0: - if quantity_max_value == 0: - quantity_max_value = max(lines_values) - lines_values = np.clip(3 * lines_values / quantity_max_value, 0.5, max_lw) - for ls in set(lines_styles): - line_idx = [i for i, c in enumerate(lines_styles) if c == ls] - ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=color1, capstyle='round')) - if dots: - ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) - ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) + def priceshape(self, + *, + ObjectName: str = None, + Color1: str = None, + **kwargs: Unpack[PlotParams] + ): + # There is no dedicated API yet but we can move to the Obj API + name = ObjectName + DSS = self.dss + DSS.Text.Command = f'? priceshape.{name}.price' + p = np.fromstring(DSS.Text.Result[1:-1].strip(), dtype=float, sep=' ') + try: + DSS.Text.Command = f'? priceshape.{name}.hour' + h = np.fromstring(DSS.Text.Result[1:-1].strip(), dtype=float, sep=' ') + except: + h = np.array([]) - # 'Daisysize' - # 'Markercode', 'Nodewidth' # NodeMarkerCode - - branch_marker_options = [ - ('MarkSwitches', 'SwitchMarkerCode', None, DSS.ActiveCircuit.Lines, switch_idxs), - ('MarkFuses', 'FuseMarkerCode', 'FuseMarkerSize', DSS.ActiveCircuit.Fuses, None), - ('MarkRegulators', 'RegMarkerCode', 'RegMarkerSize', DSS.ActiveCircuit.RegControls, None), - ('MarkRelays', 'RelayMarkerCode', 'RelayMarkerSize', DSS.ActiveCircuit.Relays, None), - ('MarkReclosers', 'RecloserMarkerCode', 'RecloserMarkerSize', DSS.ActiveCircuit.Reclosers, None) - ] - - point_marker_options = [ - ('MarkTransformers', 'TransMarkerCode', 'TransMarkerSize', DSS.ActiveCircuit.Transformers, None), - ('MarkCapacitors', 'CapMarkerCode', 'CapMarkerSize', DSS.ActiveCircuit.Capacitors, None), - ('MarkPVSystems', 'PVMarkerCode', 'PVMarkerSize', DSS.ActiveCircuit.PVSystems, None), - ('MarkStorage', 'StoreMarkerCode', 'StoreMarkerSize', 'Storage', None), - ] - - pmarkers = params.pop('Markers', None) - if pmarkers is not None: - for (mark_opt, code_opt, size_opt, objs, idxs) in branch_marker_options: - # print(mark_opt, pmarkers[mark_opt]) - if not pmarkers[mark_opt]: - continue - - marker_code = pmarkers[code_opt] - marker_size = pmarkers[size_opt] - #TODO: use marker_size? - marker_dict = get_marker_dict(marker_code) - if mark_opt == 'MarkRegulators': - for obj in objs: - DSS.ActiveCircuit.Transformers.Name = obj.Transformer - bus = remove_nodes(DSS.ActiveCircuit.ActiveCktElement.BusNames[obj.Winding - 1]) - coords = bus_coords.get(bus) - if coords is None: - continue - ax.plot(*coords, color='red', **marker_dict) - - else: - #TODO? branch_lines = get_branch_data(DSS, objs, bus_coords, idxs=idxs) - pass - - - for (mark_opt, code_opt, size_opt, objs, idxs) in point_marker_options: - if not pmarkers[mark_opt]: - continue - - marker_code = pmarkers[code_opt] - marker_size = pmarkers[size_opt] - - points = get_point_data(DSS, objs, bus_coords) - - # if marker_code not in MARKER_MAP: - #marker_code = 25 - - marker_dict = get_marker_dict(marker_code) - #marker_dict['markersize'] *= (marker_size / 2.0)**2 - marker_dict['markersize'] *= (marker_size / 1.2)**2 - - #marker_dict['marker'] = marker_dict['marker'].vertices - #marker_dict.pop('markersize') - #marker_dict.pop('markerfacecolor') - # print(mark_opt, marker_dict['marker']) - # pprint(marker_dict) - ax.plot(points[:, 0], points[:, 1], ls='', color='red', **marker_dict) - #ax.plot(points[:, 0], points[:, 1], color='red', ls='', marker=6, alpha=1) - - for bus_marker in bus_markers: - name = bus_marker['Name'] - bus = DSS.ActiveCircuit.Buses[name] - if not bus.Coorddefined: - raise RuntimeError('Bus markers: coordinates are not defined for bus "{name}"') - - marker_dict = get_marker_dict(bus_marker['Code']) - marker_size = bus_marker['Size'] - marker_dict['markersize'] *= (marker_size / 6) - ax.plot(bus.x, bus.y, ls='', color=bus_marker['Color'], **marker_dict) - - - ax.set_xlabel('X') - ax.set_ylabel('Y') - if not given_ax: - if quantity != pqNone: - ax.set_title('{}:{}, max={:g}{}'.format(DSS.ActiveCircuit.Name.upper(), quantity_str[quantity], quantity_max_value, quantity_suffix)) - ax.autoscale_view() - ax.get_xaxis().get_major_formatter().set_scientific(False) - ax.get_yaxis().get_major_formatter().set_scientific(False) - plt.tight_layout() - - if do_labels: - coords_to_names = {} - for name, coords in bus_coords.items(): - prev = coords_to_names.get(coords) - if prev: - coords_to_names[coords] = prev + ',' + name - else: - coords_to_names[coords] = name + try: + interval = f'? priceshape.{name}.interval' # hours + interval = float(DSS.Text.Result) + except: + interval = 1 - for coords, name in coords_to_names.items(): - ax.text(*coords, name, zorder=11, fontsize='xx-small', va='center', clip_on=True) + fig, ax = plt.subplots(1)#, figsize=(8.5, 6))#, num=f"PriceShape.{ObjectName}") - + if not h.size: + h = interval * np.array(range(len(p))) -def dss_scatter_plot(DSS, params): - x = np.empty(shape=(DSS.ActiveCircuit.NumBuses, )) - y = np.empty(shape=(DSS.ActiveCircuit.NumBuses, )) - vcomplex = np.empty(shape=(DSS.ActiveCircuit.NumBuses, 3), dtype=complex) - x.fill(np.nan) - y.fill(np.nan) - vcomplex.fill(np.nan) - for idx, b in enumerate(DSS.ActiveCircuit.Buses): - if not b.Coorddefined: - continue - - x[idx] = b.x - y[idx] = b.y - vnodes = b.puVoltages.view(dtype=complex) - nnodes = min(3, len(vnodes)) - vcomplex[idx, :nnodes] = vnodes[:nnodes] - - vabs = np.abs(vcomplex) - del vcomplex - vmean = np.mean(vabs, axis=1, where=np.isfinite(vabs)) - - if include_3d in ('both', '2d'): - fig, ax = plt.subplots(1, 1, constrained_layout=True)#, figsize=(8, 7)) - dss_circuit_plot(DSS, fig=fig, ax=ax, params={}) - ax.get_xaxis().get_major_formatter().set_scientific(False) - ax.get_yaxis().get_major_formatter().set_scientific(False) - sc = ax.scatter(x, y, c=vmean) - fig.colorbar(sc, label='V1 (pu)') - ax.set_title('{}:{}'.format(DSS.ActiveCircuit.Name.upper(), 'Voltage magnitude')) - - if include_3d in ('both', '3d'): - bus_coords = {} - for idx, b in enumerate(DSS.ActiveCircuit.Buses): - if b.Coorddefined: - bus_coords[b.Name] = (b.x, b.y, vmean[idx]) - - fig = plt.figure()#figsize=(7, 7)) - ax = fig.add_subplot(projection='3d') - dss_circuit_plot(DSS, fig=fig, ax=ax, params={}, is3d=True) - ax.get_xaxis().get_major_formatter().set_scientific(False) - ax.get_yaxis().get_major_formatter().set_scientific(False) - - # if is3d: - # ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=[colors[i] for i in line_idx], capstyle='round')) - # ax.set_xlim(np.min(lines_lines_3d[:, :, 0]), np.max(lines_lines_3d[:, :, 0])) - # ax.set_ylim(np.min(lines_lines_3d[:, :, 1]), np.max(lines_lines_3d[:, :, 1])) - - sc = ax.scatter(x, y, vmean, c='k', s=2) - - segs = [] - el = DSS.ActiveCircuit.ActiveCktElement - for pd in DSS.ActiveCircuit.PDElements: - buses = el.BusNames - if len(buses) != 2: - continue + x_unit = 'h' + if h[-1] < 1: + h *= 3600 + x_unit = 's' - seg = [] - for b in buses: - c = bus_coords.get(nodot(b), None) - if c is not None: - seg.append(c) - - if len(seg) == 2: - segs.append(seg) - - segs = np.array(segs, dtype=float) - seg_v = (segs[:, 0, 2] + segs[:, 1, 2]) / 2 - lc3d = Line3DCollection(segs) - ax.add_collection(lc3d) - lc3d.set_array(seg_v) - #fig.colorbar(sc, label='V1 (pu)') - ax.set_title('{}:{}'.format(DSS.ActiveCircuit.Name.upper(), 'Voltage magnitude')) - - -def dss_visualize_plot(DSS, params): - XMAX = 300 - #pprint(params) - quantity = params['Quantity'] - - # Fix for backend v0.13.1 - quantity = { - 'Power': 'Powers', - 'Current': 'Currents', - 'Voltage': 'Voltages', - }.get(quantity, quantity) - - element = DSS.ActiveCircuit.ActiveCktElement - etype, ename = params['ElementType'], params['ElementName'] - nconds = element.NumConductors - # nphases = element.NumPhases - buses = element.BusNames[:2] # max 2 terminals - vbases = [max(1, 1000 * DSS.ActiveCircuit.Buses[nodot(b)].kVBase) for b in buses] - - # assert DSS.ActiveCircuit.ActiveCktElement.Name == params['ElementType'] + '.' + params['ElementName'] - fig, ax = plt.subplots(1, gridspec_kw=dict(left=0.05, right=0.95, bottom=0.05, top=0.92))#, figsize=(8.6, 7)) - ax.get_xaxis().set_visible(False) - ax.get_yaxis().set_visible(False) - ax.grid(False) - - y = 20 + 10 * nconds - box_xy0 = np.array([100, 10]) - box_xy1 = np.array([XMAX - 100, y]) - box_wh = box_xy1 - box_xy0 - middle_box = Rectangle(box_xy0, *box_wh, facecolor='lightgray', edgecolor='k') - ax.text(XMAX / 2, 10 + (y - 10) / 2, f'{etype}.{ename.upper()}', ha='center', va='center', fontweight='bold', rotation='vertical') - ax.add_patch(middle_box) - ax.plot([0, 300], [0, 0], color='gray', lw=7) - - ax.plot([-5] * 2, [5, y - 5], color='k', lw=7) - ax.text(25, y, buses[0].upper(), ha='left') - if len(buses) > 1: - ax.plot([XMAX + 5] * 2, [5, y - 5], color='k', lw=7) - ax.text(XMAX - 25, y, buses[1].upper(), ha='right') - - voltage = (quantity == 'Voltages') - - if quantity == 'Powers': - values = 1e-3 * (element.Voltages.view(dtype=complex) * np.conj(element.Currents.view(dtype=complex))) - unit = 'kVA' - elif voltage: - values = element.Voltages.view(dtype=complex) - unit = 'pu' - elif quantity == 'Currents': - values = element.Currents.view(dtype=complex) - unit = 'A' - - ax.set_title(f'{etype}.{ename.upper()} {quantity} ({unit})') - size = 'x-small' - - def get_text(): - v = values[bus_idx * nconds + cond] - if quantity == 'Powers': - arrow_text = f"{v.real:-.6g} {'-' if v.imag < 0 else '+'} j{abs(v.imag):g}" - else: - if quantity == 'Voltages': - v /= vbase - arrow_text = f"{np.abs(v):-.6g} {unit} ∠ {np.angle(v, deg=True):.2f}°" + color1 = Color1 - return arrow_text + ax.plot(h, p, color=color1, label="Price") + ax.set_title(f"PriceShape = {ObjectName}") + ax.set_xlabel(f'Time ({x_unit})') + ax.set_ylabel('Price') - for bus_idx, vbase in enumerate(vbases): - for cond in range(nconds): - if cond < (nconds - 1): - weight = 'bold' - lw = 2 + ax.grid(ls='--') + fig.set_layout_engine(layout='tight') + + + def loadshape(self, + *, + ObjectName: str = None, + Color1: str = None, + Color2: str = None, + **kwargs: Unpack[PlotParams] + ): + # pprint(kwargs) + DSS = self.dss + + ls = DSS.ActiveCircuit.LoadShapes + ls.Name = ObjectName + h = asarray(ls.TimeArray) + p = asarray(ls.Pmult) + q = asarray(ls.Qmult) + + fig, ax = plt.subplots(1)#, figsize=(8.5, 6))#, num=f"LoadShape.{ObjectName}") + + if not h.size or h is None or len(h) != len(p): + h = ls.HrInterval * np.array(range(len(p))) + + x_unit = 'h' + if h[-1] < 1: + h *= 3600 + x_unit = 's' + + color1 = Color1 + color2 = Color2 + + ax.plot(h, p, color=color1, label="Pmult") + if q.size == p.size: + ax.plot(h, q, color=color2, label="Qmult") + + ax.set_title(f"LoadShape = {ObjectName}") + ax.set_xlabel(f'Time ({x_unit})') + if ls.UseActual: + if q.size == p.size: + ax.set_ylabel('kW, kvar') else: - weight = 'normal' - lw = 0.6667 - - if bus_idx: - arrow_x = XMAX + 5 - arrow_y = y - (cond + 1) * 10.0 - dx = box_xy1[0] - arrow_x - ax.text(arrow_x - 20, arrow_y + 2, get_text(), ha='right', fontweight=weight, size=size) - if voltage: - plt.plot([arrow_x, dx + arrow_x], [arrow_y, arrow_y], color='k', lw=lw*1.5) - x = XMAX - 4 * (cond) - 1 - ax.annotate('', xy=(x, arrow_y), xytext=(x, 0), arrowprops=dict(width=0.2, color='lightgray')) - else: - ax.annotate('', xytext=(arrow_x, arrow_y), xy=(dx + arrow_x, arrow_y), arrowprops=dict(width=lw, color='k')) + ax.set_ylabel('kW') + else: + ax.set_ylabel('p.u.') + + ax.grid(ls='--') + if q.size == p.size: + ax.legend() + fig.set_layout_engine(layout='tight') + + + def _get_branch_data(self, + branch_objects: DSSIterable, + bus_coords: Dict[str, Tuple[float, float, float]], + do_values=pqNone, + do_switches=False, + idxs=None, + single_ph_line_style: int = 1, + three_ph_line_style: int = 1 + ): + DSS = self.dss + + line_count = branch_objects.Count if not idxs else len(idxs) + lines = np.empty(shape=(line_count, 2, 2), dtype=np.float64) + lines.fill(np.nan) + values = np.empty(shape=(line_count, ), dtype=np.float64) + values.fill(np.nan) + lines_styles = np.zeros(shape=(line_count,), dtype=np.int8) + + element = DSS.ActiveCircuit.ActiveCktElement + + if do_switches: + switch_idxs = [] + isolated_idxs = [] + try: + element.IsIsolated + has_is_isolated = True + except: + has_is_isolated = False + isolated_names = set(name.lower() for name in DSS.ActiveCircuit.Topology.AllIsolatedBranches if name) + + extra = [switch_idxs, isolated_idxs] + else: + extra = [] + # def get_buses_line(l): + # b1 = remove_nodes(l.Bus1) + # b2 = remove_nodes(l.Bus2) + + offset = 0 + skip = set() + + # norm_min_volts = DSS.ActiveCircuit.Settings.NormVminpu + # norm_max_volts = DSS.ActiveCircuit.Settings.NormVmaxpu + # emerg_min_volts = DSS.ActiveCircuit.Settings.EmergVminpu + # emerg_max_volts = DSS.ActiveCircuit.Settings.EmergVmaxpu + + vbs = None + if do_values == pqCurrent: + # Currently the same as pqCapacity to match the OpenDSS impl.; the correct would be: + #max_currents = dict(zip(DSS.ActiveCircuit.PDElements.AllNames, DSS.ActiveCircuit.PDElements.AllMaxCurrents(True))) + try: + max_currents = dict(zip(DSS.ActiveCircuit.PDElements.AllNames, DSS.ActiveCircuit.PDElements.AllPctNorm(True))) + except: + max_currents = {} + element = DSS.ActiveCircuit.ActiveCktElement + for _ in DSS.ActiveCircuit.PDElements: + if not element.Enabled: + continue + currents = np.abs(asarray(element.Currents).view(dtype=complex)) + max_current = np.max(currents[:element.NumConductors]) + norm_amps = element.NormalAmps + max_currents[element.Name] = (100 * max_current / norm_amps) if norm_amps else 0.0 + + elif do_values == pqCapacity: + try: + capacities = dict(zip(DSS.ActiveCircuit.PDElements.AllNames, DSS.ActiveCircuit.PDElements.AllPctNorm(True))) + except: + max_currents = {} + element = DSS.ActiveCircuit.ActiveCktElement + for _ in DSS.ActiveCircuit.PDElements: + if not element.Enabled: + max_currents[element.Name] = np.nan + continue + currents = np.abs(asarray(element.Currents).view(dtype=complex)) + max_current = np.max(currents[:element.NumConductors]) + norm_amps = element.NormalAmps + max_currents[element.Name] = (100 * max_current / norm_amps) if norm_amps else 0.0 + + elif do_values == pqVoltage: + node_volts = dict(zip(DSS.ActiveCircuit.AllNodeNames, asarray(DSS.ActiveCircuit.AllBusVmag) * 1e-3)) + vbs = np.empty(shape=(line_count, ), dtype=np.float64) + vbs.fill(0) + extra.append(vbs) + + if idxs: + l = branch_objects + for idx in idxs: + l.idx = idx + buses = element.BusNames + b1 = remove_nodes(buses[0]) + b2 = remove_nodes(buses[1]) + + fr = bus_coords.get(b1) + to = bus_coords.get(b2) + + if fr is None or to is None: + skip.add(idx) + continue - else: - arrow_x = -5 - arrow_y = y - (cond + 1) * 10.0 - dx = box_xy0[0] + 5 - ax.text(arrow_x + 20, arrow_y + 2, get_text(), ha='left', fontweight=weight, size=size) - if voltage: - plt.plot([arrow_x, dx + arrow_x], [arrow_y, arrow_y], color='k', lw=lw*1.5) - x = 4 * (cond) + 1 - ax.annotate('', xy=(x, arrow_y), xytext=(x, 0), arrowprops=dict(width=0.2, color='lightgray')) - else: - ax.annotate('', xytext=(arrow_x, arrow_y), xy=(dx + arrow_x, arrow_y), arrowprops=dict(width=lw, color='k')) - - if quantity == 'Currents': - # Residual - v = -np.sum(values[(nconds * bus_idx):(nconds * (bus_idx + 1))]) - txt = f"{np.abs(v):-.6g} A ∠ {np.angle(v, deg=True):.2f}°" - - if bus_idx: - arrow_x = XMAX + 5 - arrow_y = -10 - dx = box_xy1[0] - arrow_x - ax.text(arrow_x - 5, arrow_y + 2, txt, ha='right', fontweight='normal', size=size) - ax.annotate('', xytext=(arrow_x, arrow_y), xy=(dx + arrow_x, arrow_y), arrowprops=dict(width=1, color='k')) - else: - arrow_x = -5 - arrow_y = -10 - dx = box_xy0[0] + 5 - ax.text(arrow_x + 5, arrow_y + 2, txt, ha='left', fontweight='normal', size=size) - ax.annotate('', xytext=(arrow_x, arrow_y), xy=(dx + arrow_x, arrow_y), arrowprops=dict(width=1, color='k')) - - ax.set_xlim(-20, XMAX + 20) - ax.set_ylim(-15, y + 5) - - -def dss_general_data_plot(DSS, params): - is_general = params['PlotType'] == 'GeneralData' - ValueIndex = max(1, params['ValueIndex'] - 1) - fn = params['ObjectName'] - MaxScaleIsSpecified = params['MaxScaleIsSpecified'] - MinScaleIsSpecified = params['MinScaleIsSpecified'] - MaxScale = params['MaxScale'] - MinScale = params['MinScale'] - - # Whenever we add Pandas as a dependency, this could be - # rewritten to avoid all the extra/slow work - exp = re.compile('[,=\t]') - with open(fn, 'r') as f: - line = f.readline().rstrip() - field = exp.split(line)[ValueIndex].strip() #TODO: Is this right?! - f.seek(0) - # Find min and max - names, vals = [], [] - for line in f: - if not line: - continue + lines[offset, 0] = fr + lines[offset, 1] = to + offset += 1 + + if do_values == pqNone: + return lines[:offset] + + offset = 0 + for idx in idxs: + if idx in skip: + continue + + l.idx = idx + + if do_values == pqPower: + values[offset] = np.abs(element.TotalPowers[0]) + elif do_values == pqLosses: + values[offset] = abs(element.Losses[0]) / l.Length + elif do_values == pqVoltage: + b2name = nodot(l.Bus2) + b = DSS.ActiveCircuit.Buses[b2name] + vb = b.kVBase + vbs[offset] = vb + value = 1e30 + if vb > 0: + for n in b.Nodes: + if n > 0 and n <= 3: + value = min(value, node_volts[f'{b2name}.{n}'] / vb) + + values[offset] = value + elif do_values == pqCurrent: + values[offset] = max_currents.get(element.Name, np.nan) + elif do_values == pqCapacity: + values[offset] = capacities.get(element.Name, np.nan) + + offset += 1 + + return lines[:offset], values[:offset] + + else: + for i, l in enumerate(branch_objects): + buses = element.BusNames + b1 = remove_nodes(buses[0]) + b2 = remove_nodes(buses[1]) + + fr = bus_coords.get(b1) + to = bus_coords.get(b2) + + if fr is None or to is None or not element.Enabled: + skip.add(i) + continue + + if do_switches: + if ((has_is_isolated and element.IsIsolated) or + ((not has_is_isolated) and (element.Name.lower() in isolated_names))): + isolated_idxs.append(offset) - data = exp.split(line) - name, val = data[0], data[ValueIndex] - if len(val): - names.append(name) - vals.append(float(val)) - - vals = np.asarray(vals) - min_val = np.min(vals) - max_val = np.max(vals) - - # Do some sanity checking on the numbers. Don't want to include negative numbers in autoadd plot - if not is_general: - if min_val < 0.0: - min_val = 0.0 - if max_val < 0.0: - max_val = 0.0 - - if MaxScaleIsSpecified: - max_val = MaxScale # Override with user specified value - if MinScaleIsSpecified: - min_val = MinScale # Override with user specified value - - diff = max_val - min_val - if diff == 0.0: - diff = max_val - if diff == 0.0: - diff = 1.0 # Everything is zero - - sidxs = np.argsort(vals) - bus: IBus = DSS.ActiveCircuit.ActiveBus - data = [] - labels = [] - do_labels = params['Labels'] - colors = [] - c1 = np.asarray(matplotlib.colors.colorConverter.to_rgb(params['Color1'])) - c2 = np.asarray(matplotlib.colors.colorConverter.to_rgb(params['Color2'])) - for i in sidxs: - name, val = names[i], vals[i] - if DSS.ActiveCircuit.SetActiveBus(name) <= 0 or not bus.Coorddefined: - continue - - if is_general: - data.append((bus.x, bus.y, val)) - s = ((val - min_val) / diff) - colors.append(c2*s + c1*(1-s)) - # InterpolateGradientColor(Color1, Color2, (GenPlotItem.Value - MinValue) / Diff), - else: # ptAutoAddLogPlot - data.append((bus.x, bus.y, val)) - # GetAutoColor((GenPlotItem.Value - MinValue) / Diff), + if l.IsSwitch: + #skip.add(i) + switch_idxs.append(offset) + #continue + + lines[offset, 0] = fr + lines[offset, 1] = to + + offset += 1 + + if do_values == pqNone: + return [lines[:offset], None, None] + extra + + offset = 0 + + for i, l in enumerate(branch_objects): + if i in skip: + continue + + lines_styles[offset] = single_ph_line_style if l.Phases == 1 else three_ph_line_style + + if not element.Enabled: + lines_styles[offset] = single_ph_line_style if l.Phases == 1 else three_ph_line_style + offset += 1 + continue + + if do_values == pqPower: + values[offset] = np.abs(element.TotalPowers[0]) + elif do_values == pqLosses: + values[offset] = abs(element.Losses[0]) / l.Length + elif do_values == pqVoltage: + b2name = nodot(l.Bus2) + b = DSS.ActiveCircuit.Buses[b2name] + vb = b.kVBase + vbs[offset] = vb + value = 1e30 + + if l.Phases < 3: + lines_styles[offset] = 1 + + if vb > 0: + for n in b.Nodes: + if n > 0 and n <= 3: + value = min(value, node_volts[f'{b2name}.{n}'] / vb) + + values[offset] = value + elif do_values == pqCurrent: + values[offset] = max_currents.get(element.Name, np.nan) + elif do_values == pqCapacity: + values[offset] = capacities.get(element.Name, np.nan) + offset += 1 - if do_labels: - labels.append(bus.Name) + return [lines[:offset], values[:offset], lines_styles[:offset]] + extra + - data = np.asarray(data) + def _get_point_data(self, + point_objects: Union[str, Iterable], + bus_coords: Dict[str, Tuple[float, float, float]], + do_values: bool = False + ): + DSS = self.dss + if isinstance(point_objects, str): + cls = point_objects + DSS.SetActiveClass(cls) + point_objects = DSS.ActiveClass + + point_count = point_objects.Count + points = np.empty(shape=(point_count, 2), dtype=np.float64) + values = np.empty(shape=(point_count, ), dtype=np.float64) - dss_circuit_plot(DSS, params) + offset = 0 + skip = set() + element = DSS.ActiveCircuit.ActiveCktElement + for i, _ in enumerate(point_objects): + buses = element.BusNames + all_coords = [] + buses = [remove_nodes(b) for b in buses] + all_coords = [c for c in (bus_coords.get(b) for b in buses) if c] + + if not all_coords: + skip.add(i) + continue - #fig = plt.figure(figsize=(8, 7)) - plt.title(f'{field}, Max={max_val:.3g}') - ax = plt.gca() - #if not is3d: - #ax.set_aspect('equal', 'datalim') + coords = tuple(sum(c) / len(all_coords) for c in zip(*all_coords)) + + points[offset] = coords + offset += 1 + + if not do_values: + return points[:offset] + + offset = 0 + for i, _ in enumerate(point_objects): + if i in skip: + continue + + if element.Enabled: + values[offset] = np.abs(element.TotalPowers[0]) + else: + values[offset] = np.nan + + offset += 1 + + return points[:offset], values[:offset] - ax.scatter(data[:, 0], data[:, 1], c=colors, zorder=10) - # ax.colorbar() - #ax.autoscale_view() - #ax.get_xaxis().get_major_formatter().set_scientific(False) - #ax.get_yaxis().get_major_formatter().set_scientific(False) - #plt.tight_layout() + def profile(self, + *, + PhasesToPlot: int = None, + ProfileScale: float = None, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + + if len(DSS.ActiveCircuit.Meters) == 0: + raise RuntimeError(f"An EnergyMeter is required to use 'plot profile'") + + vmin = DSS.ActiveCircuit.Settings.NormVminpu + vmax = DSS.ActiveCircuit.Settings.NormVmaxpu + if ProfileScale == '120kft': + xlabel = 'Distance (kft)' + ylabel = '120 Base Voltage' + DenomLN = 1.0 / 120.0 + # DenomLL = 1.732 / 120.0 + LenScale = 3.2809 + # RangeScale = 120.0 + else: + xlabel = 'Distance (km)' + ylabel = 'p.u. Voltage' + DenomLN = 1.0 + # DenomLL = 1.732 + LenScale = 1.0 + # RangeScale = 1.0 + + busnode_to_index = {(bn.rsplit('.', 1)[0], int(bn.rsplit('.', 1)[1])): num for (num, bn) in enumerate(DSS.ActiveCircuit.AllNodeNames)} + bus_to_kvbase = {b.Name: b.kVBase for b in DSS.ActiveCircuit.Buses} + puV = asarray(DSS.ActiveCircuit.AllBusVmagPu) / DenomLN + distances = {name: d for (name, d) in zip(DSS.ActiveCircuit.AllBusNames, asarray(DSS.ActiveCircuit.AllBusDistances) * LenScale)} + linewidths = [] + segments = [] + colors = [] + linestyles = [] + seg_phases = [] + pri_only = (PhasesToPlot == DSSPlotPhases.PROFILEALLPRI) + if PhasesToPlot in [DSSPlotPhases.PROFILEALL, DSSPlotPhases.PROFILEALLPRI, DSSPlotPhases.PROFILE3PH]: + phases = (1, 2, 3) + else: + phases = PhasesToPlot + try: + _ = iter(phases) + except: + phases = [phases] + + for em in DSS.ActiveCircuit.Meters: + if not DSS.ActiveCircuit.ActiveCktElement.Enabled: + continue + branch_names = em.AllBranchesInZone + br: str + for br in branch_names: + if not br.startswith('Line.'): + continue + ls = '-' + lw = 2 - # marker_code = MarkerIdx + DSS.ActiveCircuit.Lines.Name = br[len('Line.'):] - # NodeMarkerWidth: int - # MarkerIdx = NodeMarkerCode + if DSSPlotPhases.PROFILE3PH == PhasesToPlot and DSS.ActiveCircuit.Lines.Phases < 3: + continue - # marker_code = pmarkers[code_opt] - # marker_size = pmarkers[size_opt] - #marker_dict = get_marker_dict(marker_code) - # ax.plot(*coords, color='red', **marker_dict) - #MarkSpecialClasses + bus1 = nodot(DSS.ActiveCircuit.Lines.Bus1) + bus2 = nodot(DSS.ActiveCircuit.Lines.Bus2) + # Plot all phases present (between 1 and 3) + for iphs in phases: + try: + b1n_idx = busnode_to_index[(bus1, iphs)] + b2n_idx = busnode_to_index[(bus2, iphs)] + except: + continue -def dss_matrix_plot(DSS, params): - # plot_id = params.get('PlotId', None) - if params['MatrixType'] == 'IncMatrix': - title = 'Incidence matrix' - data = DSS.ActiveCircuit.Solution.IncMatrix[:-1] - else: - title = 'Laplacian matrix' - data = DSS.ActiveCircuit.Solution.Laplacian[:-1] + if bus_to_kvbase[bus1] < 1.0: + if pri_only: + continue + ls = ':' + lw = 1 + + segments.append(((distances[bus1], puV[b1n_idx]), (distances[bus2], puV[b2n_idx]))) + colors.append(Colors[iphs - 1]) + seg_phases.append(iphs) + linestyles.append(ls) + linewidths.append(lw) + #TODO: NodeMarkerCode, NodeMarkerWidth + + if self._include_3d in ('both', '2d'): + fig = plt.figure()#figsize=(9, 5)) + ax = fig.add_subplot(1, 1, 1) + ax.set_xlabel(xlabel) + ax.set_ylabel(ylabel) + if PhasesToPlot in (DSSPlotPhases.PROFILELL3PH, DSSPlotPhases.PROFILELLALL, DSSPlotPhases.PROFILELLPRI): + ax.set_title('L-L Voltage Profile') + else: + ax.set_title('L-N Voltage Profile') + - x, y, v = data[0::3], data[1::3], data[2::3] - m = coo.coo_matrix((v, (x, y))) - #fig, [ax, ax2] = plt.subplots(1, 2, figsize=(8.6 * 2, 8.6), constrained_layout=True, num=title) - - if include_3d in ('both', '2d'): - fig = plt.figure(constrained_layout=True)#, num=plot_id) #, figsize=(8.6, 8.6)) - ax = fig.add_subplot(1, 1, 1) - ax.grid(True) - ax.spy(m, marker='s', markersize=1, color=params['Color1']) - ax.set_xlabel('Column') - ax.set_ylabel('Row') - ax.set_title(title) - - if include_3d in ('both', '3d'): - fig = plt.figure()#figsize=(8.6, 8.6), num=plot_id + '_3D') - ax2 = fig.add_subplot(1, 1, 1, projection='3d') - ax2.scatter(x, y, v, c=v, marker='s') - ax2.set_xlabel('Column') - ax2.set_ylabel('Row') - ax2.set_zlabel('Value') - - -def dss_daisy_plot(DSS, params): - dss_circuit_plot(DSS, params) - - # print(params['DaisySize']) - - ax = plt.gca() - XMIN, XMAX = ax.get_xlim() - quantity = str_to_pq.get(params.get('Quantity', None), pqNone) - daisy_bus_list = params['DaisyBusList'] - do_labels = params['Labels'] - daisy_size = params['DaisySize'] - - ax.set_title(f'Device Locations / {quantity_str[quantity]}') - element = DSS.ActiveCircuit.ActiveCktElement - - if len(daisy_bus_list) == 0: - for g in DSS.ActiveCircuit.Generators: - if element.Enabled: - daisy_bus_list.append(element.BusNames[0]) - - counts = np.zeros(shape=(DSS.ActiveCircuit.NumBuses + 1,), dtype=np.int32) - for b in daisy_bus_list: - idx = DSS.ActiveCircuit.SetActiveBus(b) - if idx > 0: - counts[idx] += 1 - - radius = 0.005 * daisy_size * (XMAX - XMIN) - lines = [] - pointx, pointy = [], [] - for bidx in np.nonzero(counts)[0]: - bus: IBus = DSS.ActiveCircuit.Buses[int(bidx)] - if not bus.Coorddefined: - continue - - cnt = counts[bidx] - angle0 = 0 - angle = np.pi * 2.0 / cnt - for j in range(cnt): - Xc = bus.x + 2 * radius * np.cos(angle * j + angle0) - Yc = bus.y + 2 * radius * np.sin(angle * j + angle0) - lines.append([(bus.x, bus.y), (Xc, Yc)]) - pointx.append(Xc) - pointy.append(Yc) + lc = LineCollection(segments, linewidth=linewidths, colors=colors, linestyles=linestyles) + + # ax.set_title('{}:{}, max: {:3g}'.format(DSS.ActiveCircuit.Name, quantity, quantity_max_value)) + ax.get_xaxis().get_major_formatter().set_scientific(False) + ax.get_yaxis().get_major_formatter().set_scientific(False) + ax.add_collection(lc) + ax.autoscale_view() + ax.axhline(vmin, color='darkred', ls='-', lw=3) + ax.axhline(vmax, color='darkred', ls='-', lw=3) + ax.grid(ls='--') + fig.set_layout_engine(layout='tight') + if self._include_3d in ('both', '3d'): + fig2 = plt.figure()#figsize=(7, 7)) + ax2 = fig2.add_subplot(1, 1, 1, projection='3d') + ax2.set_xlabel(xlabel) + ax2.set_ylabel(ylabel) + if PhasesToPlot in (DSSPlotPhases.PROFILELL3PH, DSSPlotPhases.PROFILELLALL, DSSPlotPhases.PROFILELLPRI): + ax2.set_title('L-L Voltage Profile') + else: + ax2.set_title('L-N Voltage Profile') + + segments_3d = [ + [(*p, ph) for p in seg] for seg, ph in zip(segments, seg_phases) + ] + rseg = np.ravel(segments) + max_x = np.max(rseg[::2]) + max_y = np.max(rseg[1::2]) + min_y = np.min(rseg[1::2]) + lc3d = Line3DCollection(segments_3d, colors=colors, linestyles=linestyles) + ax2.add_collection(lc3d) + ax2.set_xlabel(xlabel) + ax2.set_ylabel(ylabel) + ax2.set_zlabel('Phase') + xl = [0, max_x] + yl = [min(min_y, vmin) - 0.05, min(max_y, vmax) + 0.05] + maxph = np.max(seg_phases) + 1 + ax2.set_xlim(xl) + ax2.set_ylim(yl) + ax2.set_zlim(0, maxph) + ax2.plot_surface( + np.array([xl, xl]), + np.array([[vmax, vmax]] * 2), + np.array([[0, 0], [maxph, maxph]]), + color='k', + alpha=0.5 + ) + ax2.plot_surface( + np.array([xl, xl]), + np.array([[vmin, vmin]] * 2), + np.array([[0, 0], [maxph, maxph]]), + color='k', + alpha=0.5 + ) + ax2.autoscale_view() + + + def _get_gic_line_data_altdss( + self, + altdss: IAltDSS, + bus_coords: Dict[str, Tuple[float, float, float]], + single_ph_line_style: int = 1, + three_ph_line_style: int = 1 + ): + branch_objects = altdss.GICLine + line_count = len(branch_objects)# if not idxs else len(idxs) + lines = np.empty(shape=(line_count, 2, 2), dtype=np.float64) + lines.fill(np.nan) + values = np.empty(shape=(line_count, ), dtype=np.float64) + values.fill(np.nan) + lines_styles = np.zeros(shape=(line_count,), dtype=np.int8) + offset = 0 + # skip = set() - lc = LineCollection(lines, linewidth=1, colors='r') - ax.add_collection(lc) - ax.scatter(pointx, pointy, marker='o', color='yellow', edgecolors='red', s=100, zorder=10) + # GIC lines are not exposed nicely in the classic API, so we'll use the new Obj API + for gic_line in altdss.GICLine: + if not gic_line.enabled: + continue - if not do_labels: - return + b1 = remove_nodes(gic_line.bus1) + b2 = remove_nodes(gic_line.bus2) + fr = bus_coords.get(b1) + to = bus_coords.get(b2) + + if fr is None or to is None: + # skip.add(idx) + continue + + lines[offset, 0] = fr + lines[offset, 1] = to - for bidx in np.nonzero(counts)[0]: - bus: IBus = DSS.ActiveCircuit.Buses[int(bidx)] - if not bus.Coorddefined: - continue + lines_styles[offset] = single_ph_line_style if gic_line.phases == 1 else three_ph_line_style + values[offset] = gic_line.MaxCurrent(1) + offset += 1 - ax.text(bus.x, bus.y, bus.Name, zorder=11, fontsize='xx-small', va='center', clip_on=True) + return lines[:offset], values[:offset], lines_styles[:offset] -def unquote(field: str): - field = field.strip() - if field[0] == '"' and field[-1] == '"': - return field[1:-1] - - return field + def _get_gic_line_data(self, + bus_coords: Dict[str, Tuple[float, float]], + single_ph_line_style: int = 1, + three_ph_line_style: int = 1 + ): + DSS = self.dss + try: + return self._get_gic_line_data_altdss( + DSS.to_altdss(), + bus_coords, + single_ph_line_style=single_ph_line_style, + three_ph_line_style=three_ph_line_style + ) + except: + pass + # Fallback for Oddie and COM + DSS.ActiveCircuit.SetActiveClass('GICLine') + aclass = DSS.ActiveCircuit.ActiveClass + line_count = aclass.Count# if not idxs else len(idxs) + lines = np.empty(shape=(line_count, 2, 2), dtype=np.float64) + lines.fill(np.nan) + values = np.empty(shape=(line_count, ), dtype=np.float64) + values.fill(np.nan) + lines_styles = np.zeros(shape=(line_count,), dtype=np.int8) + offset = 0 + # skip = set() -def dss_di_plot(DSS: IDSS, params): - caseYear, caseName, meterName = params['CaseYear'], params['CaseName'], params['MeterName'] - plotRegisters, peakDay = params['Registers'], params['PeakDay'] + # GIC lines are not exposed nicely in the classic API + element = DSS.ActiveCircuit.ActiveCktElement + idx = aclass.First + while idx != 0: + buses = element.BusNames + b1 = remove_nodes(buses[0]) + b2 = remove_nodes(buses[1]) + fr = bus_coords.get(b1) + to = bus_coords.get(b2) - fn = os.path.join(DSS.DataPath, caseName, f'DI_yr_{caseYear}', meterName + '.csv') + if fr is None or to is None: + # skip.add(idx) + continue + + lines[offset, 0] = fr + lines[offset, 1] = to - if len(plotRegisters) == 0: - raise RuntimeError("No register indices were provided for DI_Plot") + lines_styles[offset] = single_ph_line_style if gic_line.phases == 1 else three_ph_line_style + currents = np.abs(asarray(element.Currents).view(dtype=complex)) + max_current = np.max(currents[:element.NumConductors]) + values[offset] = max_current + offset += 1 - if not os.path.exists(fn): - fn = fn[:-4] + '_1.csv' + return lines[:offset], values[:offset], lines_styles[:offset] + + + def circuit(self, + *, + fig=None, + ax=None, + is3d=False, + Quantity: str = None, + Dots: bool = False, + Color1: str = None, + Color2: str = None, + Color3: str = None, + SinglePhLineStyle: int = None, + ThreePhLineStyle: int = None, + MaxLineThickness: float = None, + BusMarkers: List[BusMarker] = None, + Labels: bool = None, + Markers: ObjMarkers = None, + MaxScale: float = None, + MaxScaleIsSpecified: bool = None, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + + if not MaxScaleIsSpecified: + MaxScale = None + + quantity = str_to_pq.get(Quantity, pqNone) + dots = Dots + color1 = Color1 + color2 = Color2 + color3 = Color3 + single_ph_line_style = SinglePhLineStyle + three_ph_line_style = ThreePhLineStyle + max_lw = MaxLineThickness + bus_markers = BusMarkers or [] + do_labels = Labels + + norm_min_volts = DSS.ActiveCircuit.Settings.NormVminpu + # norm_max_volts = DSS.ActiveCircuit.Settings.NormVmaxpu + emerg_min_volts = DSS.ActiveCircuit.Settings.EmergVminpu + # emerg_max_volts = DSS.ActiveCircuit.Settings.EmergVmaxpu + + # bus_coords = dict((b.Name, (b.x, b.y)) for b in DSS.ActiveCircuit.Buses if (b.x, b.y) != (0.0, 0.0)) + bus_coords = dict((b.Name, (b.x, b.y)) for b in DSS.ActiveCircuit.Buses if b.Coorddefined) + + if fig is None: + fig = plt.figure()#figsize=(8, 7)) + + given_ax = ax is not None + if not given_ax: + ax = plt.gca() + else: + plt.sca(ax) - # Whenever we add Pandas as a dependency, this could be - # rewritten to avoid all the extra/slow work - selected_data = [] - day_data = [] - mult = 1 if peakDay else 0.001 + if not is3d: + ax.set_aspect('equal', 'datalim') + + lines_lines, lines_values, lines_styles, switch_idxs, isolated_idxs, *extra = self._get_branch_data( + DSS.ActiveCircuit.Lines, + bus_coords, + do_values=quantity, + do_switches=True, + single_ph_line_style=single_ph_line_style, + three_ph_line_style=three_ph_line_style + ) + + if isolated_idxs: + line_idx = isolated_idxs + if not is3d: + ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=1, linestyle='-', color='#ff00ff', capstyle='round')) - # If the file doesn't exist, let the exception raise - with open(fn, 'r') as f: - header = f.readline().rstrip() - allRegisterNames = [unquote(field) for field in header.strip().strip(' \t,').split(',')] - registerNames = [allRegisterNames[i] for i in plotRegisters] + if switch_idxs: + line_idx = switch_idxs + if not is3d: + ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=1, linestyle='-', color='#000000', capstyle='round')) + + switch_idxs = set(switch_idxs) + isolated_idxs = set(isolated_idxs) + #lc_lines = LineCollection(lines_lines, linewidths=0.5, color=color1)# + 3 * lines_values / np.max(lines_values), linestyle='solid', color=color1) + quantity_max_value = MaxScale if MaxScale is not None else 0.0 + + quantity_suffix = '' + + if lines_lines is not None and len(lines_lines) > 0: + if quantity in (pqVoltage,): + colors = [] + for v in lines_values: + if v > norm_min_volts or np.isnan(v): + colors.append(color1) + elif v > emerg_min_volts: + colors.append(color2) + else: + colors.append(color3) + + + for ls in set(lines_styles): + line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs] + if not is3d: + edgecolors = [colors[i] for i in line_idx] + ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=1, linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=edgecolors, capstyle='round')) + if dots: + ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=edgecolors, s=9, lw=1) + ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=edgecolors, s=9, lw=1) + + # if is3d: + # ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=[colors[i] for i in line_idx], capstyle='round')) + # ax.set_xlim(np.min(lines_lines_3d[:, :, 0]), np.max(lines_lines_3d[:, :, 0])) + # ax.set_ylim(np.min(lines_lines_3d[:, :, 1]), np.max(lines_lines_3d[:, :, 1])) + + quantity_max_value = 0 + elif quantity in (pqLosses,): + + if quantity_max_value == 0: + # quantity_max_value = max(lines_values) * 1e-3 + # For compatibility with the official version, loop through all lines instead + # of the actual plotted lines + element = DSS.ActiveCircuit.ActiveCktElement + quantity_max_value = max( + abs(element.Losses[0] / line.Length) + for line in DSS.ActiveCircuit.Lines + if element.Enabled + ) * 0.001 - if not len(registerNames): - raise RuntimeError("Could not find any register name in the file") + lines_values = np.clip(3 * 1e-3 * lines_values / quantity_max_value, 0.5, max_lw) + if not is3d: + for ls in set(lines_styles): + line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs] + # edgecolors = [colors[i] for i in line_idx] + ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=color1, capstyle='round')) + if dots: + ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) + ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) + + elif quantity in (pqCurrent, pqCapacity): + line_idx = [i for i in range(lines_lines.shape[0]) if i not in isolated_idxs and i not in switch_idxs] + colors = [color3 if v > 100 and not np.isnan(v) else color1 for v in lines_values[line_idx]] + + if quantity_max_value == 0: + quantity_max_value = max(lines_values) + + lines_values = np.clip(3 * lines_values / quantity_max_value, 0.5, max_lw) + if not is3d: + ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle='-', color=colors, capstyle='round')) + if dots: + ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=colors, s=9, lw=1) + ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=colors, s=9, lw=1) + + elif quantity != pqNone: + if quantity == pqPower: + quantity_suffix = ' kW' + if quantity_max_value == 0: + #lines_values *= 1e-3 + + # For compatibility with the official version, loop through all lines instead + # of the actual plotted lines + element = DSS.ActiveCircuit.ActiveCktElement + + quantity_max_value = max( + element.TotalPowers[0] + for _ in DSS.ActiveCircuit.Lines + if element.Enabled + ) #* 0.001 + else: + #TODO:may need workaround about GeneralPlotQuantity + quantity_max_value = max(lines_values) + + for ls in set(lines_styles): + line_idx = [i for i, c in enumerate(lines_styles) if c == ls and i not in isolated_idxs and i not in switch_idxs] + if not is3d: + ax.add_collection(LineCollection( + lines_lines[line_idx, :], + linewidths=np.clip(0.5 + 3 * lines_values[line_idx] / quantity_max_value, 0.5, max_lw), + linestyle=LINES_STYLE_CODE.get(ls, 'solid'), + color=color1, + capstyle='round' + )) + if dots: + ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) + ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) + else: + #TODO: handle 1 and 3 phase, etc.? + if not is3d: + ax.add_collection(LineCollection(lines_lines, linewidths=1, linestyle='-', color=color1, capstyle='round')) + # else: + # ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=color1, capstyle='round')) + # ax.set_xlim(np.min(lines_lines[:, :, 0]), np.max(lines_lines[:, :, 0])) + # ax.set_ylim(np.min(lines_lines[:, :, 1]), np.max(lines_lines[:, :, 1])) + + transformers_lines, *_ = self._get_branch_data(DSS.ActiveCircuit.Transformers, bus_coords) + + if not is3d: + lc_transformers = LineCollection(transformers_lines, linewidth=3, linestyle='solid', color='gray') + ax.add_collection(lc_transformers) + + lines_lines, lines_values, lines_styles, *_ = self._get_gic_line_data(bus_coords, single_ph_line_style=single_ph_line_style, three_ph_line_style=three_ph_line_style) + if len(lines_lines) != 0: + if quantity_max_value == 0: + quantity_max_value = max(lines_values) + + lines_values = np.clip(3 * lines_values / quantity_max_value, 0.5, max_lw) + for ls in set(lines_styles): + line_idx = [i for i, c in enumerate(lines_styles) if c == ls] + ax.add_collection(LineCollection(lines_lines[line_idx, :], linewidths=lines_values[line_idx], linestyle=LINES_STYLE_CODE.get(ls, 'solid'), color=color1, capstyle='round')) + if dots: + ax.scatter(lines_lines[line_idx, 0, 0].ravel(), lines_lines[line_idx, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) + ax.scatter(lines_lines[line_idx, 1, 0].ravel(), lines_lines[line_idx, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=color1, s=9, lw=1) + + + + # 'Daisysize' + # 'Markercode', 'Nodewidth' # NodeMarkerCode + + branch_marker_options = [ + ('MarkSwitches', 'SwitchMarkerCode', None, DSS.ActiveCircuit.Lines, switch_idxs), + ('MarkFuses', 'FuseMarkerCode', 'FuseMarkerSize', DSS.ActiveCircuit.Fuses, None), + ('MarkRegulators', 'RegMarkerCode', 'RegMarkerSize', DSS.ActiveCircuit.RegControls, None), + ('MarkRelays', 'RelayMarkerCode', 'RelayMarkerSize', DSS.ActiveCircuit.Relays, None), + ('MarkReclosers', 'RecloserMarkerCode', 'RecloserMarkerSize', DSS.ActiveCircuit.Reclosers, None) + ] + + point_marker_options = [ + ('MarkTransformers', 'TransMarkerCode', 'TransMarkerSize', DSS.ActiveCircuit.Transformers, None), + ('MarkCapacitors', 'CapMarkerCode', 'CapMarkerSize', DSS.ActiveCircuit.Capacitors, None), + ('MarkPVSystems', 'PVMarkerCode', 'PVMarkerSize', DSS.ActiveCircuit.PVSystems, None), + ('MarkStorage', 'StoreMarkerCode', 'StoreMarkerSize', DSS.ActiveCircuit.Storages, None), + ] - for line in f: - if not line: + pmarkers = Markers + if pmarkers is not None: + for (mark_opt, code_opt, size_opt, objs, idxs) in branch_marker_options: + # print(mark_opt, pmarkers[mark_opt]) + if not pmarkers[mark_opt]: + continue + + marker_code = pmarkers[code_opt] + marker_size = pmarkers[size_opt] + #TODO: use marker_size? + marker_dict = get_marker_dict(marker_code) + if mark_opt == 'MarkRegulators': + for obj in objs: + DSS.ActiveCircuit.Transformers.Name = obj.Transformer + bus = remove_nodes(DSS.ActiveCircuit.ActiveCktElement.BusNames[obj.Winding - 1]) + coords = bus_coords.get(bus) + if coords is None: + continue + ax.plot(*coords, color='red', **marker_dict) + + else: + #TODO? branch_lines = self._get_branch_data(objs, bus_coords, idxs=idxs) + pass + + + for (mark_opt, code_opt, size_opt, objs, idxs) in point_marker_options: + if not pmarkers[mark_opt]: + continue + + marker_code = pmarkers[code_opt] + marker_size = pmarkers[size_opt] + + points = self._get_point_data(objs, bus_coords) + + # if marker_code not in MARKER_MAP: + #marker_code = 25 + + marker_dict = get_marker_dict(marker_code) + #marker_dict['markersize'] *= (marker_size / 2.0)**2 + marker_dict['markersize'] *= (marker_size / 1.2)**2 + + #marker_dict['marker'] = marker_dict['marker'].vertices + #marker_dict.pop('markersize') + #marker_dict.pop('markerfacecolor') + # print(mark_opt, marker_dict['marker']) + # pprint(marker_dict) + ax.plot(points[:, 0], points[:, 1], ls='', color='red', **marker_dict) + #ax.plot(points[:, 0], points[:, 1], color='red', ls='', marker=6, alpha=1) + + for bus_marker in bus_markers: + name = bus_marker['Name'] + bus = DSS.ActiveCircuit.Buses[name] + if not bus.Coorddefined: + raise RuntimeError('Bus markers: coordinates are not defined for bus "{name}"') + + marker_dict = get_marker_dict(bus_marker['Code']) + marker_size = bus_marker['Size'] + marker_dict['markersize'] *= (marker_size / 6) + ax.plot(bus.x, bus.y, ls='', color=bus_marker['Color'], **marker_dict) + + + ax.set_xlabel('X') + ax.set_ylabel('Y') + if not given_ax: + if quantity != pqNone: + ax.set_title('{}:{}, max={:g}{}'.format(DSS.ActiveCircuit.Name.upper(), quantity_str[quantity], quantity_max_value, quantity_suffix)) + ax.autoscale_view() + ax.get_xaxis().get_major_formatter().set_scientific(False) + ax.get_yaxis().get_major_formatter().set_scientific(False) + fig.set_layout_engine(layout='tight') + + if do_labels: + coords_to_names = {} + for name, coords in bus_coords.items(): + prev = coords_to_names.get(coords) + if prev: + coords_to_names[coords] = prev + ',' + name + else: + coords_to_names[coords] = name + + for coords, name in coords_to_names.items(): + ax.text(*coords, name, zorder=11, fontsize='xx-small', va='center', clip_on=True) + + + def scatter(self, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + x = np.empty(shape=(DSS.ActiveCircuit.NumBuses, )) + y = np.empty(shape=(DSS.ActiveCircuit.NumBuses, )) + vcomplex = np.empty(shape=(DSS.ActiveCircuit.NumBuses, 3), dtype=complex) + x.fill(np.nan) + y.fill(np.nan) + vcomplex.fill(np.nan) + for idx, b in enumerate(DSS.ActiveCircuit.Buses): + if not b.Coorddefined: continue - rawValues = line.split(',') - selValues = [float(rawValues[0]), *(float(rawValues[i]) for i in plotRegisters)] - if not peakDay: - selected_data.append(selValues) + x[idx] = b.x + y[idx] = b.y + vnodes = asarray(b.puVoltages).view(dtype=complex) + nnodes = min(3, len(vnodes)) + vcomplex[idx, :nnodes] = vnodes[:nnodes] + + vabs = np.abs(vcomplex) + del vcomplex + with suppress_warnings(): + vmean = np.mean(vabs, axis=1, where=np.isfinite(vabs)) + + title = '{}:{}'.format(DSS.ActiveCircuit.Name.upper(), 'Voltage magnitude') + if self._include_3d in ('both', '2d'): + fig, ax = plt.subplots(1, 1, constrained_layout=True)#, figsize=(8, 7)) + self.circuit(fig=fig, ax=ax, Color1='k') + ax.get_xaxis().get_major_formatter().set_scientific(False) + ax.get_yaxis().get_major_formatter().set_scientific(False) + sc = ax.scatter(x, y, c=vmean) + fig.colorbar(sc, label='V1 (pu)') + ax.set_title(title) + + if self._include_3d in ('both', '3d'): + bus_coords = {} + for idx, b in enumerate(DSS.ActiveCircuit.Buses): + if b.Coorddefined: + bus_coords[b.Name] = (b.x, b.y, vmean[idx]) + + fig = plt.figure()#figsize=(7, 7)) + ax = fig.add_subplot(projection='3d') + self.circuit(fig=fig, ax=ax, is3d=True, Color1='k') + ax.get_xaxis().get_major_formatter().set_scientific(False) + ax.get_yaxis().get_major_formatter().set_scientific(False) + + # if is3d: + # ax.add_collection(Line3DCollection(lines_lines, linewidths=1, linestyle='-', color=[colors[i] for i in line_idx], capstyle='round')) + # ax.set_xlim(np.min(lines_lines_3d[:, :, 0]), np.max(lines_lines_3d[:, :, 0])) + # ax.set_ylim(np.min(lines_lines_3d[:, :, 1]), np.max(lines_lines_3d[:, :, 1])) + + sc = ax.scatter(x, y, vmean, c='k', s=2) + + segs = [] + el = DSS.ActiveCircuit.ActiveCktElement + for pd in DSS.ActiveCircuit.PDElements: + buses = el.BusNames + if len(buses) != 2: + continue + + seg = [] + for b in buses: + c = bus_coords.get(nodot(b), None) + if c is not None: + seg.append(c) + + if len(seg) == 2: + segs.append(seg) + + segs = np.array(segs, dtype=float) + seg_v = (segs[:, 0, 2] + segs[:, 1, 2]) / 2 + + lc3d = Line3DCollection(segs) + ax.add_collection(lc3d) + lc3d.set_array(seg_v) + #fig.colorbar(sc, label='V1 (pu)') + ax.set_title(title) + + + def visualize(self, + *, + Quantity: str = None, + ElementType: str = None, + ElementName: str = None, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + + XMAX = 300 + #pprint(kwargs) + quantity = Quantity + + # Fix for backend v0.13.1 + quantity = { + 'Power': 'Powers', + 'Current': 'Currents', + 'Voltage': 'Voltages', + }.get(quantity, quantity) + + element = DSS.ActiveCircuit.ActiveCktElement + etype, ename = ElementType, ElementName + full_name = f'{ElementType}.{ElementName}'.lower() + + # Check it the target element is currently selected + if element.Name.lower() != full_name: + DSS.ActiveCircuit.SetActiveElement(full_name) + + nconds = element.NumConductors + # nphases = element.NumPhases + buses = element.BusNames[:2] # max 2 terminals + vbases = [max(1, 1000 * DSS.ActiveCircuit.Buses[nodot(b)].kVBase) for b in buses] + + y = 20 + 10 * nconds + box_xy0 = np.array([100, 10]) + box_xy1 = np.array([XMAX - 100, y]) + box_wh = box_xy1 - box_xy0 + + voltage = (quantity == 'Voltages') + + if quantity == 'Powers': + values = 1e-3 * (asarray(element.Voltages).view(dtype=complex) * np.conj(asarray(element.Currents).view(dtype=complex))) + unit = 'kVA' + elif voltage: + values = asarray(element.Voltages).view(dtype=complex) + unit = 'pu' + elif quantity == 'Currents': + values = asarray(element.Currents).view(dtype=complex) + unit = 'A' + + size = 'x-small' + + def _get_text(): + v = values[bus_idx * nconds + cond] + if quantity == 'Powers': + arrow_text = f"{v.real:-.6g} {'-' if v.imag < 0 else '+'} j{abs(v.imag):g}" else: - day_data.append(selValues) - if len(day_data) == 24: - max_vals = [max(x) for x in zip(*day_data)] - max_vals[0] = day_data[0][0] - day_data = [] - selected_data.append(max_vals) - - if day_data: - max_vals = [max(x) for x in zip(*day_data)] - max_vals[0] = day_data[0][0] - day_data = [] - selected_data.append(max_vals) - - vals = np.asarray(selected_data, dtype=float) - fig, ax = plt.subplots(1) - icolor = -1 - for idx, name in enumerate(registerNames, start=1): - icolor += 1 - ax.plot(vals[:, 0], vals[:, idx] * mult, label=name, color=Colors[icolor % len(Colors)]) - - ax.set_title(f'{caseName}, Yr={caseYear}') - ax.set_xlabel('Hour') - ax.set_ylabel('MW, MWh or MVA') - ax.legend() - ax.grid() - - -def _plot_yearly_case(DSS: IDSS, caseName: str, meterName: str, plotRegisters: List[int], icolor: int, ax, registerNames: List[str]): - anyData = True - xvalues = [] - all_yvalues = [[] for _ in plotRegisters] - for caseYear in range(0, 21): - fn = os.path.join(DSS.DataPath, caseName, f'DI_yr_{caseYear}', 'Totals_1.csv') - if not os.path.exists(fn): - continue + if quantity == 'Voltages': + v /= vbase + arrow_text = f"{np.abs(v):-.6g} {unit} ∠ {np.angle(v, deg=True):.2f}°" + + return arrow_text + + fig, ax = plt.subplots(1, gridspec_kw=dict(left=0.05, right=0.95, bottom=0.05, top=0.92))#, figsize=(8.6, 7)) + ax.get_xaxis().set_visible(False) + ax.get_yaxis().set_visible(False) + ax.grid(False) + middle_box = patches.Rectangle(box_xy0, *box_wh, facecolor='lightgray', edgecolor='k') + ax.text(XMAX / 2, 10 + (y - 10) / 2, f'{etype}.{ename.upper()}', ha='center', va='center', fontweight='bold', rotation='vertical') + ax.add_patch(middle_box) + ax.plot([0, 300], [0, 0], color='gray', lw=7) + + ax.plot([-5] * 2, [5, y - 5], color='k', lw=7) + ax.text(25, y, buses[0].upper(), ha='left') + if len(buses) > 1: + ax.plot([XMAX + 5] * 2, [5, y - 5], color='k', lw=7) + ax.text(XMAX - 25, y, buses[1].upper(), ha='right') + ax.set_title(f'{etype}.{ename.upper()} {quantity} ({unit})') + + for bus_idx, vbase in enumerate(vbases): + for cond in range(nconds): + if cond < (nconds - 1): + weight = 'bold' + lw = 2 + else: + weight = 'normal' + lw = 0.6667 + + if bus_idx: + arrow_x = XMAX + 5 + arrow_y = y - (cond + 1) * 10.0 + dx = box_xy1[0] - arrow_x + ax.text(arrow_x - 20, arrow_y + 2, _get_text(), ha='right', fontweight=weight, size=size) + if voltage: + plt.plot([arrow_x, dx + arrow_x], [arrow_y, arrow_y], color='k', lw=lw*1.5) + x = XMAX - 4 * (cond) - 1 + ax.annotate('', xy=(x, arrow_y), xytext=(x, 0), arrowprops=dict(width=0.2, color='lightgray')) + else: + ax.annotate('', xytext=(arrow_x, arrow_y), xy=(dx + arrow_x, arrow_y), arrowprops=dict(width=lw, color='k')) + + else: + arrow_x = -5 + arrow_y = y - (cond + 1) * 10.0 + dx = box_xy0[0] + 5 + ax.text(arrow_x + 20, arrow_y + 2, _get_text(), ha='left', fontweight=weight, size=size) + if voltage: + plt.plot([arrow_x, dx + arrow_x], [arrow_y, arrow_y], color='k', lw=lw*1.5) + x = 4 * (cond) + 1 + ax.annotate('', xy=(x, arrow_y), xytext=(x, 0), arrowprops=dict(width=0.2, color='lightgray')) + else: + ax.annotate('', xytext=(arrow_x, arrow_y), xy=(dx + arrow_x, arrow_y), arrowprops=dict(width=lw, color='k')) + + if quantity == 'Currents': + # Residual + v = -np.sum(values[(nconds * bus_idx):(nconds * (bus_idx + 1))]) + txt = f"{np.abs(v):-.6g} A ∠ {np.angle(v, deg=True):.2f}°" + + if bus_idx: + arrow_x = XMAX + 5 + arrow_y = -10 + dx = box_xy1[0] - arrow_x + ax.text(arrow_x - 5, arrow_y + 2, txt, ha='right', fontweight='normal', size=size) + ax.annotate('', xytext=(arrow_x, arrow_y), xy=(dx + arrow_x, arrow_y), arrowprops=dict(width=1, color='k')) + else: + arrow_x = -5 + arrow_y = -10 + dx = box_xy0[0] + 5 + ax.text(arrow_x + 5, arrow_y + 2, txt, ha='left', fontweight='normal', size=size) + ax.annotate('', xytext=(arrow_x, arrow_y), xy=(dx + arrow_x, arrow_y), arrowprops=dict(width=1, color='k')) + + ax.set_xlim(-20, XMAX + 20) + ax.set_ylim(-15, y + 5) + + + def general_data(self, + *, + PlotType: str = None, + ObjectName: str = None, + ValueIndex: int = None, + Color1: str = None, + Color2: str = None, + Labels: bool = None, + MinScaleIsSpecified: bool = None, + MaxScaleIsSpecified: bool = None, + MinScale: float = None, + MaxScale: float = None, + + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + + if not MaxScaleIsSpecified: + MaxScale = None + + if not MinScaleIsSpecified: + MinScale = None + + is_general = PlotType == 'GeneralData' + ValueIndex = max(1, ValueIndex - 1) + fn = ObjectName + do_labels = Labels + color1 = Color1 + color2 = Color2 + + # Whenever we add Pandas as a dependency, this could be + # rewritten to avoid all the extra/slow work + exp = re.compile('[,=\t]') with open(fn, 'r') as f: - f.readline() # Skip the header - # Get started - initialize Registers 1 - registerVals = [float(x) * 0.001 for x in f.readline().split(',')] - if len(registerVals): - xvalues.append(registerVals[7]) - - if len(xvalues) == 0: - raise RuntimeError('No data to plot') - - for caseYear in range(0, 21): - if meterName.lower() in ('totals', 'systemmeter', 'totals_1', 'systemmeter_1'): - suffix = '' if meterName.endswith('_1') else '_1' - meterName = meterName.lower().replace('totals', 'Totals').replace('systemmeter', 'SystemMeter') - fn = os.path.join(DSS.DataPath, caseName, f'DI_yr_{caseYear}', f'{meterName}{suffix}.csv') - searchForMeterLine = False + line = f.readline().rstrip() + field = exp.split(line)[ValueIndex].strip() #TODO: Is this right?! + f.seek(0) + # Find min and max + names, vals = [], [] + for line in f: + if not line: + continue + + data = exp.split(line) + name, val = data[0], data[ValueIndex] + if len(val): + names.append(name) + vals.append(float(val)) + + vals = np.asarray(vals) + min_val = np.min(vals) + max_val = np.max(vals) + + # Do some sanity checking on the numbers. Don't want to include negative numbers in autoadd plot + if not is_general: + if min_val < 0.0: + min_val = 0.0 + if max_val < 0.0: + max_val = 0.0 + + if MaxScaleIsSpecified: + max_val = MaxScale # Override with user specified value + if MinScaleIsSpecified: + min_val = MinScale # Override with user specified value + + diff = max_val - min_val + if diff == 0.0: + diff = max_val + if diff == 0.0: + diff = 1.0 # Everything is zero + + sidxs = np.argsort(vals) + bus: IBus = DSS.ActiveCircuit.ActiveBus + data = [] + labels = [] + colors = [] + c1 = np.asarray(matplotlib.colors.colorConverter.to_rgb(color1)) + c2 = np.asarray(matplotlib.colors.colorConverter.to_rgb(color2)) + for i in sidxs: + name, val = names[i], vals[i] + if DSS.ActiveCircuit.SetActiveBus(name) <= 0 or not bus.Coorddefined: + continue + + if is_general: + data.append((bus.x, bus.y, val)) + s = ((val - min_val) / diff) + colors.append(c2*s + c1*(1-s)) + # InterpolateGradientColor(Color1, Color2, (GenPlotItem.Value - MinValue) / Diff), + else: # ptAutoAddLogPlot + data.append((bus.x, bus.y, val)) + # GetAutoColor((GenPlotItem.Value - MinValue) / Diff), + + if do_labels: + labels.append(bus.Name) + + data = np.asarray(data) + + + self.circuit(**kwargs) + + #fig = plt.figure(figsize=(8, 7)) + plt.title(f'{field}, Max={max_val:.3g}') + ax = plt.gca() + #if not is3d: + #ax.set_aspect('equal', 'datalim') + + ax.scatter(data[:, 0], data[:, 1], c=colors, zorder=10) + # ax.colorbar() + + #ax.autoscale_view() + #ax.get_xaxis().get_major_formatter().set_scientific(False) + #ax.get_yaxis().get_major_formatter().set_scientific(False) + #fig.set_layout_engine(layout='tight') + + # marker_code = MarkerIdx + + # NodeMarkerWidth: int + # MarkerIdx = NodeMarkerCode + + # marker_code = pmarkers[code_opt] + # marker_size = pmarkers[size_opt] + #marker_dict = get_marker_dict(marker_code) + # ax.plot(*coords, color='red', **marker_dict) + #MarkSpecialClasses + + + def matrix(self, + *, + MatrixType: str = None, + Color1: str = None, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + + # plot_id = kwargs.get('PlotId', None) + if MatrixType == 'IncMatrix': + title = 'Incidence matrix' + data = DSS.ActiveCircuit.Solution.IncMatrix[:-1] else: - fn = os.path.join(DSS.DataPath, caseName, f'DI_yr_{caseYear}', 'EnergyMeterTotals_1.csv') - searchForMeterLine = True + title = 'Laplacian matrix' + data = DSS.ActiveCircuit.Solution.Laplacian[:-1] + + x, y, v = data[0::3], data[1::3], data[2::3] + m = coo.coo_matrix((v, (x, y))) + #fig, [ax, ax2] = plt.subplots(1, 2, figsize=(8.6 * 2, 8.6), constrained_layout=True, num=title) + + if self._include_3d in ('both', '2d'): + fig = plt.figure(constrained_layout=True)#, num=plot_id) #, figsize=(8.6, 8.6)) + ax = fig.add_subplot(1, 1, 1) + ax.grid(True) + ax.spy(m, marker='s', markersize=1, color=Color1) + ax.set_xlabel('Column') + ax.set_ylabel('Row') + ax.set_title(title) + + if self._include_3d in ('both', '3d'): + fig = plt.figure()#figsize=(8.6, 8.6), num=plot_id + '_3D') + ax2 = fig.add_subplot(1, 1, 1, projection='3d') + ax2.scatter(x, y, v, c=v, marker='s') + ax2.set_xlabel('Column') + ax2.set_ylabel('Row') + ax2.set_zlabel('Value') + + def daisy(self, + *, + DaisyBusList: List[str] = None, + Quantity: str = None, + Labels: bool = None, + DaisySize: float = None, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + + self.circuit(**kwargs) + + # print(params['DaisySize']) + + ax = plt.gca() + XMIN, XMAX = ax.get_xlim() + quantity = str_to_pq.get(Quantity, pqNone) + daisy_bus_list = DaisyBusList + do_labels = Labels + daisy_size = DaisySize + + ax.set_title(f'Device Locations / {quantity_str[quantity]}') + element = DSS.ActiveCircuit.ActiveCktElement + + if len(daisy_bus_list) == 0: + for g in DSS.ActiveCircuit.Generators: + if element.Enabled: + daisy_bus_list.append(element.BusNames[0]) + + counts = np.zeros(shape=(DSS.ActiveCircuit.NumBuses + 1,), dtype=np.int32) + for b in daisy_bus_list: + idx = DSS.ActiveCircuit.SetActiveBus(b) + if idx > 0: + counts[idx] += 1 + + radius = 0.005 * daisy_size * (XMAX - XMIN) + lines = [] + pointx, pointy = [], [] + for bidx in np.nonzero(counts)[0]: + bus: IBus = DSS.ActiveCircuit.Buses[int(bidx)] + if not bus.Coorddefined: + continue + + cnt = counts[bidx] + angle0 = 0 + angle = np.pi * 2.0 / cnt + for j in range(cnt): + Xc = bus.x + 2 * radius * np.cos(angle * j + angle0) + Yc = bus.y + 2 * radius * np.sin(angle * j + angle0) + lines.append([(bus.x, bus.y), (Xc, Yc)]) + pointx.append(Xc) + pointy.append(Yc) + + + lc = LineCollection(lines, linewidth=1, colors='r') + ax.add_collection(lc) + ax.scatter(pointx, pointy, marker='o', color='yellow', edgecolors='red', s=100, zorder=10) + + if not do_labels: + return + + for bidx in np.nonzero(counts)[0]: + bus: IBus = DSS.ActiveCircuit.Buses[int(bidx)] + if not bus.Coorddefined: + continue + + ax.text(bus.x, bus.y, bus.Name, zorder=11, fontsize='xx-small', va='center', clip_on=True) + + + def di(self, + *, + CaseName: str = None, + MeterName: str = None, + Registers: List[int] = None, + CaseYear: str = None, + PeakDay: bool = None, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + caseYear, caseName, meterName = CaseYear, CaseName, MeterName + plotRegisters, peakDay = Registers, PeakDay + + fn = os.path.join(DSS.DataPath, caseName, f'DI_yr_{caseYear}', meterName + '.csv') + + if len(plotRegisters) == 0: + raise RuntimeError("No register indices were provided for DI_Plot") if not os.path.exists(fn): - continue + fn = fn[:-4] + '_1.csv' + # Whenever we add Pandas as a dependency, this could be + # rewritten to avoid all the extra/slow work + selected_data = [] + day_data = [] + mult = 1 if peakDay else 0.001 + + # If the file doesn't exist, let the exception raise with open(fn, 'r') as f: - header = f.readline() - if len(registerNames) == 0: - allRegisterNames = [unquote(field) for field in header.strip(' \t,').split(',')] - registerNames.extend(allRegisterNames[i] for i in plotRegisters) + header = f.readline().rstrip() + allRegisterNames = [unquote(field) for field in header.strip().strip(' \t,').split(',')] + registerNames = [allRegisterNames[i] for i in plotRegisters] - if not searchForMeterLine: - line = f.readline() - else: - for line in f: - label, rest = line.split(',', 1) - if label.strip().lower() == meterName.lower(): - line = f'{caseYear},{rest}' + if not len(registerNames): + raise RuntimeError("Could not find any register name in the file") + + for line in f: + if not line: + continue + + rawValues = line.split(',') + selValues = [float(rawValues[0]), *(float(rawValues[i]) for i in plotRegisters)] + if not peakDay: + selected_data.append(selValues) else: - raise RuntimeError("Meter not found") + day_data.append(selValues) + if len(day_data) == 24: + max_vals = [max(x) for x in zip(*day_data)] + max_vals[0] = day_data[0][0] + day_data = [] + selected_data.append(max_vals) + + if day_data: + max_vals = [max(x) for x in zip(*day_data)] + max_vals[0] = day_data[0][0] + day_data = [] + selected_data.append(max_vals) + + vals = np.asarray(selected_data, dtype=float) + fig, ax = plt.subplots(1) + icolor = -1 + for idx, name in enumerate(registerNames, start=1): + icolor += 1 + ax.plot(vals[:, 0], vals[:, idx] * mult, label=name, color=Colors[icolor % len(Colors)]) - registerVals = [float(x) * 0.001 for x in line.strip(' \t,').split(',')] - if len(registerVals): - for yvalues, idx in zip(all_yvalues, plotRegisters): - yvalues.append(registerVals[idx]) - - for yvalues, idx, regName in zip(all_yvalues, plotRegisters, registerNames): - marker_code = MARKER_SEQ[icolor % len(MARKER_SEQ)] - ax.plot(xvalues, yvalues, label=f'{caseName}:{meterName}:{regName}', color=Colors[icolor % len(Colors)], **get_marker_dict(marker_code)) - icolor += 1 + ax.set_title(f'{caseName}, Yr={caseYear}') + ax.set_xlabel('Hour') + ax.set_ylabel('MW, MWh or MVA') + ax.legend() + ax.grid() - return icolor + def _plot_yearly_case(self, caseName: str, meterName: str, plotRegisters: List[int], icolor: int, ax, registerNames: List[str]): + DSS = self.dss + anyData = True + xvalues = [] + all_yvalues = [[] for _ in plotRegisters] + for caseYear in range(0, 21): + fn = os.path.join(DSS.DataPath, caseName, f'DI_yr_{caseYear}', 'Totals_1.csv') + if not os.path.exists(fn): + continue -def dss_yearly_curve_plot(DSS: IDSS, params): - caseNames, meterName, plotRegisters = params['CaseNames'], params['MeterName'], params['Registers'] + with open(fn, 'r') as f: + f.readline() # Skip the header + # Get started - initialize Registers 1 + registerVals = [float(x) * 0.001 for x in f.readline().split(',')] + if len(registerVals): + xvalues.append(registerVals[7]) + + if len(xvalues) == 0: + raise RuntimeError('No data to plot') + + for caseYear in range(0, 21): + if meterName.lower() in ('totals', 'systemmeter', 'totals_1', 'systemmeter_1'): + suffix = '' if meterName.endswith('_1') else '_1' + meterName = meterName.lower().replace('totals', 'Totals').replace('systemmeter', 'SystemMeter') + fn = os.path.join(DSS.DataPath, caseName, f'DI_yr_{caseYear}', f'{meterName}{suffix}.csv') + searchForMeterLine = False + else: + fn = os.path.join(DSS.DataPath, caseName, f'DI_yr_{caseYear}', 'EnergyMeterTotals_1.csv') + searchForMeterLine = True - fig, ax = plt.subplots(1) - icolor = 0 - registerNames = [] - for caseName in caseNames: - icolor = _plot_yearly_case(DSS, caseName, meterName, plotRegisters, icolor, ax, registerNames) + if not os.path.exists(fn): + continue - if icolor == 0: - plt.close(fig) - raise RuntimeError('No files found') - - fig.suptitle(f"Yearly Curves for case(s): {', '.join(caseNames)}") - ax.set_title(f"Meter: {meterName}; Registers: {', '.join(registerNames)}", fontsize='small') - ax.set_xlabel('Total Area MW') - ax.set_ylabel('MW, MWh or MVA') - ax.legend() - ax.grid() - - -def dss_comparecases_plot(DSS: IDSS, params): - print('TODO: dss_comparecases_plot', params) - -def dss_zone_plot(DSS: IDSS, params): - obj_name = params['ObjectName'] - show_loops = params['ShowLoops'] - color1 = params['Color1'] - color3 = params['Color3'] - single_ph_line_style = LINES_STYLE_CODE.get(params.get('SinglePhLineStyle', 1)) - three_ph_line_style = LINES_STYLE_CODE.get(params.get('ThreePhLineStyle', 1)) - dots = params.get('Dots', False) - do_labels = params['Labels'] - quantity = str_to_pq.get(params.get('Quantity', None), pqNone) - max_lw = params.get('MaxLineThickness', 5) + with open(fn, 'r') as f: + header = f.readline() + if len(registerNames) == 0: + allRegisterNames = [unquote(field) for field in header.strip(' \t,').split(',')] + registerNames.extend(allRegisterNames[i] for i in plotRegisters) - try: - quantity_max_value = params.pop('MaxScale') - except: - quantity_max_value = 0 + if not searchForMeterLine: + line = f.readline() + else: + for line in f: + label, rest = line.split(',', 1) + if label.strip().lower() == meterName.lower(): + line = f'{caseYear},{rest}' + else: + raise RuntimeError("Meter not found") + + registerVals = [float(x) * 0.001 for x in line.strip(' \t,').split(',')] + if len(registerVals): + for yvalues, idx in zip(all_yvalues, plotRegisters): + yvalues.append(registerVals[idx]) + + for yvalues, idx, regName in zip(all_yvalues, plotRegisters, registerNames): + marker_code = MARKER_SEQ[icolor % len(MARKER_SEQ)] + ax.plot(xvalues, yvalues, label=f'{caseName}:{meterName}:{regName}', color=Colors[icolor % len(Colors)], **get_marker_dict(marker_code)) + icolor += 1 + return icolor - ActiveCircuit = DSS.ActiveCircuit - if obj_name: - ActiveCircuit.Meters.Name = obj_name - meters = [ActiveCircuit.Meters] - else: - meters = ActiveCircuit.Meters + def yearly_curve(self, *, + MeterName: str = None, + CaseNames: List[str] = None, + Registers: List[str] = None, + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + caseNames, meterName, plotRegisters = CaseNames, MeterName, Registers - elem = ActiveCircuit.ActiveCktElement - line = ActiveCircuit.Lines - topo = ActiveCircuit.Topology + fig, ax = plt.subplots(1) + icolor = 0 + registerNames = [] + for caseName in caseNames: + icolor = self._plot_yearly_case(caseName, MeterName, plotRegisters, icolor, ax, registerNames) + + if icolor == 0: + plt.close(fig) + raise RuntimeError('No files found') + + fig.suptitle(f"Yearly Curves for case(s): {', '.join(caseNames)}") + ax.set_title(f"Meter: {meterName}; Registers: {', '.join(registerNames)}", fontsize='small') + ax.set_xlabel('Total Area MW') + ax.set_ylabel('MW, MWh or MVA') + ax.legend() + ax.grid() - icolor = 0 - #TODO: check if/where we need to transform to lowercase. - bus_coords = dict((b.Name.lower(), (b.x, b.y)) for b in ActiveCircuit.Buses if b.Coorddefined) + def compare_cases(self, **kwargs: Unpack[PlotParams]): + DSS = self.dss + print('TODO: compare_cases', kwargs) + + + def zone(self, + *, + ObjectName: str, + Quantity: DSSPlotQuantity = DEFAULT_PLOT_PARAMS['Quantity'], + ShowLoops: bool = DEFAULT_PLOT_PARAMS['ShowLoops'], + Dots: bool = DEFAULT_PLOT_PARAMS['Dots'], + Labels: bool = DEFAULT_PLOT_PARAMS['Labels'], + Color1: str = DEFAULT_PLOT_PARAMS['Color1'], + Color3: str = DEFAULT_PLOT_PARAMS['Color3'], + SinglePhLineStyle: int = DEFAULT_PLOT_PARAMS['SinglePhLineStyle'], + ThreePhLineStyle: int = DEFAULT_PLOT_PARAMS['ThreePhLineStyle'], + MaxLineThickness: float = DEFAULT_PLOT_PARAMS['MaxLineThickness'], + MaxScale: float = DEFAULT_PLOT_PARAMS['MaxScale'], + **kwargs: Unpack[PlotParams] + ): + DSS = self.dss + obj_name = ObjectName + show_loops = ShowLoops + color1 = Color1 + color3 = Color3 + single_ph_line_style = LINES_STYLE_CODE.get(SinglePhLineStyle) + three_ph_line_style = LINES_STYLE_CODE.get(ThreePhLineStyle) + dots = Dots + do_labels = Labels + quantity = str_to_pq.get(Quantity, pqNone) + max_lw = MaxLineThickness + + if MaxScale is not None: + quantity_max_value = MaxScale + else: + quantity_max_value = 0 - meter_marker_dict = get_marker_dict(24) - meter_marker_dict['markersize'] *= (3 / 3.5)**2 - lines1, lines1_colors, labels1 = [], [], [] - lines3, lines3_colors, labels3 = [], [], [] + ActiveCircuit = DSS.ActiveCircuit - # lw1, lw3 will initially hold the values, later transformed to actual widths - lw1, lw3 = [], [] + if obj_name: + ActiveCircuit.Meters.Name = obj_name + meters = [ActiveCircuit.Meters] + else: + meters = ActiveCircuit.Meters - if quantity in (pqCurrent, pqCapacity): - capacities = dict(zip(DSS.ActiveCircuit.PDElements.AllNames, DSS.ActiveCircuit.PDElements.AllPctNorm(True))) + element = ActiveCircuit.ActiveCktElement + line = ActiveCircuit.Lines + topo = ActiveCircuit.Topology - coords_to_names = {} + icolor = 0 - def _name_coords(c, name): - prev = coords_to_names.get(c) - if prev is None: - coords_to_names[c] = name - return - elif prev == name: - return - - if prev.endswith(',' + name) or prev.startswith(name + ',') or (',' + name + ',') in prev: - return + #TODO: check if/where we need to transform to lowercase. + bus_coords = dict((b.Name.lower(), (b.x, b.y)) for b in ActiveCircuit.Buses if b.Coorddefined) + + meter_marker_dict = get_marker_dict(24) + meter_marker_dict['markersize'] *= (3 / 3.5)**2 + + lines1, lines1_colors, labels1 = [], [], [] + lines3, lines3_colors, labels3 = [], [], [] - coords_to_names[c] = prev + ',' + name + # lw1, lw3 will initially hold the values, later transformed to actual widths + lw1, lw3 = [], [] + if quantity in (pqCurrent, pqCapacity): + capacities = dict(zip(DSS.ActiveCircuit.PDElements.AllNames, DSS.ActiveCircuit.PDElements.AllPctNorm(True))) - def _add_line(element, color): - br_name = element.Name - bus1, bus2 = element.BusNames[:2] - bus1, bus2 = nodot(bus1).lower(), nodot(bus2).lower() - c1 = bus_coords.get(bus1) - c2 = bus_coords.get(bus2) - lw = 1 - if not c1 or not c2: - return None, None + coords_to_names = {} + + element = DSS.ActiveCircuit.ActiveCktElement + + def _name_coords(c, name): + prev = coords_to_names.get(c) + if prev is None: + coords_to_names[c] = name + return + elif prev == name: + return + + if prev.endswith(',' + name) or prev.startswith(name + ',') or (',' + name + ',') in prev: + return - if do_labels: - _name_coords(c1, f'{bus1}({feeder_name})') - _name_coords(c2, f'{bus2}({feeder_name})') + coords_to_names[c] = prev + ',' + name - if quantity == pqPower: - lw = element.TotalPowers[0] - elif quantity == pqVoltage: + + def _add_line(element, color): + br_name = element.Name + bus1, bus2 = element.BusNames[:2] + bus1, bus2 = nodot(bus1).lower(), nodot(bus2).lower() + c1 = bus_coords.get(bus1) + c2 = bus_coords.get(bus2) lw = 1 - elif quantity == pqLosses: - lw = 0 - try: - if element.Name.startswith('Line.'): - lw = 1e-3 * abs(element.Losses[0] / line.Length) - except: - pass - elif quantity in (pqCurrent, pqCapacity): - lw = capacities.get(element.Name, np.NaN) - - if (element.NumPhases == 1): - lines1.append([c1, c2]) - lines1_colors.append(color) - labels1.append(br_name) - lw1.append(lw) - return lines1_colors, len(lines1_colors) - 1 - else: - lines3.append([c1, c2]) - lines3_colors.append(color) - labels3.append(br_name) - lw3.append(lw) - return lines3_colors, len(lines3_colors) - 1 - - - fig, ax = plt.subplots(1) - for meter in meters: - if not elem.Enabled: - continue - - feeder_name = meter.Name - branches = meter.AllBranchesInZone - if not branches: - continue - - # Meter marker - _ = topo.First - coords = bus_coords.get(elem.BusNames[meter.MeteredTerminal - 1]) - if coords: - plt.plot(*coords, color='red', **meter_marker_dict) - - feeder_color = color1 if show_loops else Colors[icolor % len(Colors)] - icolor += 1 - - br_idx = topo.First - while br_idx != 0: - if not elem.Enabled: + if not c1 or not c2: + return None, None + + if do_labels: + _name_coords(c1, f'{bus1}({feeder_name})') + _name_coords(c2, f'{bus2}({feeder_name})') + + if quantity == pqPower: + lw = element.TotalPowers[0] + elif quantity == pqVoltage: + lw = 1 + elif quantity == pqLosses: + lw = 0 + try: + if element.Name.startswith('Line.'): + lw = 1e-3 * abs(element.Losses[0] / line.Length) + except: + pass + elif quantity in (pqCurrent, pqCapacity): + lw = capacities.get(element.Name, np.nan) + + if (element.NumPhases == 1): + lines1.append([c1, c2]) + lines1_colors.append(color) + labels1.append(br_name) + lw1.append(lw) + return lines1_colors, len(lines1_colors) - 1 + else: + lines3.append([c1, c2]) + lines3_colors.append(color) + labels3.append(br_name) + lw3.append(lw) + return lines3_colors, len(lines3_colors) - 1 + + + fig, ax = plt.subplots(1) + for meter in meters: + if not element.Enabled: + continue + + feeder_name = meter.Name + branches = meter.AllBranchesInZone + if not branches: continue + + # Meter marker + _ = topo.First + coords = bus_coords.get(element.BusNames[meter.MeteredTerminal - 1]) + if coords: + plt.plot(*coords, color='red', **meter_marker_dict) + + feeder_color = color1 if show_loops else Colors[icolor % len(Colors)] + icolor += 1 + + br_idx = topo.First + while br_idx != 0: + if not element.Enabled: + continue + + lcs, lidx = _add_line(element, feeder_color) + if show_loops: + looped = (topo.LoopedBranch != 0) + if looped: + # The looped PDE is set as active by LoopedBranch + _add_line(element, color3) + # Adjust the original to color3 + if lidx is not None: + lcs[lidx] = color3 + + br_idx = topo.Next + + + lw1 = np.asarray(lw1) + lw3 = np.asarray(lw3) + + if quantity_max_value == 0: + lw1_max_value = 0 + lw3_max_value = 0 + if len(lw1): + lw1_max_value = np.nanmax(lw1) + if np.isfinite(lw1_max_value): + quantity_max_value = max(quantity_max_value, lw1_max_value) + if len(lw3): + lw3_max_value = np.nanmax(lw3) + if np.isfinite(lw3_max_value): + quantity_max_value = max(quantity_max_value, lw3_max_value) - lcs, lidx = _add_line(elem, feeder_color) - if show_loops: - looped = (topo.LoopedBranch != 0) - if looped: - # The looped PDE is set as active by LoopedBranch - _add_line(elem, color3) - # Adjust the original to color3 - if lidx is not None: - lcs[lidx] = color3 - - br_idx = topo.Next - - - lw1 = np.asarray(lw1) - lw3 = np.asarray(lw3) - - if quantity_max_value == 0: - lw1_max_value = 0 - lw3_max_value = 0 - if len(lw1): - lw1_max_value = np.nanmax(lw1) - if np.isfinite(lw1_max_value): - quantity_max_value = max(quantity_max_value, lw1_max_value) - if len(lw3): - lw3_max_value = np.nanmax(lw3) - if np.isfinite(lw3_max_value): - quantity_max_value = max(quantity_max_value, lw3_max_value) - - if quantity_max_value == 0: - quantity_max_value = 1 - - lw1 = np.clip(3 * lw1 / quantity_max_value, 0.5, max_lw) - lw3 = np.clip(3 * lw3 / quantity_max_value, 0.5, max_lw) - lines1 = np.asarray(lines1) - lines3 = np.asarray(lines3) - lc1 = LineCollection(lines1, linewidth=lw1, colors=lines1_colors, linestyle=single_ph_line_style) - lc3 = LineCollection(lines3, linewidth=lw3, colors=lines3_colors, linestyle=three_ph_line_style) - ax.add_collection(lc1) - ax.add_collection(lc3) - if dots: - for lines, lc in ((lines1, lc1), (lines3, lc3)): - ax.scatter(lines[:, 0, 0].ravel(), lines[:, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=lc, s=9, lw=1) - ax.scatter(lines[:, 1, 0].ravel(), lines[:, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=lc, s=9, lw=1) + if quantity_max_value == 0: + quantity_max_value = 1 + + lw1 = np.clip(3 * lw1 / quantity_max_value, 0.5, max_lw) + lw3 = np.clip(3 * lw3 / quantity_max_value, 0.5, max_lw) + lines1 = np.asarray(lines1) + lines3 = np.asarray(lines3) + lc1 = LineCollection(lines1, linewidth=lw1, colors=lines1_colors, linestyle=single_ph_line_style) + lc3 = LineCollection(lines3, linewidth=lw3, colors=lines3_colors, linestyle=three_ph_line_style) + ax.add_collection(lc1) + ax.add_collection(lc3) + if dots: + for lines, lc in ((lines1, lc1), (lines3, lc3)): + ax.scatter(lines[:, 0, 0].ravel(), lines[:, 0, 1].ravel(), marker='o', facecolors='none', edgecolors=lc, s=9, lw=1) + ax.scatter(lines[:, 1, 0].ravel(), lines[:, 1, 1].ravel(), marker='o', facecolors='none', edgecolors=lc, s=9, lw=1) + + ax.set_title(f'Meter Zone: {obj_name}' if obj_name else 'All Meter Zones') + + for coords, name in coords_to_names.items(): + ax.text(*coords, name, zorder=11, fontsize='xx-small', va='center', clip_on=True) + + ax.set_aspect('equal', 'datalim') + ax.autoscale() + + def enable(self, plot3d: bool = False, plot2d: bool = True, show: bool = True, dss: Optional[IDSS] = None): + """ + Enables the plotting subsystem from DSS-Extensions. + + Set plot3d to `True` to try to reproduce some of the plots from the + alternative OpenDSS Visualization Tool / OpenDSS Viewer addition + to OpenDSS. + + Use `show` to control whether this backend should call `pyplot.show()` + or leave that to the system or the user. If the user plans to customize + the figure, it is better to set `show=False` in order to preserve the + figures, since `pyplot.show()` discards them. + """ + + if dss is not None: + get_plotter(dss).enable(plot3d=plot3d, plot2d=plot2d, show=show) + return + + self._do_show = show + was_enabled = self._enabled + self._enabled = True + + if plot3d and plot2d: + self._include_3d = 'both' + elif plot3d and not plot2d: + self._include_3d = '3d' + elif plot2d and not plot3d: + self._include_3d = '2d' + + dss = self.dss + if not was_enabled: + api_util.lib_unpatched.DSS_RegisterPlotCallback(dss._api_util.ctx, loader_lib.dss_python_cb_plot) + api_util.lib_unpatched.DSS_RegisterMessageCallback(dss._api_util.ctx, loader_lib.dss_python_cb_write) + self._original_allow_forms = dss.AllowForms + + dss.AllowForms = True + + def disable(self, dss: Optional[IDSS] = None): + if dss is not None: + get_plotter(dss).enable(plot3d=plot3d, plot2d=plot2d, show=show) + return - ax.set_title(f'Meter Zone: {obj_name}' if obj_name else 'All Meter Zones') - - for coords, name in coords_to_names.items(): - ax.text(*coords, name, zorder=11, fontsize='xx-small', va='center', clip_on=True) - - ax.set_aspect('equal', 'datalim') - ax.autoscale() - - - -dss_plot_funcs = { - 'Scatter': dss_scatter_plot, - 'Daisy': dss_daisy_plot, - 'TShape': dss_tshape_plot, - 'PriceShape': dss_priceshape_plot, - 'LoadShape': dss_loadshape_plot, - 'Monitor': dss_monitor_plot, - 'Circuit': dss_circuit_plot, - 'Profile': dss_profile_plot, - 'Visualize': dss_visualize_plot, - 'YearlyCurve': dss_yearly_curve_plot, - 'Matrix': dss_matrix_plot, - 'GeneralData': dss_general_data_plot, - 'DI': dss_di_plot, -# 'CompareCases': dss_comparecases_plot, - 'MeterZones': dss_zone_plot + dss = self.dss + self._enabled = False + api_util.lib_unpatched.DSS_RegisterPlotCallback(dss._api_util.ctx, dss._api_util.ffi.NULL) + api_util.lib_unpatched.DSS_RegisterMessageCallback(dss._api_util.ctx, dss._api_util.ffi.NULL) + if self._original_allow_forms is not None: + self.dss.AllowForms = self._original_allow_forms + +DSSPlotter = DSSMPLPlotter + +dss_plot_methods = { + 'Scatter': 'scatter', + 'Daisy': 'daisy', + 'TShape': 'tshape', + 'PriceShape': 'priceshape', + 'LoadShape': 'loadshape', + 'Monitor': 'monitor', + 'Circuit': 'circuit', + 'Profile': 'profile', + 'Visualize': 'visualize', + 'YearlyCurve': 'yearly_curve', + 'Matrix': 'matrix', + 'GeneralData': 'general_data', + 'DI': 'di', +# 'CompareCases': 'compare_cases', + 'MeterZones': 'zone' } -def dss_plot(DSS, params): +def _dss_plot(DSS: IDSS, **kwargs: Unpack[PlotParams]): try: - ptype = params['PlotType'] - if ptype not in dss_plot_funcs: + ptype = kwargs['PlotType'] + if ptype not in dss_plot_methods: raise NotImplementedError(f'ERROR: not implemented plot type "{ptype}"') return -1 - with ToggleAdvancedTypes(DSS, False), warnings.catch_warnings(): + plotter = get_plotter(DSS._api_util.ctx, create=False) + if plotter is None: + # plotter = DSSPlotter(DSS) + return 0 + + with DSS.ActiveCircuit.Settings.Context() as settings, warnings.catch_warnings(): warnings.simplefilter("ignore") - dss_plot_funcs.get(ptype)(DSS, params) + settings.AdvancedTypes = False + settings.PreferLists = False + func = getattr(plotter, dss_plot_methods.get(ptype)) + fig = func(**kwargs) + if plotter._do_show and fig is not None: + plt.show(fig) + + return 0 except Exception as ex: from traceback import format_exc - # print('DSS: Error while plotting. Parameters:', params, file=sys.stderr) + # print('DSS: Error while plotting. Parameters:', kwargs, file=sys.stderr) DSS._errorPtr[0] = 777 DSS._lib.Error_Set_Description(f"Error in the plot backend: {ex}\n{format_exc()}".encode()) return 777 @@ -1867,132 +2644,41 @@ def dss_plot(DSS, params): return 0 -# dss_progress_bar = None -# dss_progress_desc = '' - -@api_util.ffi.def_extern() -def dss_python_cb_write(ctx, message_str, message_type: int, message_size: int, message_subtype: int): - global dss_progress_bar - global dss_progress_desc - - # DSS = _ctx2dss(ctx) - - message_str = api_util.ffi.string(message_str).decode(api_util.codec) - if message_type == api_util.lib.DSSMessageType_Error: - #print('DSS Error:', message_str, file=sys.stderr) - pass - elif message_type in (api_util.lib.DSSMessageType_ProgressCaption, api_util.lib.DSSMessageType_ProgressFormCaption): - #dss_progress_desc = message_str - # print('Progress Caption:', message_str, file=sys.stderr) - pass - elif message_type == api_util.lib.DSSMessageType_Progress: - #print('DSS Progress:', message_str, file=sys.stderr) - pass - elif message_type == api_util.lib.DSSMessageType_FireOffEditor: - link_file(message_str) - # try: - # # print('DSSMessageType_FireOffEditor') - # with open(message_str, 'r') as f: - # text = f.read() - - # IPython.display.display({'text/plain': text}, raw=True) - # except: - # print(f'Could not display file "{message_str}"') - # return 1 - - elif message_type == api_util.lib.DSSMessageType_ProgressPercent: - try: - pass - # n = int(message_str) - # desc = '' - # if n == 0 and dss_progress_bar is not None: - # dss_progress_bar = None - - # if dss_progress_bar is None: - # dss_progress_bar = tqdm(total=100, desc=dss_progress_desc) - - # if n < 0: - # del dss_progress_bar - # dss_progress_bar = None - # return 0 - - - # dss_progress_bar.n = n - # dss_progress_bar.refresh() -# if n == 100: -# dss_progress_bar.close() - except: - import traceback - traceback.print_exc() - print('DSS Progress:', message_str) - - # else: - # # print(message_type) - # # print(message_str) - # IPython.display.display({'text/plain': message_str}, raw=True) - else: - # do nothing for now... - pass - - return 0 - - -@api_util.ffi.def_extern() -def dss_python_cb_plot(ctx, paramsStr): +@api_util.ffi.def_extern(name="dss_python_cb_plot") +def _dss_python_cb_plot(ctx, paramsStr): params = json.loads(api_util.ffi.string(paramsStr)) result = 0 try: DSS = IDSS._get_instance(ctx=ctx) - result = dss_plot(DSS, params) - if _do_show: - plt.show() + result = _dss_plot(DSS, **params) + except: from traceback import print_exc print('DSS: Error while plotting. Parameters:', params, file=sys.stderr) print_exc() return 0 if result is None else result -_original_allow_forms = None -_do_show = True -def enable(plot3d: bool = False, plot2d: bool = True, show: bool = True): +def get_plotter(ctx: IDSS, create=True): """ - Enables the plotting subsystem from DSS-Extensions. - - Set plot3d to `True` to try to reproduce some of the plots from the - alternative OpenDSS Visualization Tool / OpenDSS Viewer addition - to OpenDSS. - - Use `show` to control whether this backend should call `pyplot.show()` - or leave that to the system or the user. If the user plans to customize - the figure, it is better to set `show=False` in order to preserve the - figures, since `pyplot.show()` discards them. + Returns the DSS plotter associated with the context `ctx`, if any. + If none exists and `create=True` (default), as new plotter is created + and returned. """ + if hasattr(ctx, '_api_util'): + ctx = ctx._api_util.ctx - global include_3d - global _original_allow_forms - global _do_show - - _do_show = show - - if plot3d and plot2d: - include_3d = 'both' - elif plot3d and not plot2d: - include_3d = '3d' - elif plot2d and not plot3d: - include_3d = '2d' - - api_util.lib.DSS_RegisterPlotCallback(api_util.lib.dss_python_cb_plot) - api_util.lib.DSS_RegisterMessageCallback(api_util.lib.dss_python_cb_write) - _original_allow_forms = DSSPrime.AllowForms - DSSPrime.AllowForms = True + plotter = DSSPlotter._ctx_to_plotter.get(ctx) + if create and plotter is None: + DSS = IDSS._get_instance(ctx=ctx) + plotter = DSSPlotter(DSS) -def disable(): - api_util.lib.DSS_RegisterPlotCallback(api_util.ffi.NULL) - api_util.lib.DSS_RegisterMessageCallback(api_util.ffi.NULL) - if _original_allow_forms is not None: - DSSPrime.AllowForms = _original_allow_forms + return plotter +plotter = DSSPlotter(dss_nb_ctx) # Main plotter instance (default DSS context) +enable = plotter.enable +disable = plotter.disable +dsv = plotter.dsv -__all__ = ['enable', 'disable'] +__all__ = ['enable', 'disable', 'plotter', 'get_plotter', 'dsv'] diff --git a/pyproject.toml b/pyproject.toml index 6a38eb75..8c458402 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,11 +24,11 @@ packages = ["dss"] name = "dss-python" dynamic = ["version"] dependencies = [ - "dss_python_backend==0.14.6a1", - "numpy>=1.21.0", + "dss_python_backend==0.15.0b4", + "numpy>=2,<3", "typing_extensions>=4.5,<5", ] -requires-python = ">=3.7" +requires-python = ">=3.11" # Following NumPy's requirements authors = [ {name = "Paulo Meira", email = "pmeira@ieee.org"}, {name = "Dheepak Krishnamurthy", email = "me@kdheepak.com"}, @@ -37,20 +37,17 @@ maintainers = [ {name = "Paulo Meira", email = "pmeira@ieee.org"}, ] description = "Python interface (bindings and tools) for OpenDSS. Based on the AltDSS/DSS C-API project, the alternative OpenDSS implementation from DSS-Extensions.org. Multiplatform, API-compatible/drop-in replacement for the COM version of OpenDSS." -readme = "README.md" +readme = {file = "README.md", content-type = "text/markdown"} license = {file = "LICENSE"} keywords = ["opendss", "altdss", "electric power systems", "opendssdirect", "powerflow", "short-circuit", ] classifiers = [ 'Intended Audience :: Science/Research', 'Intended Audience :: Education', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', 'Development Status :: 5 - Production/Stable', 'Topic :: Scientific/Engineering', 'License :: OSI Approved :: BSD License' diff --git a/tests/_settings.py b/tests/_settings.py index f448eec7..659c470b 100644 --- a/tests/_settings.py +++ b/tests/_settings.py @@ -3,13 +3,17 @@ import faulthandler faulthandler.disable() -from dss import DSS, IOddieDSS -faulthandler.enable() +from dss import DSS +# DSS.ActiveCircuit.Settings.COMErrorResults = False +try: + from dss import IOddieDSS +except: + pass org_dir = os.getcwd() -USE_ODDIE = os.getenv('DSS_PYTHON_ODDIE', None) -if USE_ODDIE: +USE_ODDIE = os.getenv('DSS_EXTENSIONS_TEST_ODDIE', None) +if USE_ODDIE is not None and USE_ODDIE.upper() not in ('0', 'FALSE', 'F', 'OFF', 'NO'): # print("Using Oddie:", USE_ODDIE) if USE_ODDIE != '1': DSS = IOddieDSS(USE_ODDIE) @@ -18,6 +22,8 @@ os.chdir(org_dir) +DSS.ClearAll() +faulthandler.enable() WIN32 = (sys.platform == 'win32') if os.path.exists('../../electricdss-tst/'): @@ -44,6 +50,10 @@ #"L!Distrib/IEEETestCases/4wire-Delta/Kersting4wireIndMotor.dss", test_filenames = ''' +Version8/Distrib/Examples/WindGenerator/WindGen_QSTS/Run_IEEE123Bus_GFLDaily.DSS +Version8/Distrib/Examples/WindGenerator/WindGen_GFL_Dynamics/Run_IEEE123Bus_GFLDaily.DSS +Version8/Distrib/Examples/NCIM/Xmission_System_Kundur2Area/Master.dss +Version8/Distrib/IEEETestCases/IEEE118Bus/master_file.dss Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-csv-p.dss Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-csv-pq.dss Version8/Distrib/Examples/MemoryMappingLoadShapes/ckt24/master_ckt24-mm-dbl-p.dss @@ -252,3 +262,29 @@ Version8/Distrib/Examples/CIM/IEEE13_Assets.dss Version8/Distrib/Examples/CIM/IEEE13_CDPSM.dss '''.strip().split('\n') + +json_test_fns = os.environ.get('DSS_EXTENSIONS_TEST_SYSTEMS', '') +if json_test_fns: + import json + with open(json_test_fns, 'r') as f_test_fns: + config = json.load(f_test_fns) + + extra = config.get('extraTestSystems') + replacements = config.get('testSystems') + + if replacements: + test_filenames = replacements + + if extra: + extra.extend(test_filenames) + test_filenames = extra + + cim_replacements = config.get('cimTestSystems') + cim_extra = config.get('cimExtraTestSystems') + if cim_replacements is not None: + cimxml_test_filenames = cim_replacements + + if cim_extra: + cim_extra.extend(cimxml_test_filenames) + cimxml_test_filenames = cim_extra + diff --git a/tests/compare_outputs.py b/tests/compare_outputs.py index 7600644e..df774754 100644 --- a/tests/compare_outputs.py +++ b/tests/compare_outputs.py @@ -30,7 +30,7 @@ class MISSING: ENABLE_JSON = True KNOWN_COM_DIFF = set([ - # On official COM, uninitialized values for CalcCurrent, AllocFactors + # On EPRI's OpenDSS COM, uninitialized values for CalcCurrent, AllocFactors # Note that this could be a bug on the upstream version, but debugging without Delphi gets tricky *[('Version8/Distrib/Examples/DOCTechNote/1_2.dss.json', 'Meters', 'records', x, 'CalcCurrent') for x in range(77)], *[('Version8/Distrib/Examples/DOCTechNote/2_1.dss.json', 'Meters', 'records', x, 'CalcCurrent') for x in range(77)], @@ -198,7 +198,7 @@ def compare(self, a, b, org_path=None): continue # print(path) - va, vb = a.get(k), b.get(k, MISSING) + va, vb = a.get(k), (b or {}).get(k, MISSING) if vb is MISSING: continue @@ -236,6 +236,11 @@ def compare(self, a, b, org_path=None): continue if isinstance(va, list): + if not isinstance(vb, list): + if (path[-4], path[-1]) in (('Relays', 'State'), ('Relays', 'NormalState')): + continue + + if ((va == ['none'] or va == ['NONE']) and vb == []) or (va == [] and (vb == ['none'] or vb == ['NONE'])): continue @@ -293,12 +298,27 @@ def compare(self, a, b, org_path=None): continue + if isinstance(va[0], int): + va = np.asarray(va) + vb = np.asarray(vb) + + if len(vb) != len(va): + self.printe('ERROR (int, vector, shapes):', path, f'a: {len(va)}, b: {len(vb)}') + continue + + if not all(va == vb): + self.printe('ERROR (int. vector):', path, f'a: {va}, b: {vb}') + + continue + + + if isinstance(va[0], float) or va[0] is None: if None in va: - va = [x if x is not None else np.NaN for x in va] + va = [x if x is not None else np.nan for x in va] if None in vb: - vb = [x if x is not None else np.NaN for x in vb] + vb = [x if x is not None else np.nan for x in vb] atol = tol rtol = tol @@ -415,8 +435,8 @@ def compare_all(self): print('Skipping, not converged in A:', fn) continue - self.A_IS_COM = 'C-API' not in dataA['DSS']['Version'] - self.B_IS_COM = 'C-API' not in dataB['DSS']['Version'] + self.A_IS_COM = 'C-API' not in dataA['DSS']['Version'].split('\n')[0] + self.B_IS_COM = 'C-API' not in dataB['DSS']['Version'].split('\n')[0] try: self.compare(dataA, dataB, [fn]) if not self.per_file[fn]: @@ -472,8 +492,10 @@ def compare_all(self): df_a = pd.read_csv(sfA) except pd.errors.EmptyDataError: continue - - df_b = pd.read_csv(sfB) + try: + df_b = pd.read_csv(sfB) + except pd.errors.EmptyDataError: + continue df_a.columns = [x.strip() for x in df_a.columns] df_b.columns = [x.strip() for x in df_b.columns] diff --git a/tests/save_outputs.py b/tests/save_outputs.py index 330c8aaf..1299d555 100644 --- a/tests/save_outputs.py +++ b/tests/save_outputs.py @@ -114,6 +114,9 @@ def run(dss: dss.IDSS, fn: str, line_by_line: bool): ): dss.Text.Command = 'export profile phases=all' + dss.Text.Command = 'CalcIncMatrix' + dss.Text.Command = 'CalcLaplacian' + reliabity_ran = True try: dss.ActiveCircuit.Meters.DoReliabilityCalc(False) @@ -159,11 +162,29 @@ def adjust_to_json(cls, field): def export_dss_api_cls(dss: dss.IDSS, dss_cls): printv(dss_cls) + lname = type(dss_cls).__name__.lower() has_iter = hasattr(type(dss_cls), '__iter__') is_ckt_element = getattr(type(dss_cls), '_is_circuit_element', False) ckt_elem = dss.ActiveCircuit.ActiveCktElement ckt_elem_columns = set(type(ckt_elem)._columns) - ckt_elem_columns_meta - pc_elem_columns - {'Handle', 'IsIsolated', 'HasOCPDevice'} - fields = list(type(dss_cls)._columns) + try: + fields = list(type(dss_cls)._columns) + except: + print(dss_cls, '_columns not found, skipping...') + return + + if lname.endswith('fuses') and IS_V11: + for f in ['CurveMultiplier', 'InterruptingRating']: + if f in fields: + fields.remove(f) + + # if lname.endswith('swtcontrols') and IS_V11: + # for f in ['Action', 'NormalState', 'State', 'RatedCurrent', 'Open', 'Close', ]: + # if f in fields: + # fields.remove(f) + + if lname.endswith('solution'): + fields.extend(['IncMatrix', 'Laplacian', 'IncMatrixCols', 'IncMatrixRows', ]) if 'UserClasses' in fields: fields.remove('UserClasses') @@ -188,7 +209,7 @@ def export_dss_api_cls(dss: dss.IDSS, dss_cls): if 'AllPDEatBus' in fields: fields.remove('AllPDEatBus') # if 'Sensor' in fields: # Both Loads and PVSystems - if 'ipvsystems' in type(dss_cls).__name__.lower(): + if 'ipvsystems' in lname: fields.remove('Sensor') # if 'IrradianceNow' in fields: @@ -216,79 +237,108 @@ def export_dss_api_cls(dss: dss.IDSS, dss_cls): else: items = [dss_cls] - for _ in items: - record = {} - for field in fields: - # printv('>', field) - try: - record[field] = adjust_to_json(dss_cls, field) - except DSSException as e: - # Check for methods not implemented - if 'not implemented' in e.args[1].lower(): - #print(e.args) + try: + if dss_cls.Count == 0: + return + + _ = dss_cls.First + name1 = dss_cls.Name + nxt = dss_cls.Next + name2 = dss_cls.Name + if nxt != 0 and name1 == name2: + print("Replacing iterator for", lname, (name1, name2)) + def iter_cls(): + for i in range(dss_cls.Count): + dss_cls.idx = i + yield dss_cls + + items = iter_cls() + + except: + pass + + if 'loadshapes' in lname: + dss('//!AltDSS PushCompatFlags') + dss('//!AltDSS SetCompatFlag PermissiveProperties') + + try: + for _ in items: + record = {} + for field in fields: + # printv('>', getattr(_, 'Name', '---'), field) + try: + record[field] = adjust_to_json(dss_cls, field) + except DSSException as e: + # Check for methods not implemented + if 'not implemented' in e.args[1].lower(): + #print(e.args) + continue + raise + except StopIteration: + # Some fields are functions, skip those + continue + except AttributeError: + # Depending on the version, a field doesn't exist continue - raise - except StopIteration: - # Some fields are functions, skip those - continue - except AttributeError: - # Depending on the version, a field doesn't exist - continue - if meter_section_fields: - if dss_cls.NumSections > 0: - dss_cls.SetActiveSection(1) - for field in meter_section_fields: + if meter_section_fields: + if dss_cls.NumSections > 0: + dss_cls.SetActiveSection(1) + for field in meter_section_fields: + # printv('>', field) + try: + record[field] = adjust_to_json(dss_cls, field) + except StopIteration: + # Some fields are functions, skip those + continue + + if is_ckt_element: + # also dump the circuit element info + ckt_record = {} + for field in ckt_elem_columns: # printv('>', field) - try: - record[field] = adjust_to_json(dss_cls, field) - except StopIteration: - # Some fields are functions, skip those - continue + ckt_record[field] = adjust_to_json(ckt_elem, field) - if is_ckt_element: - # also dump the circuit element info - ckt_record = {} - for field in ckt_elem_columns: - # printv('>', field) - ckt_record[field] = adjust_to_json(ckt_elem, field) + record['ActiveCktElement'] = ckt_record - record['ActiveCktElement'] = ckt_record + if not has_iter: + # simple record + return record - if not has_iter: - # simple record - return record + # accumulate records + records.append(record) - # accumulate records - records.append(record) + if is_ckt_element and not metadata_record: + if records: + for field in ckt_elem_columns_meta: + # printv('>', field) + metadata_record[field] = adjust_to_json(ckt_elem, field) - if is_ckt_element and not metadata_record: - if records: - for field in ckt_elem_columns_meta: + for field in ckt_iter_columns_meta: # printv('>', field) - metadata_record[field] = adjust_to_json(ckt_elem, field) + try: + metadata_record[field] = adjust_to_json(dss_cls, field) + except DSSException as e: + if 'not implemented' in e.args[1].lower(): + # print(e.args) + continue - for field in ckt_iter_columns_meta: - # printv('>', field) - try: - metadata_record[field] = adjust_to_json(dss_cls, field) - except DSSException as e: - if 'not implemented' in e.args[1].lower(): - # print(e.args) - continue + raise - raise + if 'Meters' in type(dss_cls).__name__: + # This breaks the iteration + extra = {'Totals': adjust_to_json(dss_cls, 'Totals')} - if 'Meters' in type(dss_cls).__name__: - # This breaks the iteration - extra = {'Totals': adjust_to_json(dss_cls, 'Totals')} + # elif has_iter and not metadata_record: + # for field in iter_columns_meta: + # metadata_record[field] = adjust_to_json(dss_cls, field) - # elif has_iter and not metadata_record: - # for field in iter_columns_meta: - # metadata_record[field] = adjust_to_json(dss_cls, field) + finally: + if 'loadshapes' in lname: + dss('//!AltDSS PopCompatFlags') return {'records': records, 'metadata': metadata_record, **extra} @@ -314,26 +364,49 @@ def save_state(dss: dss.IDSS, runtime: float = 0.0) -> str: 'Monitors': dss.ActiveCircuit.Monitors, 'PDElements': dss.ActiveCircuit.PDElements, 'PVSystems': dss.ActiveCircuit.PVSystems, - 'Reclosers': dss.ActiveCircuit.Reclosers, 'RegControls': dss.ActiveCircuit.RegControls, 'Relays': dss.ActiveCircuit.Relays, 'Sensors': dss.ActiveCircuit.Sensors, 'Settings': dss.ActiveCircuit.Settings, 'Solution': dss.ActiveCircuit.Solution, - 'SwtControls': dss.ActiveCircuit.SwtControls, 'Topology': dss.ActiveCircuit.Topology, 'Transformers': dss.ActiveCircuit.Transformers, 'Vsources': dss.ActiveCircuit.Vsources, 'XYCurves': dss.ActiveCircuit.XYCurves, } + if not IS_V11: + dss_classes.update({ + 'Reclosers': dss.ActiveCircuit.Reclosers, + 'SwtControls': dss.ActiveCircuit.SwtControls, + }) + + try: + dss_classes.update({ + 'Storages': dss.ActiveCircuit.Storages, + }) + except AttributeError: + pass + + try: + dss_classes.update({ + 'WindGens': dss.ActiveCircuit.WindGens, + }) + except AttributeError: + pass + + try: + dss_classes.update({ + 'Reactors': dss.ActiveCircuit.Reactors, + }) + except AttributeError: + pass + try: dss_classes.update({ 'CNData': dss.ActiveCircuit.CNData, 'LineGeometries': dss.ActiveCircuit.LineGeometries, 'LineSpacings': dss.ActiveCircuit.LineSpacings, - 'Reactors': dss.ActiveCircuit.Reactors, - 'Storages': dss.ActiveCircuit.Storages, 'TSData': dss.ActiveCircuit.TSData, 'WireData': dss.ActiveCircuit.WireData, }) @@ -371,6 +444,8 @@ def get_archive_fn(live_fn, fn_prefix=None): return archive_fn if __name__ == '__main__': + IS_V11 = False + if os.path.exists('../../electricdss-tst/'): ROOT_DIR = os.path.abspath('../../electricdss-tst/') else: @@ -392,29 +467,28 @@ def get_archive_fn(live_fn, fn_prefix=None): from _settings import DSS oddd_ver = DSS.Version.split(' ')[1] - print("Using official OpenDSS through ODDIE:", DSS.Version) + print("Using EPRI's OpenDSS (or API-compatible) through Oddie:", DSS.Version) if USE_ODDIE != '1': print("User-provided library path:", USE_ODDIE) debug_suffix = '-debug' if 'debug' in DSS.Version.lower() else '' suffix = f'-dssx_oddd-{sys.platform}-{platform.machine()}-{oddd_ver}{debug_suffix}' - #test_idx = test_filenames.index('L!Version8/Distrib/IEEETestCases/123Bus/RevRegTest.dss') + 50 - # test_filenames = [fn for fn in test_filenames if 'DOCTechNote' not in fn] # DOC not implemented - # test_filenames = ['L!Version8/Distrib/IEEETestCases/123Bus/Run_YearlySim.dss'] - # test_filenames = ['L!Version8/Distrib/IEEETestCases/123Bus/SolarRamp.DSS'] - cimxml_test_filenames = [] # Cannot run these now DSS.AllowForms = False elif SAVE_DSSX_OUTPUT: from dss import DSS, DSSCompatFlags - DSS.CompatFlags = 0 # DSSCompatFlags.InvControl9611 + extrasuffix = '' + if DSS.ActiveCircuit.Settings.COMErrorResults: + extrasuffix += '_CER' + + DSS.ActiveCircuit.Settings.CompatFlags = 0 # DSSCompatFlags.InvControl9611 print("Using DSS-Extensions:", DSS.Version) match = re.match('DSS C-API Library version ([^ ]+) revision.* ([0-9]+);.*', DSS.Version) dssx_ver, dssx_timestamp = match.groups() if (DSSCompatFlags.InvControl9611 & DSS.CompatFlags): - suffix = f'-dssx_InvControl9611-{sys.platform}-{platform.machine()}-{dssx_ver}-{dssx_timestamp}' - else: - suffix = f'-dssx-{sys.platform}-{platform.machine()}-{dssx_ver}-{dssx_timestamp}' + extrasuffix += '_InvControl9611' + + suffix = f'-dssx{extrasuffix}-{sys.platform}-{platform.machine()}-{dssx_ver}-{dssx_timestamp}' DSS.AllowEditor = False else: @@ -422,10 +496,11 @@ def get_archive_fn(live_fn, fn_prefix=None): DSS = comtypes.client.CreateObject("OpenDSSEngine.DSS") DSS = dss.patch_dss_com(DSS) #DSS.Text.Command = r'set editor=ignore_me_invalid_executable' -- need to let it open for some reports :| - print("Using official OpenDSS COM:", DSS.Version) + print("Using EPRI's OpenDSS COM engine:", DSS.Version) com_ver = DSS.Version.split(' ')[1] suffix = f'-COM-{platform.machine()}-{com_ver}' + IS_V11 = hasattr(DSS.ActiveCircuit.Fuses, 'InterruptingRating') DSS.AllowForms = False try: @@ -436,7 +511,16 @@ def get_archive_fn(live_fn, fn_prefix=None): else: DSS.Text.Command = r'set Editor="C:\Program Files\Git\usr\bin\true.exe"' - DSS.Text.Command = 'set ShowExport=NO' + try: + DSS.Text.Command = 'set ShowExport=NO' + except: + pass + + try: + DSS.Text.Command = 'set ShowReports=NO' + except: + pass + check_error() sleep(0.1) DSS.Text.Command = 'clear' @@ -448,7 +532,7 @@ def get_archive_fn(live_fn, fn_prefix=None): total_runtime = 0.0 zip_fn = f'results{suffix}.zip' with ZipFile(os.path.join(original_working_dir, zip_fn), mode='a', compression=ZIP_DEFLATED) as zip_out: - for fn in test_filenames + cimxml_test_filenames: + for fn in cimxml_test_filenames + test_filenames: if not fn.strip(): break @@ -456,7 +540,7 @@ def get_archive_fn(live_fn, fn_prefix=None): org_fn = fn fixed_fn = fn if not fn.startswith('L!') else fn[2:] line_by_line = fn.startswith('L!') - fn = os.path.join(ROOT_DIR, fixed_fn) + fn = os.path.join(ROOT_DIR, fixed_fn) if not fixed_fn.startswith('/') else fixed_fn json_fn = get_archive_fn(fn) + '.json' try: zip_out.getinfo(json_fn) @@ -478,8 +562,11 @@ def get_archive_fn(live_fn, fn_prefix=None): exit() except OSError: traceback.print_exc() + print('Last file was:') + print(fn) exit() except: + print('=' * 60) print('ERROR:', fn) if colorizer: colorizer.colorize_traceback(*sys.exc_info()) @@ -490,7 +577,7 @@ def get_archive_fn(live_fn, fn_prefix=None): if org_fn in cimxml_test_filenames: DSS.Text.Command = 'export cim100' - xml_live_fns = [DSS.Text.Result] + xml_live_fns = [DSS.Text.Result.strip()] DSS.Text.Command = 'export cim100fragments' xml_live_fns.extend(glob(DSS.Text.Result + '_*.xml')) for xml_live_fn in xml_live_fns: diff --git a/tests/test_ctrlqueue.py b/tests/test_ctrlqueue.py index b8a0a6fb..451640cc 100644 --- a/tests/test_ctrlqueue.py +++ b/tests/test_ctrlqueue.py @@ -80,6 +80,7 @@ def test_ctrlqueue(): # until all cap steps are on (no more available) i = 0 + v_step_up = [ 119.26520933058164, 118.53391703558978, @@ -89,6 +90,16 @@ def test_ctrlqueue(): 119.1133497058388, 118.25980207026679, ] + # # Values below were updated for OpenDSS 10.1, which included the change to reset YPrimInvalid to false + # v_step_up = [ + # 119.26520933058164, + # 118.53391703558978, + # 119.00162278912609, + # 118.22495566279100, + # 118.66307559565404, + # 119.11533205526253, + # 118.26023037859353, + # ] while DSSCapacitors.AvailableSteps > 0: print('DSSCapacitors.AvailableSteps', DSSCapacitors.AvailableSteps) i = i + 1 @@ -127,7 +138,7 @@ def test_ctrlqueue(): # Print result print("Capacitor", DSSCapacitors.Name, "States =", tuple(DSSCapacitors.States)) - + v_step_down = [ 121.8764097324052, 121.1919475267437, @@ -138,6 +149,17 @@ def test_ctrlqueue(): 121.24330745091815, 120.41556666716592, ] + # # Values below were updated for OpenDSS 10.1, which included the change to reset YPrimInvalid to false + # v_step_down = [ + # 121.87640973217214, + # 121.19194698692986, + # 121.72771816928677, + # 121.00166364015094, + # 121.51952384526496, + # 120.74621507897345, + # 121.24285846717471, + # 120.41741114839155, + # ] # Now let's reverse Direction and start removing steps while DSSCapacitors.AvailableSteps < DSSCapacitors.NumSteps: diff --git a/tests/test_events.py b/tests/test_events.py index ce8f2eb2..3eb8be4b 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -1,10 +1,10 @@ import pytest -from dss import DSS, DSSException +from dss import DSSException try: - from ._settings import BASE_DIR + from ._settings import BASE_DIR, DSS except ImportError: - from _settings import BASE_DIR + from _settings import BASE_DIR, DSS class EventHandler: # Note: for real usage, prefer to generate local classes bound to some @@ -23,6 +23,10 @@ def OnCheckControls(self): def test_events_style_win32com(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the DSSEvents interface through the DirectDLL API.") + return + EventHandler.event_sequence.clear() evt_conn = DSS.Events.WithEvents(EventHandler) @@ -38,6 +42,10 @@ def test_events_style_win32com(): def test_events_style_comtypes(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the DSSEvents interface through the DirectDLL API.") + return + EventHandler.event_sequence.clear() evt_conn = DSS.Events.GetEvents(EventHandler()) diff --git a/tests/test_general.py b/tests/test_general.py index 1c84b80b..f5921c3b 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -15,24 +15,29 @@ import dss -from dss import IDSS, DSSException, SparseSolverOptions, SolveModes, set_case_insensitive_attributes, DSSCompatFlags, LoadModels, DSSPropertyNameStyle, IOddieDSS +from dss import IDSS, DSSException, SparseSolverOptions, SolveModes, set_case_insensitive_attributes, DSSCompatFlags, LoadModels, DSSPropertyNameStyle org_dir = os.getcwd() def setup_function(): DSS.ClearAll() - if not DSS._api_util._is_odd: + DSS.AllowForms = False + DSS.ActiveCircuit.Settings.AdvancedTypes = False + + if not DSS.is_oddie(): + DSS.ActiveCircuit.Settings.CompatFlags = 0 DSS.AllowEditor = False - DSS.AdvancedTypes = False DSS.AllowChangeDir = True - DSS.COMErrorResults = True # TODO: change to False - DSS.CompatFlags = 0 + DSS.ActiveCircuit.Settings.COMErrorResults = False - DSS.AllowForms = False DSS.Error.UseExceptions = True DSS.Text.Command = 'set DefaultBaseFreq=60' def test_zip_redirect(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the DSS-Extensions ZIP interface.") + return + with pytest.raises(DSSException): DSS.ZIP.Redirect('13Bus/IEEE13Nodeckt.dss') @@ -46,6 +51,10 @@ def test_zip_redirect(): def test_zip_contains(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the DSS-Extensions ZIP interface.") + return + with pytest.raises(DSSException): assert 'before open' in DSS.ZIP @@ -56,10 +65,18 @@ def test_zip_contains(): def test_zip_exists(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the DSS-Extensions ZIP interface.") + return + with pytest.raises(DSSException): DSS.ZIP.Open('something1/something2/something3.zip') def test_zip_filelist(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the DSS-Extensions ZIP interface.") + return + DSS.ZIP.Open(ZIP_FN) assert set(DSS.ZIP.List()) == {'13Bus/', '13Bus/IEEE13Node_BusXY.csv', '13Bus/IEEE13Nodeckt.dss', '13Bus/IEEELineCodes.DSS', '13Bus/README.txt'} assert DSS.ZIP.List('.*/RE.*') == ['13Bus/README.txt'] @@ -71,14 +88,15 @@ def test_zipv(): DSS.Text.Command = 'new load.test_load' load = DSS.ActiveCircuit.Loads load.First - - with pytest.raises(DSSException): - # Too few elements - load.ZIPV = [1, 2, 3, 4] - with pytest.raises(DSSException): - # Too many elements - load.ZIPV = [1, 2, 3, 4, 5, 6, 7, 8] + if not DSS.is_oddie(): + with pytest.raises(DSSException): + # Too few elements + load.ZIPV = [1, 2, 3, 4] + + with pytest.raises(DSSException): + # Too many elements + load.ZIPV = [1, 2, 3, 4, 5, 6, 7, 8] load.ZIPV = [1, 0, 0, 1, 0, 0, 0.6] assert list(load.ZIPV) == [1, 0, 0, 1, 0, 0, 0.6] @@ -98,20 +116,28 @@ def _run_mode(mode): def test_sparse_options(): - expected = _run_mode(SparseSolverOptions.ReuseNothing) - for mode in [ - SparseSolverOptions.AlwaysResetYPrimInvalid, - SparseSolverOptions.ReuseCompressedMatrix | SparseSolverOptions.AlwaysResetYPrimInvalid, - SparseSolverOptions.ReuseCompressedMatrix, - SparseSolverOptions.ReuseSymbolicFactorization | SparseSolverOptions.AlwaysResetYPrimInvalid, - SparseSolverOptions.ReuseSymbolicFactorization, - SparseSolverOptions.ReuseNumericFactorization | SparseSolverOptions.AlwaysResetYPrimInvalid, - SparseSolverOptions.ReuseNumericFactorization, - ]: - np.testing.assert_allclose(expected, _run_mode(mode)) + try: + expected = _run_mode(SparseSolverOptions.ReuseNothing) + for mode in [ + SparseSolverOptions.AlwaysResetYPrimInvalid, + SparseSolverOptions.ReuseCompressedMatrix | SparseSolverOptions.AlwaysResetYPrimInvalid, + SparseSolverOptions.ReuseCompressedMatrix, + SparseSolverOptions.ReuseSymbolicFactorization | SparseSolverOptions.AlwaysResetYPrimInvalid, + SparseSolverOptions.ReuseSymbolicFactorization, + SparseSolverOptions.ReuseNumericFactorization | SparseSolverOptions.AlwaysResetYPrimInvalid, + SparseSolverOptions.ReuseNumericFactorization, + ]: + np.testing.assert_allclose(expected, _run_mode(mode)) + except DSSException as ex: + if ex.args[0] == 42: #TODO: enum for Oddie errors + pytest.skip("This DSS engine does not seem to implement SolverOptions.") def test_pd_extras(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the DSS-Extensions PD extras.") + return + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' DSS.ActiveCircuit.Solution.Solve() @@ -170,6 +196,10 @@ def test_case_check(): def test_basic_ctx(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the AltDSS Context API and user-managed threads.") + return + prime_engine = DSS prime_engine.AllowChangeDir = False prime_engine.Text.Command = 'new circuit.test_prime' @@ -185,19 +215,26 @@ def test_basic_ctx(): def test_compat_precision(): + if DSS.is_oddie(): + pytest.skip("Precision compatibility only available in the AltDSS engine.") + return + DSS.ZIP.Open(ZIP_FN) DSS.ZIP.Redirect('13Bus/IEEE13Nodeckt.dss') DSS.ZIP.Close() DSS.ActiveCircuit.Vsources.First good = DSS.ActiveCircuit.ActiveCktElement.SeqVoltages.view(dtype=complex) - DSS.CompatFlags = DSSCompatFlags.BadPrecision + DSS.ActiveCircuit.Settings.CompatFlags = DSSCompatFlags.BadPrecision bad = DSS.ActiveCircuit.ActiveCktElement.SeqVoltages.view(dtype=complex) assert max(abs(good - bad)) > 1e-6 def test_compat_activeline(): DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + if DSS.is_oddie(): + pytest.skip("Test not required EPRI's OpenDSS and OpenDSS-C; different general behavior and error-handling expected.") + return Lines = DSS.ActiveCircuit.Lines Lines.First @@ -206,10 +243,9 @@ def test_compat_activeline(): name = Lines.Name DSS.ActiveCircuit.Loads.First - - assert name == Lines.Name - DSS.CompatFlags = DSSCompatFlags.ActiveLine + assert name == Lines.Name + DSS.ActiveCircuit.Settings.CompatFlags = DSSCompatFlags.ActiveLine with pytest.raises(DSSException): assert name == Lines.Name @@ -267,15 +303,18 @@ def test_set_mode(): def test_pm_threads(): - if not isinstance(DSS, IOddieDSS): + if DSS.is_oddie(): + pytest.skip("Disabled with EPRI's engines until we investigate more; getting some crashes with PM.") + return + + if not DSS.is_oddie(): DSS.AllowChangeDir = False Parallel = DSS.ActiveCircuit.Parallel if Parallel.NumCPUs < 4: return # Cannot run in this machine, e.g. won't run on GitHub Actions - if not isinstance(DSS, IOddieDSS): - DSS.AdvancedTypes = True + DSS.ActiveCircuit.Settings.AdvancedTypes = True DSS.Text.Command = 'set parallel=No' fn = os.path.abspath(f'{BASE_DIR}/Version8/Distrib/EPRITestCircuits/ckt5/Master_ckt5.dss') @@ -288,23 +327,26 @@ def test_pm_threads(): # Let's run 4 days in 4 actors Parallel.ActiveParallel = 1 DSS.Text.Command = 'set activeActor=*' - DSS.Text.Command = 'set mode=yearly number=144 hour=0 controlmode=off stepsize=600' + DSS.Text.Command = 'set mode=yearly number=432 hour=0 controlmode=off stepsize=600' DSS.Text.Command = 'set activeActor=1' DSS.Text.Command = 'set hour=0' DSS.Text.Command = 'set activeActor=2' - DSS.Text.Command = 'set hour=24' + DSS.Text.Command = 'set hour=72' DSS.Text.Command = 'set activeActor=3' - DSS.Text.Command = 'set hour=48' + DSS.Text.Command = 'set hour=144' DSS.Text.Command = 'set activeActor=4' - DSS.Text.Command = 'set hour=72' + DSS.Text.Command = 'set hour=216' DSS.ActiveCircuit.Solution.SolveAll() - DSS.Text.Command = 'wait' - + Parallel.Wait() + + # DSS.Text.Command = 'solve all' + # DSS.Text.Command = 'wait' + assert tuple(Parallel.ActorStatus) == (1, 1, 1, 1) assert tuple(Parallel.ActorProgress) == (100, 100, 100, 100) t1 = perf_counter() @@ -328,7 +370,7 @@ def test_pm_threads(): t0 = perf_counter() DSS.Text.Command = f'compile "{fn}"' DSS.ActiveCircuit.Solution.Solve() - DSS.Text.Command = 'set mode=yearly number=144 hour=0 controlmode=off stepsize=600' + DSS.Text.Command = 'set mode=yearly number=432 hour=0 controlmode=off stepsize=600' DSS.ActiveCircuit.Solution.Solve() v_seq.append(DSS.ActiveCircuit.AllBusVolts) @@ -350,14 +392,14 @@ def test_pm_threads(): assert max(abs(v_pm[3] - v_pm[0])) > 1e-1 assert dt_pm < dt_seq - if not isinstance(DSS, IOddieDSS): + if not DSS.is_oddie(): # Let's run with threads, using DSSContexts too v_ctx = [None] * 4 def _run(ctx, i): ctx.Text.Command = f'compile "{fn}"' ctx.ActiveCircuit.Solution.Solve() - ctx.Text.Command = f'set mode=yearly number=144 hour={i * 24} controlmode=off stepsize=600' + ctx.Text.Command = f'set mode=yearly number=432 hour={i * 24 * 3} controlmode=off stepsize=600' ctx.ActiveCircuit.Solution.Solve() v_ctx[i] = ctx.ActiveCircuit.AllBusVolts @@ -376,19 +418,25 @@ def _run(ctx, i): t1 = perf_counter() dt_ctx = t1 - t0 - else: - dt_ctx = np.NaN - np.testing.assert_allclose(v_ctx[0], v_seq[0]) - np.testing.assert_allclose(v_ctx[1], v_seq[1]) - np.testing.assert_allclose(v_ctx[2], v_seq[2]) - np.testing.assert_allclose(v_ctx[3], v_seq[3]) - + np.testing.assert_allclose(v_ctx[0], v_seq[0]) + np.testing.assert_allclose(v_ctx[1], v_seq[1]) + np.testing.assert_allclose(v_ctx[2], v_seq[2]) + np.testing.assert_allclose(v_ctx[3], v_seq[3]) + else: + dt_ctx = np.nan print(f"PM: {dt_pm:.3g} s; Python threads: {dt_ctx:.3g} s; Sequential: {dt_seq:.3g} s") def test_threading2(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support the AltDSS Context API and user-managed threads.") + return + DSS.AllowChangeDir = False + + # EPRITestCircuits/epri_dpv/M1 has loads with zero power + DSS.ActiveCircuit.Settings.CompatFlags |= DSSCompatFlags.PermissiveProperties fns = [ f"{BASE_DIR}/Version8/Distrib/EPRITestCircuits/epri_dpv/M1/Master_NoPV.dss", @@ -760,7 +808,7 @@ def test_capacitor_reactor(DSS: IDSS = DSS): from itertools import product kVA = 1329.53 kV = 2.222 - DSS.AdvancedTypes = True + DSS.ActiveCircuit.Settings.AdvancedTypes = True for component, f in product(('Capacitor', 'Reactor'), (50, 60)): bus = 1 @@ -768,9 +816,9 @@ def test_capacitor_reactor(DSS: IDSS = DSS): DSS.Text.Command = 'clear' debug_print('clear') DSS.Text.Command = f'set DefaultBaseFreq={f}' - debug_print(DSS.Text.Command) + # debug_print(DSS.Text.Command) DSS.Text.Command = f'new circuit.test{f} bus1={bus}' - debug_print(DSS.Text.Command) + # debug_print(DSS.Text.Command) for conn in ('delta', 'wye'): for phases0 in (1, 2, 3): phases = phases0 @@ -785,10 +833,10 @@ def test_capacitor_reactor(DSS: IDSS = DSS): kV_eff = kV * sqrt(3) DSS.Text.Command = f'new Line.{bus}-{bus + 1} bus1={bus} bus2={bus + 1}' - debug_print(DSS.Text.Command) + # debug_print(DSS.Text.Command) bus += 1 DSS.Text.Command = f'new {component}.{conn}_{phases} bus1={bus} phases={phases} conn={conn} kva={kVA} kV={kV_eff}' - debug_print(DSS.Text.Command) + # debug_print(DSS.Text.Command) DSS.Text.Command = 'solve' # debug_print(DSS.Text.Command) assert DSS.ActiveCircuit.ActiveCktElement.Name == f'{component}.{conn}_{phases}' @@ -801,7 +849,8 @@ def test_capacitor_reactor(DSS: IDSS = DSS): assert DSS.ActiveCircuit.ActiveCktElement.Name == f'{component}.{conn}_{phases}_alt' Y_dss2 = DSS.ActiveCircuit.ActiveCktElement.Yprim - np.testing.assert_allclose(Y_dss, Y_dss2) + if not DSS.is_oddie(): + np.testing.assert_allclose(Y_dss, Y_dss2) if conn == 'wye': VA_branch = 1000 * kVA / phases @@ -840,10 +889,10 @@ def test_capacitor_reactor(DSS: IDSS = DSS): phases = phases0 model = int(LoadModels.ConstZ) DSS.Text.Command = f'new Line.{bus}-{bus + 1} bus1={bus} bus2={bus + 1}' - debug_print(DSS.Text.Command) + # debug_print(DSS.Text.Command) bus += 1 DSS.Text.Command = f'new Load.{conn}_{phases} bus1={bus} phases={phases} conn={conn} kw=0 kvar={-sign * kVA} kV={kV_eff} model={model} Xneut=0 Rneut=0' - debug_print(DSS.Text.Command) + # debug_print(DSS.Text.Command) DSS.Text.Command = 'solve' # debug_print(DSS.Text.Command) assert DSS.ActiveCircuit.ActiveCktElement.Name == f'Load.{conn}_{phases}' @@ -864,18 +913,49 @@ def test_capacitor_reactor(DSS: IDSS = DSS): def test_patch_comtypes(): if WIN32: - import comtypes.client + if DSS.is_oddie(): + pytest.skip("Skipping COM test; OpenDSSDirect.DLL already loaded.") + return + + try: + import comtypes.client + except: + pytest.skip("Skipping COM test; comtypes is not installed") + return + DSS_COM = dss.patch_dss_com(comtypes.client.CreateObject("OpenDSSengine.DSS")) test_essentials(DSS_COM) + else: + if DSS.is_oddie(): + pytest.skip("Skipping COM test (requires Windows).") + return + def test_patch_win32com(): if WIN32: - import win32com.client + if DSS.is_oddie(): + pytest.skip("Skipping COM test; OpenDSSDirect.DLL already loaded.") + return + + try: + import win32com.client + except: + pytest.skip("Skipping COM test; win32com is not installed") + return + win32com.client.Dispatch("OpenDSSengine.DSS") DSS_COM = dss.patch_dss_com(win32com.client.gencache.EnsureDispatch("OpenDSSengine.DSS")) test_essentials(DSS_COM) + else: + if DSS.is_oddie(): + pytest.skip("Skipping COM test (requires Windows).") + return def test_namingstyle(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support SetPropertyNameStyle.") + return + DSS.ClearAll() DSS('new circuit.test') DSS.ActiveCircuit.Vsources.First @@ -914,35 +994,263 @@ def test_loadshape_save(): npt.assert_allclose(pmult, pmult_sng) +def test_loadshape_extended(): + + # Added for OpenDSSC + + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + LS = DSS.ActiveCircuit.LoadShapes + LS.Name = "default" + assert LS.Npts == 24 + ref_p = np.asarray([.677, .6256, .6087, .5833, .58028, .6025, .657, .7477, .832, .88, .94, .989, .985, .98, .9898, .999, 1, .958, .936, .913, .876, .876, .828, .756]) + npt.assert_allclose(LS.Pmult, ref_p) + ref_t = list(range(len(ref_p))) + LS.TimeArray = list(ref_t) + npt.assert_allclose(LS.TimeArray, ref_t) + ref_q = LS.TimeArray + ref_p + LS.Qmult = list(ref_q) + npt.assert_allclose(LS.Qmult, ref_q) + + DSS.Text.Command = 'new loadshape.test npts=3 pmult=[1.1, 2.2, 3.3] qmult=[4.5, 4.6, 4.7] hour=[1, 2, 7]' + LS.Name = 'test' + assert LS.Npts == 3 + npt.assert_allclose(LS.Pmult, [1.1, 2.2, 3.3]) + npt.assert_allclose(LS.Qmult, [4.5, 4.6, 4.7]) + npt.assert_allclose(LS.TimeArray, [1, 2, 7]) + LS.Pmult = list(np.asarray(LS.Pmult) * 2) + npt.assert_allclose(LS.Pmult, [2.2, 4.4, 6.6]) + LS.Qmult = list(np.asarray(LS.Qmult) / 2.5) + npt.assert_allclose(np.asarray(LS.Qmult) * 2.5, [4.5, 4.6, 4.7]) + LS.TimeArray = list(np.asarray(LS.TimeArray) * 12) + npt.assert_allclose(np.asarray(LS.TimeArray) / 12, [1, 2, 7]) + + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + DSS.Text.Command = 'new loadshape.test npts=3 pmult=[1.1, 2.2, 3.3] qmult=[4.5, 4.6, 4.7] hour=[1, 2, 7]' + DSS.Text.Command = 'BatchEdit Load..* Daily=test' + DSS.ActiveCircuit.Solution.Mode = SolveModes.Daily + DSS.ActiveCircuit.Solution.StepSize = 3600 + DSS.ActiveCircuit.Solution.Number = 1 + + avg_results_ref = [-3850.602050692013, -6933.281474723415, -7515.891810916165, -8086.495438035473, -8645.055975533884, -9190.98268197323, -9725.069704090525, -3850.634926011579] + avg_results = [] + for _ in range(8): + DSS.ActiveCircuit.Solution.Solve() + avg_results.append(DSS.ActiveCircuit.TotalPower[0]) + + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + DSS.Text.Command = 'new loadshape.test npts=3 pmult=[1.1, 2.2, 3.3] qmult=[4.5, 4.6, 4.7] hour=[1, 2, 7] interpolation=edge' + DSS.Text.Command = 'BatchEdit Load..* Daily=test' + DSS.ActiveCircuit.Solution.Mode = SolveModes.Daily + DSS.ActiveCircuit.Solution.StepSize = 3600 + DSS.ActiveCircuit.Solution.Number = 1 + + edge_results_ref = [-3850.602050692013, -6933.281474723415, -6933.300378973458, -6933.299600116585, -6933.2996080286175, -6933.29960833009, -9724.825136610381, -3850.6349396192672] + edge_results = [] + for _ in range(8): + DSS.ActiveCircuit.Solution.Solve() + edge_results.append(DSS.ActiveCircuit.TotalPower[0]) + + npt.assert_allclose(avg_results, avg_results_ref) + npt.assert_allclose(edge_results, edge_results_ref) + + +def test_xycurve_extended(): + # Added for OpenDSSC + + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + LS = DSS.ActiveCircuit.XYCurves + + DSS.Text.Command = 'New XYCurve.test npts=4 xarray=[.1 .2 .4 1.0] yarray=[.86 .9 .93 .97]' + LS.Name = "test" + assert LS.Npts == 4 + ref_x = np.asarray([.1, .2, .4, 1.0]) + ref_y = np.asarray([.86, .9, .93, .97]) + npt.assert_allclose(LS.Xarray, ref_x) + npt.assert_allclose(LS.Yarray, ref_y) + LS.Xarray *= 2 + npt.assert_allclose(LS.Xarray, ref_x * 2) + LS.Yarray /= 2.5 + npt.assert_allclose(LS.Yarray, ref_y / 2.5) + + def test_line_parent_compat(): - from dss import DSSCompatFlags + from dss import DSSCompatFlags, DSSException DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' DSS.Text.Command = 'new energymeter.m1 element=transformer.sub' DSS.Text.Command = 'solve mode=snap' - DSS.CompatFlags = DSSCompatFlags.ActiveLine Lines = DSS.ActiveCircuit.Lines + no_compat_expected = (1, 2, '632670', 3, '670671', 2, '632670', 1, '650632') + + if not DSS.is_oddie(): + DSS.ActiveCircuit.Settings.CompatFlags = DSSCompatFlags.ActiveLine + res_compat = Lines.First, Lines.Next, Lines.Name, Lines.Next, Lines.Name, Lines.Parent, Lines.Name, Lines.Parent, Lines.Name - DSS.CompatFlags = 0 - res_no_compat = Lines.First, Lines.Next, Lines.Name, Lines.Next, Lines.Name, Lines.Parent, Lines.Name, Lines.Parent, Lines.Name + + if not DSS.is_oddie(): + DSS.ActiveCircuit.Settings.CompatFlags = 0 + res_no_compat = Lines.First, Lines.Next, Lines.Name, Lines.Next, Lines.Name, Lines.Parent, Lines.Name, Lines.Parent, Lines.Name - assert res_no_compat == (1, 2, '632670', 3, '670671', 2, '632670', 1, '650632') + if not DSS.is_oddie(): + assert res_no_compat == no_compat_expected + else: + res_no_compat = no_compat_expected # The indices returned in compat mode are "wrong", to match the official DSS implementation assert res_compat[3:2:] == res_no_compat[3:2:] +def test_skip_commands(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support SkipCommands.") + return + + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + DSS.ActiveCircuit.Settings.SkipCommands = ['clear'] + # Since we are skipping the clear command, an exception should be raised + with pytest.raises(DSSException): + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + + DSS.ActiveCircuit.Settings.SkipCommands = [] + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + + DSS.ActiveCircuit.Settings.SkipCommands = ['clear'] + with pytest.raises(DSSException): + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + + DSS.ClearAll() + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + + +def test_skip_files(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support SkipFileRegExp.") + return + + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + DSS.Text.Command = 'clear' + + DSS.ActiveCircuit.Settings.SkipCommands = ['clear'] # We need to skip clear since it resets SkipFileRegExp + DSS.ActiveCircuit.Settings.SkipFileRegExp = r'.*LineCodes\.DSS' + + # This should fail since we won't have the LineCodes + with pytest.raises(DSSException): + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/34Bus/ieee34Mod1.dss"' + + DSS.ActiveCircuit.Settings.SkipCommands = [] + DSS.ActiveCircuit.Settings.SkipFileRegExp = '' + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/34Bus/ieee34Mod1.dss"' + + DSS.Text.Command = 'clear' + DSS.ActiveCircuit.Settings.SkipCommands = ['clear'] + DSS.ActiveCircuit.Settings.SkipFileRegExp = None + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/34Bus/ieee34Mod1.dss"' + DSS.ActiveCircuit.Settings.SkipCommands = [] + + DSS.Text.Command = 'clear' + DSS.ActiveCircuit.Settings.SkipCommands = ['clear'] + DSS.ActiveCircuit.Settings.SkipFileRegExp = 'some random string just to test' + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/34Bus/ieee34Mod1.dss"' + DSS.ActiveCircuit.Settings.SkipCommands = [] + + DSS.ActiveCircuit.Settings.SkipFileRegExp = None + + def test_path_sideeffects(): test_loadshape_save() test_basic_input_errors() test_loadshape_save() +def test_busnames_ext(): + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + CE = DSS.ActiveCircuit.ActiveCktElement + + DSS.ActiveCircuit.SetActiveElement('Line.632633') + assert tuple(CE.BusNames) == ('632.1.2.3', '633.1.2.3') + assert tuple(CE._get_BusNames(True)) == ('632', '633') + + DSS.ActiveCircuit.SetActiveElement('Transformer.Sub') + assert tuple(CE.BusNames) == ('sourcebus', '650') + assert tuple(CE._get_BusNames(True)) == ('sourcebus', '650') + + +def test_settings_context(): + DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' + + with DSS.ActiveCircuit.Settings.Context() as settings: + settings.AdvancedTypes = True + settings.PreferLists = True + assert isinstance(DSS.ActiveCircuit.LineLosses, complex) + assert isinstance(DSS.ActiveCircuit.AllBusVmag, list) + + assert not settings.AdvancedTypes + assert not settings.PreferLists + assert isinstance(DSS.ActiveCircuit.LineLosses, np.ndarray) + assert isinstance(DSS.ActiveCircuit.AllBusVmag, np.ndarray) + + +def test_share_general(): + if DSS.is_oddie(): + pytest.skip("EPRI's OpenDSS and OpenDSS-C do not support ShareGeneral.") + return + + DSS2 = DSS.NewContext() + DSS('new loadshape.sharedloadshape npts=4 pmult=[1, 2, 3, 4]') + DSS.ShareGeneral(DSS2) + DSS.NewCircuit('test1') + DSS2.NewCircuit('test2') + + assert 'sharedloadshape' in DSS.ActiveCircuit.LoadShapes.AllNames + assert 'sharedloadshape' in DSS2.ActiveCircuit.LoadShapes.AllNames + + LS = DSS.ActiveCircuit.LoadShapes + LS.Name = 'sharedloadshape' + + LS2 = DSS2.ActiveCircuit.LoadShapes + LS2.Name = 'sharedloadshape' + assert list(LS2.Pmult) == list(LS.Pmult) + assert list(LS2.Pmult) == [1., 2., 3., 4.] + + +def test_windgen_iteration(): + ''' + Added to test fix in official OpenDSS v10.2.0.1. + We had it already fixed in AltDSS, so this test ensures all engines work fine. + + Do not use this as a sample/example; it's not intended as a full working sample. + ''' + DSS.ClearAll() + DSS('new circuit.1') + DSS('new windgen.w1') + DSS('new windgen.w2') + assert DSS.ActiveCircuit.WindGens.Next == 0 + assert DSS.ActiveCircuit.WindGens.AllNames == ['w1', 'w2'] + assert DSS.ActiveCircuit.WindGens.First == 1 + assert DSS.ActiveCircuit.WindGens.Name == 'w1' + assert DSS.ActiveCircuit.WindGens.Next == 2 + assert DSS.ActiveCircuit.WindGens.Name == 'w2' + assert DSS.ActiveCircuit.WindGens.Next == 0 + assert DSS.ActiveCircuit.WindGens.Name == 'w2' + assert DSS.ActiveCircuit.WindGens.Count == 2 + + DSS.ClearAll() + DSS('new circuit.2') + assert DSS.ActiveCircuit.WindGens.AllNames == [] + assert DSS.ActiveCircuit.WindGens.Next == 0 + assert DSS.ActiveCircuit.WindGens.First == 0 + assert DSS.ActiveCircuit.WindGens.Count == 0 + + if __name__ == '__main__': DSS.AllowForms = False print(DSS.Version) # for _ in range(250): # test_pm_threads() - test_path_sideeffects() - test_capacitor_reactor() - print('DONE!') \ No newline at end of file + # test_path_sideeffects() + # test_capacitor_reactor() + test_loadshape_extended() + test_xycurve_extended() + print('DONE!') + diff --git a/tests/test_past_issues.py b/tests/test_past_issues.py index 9d8d5587..79bbd255 100644 --- a/tests/test_past_issues.py +++ b/tests/test_past_issues.py @@ -1,25 +1,40 @@ -import sys, os +import faulthandler +faulthandler.disable() + +import sys, os, warnings from time import perf_counter import dss -from dss import DSS, IDSS, DSSException, SparseSolverOptions, SolveModes, set_case_insensitive_attributes +from dss import IDSS, DSSException, SparseSolverOptions, SolveModes, set_case_insensitive_attributes import numpy as np import pytest -import scipy.sparse as sp +try: + import scipy.sparse as sp +except: + sp = None + + try: from ._settings import BASE_DIR, WIN32, ZIP_FN, DSS except ImportError: from _settings import BASE_DIR, WIN32, ZIP_FN, DSS +faulthandler.enable() + def setup_function(): DSS.ClearAll() + DSS.AllowForms = False - if not DSS._api_util._is_odd: + DSS.ActiveCircuit.Settings.AdvancedTypes = False + + if not DSS.is_oddie(): + DSS.ActiveCircuit.Settings.CompatFlags = 0 DSS.AllowEditor = False - DSS.AdvancedTypes = False DSS.AllowChangeDir = True - DSS.COMErrorResults = True # TODO: change to False - DSS.CompatFlags = 0 + DSS.ActiveCircuit.Settings.COMErrorResults = False + + DSS.Error.UseExceptions = True + DSS.Text.Command = 'set DefaultBaseFreq=60' def test_rxmatrix(): @@ -27,22 +42,27 @@ def test_rxmatrix(): DSS.NewCircuit('test_rxmatrix') for r_or_x in 'rx': DSS.Text.Command = f'new Line.ourline{r_or_x} phases=3' - DSS.Text.Command = f'~ {r_or_x}matrix=[1,2,3]' + + if not DSS.is_oddie(): # This works but pops up an annoying window with the Delphi ODD.DLL + DSS.Text.Command = f'~ {r_or_x}matrix=[1,2,3]' + DSS.Text.Command = f'~ {r_or_x}matrix=[1,2,3 | 4,5,6 | 7,8,9]' DSS.Text.Command = f'? Line.ourline{r_or_x}.{r_or_x}matrix' assert DSS.Text.Result == '[1 |4 5 |7 8 9 ]' - with pytest.raises(DSSException): - DSS.Text.Command = f'~ {r_or_x}matrix=[10,20,30,40]' + if not DSS.is_oddie(): # This works but pops up an annoying window with the Delphi ODD.DLL + with pytest.raises(DSSException): + DSS.Text.Command = f'~ {r_or_x}matrix=[10,20,30,40]' DSS.Text.Command = f'? Line.ourline{r_or_x}.{r_or_x}matrix' assert DSS.Text.Result == '[1 |4 5 |7 8 9 ]' - with pytest.raises(DSSException): - DSS.Text.Command = f'~ {r_or_x}matrix={list(range(1000))}' + if not DSS.is_oddie(): # This would crash the official Delphi ODD.DLL + with pytest.raises(DSSException): + DSS.Text.Command = f'~ {r_or_x}matrix={list(range(1000))}' - with pytest.raises(DSSException): - DSS.Text.Command = f'~ {r_or_x}matrix=[1,2,3 | 4,5,6,7]' + with pytest.raises(DSSException): + DSS.Text.Command = f'~ {r_or_x}matrix=[1,2,3 | 4,5,6,7]' DSS.Text.Command = f'~ {r_or_x}matrix=[11 | 22, 33 | 44, 55, 66]' DSS.Text.Command = f'? Line.ourline{r_or_x}.{r_or_x}matrix' @@ -55,21 +75,34 @@ def test_create_no_circuit(): 'TShape', 'TCC_Curve', 'TSData', 'XfmrCode', 'XYcurve', 'WireData', ) for cls in DSS.Classes: + if cls == 'Solution': + continue # Added for OpenDSSDirect.DLL + DSS.ClearAll() if cls in general_classes: + if cls == 'GrowthShape' and DSS.is_oddie(): + continue + DSS.Text.Command = f'new {cls}.test' else: - with pytest.raises(DSSException, match=r'\(#(279)|(265)\)'): - DSS.Text.Command = f'new {cls}.test' - pytest.fail(f'Object of type "{cls}" was allowed to be created without a circuit!') + if not DSS.is_oddie(): + with pytest.raises(DSSException, match=r'\(#(279)|(265)\)'): + DSS.Text.Command = f'new {cls}.test' + pytest.fail(f'Object of type "{cls}" was allowed to be created without a circuit!') + + DSS.Text.Command = 'new circuit.test' def test_create_with_circuit(): + if DSS.is_oddie(): + pytest.skip("This test is dangerous with EPRI's OpenDSS and OpenDSS-C. Skipping.") + return + for cls in DSS.Classes: DSS.ClearAll() DSS.NewCircuit(f'test_{cls}') - if cls in ('CapControl', 'RegControl', 'GenDispatcher', 'StorageController', 'Relay', 'Fuse', 'SwtControl', 'ESPVLControl', 'GICsource'): + if cls in ('CapControl', 'RegControl', 'GenDispatcher', 'StorageController', 'Relay', 'Fuse', 'SwtControl', 'ESPVLControl', 'GICsource', 'FMonitor', 'Generic5'): with pytest.raises(DSSException): DSS.Text.Command = f'new {cls}.test{cls}' @@ -81,6 +114,10 @@ def test_create_with_circuit(): DSS.Text.Command = f'new {cls}.test{cls}2 element=transformer.testtr capacitor=testcap' elif cls == 'GenDispatcher': DSS.Text.Command = f'new {cls}.test{cls}2 element=transformer.testtr' + elif cls in ('FMonitor', 'Generic5'): + # Skip these for now... they are disabled by default + # DSS.Text.Command = f'new {cls}.test{cls}2 element=transformer.testtr' + pass else: DSS.Text.Command = f'new {cls}.test{cls}' @@ -92,5 +129,9 @@ def test_ymatrix_csc(): DSS.Text.Command = f'redirect "{BASE_DIR}/Version8/Distrib/IEEETestCases/13Bus/IEEE13Nodeckt.dss"' DSS.ActiveCircuit.Solution.Solve() - DSS.AdvancedTypes = True - assert np.all(DSS.ActiveCircuit.SystemY == sp.csc_matrix(DSS.YMatrix.GetCompressedYMatrix())) + DSS.ActiveCircuit.Settings.AdvancedTypes = True + ydense = DSS.ActiveCircuit.SystemY + if sp is not None: + assert np.all(ydense == sp.csc_matrix(DSS.YMatrix.GetCompressedYMatrix())) + else: + pytest.skip("SciPy is not installed, skipping sparse-dense comparison.")