diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..0e696ae --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,79 @@ +name: CI + +on: + push: + branches: + - 'main' + - 'release-v**' + - 'full-sonar-analysis-**' + pull_request: + +jobs: + build: + name: Build OS ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] # macos-latest : Knitro does not yet support Java API on macOS, to be tried later on + + steps: + - name: Install Knitro (Linux) + if: matrix.os == 'ubuntu-latest' + run: | + wget -nv -O knitro.tar.gz --user "$KNITRO_DOWNLOAD_USER" --password "$KNITRO_DOWNLOAD_PASSWORD" "$KNITRO_LINUX_URL" + mkdir -p $RUNNER_TEMP/knitro + tar xzf knitro.tar.gz -C $RUNNER_TEMP/knitro + echo "KNITRODIR=$RUNNER_TEMP/knitro/knitro-14.1.0-Linux64" >> "$GITHUB_ENV" + env: + KNITRO_DOWNLOAD_USER: ${{ secrets.KNITRO_DOWNLOAD_USER }} + KNITRO_DOWNLOAD_PASSWORD: ${{ secrets.KNITRO_DOWNLOAD_PASSWORD }} + KNITRO_LINUX_URL: ${{ secrets.KNITRO_LINUX_URL }} + + - name: Install Knitro (Windows) + if: matrix.os == 'windows-latest' + shell: powershell + run: | + C:\msys64\usr\bin\wget.exe -nv -O knitro.zip --user "$env:KNITRO_DOWNLOAD_USER" --password "$env:KNITRO_DOWNLOAD_PASSWORD" "$env:KNITRO_WINDOWS_URL" + 7z x -y knitro.zip -oC:\knitro + echo "KNITRODIR=C:\knitro\knitro-14.1.0-Win64" >> "$env:GITHUB_ENV" + env: + KNITRO_DOWNLOAD_USER: ${{ secrets.KNITRO_DOWNLOAD_USER }} + KNITRO_DOWNLOAD_PASSWORD: ${{ secrets.KNITRO_DOWNLOAD_PASSWORD }} + KNITRO_WINDOWS_URL: ${{ secrets.KNITRO_WINDOWS_URL }} + + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Build with Maven (Ubuntu) + if: matrix.os != 'windows-latest' + run: | + ./mvnw install:install-file -Dfile="$KNITRODIR/examples/Java/lib/Knitro-Interfaces-2.5-KN_14.1.0.jar" -DgroupId=com.artelys -DartifactId=knitro-interfaces -Dversion=14.1.0 -Dpackaging=jar -DgeneratePom=true + ./mvnw --batch-mode -Pjacoco install + env: + ARTELYS_LICENSE: ${{ secrets.ARTELYS_LICENSE }} + + - name: Build with Maven (Windows) + if: matrix.os == 'windows-latest' + run: | + call mvnw.cmd install:install-file -Dfile="%KNITRODIR%\examples\Java\lib\Knitro-Interfaces-2.5-KN_14.1.0.jar" -DgroupId=com.artelys -DartifactId=knitro-interfaces -Dversion=14.1.0 -Dpackaging=jar -DgeneratePom=true + mvnw.cmd --batch-mode install + shell: cmd + env: + ARTELYS_LICENSE: ${{ secrets.ARTELYS_LICENSE }} + + - name: Run SonarCloud analysis + if: matrix.os == 'ubuntu-latest' + run: > + ./mvnw --batch-mode -DskipTests sonar:sonar + -Dsonar.host.url=https://sonarcloud.io + -Dsonar.organization=powsybl-ci-github + -Dsonar.projectKey=com.powsybl:powsybl-open-loadflow-knitro-solver + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d665ca6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Maven projects +target/ + +# IntelliJ +/.idea +*.iml + +# Eclipse projects +.classpath +.project +org.eclipse.core.resources.prefs +org.eclipse.jdt.core.prefs +org.eclipse.m2e.core.prefs +org.eclipse.jdt.groovy.core.prefs + +# Maven wrapper jar +.mvn/wrapper/*.jar + +# Generated readthedocs pages +build-docs/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..62b325c --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,24 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + + +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip +distributionSha256Sum=80b3b63df0e40ca8cde902bb1a40e4488ede24b3f282bd7bd6fba8eb5a7e055c + +# Beware: wrapperUrl must not be set! It prevents the "MVNW_REPOURL" envvar to work when downloading maven-wrapper +# SHA-256 of maven-wrapper-3.2.0.jar: +wrapperSha256Sum=e63a53cfb9c4d291ebe3c2b0edacb7622bbc480326beaa5a0456e412f52f066a diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index 346e522..2ed0d83 100644 --- a/README.md +++ b/README.md @@ -1 +1,200 @@ -# powsybl-open-loadflow-knitro-solver \ No newline at end of file +# PowSyBl Open Load Flow - Knitro Solver + +[![Actions Status](https://github.com/powsybl/powsybl-open-loadflow-knitro-solver/workflows/CI/badge.svg)](https://github.com/powsybl/powsybl-open-loadflow-knitro-solver/actions) +[![Coverage Status](https://sonarcloud.io/api/project_badges/measure?project=com.powsybl%3Apowsybl-open-loadflow-knitro-solver&metric=coverage)](https://sonarcloud.io/component_measures?id=com.powsybl%3Apowsybl-open-loadflow-knitro-solver&metric=coverage) +[![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=com.powsybl%3Apowsybl-open-loadflow-knitro-solver&metric=alert_status)](https://sonarcloud.io/dashboard?id=com.powsybl%3Apowsybl-open-loadflow-knitro-solver) +[![MPL-2.0 License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://www.mozilla.org/en-US/MPL/2.0/) +[![Slack](https://img.shields.io/badge/slack-powsybl-blueviolet.svg?logo=slack)](https://join.slack.com/t/powsybl/shared_invite/zt-rzvbuzjk-nxi0boim1RKPS5PjieI0rA) + +PowSyBl (**Pow**er **Sy**stem **Bl**ocks) is an open source library written in Java, that makes it easy to write complex +software for power systems’ simulations and analysis. Its modular approach allows developers to extend or customize its +features. + +PowSyBl is part of the LF Energy Foundation, a project of The Linux Foundation that supports open source innovation projects +within the energy and electricity sectors. + +

+PowSyBl Logo +

+ +Read more at https://www.powsybl.org ! + +This project and everyone participating in it is under the [Linux Foundation Energy governance principles](https://www.powsybl.org/pages/project/governance.html) and must respect the [PowSyBl Code of Conduct](https://github.com/powsybl/.github/blob/main/CODE_OF_CONDUCT.md). +By participating, you are expected to uphold this code. Please report unacceptable behavior to [powsybl-tsc@lists.lfenergy.org](mailto:powsybl-tsc@lists.lfenergy.org). + +## PowSyBl vs PowSyBl Open Load Flow Knitro Solver + +PowSyBl Open Load Flow Knitro Solver is an extension to [PowSyBl Open Load Flow](https://github.com/powsybl/powsybl-open-loadflow) allowing to solve +the load flow equations with the **non-linear solver Knitro** instead of the default **Newton-Raphson** method. + +The Knitro solver extension models the load flow problem as a **constraint satisfaction problem** (without an objective function). + +## Getting Started + +To use the PowSyBl Open Load Flow Knitro Solver extension, a valid Knitro installation is required. + +### Platform compatibility + +Knitro supports Linux, Windows, and macOS; however, its Java bindings are currently available only on Linux and Windows. + +### Installing Knitro + +1. Obtain the installation kit and trial license from the [Artelys website](https://www.artelys.com/solvers/knitro/programs/#trial). +2. Configure the following environment variables: + - `KNITRODIR`: Path to the Knitro installation directory. + - `ARTELYS_LICENSE`: Path to the Knitro license file or its content. + +You may then validate your installation by running one of the Java examples like this (here on Linux): + +```bash +cd $KNITRODIR/examples/java/examples +# compile example +javac -cp ".;../lib/*" com/artelys/knitro/examples/ExampleNLP1.java +# run example +java -cp ".;../lib/*" com.artelys.knitro.examples.ExampleNLP1 +``` + +
+ +Here an example output (click to expand) + +``` +$ java -cp ".;../lib/*" com.artelys.knitro.examples.ExampleNLP1 + +--- snip --- + +Optimal objective value = 306.5000025616414 +Optimal x (with corresponding multiplier) +x1 = 0,500000 (lambda = -700,000000) +x2 = 2,000000 (lambda = -0,000000) +Optimal constraint values (with corresponding multiplier) + c[0] = 1,000000 (lambda = -700,000000) + c[1] = 4,500000 (lambda = -0,000000) +Feasibility violation = 0,000000 +Optimality violation = 0,000003 +``` + +
+ + +### Installing Knitro Java Bindings +Knitro Java bindings require a private JAR file that must be installed locally, as it is not available on Maven Central. +Use the following command: + +```bash +./mvnw install:install-file -Dfile="$KNITRODIR/examples/Java/lib/Knitro-Interfaces-2.5-KN_14.1.0.jar" -DgroupId=com.artelys -DartifactId=knitro-interfaces -Dversion=14.1.0 -Dpackaging=jar -DgeneratePom=true +``` + +### Running a Load Flow with Knitro Solver + +To run a load flow with PowSyBl Open Load Flow Knitro Solver. We first add a few Maven +dependencies to respectively have access to network model, IEEE test networks and simple logging capabilities: + +```xml + + com.powsybl + powsybl-iidm-impl + 6.6.0 + + + com.powsybl + powsybl-ieee-cdf-converter + 6.6.0 + + + org.slf4j + slf4j-simple + 2.0.13 + +``` + +We are now able to load the IEEE 14 bus test network: + ```java +Network network = IeeeCdfNetworkFactory.create14(); + ``` + +After adding dependency on both Open Load Flow implementation and Knitro Solver extension: +```xml + + com.powsybl + powsybl-open-loadflow + 1.14.0 + + + com.powsybl + powsybl-open-loadflow-knitro-solver + 0.1.0 + +``` + +To run the load flow with the Knitro solver, configure the +[Open Load Flow parameter `acSolverType`](https://powsybl.readthedocs.io/projects/powsybl-open-loadflow/en/latest/loadflow/parameters.html) +as follows: + +```java +LoadFlowParameters parameters = new LoadFlowParameters(); +OpenLoadFlowParameters.create(parameters) + .setAcSolverType("KNITRO"); // Change default Open Load Flow parameter acSolverType from NEWTON_RAPHSON to KNITRO +LoadFlow.run(network, parameters); +``` + +## Features + +The Knitro solver is used as a substitute for the **inner loop calculations** in the load flow process. +The **outer loops** such as distributed slack, reactive limits, etc... operate identically as when using the Newton-Raphson method. + +### Configuration + +Most parameters for Knitro are configured similarly to the Newton-Raphson method. +However, specific parameters tailored to Knitro are provided through the `KnitroLoadFlowParameters` extension of `LoadFlowParameters`. + +Here an example on how to provide Knitro solver specific parameters in Java: + +```java +LoadFlowParameters parameters = new LoadFlowParameters(); +OpenLoadFlowParameters.create(parameters) + .setAcSolverType("KNITRO"); +KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); +knitroLoadFlowParameters.setGradientComputationMode(2); +parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); +``` + +### Knitro Parameters + +1. **Voltage Bounds**: + - Default range: **0.5 p.u. to 1.5 p.u.** : they define lower and upper bounds of voltages. + - Modify using: + - `setLowerVoltageBound` + - `setUpperVoltageBound` + +2. **Jacobian Matrix Usage**: + - The solver utilizes the **Jacobian matrix** for solving successive linear approximations of the problem. + - **Gradient Computation Modes**: + - `1 (exact)`: Gradients computed in PowSyBl are provided to Knitro. + - `2 (forward)`: Knitro computes gradients via forward finite differences. + - `3 (central)`: Knitro computes gradients via central finite differences. + - Use `setGradientComputationMode` in the `KnitroLoadFlowParameters` extension. + +3. **Jacobian Sparsity**: + - Default: **Sparse form** (highly recommended, improves calculation as load flow problems are highly sparse problems). + - To specify dense form: + - Use `setGradientUserRoutineKnitro` in the `KnitroLoadFlowParameters` extension. + - **Options**: + - `1 (dense)`: All constraints are considered as dependent of all variables. + - `2 (sparse)`: Derivatives are computed only for variables involved in the constraints (recommended). + +4. **Maximum Iterations**: + - Default: **200** + - Modify using `setMaxIterations`. + +### Constraint Handling + +Constraints are categorized into two types: + +1. **Linear and Quadratic Constraints**: + - Explicitly passed to the solver. + +2. **Non-linear (Non-quadratic) Constraints**: + - Evaluated during each iteration by a **callback class**, based on the current state. + +--- diff --git a/mvnw b/mvnw new file mode 100755 index 0000000..8923779 --- /dev/null +++ b/mvnw @@ -0,0 +1,307 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..c4586b5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..036c48e --- /dev/null +++ b/pom.xml @@ -0,0 +1,142 @@ + + + + 4.0.0 + + + com.powsybl + powsybl-parent + 20 + + + + powsybl-open-loadflow-knitro-solver + 0.1.0-SNAPSHOT + + powsybl open loadflow knitro solver + A Load Flow solver based on Knitro for PowSyBl Open Load Flow + http://www.powsybl.org + + + scm:git:https://github.com/powsybl/powsybl-open-loadflow-knitro-solver.git + scm:git:https://github.com/powsybl/powsybl-open-loadflow-knitro-solver.git + https://github.com/powsybl/powsybl-open-loadflow-knitro-solver + + + + + Jeanne ARCHAMBAULT + jeanne.archambault@artelys.com + Artelys + http://www.artelys.com + + + + + 17 + 6.6.0 + 1.14.0 + 14.1.0 + 0.7.0 + + + + + + org.codehaus.mojo + buildnumber-maven-plugin + + + org.codehaus.mojo + templating-maven-plugin + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + com.powsybl:powsybl-config-classic + + + + + + + + + + + com.powsybl + powsybl-core + ${powsybl-core.version} + pom + import + + + + + + + + com.powsybl + powsybl-open-loadflow + ${powsybl-open-loadflow.version} + + + com.artelys + knitro-interfaces + ${knitro-interfaces.version} + + + com.nativelibs4java + bridj + ${bridj.version} + + + + + ch.qos.logback + logback-classic + test + + + org.junit.jupiter + junit-jupiter + test + + + com.powsybl + powsybl-open-loadflow + ${powsybl-open-loadflow.version} + tests + test + + + com.powsybl + powsybl-iidm-impl + test + + + com.powsybl + powsybl-iidm-test + test + + + com.powsybl + powsybl-ieee-cdf-converter + test + + + diff --git a/src/main/java-templates/com/powsybl/openloadflow/knitro/util/PowsyblOpenLoadFlowKnitroSolverVersion.java b/src/main/java-templates/com/powsybl/openloadflow/knitro/util/PowsyblOpenLoadFlowKnitroSolverVersion.java new file mode 100644 index 0000000..9e906b8 --- /dev/null +++ b/src/main/java-templates/com/powsybl/openloadflow/knitro/util/PowsyblOpenLoadFlowKnitroSolverVersion.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2024, Artelys (http://www.artelys.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.util; + +import com.google.auto.service.AutoService; +import com.powsybl.tools.*; + +/** + * @author Damien Jeandemange {@literal } + */ +@AutoService(Version.class) +public class PowsyblOpenLoadFlowKnitroSolverVersion extends AbstractVersion { + + public PowsyblOpenLoadFlowKnitroSolverVersion() { + super("powsybl-open-loadflow-knitro-solver", "${project.version}", "${buildNumber}", "${scmBranch}", Long.parseLong("${timestamp}")); + } +} diff --git a/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroLoadFlowParameters.java b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroLoadFlowParameters.java new file mode 100644 index 0000000..3e5b31b --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroLoadFlowParameters.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2024, Artelys (http://www.artelys.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.commons.extensions.AbstractExtension; +import com.powsybl.loadflow.LoadFlowParameters; + +/** + * @author Jeanne Archambault {@literal } + */ +public class KnitroLoadFlowParameters extends AbstractExtension { + + private int gradientComputationMode = KnitroSolverParameters.DEFAULT_GRADIENT_COMPUTATION_MODE; + private int gradientUserRoutine = KnitroSolverParameters.DEFAULT_GRADIENT_USER_ROUTINE; + private double lowerVoltageBound = KnitroSolverParameters.DEFAULT_LOWER_VOLTAGE_BOUND; + private double upperVoltageBound = KnitroSolverParameters.DEFAULT_UPPER_VOLTAGE_BOUND; + private int maxIterations = KnitroSolverParameters.DEFAULT_MAX_ITERATIONS; + private double convEps = KnitroSolverParameters.DEFAULT_STOPPING_CRITERIA; + + public int getGradientComputationMode() { + return gradientComputationMode; + } + + public KnitroLoadFlowParameters setGradientComputationMode(int gradientComputationMode) { + if (gradientComputationMode < 1 || gradientComputationMode > 3) { + throw new IllegalArgumentException("User routine must be between 1 and 3"); + } + this.gradientComputationMode = gradientComputationMode; + return this; + } + + public int getGradientUserRoutine() { + return gradientUserRoutine; + } + + public KnitroLoadFlowParameters setGradientUserRoutine(int gradientUserRoutine) { + if (gradientUserRoutine < 1 || gradientUserRoutine > 2) { + throw new IllegalArgumentException("User routine must be between 1 and 2"); + } + this.gradientUserRoutine = gradientUserRoutine; + return this; + } + + public double getLowerVoltageBound() { + return lowerVoltageBound; + } + + public KnitroLoadFlowParameters setLowerVoltageBound(double lowerVoltageBound) { + if (lowerVoltageBound < 0) { + throw new IllegalArgumentException("Realistic voltage bounds must strictly greater than 0"); + } + this.lowerVoltageBound = lowerVoltageBound; + return this; + } + + public double getUpperVoltageBound() { + return upperVoltageBound; + } + + public KnitroLoadFlowParameters setUpperVoltageBound(double upperVoltageBound) { + if (upperVoltageBound < 0) { + throw new IllegalArgumentException("Realistic voltage bounds must strictly greater than 0"); + } + if (upperVoltageBound <= lowerVoltageBound) { + throw new IllegalArgumentException("Realistic voltage upper bounds must greater than lower bounds"); + } + this.upperVoltageBound = upperVoltageBound; + return this; + } + + public int getMaxIterations() { + return maxIterations; + } + + public KnitroLoadFlowParameters setMaxIterations(int maxIterations) { + if (maxIterations < 0) { + throw new IllegalArgumentException("Max iterations parameter must be greater than 0"); + } + this.maxIterations = maxIterations; + return this; + } + + public double getConvEps() { + return convEps; + } + + public KnitroLoadFlowParameters setConvEps(double convEps) { + if (convEps <= 0) { + throw new IllegalArgumentException("Convergence stopping criteria must be greater than 0"); + } + this.convEps = convEps; + return this; + } + + @Override + public String getName() { + return "knitro-load-flow-parameters"; + } + +} diff --git a/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolver.java b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolver.java new file mode 100644 index 0000000..680b853 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolver.java @@ -0,0 +1,624 @@ +/** + * Copyright (c) 2024, Artelys (http://www.artelys.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.artelys.knitro.api.*; +import com.artelys.knitro.api.callbacks.*; +import com.powsybl.commons.PowsyblException; +import com.powsybl.commons.report.ReportNode; +import com.powsybl.math.matrix.SparseMatrix; +import com.powsybl.openloadflow.ac.equations.AcEquationType; +import com.powsybl.openloadflow.ac.equations.AcVariableType; +import com.powsybl.openloadflow.ac.solver.AbstractAcSolver; +import com.powsybl.openloadflow.ac.solver.AcSolverResult; +import com.powsybl.openloadflow.ac.solver.AcSolverStatus; +import com.powsybl.openloadflow.ac.solver.AcSolverUtil; +import com.powsybl.openloadflow.equations.*; +import com.powsybl.openloadflow.network.LfBus; +import com.powsybl.openloadflow.network.LfNetwork; +import com.powsybl.openloadflow.network.util.VoltageInitializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.commons.lang3.Range; + +import java.util.*; + +import static com.google.common.primitives.Doubles.toArray; + +/** + * @author Pierre Arvy {@literal } + * @author Jeanne Archambault {@literal } + */ +public class KnitroSolver extends AbstractAcSolver { + + private static final Logger LOGGER = LoggerFactory.getLogger(KnitroSolver.class); + + protected KnitroSolverParameters knitroParameters; + + public KnitroSolver( + LfNetwork network, + KnitroSolverParameters knitroParameters, + EquationSystem equationSystem, + JacobianMatrix j, + TargetVector targetVector, + EquationVector equationVector, + boolean detailedReport) { + + super(network, equationSystem, j, targetVector, equationVector, detailedReport); + this.knitroParameters = knitroParameters; + } + + @Override + public String getName() { + return "Knitro Solver"; + } + + /** + * Enum representing all possible Knitro status values. + */ + public enum KnitroStatus { + CONVERGED_TO_LOCAL_OPTIMUM(0, 0, AcSolverStatus.CONVERGED), + CONVERGED_TO_FEASIBLE_APPROXIMATE_SOLUTION(-199, -100, AcSolverStatus.CONVERGED), + TERMINATED_AT_INFEASIBLE_POINT(-299, -200, AcSolverStatus.SOLVER_FAILED), + PROBLEM_UNBOUNDED(-399, -300, AcSolverStatus.SOLVER_FAILED), + TERMINATED_DUE_TO_PRE_DEFINED_LIMIT(-499, -400, AcSolverStatus.MAX_ITERATION_REACHED), + INPUT_OR_NON_STANDARD_ERROR(-599, -500, AcSolverStatus.SOLVER_FAILED); + + private final Range statusRange; + private final AcSolverStatus acSolverStatus; + + /** + * Constructor initializing the range and corresponding AcSolverStatus. + * + * @param min The minimum status code. + * @param max The maximum status code. + * @param acSolverStatus The corresponding AcSolverStatus. + */ + KnitroStatus(int min, int max, AcSolverStatus acSolverStatus) { + this.statusRange = Range.of(min, max); + this.acSolverStatus = acSolverStatus; + } + + /** + * Finds the KnitroStatus from a given integer status code. + * + * @param statusCode The Knitro status code. + * @return The corresponding KnitroStatus. + * @throws IllegalArgumentException if the status code is unknown. + */ + public static KnitroStatus fromStatusCode(int statusCode) { + for (KnitroStatus status : KnitroStatus.values()) { + if (status.statusRange.contains(statusCode)) { + return status; + } + } + throw new IllegalArgumentException("Unknown Knitro Status"); + } + + /** + * Converts the KnitroStatus to its corresponding AcSolverStatus. + * + * @return The corresponding AcSolverStatus. + */ + public AcSolverStatus toAcSolverStatus() { + return this.acSolverStatus; + } + } + + private void logKnitroStatus(KnitroStatus status) { + LOGGER.info("Knitro Status: {}", status); + } + + /** + * Handles setting lower and upper variable bounds. + */ + public class VariableBounds { + private final List lowerBounds; + private final List upperBounds; + + public VariableBounds(List> sortedVariables) { + this.lowerBounds = new ArrayList<>(); + this.upperBounds = new ArrayList<>(); + setBounds(sortedVariables); + } + + private void setBounds(List> sortedVariables) { + double loBndV = knitroParameters.getLowerVoltageBound(); + double upBndV = knitroParameters.getUpperVoltageBound(); + + for (Variable variable : sortedVariables) { + Enum typeVar = variable.getType(); + if (typeVar == AcVariableType.BUS_V) { + lowerBounds.add(loBndV); + upperBounds.add(upBndV); + } else { + lowerBounds.add(-KNConstants.KN_INFINITY); + upperBounds.add(KNConstants.KN_INFINITY); + } + } + } + + public List getLowerBounds() { + return lowerBounds; + } + + public List getUpperBounds() { + return upperBounds; + } + } + + /** + * Handles initialization of the state vector. + */ + public static class StateInitializer { + private final List initialState; + + public StateInitializer(LfNetwork lfNetwork, EquationSystem equationSystem, + VoltageInitializer voltageInitializer) { + int numVar = equationSystem.getIndex().getSortedVariablesToFind().size(); + this.initialState = new ArrayList<>(numVar); + initializeState(lfNetwork, equationSystem, voltageInitializer); + } + + private void initializeState(LfNetwork lfNetwork, EquationSystem equationSystem, + VoltageInitializer voltageInitializer) { + AcSolverUtil.initStateVector(lfNetwork, equationSystem, voltageInitializer); + for (int i = 0; i < equationSystem.getIndex().getSortedVariablesToFind().size(); i++) { + initialState.add(equationSystem.getStateVector().get(i)); + } + } + + public List getInitialState() { + return initialState; + } + } + + public void buildDenseJacobianMatrix(int numVar, List listNonLinearConsts, List listNonZerosCtsDense, List listNonZerosVarsDense) { + for (Integer idCt : listNonLinearConsts) { + for (int i = 0; i < numVar; i++) { + // there are numVar*numNonLinearConstraints derivates to evaluate because every non-linear constraint is derived with respect to every variable + listNonZerosCtsDense.add(idCt); + } + } + + List listVars = new ArrayList<>(); + for (int i = 0; i < numVar; i++) { + listVars.add(i); + } + for (int i = 0; i < listNonLinearConsts.size(); i++) { + listNonZerosVarsDense.addAll(listVars); + } + } + + public void buildSparseJacobianMatrix(List> sortedEquationsToSolve, List listNonLinearConsts, List listNonZerosCtsSparse, List listNonZerosVarsSparse) { + for (Integer ct : listNonLinearConsts) { // for each non-linear constraint, we get the variables of which it depends on + Equation equation = sortedEquationsToSolve.get(ct); + List> terms = equation.getTerms(); + List listNonZerosVarsCurrentCt = new ArrayList<>(); //list of variables involved in current constraint + + for (EquationTerm term : terms) { + for (Variable variable : term.getVariables()) { + listNonZerosVarsCurrentCt.add(variable.getRow()); + } + } + List uniqueListVarsCurrentCt = listNonZerosVarsCurrentCt.stream().distinct().sorted().toList(); // remove duplicate elements from the list, because the same variables may be present in several terms of the constraint + listNonZerosVarsSparse.addAll(uniqueListVarsCurrentCt); + // we add uniqueListVarsCurrentCt.size() times the constraint ct to the list of constraints to derive + listNonZerosCtsSparse.addAll(new ArrayList<>(Collections.nCopies(uniqueListVarsCurrentCt.size(), ct))); + } + } + + private final class KnitroProblem extends KNProblem { + + /** + * Callback function to evaluate non-linear parts of the objective and of constraints + */ + + private static final class CallbackEvalFC extends KNEvalFCCallback { + + private final List> sortedEquationsToSolve; + private final List listNonLinearConsts; + + private CallbackEvalFC(List> sortedEquationsToSolve, List listNonLinearConsts) { + this.sortedEquationsToSolve = sortedEquationsToSolve; + this.listNonLinearConsts = listNonLinearConsts; + } + + // Callback function for non-linear parts of objective and constraints + @Override + public void evaluateFC(final List x, final List obj, final List c) { + LOGGER.trace("============ Knitro evaluating callback function ============"); + + // =============== Non-linear constraints in P and Q =============== + + // Update current state + StateVector currentState = new StateVector(toArray(x)); + LOGGER.trace("Current state vector {}", currentState.get()); + LOGGER.trace("Evaluating {} non-linear constraints", listNonLinearConsts.size()); + + // Add non-linear constraints + int indexNonLinearCst = 0; // callback index of current constraint added + for (int equationId : listNonLinearConsts) { + Equation equation = sortedEquationsToSolve.get(equationId); + AcEquationType typeEq = equation.getType(); + double valueConst = 0; + if (NonLinearExternalSolverUtils.isLinear(typeEq, equation.getTerms())) { // check that the constraint is really non-linear + throw new IllegalArgumentException("Equation of type " + typeEq + " is linear, and should be considered in the main function of Knitro, not in the callback function"); + } else { + // we evaluate the equation with respect to the current state + for (EquationTerm term : equation.getTerms()) { + term.setStateVector(currentState); + if (term.isActive()) { + valueConst += term.eval(); + } + } + try { + c.set(indexNonLinearCst, valueConst); // adding the constraint + LOGGER.trace("Adding non-linear constraint n° {}, of type {} and of value {}", equationId, typeEq, valueConst); + } catch (Exception e) { + throw new PowsyblException("Exception found while trying to add non-linear constraint n° " + equationId, e); + } + } + indexNonLinearCst += 1; // we move on to the next constraint + } + } + } + + /** + * Callback function to evaluate the gradient + * When no objective, that is to say the matrix of the constraints, the Jacobian matrix + */ + private static final class CallbackEvalG extends KNEvalGACallback { + private final JacobianMatrix oldMatrix; + private final List listNonZerosCtsDense; + private final List listNonZerosVarsDense; + private final List listNonZerosCtsSparse; + private final List listNonZerosVarsSparse; + private final LfNetwork network; + private final EquationSystem equationSystem; + private final KnitroSolverParameters knitroParameters; + + private CallbackEvalG(JacobianMatrix oldMatrix, List listNonZerosCts, List listNonZerosVars, List listNonZerosCts2, List listNonZerosVars2, LfNetwork network, EquationSystem equationSystem, KnitroSolverParameters knitroParameters) { + this.oldMatrix = oldMatrix; + this.listNonZerosCtsDense = listNonZerosCts; + this.listNonZerosVarsDense = listNonZerosVars; + this.listNonZerosCtsSparse = listNonZerosCts2; + this.listNonZerosVarsSparse = listNonZerosVars2; + this.network = network; + this.equationSystem = equationSystem; + this.knitroParameters = knitroParameters; + } + + @Override + public void evaluateGA(final List x, final List objGrad, final List jac) { + // Update current Jacobian + equationSystem.getStateVector().set(toArray(x)); + AcSolverUtil.updateNetwork(network, equationSystem); + oldMatrix.forceUpdate(); + + // For sparse matrix, get values, row and column structure + SparseMatrix sparseOldMatrix = oldMatrix.getMatrix().toSparse(); + int[] columnStart = sparseOldMatrix.getColumnStart(); + int[] rowIndices = sparseOldMatrix.getRowIndices(); + double[] values = sparseOldMatrix.getValues(); + + // Number of constraints evaluated in callback + int numCbCts = 0; + if (knitroParameters.getGradientUserRoutine() == 1) { + numCbCts = listNonZerosCtsDense.size(); + } else if (knitroParameters.getGradientUserRoutine() == 2) { + numCbCts = listNonZerosCtsSparse.size(); + } + + // Pass coefficients of Jacobian matrix to Knitro + for (int index = 0; index < numCbCts; index++) { + try { + int variable = 0; + int ct = 0; + if (knitroParameters.getGradientUserRoutine() == 1) { + variable = listNonZerosVarsDense.get(index); + ct = listNonZerosCtsDense.get(index); + } else if (knitroParameters.getGradientUserRoutine() == 2) { + variable = listNonZerosVarsSparse.get(index); + ct = listNonZerosCtsSparse.get(index); + } + + // Start and end index in the values array for column ct + int colStart = columnStart[ct]; + int colEnd = columnStart[ct + 1]; + double valueSparse = 0.0; + + // Iterate through the column range + for (int i = colStart; i < colEnd; i++) { + // Check if the row index matches var + if (rowIndices[i] == variable) { + // Get the corresponding value + valueSparse = values[i]; + break; // Exit loop since the value is found + } + } + jac.set(index, valueSparse); + } catch (Exception e) { + LOGGER.error( + "Exception found while trying to add Jacobian term {} in non-linear constraint n° {}", + listNonZerosVarsSparse.get(index), + listNonZerosCtsSparse.get(index) + ); + LOGGER.error(e.getMessage()); + } + } + } + } + + /** + * Callback function to evaluate the Hessian Matrix + */ + private static class CallbackEvalH extends KNEvalHCallback { + @Override + public void evaluateH(final List x, final double sigma, final List lambda, List hess) { + // TODO : add Hessian matrix to improve performances + } + } + + /** + * Knitro Problem definition with : + * - initialization of variables (types, bounds, initial state) + * - definition of linear constraints + * - definition of non-linear constraints, evaluated in the callback function + * - definition of the Jacobian matrix passed to Knitro to solve the problem + */ + private KnitroProblem(LfNetwork lfNetwork, EquationSystem equationSystem, TargetVector targetVector, VoltageInitializer voltageInitializer, JacobianMatrix jacobianMatrix) throws KNException { + + // =============== Variables =============== + // Defining variables + super(equationSystem.getIndex().getSortedVariablesToFind().size(), // only active variables + equationSystem.getIndex().getSortedEquationsToSolve().size()); // only active constraints + int numVar = equationSystem.getIndex().getSortedVariablesToFind().size(); + List> sortedVariables = equationSystem.getIndex().getSortedVariablesToFind(); // ordering variables + LOGGER.info("Defining {} variables", numVar); + + // Types, bounds and initial states of variables + // Types + List listVarTypes = new ArrayList<>(Collections.nCopies(numVar, KNConstants.KN_VARTYPE_CONTINUOUS)); + setVarTypes(listVarTypes); + + // Bounds + VariableBounds variableBounds = new VariableBounds(sortedVariables); + setVarLoBnds(variableBounds.getLowerBounds()); + setVarUpBnds(variableBounds.getUpperBounds()); + + // Initial state + StateInitializer stateInitializer = new StateInitializer(lfNetwork, equationSystem, voltageInitializer); + setXInitial(stateInitializer.getInitialState()); + LOGGER.info("Initialization of variables : type of initialization {}", voltageInitializer); + + // =============== Constraints ============== + // ----- Active constraints ----- + List> sortedEquationsToSolve = equationSystem.getIndex().getSortedEquationsToSolve(); + int numConst = sortedEquationsToSolve.size(); + List listNonLinearConsts = new ArrayList<>(); // list of indexes of non-linear constraints + LOGGER.info("Defining {} active constraints", numConst); + + // ----- Linear constraints ----- + NonLinearExternalSolverUtils solverUtils = new NonLinearExternalSolverUtils(); + addLinearConstraints(sortedEquationsToSolve, solverUtils, listNonLinearConsts); // add linear constraints and fill the list of non-linear constraints + + // ----- Non-linear constraints ----- + // Callback + // Pass to Knitro the indexes of non-linear constraints, that will be evaluated in the callback function + setMainCallbackCstIndexes(listNonLinearConsts); + + // ----- RHS : targets ----- + setConEqBnds(Arrays.stream(targetVector.getArray()).boxed().toList()); + + // =============== Objective ============== + setObjConstPart(0.0); + + // =============== Callback ============== + // ----- Constraints ----- + setObjEvalCallback(new CallbackEvalFC(sortedEquationsToSolve, listNonLinearConsts)); + + // ----- Jacobian matrix ----- + // Non-zero pattern : for each constraint, we detail the variables of which the constraint is a function of. + List listNonZerosCtsDense = new ArrayList<>(); // for the dense method, list of constraints to pass to Knitro's non-zero pattern + List listNonZerosVarsDense = new ArrayList<>(); // for the dense method, list of variables to pass to Knitro's non-zero pattern + List listNonZerosCtsSparse = new ArrayList<>(); + List listNonZerosVarsSparse = new ArrayList<>(); + + setJacobianMatrix(lfNetwork, jacobianMatrix, sortedEquationsToSolve, listNonLinearConsts, + listNonZerosCtsDense, listNonZerosVarsDense, + listNonZerosCtsSparse, listNonZerosVarsSparse); + } + + /** + * Adds constraints to the Knitro problem, classifying them as linear or non-linear. + * + * @param sortedEquationsToSolve A list of equations to solve. + * @param solverUtils Utils for solving external non-linear equations. + * @param listNonLinearConsts A list to store ids of non-linear constraints. + */ + private void addLinearConstraints(List> sortedEquationsToSolve, + NonLinearExternalSolverUtils solverUtils, List listNonLinearConsts) { + + int numConst = sortedEquationsToSolve.size(); + + for (int equationId = 0; equationId < numConst; equationId++) { + addConstraint(equationId, sortedEquationsToSolve, solverUtils, listNonLinearConsts); + } + } + + /** + * Adds a specific constraint to the Knitro problem, classifying it as linear or non-linear. + * + * @param equationId The id of the equation. + * @param sortedEquationsToSolve A list of equations to solve. + * @param solverUtils Utils for solving external non-linear equations. + * @param listNonLinearConsts A list to store ids of non-linear constraints. + */ + private void addConstraint(int equationId, List> sortedEquationsToSolve, NonLinearExternalSolverUtils solverUtils, List listNonLinearConsts) { + Equation equation = sortedEquationsToSolve.get(equationId); + AcEquationType typeEq = equation.getType(); + List> terms = equation.getTerms(); + + if (NonLinearExternalSolverUtils.isLinear(typeEq, terms)) { + try { + List listVar = solverUtils.getLinearConstraint(typeEq, terms).listIdVar(); + + List listCoef = solverUtils.getLinearConstraint(typeEq, terms).listCoef(); + + for (int i = 0; i < listVar.size(); i++) { + this.addConstraintLinearPart(equationId, listVar.get(i), listCoef.get(i)); + } + LOGGER.trace("Adding linear constraint n° {} of type {}", equationId, typeEq); + } catch (UnsupportedOperationException e) { + throw new PowsyblException(e); + } + } else { + // ----- Non-linear constraints ----- + listNonLinearConsts.add(equationId); // Add constraint number to list of non-linear constraints + } + } + + /** + * Configures the Jacobian matrix for the Knitro problem, using either a dense or sparse representation. + * + * @param lfNetwork The PowSyBl network. + * @param jacobianMatrix The PowSyBl Jacobian matrix. + * @param sortedEquationsToSolve The list of equations to solve. + * @param listNonLinearConsts The list of non-linear constraint ids. + * @param listNonZerosCtsDense Dense non-zero constraints. + * @param listNonZerosVarsDense Dense non-zero variables. + * @param listNonZerosCtsSparse Sparse non-zero constraints. + * @param listNonZerosVarsSparse Sparse non-zero variables. + * @throws KNException If an error occurs in Knitro operations. + */ + private void setJacobianMatrix(LfNetwork lfNetwork, JacobianMatrix jacobianMatrix, + List> sortedEquationsToSolve, List listNonLinearConsts, + List listNonZerosCtsDense, List listNonZerosVarsDense, + List listNonZerosCtsSparse, List listNonZerosVarsSparse) throws KNException { + + int numVar = equationSystem.getIndex().getSortedVariablesToFind().size(); + if (knitroParameters.getGradientComputationMode() == 1) { // User routine to compute the Jacobian + if (knitroParameters.getGradientUserRoutine() == 1) { + // Dense method: all non-linear constraints are considered as a function of all variables. + buildDenseJacobianMatrix(numVar, listNonLinearConsts, listNonZerosCtsDense, listNonZerosVarsDense); + this.setJacNnzPattern(listNonZerosCtsDense, listNonZerosVarsDense); + } else if (knitroParameters.getGradientUserRoutine() == 2) { + // Sparse method: compute Jacobian only for variables the constraints depend on. + buildSparseJacobianMatrix(sortedEquationsToSolve, listNonLinearConsts, listNonZerosCtsSparse, listNonZerosVarsSparse); + this.setJacNnzPattern(listNonZerosCtsSparse, listNonZerosVarsSparse); + } + // Set the callback for gradient evaluations if the user directly passes the Jacobian to the solver. + this.setGradEvalCallback(new KnitroProblem.CallbackEvalG( + jacobianMatrix, + listNonZerosCtsDense, + listNonZerosVarsDense, + listNonZerosCtsSparse, + listNonZerosVarsSparse, + lfNetwork, + equationSystem, + knitroParameters + )); + } + } + } + + private void setSolverParameters(KNSolver solver) throws KNException { + + solver.setParam(KNConstants.KN_PARAM_GRADOPT, knitroParameters.getGradientComputationMode()); + solver.setParam(KNConstants.KN_PARAM_FEASTOL, knitroParameters.getConvEps()); + solver.setParam(KNConstants.KN_PARAM_MAXIT, knitroParameters.getMaxIterations()); + } + + /** + * Temporary to workaround Knitro finalization issue - FIXME on Knitro v14.2 release which will have the proper fix + */ + @SuppressWarnings({"java:S1113", "java:S1874", "java:S5738"}) + public static class FinalizeSafeSolver extends KNSolver implements AutoCloseable { + + public FinalizeSafeSolver(KNBaseProblem problem) throws KNException { + super(problem); + } + + @Override + protected void finalize() { + // no-op. + } + + @Override + public void close() { + this.KNFree(); + } + } + + @Override + public AcSolverResult run(VoltageInitializer voltageInitializer, ReportNode reportNode) { + int nbIter; + AcSolverStatus acStatus; + KnitroProblem instance; + + try { + // Create a new Knitro problem instance + instance = new KnitroProblem(network, equationSystem, targetVector, voltageInitializer, j); + } catch (KNException e) { + throw new PowsyblException("Exception while trying to build Knitro Problem", e); + } + + try (FinalizeSafeSolver solver = new FinalizeSafeSolver(instance)) { + // Initialize problem + solver.initProblem(); + + // Set solver parameters + setSolverParameters(solver); + + // Solve + solver.solve(); + KNSolution solution = solver.getSolution(); + List constraintValues = solver.getConstraintValues(); + acStatus = KnitroStatus.fromStatusCode(solution.getStatus()).toAcSolverStatus(); + logKnitroStatus(KnitroStatus.fromStatusCode(solution.getStatus())); + nbIter = solver.getNumberIters(); + + // Log solution + LOGGER.info("Optimal objective value = {}", solution.getObjValue()); + LOGGER.info("Feasibility violation = {}", solver.getAbsFeasError()); + LOGGER.info("Optimality violation = {}", solver.getAbsOptError()); + + LOGGER.debug("Optimal x"); + for (int i = 0; i < solution.getX().size(); i++) { + LOGGER.debug(" x[{}] = {}", i, solution.getX().get(i)); + } + LOGGER.debug("Optimal constraint values (with corresponding multiplier)"); + for (int i = 0; i < instance.getNumCons(); i++) { + LOGGER.debug(" c[{}] = {} (lambda = {} )", i, constraintValues.get(i), solution.getLambda().get(i)); + } + LOGGER.debug("Constraint violation"); + for (int i = 0; i < instance.getNumCons(); i++) { + LOGGER.debug(" violation[{}] = {} ", i, solver.getConViol(i)); + } + + // Update the network if required + if (acStatus == AcSolverStatus.CONVERGED || knitroParameters.isAlwaysUpdateNetwork()) { + equationSystem.getStateVector().set(toArray(solution.getX())); // update equation system + for (Equation equation : equationSystem.getEquations()) { // update terms + for (EquationTerm term : equation.getTerms()) { + term.setStateVector(equationSystem.getStateVector()); + } + } + AcSolverUtil.updateNetwork(network, equationSystem); // update network + } + + } catch (KNException e) { + throw new PowsyblException("Exception while trying to solve with Knitro", e); + } + + double slackBusActivePowerMismatch = network.getSlackBuses().stream().mapToDouble(LfBus::getMismatchP).sum(); + return new AcSolverResult(acStatus, nbIter, slackBusActivePowerMismatch); + } + +} diff --git a/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverFactory.java b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverFactory.java new file mode 100644 index 0000000..a49ea16 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverFactory.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2024, Artelys (http://www.artelys.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.google.auto.service.AutoService; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.ac.AcLoadFlowParameters; +import com.powsybl.openloadflow.ac.equations.AcEquationType; +import com.powsybl.openloadflow.ac.equations.AcVariableType; +import com.powsybl.openloadflow.ac.solver.*; +import com.powsybl.openloadflow.equations.EquationSystem; +import com.powsybl.openloadflow.equations.EquationVector; +import com.powsybl.openloadflow.equations.JacobianMatrix; +import com.powsybl.openloadflow.equations.TargetVector; +import com.powsybl.openloadflow.network.LfNetwork; + +/** + * @author Pierre Arvy {@literal } + */ +@AutoService(AcSolverFactory.class) +public class KnitroSolverFactory implements AcSolverFactory { + + public static final String NAME = "KNITRO"; + + @Override + public String getName() { + return NAME; + } + + @Override + public AcSolverParameters createParameters(LoadFlowParameters parameters) { + OpenLoadFlowParameters parametersExt = OpenLoadFlowParameters.get(parameters); + KnitroSolverParameters knitroSolverParameters = new KnitroSolverParameters() + .setStateVectorScalingMode(parametersExt.getStateVectorScalingMode()) + .setLineSearchStateVectorScalingMaxIteration(parametersExt.getLineSearchStateVectorScalingMaxIteration()) + .setLineSearchStateVectorScalingStepFold(parametersExt.getLineSearchStateVectorScalingStepFold()) + .setMaxVoltageChangeStateVectorScalingMaxDv(parametersExt.getMaxVoltageChangeStateVectorScalingMaxDv()) + .setMaxVoltageChangeStateVectorScalingMaxDphi(parametersExt.getMaxVoltageChangeStateVectorScalingMaxDphi()) + .setAlwaysUpdateNetwork(parametersExt.isAlwaysUpdateNetwork()); + if (parameters.getExtension(KnitroLoadFlowParameters.class) != null) { + knitroSolverParameters + .setGradientComputationMode(parameters.getExtension(KnitroLoadFlowParameters.class).getGradientComputationMode()) + .setGradientUserRoutine(parameters.getExtension(KnitroLoadFlowParameters.class).getGradientUserRoutine()) + .setLowerVoltageBound(parameters.getExtension(KnitroLoadFlowParameters.class).getLowerVoltageBound()) + .setUpperVoltageBound(parameters.getExtension(KnitroLoadFlowParameters.class).getUpperVoltageBound()) + .setMaxIterations(parameters.getExtension(KnitroLoadFlowParameters.class).getMaxIterations()) + .setConvEps(parameters.getExtension(KnitroLoadFlowParameters.class).getConvEps()); + + } + return knitroSolverParameters; + } + + @Override + public AcSolver create(LfNetwork network, AcLoadFlowParameters parameters, EquationSystem equationSystem, + JacobianMatrix j, TargetVector targetVector, + EquationVector equationVector) { + return new KnitroSolver(network, (KnitroSolverParameters) parameters.getAcSolverParameters(), equationSystem, + j, targetVector, equationVector, parameters.isDetailedReport()); + } + +} diff --git a/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverParameters.java b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverParameters.java new file mode 100644 index 0000000..310dbe5 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverParameters.java @@ -0,0 +1,189 @@ +/** + * Copyright (c) 2024, Artelys (http://www.artelys.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.openloadflow.ac.solver.*; + +import java.util.Objects; + +/** + * @author Pierre Arvy {@literal } + * @author Jeanne Archambault {@literal } + */ + +public class KnitroSolverParameters implements AcSolverParameters { + + public static final int DEFAULT_GRADIENT_COMPUTATION_MODE = 1; // Specifies how the Jacobian matrix is computed + public static final int DEFAULT_GRADIENT_USER_ROUTINE = 2; // If the user chooses to pass the exact Jacobian to knitro, specifies the sparsity pattern for the Jacobian matrix. + public static final double DEFAULT_LOWER_VOLTAGE_BOUND = 0.5; // Lower bound for voltage magnitude + public static final double DEFAULT_UPPER_VOLTAGE_BOUND = 1.5; // Upper bound for voltage magnitude + public static final int DEFAULT_MAX_ITERATIONS = 200; + public static final double DEFAULT_STOPPING_CRITERIA = Math.pow(10, -6); + public static final StateVectorScalingMode DEFAULT_STATE_VECTOR_SCALING_MODE = StateVectorScalingMode.NONE; + public static final boolean ALWAYS_UPDATE_NETWORK_DEFAULT_VALUE = false; + + private StateVectorScalingMode stateVectorScalingMode = DEFAULT_STATE_VECTOR_SCALING_MODE; + + private int lineSearchStateVectorScalingMaxIteration = LineSearchStateVectorScaling.DEFAULT_MAX_ITERATION; + + private double lineSearchStateVectorScalingStepFold = LineSearchStateVectorScaling.DEFAULT_STEP_FOLD; + + private double maxVoltageChangeStateVectorScalingMaxDv = MaxVoltageChangeStateVectorScaling.DEFAULT_MAX_DV; + + private double maxVoltageChangeStateVectorScalingMaxDphi = MaxVoltageChangeStateVectorScaling.DEFAULT_MAX_DPHI; + + private int gradientComputationMode = DEFAULT_GRADIENT_COMPUTATION_MODE; + + private int gradientUserRoutine = DEFAULT_GRADIENT_USER_ROUTINE; + + private double lowerVoltageBound = DEFAULT_LOWER_VOLTAGE_BOUND; + + private double upperVoltageBound = DEFAULT_UPPER_VOLTAGE_BOUND; + + private boolean alwaysUpdateNetwork = ALWAYS_UPDATE_NETWORK_DEFAULT_VALUE; + + private int maxIterations = DEFAULT_MAX_ITERATIONS; + + private double convEps = DEFAULT_STOPPING_CRITERIA; + + public int getGradientComputationMode() { + return gradientComputationMode; + } + + public KnitroSolverParameters setGradientComputationMode(int gradientComputationMode) { + if (gradientComputationMode < 1 || gradientComputationMode > 3) { + throw new IllegalArgumentException("Knitro gradient computation mode must be between 1 and 3"); + } + this.gradientComputationMode = gradientComputationMode; + return this; + } + + public int getGradientUserRoutine() { + return gradientUserRoutine; + } + + public KnitroSolverParameters setGradientUserRoutine(int gradientUserRoutine) { + if (gradientUserRoutine < 1 || gradientUserRoutine > 2) { + throw new IllegalArgumentException("User routine must be between 1 and 2"); + } + this.gradientUserRoutine = gradientUserRoutine; + return this; + } + + public double getLowerVoltageBound() { + return lowerVoltageBound; + } + + public KnitroSolverParameters setLowerVoltageBound(double lowerVoltageBound) { + if (lowerVoltageBound < 0) { + throw new IllegalArgumentException("Realistic voltage bounds must strictly greater than 0"); + } + this.lowerVoltageBound = lowerVoltageBound; + return this; + } + + public double getUpperVoltageBound() { + return upperVoltageBound; + } + + public KnitroSolverParameters setUpperVoltageBound(double upperVoltageBound) { + if (upperVoltageBound < 0) { + throw new IllegalArgumentException("Realistic voltage bounds must strictly greater than 0"); + } + if (upperVoltageBound <= lowerVoltageBound) { + throw new IllegalArgumentException("Realistic voltage upper bounds must greater than lower bounds"); + } + this.upperVoltageBound = upperVoltageBound; + return this; + } + + public StateVectorScalingMode getStateVectorScalingMode() { + return stateVectorScalingMode; + } + + public KnitroSolverParameters setStateVectorScalingMode(StateVectorScalingMode stateVectorScalingMode) { + this.stateVectorScalingMode = Objects.requireNonNull(stateVectorScalingMode); + return this; + } + + public boolean isAlwaysUpdateNetwork() { + return alwaysUpdateNetwork; + } + + public KnitroSolverParameters setAlwaysUpdateNetwork(boolean alwaysUpdateNetwork) { + this.alwaysUpdateNetwork = alwaysUpdateNetwork; + return this; + } + + public int getLineSearchStateVectorScalingMaxIteration() { + return lineSearchStateVectorScalingMaxIteration; + } + + public KnitroSolverParameters setLineSearchStateVectorScalingMaxIteration(int lineSearchStateVectorScalingMaxIteration) { + this.lineSearchStateVectorScalingMaxIteration = lineSearchStateVectorScalingMaxIteration; + return this; + + } + + public double getLineSearchStateVectorScalingStepFold() { + return lineSearchStateVectorScalingStepFold; + } + + public KnitroSolverParameters setLineSearchStateVectorScalingStepFold(double lineSearchStateVectorScalingStepFold) { + this.lineSearchStateVectorScalingStepFold = lineSearchStateVectorScalingStepFold; + return this; + } + + public double getMaxVoltageChangeStateVectorScalingMaxDv() { + return maxVoltageChangeStateVectorScalingMaxDv; + } + + public KnitroSolverParameters setMaxVoltageChangeStateVectorScalingMaxDv(double maxVoltageChangeStateVectorScalingMaxDv) { + this.maxVoltageChangeStateVectorScalingMaxDv = maxVoltageChangeStateVectorScalingMaxDv; + return this; + } + + public double getMaxVoltageChangeStateVectorScalingMaxDphi() { + return maxVoltageChangeStateVectorScalingMaxDphi; + } + + public KnitroSolverParameters setMaxVoltageChangeStateVectorScalingMaxDphi(double maxVoltageChangeStateVectorScalingMaxDphi) { + this.maxVoltageChangeStateVectorScalingMaxDphi = maxVoltageChangeStateVectorScalingMaxDphi; + return this; + } + + public int getMaxIterations() { + return maxIterations; + } + + public KnitroSolverParameters setMaxIterations(int maxIterations) { + this.maxIterations = maxIterations; + return this; + } + + public double getConvEps() { + return convEps; + } + + public KnitroSolverParameters setConvEps(double convEps) { + this.convEps = convEps; + return this; + } + + @Override + public String toString() { + return "KnitroSolverParameters(" + + "gradientComputationMode=" + gradientComputationMode + + ", stoppingCriteria=" + convEps + + ", minRealisticVoltage=" + lowerVoltageBound + + ", maxRealisticVoltage=" + upperVoltageBound + + ", alwaysUpdateNetwork=" + alwaysUpdateNetwork + + ", maxIterations=" + maxIterations + + ')'; + } +} diff --git a/src/main/java/com/powsybl/openloadflow/knitro/solver/NonLinearExternalSolverUtils.java b/src/main/java/com/powsybl/openloadflow/knitro/solver/NonLinearExternalSolverUtils.java new file mode 100644 index 0000000..b1d13d5 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/knitro/solver/NonLinearExternalSolverUtils.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2024, Artelys (http://www.artelys.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.openloadflow.ac.equations.AcVariableType; +import com.powsybl.openloadflow.ac.equations.AcEquationType; +import com.powsybl.openloadflow.equations.EquationTerm; +import com.powsybl.openloadflow.equations.VariableEquationTerm; + +import java.util.*; + +/** + * @author Jeanne Archambault {@literal } + */ +public final class NonLinearExternalSolverUtils { + + // List of always linear constraints + private static final List LINEAR_CONSTRAINTS_TYPES = new ArrayList<>(Arrays.asList( + AcEquationType.BUS_TARGET_PHI, + AcEquationType.DUMMY_TARGET_P, + AcEquationType.DUMMY_TARGET_Q, + AcEquationType.ZERO_V, + AcEquationType.ZERO_PHI, + AcEquationType.DISTR_SHUNT_B, + AcEquationType.DISTR_RHO, + AcEquationType.SHUNT_TARGET_B, + AcEquationType.BRANCH_TARGET_ALPHA1, + AcEquationType.BRANCH_TARGET_RHO1 + )); + + // Classifies a constraint as linear or non-linear based on its type and terms + public static boolean isLinear(AcEquationType typeEq, List> terms) { + // Check if the constraint type is BUS_TARGET_V + if (typeEq == AcEquationType.BUS_TARGET_V) { + return terms.size() == 1; // If there's only one term, it is linear + } + return LINEAR_CONSTRAINTS_TYPES.contains(typeEq); + } + + // Return lists of variables and coefficients to pass to Knitro for a linear constraint + public VarAndCoefList getLinearConstraint(AcEquationType typeEq, List> terms) throws UnsupportedOperationException { + VarAndCoefList varAndCoefList = null; + + // Check if the constraint is linear + if (isLinear(typeEq, terms)) { + switch (typeEq) { + case BUS_TARGET_V, BUS_TARGET_PHI, DUMMY_TARGET_P, DUMMY_TARGET_Q, SHUNT_TARGET_B, BRANCH_TARGET_ALPHA1, BRANCH_TARGET_RHO1: + // BUS_TARGET_V should be treated as linear + varAndCoefList = addConstraintConstantTarget(terms); + break; + case DISTR_SHUNT_B, DISTR_RHO: + varAndCoefList = addConstraintDistrQ(terms); + break; + case ZERO_V, ZERO_PHI: + varAndCoefList = addConstraintZero(terms); + break; + default: + throw new UnsupportedOperationException("Non-linear equation : " + typeEq); + } + } + return varAndCoefList; + } + + public record VarAndCoefList(List listIdVar, List listCoef) { + } + + public VarAndCoefList addConstraintConstantTarget(List> terms) { + // get the variable V/Theta/DummyP/DummyQ/... corresponding to the constraint + int idVar = terms.get(0).getVariables().get(0).getRow(); + return new VarAndCoefList(List.of(idVar), List.of(1.0)); + } + + public VarAndCoefList addConstraintZero(List> terms) { + // get the variables Vi and Vj / Thetai and Thetaj corresponding to the constraint + int idVari = terms.get(0).getVariables().get(0).getRow(); + int idVarj = terms.get(1).getVariables().get(0).getRow(); + return new VarAndCoefList(Arrays.asList(idVari, idVarj), Arrays.asList(1.0, -1.0)); + } + + public VarAndCoefList addConstraintDistrQ(List> terms) { + // get the variables corresponding to the constraint + List listVar = new ArrayList<>(); + List listCoef = new ArrayList<>(); + for (EquationTerm equationTerm : terms) { + double scalar = 0.0; + if (((EquationTerm.MultiplyByScalarEquationTerm) equationTerm).getChildren().get(0) instanceof VariableEquationTerm) { + scalar = ((EquationTerm.MultiplyByScalarEquationTerm) equationTerm).getScalar(); + } else if (((EquationTerm.MultiplyByScalarEquationTerm) equationTerm).getChildren().get(0) instanceof EquationTerm.MultiplyByScalarEquationTerm) { + scalar = ((EquationTerm.MultiplyByScalarEquationTerm) equationTerm).getScalar(); + scalar *= ((EquationTerm.MultiplyByScalarEquationTerm) ((EquationTerm.MultiplyByScalarEquationTerm) equationTerm).getChildren().get(0)).getScalar(); + } + listVar.add(((EquationTerm.MultiplyByScalarEquationTerm) equationTerm).getChildren().get(0) .getVariables().get(0).getRow()); + listCoef.add(scalar); + } + return new VarAndCoefList(listVar, listCoef); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlow3wtTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlow3wtTest.java new file mode 100644 index 0000000..5805560 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlow3wtTest.java @@ -0,0 +1,168 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.network.T3wtFactory; +import com.powsybl.openloadflow.util.LoadFlowAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AcLoadFlow3wtTest { + + private Network network; + private Substation s; + private Bus bus1; + private Bus bus2; + private Bus bus3; + private ThreeWindingsTransformer twt; + + private LoadFlow.Runner loadFlowRunner; + + private LoadFlowParameters parameters; + + @BeforeEach + void setUp() { + network = T3wtFactory.create(); + s = network.getSubstation("s"); + bus1 = network.getBusBreakerView().getBus("b1"); + bus2 = network.getBusBreakerView().getBus("b2"); + bus3 = network.getBusBreakerView().getBus("b3"); + twt = network.getThreeWindingsTransformer("3wt"); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters().setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void test() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(405, bus1); + LoadFlowAssert.assertAngleEquals(0, bus1); + assertVoltageEquals(235.132, bus2); + LoadFlowAssert.assertAngleEquals(-2.259241, bus2); + assertVoltageEquals(20.834, bus3); + LoadFlowAssert.assertAngleEquals(-2.721885, bus3); + assertActivePowerEquals(161.095, twt.getLeg1().getTerminal()); + assertReactivePowerEquals(81.884, twt.getLeg1().getTerminal()); + assertActivePowerEquals(-161, twt.getLeg2().getTerminal()); + assertReactivePowerEquals(-74, twt.getLeg2().getTerminal()); + assertActivePowerEquals(0, twt.getLeg3().getTerminal()); + assertReactivePowerEquals(0, twt.getLeg3().getTerminal()); + } + + @Test + void testWithRatioTapChangers() { + // create a ratio tap changer on leg 1 and check that voltages on leg 2 and 3 have changed compare to previous + // test + twt.getLeg1().newRatioTapChanger() + .setLoadTapChangingCapabilities(false) + .setTapPosition(0) + .beginStep() + .setR(5) + .setX(10) + .setRho(0.9) + .endStep() + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(405, bus1); + assertVoltageEquals(209.886, bus2); + assertVoltageEquals(18.582, bus3); + } + + @Test + void testWithPhaseTapChangers() { + // create a phase tap changer at leg 2 with a zero phase shifting + PhaseTapChanger ptc = twt.getLeg2().newPhaseTapChanger() + .setTapPosition(0) + .beginStep() + .setAlpha(0) + .endStep() + .add(); + // create a transformer between bus 1 / bus2 in parallel of leg1 / leg2 + TwoWindingsTransformer twtParallel = s.newTwoWindingsTransformer() + .setId("2wt") + .setBus1("b1") + .setConnectableBus1("b1") + .setBus2("b2") + .setConnectableBus2("b2") + .setRatedU1(390) + .setRatedU2(220) + .setR(4) + .setX(80) + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(21.97, twtParallel.getTerminal1()); + assertActivePowerEquals(-139.088, twt.getLeg2().getTerminal()); + + // set the phase shifting to 10 degree and check active flow change + ptc.getStep(0).setAlpha(10); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(121.691, twtParallel.getTerminal1()); + assertActivePowerEquals(-40.452, twt.getLeg2().getTerminal()); + } + + @Test + void testSplitShuntAdmittance() { + parameters.setTwtSplitShuntAdmittance(false); + twt.getLeg1().setB(0.00004); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(405, bus1); + LoadFlowAssert.assertAngleEquals(0, bus1); + assertVoltageEquals(235.132, bus2); + LoadFlowAssert.assertAngleEquals(-2.259241, bus2); + assertVoltageEquals(20.834, bus3); + LoadFlowAssert.assertAngleEquals(-2.721885, bus3); + assertActivePowerEquals(161.095, twt.getLeg1().getTerminal()); + assertReactivePowerEquals(75.323, twt.getLeg1().getTerminal()); + assertActivePowerEquals(-161, twt.getLeg2().getTerminal()); + assertReactivePowerEquals(-74, twt.getLeg2().getTerminal()); + assertActivePowerEquals(0, twt.getLeg3().getTerminal()); + assertReactivePowerEquals(0, twt.getLeg3().getTerminal()); + + parameters.setTwtSplitShuntAdmittance(true); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(405, bus1); + LoadFlowAssert.assertAngleEquals(0, bus1); + assertVoltageEquals(235.358, bus2); + LoadFlowAssert.assertAngleEquals(-2.257583, bus2); + assertVoltageEquals(20.854, bus3); + LoadFlowAssert.assertAngleEquals(-2.719334, bus3); + assertActivePowerEquals(161.095, twt.getLeg1().getTerminal()); + assertReactivePowerEquals(75.314, twt.getLeg1().getTerminal()); + assertActivePowerEquals(-161, twt.getLeg2().getTerminal()); + assertReactivePowerEquals(-74, twt.getLeg2().getTerminal()); + assertActivePowerEquals(0, twt.getLeg3().getTerminal()); + assertReactivePowerEquals(0, twt.getLeg3().getTerminal()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowBatteryTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowBatteryTest.java new file mode 100644 index 0000000..7bde9ea --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowBatteryTest.java @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Battery; +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.VoltageRegulationAdder; +import com.powsybl.iidm.network.test.BatteryNetworkFactory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.util.LoadFlowAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertVoltageEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AcLoadFlowBatteryTest { + + private Network network; + private Bus genBus; + private Bus batBus; + private Generator generator; + private Battery battery1; + private Battery battery2; + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + + @BeforeEach + void setUp() { + network = BatteryNetworkFactory.create(); + genBus = network.getBusBreakerView().getBus("NGEN"); + batBus = network.getBusBreakerView().getBus("NBAT"); + generator = network.getGenerator("GEN"); + generator.setMinP(0).setMaxP(1000).setTargetV(401.); + battery1 = network.getBattery("BAT"); + battery1.setMinP(-1000).setMaxP(1000).setTargetQ(0).setTargetP(0); + battery2 = network.getBattery("BAT2"); + battery2.setTargetP(-1000).setMaxP(1000); + + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters().setUseReactiveLimits(true) + .setDistributedSlack(true); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2).setMaxIterations(0); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void test() { + battery2.newExtension(VoltageRegulationAdder.class) + .withTargetV(401) + .withVoltageRegulatorOn(false) + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(401, genBus); + LoadFlowAssert.assertAngleEquals(5.916585, genBus); + assertVoltageEquals(397.660, batBus); + LoadFlowAssert.assertAngleEquals(0.0, batBus); + } + + @Test + void testWithVoltageControl() { + generator.setVoltageRegulatorOn(false); + battery2.newExtension(VoltageRegulationAdder.class) + .withTargetV(401) + .withVoltageRegulatorOn(true) + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(417.328, genBus); + LoadFlowAssert.assertAngleEquals(5.468361, genBus); + assertVoltageEquals(401.0, batBus); + LoadFlowAssert.assertAngleEquals(0.0, batBus); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowBoundaryTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowBoundaryTest.java new file mode 100644 index 0000000..ec20acd --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowBoundaryTest.java @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.BoundaryFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.network.VoltageControlNetworkFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class AcLoadFlowBoundaryTest { + + private Network network; + private Bus bus1; + private Bus bus2; + private DanglingLine dl1; + private Generator g1; + + private LoadFlow.Runner loadFlowRunner; + + private LoadFlowParameters parameters; + + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + network = BoundaryFactory.create(); + bus1 = network.getBusBreakerView().getBus("b1"); + bus2 = network.getBusBreakerView().getBus("b2"); + dl1 = network.getDanglingLine("dl1"); + g1 = network.getGenerator("g1"); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters() + .setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void test() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(390, bus1); + assertAngleEquals(0.058104, bus1); + assertVoltageEquals(388.582864, bus2); + assertAngleEquals(0, bus2); + assertActivePowerEquals(101.303, dl1.getTerminal()); + assertReactivePowerEquals(149.764, dl1.getTerminal()); + } + + @Test + void testWithVoltageRegulationOn() { + g1.setTargetQ(0); + g1.setVoltageRegulatorOn(false); + // FIXME: no targetV here? + dl1.getGeneration().setVoltageRegulationOn(true); + dl1.getGeneration().setMinP(0); + dl1.getGeneration().setMaxP(10); + dl1.getGeneration().newMinMaxReactiveLimits() + .setMinQ(-100) + .setMaxQ(100) + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(390.440, bus1); + assertAngleEquals(0.114371, bus1); + assertVoltageEquals(390.181, bus2); + assertAngleEquals(0, bus2); + assertActivePowerEquals(101.2, dl1.getTerminal()); + assertReactivePowerEquals(-0.202, dl1.getTerminal()); + + parameters.setDistributedSlack(true) + .setUseReactiveLimits(true); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + + assertVoltageEquals(390.440, bus1); + assertAngleEquals(0.114371, bus1); + assertVoltageEquals(390.181, bus2); + assertAngleEquals(0, bus2); + assertActivePowerEquals(101.2, dl1.getTerminal()); + assertReactivePowerEquals(-0.202, dl1.getTerminal()); + } + + @Test + void testWithXnode() { + Network network = BoundaryFactory.createWithXnode(); + parameters.setUseReactiveLimits(true); + parameters.setDistributedSlack(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(400.000, network.getBusBreakerView().getBus("b1")); + assertVoltageEquals(399.999, network.getBusBreakerView().getBus("xnode")); + assertVoltageEquals(399.999, network.getBusBreakerView().getBus("b3")); + assertVoltageEquals(400.000, network.getBusBreakerView().getBus("b4")); + } + + @Test + void testWithTieLine() { + Network network = BoundaryFactory.createWithTieLine(); + parameters.setUseReactiveLimits(true); + parameters.setDistributedSlack(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(400.000, network.getBusBreakerView().getBus("b1")); + assertVoltageEquals(399.999, network.getBusBreakerView().getBus("b3")); + assertVoltageEquals(400.000, network.getBusBreakerView().getBus("b4")); + assertReactivePowerEquals(0.0044, network.getLine("l34").getTerminal2()); + + TieLine line = network.getTieLine("t12"); + line.getDanglingLine1().getTerminal().disconnect(); + line.getDanglingLine1().getTerminal().disconnect(); + loadFlowRunner.run(network, parameters); + assertVoltageEquals(400.0, network.getBusBreakerView().getBus("b3")); + System.out.println(network.getLine("l34").getTerminal2().getQ()); + assertReactivePowerEquals(-0, network.getLine("l34").getTerminal2()); + } + + @Test + void testEquivalentBranch() { + Network network = VoltageControlNetworkFactory.createNetworkWithT2wt(); + network.newLine() + .setId("LINE_23") + .setBus1("BUS_2") + .setBus2("BUS_3") + .setR(0.0) + .setX(100) + .add(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(135.0, network.getBusBreakerView().getBus("BUS_1")); + assertVoltageEquals(127.198, network.getBusBreakerView().getBus("BUS_2")); + assertVoltageEquals(40.19, network.getBusBreakerView().getBus("BUS_3")); + } + + @Test + void testWithNonImpedantDanglingLine() { + dl1.setR(0.0).setX(0.0); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(101.0, dl1.getTerminal()); + assertReactivePowerEquals(150.0, dl1.getTerminal()); + + dl1.getGeneration().setVoltageRegulationOn(true); + dl1.getGeneration().setTargetV(390.0); + dl1.getGeneration().setMinP(0); + dl1.getGeneration().setMaxP(10); + dl1.getGeneration().newMinMaxReactiveLimits() + .setMinQ(-100) + .setMaxQ(100) + .add(); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertActivePowerEquals(101.0, dl1.getTerminal()); + assertReactivePowerEquals(-33.888, dl1.getTerminal()); + + parameters.setDc(true); + LoadFlowResult result3 = loadFlowRunner.run(network, parameters); + assertTrue(result3.isFullyConverged()); + assertActivePowerEquals(101.0, dl1.getTerminal()); + assertReactivePowerEquals(Double.NaN, dl1.getTerminal()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowEurostagTutorialExample1Test.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowEurostagTutorialExample1Test.java new file mode 100644 index 0000000..7bb774e --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowEurostagTutorialExample1Test.java @@ -0,0 +1,495 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.computation.local.LocalComputationManager; +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.SlackTerminal; +import com.powsybl.iidm.network.extensions.SlackTerminalAdder; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.EurostagFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class AcLoadFlowEurostagTutorialExample1Test { + + private Network network; + private Bus genBus; + private Bus bus1; + private Bus bus2; + private Bus loadBus; + private Line line1; + private Line line2; + private Generator gen; + private VoltageLevel vlgen; + private VoltageLevel vlload; + private VoltageLevel vlhv1; + private VoltageLevel vlhv2; + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + genBus = network.getBusBreakerView().getBus("NGEN"); + bus1 = network.getBusBreakerView().getBus("NHV1"); + bus2 = network.getBusBreakerView().getBus("NHV2"); + loadBus = network.getBusBreakerView().getBus("NLOAD"); + line1 = network.getLine("NHV1_NHV2_1"); + line2 = network.getLine("NHV1_NHV2_2"); + gen = network.getGenerator("GEN"); + vlgen = network.getVoltageLevel("VLGEN"); + vlload = network.getVoltageLevel("VLLOAD"); + vlhv1 = network.getVoltageLevel("VLHV1"); + vlhv2 = network.getVoltageLevel("VLHV2"); + + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters().setUseReactiveLimits(false) + .setDistributedSlack(false); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.FIRST) +// .setGradientComputationModeKnitro(2) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void baseCaseTest() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(24.5, genBus); + assertAngleEquals(0, genBus); + assertVoltageEquals(402.143, bus1); + assertAngleEquals(-2.325965, bus1); + assertVoltageEquals(389.953, bus2); + assertAngleEquals(-5.832323, bus2); + assertVoltageEquals(147.578, loadBus); + assertAngleEquals(-11.940451, loadBus); + assertActivePowerEquals(302.444, line1.getTerminal1()); + assertReactivePowerEquals(98.74, line1.getTerminal1()); + assertActivePowerEquals(-300.434, line1.getTerminal2()); + assertReactivePowerEquals(-137.188, line1.getTerminal2()); + assertActivePowerEquals(302.444, line2.getTerminal1()); + assertReactivePowerEquals(98.74, line2.getTerminal1()); + assertActivePowerEquals(-300.434, line2.getTerminal2()); + assertReactivePowerEquals(-137.188, line2.getTerminal2()); + + // check pv bus reactive power update + assertReactivePowerEquals(-225.282, gen.getTerminal()); + } + + @Test + void dcLfVoltageInitTest() { + parameters.setVoltageInitMode(LoadFlowParameters.VoltageInitMode.DC_VALUES); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(3, result.getComponentResults().get(0).getIterationCount()); + } + + @Test + void line1Side1DeconnectionTest() { + line1.getTerminal1().disconnect(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(24.5, genBus); + assertAngleEquals(0, genBus); + assertVoltageEquals(400.277, bus1); + assertAngleEquals(-2.348788, bus1); + assertVoltageEquals(374.537, bus2); + assertAngleEquals(-9.719157, bus2); + assertVoltageEquals(141.103, loadBus); + assertAngleEquals(-16.372920, loadBus); + assertActivePowerEquals(0, line1.getTerminal1()); + assertReactivePowerEquals(0, line1.getTerminal1()); + assertActivePowerEquals(0.016, line1.getTerminal2()); + assertReactivePowerEquals(-54.321, line1.getTerminal2()); + assertActivePowerEquals(609.544, line2.getTerminal1()); + assertReactivePowerEquals(263.412, line2.getTerminal1()); + assertActivePowerEquals(-600.965, line2.getTerminal2()); + assertReactivePowerEquals(-227.04, line2.getTerminal2()); + } + + @Test + void line1Side2DeconnectionTest() { + line1.getTerminal2().disconnect(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(24.5, genBus); + assertAngleEquals(0, genBus); + assertVoltageEquals(400.120, bus1); + assertAngleEquals(-2.352669, bus1); + assertVoltageEquals(368.797, bus2); + assertAngleEquals(-9.773427, bus2); + assertVoltageEquals(138.678, loadBus); + assertAngleEquals(-16.649943, loadBus); + assertActivePowerEquals(0.01812, line1.getTerminal1()); + assertReactivePowerEquals(-61.995296, line1.getTerminal1()); + assertActivePowerEquals(0, line1.getTerminal2()); + assertReactivePowerEquals(0, line1.getTerminal2()); + assertActivePowerEquals(610.417, line2.getTerminal1()); + assertReactivePowerEquals(330.862, line2.getTerminal1()); + assertActivePowerEquals(-600.983, line2.getTerminal2()); + assertReactivePowerEquals(-284.230, line2.getTerminal2()); + } + + @Test + void line1DeconnectionTest() { + line1.getTerminal1().disconnect(); + line1.getTerminal2().disconnect(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(24.5, genBus); + assertAngleEquals(0, genBus); + assertVoltageEquals(398.265, bus1); + assertAngleEquals(-2.358007, bus1); + assertVoltageEquals(366.585, bus2); + assertAngleEquals(-9.857221, bus2); + assertVoltageEquals(137.742, loadBus); + assertAngleEquals(-16.822678, loadBus); + assertUndefinedActivePower(line1.getTerminal1()); + assertUndefinedReactivePower(line1.getTerminal2()); + assertUndefinedActivePower(line1.getTerminal1()); + assertUndefinedReactivePower(line1.getTerminal2()); + assertActivePowerEquals(610.562, line2.getTerminal1()); + assertReactivePowerEquals(334.056, line2.getTerminal1()); + assertActivePowerEquals(-600.996, line2.getTerminal2()); + assertReactivePowerEquals(-285.379, line2.getTerminal2()); + } + + @Test + void shuntCompensatorTest() { + loadBus.getVoltageLevel().newShuntCompensator() + .setId("SC") + .setBus(loadBus.getId()) + .setConnectableBus(loadBus.getId()) + .setSectionCount(1) + .newLinearModel() + .setBPerSection(3.25 * Math.pow(10, -3)) + .setMaximumSectionCount(1) + .add() + .add(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(152.327, loadBus); + assertReactivePowerEquals(52.988, line1.getTerminal1()); + assertReactivePowerEquals(-95.063, line1.getTerminal2()); + assertReactivePowerEquals(52.988, line2.getTerminal1()); + assertReactivePowerEquals(-95.063, line2.getTerminal2()); + } + + @Test + void invalidTargetQIssueTest() { + // create a generator with a targetP to 0 and a minP > 0 so that the generator will be discarded from voltage + // regulation + // targetQ is not defined so value is NaN + Generator g1 = loadBus.getVoltageLevel().newGenerator() + .setId("g1") + .setBus(loadBus.getId()) + .setConnectableBus(loadBus.getId()) + .setEnergySource(EnergySource.THERMAL) + .setMinP(10) + .setMaxP(200) + .setTargetP(0) + .setTargetV(150) + .setVoltageRegulatorOn(true) + .add(); + // check that the issue that add an undefined targetQ (NaN) to bus generation sum is solved + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + } + + @Test + void slackBusLoadTest() { + parameters.setReadSlackBus(true); + parameters.setWriteSlackBus(true); + Load load = network.getLoad("LOAD"); + vlload.newExtension(SlackTerminalAdder.class) + .withTerminal(load.getTerminal()) + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertNotNull(vlload.getExtension(SlackTerminal.class)); + assertNull(vlgen.getExtension(SlackTerminal.class)); + assertNull(vlhv1.getExtension(SlackTerminal.class)); + assertNull(vlhv2.getExtension(SlackTerminal.class)); + } + + @Test + void slackBusWriteTest() { + parameters.setWriteSlackBus(true); + assertNull(vlgen.getExtension(SlackTerminal.class)); + assertNull(vlload.getExtension(SlackTerminal.class)); + assertNull(vlhv1.getExtension(SlackTerminal.class)); + assertNull(vlhv2.getExtension(SlackTerminal.class)); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertNotNull(vlgen.getExtension(SlackTerminal.class)); + assertNull(vlload.getExtension(SlackTerminal.class)); + assertNull(vlhv1.getExtension(SlackTerminal.class)); + assertNull(vlhv2.getExtension(SlackTerminal.class)); + } + + @Test + void lineWithDifferentNominalVoltageTest() { + network.getVoltageLevel("VLHV2").setNominalV(420); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(4, result.getComponentResults().get(0).getIterationCount()); + + assertVoltageEquals(24.5, genBus); + assertVoltageEquals(402.143, bus1); + assertVoltageEquals(389.953, bus2); + assertVoltageEquals(147.578, loadBus); + } + + @Test + void noGeneratorPvTest() { + // GEN is only generator with voltage control, disable it + network.getGenerator("GEN").setVoltageRegulatorOn(false); + + ReportNode reportNode = ReportNode.newRootReportNode() + .withMessageTemplate("unitTest", "") + .build(); + LoadFlowResult result = loadFlowRunner.run(network, VariantManagerConstants.INITIAL_VARIANT_ID, LocalComputationManager.getDefault(), parameters, reportNode); + assertFalse(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, result.getComponentResults().get(0).getStatus()); + + // also check there is a report added for this error + assertEquals(1, reportNode.getChildren().size()); + ReportNode lfReportNode = reportNode.getChildren().get(0); + assertEquals(1, lfReportNode.getChildren().size()); + ReportNode networkReportNode = lfReportNode.getChildren().get(0); + assertEquals("lfNetwork", networkReportNode.getMessageKey()); + ReportNode networkInfoReportNode = networkReportNode.getChildren().get(0); + assertEquals("networkInfo", networkInfoReportNode.getMessageKey()); + assertEquals(1, networkInfoReportNode.getChildren().size()); + assertEquals("Network must have at least one bus with generator voltage control enabled", + networkInfoReportNode.getChildren().get(0).getMessage()); + } + + @Test + void noGeneratorBecauseOfReactiveRangeTest() { + Generator gen = network.getGenerator("GEN"); + gen.newMinMaxReactiveLimits() + .setMinQ(0) + .setMaxQ(0) + .add(); + + parameters.setUseReactiveLimits(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertFalse(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, result.getComponentResults().get(0).getStatus()); + + // but if we do not take into account reactive limits in parameters, calculation should be ok + parameters.setUseReactiveLimits(false); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + } + + @Test + void testSeveralShunts() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network.getVoltageLevel("VLLOAD").newShuntCompensator() + .setId("SC") + .setBus("NLOAD") + .setConnectableBus("NLOAD") + .setSectionCount(1) + .newLinearModel() + .setBPerSection(3.25 * Math.pow(10, -3)) + .setMaximumSectionCount(1) + .add() + .add(); + network.getVoltageLevel("VLLOAD").newShuntCompensator() + .setId("SC2") + .setBus("NLOAD") + .setConnectableBus("NLOAD") + .setSectionCount(1) + .newLinearModel() + .setBPerSection(-3.25 * Math.pow(10, -3)) + .setMaximumSectionCount(1) + .add() + .add(); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setConnectedComponentMode(LoadFlowParameters.ConnectedComponentMode.ALL) + .setVoltageInitMode(LoadFlowParameters.VoltageInitMode.DC_VALUES); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(-70.783, network.getShuntCompensator("SC").getTerminal()); + assertReactivePowerEquals(70.783, network.getShuntCompensator("SC2").getTerminal()); + } + + @Test + void testSeveralShunts2() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network.getVoltageLevel("VLLOAD").newShuntCompensator() + .setId("SC") + .setBus("NLOAD") + .setConnectableBus("NLOAD") + .setSectionCount(1) + .newLinearModel() + .setBPerSection(0.0) + .setMaximumSectionCount(1) + .add() + .add(); + network.getVoltageLevel("VLLOAD").newShuntCompensator() + .setId("SC2") + .setBus("NLOAD") + .setConnectableBus("NLOAD") + .setSectionCount(1) + .newLinearModel() + .setBPerSection(0.0) + .setMaximumSectionCount(1) + .add() + .add(); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setConnectedComponentMode(LoadFlowParameters.ConnectedComponentMode.ALL) + .setVoltageInitMode(LoadFlowParameters.VoltageInitMode.DC_VALUES); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(0, network.getShuntCompensator("SC").getTerminal()); + assertReactivePowerEquals(0, network.getShuntCompensator("SC2").getTerminal()); + } + + @Test + void testEmptyNetwork() { + Network network = Network.create("empty", ""); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.getComponentResults().isEmpty()); + } + + @Test + void testWithDisconnectedGenerator() { + loadFlowRunner.run(network, parameters); + gen.getTerminal().disconnect(); + loadBus.getVoltageLevel().newGenerator() + .setId("g1") + .setBus(loadBus.getId()) + .setConnectableBus(loadBus.getId()) + .setEnergySource(EnergySource.THERMAL) + .setMinP(1) + .setMaxP(200) + .setTargetP(1) + .setTargetV(150) + .setVoltageRegulatorOn(true) + .add(); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertActivePowerEquals(Double.NaN, gen.getTerminal()); + assertReactivePowerEquals(Double.NaN, gen.getTerminal()); + } + + @Test + void testGeneratorReactiveLimits() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network.getGenerator("GEN").newMinMaxReactiveLimits().setMinQ(0).setMaxQ(150).add(); + network.getVoltageLevel("VLGEN").newGenerator().setId("GEN1") + .setBus("NGEN").setConnectableBus("NGEN") + .setMinP(-9999.99D).setMaxP(9999.99D) + .setVoltageRegulatorOn(true).setTargetV(24.5D) + .setTargetP(607.0D).setTargetQ(301.0D).add(); + // GEN1 reactive limits are not plausible => fallback into split Q equally + network.getGenerator("GEN1").newMinMaxReactiveLimits().setMinQ(-10000).setMaxQ(10000).add(); + LoadFlowParameters parameters = new LoadFlowParameters().setUseReactiveLimits(true) + .setDistributedSlack(false) + .setVoltageInitMode(LoadFlowParameters.VoltageInitMode.DC_VALUES); + loadFlowRunner.run(network, parameters); + network.getGenerators().forEach(gen -> { + if (gen.getReactiveLimits() instanceof MinMaxReactiveLimits) { + assertTrue(-gen.getTerminal().getQ() <= ((MinMaxReactiveLimits) gen.getReactiveLimits()).getMaxQ()); + assertTrue(-gen.getTerminal().getQ() >= ((MinMaxReactiveLimits) gen.getReactiveLimits()).getMinQ()); + } + }); + assertEquals(-150, network.getGenerator("GEN").getTerminal().getQ(), 0.01); + assertEquals(-153.86, network.getGenerator("GEN1").getTerminal().getQ(), 0.01); + } + + @Test + void testGeneratorsConnectedToSameBusNotControllingSameBus() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network.getVoltageLevel("VLGEN").newGenerator() + .setId("GEN2") + .setConnectableBus("NGEN") + .setBus("NGEN") + .setMinP(0) + .setMaxP(100) + .setTargetP(1) + .setVoltageRegulatorOn(true) + .setTargetV(148) + .setRegulatingTerminal(network.getLoad("LOAD").getTerminal()) + .add(); + loadFlowRunner.run(network); + assertVoltageEquals(24.5, network.getBusBreakerView().getBus("NGEN")); + assertVoltageEquals(147.57, network.getBusBreakerView().getBus("NLOAD")); + } + + @Test + void maxOuterLoopIterationTest() { + gen.setTargetP(1000); + parameters.setDistributedSlack(true); + parametersExt.setMaxOuterLoopIterations(1); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertFalse(result.isFullyConverged()); + assertEquals(LoadFlowResult.ComponentResult.Status.MAX_ITERATION_REACHED, result.getComponentResults().get(0).getStatus()); + } + + @Test + void testWriteReadSlackBus() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network.getVariantManager().cloneVariant(network.getVariantManager().getWorkingVariantId(), "newVariant"); + LoadFlowParameters parameters = new LoadFlowParameters().setWriteSlackBus(true); + OpenLoadFlowParameters openLoadFlowParameters = + OpenLoadFlowParameters.create(parameters).setSlackBusSelectionMode(SlackBusSelectionMode.FIRST); + LoadFlowResult result = LoadFlow.run(network, parameters); + assertTrue(result.isFullyConverged()); + SlackTerminal slackTerminal = network.getVoltageLevel("VLGEN").getExtension(SlackTerminal.class); + assertNotNull(slackTerminal); + assertNotNull(slackTerminal.getTerminal()); + openLoadFlowParameters.setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED); + LoadFlowResult result2 = LoadFlow.run(network, "newVariant", LocalComputationManager.getDefault(), parameters); + assertTrue(result2.isFullyConverged()); + assertEquals("VLHV1_0", result2.getComponentResults().get(0).getSlackBusResults().get(0).getId()); + network.getVariantManager().setWorkingVariant("newVariant"); + assertNull(slackTerminal.getTerminal()); + LoadFlowResult result3 = LoadFlow.run(network, VariantManagerConstants.INITIAL_VARIANT_ID, LocalComputationManager.getDefault(), parameters); + assertTrue(result3.isFullyConverged()); + assertEquals("VLGEN_0", result3.getComponentResults().get(0).getSlackBusResults().get(0).getId()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowGeneratorTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowGeneratorTest.java new file mode 100644 index 0000000..6552b42 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowGeneratorTest.java @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.FourBusNetworkFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Anne Tilloy {@literal } + */ +class AcLoadFlowGeneratorTest { + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters(); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void testWithCondenser() { + Network network = FourBusNetworkFactory.createWithCondenser(); + System.out.println(System.getProperty("java.library.path")); + Bus b1 = network.getBusBreakerView().getBus("b1"); + Bus b4 = network.getBusBreakerView().getBus("b4"); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(1.0, b1); + assertAngleEquals(0, b1); + assertVoltageEquals(1.0, b4); + assertAngleEquals(-2.583605, b4); + } + + @Test + void testGeneratorDiscardedFromVoltageControl() { + Network network = FourBusNetworkFactory.createWith2GeneratorsAtBus1(); + Generator g1Bis = network.getGenerator("g1Bis").setTargetP(0.0).setMinP(1.0).setTargetQ(Double.NaN); // must be discarded from voltage control + Bus b1 = network.getBusBreakerView().getBus("b1"); + Bus b4 = network.getBusBreakerView().getBus("b4"); + Generator g1 = network.getGenerator("g1"); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(1.0, b1); + assertAngleEquals(0, b1); + assertVoltageEquals(1.0, b4); + assertAngleEquals(-1.4523745, b4); + assertReactivePowerEquals(0.0, g1Bis.getTerminal()); + assertReactivePowerEquals(-0.571, g1.getTerminal()); + } +} + diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowLccTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowLccTest.java new file mode 100644 index 0000000..c34cec8 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowLccTest.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2020, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.LccConverterStation; +import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.HvdcNetworkFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AcLoadFlowLccTest { + + @Test + void test() { + Network network = HvdcNetworkFactory.createLcc(); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters().setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + Bus bus1 = network.getBusView().getBus("vl1_0"); + assertVoltageEquals(390, bus1); + assertAngleEquals(0, bus1); + + Bus bus2 = network.getBusView().getBus("vl2_0"); + assertVoltageEquals(389.3763, bus2); + assertAngleEquals(-0.095268, bus2); + + Bus bus3 = network.getBusView().getBus("vl3_0"); + assertVoltageEquals(380, bus3); + assertAngleEquals(0, bus3); + + LccConverterStation cs2 = network.getLccConverterStation("cs2"); + assertActivePowerEquals(50.00, cs2.getTerminal()); + assertReactivePowerEquals(37.499, cs2.getTerminal()); + + LccConverterStation cs3 = network.getLccConverterStation("cs3"); + assertActivePowerEquals(-49.399, cs3.getTerminal()); + assertReactivePowerEquals(37.049, cs3.getTerminal()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowPhaseShifterTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowPhaseShifterTest.java new file mode 100644 index 0000000..cae0d97 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowPhaseShifterTest.java @@ -0,0 +1,635 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.ac.AcLoadFlowContext; +import com.powsybl.openloadflow.ac.AcLoadFlowParameters; +import com.powsybl.openloadflow.ac.AcLoadFlowResult; +import com.powsybl.openloadflow.ac.AcloadFlowEngine; +import com.powsybl.openloadflow.ac.outerloop.AcIncrementalPhaseControlOuterLoop; +import com.powsybl.openloadflow.ac.solver.AcSolverStatus; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.impl.Networks; +import com.powsybl.openloadflow.util.PerUnit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class AcLoadFlowPhaseShifterTest { + + private Network network; + private Bus bus1; + private Bus bus2; + private Bus bus3; + private Bus bus4; + private Line line1; + private Line line2; + private TwoWindingsTransformer t2wt; + private ThreeWindingsTransformer t3wt; + + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters() + .setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.FIRST) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void baseCaseT2wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(400, bus1); + assertAngleEquals(0, bus1); + assertVoltageEquals(385.698, bus2); + assertAngleEquals(-3.679206, bus2); + assertVoltageEquals(392.648, bus3); + assertAngleEquals(-1.806094, bus3); + assertActivePowerEquals(50.084, line1.getTerminal1()); + assertReactivePowerEquals(29.201, line1.getTerminal1()); + assertActivePowerEquals(-50.000, line1.getTerminal2()); + assertReactivePowerEquals(-24.999, line1.getTerminal2()); + assertActivePowerEquals(50.042, line2.getTerminal1()); + assertReactivePowerEquals(27.100, line2.getTerminal1()); + assertActivePowerEquals(-50.000, line2.getTerminal2()); + assertReactivePowerEquals(-24.999, line2.getTerminal2()); + } + + @Test + void tapPlusOneT2wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + t2wt.getPhaseTapChanger().setTapPosition(2); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(400, bus1); + assertAngleEquals(0, bus1); + assertVoltageEquals(385.296, bus2); + assertAngleEquals(-1.186517, bus2); + assertVoltageEquals(392.076, bus3); + assertAngleEquals(1.964715, bus3); + assertActivePowerEquals(16.541, line1.getTerminal1()); + assertReactivePowerEquals(29.241, line1.getTerminal1()); + assertActivePowerEquals(-16.513, line1.getTerminal2()); + assertReactivePowerEquals(-27.831, line1.getTerminal2()); + assertActivePowerEquals(83.587, line2.getTerminal1()); + assertReactivePowerEquals(27.195, line2.getTerminal1()); + assertActivePowerEquals(-83.487, line2.getTerminal2()); + assertReactivePowerEquals(-22.169, line2.getTerminal2()); + } + + @Test + void flowControlT2wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + parameters.setPhaseShifterRegulationOn(true); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(83); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(83.587, line2.getTerminal1()); + assertActivePowerEquals(-83.486, line2.getTerminal2()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(1) + .setRegulationValue(83) + .setRegulationTerminal(t2wt.getTerminal2()); + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(16.528, line2.getTerminal1()); + assertActivePowerEquals(-16.514, line2.getTerminal2()); + assertEquals(0, t2wt.getPhaseTapChanger().getTapPosition()); + } + + @Test + void remoteFlowControlT2wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + parameters.setPhaseShifterRegulationOn(true); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(line1.getTerminal1()) + .setRegulationValue(83); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(83.688, line1.getTerminal1()); + assertActivePowerEquals(16.527, line2.getTerminal1()); + assertEquals(0, t2wt.getPhaseTapChanger().getTapPosition()); + } + + @Test + void currentLimiterT2wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + parameters.setPhaseShifterRegulationOn(true); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.CURRENT_LIMITER) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(false) + .setTapPosition(2) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(83); // in A + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertCurrentEquals(129.436, t2wt.getTerminal1()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.CURRENT_LIMITER) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(83); // in A + + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertCurrentEquals(48.482, t2wt.getTerminal1()); + assertEquals(0, t2wt.getPhaseTapChanger().getTapPosition()); + + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.CURRENT_LIMITER) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(90); // A + + LoadFlowResult result3 = loadFlowRunner.run(network, parameters); + assertTrue(result3.isFullyConverged()); + assertCurrentEquals(83.680, line2.getTerminal1()); + assertEquals(1, t2wt.getPhaseTapChanger().getTapPosition()); + + t2wt.getPhaseTapChanger().getStep(0).setAlpha(5.); + t2wt.getPhaseTapChanger().getStep(1).setAlpha(0.); + t2wt.getPhaseTapChanger().getStep(2).setAlpha(-5.); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.CURRENT_LIMITER) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(83); // A + + LoadFlowResult result4 = loadFlowRunner.run(network, parameters); + assertTrue(result4.isFullyConverged()); + assertCurrentEquals(48.492, line2.getTerminal1()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + } + + @Test + void openT2wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + parameters.setPhaseShifterRegulationOn(true); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(83); + t2wt.getTerminal1().disconnect(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + } + + @Test + void regulatingTerminalDisconnectedTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + Line line = network.getLine("L2"); + line.getTerminal2().disconnect(); + + parameters.setPhaseShifterRegulationOn(true); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(line.getTerminal2()) + .setRegulationValue(83); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + } + + @Test + void nullControlledBranchTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + parameters.setPhaseShifterRegulationOn(true); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(network.getLoad("LD2").getTerminal()) + .setRegulationValue(83); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + } + + @Test + void openControlledBranchTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + network.newLine() + .setId("L3") + .setConnectableBus1("B1") + .setBus1("B1") + .setConnectableBus2("B2") + .setBus2("B2") + .setR(4.0) + .setX(10.0) + .add(); + line1.getTerminal1().disconnect(); + parameters.setPhaseShifterRegulationOn(true); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(line1.getTerminal1()) + .setRegulationValue(83); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + } + + @Test + void baseCaseT3wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT3wt()); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(400, bus1); + assertAngleEquals(0, bus1); + assertVoltageEquals(378.252, bus2); + assertAngleEquals(-3.6350005363865856, bus2); + assertVoltageEquals(381.456, bus3); + assertAngleEquals(-2.600962217409659, bus3); + assertVoltageEquals(372.162, bus4); + assertAngleEquals(-2.550228747137401, bus4); + assertActivePowerEquals(48.842, line1.getTerminal1()); + assertReactivePowerEquals(44.048, line1.getTerminal1()); + assertActivePowerEquals(-48.734, line1.getTerminal2()); + assertReactivePowerEquals(-38.641, line1.getTerminal2()); + assertActivePowerEquals(26.276, line2.getTerminal1()); + assertReactivePowerEquals(11.9311, line2.getTerminal1()); + assertActivePowerEquals(-26.265, line2.getTerminal2()); + assertReactivePowerEquals(-11.358, line2.getTerminal2()); + } + + @Test + void tapPlusOneT3wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT3wt()); + t3wt.getLeg2().getPhaseTapChanger().setTapPosition(2); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(400, bus1); + assertAngleEquals(0, bus1); + assertVoltageEquals(377.782, bus2); + assertAngleEquals(-5.697417220150056, bus2); + assertVoltageEquals(381.293, bus3); + assertAngleEquals(-5.737530604739468, bus3); + assertVoltageEquals(372.172, bus4); + assertAngleEquals(-1.6404666559146515, bus4); + assertActivePowerEquals(75.941, line1.getTerminal1()); + assertReactivePowerEquals(46.650, line1.getTerminal1()); + assertActivePowerEquals(-75.742, line1.getTerminal2()); + assertReactivePowerEquals(-36.721, line1.getTerminal2()); + assertActivePowerEquals(-0.740, line2.getTerminal1()); + assertReactivePowerEquals(13.403, line2.getTerminal1()); + assertActivePowerEquals(0.743, line2.getTerminal2()); + assertReactivePowerEquals(-13.279, line2.getTerminal2()); + } + + @Test + void flowControlT3wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT3wt()); + parameters.setPhaseShifterRegulationOn(true); + t3wt.getLeg2().getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(t3wt.getLeg2().getTerminal()) + .setRegulationValue(0.); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-0.7403999884197101, line2.getTerminal1()); + assertActivePowerEquals(0.7428793087142719, line2.getTerminal2()); + assertEquals(2, t3wt.getLeg2().getPhaseTapChanger().getTapPosition()); + } + + @Test + void remoteFlowControlT3wtTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT3wt()); + parameters.setPhaseShifterRegulationOn(true); + t3wt.getLeg2().getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(line1.getTerminal1()) + .setRegulationValue(75); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(75.94143342722937, line1.getTerminal1()); + assertEquals(2, t3wt.getLeg2().getPhaseTapChanger().getTapPosition()); + } + + @Test + void ratioAndPhaseTapChangerTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + t2wt.getPhaseTapChanger().setTapPosition(2); + t2wt.newRatioTapChanger() + .setLoadTapChangingCapabilities(false) + .setTapPosition(0) + .beginStep() + .setRho(0.9) + .endStep() + .add(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertActivePowerEquals(76.830, t2wt.getTerminal1()); + assertReactivePowerEquals(-4.922, t2wt.getTerminal1()); + assertActivePowerEquals(-76.738, t2wt.getTerminal2()); + assertReactivePowerEquals(9.495, t2wt.getTerminal2()); + } + + @Test + void nonSupportedPhaseControl() { + Network network = HvdcNetworkFactory.createLccWithBiggerComponents(); + TwoWindingsTransformer twt = network.getTwoWindingsTransformer("l45"); + parameters.setPhaseShifterRegulationOn(true); + twt.getPhaseTapChanger().setRegulationTerminal(network.getLine("l12").getTerminal1()) + .setRegulationValue(0) + .setTargetDeadband(1) + .setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setRegulating(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(100.0805, network.getLine("l12").getTerminal1()); + } + + @Test + void nonSupportedPhaseControl2() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + parameters.setPhaseShifterRegulationOn(true); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) // FIXME how to take this into account + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(line1.getTerminal1()) + .setRegulationValue(83); + t2wt.getTerminal1().disconnect(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, t2wt.getPhaseTapChanger().getTapPosition()); + } + + private void selectNetwork(Network network) { + this.network = network; + bus1 = network.getBusBreakerView().getBus("B1"); + bus2 = network.getBusBreakerView().getBus("B2"); + bus3 = network.getBusBreakerView().getBus("B3"); + bus4 = network.getBusBreakerView().getBus("B4"); + + line1 = network.getLine("L1"); + line2 = network.getLine("L2"); + t2wt = network.getTwoWindingsTransformer("PS1"); + t3wt = network.getThreeWindingsTransformer("PS1"); + } + + @Test + void testPhaseShifterNecessaryForConnectivity() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + + // remove L1 so that PS1 loss would break connectivity + line1.getTerminal1().disconnect(); + line1.getTerminal2().disconnect(); + + // switch PS1 to active power control + t2wt.getPhaseTapChanger() + .setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) + .setRegulating(true) + .setRegulationValue(83); + + parameters.setPhaseShifterRegulationOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(100.3689, t2wt.getTerminal1()); + assertActivePowerEquals(-100.1844, t2wt.getTerminal2()); + } + + @Test + void incrementalPhaseShifterCurrentLimiterTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + t2wt.getPhaseTapChanger() + .setRegulationMode(PhaseTapChanger.RegulationMode.CURRENT_LIMITER) + .setTargetDeadband(1) + .setRegulating(false) + .setTapPosition(2) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(83); // in A + + parameters.setPhaseShifterRegulationOn(true); + parametersExt.setPhaseShifterControlMode(OpenLoadFlowParameters.PhaseShifterControlMode.INCREMENTAL); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertCurrentEquals(129.436, t2wt.getTerminal1()); + + t2wt.getPhaseTapChanger().setRegulating(true); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertCurrentEquals(48.482, t2wt.getTerminal1()); + } + + @Test + void incrementalPhaseShifterActivePowerControlTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + t2wt.getPhaseTapChanger() + .setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) + .setRegulating(false) + .setTapPosition(1) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(100); // in MW + + parameters.setPhaseShifterRegulationOn(true); + parametersExt.setPhaseShifterControlMode(OpenLoadFlowParameters.PhaseShifterControlMode.INCREMENTAL); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(50.084, t2wt.getTerminal1()); + + t2wt.getPhaseTapChanger().setRegulating(true); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + assertActivePowerEquals(83.687, t2wt.getTerminal1()); + + t2wt.getPhaseTapChanger().setRegulationTerminal(t2wt.getTerminal2()); + t2wt.getPhaseTapChanger().setRegulationValue(10); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(0, t2wt.getPhaseTapChanger().getTapPosition()); + assertActivePowerEquals(16.541, t2wt.getTerminal1()); + } + + @Test + void incrementalPhaseShifterSensiTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + t2wt.getPhaseTapChanger() + .setRegulationMode(PhaseTapChanger.RegulationMode.CURRENT_LIMITER) + .setTargetDeadband(1) + .setRegulating(false) + .setTapPosition(1) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(83); // in A + + parameters.setPhaseShifterRegulationOn(true); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + double i1t1 = t2wt.getTerminal1().getI(); + double i2t1 = t2wt.getTerminal1().getI(); + + t2wt.getPhaseTapChanger().setTapPosition(0); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + double i1t0 = t2wt.getTerminal1().getI(); + double i2t0 = t2wt.getTerminal1().getI(); + + t2wt.getPhaseTapChanger().setTapPosition(2); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + double i1t2 = t2wt.getTerminal1().getI(); + double i2t2 = t2wt.getTerminal1().getI(); + + double di1t10 = i1t1 - i1t0; + double di2t10 = i2t1 - i2t0; + double di1t12 = i1t1 - i1t2; + double di2t12 = i2t1 - i2t2; + assertEquals(35.18805497279091, di1t10, 1e-6); + assertEquals(35.18805497279091, di2t10, 1e-6); + assertEquals(-45.75648113263733, di1t12, 1e-6); + assertEquals(-45.75648113263733, di2t12, 1e-6); + + // compare with sensi on tap 1 + t2wt.getPhaseTapChanger().setTapPosition(1); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + t2wt.getPhaseTapChanger().setRegulating(true); + + LfNetworkParameters lfNetworkParameters = new LfNetworkParameters() + .setPhaseControl(true); + LfNetwork lfNetwork = Networks.load(network, lfNetworkParameters).get(0); + AcLoadFlowParameters acParameters = new AcLoadFlowParameters() + .setNetworkParameters(lfNetworkParameters) + .setMatrixFactory(new DenseMatrixFactory()); + try (AcLoadFlowContext lfContext = new AcLoadFlowContext(lfNetwork, acParameters)) { + AcLoadFlowResult lfResult = new AcloadFlowEngine(lfContext) + .run(); + assertEquals(AcSolverStatus.CONVERGED, lfResult.getSolverStatus()); + LfBranch ps1 = lfNetwork.getBranchById("PS1"); + List controllerBranches = List.of(ps1); + var sensitivityContext = new AcIncrementalPhaseControlOuterLoop.AcSensitivityContext(lfNetwork, + controllerBranches, + lfContext.getEquationSystem(), + lfContext.getJacobianMatrix()); + double da10 = t2wt.getPhaseTapChanger().getStep(1).getAlpha() - t2wt.getPhaseTapChanger().getStep(0).getAlpha(); + double da12 = t2wt.getPhaseTapChanger().getStep(1).getAlpha() - t2wt.getPhaseTapChanger().getStep(2).getAlpha(); + double ib = PerUnit.ib(ps1.getBus1().getNominalV()); + double sensi1 = sensitivityContext.calculateSensitivityFromA2I(ps1, ps1, TwoSides.ONE); + double di1t10p = sensi1 * da10 * ib; + double di1t12p = sensi1 * da12 * ib; + assertEquals(43.007011829925496, di1t10p, 1e-6); + assertEquals(-43.007011829925496, di1t12p, 1e-6); + double sensi2 = sensitivityContext.calculateSensitivityFromA2I(ps1, ps1, TwoSides.TWO); + double di2t10p = sensi2 * da10 * ib; + double di2t12p = sensi2 * da12 * ib; + assertEquals(43.007011829925496, di2t10p, 1e-6); + assertEquals(-43.007011829925496, di2t12p, 1e-6); + } + } + + @Test + void activePowerflowControlAndNonImpedantPhaseShifterTest() { + selectNetwork(PhaseControlFactory.createNetworkWithT2wt()); + parameters.setPhaseShifterRegulationOn(true); + t2wt.setR(0).setX(0); + t2wt.getPhaseTapChanger().setRegulationMode(PhaseTapChanger.RegulationMode.ACTIVE_POWER_CONTROL) + .setTargetDeadband(1) + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationValue(100); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(112.197, line2.getTerminal1()); + assertActivePowerEquals(-112.019, line2.getTerminal2()); + assertEquals(2, t2wt.getPhaseTapChanger().getTapPosition()); + + line1.setR(0.0).setX(0.0); + t2wt.setR(2.0).setX(100.0); + t2wt.getPhaseTapChanger() + .setRegulationTerminal(line1.getTerminal1()) + .setTapPosition(0); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + + assertTrue(result2.isFullyConverged()); + assertActivePowerEquals(100.0, line1.getTerminal1()); + assertActivePowerEquals(0.0, line2.getTerminal1()); + assertEquals(1, t2wt.getPhaseTapChanger().getTapPosition()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowShuntTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowShuntTest.java new file mode 100644 index 0000000..ab3dd10 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowShuntTest.java @@ -0,0 +1,640 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.ShuntNetworkFactory; +import com.powsybl.openloadflow.network.VoltageControlNetworkFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Shunt test case. + * + * g1 ld1 + * | | + * b1---------b2 + * l1 | + * shunt + * + * @author Anne Tilloy {@literal } + */ +class AcLoadFlowShuntTest { + + private Network network; + private Bus bus1; + private Bus bus2; + private Bus bus3; + private Line l1; + private Line l2; + private ShuntCompensator shunt; + + private LoadFlow.Runner loadFlowRunner; + + private LoadFlowParameters parameters; + + @BeforeEach + void setUp() { + network = ShuntNetworkFactory.create(); + bus1 = network.getBusBreakerView().getBus("b1"); + bus2 = network.getBusBreakerView().getBus("b2"); + bus3 = network.getBusBreakerView().getBus("b3"); + l1 = network.getLine("l1"); + l2 = network.getLine("l2"); + shunt = network.getShuntCompensator("SHUNT"); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters().setUseReactiveLimits(true) + .setDistributedSlack(true); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void testBaseCase() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390, bus1); + assertVoltageEquals(388.581, bus2); + assertVoltageEquals(388.581, bus3); + } + + @Test + void testShuntSectionOne() { + shunt.setSectionCount(1); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390, bus1); + assertVoltageEquals(389.758, bus2); + assertVoltageEquals(390.93051, bus3); + } + + @Test + void testShuntSectionTwo() { + shunt.setSectionCount(2); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390, bus1); + assertVoltageEquals(392.149468, bus2); + assertVoltageEquals(395.709, bus3); + } + + @Test + void testVoltageControl() { + parameters.setShuntCompensatorVoltageControlOn(true); + shunt.setSectionCount(0); + shunt.setVoltageRegulatorOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390.930, bus3); + assertEquals(1, shunt.getSectionCount()); + } + + @Test + void testRemoteVoltageControl() { + Network network = VoltageControlNetworkFactory.createWithShuntSharedRemoteControl(); + ShuntCompensator shuntCompensator2 = network.getShuntCompensator("SHUNT2"); + shuntCompensator2.setVoltageRegulatorOn(false); + ShuntCompensator shuntCompensator3 = network.getShuntCompensator("SHUNT3"); + parameters.setShuntCompensatorVoltageControlOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(399.921, network.getBusBreakerView().getBus("b4")); + assertEquals(0, shuntCompensator2.getSectionCount()); + assertEquals(24, shuntCompensator3.getSectionCount()); + } + + @Test + void testLocalSharedVoltageControl() { + shunt.setSectionCount(2); + ShuntCompensator shunt2 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(0) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal1()) + .setTargetV(393) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(1e-3) + .setG(0.0) + .endSection() + .beginSection() + .setB(3e-3) + .setG(0.) + .endSection() + .add() + .add(); + + parameters.setShuntCompensatorVoltageControlOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(393.308, bus3); + assertEquals(1, shunt.getSectionCount()); + assertEquals(1, shunt2.getSectionCount()); + } + + @Test + void testLocalSharedVoltageControl2() { + // in that test case, we test two shunts connected to the same bus, both are in voltage regulation + // we decrease the b per section of shunt2 + ShuntCompensator shunt2 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(0) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal1()) + .setTargetV(393) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(1e-4) + .setG(0.0) + .endSection() + .beginSection() + .setB(3e-4) + .setG(0.) + .endSection() + .add() + .add(); + + parameters.setShuntCompensatorVoltageControlOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(391.640, bus3); + assertEquals(1, shunt.getSectionCount()); + assertEquals(2, shunt2.getSectionCount()); + } + + @Test + void testLocalVoltageControl2() { + // in that test case, we test two shunts connected to the same bus, but with just one in voltage regulation + shunt.setSectionCount(2); + ShuntCompensator shunt2 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(1) + .setVoltageRegulatorOn(false) + .setRegulatingTerminal(l2.getTerminal1()) + .setTargetV(393) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(1e-3) + .setG(0.0) + .endSection() + .beginSection() + .setB(3e-3) + .setG(0.) + .endSection() + .add() + .add(); + + parameters.setShuntCompensatorVoltageControlOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(393.308, bus3); + assertEquals(1, shunt.getSectionCount()); + assertEquals(1, shunt2.getSectionCount()); + } + + @Test + void testLocalVoltageControl3() { + // in that test case, we test two shunts connected to the same bus, but with just one in voltage regulation + network.getShuntCompensator("SHUNT").remove(); + ShuntCompensator shunt2 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(10) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal1()) + .setTargetV(400) + .setTargetDeadband(5.0) + .newLinearModel() + .setMaximumSectionCount(10) + .setBPerSection(1E-3) + .setGPerSection(0.0) + .add() + .add(); + + parameters.setShuntCompensatorVoltageControlOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(400.600, bus3); + assertEquals(5, shunt2.getSectionCount()); + } + + @Test + void testSharedRemoteVoltageControl() { + Network network = VoltageControlNetworkFactory.createWithShuntSharedRemoteControl(); + parameters.setShuntCompensatorVoltageControlOn(true); + ShuntCompensator shuntCompensator2 = network.getShuntCompensator("SHUNT2"); + ShuntCompensator shuntCompensator3 = network.getShuntCompensator("SHUNT3"); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(399.852, network.getBusBreakerView().getBus("b4")); + assertEquals(12, shuntCompensator2.getSectionCount()); + assertEquals(12, shuntCompensator3.getSectionCount()); + } + + @Test + void testNoShuntVoltageControl() { + parameters.setShuntCompensatorVoltageControlOn(true); + shunt.setRegulatingTerminal(network.getGenerator("g1").getTerminal()); + shunt.setSectionCount(0); + shunt.setVoltageRegulatorOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(388.581, bus3); + assertEquals(0, shunt.getSectionCount()); + } + + @Test + void testNoShuntVoltageControl2() { + parameters.setShuntCompensatorVoltageControlOn(true); + shunt.setSectionCount(0); + shunt.setVoltageRegulatorOn(true); + shunt.setRegulatingTerminal(network.getLoad("ld1").getTerminal()); + network.getLoad("ld1").getTerminal().disconnect(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(389.999, bus3); + assertEquals(0, shunt.getSectionCount()); + } + + @Test + void testNoShuntVoltageControl3() { + Network network = VoltageControlNetworkFactory.createWithShuntSharedRemoteControl(); + TwoWindingsTransformer twt = network.getTwoWindingsTransformer("tr1"); + twt.newRatioTapChanger() + .setTargetDeadband(0) + .setTapPosition(0) + .setLoadTapChangingCapabilities(true) + .setRegulating(true) + .setTargetV(400) + .setRegulationTerminal(network.getLoad("l4").getTerminal()) + .beginStep() + .setRho(0.9) + .setR(0.1089) + .setX(0.01089) + .setG(0.8264462809917356) + .setB(0.08264462809917356) + .endStep() + .beginStep() + .setRho(1.0) + .setR(0.121) + .setX(0.0121) + .setG(0.8264462809917356) + .setB(0.08264462809917356) + .endStep() + .beginStep() + .setRho(1.1) + .setR(0.1331) + .setX(0.01331) + .setG(0.9090909090909092) + .setB(0.09090909090909092) + .endStep() + .add(); + parameters.setShuntCompensatorVoltageControlOn(true); + parameters.setTransformerVoltageControlOn(true); + ShuntCompensator shuntCompensator2 = network.getShuntCompensator("SHUNT2"); + ShuntCompensator shuntCompensator3 = network.getShuntCompensator("SHUNT3"); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(407.403, network.getBusBreakerView().getBus("b4")); + assertEquals(0, shuntCompensator2.getSectionCount()); + assertEquals(0, shuntCompensator3.getSectionCount()); + } + + @Test + void testUnsupportedSharedVoltageControl() { + // in that test case, we test two shunts connected to the same bus, both are in voltage regulation + // but with a different regulating terminal. + ShuntCompensator shunt2 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(0) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal2()) + .setTargetV(405) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(1e-4) + .setG(0.0) + .endSection() + .beginSection() + .setB(3e-4) + .setG(0.) + .endSection() + .add() + .add(); + + parameters.setShuntCompensatorVoltageControlOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(391.640, bus3); + assertEquals(1, shunt.getSectionCount()); + assertEquals(2, shunt2.getSectionCount()); + } + + @Test + void testAdmittanceShift() { + // Test with G component on shunt + network.getShuntCompensator("SHUNT").getTerminal().disconnect(); + ShuntCompensator shuntG = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(0) + .setVoltageRegulatorOn(true) + .setTargetV(393) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(1e-3) + .setG(1e-5) + .endSection() + .beginSection() + .setB(3e-3) + .setG(3e-5) + .endSection() + .add() + .add(); + + shuntG.setSectionCount(1); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertActivePowerEquals(102.749, l1.getTerminal(TwoSides.ONE)); + assertActivePowerEquals(-102.679, l1.getTerminal(TwoSides.TWO)); + assertReactivePowerEquals(-2.154, l1.getTerminal(TwoSides.ONE)); + assertReactivePowerEquals(2.362, l1.getTerminal(TwoSides.TWO)); + assertActivePowerEquals(-1.528, l2.getTerminal(TwoSides.ONE)); + assertActivePowerEquals(1.681, l2.getTerminal(TwoSides.TWO)); + assertReactivePowerEquals(152.821, l2.getTerminal(TwoSides.ONE)); + assertReactivePowerEquals(-152.362, l2.getTerminal(TwoSides.TWO)); + assertActivePowerEquals(1.528, shuntG.getTerminal()); + assertReactivePowerEquals(-152.82, shuntG.getTerminal()); + + shuntG.setSectionCount(2); + LoadFlowResult result3 = loadFlowRunner.run(network, parameters); + assertTrue(result3.isFullyConverged()); + assertActivePowerEquals(4.697, shuntG.getTerminal()); + assertReactivePowerEquals(-469.698, shuntG.getTerminal()); + assertVoltageEquals(395.684, shuntG.getTerminal().getBusView().getBus()); + + shuntG.setSectionCount(0); + parameters.setShuntCompensatorVoltageControlOn(true); + LoadFlowResult result4 = loadFlowRunner.run(network, parameters); + assertTrue(result4.isFullyConverged()); + assertVoltageEquals(390.93, shuntG.getTerminal().getBusView().getBus()); + assertEquals(1, shuntG.getSectionCount()); + assertActivePowerEquals(1.528, shuntG.getTerminal()); + assertReactivePowerEquals(-152.826, shuntG.getTerminal()); + } + + @Test + void testIncrementalVoltageControl() { + parameters.setShuntCompensatorVoltageControlOn(true); + OpenLoadFlowParameters.create(parameters).setShuntVoltageControlMode(OpenLoadFlowParameters.ShuntVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + shunt.setSectionCount(0); + shunt.setVoltageRegulatorOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390.930, bus3); + assertEquals(1, shunt.getSectionCount()); + + shunt.setSectionCount(0); + shunt.setTargetDeadband(10); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertVoltageEquals(388.581, bus3); + assertEquals(0, shunt.getSectionCount()); + } + + @Test + void testIncrementalVoltageRemote() { + Network network = VoltageControlNetworkFactory.createWithShuntSharedRemoteControl(); + ShuntCompensator shuntCompensator2 = network.getShuntCompensator("SHUNT2"); + shuntCompensator2.setVoltageRegulatorOn(false); + ShuntCompensator shuntCompensator3 = network.getShuntCompensator("SHUNT3"); + parameters.setShuntCompensatorVoltageControlOn(true); + OpenLoadFlowParameters.create(parameters).setShuntVoltageControlMode(OpenLoadFlowParameters.ShuntVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(402.354, network.getBusBreakerView().getBus("b4")); + assertEquals(0, shuntCompensator2.getSectionCount()); + assertEquals(16, shuntCompensator3.getSectionCount()); + + shuntCompensator3.setTargetDeadband(0.1); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertVoltageEquals(399.922, network.getBusBreakerView().getBus("b4")); + assertEquals(0, shuntCompensator2.getSectionCount()); + assertEquals(24, shuntCompensator3.getSectionCount()); + } + + @Test + void testSharedIncrementalVoltage() { + shunt.setSectionCount(2); + ShuntCompensator shunt2 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(0) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal1()) + .setTargetV(393) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(1e-3) + .setG(0.0) + .endSection() + .beginSection() + .setB(3e-3) + .setG(0.) + .endSection() + .add() + .add(); + + parameters.setShuntCompensatorVoltageControlOn(true); + OpenLoadFlowParameters.create(parameters).setShuntVoltageControlMode(OpenLoadFlowParameters.ShuntVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390.931, bus3); + assertEquals(1, shunt.getSectionCount()); + assertEquals(0, shunt2.getSectionCount()); + } + + @Test + void testSharedIncrementalVoltageRemote() { + Network network = VoltageControlNetworkFactory.createWithShuntSharedRemoteControl(); + ShuntCompensator shuntCompensator2 = network.getShuntCompensator("SHUNT2"); + shuntCompensator2.setSectionCount(2); + ShuntCompensator shuntCompensator3 = network.getShuntCompensator("SHUNT3"); + parameters.setShuntCompensatorVoltageControlOn(true); + OpenLoadFlowParameters.create(parameters).setShuntVoltageControlMode(OpenLoadFlowParameters.ShuntVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(402.324, network.getBusBreakerView().getBus("b4")); + assertEquals(9, shuntCompensator2.getSectionCount()); + assertEquals(7, shuntCompensator3.getSectionCount()); + } + + @Test + void testOppositeSignBIncremental() { + ShuntCompensator shunt2 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(1) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal1()) + .setTargetV(393) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(-1e-3) + .setG(0.0) + .endSection() + .beginSection() + .setB(-3e-3) + .setG(0.) + .endSection() + .add() + .add(); + + parameters.setShuntCompensatorVoltageControlOn(true); + OpenLoadFlowParameters.create(parameters).setShuntVoltageControlMode(OpenLoadFlowParameters.ShuntVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390.931, bus3); + assertEquals(1, shunt.getSectionCount()); + assertEquals(0, shunt2.getSectionCount()); + } + + @Test + void testNonLinearControllersIncremental() { + shunt.setVoltageRegulatorOn(false); + ShuntCompensator shunt2 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT2") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(1) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal1()) + .setTargetV(393) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(-3e-3) + .setG(0.0) + .endSection() + .beginSection() + .setB(4e-3) + .setG(0.) + .endSection() + .add() + .add(); + ShuntCompensator shunt3 = network.getVoltageLevel("vl3").newShuntCompensator() + .setId("SHUNT3") + .setBus("b3") + .setConnectableBus("b3") + .setSectionCount(1) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal1()) + .setTargetV(393) + .setTargetDeadband(5.0) + .newNonLinearModel() + .beginSection() + .setB(-3e-3) + .setG(0.0) + .endSection() + .beginSection() + .setB(4e-3) + .setG(0.) + .endSection() + .add() + .add(); + + parameters.setShuntCompensatorVoltageControlOn(true); + OpenLoadFlowParameters.create(parameters).setShuntVoltageControlMode(OpenLoadFlowParameters.ShuntVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390.930, bus3); + assertEquals(0, shunt.getSectionCount()); + assertEquals(2, shunt2.getSectionCount()); + assertEquals(1, shunt3.getSectionCount()); + + shunt2.setSectionCount(1); + shunt2.setTargetV(408.0); + shunt3.setSectionCount(1); + shunt3.setTargetV(408.0); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertVoltageEquals(408.150, bus3); + assertEquals(0, shunt.getSectionCount()); + assertEquals(2, shunt2.getSectionCount()); + assertEquals(2, shunt3.getSectionCount()); + } + + @Test + void testIncrementalVoltageControlWithGenerator() { + Network network = ShuntNetworkFactory.createWithGeneratorAndShunt(); + parameters.setShuntCompensatorVoltageControlOn(true); + OpenLoadFlowParameters.create(parameters).setShuntVoltageControlMode(OpenLoadFlowParameters.ShuntVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + ShuntCompensator shunt = network.getShuntCompensator("SHUNT"); + shunt.setTargetDeadband(2); + Bus b3 = network.getBusBreakerView().getBus("b3"); + Generator g2 = network.getGenerator("g2"); + + // Generator reactive capability is enough to hold voltage target + shunt.setVoltageRegulatorOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(393, b3); + assertEquals(0, shunt.getSectionCount()); + assertReactivePowerEquals(-289.033, g2.getTerminal()); + + network.getGenerator("g2").newMinMaxReactiveLimits().setMinQ(-150).setMaxQ(150).add(); + // Generator reactive capability is not enough to hold voltage target and shunt is deactivated + shunt.setVoltageRegulatorOn(false); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertVoltageEquals(390.887, b3); + assertEquals(0, shunt.getSectionCount()); + assertReactivePowerEquals(-150.0, g2.getTerminal()); + + // Generator reactive capability is not enough to hold voltage alone but with shunt it is ok + shunt.setVoltageRegulatorOn(true); + LoadFlowResult result3 = loadFlowRunner.run(network, parameters); + assertTrue(result3.isFullyConverged()); + assertVoltageEquals(393, b3); + assertEquals(1, shunt.getSectionCount()); + assertReactivePowerEquals(-134.585, g2.getTerminal()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTransformerReactivePowerControlTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTransformerReactivePowerControlTest.java new file mode 100644 index 0000000..51e845c --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTransformerReactivePowerControlTest.java @@ -0,0 +1,580 @@ +/** + * Copyright (c) 2023, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.RemoteReactivePowerControlAdder; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.ReactivePowerControlNetworkFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.network.VoltageControlNetworkFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertReactivePowerEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Pierre Arvy {@literal } + */ +class AcLoadFlowTransformerReactivePowerControlTest { + + private Network network; + private TwoWindingsTransformer t2wt; + private TwoWindingsTransformer t2wt2; + private ThreeWindingsTransformer t3wt; + + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters(); + parameters.setTransformerVoltageControlOn(false); + parameters.setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.FIRST) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void baseCaseT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertReactivePowerEquals(7.618, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-7.318, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.181, t2wt.getTerminal1()); + assertReactivePowerEquals(3.205e-5, t2wt.getTerminal2()); + } + + @Test + void testGeneratorRemoteReactivePowerControlOutsideReactiveLimits() { + Network network = ReactivePowerControlNetworkFactory.create4BusNetworkWithRatioTapChanger(); + + // controllers of reactive power + Generator g4 = network.getGenerator("g4"); + TwoWindingsTransformer t2wt = network.getTwoWindingsTransformer("l34"); + + double gTargetQ = 3.155; + double t2wtTargetQ = 1; + Terminal regulatedTerminal = t2wt.getTerminal2(); + + g4.setTargetQ(0.0).setVoltageRegulatorOn(false); + g4.newExtension(RemoteReactivePowerControlAdder.class) + .withTargetQ(gTargetQ) + .withRegulatingTerminal(regulatedTerminal) + .withEnabled(true).add(); + g4.newMinMaxReactiveLimits().setMinQ(-5.0).setMaxQ(5.0).add(); + + t2wt.getRatioTapChanger() + .setLoadTapChangingCapabilities(true) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setTargetDeadband(0) + .setRegulationValue(t2wtTargetQ) + .setRegulationTerminal(regulatedTerminal) + .setRegulating(true); + + // without transformer regulating, generator does not hold its target + parameters.setUseReactiveLimits(true); + parametersExt.setGeneratorReactivePowerRemoteControl(true); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(-5.0, g4.getTerminal()); + assertReactivePowerEquals(2.588, regulatedTerminal); // not targetQ + + // without generator regulating, transformer does not hold its target + parametersExt.setGeneratorReactivePowerRemoteControl(false) + .setTransformerReactivePowerControl(true); + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(0.891, regulatedTerminal); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + + // with transformer/generator regulating, generator target Q is held + t2wt.getRatioTapChanger().setTapPosition(1); + parametersExt.setGeneratorReactivePowerRemoteControl(true) + .setTransformerReactivePowerControl(true); + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(-5.0, g4.getTerminal()); // limit of generator + assertReactivePowerEquals(gTargetQ, regulatedTerminal); // targetQ of generator is held + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void tapPlusThreeT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + t2wt.getRatioTapChanger().setTapPosition(3); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertReactivePowerEquals(7.285, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-6.927, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.573, t2wt.getTerminal1()); + assertReactivePowerEquals(5.170e-5, t2wt.getTerminal2()); + } + + @Test + void transformerReactivePowerControlT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-0.55); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.285, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-6.927, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.573, t2wt.getTerminal1()); + assertReactivePowerEquals(5.170e-5, t2wt.getTerminal2()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void transformerReactivePowerControlT2wtTest2() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(3) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-0.55); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.285, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-6.927, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.573, t2wt.getTerminal1()); + assertReactivePowerEquals(5.170e-5, t2wt.getTerminal2()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void transformerReactivePowerControlT2wtTest3() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(network.getLine("LINE_12").getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(7.6); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.618, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-7.318, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.181, t2wt.getTerminal1()); + assertReactivePowerEquals(3.205e-5, t2wt.getTerminal2()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void transformerReactivePowerControlT2wtTest4() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(network.getLine("LINE_12").getTerminal2()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-7.3); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.618, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-7.318, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.181, t2wt.getTerminal1()); + assertReactivePowerEquals(7.06 * Math.pow(10, -7), t2wt.getTerminal2()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void transformerReactivePowerControlT2wtTest5() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-0.48); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.362, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-7.021, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.479, t2wt.getTerminal1()); + assertReactivePowerEquals(7.654e-5, t2wt.getTerminal2()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void transformerReactivePowerControlT2wtTest6() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-1); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.285, network.getLine("LINE_12").getTerminal1()); // FIXME shouldn't be 7.285 ? + assertReactivePowerEquals(-6.927, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.572, t2wt.getTerminal1()); + assertReactivePowerEquals(8.566 * Math.pow(10, -7), t2wt.getTerminal2()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void baseCase2T2wtTest() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertReactivePowerEquals(7.032, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-6.603, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.448, t2wt.getTerminal1()); + assertReactivePowerEquals(2.609e-6, t2wt.getTerminal2()); + assertReactivePowerEquals(-0.448, t2wt2.getTerminal1()); + assertReactivePowerEquals(2.609e-6, t2wt2.getTerminal2()); + } + + @Test + void tapPlusThree2T2wtTest() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + t2wt.getRatioTapChanger().setTapPosition(3); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertReactivePowerEquals(7.436, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-6.891, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(2.462, t2wt.getTerminal1()); + assertReactivePowerEquals(-2.665, t2wt.getTerminal2()); + assertReactivePowerEquals(-3.071, t2wt2.getTerminal1()); + assertReactivePowerEquals(2.665, t2wt2.getTerminal2()); + } + + @Test + void transformerReactivePowerControl2T2wtTest() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(network.getLine("LINE_12").getTerminal2()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-6.89); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.436, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-6.891, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(2.462, t2wt.getTerminal1()); + assertReactivePowerEquals(-2.665, t2wt.getTerminal2()); + assertReactivePowerEquals(-3.071, t2wt2.getTerminal1()); + assertReactivePowerEquals(2.665, t2wt2.getTerminal2()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void transformerReactivePowerControl2T2wtTest2() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0.1) + .setRegulating(true) + .setTapPosition(3) + .setRegulationTerminal(t2wt.getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-3); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0.1) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(network.getLine("LINE_12").getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-7.4); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.436, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-6.891, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-3.070, t2wt.getTerminal1()); + assertReactivePowerEquals(2.665, t2wt.getTerminal2()); + assertReactivePowerEquals(2.462, t2wt2.getTerminal1()); + assertReactivePowerEquals(-2.665, t2wt2.getTerminal2()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(3, t2wt2.getRatioTapChanger().getTapPosition()); + } + + @Test + void transformerReactivePowerControlOnNonImpedantBranch() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0.1) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(network.getLine("LINE_12").getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(7.3); + + network.getLine("LINE_12").setR(0).setX(0).setG1(0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.308, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-7.308, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(-0.192, t2wt.getTerminal1()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void transformerReactivePowerControlNonImpedantRatioTapChanger() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wtAndSwitch()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0.1) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(network.getLine("LINE_12").getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(3.89); + + t2wt2.setR(0).setX(0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(3.891, network.getLine("LINE_12").getTerminal1()); + assertEquals(0, t2wt2.getRatioTapChanger().getTapPosition()); + } + + @Test + void baseCaseT3wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT3wt()); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertReactivePowerEquals(7.816, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-7.535, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(0.035, t3wt.getLeg1().getTerminal()); + assertReactivePowerEquals(1.231 * Math.pow(10, -5), t3wt.getLeg2().getTerminal()); + assertReactivePowerEquals(1.228 * Math.pow(10, -4), t3wt.getLeg3().getTerminal()); + } + + @Test + void tapPlusTwoT3wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT3wt()); + + t3wt.getLeg2().getRatioTapChanger().setTapPosition(2); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertReactivePowerEquals(7.816, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-7.535, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(0.035, t3wt.getLeg1().getTerminal()); + assertReactivePowerEquals(8.076e-6, t3wt.getLeg2().getTerminal()); + assertReactivePowerEquals(6.698e-8, t3wt.getLeg3().getTerminal()); + } + + @Test + void transformerReactivePowerControlT3wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT3wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t3wt.getLeg2().getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t3wt.getLeg2().getTerminal()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(0.035); + + parameters.setTransformerVoltageControlOn(true); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.816, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-7.535, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(0.035, t3wt.getLeg1().getTerminal()); + assertReactivePowerEquals(8.076e-6, t3wt.getLeg2().getTerminal()); + assertReactivePowerEquals(6.698e-8, t3wt.getLeg3().getTerminal()); + assertEquals(2, t3wt.getLeg2().getRatioTapChanger().getTapPosition()); + } + + @Test + void openedControllerBranchTest() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-3.071); + + // no transformer reactive power control if terminal 2 is opened + t2wt.getTerminal2().disconnect(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + + // no transformer reactive power control if terminal 1 is opened + t2wt.getTerminal2().connect(); + t2wt.getTerminal1().disconnect(); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void openedControlledBranchTest() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-3.071); + + // no transformer reactive power control if terminal 2 is opened on controlled branch + t2wt2.getTerminal2().disconnect(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + + // no transformer reactive power control if terminal 1 is opened on controlled branch + t2wt2.getTerminal2().connect(); + t2wt.getRatioTapChanger() + .setRegulationTerminal(t2wt2.getTerminal2()) + .setRegulationValue(2.665); + t2wt2.getTerminal1().disconnect(); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void regulatingTerminalDisconnectedTransformerReactivePowerControlTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + Load load = network.getLoad("LOAD_2"); + load.getTerminal().disconnect(); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(load.getTerminal()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(33.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void twoControllersOnTheSameBranchTest() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + parametersExt.setTransformerReactivePowerControl(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(network.getLine("LINE_12").getTerminal2()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-6.89); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(network.getLine("LINE_12").getTerminal1()) + .setRegulationMode(RatioTapChanger.RegulationMode.REACTIVE_POWER) + .setRegulationValue(-6.603); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(7.436, network.getLine("LINE_12").getTerminal1()); + assertReactivePowerEquals(-6.891, network.getLine("LINE_12").getTerminal2()); + assertReactivePowerEquals(2.462, t2wt.getTerminal1()); + assertReactivePowerEquals(-2.665, t2wt.getTerminal2()); + assertReactivePowerEquals(-3.071, t2wt2.getTerminal1()); + assertReactivePowerEquals(2.665, t2wt2.getTerminal2()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(0, t2wt2.getRatioTapChanger().getTapPosition()); + } + + private void selectNetwork(Network network) { + this.network = network; + + t2wt = network.getTwoWindingsTransformer("T2wT"); + t3wt = network.getThreeWindingsTransformer("T3wT"); + } + + private void selectNetwork2(Network network) { + this.network = network; + + t2wt = network.getTwoWindingsTransformer("T2wT1"); + t2wt2 = network.getTwoWindingsTransformer("T2wT2"); + } + +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTransformerVoltageControlTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTransformerVoltageControlTest.java new file mode 100644 index 0000000..4c51883 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTransformerVoltageControlTest.java @@ -0,0 +1,924 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.HvdcNetworkFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.network.VoltageControlNetworkFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertReactivePowerEquals; +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertVoltageEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Anne Tilloy {@literal } + */ +class AcLoadFlowTransformerVoltageControlTest { + + private Network network; + private Bus bus1; + private Bus bus2; + private Bus bus3; + private Bus bus4; + private TwoWindingsTransformer t2wt; + private TwoWindingsTransformer t2wt2; + private ThreeWindingsTransformer t3wt; + + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters(); + parameters.setTransformerVoltageControlOn(false); + parameters.setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.FIRST) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void baseCaseT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(135.0, bus1); + assertVoltageEquals(134.273, bus2); + assertVoltageEquals(27.0038, bus3); + } + + @Test + void tapPlusTwoT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + t2wt.getRatioTapChanger().setTapPosition(3); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(135.0, bus1); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(34.427, bus3); + } + + @Test + void voltageControlT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parameters.setTransformerVoltageControlOn(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(34.433, t2wt.getTerminal2().getBusView().getBus()); //FIXME: should be 34.427 + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void voltageControlT2wtTest2() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.AFTER_GENERATOR_VOLTAGE_CONTROL); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(34.433, t2wt.getTerminal2().getBusView().getBus()); //FIXME: should be 34.427 + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void voltageControlT2wtTest3() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parameters.setTransformerVoltageControlOn(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal1()) + .setTargetV(135.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.AFTER_GENERATOR_VOLTAGE_CONTROL); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(27.0, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void voltageControlT2wtTest4() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(28.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(27.003, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void voltageControlT2wtTest5() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(33.989, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(2, t2wt2.getRatioTapChanger().getTapPosition()); + } + + @Test + void voltageControlT2wtTest6() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + t2wt.getRatioTapChanger() + .setTargetDeadband(4.0) + .setRegulating(true) + .setTapPosition(1) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(32.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(30.766, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(1, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void voltageControlT2wtTest7() { + selectNetwork2(VoltageControlNetworkFactory.createNetworkWith2T2wt()); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + t2wt.getRatioTapChanger() + .setTargetDeadband(6.0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(6.0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(32.242, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(1, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(1, t2wt2.getRatioTapChanger().getTapPosition()); + } + + @Test + void voltageControlT2wtTest8() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt2()); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(7) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(34.43, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(4, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void remoteVoltageControlT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + Substation substation = network.newSubstation() + .setId("SUBSTATION4") + .setCountry(Country.FR) + .add(); + VoltageLevel vl4 = substation.newVoltageLevel() + .setId("VL_4") + .setNominalV(33.0) + .setLowVoltageLimit(0) + .setHighVoltageLimit(100) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + Bus bus4 = vl4.getBusBreakerView().newBus() + .setId("BUS_4") + .add(); + vl4.newLoad() + .setId("LOAD_4") + .setBus("BUS_4") + .setP0(2.) + .setQ0(0.5) + .add(); + + Line line34 = network.newLine() + .setId("LINE_34") + .setBus1("BUS_3") + .setBus2("BUS_4") + .setR(1.05) + .setX(10.0) + .setG1(0.0000005) + .add(); + + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(false) + .setTapPosition(3) + .setRegulationTerminal(line34.getTerminal2()) + .setTargetV(33.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertVoltageEquals(32.872, bus4); + assertTrue(result.isFullyConverged()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + + parameters.setTransformerVoltageControlOn(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(line34.getTerminal2()) + .setTargetV(33.0); + + result = loadFlowRunner.run(network, parameters); + assertVoltageEquals(32.874, bus4); //FIXME: should be 32.872 + assertTrue(result.isFullyConverged()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void remoteVoltageControlT2wtTest2() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + Substation substation = network.newSubstation() + .setId("SUBSTATION4") + .setCountry(Country.FR) + .add(); + VoltageLevel vl4 = substation.newVoltageLevel() + .setId("VL_4") + .setNominalV(33.0) + .setLowVoltageLimit(0) + .setHighVoltageLimit(100) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + Bus bus4 = vl4.getBusBreakerView().newBus() + .setId("BUS_4") + .add(); + vl4.newLoad() + .setId("LOAD_4") + .setBus("BUS_4") + .setP0(2.) + .setQ0(0.5) + .add(); + + Line line34 = network.newLine() + .setId("LINE_34") + .setBus1("BUS_3") + .setBus2("BUS_4") + .setR(1.05) + .setX(10.0) + .setG1(0.0000005) + .add(); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(line34.getTerminal2()) + .setTargetV(33.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertVoltageEquals(32.873, bus4); + assertTrue(result.isFullyConverged()); + assertEquals(3, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void testIncrementalVoltageControlWithGenerator() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + Substation substation = network.newSubstation() + .setId("SUBSTATION4") + .setCountry(Country.FR) + .add(); + VoltageLevel vl4 = substation.newVoltageLevel() + .setId("VL_4") + .setNominalV(33.0) + .setLowVoltageLimit(0) + .setHighVoltageLimit(100) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + Bus bus4 = vl4.getBusBreakerView().newBus() + .setId("BUS_4") + .add(); + vl4.newLoad() + .setId("LOAD_4") + .setBus("BUS_4") + .setP0(2.) + .setQ0(0.5) + .add(); + + Line line34 = network.newLine() + .setId("LINE_34") + .setBus1("BUS_3") + .setBus2("BUS_4") + .setR(1.05) + .setX(10.0) + .setG1(0.0000005) + .add(); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.INCREMENTAL_VOLTAGE_CONTROL); + + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(line34.getTerminal2()) + .setTargetV(30.0); + + Generator g4 = vl4.newGenerator() + .setId("GEN_4") + .setBus("BUS_4") + .setMinP(0.0) + .setMaxP(30) + .setTargetP(5) + .setTargetV(33) + .setVoltageRegulatorOn(true) + .add(); + + // Generator reactive capability is enough to hold voltage target + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(33, bus4); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + assertReactivePowerEquals(-7.126, g4.getTerminal()); + + g4.newMinMaxReactiveLimits().setMinQ(-3.5).setMaxQ(3.5).add(); + // Generator reactive capability is not enough to hold voltage target and rtc is deactivated + t2wt.getRatioTapChanger().setRegulating(false); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + assertVoltageEquals(31.032, bus4); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + assertReactivePowerEquals(-3.5, g4.getTerminal()); + + // Generator reactive capability is not enough to hold voltage alone but with rtc it is ok + t2wt.getRatioTapChanger().setRegulating(true); + LoadFlowResult result3 = loadFlowRunner.run(network, parameters); + assertTrue(result3.isFullyConverged()); + assertVoltageEquals(33, bus4); + assertEquals(1, t2wt.getRatioTapChanger().getTapPosition()); +// assertReactivePowerEquals(-1.172, g4.getTerminal()); //FIXME + + ////////////////////////////////////////////////////////////////////////////////////////// + loadFlowRunner.run(network, parameters); + assertReactivePowerEquals(-1.172, g4.getTerminal()); + } + + @Test + void nonSupportedVoltageControlT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(network.getGenerator("GEN_1").getTerminal()) + .setTargetV(33.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(135.0, bus1); + } + + @Test + void nonSupportedVoltageControlT2wtTest2() { + Network network = HvdcNetworkFactory.createLccWithBiggerComponents(); + TwoWindingsTransformer twt = network.getTwoWindingsTransformer("l45"); + parameters.setTransformerVoltageControlOn(true); + twt.getPhaseTapChanger().remove(); + twt.newRatioTapChanger().setTapPosition(0) + .beginStep() + .setX(0.1f) + .setRho(1) + .endStep() + .add(); + twt.getRatioTapChanger().setRegulationTerminal(network.getGenerator("g1").getTerminal()).setTargetV(400).setTargetDeadband(1).setLoadTapChangingCapabilities(true).setRegulating(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(390, network.getGenerator("g1").getTerminal().getBusView().getBus()); + } + + @Test + void sharedVoltageControlT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createWithTransformerSharedRemoteControl()); + parameters.setTransformerVoltageControlOn(true); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.279, bus2); + assertVoltageEquals(33.989, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void sharedVoltageControlT2wtWithZeroImpedanceLinesTest() { + selectNetwork(createNetworkWithSharedControl()); + + parameters.setTransformerVoltageControlOn(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.279, bus2); + assertVoltageEquals(35.73, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void inconsistentT2wtTargetVoltagesTest() { + selectNetwork(createNetworkWithSharedControl()); + + parameters.setTransformerVoltageControlOn(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(33.6); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.279, bus2); + assertVoltageEquals(35.73, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void sharedVoltageControlT2wtWithZeroImpedanceLinesTest2() { + selectNetwork(createNetworkWithSharedControl()); + network.getVoltageLevel("VL_3").newGenerator() + .setId("GEN_3") + .setBus("BUS_3") + .setMinP(0.0) + .setMaxP(35.0) + .setTargetP(2) + .setTargetV(34.0) + .setVoltageRegulatorOn(true) + .add(); + + parameters.setTransformerVoltageControlOn(true); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + t2wt2.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt2.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(136.605, bus2); + assertVoltageEquals(34.0, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void openT2wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(network.getGenerator("GEN_1").getTerminal()) + .setTargetV(33.0); + t2wt.getTerminal2().disconnect(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void regulatingTerminalDisconnectedTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + Load load = network.getLoad("LOAD_2"); + load.getTerminal().disconnect(); + + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(load.getTerminal()) + .setTargetV(33.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void t2wtWithoutLoadTapChangingCapabilitiesTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(network.getGenerator("GEN_1").getTerminal()) + .setTargetV(33.0) + .setLoadTapChangingCapabilities(false); + t2wt.getTerminal2().disconnect(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, t2wt.getRatioTapChanger().getTapPosition()); + } + + @Test + void baseCaseT3wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT3wt()); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(135.0, bus1); + assertVoltageEquals(134.265, bus2); + assertVoltageEquals(34.402, bus3); + assertVoltageEquals(10.320, bus4); + } + + @Test + void tapPlusTwoT3wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT3wt()); + + t3wt.getLeg2().getRatioTapChanger().setTapPosition(2); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertVoltageEquals(135.0, bus1); + assertVoltageEquals(134.264, bus2); + assertVoltageEquals(28.147, bus3); + assertVoltageEquals(10.320, bus4); + } + + @Test + void voltageControlT3wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT3wt()); + + t3wt.getLeg2().getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t3wt.getLeg2().getTerminal()) + .setTargetV(28.); + + parameters.setTransformerVoltageControlOn(true); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(28.147, bus3); + assertEquals(2, t3wt.getLeg2().getRatioTapChanger().getTapPosition()); + } + + @Test + void remoteVoltageControlT3wtTest() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT3wt()); + + Substation substation = network.newSubstation() + .setId("SUBSTATION5") + .setCountry(Country.FR) + .add(); + VoltageLevel vl5 = substation.newVoltageLevel() + .setId("VL_5") + .setNominalV(33.0) + .setLowVoltageLimit(0) + .setHighVoltageLimit(100.0) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + Bus bus5 = vl5.getBusBreakerView().newBus() + .setId("BUS_5") + .add(); + vl5.newLoad() + .setId("LOAD_5") + .setBus("BUS_5") + .setP0(2.) + .setQ0(0.5) + .add(); + + Line line35 = network.newLine() + .setId("LINE_35") + .setBus1("BUS_3") + .setBus2("BUS_5") + .setR(0.5) + .setX(1.0) + .setG1(0.0000005) + .add(); + + t3wt.getLeg2().getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(false) + .setTapPosition(0) + .setRegulationTerminal(line35.getTerminal2()) + .setTargetV(33.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(34.329, bus5); + + parameters.setTransformerVoltageControlOn(true); + t3wt.getLeg2().getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(2) + .setRegulationTerminal(line35.getTerminal2()) + .setTargetV(33.0); + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(34.329, bus5); + } + + /** + * A very small network to test with a T2wt. + * + * G1 LD2 + * | L12 | + * | ------- | + * B1 B2 - T2WT2 - B4 - L43 - B3 + * \ \ + * T2WT - B5 - L53 - B3 - LD3 + */ + private Network createNetworkWithSharedControl() { + + Network network = VoltageControlNetworkFactory.createTransformerBaseNetwork("two-windings-transformer-control"); + + VoltageLevel vl4 = network.getSubstation("SUBSTATION").newVoltageLevel() + .setId("VL_4") + .setNominalV(33.0) + .setLowVoltageLimit(30.0) + .setHighVoltageLimit(40.0) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + vl4.getBusBreakerView().newBus() + .setId("BUS_4") + .add(); + + VoltageLevel vl5 = network.getSubstation("SUBSTATION").newVoltageLevel() + .setId("VL_5") + .setNominalV(33.0) + .setLowVoltageLimit(30.0) + .setHighVoltageLimit(40.0) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + vl5.getBusBreakerView().newBus() + .setId("BUS_5") + .add(); + + Line line43 = network.newLine() + .setId("LINE_43") + .setBus1("BUS_4") + .setBus2("BUS_3") + .setR(0.) + .setX(0.) + .add(); + + Line line53 = network.newLine() + .setId("LINE_53") + .setBus1("BUS_5") + .setBus2("BUS_3") + .setR(0.) + .setX(0.) + .add(); + + t2wt = network.getSubstation("SUBSTATION").newTwoWindingsTransformer() + .setId("T2wT") + .setRatedU1(132.0) + .setRatedU2(33.0) + .setR(17.0) + .setX(10.0) + .setG(0.00573921028466483) + .setB(0.000573921028466483) + .setBus1("BUS_2") + .setBus2("BUS_5") + .add(); + + t2wt.newRatioTapChanger() + .beginStep() + .setRho(0.9) + .setR(0.1089) + .setX(0.01089) + .setG(0.8264462809917356) + .setB(0.08264462809917356) + .endStep() + .beginStep() + .setRho(1.0) + .setR(0.121) + .setX(0.0121) + .setG(0.8264462809917356) + .setB(0.08264462809917356) + .endStep() + .beginStep() + .setRho(1.1) + .setR(0.1331) + .setX(0.01331) + .setG(0.9090909090909092) + .setB(0.09090909090909092) + .endStep() + .setTapPosition(0) + .setLoadTapChangingCapabilities(true) + .setRegulating(false) + .setTargetV(33.0) + .setRegulationTerminal(network.getLoad("LOAD_3").getTerminal()) + .add(); + + t2wt2 = network.getSubstation("SUBSTATION").newTwoWindingsTransformer() + .setId("T2wT2") + .setRatedU1(132.0) + .setRatedU2(33.0) + .setR(17.0) + .setX(10.0) + .setG(0.00573921028466483) + .setB(0.000573921028466483) + .setBus1("BUS_2") + .setBus2("BUS_4") + .add(); + + t2wt2.newRatioTapChanger() + .beginStep() + .setRho(0.9) + .setR(0.1089) + .setX(0.01089) + .setG(0.8264462809917356) + .setB(0.08264462809917356) + .endStep() + .beginStep() + .setRho(1.0) + .setR(0.121) + .setX(0.0121) + .setG(0.8264462809917356) + .setB(0.08264462809917356) + .endStep() + .beginStep() + .setRho(1.1) + .setR(0.1331) + .setX(0.01331) + .setG(0.9090909090909092) + .setB(0.09090909090909092) + .endStep() + .setTapPosition(0) + .setLoadTapChangingCapabilities(true) + .setRegulating(false) + .setTargetV(33.0) + .setRegulationTerminal(network.getLoad("LOAD_3").getTerminal()) + .add(); + + return network; + } + + private void selectNetwork(Network network) { + this.network = network; + + bus1 = network.getBusBreakerView().getBus("BUS_1"); + bus2 = network.getBusBreakerView().getBus("BUS_2"); + bus3 = network.getBusBreakerView().getBus("BUS_3"); + bus4 = network.getBusBreakerView().getBus("BUS_4"); + + t2wt = network.getTwoWindingsTransformer("T2wT"); + t3wt = network.getThreeWindingsTransformer("T3wT"); + } + + private void selectNetwork2(Network network) { + this.network = network; + + bus1 = network.getBusBreakerView().getBus("BUS_1"); + bus2 = network.getBusBreakerView().getBus("BUS_2"); + bus3 = network.getBusBreakerView().getBus("BUS_3"); + + t2wt = network.getTwoWindingsTransformer("T2wT1"); + t2wt2 = network.getTwoWindingsTransformer("T2wT2"); + } + + @Test + void testTargetDeadband() { + selectNetwork(VoltageControlNetworkFactory.createNetworkWithT2wt()); + + parameters.setTransformerVoltageControlOn(true); + parametersExt.setTransformerVoltageControlMode(OpenLoadFlowParameters.TransformerVoltageControlMode.AFTER_GENERATOR_VOLTAGE_CONTROL); + t2wt.getRatioTapChanger() + .setTargetDeadband(16.0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(134.281, bus2); + assertVoltageEquals(27.00, t2wt.getTerminal2().getBusView().getBus()); + assertEquals(0, t2wt.getRatioTapChanger().getTapPosition()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTwoBusNetworkTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTwoBusNetworkTest.java new file mode 100644 index 0000000..e14a73f --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowTwoBusNetworkTest.java @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.ActivePowerControlAdder; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.network.TwoBusNetworkFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class AcLoadFlowTwoBusNetworkTest { + + private Network network; + private Bus bus1; + private Bus bus2; + private Line line1; + + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + network = TwoBusNetworkFactory.create(); + bus1 = network.getBusBreakerView().getBus("b1"); + bus2 = network.getBusBreakerView().getBus("b2"); + line1 = network.getLine("l12"); + + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters().setUseReactiveLimits(false) + .setDistributedSlack(false); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.FIRST) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void baseCaseTest() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(1, bus1); + assertAngleEquals(0, bus1); + assertVoltageEquals(0.855, bus2); + assertAngleEquals(-13.521852, bus2); + assertActivePowerEquals(2, line1.getTerminal1()); + assertReactivePowerEquals(1.683, line1.getTerminal1()); + assertActivePowerEquals(-2, line1.getTerminal2()); + assertReactivePowerEquals(-1, line1.getTerminal2()); + } + + @Test + void voltageInitModeTest() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(4, result.getComponentResults().get(0).getIterationCount()); + // restart loadflow from previous calculated state, it should convergence in zero iteration + result = loadFlowRunner.run(network, parameters.setVoltageInitMode(LoadFlowParameters.VoltageInitMode.PREVIOUS_VALUES)); + assertTrue(result.isFullyConverged()); + assertEquals(0, result.getComponentResults().get(0).getIterationCount()); + } + + @Test + void withAnAdditionalBattery() { + bus2.getVoltageLevel().newBattery() + .setId("bt2") + .setBus("b2") + .setTargetP(-1) + .setTargetQ(-0.1) + .setMinP(-1) + .setMaxP(0) + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(1, bus1); + assertAngleEquals(0, bus1); + assertVoltageEquals(0.784, bus2); + assertAngleEquals(-22.518194, bus2); + assertActivePowerEquals(2.999, line1.getTerminal1()); + assertReactivePowerEquals(2.763, line1.getTerminal1()); + assertActivePowerEquals(-2.999, line1.getTerminal2()); + assertReactivePowerEquals(-1.099, line1.getTerminal2()); + assertActivePowerEquals(1, network.getBattery("bt2").getTerminal()); + } + + @Test + void withAnAdditionalBattery2() { + bus2.getVoltageLevel().newBattery() + .setId("bt2") + .setBus("b2") + .setTargetP(-1) + .setTargetQ(-0.1) + .setMinP(-2) + .setMaxP(2) + .add(); + network.getBattery("bt2").newExtension(ActivePowerControlAdder.class).withDroop(1).withParticipate(true).add(); + network.getGenerator("g1").setMaxP(3); + parameters.setDistributedSlack(true); + parameters.getExtension(OpenLoadFlowParameters.class).setSlackBusPMaxMismatch(0.001); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(1, bus1); + assertAngleEquals(0, bus1); + assertVoltageEquals(0.829, bus2); + assertAngleEquals(-15.911666, bus2); + assertActivePowerEquals(2.2727, line1.getTerminal1()); + assertReactivePowerEquals(2.0276, line1.getTerminal1()); + assertActivePowerEquals(-2.2727, line1.getTerminal2()); + assertReactivePowerEquals(-1.099, line1.getTerminal2()); + assertActivePowerEquals(0.272, network.getBattery("bt2").getTerminal()); + } + + @Test + void withAnAdditionalBattery3() { + bus2.getVoltageLevel().newBattery() + .setId("bt2") + .setBus("b2") + .setTargetP(-1) + .setTargetQ(-0.1) + .setMinP(-2) + .setMaxP(2) + .add(); + network.getGenerator("g1").setMaxP(3); + parameters.setDistributedSlack(true); + parameters.getExtension(OpenLoadFlowParameters.class).setSlackBusPMaxMismatch(0.001); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(1, bus1); + assertAngleEquals(0, bus1); + assertVoltageEquals(0.812, bus2); + assertAngleEquals(-18.678874, bus2); + assertActivePowerEquals(2.600, line1.getTerminal1()); + assertReactivePowerEquals(2.309, line1.getTerminal1()); + assertActivePowerEquals(-2.600, line1.getTerminal2()); + assertReactivePowerEquals(-1.0999, line1.getTerminal2()); + assertActivePowerEquals(0.600, network.getBattery("bt2").getTerminal()); + } + + @Test + void zeroImpedanceToShuntCompensator() { + var network = TwoBusNetworkFactory.createZeroImpedanceToShuntCompensator(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(0.9014977258927488, network.getLine("l23").getTerminal1()); + assertActivePowerEquals(0.0, network.getLine("l23").getTerminal1()); + } + + @Test + void modifiedLoadCaseTest() { + // previous value : l1 (P0, Q0) = (2, 1) + // new values : l1 (P0, Q0) = (0, 1) and + // l2 (P0, Q0) = (2, 0) + network.getLoad("l1").setP0(0); + Load l2 = bus2.getVoltageLevel().newLoad() + .setId("l2") + .setBus("b2") + .setConnectableBus("b2") + .setP0(2) + .setQ0(0) + .add(); + l2.getTerminal().setP(2).setQ(0); + + parameters = new LoadFlowParameters() + .setReadSlackBus(false) + .setDistributedSlack(true) + .setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setLoadPowerFactorConstant(true); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + Double bus2BalanceQ = bus2.getConnectedTerminalStream().map(Terminal::getQ) + .filter(d -> !Double.isNaN(d)) + .reduce(0.0, Double::sum); + + assertEquals(0.0, bus2BalanceQ, DELTA_POWER); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowVscTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowVscTest.java new file mode 100644 index 0000000..4a35135 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowVscTest.java @@ -0,0 +1,642 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControl; +import com.powsybl.iidm.network.extensions.HvdcAngleDroopActivePowerControlAdder; +import com.powsybl.iidm.network.extensions.HvdcOperatorActivePowerRangeAdder; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.HvdcNetworkFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AcLoadFlowVscTest { + + @Test + void test() { + Network network = HvdcNetworkFactory.createVsc(); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters() + .setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters).setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + Bus bus1 = network.getBusView().getBus("vl1_0"); + assertVoltageEquals(390, bus1); + assertAngleEquals(0, bus1); + + Bus bus2 = network.getBusView().getBus("vl2_0"); + assertVoltageEquals(385, bus2); + assertAngleEquals(0.117616, bus2); + + Bus bus3 = network.getBusView().getBus("vl3_0"); + assertVoltageEquals(383, bus3); + assertAngleEquals(0, bus3); + + Generator g1 = network.getGenerator("g1"); + assertActivePowerEquals(-102.56, g1.getTerminal()); + assertReactivePowerEquals(-615.918, g1.getTerminal()); + + VscConverterStation cs2 = network.getVscConverterStation("cs2"); + assertActivePowerEquals(50.00, cs2.getTerminal()); + assertReactivePowerEquals(598.228, cs2.getTerminal()); + + VscConverterStation cs3 = network.getVscConverterStation("cs3"); + assertActivePowerEquals(-49.35, cs3.getTerminal()); + assertReactivePowerEquals(-10.0, cs3.getTerminal()); + + Line l12 = network.getLine("l12"); + assertActivePowerEquals(102.563, l12.getTerminal1()); + assertReactivePowerEquals(615.918, l12.getTerminal1()); + assertActivePowerEquals(-99.999, l12.getTerminal2()); + assertReactivePowerEquals(-608.228, l12.getTerminal2()); + } + + @Test + void testRegulatingTerminal() { + Network network = HvdcNetworkFactory.createVsc(); + network.getGenerator("g1").setTargetQ(50).setVoltageRegulatorOn(false); + VscConverterStation vscConverterStation = network.getVscConverterStation("cs2"); + vscConverterStation.setRegulatingTerminal(network.getGenerator("g1").getTerminal()).setVoltageSetpoint(390); + vscConverterStation.setVoltageRegulatorOn(true); //FIXME + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters().setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters).setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + Bus bus1 = network.getBusView().getBus("vl1_0"); + assertVoltageEquals(390.0, bus1); + } + + @Test + void testRegulatingTerminal2() { + Network network = HvdcNetworkFactory.createVsc(); + network.getGenerator("g1").setTargetV(390); + VscConverterStation vscConverterStation = network.getVscConverterStation("cs2"); + vscConverterStation.setRegulatingTerminal(network.getVscConverterStation("cs3").getTerminal()).setVoltageSetpoint(400); // will be discarded. + vscConverterStation.setVoltageRegulatorOn(true); //FIXME + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters() + .setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters).setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + Bus bus1 = network.getBusView().getBus("vl1_0"); + assertVoltageEquals(390.0, bus1); + } + + @Test + void testHvdcAcEmulation() { + Network network = HvdcNetworkFactory.createVsc(); + network.getHvdcLine("hvdc23").newExtension(HvdcAngleDroopActivePowerControlAdder.class) + .withDroop(180) + .withP0(0.f) + .withEnabled(true) + .add(); + network.newLine() + .setId("l23") + .setBus1("b2") + .setBus2("b3") + .setR(1) + .setX(3) + .add(); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters().setHvdcAcEmulation(true); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters).setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED).setAcSolverType(KnitroSolverFactory.NAME); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + VscConverterStation cs2 = network.getVscConverterStation("cs2"); + assertActivePowerEquals(-4.9634, cs2.getTerminal()); + assertReactivePowerEquals(360.034, cs2.getTerminal()); + + VscConverterStation cs3 = network.getVscConverterStation("cs3"); + assertActivePowerEquals(5.0286, cs3.getTerminal()); + assertReactivePowerEquals(226.984, cs3.getTerminal()); + } + + @Test + void testHvdcAcEmulation2() { + Network network = HvdcNetworkFactory.createWithHvdcInAcEmulation(); + network.getHvdcLine("hvdc34").newExtension(HvdcAngleDroopActivePowerControlAdder.class) + .withDroop(180) + .withP0(0.f) + .withEnabled(true) + .add(); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters(); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD).setHvdcAcEmulation(true); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters).setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED).setAcSolverType(KnitroSolverFactory.NAME); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + VscConverterStation cs3 = network.getVscConverterStation("cs3"); + assertActivePowerEquals(-0.114, cs3.getTerminal()); + assertReactivePowerEquals(-4.226, cs3.getTerminal()); + + VscConverterStation cs4 = network.getVscConverterStation("cs4"); + assertActivePowerEquals(0.1166, cs4.getTerminal()); + assertReactivePowerEquals(-3.600, cs4.getTerminal()); + + network.getVscConverterStation("cs3").setVoltageRegulatorOn(false); + network.getVscConverterStation("cs4").setVoltageRegulatorOn(false); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + + assertActivePowerEquals(-0.089, cs3.getTerminal()); + assertReactivePowerEquals(0.0, cs3.getTerminal()); + assertActivePowerEquals(0.0914, cs4.getTerminal()); + assertReactivePowerEquals(0.0, cs4.getTerminal()); + } + + @Test + void testHvdcAcEmulationNonSupported() { + Network network = HvdcNetworkFactory.createVsc(); + network.getHvdcLine("hvdc23").newExtension(HvdcAngleDroopActivePowerControlAdder.class) + .withDroop(180) + .withP0(0.f) + .withEnabled(true) + .add(); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters().setHvdcAcEmulation(true); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters).setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED).setAcSolverType(KnitroSolverFactory.NAME); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + VscConverterStation cs2 = network.getVscConverterStation("cs2"); + assertActivePowerEquals(50.0, cs2.getTerminal()); + assertReactivePowerEquals(598.227, cs2.getTerminal()); + + VscConverterStation cs3 = network.getVscConverterStation("cs3"); + assertActivePowerEquals(-49.35, cs3.getTerminal()); + assertReactivePowerEquals(-10.0, cs3.getTerminal()); + } + + @Test + void testHvdcDisconnectedAtOneSide() { + Network network = HvdcNetworkFactory.createVsc(); + network.getVscConverterStation("cs3").getTerminal().disconnect(); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters() + .setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters).setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + Bus bus1 = network.getBusView().getBus("vl1_0"); + assertVoltageEquals(390, bus1); + assertAngleEquals(0, bus1); + + Bus bus2 = network.getBusView().getBus("vl2_0"); + assertVoltageEquals(385, bus2); + assertAngleEquals(0.18116, bus2); + + Bus bus3 = network.getBusView().getBus("vl3_0"); + assertVoltageEquals(Double.NaN, bus3); + assertAngleEquals(Double.NaN, bus3); + + Generator g1 = network.getGenerator("g1"); + assertActivePowerEquals(-102.56, g1.getTerminal()); + assertReactivePowerEquals(-632.700, g1.getTerminal()); + + VscConverterStation cs2 = network.getVscConverterStation("cs2"); + assertActivePowerEquals(0.00, cs2.getTerminal()); + assertReactivePowerEquals(614.750, cs2.getTerminal()); + + VscConverterStation cs3 = network.getVscConverterStation("cs3"); + assertActivePowerEquals(Double.NaN, cs3.getTerminal()); + assertReactivePowerEquals(Double.NaN, cs3.getTerminal()); + + Line l12 = network.getLine("l12"); + assertActivePowerEquals(52.65, l12.getTerminal1()); + assertReactivePowerEquals(632.700, l12.getTerminal1()); + assertActivePowerEquals(-50.00, l12.getTerminal2()); + assertReactivePowerEquals(-624.750, l12.getTerminal2()); + } + + @Test + void testVscConverterWithoutHvdcLineNpe() { + Network network = HvdcNetworkFactory.createVsc(); + network.getHvdcLine("hvdc23").remove(); + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + } + + @Test + void testHvdcPowerAcEmulation() { + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(); + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(198.158, network.getHvdcConverterStation("cs2").getTerminal()); + assertActivePowerEquals(-193.798, network.getHvdcConverterStation("cs3").getTerminal()); + assertActivePowerEquals(-304.359, network.getGenerator("g1").getTerminal()); + assertActivePowerEquals(300.0, network.getLoad("l4").getTerminal()); + } + + @Test + void testHvdcDirectionChangeAcEmulation() { + Network network = HvdcNetworkFactory.createHvdcInAcEmulationInSymetricNetwork(); + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + double pg2 = network.getGenerator("g2").getTerminal().getP(); + double pg1 = network.getGenerator("g1").getTerminal().getP(); + double pcs1 = network.getVscConverterStation("cs1").getTerminal().getP(); + double pcs2 = network.getVscConverterStation("cs2").getTerminal().getP(); + + // Test basic energy conservation terms + assertEquals(0.0, pg2, DELTA_POWER, "g2 should be off"); + assertTrue(-pg1 >= 5.99999, "g1 generates power for all loads"); + assertTrue(-pg1 <= 6.06, "reasonable loss"); + assertTrue(pcs1 > 0, "Power enters at cs1"); + assertTrue(pcs2 < 0, "Power delivered by cs2"); + assertTrue(Math.abs(pcs1) > Math.abs(pcs2), "Loss at HVDC output"); + + // Reverse power flow direction + network.getGenerator("g2").setTargetP(5); + network.getGenerator("g1").setTargetP(0); + result = loadFlowRunner.run(network, new LoadFlowParameters()); + assertTrue(result.isFullyConverged()); + + pg2 = network.getGenerator("g2").getTerminal().getP(); + pg1 = network.getGenerator("g1").getTerminal().getP(); + pcs1 = network.getVscConverterStation("cs1").getTerminal().getP(); + pcs2 = network.getVscConverterStation("cs2").getTerminal().getP(); + + // Test basic energy conservation terms in symetric network + // (active power is not a close enough symetric as in first run for some reason - so we can't compare b1 and b2 values for all termnals) + assertEquals(0.0, pg1, DELTA_POWER, "g1 should be off"); + assertTrue(-pg2 >= 5.99999, "g2 generates power for all loads"); + assertTrue(-pg2 <= 6.06, "reasonable loss"); + assertTrue(pcs2 > 0, "Power enters at cs2"); + assertTrue(pcs1 < 0, "Power delivered by cs1"); + assertTrue(Math.abs(pcs2) > Math.abs(pcs1), "Loss at HVDC output"); + } + + @Test + void testLccOpenAtOneSide() { + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.LCC); + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + // generator g1 expected to deliver enough power for the load + assertActivePowerEquals(-304.400, network.getGenerator("g1").getTerminal()); + assertActivePowerEquals(300.00, network.getLoad("l4").getTerminal()); + assertActivePowerEquals(-195.600, network.getHvdcConverterStation("cs2").getTerminal()); + assertActivePowerEquals(200.00, network.getHvdcConverterStation("cs3").getTerminal()); + + Line l34 = network.getLine("l34"); + l34.getTerminals().stream().forEach(Terminal::disconnect); + result = loadFlowRunner.run(network); + assertTrue(result.isFullyConverged()); // note that for LCC test the smaller component is flagged as NO_CALCULATION + + assertActivePowerEquals(-300.00, network.getGenerator("g1").getTerminal()); + assertActivePowerEquals(300.00, network.getLoad("l4").getTerminal()); + assertTrue(Double.isNaN(network.getHvdcConverterStation("cs2").getTerminal().getP())); // FIXME + assertTrue(Double.isNaN(network.getHvdcConverterStation("cs3").getTerminal().getP())); + } + + @Test + void testVscOpenAtOneSide() { + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC); + LoadFlowParameters parameters = new LoadFlowParameters() + .setHvdcAcEmulation(false); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + // generator g1 expected to deliver enough power for the load + assertActivePowerEquals(-304.400, network.getGenerator("g1").getTerminal()); + assertActivePowerEquals(300.00, network.getLoad("l4").getTerminal()); + assertActivePowerEquals(-195.600, network.getHvdcConverterStation("cs2").getTerminal()); + assertActivePowerEquals(200.00, network.getHvdcConverterStation("cs3").getTerminal()); + + Line l34 = network.getLine("l34"); + l34.getTerminals().stream().forEach(Terminal::disconnect); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertActivePowerEquals(-300.00, network.getGenerator("g1").getTerminal()); + assertActivePowerEquals(300.00, network.getLoad("l4").getTerminal()); + assertActivePowerEquals(0.0, network.getHvdcConverterStation("cs2").getTerminal()); + assertActivePowerEquals(0.0, network.getHvdcConverterStation("cs3").getTerminal()); + } + + @Test + void testHvdcAndGenerator() { + Network network = HvdcNetworkFactory.createWithHvdcAndGenerator(); + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-1.956, network.getVscConverterStation("cs3").getTerminal()); + assertActivePowerEquals(2.0, network.getVscConverterStation("cs4").getTerminal()); + assertActivePowerEquals(-2.0, network.getGenerator("g4").getTerminal()); + assertActivePowerEquals(-2.043, network.getGenerator("g1").getTerminal()); + } + + @Test + void testVscVoltageControlWithZeroTargetP() { + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + // Set specific voltage setPoints to the stations + double vcs2 = 397; + double vcs3 = 401; + network.getVscConverterStation("cs2").setVoltageSetpoint(vcs2); + network.getVscConverterStation("cs3").setVoltageSetpoint(vcs3); + + // shut down active power flow in HVDC + network.getHvdcLine("hvdc23").setActivePowerSetpoint(0); + network.getHvdcLine("hvdc23").getExtension(HvdcAngleDroopActivePowerControl.class).setDroop(0).setP0(0); + + LoadFlowParameters p = new LoadFlowParameters(); + + // without AC emulation + p.setHvdcAcEmulation(false); + OpenLoadFlowParameters.create(p).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, p); + + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(0, network.getVscConverterStation("cs2").getTerminal()); + assertVoltageEquals(vcs2, network.getVscConverterStation("cs2").getTerminal().getBusView().getBus()); + assertActivePowerEquals(0, network.getVscConverterStation("cs3").getTerminal()); + assertVoltageEquals(vcs3, network.getVscConverterStation("cs3").getTerminal().getBusView().getBus()); + + // with AC emulation + p.setHvdcAcEmulation(true); + result = loadFlowRunner.run(network, p); + + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(0, network.getVscConverterStation("cs2").getTerminal()); + assertVoltageEquals(vcs2, network.getVscConverterStation("cs2").getTerminal().getBusView().getBus()); + assertActivePowerEquals(0, network.getVscConverterStation("cs3").getTerminal()); + assertVoltageEquals(vcs3, network.getVscConverterStation("cs3").getTerminal().getBusView().getBus()); + } + + @Test + void testVscVoltageControlWithOneSideDisconnected() { + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + // Set specific voltage setPoints to the stations + double vcs2 = 397; + double vcs3 = 401; + network.getVscConverterStation("cs2").setVoltageSetpoint(vcs2); + network.getVscConverterStation("cs3").setVoltageSetpoint(vcs3); + + // Disconnect line at HVDCoutput + Line l34 = network.getLine("l34"); + l34.getTerminals().stream().forEach(Terminal::disconnect); + + LoadFlowParameters p = new LoadFlowParameters(); + + // without AC emulation + p.setHvdcAcEmulation(false); + OpenLoadFlowParameters.create(p).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, p); + + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(0, network.getVscConverterStation("cs2").getTerminal()); + assertVoltageEquals(vcs2, network.getVscConverterStation("cs2").getTerminal().getBusView().getBus()); + + // with AC emulation + p.setHvdcAcEmulation(true); + result = loadFlowRunner.run(network, p); + + assertActivePowerEquals(0, network.getVscConverterStation("cs2").getTerminal()); + assertVoltageEquals(vcs2, network.getVscConverterStation("cs2").getTerminal().getBusView().getBus()); + } + + @Test + void testAcEmuWithOperationalLimits() { + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC); + // without limit p=195 + network.getHvdcLine("hvdc23") + .newExtension(HvdcOperatorActivePowerRangeAdder.class) + .withOprFromCS2toCS1(180) + .withOprFromCS1toCS2(170) + .add(); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters p = new LoadFlowParameters(); + p.setHvdcAcEmulation(true); + OpenLoadFlowParameters.create(p).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, p); + + assertTrue(result.isFullyConverged()); + + // Active flow capped at limit. Output has losses (due to VSC stations) + assertEquals(170, network.getHvdcConverterStation("cs2").getTerminal().getP(), DELTA_POWER); + assertEquals(-166.263, network.getHvdcConverterStation("cs3").getTerminal().getP(), DELTA_POWER); + + // now invert power direction + HvdcAngleDroopActivePowerControl activePowerControl = network.getHvdcLine("hvdc23").getExtension(HvdcAngleDroopActivePowerControl.class); + activePowerControl.setP0(-activePowerControl.getP0()); + result = loadFlowRunner.run(network, p); + assertTrue(result.isFullyConverged()); + + // Active flow capped at other direction's limit. Output has losses (due to VSC stations) + assertEquals(-176.042, network.getHvdcConverterStation("cs2").getTerminal().getP(), DELTA_POWER); + assertEquals(180, network.getHvdcConverterStation("cs3").getTerminal().getP(), DELTA_POWER); + } + + @Test + void testAcEmuAndPMax() { + Network network = HvdcNetworkFactory.createHvdcLinkedByTwoLinesAndSwitch(HvdcConverterStation.HvdcType.VSC); + // without limit p=195 + network.getHvdcLine("hvdc23") + .setMaxP(170); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters p = new LoadFlowParameters(); + p.setHvdcAcEmulation(true); + OpenLoadFlowParameters.create(p).setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, p); + + assertTrue(result.isFullyConverged()); + + // Active flow capped at limit. Output has losses (due to VSC stations) + assertActivePowerEquals(170, network.getHvdcConverterStation("cs2").getTerminal()); + assertActivePowerEquals(-166.263, network.getHvdcConverterStation("cs3").getTerminal()); + + // now invert power direction + HvdcAngleDroopActivePowerControl activePowerControl = network.getHvdcLine("hvdc23").getExtension(HvdcAngleDroopActivePowerControl.class); + activePowerControl.setP0(-activePowerControl.getP0()); + result = loadFlowRunner.run(network, p); + assertTrue(result.isFullyConverged()); + + assertActivePowerEquals(-166.263, network.getHvdcConverterStation("cs2").getTerminal()); + assertActivePowerEquals(170, network.getHvdcConverterStation("cs3").getTerminal()); + } + + @Test + void testDcLoadFlowWithHvdcAcEmulation2() { + Network network = HvdcNetworkFactory.createVsc(); + network.newLine() // in order to have only one synchronous component for the moment. + .setId("l23") + .setVoltageLevel1("vl2") + .setBus1("b2") + .setVoltageLevel2("vl3") + .setBus2("b3") + .setR(1) + .setX(3) + .setG1(0) + .setG2(0) + .setB1(0) + .setB2(0) + .add(); + HvdcAngleDroopActivePowerControl hvdcAngleDroopActivePowerControl = network.getHvdcLine("hvdc23").newExtension(HvdcAngleDroopActivePowerControlAdder.class) + .withDroop(180) + .withP0(0.f) + .withEnabled(true) + .add(); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters() + .setDc(true); + OpenLoadFlowParameters olfParams = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setAcSolverType(KnitroSolverFactory.NAME); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + VscConverterStation cs2 = network.getVscConverterStation("cs2"); + assertActivePowerEquals(8.102, cs2.getTerminal()); // 0MW + 180 MW/deg * 0.04501deg + assertAngleEquals(0.0, cs2.getTerminal().getBusView().getBus()); + VscConverterStation cs3 = network.getVscConverterStation("cs3"); + assertActivePowerEquals(-8.102, cs3.getTerminal()); + assertAngleEquals(-0.04501, cs3.getTerminal().getBusView().getBus()); + + // Now with a non null P0 + hvdcAngleDroopActivePowerControl.setP0(10); + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertActivePowerEquals(16.482, cs2.getTerminal()); // 10MW + 180 MW/deg * 0.036008deg + assertAngleEquals(0.0, cs2.getTerminal().getBusView().getBus()); + assertActivePowerEquals(-16.482, cs3.getTerminal()); + assertAngleEquals(-0.036008, cs3.getTerminal().getBusView().getBus()); + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertActivePowerEquals(16.482, cs2.getTerminal()); // 10MW + 180 MW/deg * 0.036008deg + assertAngleEquals(0.0, cs2.getTerminal().getBusView().getBus()); + assertActivePowerEquals(-16.482, cs3.getTerminal()); + assertAngleEquals(-0.036008, cs3.getTerminal().getBusView().getBus()); + + // Add another HVDC line connected the other way arround to make sure all equation terms are run + // (the equations on the slack bus are not run) + network.getVoltageLevel("vl2") + .newVscConverterStation() + .setId("cs2Bis") + .setConnectableBus("b2") + .setBus("b2") + .setVoltageRegulatorOn(true) + .setVoltageSetpoint(385) + .setReactivePowerSetpoint(100) + .setLossFactor(1.1f) + .add(); + network.getVoltageLevel("vl3") + .newVscConverterStation() + .setId("cs3Bis") + .setConnectableBus("b3") + .setBus("b3") + .setVoltageRegulatorOn(true) + .setVoltageSetpoint(383) + .setReactivePowerSetpoint(100) + .setLossFactor(0.2f) + .add(); + + network.newHvdcLine() + .setId("hvdc32") + .setConverterStationId1("cs3Bis") + .setConverterStationId2("cs2Bis") + .setNominalV(400) + .setR(0.1) + .setActivePowerSetpoint(50) + .setConvertersMode(HvdcLine.ConvertersMode.SIDE_1_RECTIFIER_SIDE_2_INVERTER) + .setMaxP(500) + .add(); + network.getHvdcLine("hvdc32").newExtension(HvdcAngleDroopActivePowerControlAdder.class) + .withDroop(180) + .withP0(0.f) + .withEnabled(true) + .add(); + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + + assertActivePowerEquals(15.578, cs2.getTerminal()); // 10MW + 180 MW/deg * 0.030988deg + assertAngleEquals(0.0, cs2.getTerminal().getBusView().getBus()); + assertActivePowerEquals(-15.578, cs3.getTerminal()); + assertAngleEquals(-0.030988, cs3.getTerminal().getBusView().getBus()); + assertActivePowerEquals(5.578, network.getVscConverterStation("cs2Bis").getTerminal()); // 0MW + 180 MW/deg * 0.030988deg + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowWithCachingTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowWithCachingTest.java new file mode 100644 index 0000000..1afce29 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcLoadFlowWithCachingTest.java @@ -0,0 +1,621 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VariantManagerConstants; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.NetworkCache; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class AcLoadFlowWithCachingTest { + + private LoadFlow.Runner loadFlowRunner; + + private LoadFlowParameters parameters; + + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters(); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setNetworkCacheEnabled(true) + .setAcSolverType(KnitroSolverFactory.NAME); + NetworkCache.INSTANCE.clear(); + } + + @Test + void testTargetV() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + var gen = network.getGenerator("GEN"); + var ngen = network.getBusBreakerView().getBus("NGEN"); + var nload = network.getBusBreakerView().getBus("NLOAD"); + + assertEquals(0, NetworkCache.INSTANCE.getEntryCount()); + var result = loadFlowRunner.run(network, parameters); + assertEquals(1, NetworkCache.INSTANCE.getEntryCount()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(5, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(24.5, ngen); + assertVoltageEquals(147.578, nload); + + gen.setTargetV(24.1); + + result = loadFlowRunner.run(network, parameters); + assertEquals(1, NetworkCache.INSTANCE.getEntryCount()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(3, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(24.1, ngen); + assertVoltageEquals(144.402, nload); + + result = loadFlowRunner.run(network, parameters); + assertEquals(1, NetworkCache.INSTANCE.getEntryCount()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(0, result.getComponentResults().get(0).getIterationCount()); + } + + @Test + void testParameterChange() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + assertEquals(0, NetworkCache.INSTANCE.getEntryCount()); + loadFlowRunner.run(network, parameters); + assertEquals(1, NetworkCache.INSTANCE.getEntryCount()); + NetworkCache.Entry entry = NetworkCache.INSTANCE.findEntry(network).orElseThrow(); + loadFlowRunner.run(network, parameters); + assertEquals(1, NetworkCache.INSTANCE.getEntryCount()); + NetworkCache.Entry entry2 = NetworkCache.INSTANCE.findEntry(network).orElseThrow(); + assertSame(entry, entry2); // reuse same cache + + // run with different parameters + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + loadFlowRunner.run(network, parameters); + assertEquals(1, NetworkCache.INSTANCE.getEntryCount()); + NetworkCache.Entry entry3 = NetworkCache.INSTANCE.findEntry(network).orElseThrow(); + assertNotSame(entry, entry3); // cache has been evicted and recreated + } + + @Test + @Disabled("Disabled by default because not reliable, depends on JVM, garbage collector, and machine performance") + void testCacheEvictionBusBreaker() { + int runCount = 10; + for (int i = 0; i < runCount; i++) { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + loadFlowRunner.run(network, parameters); + System.gc(); + } + assertTrue(NetworkCache.INSTANCE.getEntryCount() < runCount); + } + + @Test + @Disabled("Disabled by default because not reliable, depends on JVM, garbage collector, and machine performance") + void testCacheEvictionNodeBreaker() { + int runCount = 10; + parametersExt.setActionableSwitchesIds(Set.of("S1VL1_LD1_BREAKER")); + for (int i = 0; i < runCount; i++) { + var network = FourSubstationsNodeBreakerFactory.create(); + loadFlowRunner.run(network, parameters); + System.gc(); + } + assertTrue(NetworkCache.INSTANCE.getEntryCount() < runCount); + } + + @Test + void testEntryEviction() { + var network = FourSubstationsNodeBreakerFactory.create(); + assertEquals(1, network.getVariantManager().getVariantIds().size()); + + parametersExt.setActionableSwitchesIds(Set.of("S1VL1_LD1_BREAKER")); + loadFlowRunner.run(network, parameters); + assertEquals(2, network.getVariantManager().getVariantIds().size()); + + parametersExt.setActionableSwitchesIds(Set.of("S1VL1_TWT_BREAKER")); + loadFlowRunner.run(network, parameters); + assertEquals(2, network.getVariantManager().getVariantIds().size()); + } + + @Test + void testUnsupportedAttributeChange() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + var gen = network.getGenerator("GEN"); + + loadFlowRunner.run(network, parameters); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + gen.setTargetQ(10); + assertNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } + + @Test + void testPropertiesChange() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + var gen = network.getGenerator("GEN"); + + loadFlowRunner.run(network, parameters); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + gen.setProperty("foo", "bar"); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + gen.setProperty("foo", "baz"); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + gen.removeProperty("foo"); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } + + @Test + void testVariantChange() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + loadFlowRunner.run(network, parameters); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, "v"); + assertNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + loadFlowRunner.run(network, parameters); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + network.getVariantManager().cloneVariant(VariantManagerConstants.INITIAL_VARIANT_ID, "v", true); + assertNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + loadFlowRunner.run(network, parameters); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + network.getVariantManager().removeVariant("v"); + assertNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } + + @Test + void testLoadAddition() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + loadFlowRunner.run(network, parameters); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + network.getVoltageLevel("VLLOAD").newLoad() + .setId("NEWLOAD") + .setConnectableBus("NLOAD") + .setBus("NLOAD") + .setP0(10) + .setQ0(10) + .add(); + + assertNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } + + @Test + void testLoadRemoval() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + loadFlowRunner.run(network, parameters); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + network.getLoad("LOAD").remove(); + + assertNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } + + @Test + void testShunt() { + var network = ShuntNetworkFactory.create(); + var shunt = network.getShuntCompensator("SHUNT"); + + assertTrue(NetworkCache.INSTANCE.findEntry(network).isEmpty()); + loadFlowRunner.run(network, parameters); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + assertActivePowerEquals(0, shunt.getTerminal()); + assertReactivePowerEquals(0, shunt.getTerminal()); + + shunt.setSectionCount(1); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // cache has not been invalidated but updated + + loadFlowRunner.run(network, parameters); + assertActivePowerEquals(0, shunt.getTerminal()); + assertReactivePowerEquals(-152.826, shunt.getTerminal()); + } + + @Test + void testShunt2() { + var network = ShuntNetworkFactory.create(); + var shunt = network.getShuntCompensator("SHUNT"); + + assertTrue(NetworkCache.INSTANCE.findEntry(network).isEmpty()); + loadFlowRunner.run(network, parameters); // Run a first LF before changing a parameter. + parameters.setShuntCompensatorVoltageControlOn(true); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + loadFlowRunner.run(network, parameters); + assertActivePowerEquals(0, shunt.getTerminal()); + assertReactivePowerEquals(-152.826, shunt.getTerminal()); + assertEquals(1, shunt.getSectionCount()); + + shunt.setSectionCount(0); + assertNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // cache has been invalidated + } + + @Test + void testShunt3() { + var network = ShuntNetworkFactory.createWithTwoShuntCompensators(); + var shunt = network.getShuntCompensator("SHUNT"); // with voltage control capabilities. + + assertTrue(NetworkCache.INSTANCE.findEntry(network).isEmpty()); + loadFlowRunner.run(network, parameters); + parameters.setShuntCompensatorVoltageControlOn(true); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + loadFlowRunner.run(network, parameters); + assertActivePowerEquals(0, shunt.getTerminal()); + assertReactivePowerEquals(-152.826, shunt.getTerminal()); + assertEquals(1, shunt.getSectionCount()); + + shunt.setSectionCount(1); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } + + @Test + void testSwitchOpen() { + var network = NodeBreakerNetworkFactory.create(); + var l1 = network.getLine("L1"); + var l2 = network.getLine("L2"); + + parametersExt.setActionableSwitchesIds(Set.of("C")); + + assertTrue(NetworkCache.INSTANCE.findEntry(network).isEmpty()); + + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(3, result.getComponentResults().get(0).getIterationCount()); + assertActivePowerEquals(301.884, l1.getTerminal1()); + assertActivePowerEquals(301.884, l2.getTerminal1()); + + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + network.getSwitch("C").setOpen(true); + + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(5, result.getComponentResults().get(0).getIterationCount()); + assertActivePowerEquals(0.0974, l1.getTerminal1()); + assertActivePowerEquals(607.682, l2.getTerminal1()); + } + + @Test + void testSwitchClose() { + var network = NodeBreakerNetworkFactory.create(); + var l1 = network.getLine("L1"); + var l2 = network.getLine("L2"); + + parametersExt.setActionableSwitchesIds(Set.of("C")); + + var c = network.getSwitch("C"); + c.setOpen(true); + + assertTrue(NetworkCache.INSTANCE.findEntry(network).isEmpty()); + + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(5, result.getComponentResults().get(0).getIterationCount()); + assertActivePowerEquals(0.0980, l1.getTerminal1()); + assertActivePowerEquals(607.682, l2.getTerminal1()); + + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + network.getSwitch("C").setOpen(false); + + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(4, result.getComponentResults().get(0).getIterationCount()); + assertActivePowerEquals(301.884, l1.getTerminal1()); + assertActivePowerEquals(301.884, l2.getTerminal1()); + } + + @Test + void testInvalidNetwork() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + var result = loadFlowRunner.run(network, parameters); + + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + var gen = network.getGenerator("GEN"); + gen.setTargetV(1000); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, result.getComponentResults().get(0).getStatus()); + } + + @Test + @Disabled("To support later") + void testInitiallyInvalidNetwork() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + var gen = network.getGenerator("GEN"); + gen.setTargetV(1000); + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, result.getComponentResults().get(0).getStatus()); + + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + gen.setTargetV(24); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + } + + @Test + void testSwitchIssueWithInit() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + var vlload = network.getVoltageLevel("VLLOAD"); + vlload.getBusBreakerView().newBus() + .setId("NLOAD2") + .add(); + var br = vlload.getBusBreakerView().newSwitch() + .setId("BR") + .setBus1("NLOAD") + .setBus2("NLOAD2") + .setOpen(true) + .add(); + vlload.newLoad() + .setId("LOAD2") + .setBus("NLOAD2") + .setP0(5) + .setQ0(5) + .add(); + parametersExt.setActionableSwitchesIds(Set.of("BR")); +// parametersExt.setAcSolverType(NewtonRaphsonFactory.NAME); + + assertTrue(NetworkCache.INSTANCE.findEntry(network).isEmpty()); + + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(5, result.getComponentResults().get(0).getIterationCount()); + + br.setOpen(false); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(4, result.getComponentResults().get(0).getIterationCount()); + + br.setOpen(true); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(1, result.getComponentResults().get(0).getIterationCount()); + } + + private static void checkVoltageIsDefinedForAllBuses(Network network) { + for (Bus bus : network.getBusView().getBuses()) { + assertFalse(Double.isNaN(bus.getV())); + assertFalse(Double.isNaN(bus.getAngle())); + } + } + + @Test + void testUpdateNetworkFix() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + checkVoltageIsDefinedForAllBuses(network); + } + + @Test + void testUpdateWithMultipleSynchronousComponents() { + Network network = HvdcNetworkFactory.createVsc(); + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + checkVoltageIsDefinedForAllBuses(network); + var g1 = network.getGenerator("g1"); + g1.setTargetV(g1.getTargetV() + 0.1); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + checkVoltageIsDefinedForAllBuses(network); + } + + @Test + void fixCacheInvalidationWhenUpdatingTapPosition() { + Network network = VoltageControlNetworkFactory.createNetworkWithT2wt(); + var t2wt = network.getTwoWindingsTransformer("T2wT"); + t2wt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(t2wt.getTerminal2()) + .setTargetV(34.0); + + parameters.setTransformerVoltageControlOn(true); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } + +// FIXME +// @Test +// void testSecondaryVoltageControl() { +// parametersExt.setSecondaryVoltageControl(true); +// var network = IeeeCdfNetworkFactory.create14(); +// network.newExtension(SecondaryVoltageControlAdder.class) +// .newControlZone() +// .withName("z1") +// .newPilotPoint() +// .withTargetV(12.7) +// .withBusbarSectionsOrBusesIds(List.of("B10")) +// .add() +// .newControlUnit() +// .withId("B6-G") +// .add() +// .newControlUnit() +// .withId("B8-G") +// .add() +// .add() +// .add(); +// SecondaryVoltageControl control = network.getExtension(SecondaryVoltageControl.class); +// ControlZone z1 = control.getControlZone("z1").orElseThrow(); +// PilotPoint pilotPoint = z1.getPilotPoint(); +// LoadFlowResult result = loadFlowRunner.run(network, parameters); +// assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); +// assertEquals(6, result.getComponentResults().get(0).getIterationCount()); +// var b10 = network.getBusBreakerView().getBus("B10"); +// assertVoltageEquals(12.7, b10); +// assertReactivePowerEquals(-17.827, network.getGenerator("B6-G").getTerminal()); +// assertReactivePowerEquals(-17.827, network.getGenerator("B8-G").getTerminal()); +// +// // update pilot point target voltage +// pilotPoint.setTargetV(12.5); +// assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated +// +// result = loadFlowRunner.run(network, parameters); +// assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); +// assertEquals(3, result.getComponentResults().get(0).getIterationCount()); +// assertVoltageEquals(12.5, b10); +// assertReactivePowerEquals(-11.836, network.getGenerator("B6-G").getTerminal()); +// assertReactivePowerEquals(-11.836, network.getGenerator("B8-G").getTerminal()); +// +// ControlUnit b6g = z1.getControlUnit("B6-G").orElseThrow(); +// b6g.setParticipate(false); +// assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated +// +// result = loadFlowRunner.run(network, parameters); +// assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); +// assertEquals(0, result.getComponentResults().get(0).getIterationCount()); +// // there is no re-run of secondary voltage control outer loop, this is expected as pilot point has already reached +// // its target voltage and remaining control unit is necessarily aligned. +// assertVoltageEquals(12.5, b10); +// assertReactivePowerEquals(-11.836, network.getGenerator("B6-G").getTerminal()); +// assertReactivePowerEquals(-11.836, network.getGenerator("B8-G").getTerminal()); +// +// pilotPoint.setTargetV(12.7); +// assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated +// result = loadFlowRunner.run(network, parameters); +// assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); +// assertEquals(6, result.getComponentResults().get(0).getIterationCount()); +// assertVoltageEquals(12.613, b10); // we cannot reach back to 12.7 Kv with only one control unit +// assertReactivePowerEquals(-6.776, network.getGenerator("B6-G").getTerminal()); +// assertReactivePowerEquals(-24.0, network.getGenerator("B8-G").getTerminal()); +// +// // get b6 generator back +// b6g.setParticipate(true); +// assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated +// result = loadFlowRunner.run(network, parameters); +// assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); +// assertEquals(5, result.getComponentResults().get(0).getIterationCount()); +// assertVoltageEquals(12.7, b10); // we can reach now 12.7 Kv with the 2 control units +// assertReactivePowerEquals(-17.827, network.getGenerator("B6-G").getTerminal()); +// assertReactivePowerEquals(-17.827, network.getGenerator("B8-G").getTerminal()); +// } + + @Test + void testTransfo2VoltageTargetChange() { + var network = VoltageControlNetworkFactory.createNetworkWithT2wt(); + var twt = network.getTwoWindingsTransformer("T2wT"); + + parameters.setTransformerVoltageControlOn(true); + twt.getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(twt.getTerminal2()) + .setTargetV(30.0); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, twt.getRatioTapChanger().getTapPosition()); + + twt.getRatioTapChanger().setTargetV(32); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, twt.getRatioTapChanger().getTapPosition()); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated + } + + @Test + void testTransfo3VoltageTargetChange() { + var network = VoltageControlNetworkFactory.createNetworkWithT3wt(); + var twt = network.getThreeWindingsTransformer("T3wT"); + + twt.getLeg2().getRatioTapChanger() + .setTargetDeadband(0) + .setRegulating(true) + .setTapPosition(0) + .setRegulationTerminal(twt.getLeg2().getTerminal()) + .setTargetV(30); + + parameters.setTransformerVoltageControlOn(true); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, twt.getLeg2().getRatioTapChanger().getTapPosition()); + + twt.getLeg2().getRatioTapChanger().setTargetV(26); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated + + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(2, twt.getLeg2().getRatioTapChanger().getTapPosition()); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated + } + + @Test + void testTransfo2TapPositionChange() { + var network = VoltageControlNetworkFactory.createNetworkWithT2wt(); + var twt = network.getTwoWindingsTransformer("T2wT"); + assertEquals(0, twt.getRatioTapChanger().getTapPosition()); + + parametersExt.setActionableTransformersIds(Set.of("T2wT")); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + twt.getRatioTapChanger().setTapPosition(1); + + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(5, result.getComponentResults().get(0).getIterationCount()); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } + + @Test + void testTransfo3TapPositionChange() { + var network = VoltageControlNetworkFactory.createNetworkWithT3wt(); + var twt = network.getThreeWindingsTransformer("T3wT"); + assertEquals(0, twt.getLeg2().getRatioTapChanger().getTapPosition()); + + parametersExt.setActionableTransformersIds(Set.of("T3wT")); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + + twt.getLeg2().getRatioTapChanger().setTapPosition(1); + + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); // check cache has not been invalidated + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(4, result.getComponentResults().get(0).getIterationCount()); + assertNotNull(NetworkCache.INSTANCE.findEntry(network).orElseThrow().getContexts()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AcloadFlowReactiveLimitsTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcloadFlowReactiveLimitsTest.java new file mode 100644 index 0000000..221b1f3 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AcloadFlowReactiveLimitsTest.java @@ -0,0 +1,166 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.impl.Networks; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertReactivePowerEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class AcloadFlowReactiveLimitsTest { + + private Network network; + private VoltageLevel vlgen; + private VoltageLevel vlgen2; + private Generator gen; + private Generator gen2; + private Load load; + private TwoWindingsTransformer nhv2Nload; + private TwoWindingsTransformer ngen2Nhv1; + + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + + private void createNetwork() { + network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + // access to already created equipments + load = network.getLoad("LOAD"); + vlgen = network.getVoltageLevel("VLGEN"); + nhv2Nload = network.getTwoWindingsTransformer("NHV2_NLOAD"); + gen = network.getGenerator("GEN"); + Substation p1 = network.getSubstation("P1"); + + // reduce GEN reactive range + gen.newMinMaxReactiveLimits() + .setMinQ(0) + .setMaxQ(280) + .add(); + + // create a new generator GEN2 + vlgen2 = p1.newVoltageLevel() + .setId("VLGEN2") + .setNominalV(24.0) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + vlgen2.getBusBreakerView().newBus() + .setId("NGEN2") + .add(); + gen2 = vlgen2.newGenerator() + .setId("GEN2") + .setBus("NGEN2") + .setConnectableBus("NGEN2") + .setMinP(-9999.99) + .setMaxP(9999.99) + .setVoltageRegulatorOn(true) + .setTargetV(24.5) + .setTargetP(100) + .add(); + gen2.newMinMaxReactiveLimits() + .setMinQ(0) + .setMaxQ(100) + .add(); + int zb380 = 380 * 380 / 100; + ngen2Nhv1 = p1.newTwoWindingsTransformer() + .setId("NGEN2_NHV1") + .setBus1("NGEN2") + .setConnectableBus1("NGEN2") + .setRatedU1(24.0) + .setBus2("NHV1") + .setConnectableBus2("NHV1") + .setRatedU2(400.0) + .setR(0.24 / 1800 * zb380) + .setX(Math.sqrt(10 * 10 - 0.24 * 0.24) / 1800 * zb380) + .add(); + + // fix active power balance + load.setP0(699.838); + } + + @BeforeEach + void setUp() { + createNetwork(); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters().setUseReactiveLimits(true) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void diagramTest() { + List lfNetworks = Networks.load(network, new FirstSlackBusSelector()); + assertEquals(1, lfNetworks.size()); + LfNetwork lfNetwork = lfNetworks.get(0); + LfBus genBus = lfNetwork.getBus(0); + assertEquals("VLGEN_0", genBus.getId()); + LfGenerator gen = genBus.getGenerators().get(0); + assertEquals(0, gen.getMinQ(), 0); + assertEquals(2.8, gen.getMaxQ(), 0); + } + + @Test + void test() { + parameters.setUseReactiveLimits(false); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(-109.228, gen.getTerminal()); + assertReactivePowerEquals(-152.265, gen2.getTerminal()); + assertReactivePowerEquals(-199.998, nhv2Nload.getTerminal2()); + + parameters.setUseReactiveLimits(true); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(-164.315, gen.getTerminal()); + assertReactivePowerEquals(-100, gen2.getTerminal()); // GEN is correctly limited to 100 MVar + assertReactivePowerEquals(100, ngen2Nhv1.getTerminal1()); + assertReactivePowerEquals(-200, nhv2Nload.getTerminal2()); + } + + @Test + void testWithMixedGenLoad() { + // add a 20 MVar to LOAD2 connected to same bus as GEN2 and allow 20 MVar additional max reactive power + // to GEN2 => result should be the same + vlgen2.newLoad() + .setId("LOAD2") + .setConnectableBus("NGEN2") + .setBus("NGEN2") + .setP0(0) + .setQ0(20) + .add(); + gen2.newMinMaxReactiveLimits() + .setMinQ(0) + .setMaxQ(120) + .add(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertReactivePowerEquals(-164.315, gen.getTerminal()); + assertReactivePowerEquals(-120, gen2.getTerminal()); + assertReactivePowerEquals(100, ngen2Nhv1.getTerminal1()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/AutomationSystemTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/AutomationSystemTest.java new file mode 100644 index 0000000..179f68e --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/AutomationSystemTest.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Line; +import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.AutomationSystemNetworkFactory; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertCurrentEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class AutomationSystemTest { + + @Test + void test() { + Network network = AutomationSystemNetworkFactory.create(); + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters.create(parameters) + .setSimulateAutomationSystems(true) + .setAcSolverType(KnitroSolverFactory.NAME); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + Line l12 = network.getLine("l12"); + Line l34 = network.getLine("l34"); + assertCurrentEquals(298.953, l12.getTerminal1()); + assertCurrentEquals(34.333, l34.getTerminal1()); // no more loop in LV network + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/CountriesToBalanceTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/CountriesToBalanceTest.java new file mode 100644 index 0000000..77aa31c --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/CountriesToBalanceTest.java @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory; +import com.powsybl.iidm.network.Country; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class CountriesToBalanceTest { + + private Network network; + private Generator g1; + private Generator g2; + + private LoadFlow.Runner loadFlowRunner; + + @BeforeEach + void setUp() { + network = IeeeCdfNetworkFactory.create14(); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + for (Generator g : network.getGenerators()) { + g.setMaxP(2000); + } + g1 = network.getGenerator("B1-G"); + g2 = network.getGenerator("B2-G"); + g1.getTerminal().getVoltageLevel().getSubstation().orElseThrow().setCountry(Country.FR); + g2.getTerminal().getVoltageLevel().getSubstation().orElseThrow().setCountry(Country.BE); + for (Load l : network.getLoads()) { + l.setP0(l.getP0() * 1.1); + l.setQ0(l.getQ0() * 1.1); + } + } + + @Test + void testAc() { + var parameters = new LoadFlowParameters(); + OpenLoadFlowParameters parametersExt = OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + + loadFlowRunner.run(network, parameters); + // by default g1 and g2 are balancing + assertActivePowerEquals(-246.973, g1.getTerminal()); + assertActivePowerEquals(-54.573, g2.getTerminal()); + + parameters.setCountriesToBalance(Set.of(Country.FR)); + loadFlowRunner.run(network, parameters); + assertActivePowerEquals(-261.547, g1.getTerminal()); // only g1 is balancing + assertActivePowerEquals(-40.0, g2.getTerminal()); + + parameters.setCountriesToBalance(Set.of(Country.BE)); + loadFlowRunner.run(network, parameters); + assertActivePowerEquals(-232.4, g1.getTerminal()); + assertActivePowerEquals(-67.458, g2.getTerminal()); // only g2 is balancing + + parameters.setCountriesToBalance(Set.of(Country.FR, Country.BE)); + loadFlowRunner.run(network, parameters); + // both are balancing + assertActivePowerEquals(-246.973, g1.getTerminal()); + assertActivePowerEquals(-54.573, g2.getTerminal()); + + parameters.setCountriesToBalance(Set.of()); + loadFlowRunner.run(network, parameters); + // empty also means both are balancing + assertActivePowerEquals(-246.973, g1.getTerminal()); + assertActivePowerEquals(-54.573, g2.getTerminal()); + + parameters.setDistributedSlack(false); + loadFlowRunner.run(network, parameters); + // no more balancing + assertActivePowerEquals(-232.4, g1.getTerminal()); + assertActivePowerEquals(-40.0, g2.getTerminal()); + } + + @Test + void testDc() { + var parameters = new LoadFlowParameters() + .setDc(true); + OpenLoadFlowParameters parametersExt = OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + + loadFlowRunner.run(network, parameters); + // by default g1 and g2 are balancing + assertActivePowerEquals(-238.649, g1.getTerminal()); + assertActivePowerEquals(-46.25, g2.getTerminal()); + + parameters.setCountriesToBalance(Set.of(Country.FR)); + loadFlowRunner.run(network, parameters); + assertActivePowerEquals(-244.9, g1.getTerminal()); // only g1 is balancing + assertActivePowerEquals(-40.0, g2.getTerminal()); + + parameters.setCountriesToBalance(Set.of(Country.BE)); + loadFlowRunner.run(network, parameters); + assertActivePowerEquals(-232.4, g1.getTerminal()); + assertActivePowerEquals(-52.5, g2.getTerminal()); // only g2 is balancing + + parameters.setCountriesToBalance(Set.of(Country.FR, Country.BE)); + loadFlowRunner.run(network, parameters); + // both are balancing + assertActivePowerEquals(-238.649, g1.getTerminal()); + assertActivePowerEquals(-46.25, g2.getTerminal()); + + parameters.setCountriesToBalance(Set.of()); + loadFlowRunner.run(network, parameters); + // empty also means both are balancing + assertActivePowerEquals(-238.649, g1.getTerminal()); + assertActivePowerEquals(-46.25, g2.getTerminal()); + + parameters.setDistributedSlack(false); + loadFlowRunner.run(network, parameters); + // no more balancing + assertActivePowerEquals(-232.4, g1.getTerminal()); + assertActivePowerEquals(-40.0, g2.getTerminal()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/DistributedSlackOnGenerationTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/DistributedSlackOnGenerationTest.java new file mode 100644 index 0000000..6c568b8 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/DistributedSlackOnGenerationTest.java @@ -0,0 +1,513 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * Copyright (c) 2023, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.ActivePowerControl; +import com.powsybl.iidm.network.extensions.ReferencePriority; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.DistributedSlackNetworkFactory; +import com.powsybl.openloadflow.network.EurostagFactory; +import com.powsybl.openloadflow.network.ReferenceBusSelectionMode; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.util.LoadFlowAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.EnumSet; +import java.util.concurrent.CompletionException; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Geoffroy Jamgotchian {@literal } + * @author Caio Luke {@literal } + */ +class DistributedSlackOnGenerationTest { + + private Network network; + private Generator g1; + private Generator g2; + private Generator g3; + private Generator g4; + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + + @BeforeEach + void setUp() { + network = DistributedSlackNetworkFactory.create(); + g1 = network.getGenerator("g1"); + g2 = network.getGenerator("g2"); + g3 = network.getGenerator("g3"); + g4 = network.getGenerator("g4"); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + // Note that in core, default balance type is proportional to generation Pmax + parameters = new LoadFlowParameters().setUseReactiveLimits(false) + .setDistributedSlack(true); + OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.THROW) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void test() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + assertEquals(120, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + assertActivePowerEquals(-115, g1.getTerminal()); + assertActivePowerEquals(-245, g2.getTerminal()); + assertActivePowerEquals(-105, g3.getTerminal()); + assertActivePowerEquals(-135, g4.getTerminal()); + assertReactivePowerEquals(159.741, g1.getTerminal()); + Line l14 = network.getLine("l14"); + Line l24 = network.getLine("l24"); + Line l34 = network.getLine("l34"); + assertActivePowerEquals(115, l14.getTerminal1()); + assertActivePowerEquals(-115, l14.getTerminal2()); + assertActivePowerEquals(245, l24.getTerminal1()); + assertActivePowerEquals(-245, l24.getTerminal2()); + assertActivePowerEquals(240, l34.getTerminal1()); + assertActivePowerEquals(-240, l34.getTerminal2()); + } + + @Test + void testProportionalToP() { + // decrease g1 max limit power, so that distributed slack algo reach the g1 max + g1.setMaxP(105); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_P); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-105, g1.getTerminal()); + assertActivePowerEquals(-260.526, g2.getTerminal()); + assertActivePowerEquals(-117.236, g3.getTerminal()); + assertActivePowerEquals(-117.236, g4.getTerminal()); + } + + @Test + @SuppressWarnings("unchecked") + void testProportionalToParticipationFactor() { + // decrease g1 max limit power, so that distributed slack algo reach the g1 max + g1.setMaxP(100); + + // set participationFactor + // g1 NaN participationFactor should be discarded + g1.getExtension(ActivePowerControl.class).setParticipationFactor(Double.NaN); + g2.getExtension(ActivePowerControl.class).setParticipationFactor(3.0); + g3.getExtension(ActivePowerControl.class).setParticipationFactor(1.0); + g4.getExtension(ActivePowerControl.class).setParticipationFactor(-4.0); // Should be discarded + + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_PARTICIPATION_FACTOR); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-100, g1.getTerminal()); + assertActivePowerEquals(-290, g2.getTerminal()); + assertActivePowerEquals(-120, g3.getTerminal()); + assertActivePowerEquals(-90, g4.getTerminal()); + } + + @Test + void testProportionalToRemainingMargin() { + // decrease g1 max limit power, so that distributed slack algo reach the g1 max + g1.setMaxP(105); + + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_REMAINING_MARGIN); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-102.667, g1.getTerminal()); + assertActivePowerEquals(-253.333, g2.getTerminal()); + assertActivePowerEquals(-122.0, g3.getTerminal()); + assertActivePowerEquals(-122.0, g4.getTerminal()); + } + + @Test + void testProportionalToRemainingMarginPmaxBelowTargetP() { + // decrease g1 max limit power below target P + g1.setMaxP(90); + + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_REMAINING_MARGIN); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-100.0, g1.getTerminal()); + assertActivePowerEquals(-254.545, g2.getTerminal()); + assertActivePowerEquals(-122.727, g3.getTerminal()); + assertActivePowerEquals(-122.727, g4.getTerminal()); + } + + @Test + void maxTest() { + // decrease g1 max limit power, so that distributed slack algo reach the g1 max + g1.setMaxP(105); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-105, g1.getTerminal()); + assertActivePowerEquals(-249.285, g2.getTerminal()); + assertActivePowerEquals(-106.428, g3.getTerminal()); + assertActivePowerEquals(-139.285, g4.getTerminal()); + } + + @Test + void minTest() { + // increase g1 min limit power and global load so that distributed slack algo reach the g1 min + g1.setMinP(95); + network.getLoad("l1").setP0(400); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-95, g1.getTerminal()); + assertActivePowerEquals(-167.857, g2.getTerminal()); + assertActivePowerEquals(-79.285, g3.getTerminal()); + assertActivePowerEquals(-57.857, g4.getTerminal()); + } + + @Test + void maxTestActivePowerLimitDisabled() { + parameters.getExtension(OpenLoadFlowParameters.class).setUseActiveLimits(false); + // decrease g1 max limit power, so that distributed slack algo reach the g1 max + // Because we disabled active power limits, g1 will exceed max + g1.setMaxP(105); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-108.372, g1.getTerminal()); + assertActivePowerEquals(-247.840, g2.getTerminal()); + assertActivePowerEquals(-105.946, g3.getTerminal()); + assertActivePowerEquals(-137.840, g4.getTerminal()); + assertEquals(120, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + } + + @Test + void minTestActivePowerLimitDisabled() { + parameters.getExtension(OpenLoadFlowParameters.class).setUseActiveLimits(false); + // increase g1 min limit power and global load so that distributed slack algo reach the g1 min + // Because we disabled active power limits, g1 will exceed min + g1.setMinP(95); + network.getLoad("l1").setP0(400); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-90, g1.getTerminal()); + assertActivePowerEquals(-170, g2.getTerminal()); + assertActivePowerEquals(-80, g3.getTerminal()); + assertActivePowerEquals(-60, g4.getTerminal()); + } + + @Test + void targetBelowMinAndActivePowerLimitDisabled() { + parameters.getExtension(OpenLoadFlowParameters.class).setUseActiveLimits(false); + g1.setMinP(100); // was 0 + g1.setTargetP(80); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-97.5, g1.getTerminal()); // allowed to participate even though targetP < minP + assertActivePowerEquals(-252.5, g2.getTerminal()); + assertActivePowerEquals(-107.5, g3.getTerminal()); + assertActivePowerEquals(-142.5, g4.getTerminal()); + assertEquals(140, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + } + + @Test + void targetAboveMaxAndActivePowerLimitDisabled() { + parameters.getExtension(OpenLoadFlowParameters.class).setUseActiveLimits(false); + g1.setTargetP(240); // max is 200 + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-237.5, g1.getTerminal()); // allowed to participate even though targetP > maxP + assertActivePowerEquals(-192.5, g2.getTerminal()); + assertActivePowerEquals(-87.5, g3.getTerminal()); + assertActivePowerEquals(-82.5, g4.getTerminal()); + assertEquals(-20.0, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + } + + @Test + void targetBelowPositiveMinTest() { + // g1 targetP below positive minP (e.g. unit starting up / ramping) + g1.setMinP(100); + g1.setTargetP(80); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-80.0, g1.getTerminal()); // stays at targetP + assertActivePowerEquals(-260.0, g2.getTerminal()); + assertActivePowerEquals(-110.0, g3.getTerminal()); + assertActivePowerEquals(-150.0, g4.getTerminal()); + assertEquals(140, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + } + + @Test + void targetBelowZeroMinTest() { + // g1 targetP below zero minP (e.g. unit modelled lumped with station supply and not producing but consuming a little bit) + g1.setMinP(0); + g1.setTargetP(-20); + network.getLoad("l1").setP0(500); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(20.0, g1.getTerminal()); // stays at targetP + assertActivePowerEquals(-260.0, g2.getTerminal()); + assertActivePowerEquals(-110.0, g3.getTerminal()); + assertActivePowerEquals(-150.0, g4.getTerminal()); + assertEquals(140, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + } + + @Test + void targetBelowNegativeMinTest() { + // g1 targetP below negative minP (e.g. generator pumping more than tech limit) + g1.setMinP(-100); + g1.setTargetP(-120); + network.getLoad("l1").setP0(400); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(120.0, g1.getTerminal()); // stays at targetP + assertActivePowerEquals(-260.0, g2.getTerminal()); + assertActivePowerEquals(-110.0, g3.getTerminal()); + assertActivePowerEquals(-150.0, g4.getTerminal()); + assertEquals(140, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + } + + @Test + @SuppressWarnings("unchecked") + void zeroParticipatingGeneratorsThrowTest() { + g1.getExtension(ActivePowerControl.class).setDroop(2); + g2.getExtension(ActivePowerControl.class).setDroop(-3); + g3.getExtension(ActivePowerControl.class).setDroop(0); + g4.getExtension(ActivePowerControl.class).setDroop(0); + CompletionException thrown = assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters)); + assertTrue(thrown.getCause().getMessage().startsWith("Failed to distribute slack bus active power mismatch, ")); + } + + @Test + void notEnoughActivePowerThrowTest() { + network.getLoad("l1").setP0(1000); + CompletionException thrown = assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters)); + assertTrue(thrown.getCause().getMessage().startsWith("Failed to distribute slack bus active power mismatch, ")); + } + + @Test + void notEnoughActivePowerFailTest() { + network.getLoad("l1").setP0(1000); + parameters.getExtension(OpenLoadFlowParameters.class).setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + assertFalse(result.isFullyConverged()); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, componentResult.getStatus()); + assertEquals(0, componentResult.getDistributedActivePower(), 1e-4); + assertEquals(520, componentResult.getSlackBusResults().get(0).getActivePowerMismatch(), 1e-4); + } + + @Test + void notEnoughActivePowerLeaveOnSlackBusTest() { + network.getLoad("l1").setP0(1000); + parameters.getExtension(OpenLoadFlowParameters.class).setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.LEAVE_ON_SLACK_BUS); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + assertTrue(result.isFullyConverged()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, componentResult.getStatus()); + assertEquals(320, componentResult.getDistributedActivePower(), 1e-4); + assertEquals(200, componentResult.getSlackBusResults().get(0).getActivePowerMismatch(), 1e-4); + } + + @Test + void notEnoughActivePowerDistributeReferenceGeneratorTest() { + network.getLoad("l1").setP0(1000); + ReferencePriority.set(g1, 1); + g1.setMaxP(200.); + parameters.getExtension(OpenLoadFlowParameters.class) + .setReferenceBusSelectionMode(ReferenceBusSelectionMode.GENERATOR_REFERENCE_PRIORITY) + .setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.DISTRIBUTE_ON_REFERENCE_GENERATOR); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + assertTrue(result.isFullyConverged()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, componentResult.getStatus()); + // DistributedActivePower: 520MW, breakdown: + // - 320MW by all 4 generators hitting maxP limit + // - 200MW by distributing on reference generator g1 + assertEquals(520., componentResult.getDistributedActivePower(), 1e-3); + assertEquals(0., componentResult.getSlackBusResults().get(0).getActivePowerMismatch(), 1e-3); + assertAngleEquals(0., g1.getTerminal().getBusView().getBus()); + // can exceed maxP (200MW) + assertActivePowerEquals(-400., g1.getTerminal()); + } + + @Test + void notEnoughActivePowerDistributeNoReferenceGeneratorTest() { + network.getLoad("l1").setP0(1000); + ReferencePriority.set(g1, 1); + g1.setMaxP(200.); + // We request to distribute on reference generator, but ReferenceBusSelectionMode is FIRST_SLACK. + // FIRST_SLACK mode does not select a reference generator, therefore internally we switch to FAIL mode. + parameters.getExtension(OpenLoadFlowParameters.class) + .setReferenceBusSelectionMode(ReferenceBusSelectionMode.FIRST_SLACK) + .setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.DISTRIBUTE_ON_REFERENCE_GENERATOR); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + assertTrue(result.isFailed()); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, componentResult.getStatus()); + assertEquals(520., componentResult.getSlackBusResults().get(0).getActivePowerMismatch(), 1e-3); + } + + @Test + void generatorWithNegativeTargetP() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network.getGenerator("GEN").setMaxP(1000); + network.getGenerator("GEN").setTargetP(-607); + network.getLoad("LOAD").setP0(-600); + network.getLoad("LOAD").setQ0(-200); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(595.328, network.getGenerator("GEN").getTerminal()); + } + + @Test + void generatorWithMaxPEqualsToMinP() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network.getGenerator("GEN").setMaxP(1000); + network.getGenerator("GEN").setMinP(1000); + network.getGenerator("GEN").setTargetP(1000); + assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters), + "Failed to distribute slack bus active power mismatch, -393.367476483181 MW remains"); + } + + @Test + void nonParticipatingBus() { + //B1 and B2 are located in germany the rest is in france + Substation b1s = network.getSubstation("b1_s"); + b1s.setCountry(Country.GE); + Substation b2s = network.getSubstation("b2_s"); + b2s.setCountry(Country.GE); + + //Only substation located in france are used + parameters.setCountriesToBalance(EnumSet.of(Country.FR)); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-100, g1.getTerminal()); + assertActivePowerEquals(-200, g2.getTerminal()); + assertActivePowerEquals(-150, g3.getTerminal()); + assertActivePowerEquals(-150, g4.getTerminal()); + } + + @Test + void generatorWithTargetPLowerThanMinP() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network.getGenerator("GEN").setMaxP(1000); + network.getGenerator("GEN").setMinP(200); + network.getGenerator("GEN").setTargetP(100); + network.getVoltageLevel("VLGEN").newGenerator() + .setId("g1") + .setBus("NGEN") + .setConnectableBus("NGEN") + .setEnergySource(EnergySource.THERMAL) + .setMinP(0) + .setMaxP(0) + .setTargetP(0) + .setTargetV(24.5) + .setVoltageRegulatorOn(true) + .add(); + assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters), + "Failed to distribute slack bus active power mismatch, 504.9476825313616 MW remains"); + } + + @Test + @SuppressWarnings("unchecked") + void notParticipatingTest() { + g1.getExtension(ActivePowerControl.class).setParticipate(false); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(-100, g1.getTerminal()); + assertActivePowerEquals(-251.428, g2.getTerminal()); + assertActivePowerEquals(-107.142, g3.getTerminal()); + assertActivePowerEquals(-141.428, g4.getTerminal()); + } + + @Test + void batteryTest() { + Network network = DistributedSlackNetworkFactory.createWithBattery(); + Generator g1 = network.getGenerator("g1"); + Generator g2 = network.getGenerator("g2"); + Generator g3 = network.getGenerator("g3"); + Generator g4 = network.getGenerator("g4"); + Battery bat1 = network.getBattery("bat1"); + Battery bat2 = network.getBattery("bat2"); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + assertEquals(123, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + assertActivePowerEquals(-115.122, g1.getTerminal()); + assertActivePowerEquals(-245.368, g2.getTerminal()); + assertActivePowerEquals(-105.123, g3.getTerminal()); + assertActivePowerEquals(-135.369, g4.getTerminal()); + assertActivePowerEquals(-2, bat1.getTerminal()); + assertActivePowerEquals(2.983, bat2.getTerminal()); + } + + @Test + @SuppressWarnings("unchecked") + void batteryTestProportionalToParticipationFactor() { + Network network = DistributedSlackNetworkFactory.createWithBattery(); + Generator g1 = network.getGenerator("g1"); + Generator g2 = network.getGenerator("g2"); + Generator g3 = network.getGenerator("g3"); + Generator g4 = network.getGenerator("g4"); + Battery bat1 = network.getBattery("bat1"); + Battery bat2 = network.getBattery("bat2"); + g1.getExtension(ActivePowerControl.class).setParticipationFactor(Double.NaN); + g2.getExtension(ActivePowerControl.class).setParticipationFactor(3.0); + g3.getExtension(ActivePowerControl.class).setParticipationFactor(1.0); + g4.getExtension(ActivePowerControl.class).setParticipationFactor(-4.0); // Should be discarded + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_GENERATION_PARTICIPATION_FACTOR); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + assertEquals(123, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + assertActivePowerEquals(-100, g1.getTerminal()); + assertActivePowerEquals(-288.5, g2.getTerminal()); + assertActivePowerEquals(-119.5, g3.getTerminal()); + assertActivePowerEquals(-90, g4.getTerminal()); + assertActivePowerEquals(-2, bat1.getTerminal()); + assertActivePowerEquals(0, bat2.getTerminal()); + } + + @Test + void testDistributedActivePower() { + parameters.setUseReactiveLimits(true).getExtension(OpenLoadFlowParameters.class).setSlackBusPMaxMismatch(0.0001); + Network network = DistributedSlackNetworkFactory.createWithLossesAndPvPqTypeSwitch(); + Generator g1 = network.getGenerator("g1"); + Generator g2 = network.getGenerator("g2"); + Generator g3 = network.getGenerator("g3"); + Generator g4 = network.getGenerator("g4"); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + // we were getting 132.47279 when computing distributedActivePower as initial NR slack - final NR slack, while difference targetP - P was only 120.1961 + var expectedDistributedActivePower = -network.getGeneratorStream().mapToDouble(g -> g.getTargetP() + g.getTerminal().getP()).sum(); + assertEquals(120.1961, expectedDistributedActivePower, LoadFlowAssert.DELTA_POWER); + assertEquals(expectedDistributedActivePower, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + assertActivePowerEquals(-115.024, g1.getTerminal()); + assertActivePowerEquals(-245.073, g2.getTerminal()); + assertActivePowerEquals(-105.024, g3.getTerminal()); + assertActivePowerEquals(-135.073, g4.getTerminal()); + } + + @Test + void testDistributedActivePowerSlackDistributionDisabled() { + parameters.setUseReactiveLimits(true).setDistributedSlack(false); + Network network = DistributedSlackNetworkFactory.createWithLossesAndPvPqTypeSwitch(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + // we were getting 12.307 when computing distributedActivePower as initial NR slack - final NR slack, expecting zero here + assertEquals(0.0, result.getComponentResults().get(0).getDistributedActivePower(), LoadFlowAssert.DELTA_POWER); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/DistributedSlackOnLoadTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/DistributedSlackOnLoadTest.java new file mode 100644 index 0000000..1dc7900 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/DistributedSlackOnLoadTest.java @@ -0,0 +1,265 @@ +/** + * Copyright (c) 2020, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.extensions.LoadDetailAdder; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.DistributedSlackNetworkFactory; +import com.powsybl.openloadflow.network.EurostagFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.util.LoadFlowResultBuilder; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CompletionException; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals; +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertLoadFlowResultsEquals; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Anne Tilloy {@literal } + */ +class DistributedSlackOnLoadTest { + + private Network network; + private Load l1; + private Load l2; + private Load l3; + private Load l4; + private Load l5; + private Load l6; + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + public static final double DELTA_MISMATCH = 1E-4d; + + @BeforeEach + void setUp() { + network = DistributedSlackNetworkFactory.createNetworkWithLoads(); + l1 = network.getLoad("l1"); + l2 = network.getLoad("l2"); + l3 = network.getLoad("l3"); + l4 = network.getLoad("l4"); + l5 = network.getLoad("l5"); + l6 = network.getLoad("l6"); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters().setDistributedSlack(true) + .setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void test() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(35.294, l1.getTerminal()); + assertActivePowerEquals(70.588, l2.getTerminal()); + assertActivePowerEquals(58.824, l3.getTerminal()); + assertActivePowerEquals(164.705, l4.getTerminal()); + assertActivePowerEquals(11.765, l5.getTerminal()); + assertActivePowerEquals(-41.176, l6.getTerminal()); + LoadFlowResult loadFlowResultExpected = new LoadFlowResultBuilder(true) + .addMetrics("3", "CONVERGED") + .addComponentResult(0, 0, LoadFlowResult.ComponentResult.Status.CONVERGED, 3, "b4_vl_0", 1.6895598253796607E-7) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected, result); + } + + @Test + void testWithLoadDetail() { + l2.newExtension(LoadDetailAdder.class) + .withVariableActivePower(40) + .withFixedActivePower(20) + .add(); + l6.newExtension(LoadDetailAdder.class) + .withVariableActivePower(-25) + .withFixedActivePower(-25) + .add(); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertActivePowerEquals(30.0, l1.getTerminal()); + assertActivePowerEquals(96.923, l2.getTerminal()); + assertActivePowerEquals(50.0, l3.getTerminal()); + assertActivePowerEquals(140, l4.getTerminal()); + assertActivePowerEquals(10.0, l5.getTerminal()); + assertActivePowerEquals(-26.923, l6.getTerminal()); + LoadFlowResult loadFlowResultExpected = new LoadFlowResultBuilder(true) + .addMetrics("3", "CONVERGED") + .addComponentResult(0, 0, LoadFlowResult.ComponentResult.Status.CONVERGED, 4, "b4_vl_0", 9.726437433243973E-8) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected, result); + } + + private void assertPowerFactor(Network network) { + switch (parameters.getBalanceType()) { + case PROPORTIONAL_TO_CONFORM_LOAD: + case PROPORTIONAL_TO_LOAD: + for (Load load : network.getLoads()) { + assertEquals(load.getP0() / load.getQ0(), + load.getTerminal().getP() / load.getTerminal().getQ(), + DELTA_MISMATCH, "Power factor should be a constant value for load " + load.getId()); + } + break; + default: + break; + } + } + + @Test + void testPowerFactorConstant() { + // PROPORTIONAL_TO_LOAD and power factor constant for loads + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parametersExt.setLoadPowerFactorConstant(true); + Network network1 = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + + LoadFlowResult loadFlowResult1 = loadFlowRunner.run(network1, parameters); + + assertPowerFactor(network1); + LoadFlowResult loadFlowResultExpected1 = new LoadFlowResultBuilder(true) + .addMetrics("4", "CONVERGED") + .addComponentResult(0, 0, LoadFlowResult.ComponentResult.Status.CONVERGED, 6, "VLHV1_0", 0.028512434226080074) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected1, loadFlowResult1); + + // PROPORTIONAL_TO_CONFORM_LOAD and power factor constant for loads + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD); + parametersExt.setLoadPowerFactorConstant(true); + Network network2 = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + // fixedActivePower and FixedReactivePower are unbalanced + network2.getLoad("LOAD").newExtension(LoadDetailAdder.class) + .withFixedActivePower(500).withVariableActivePower(100) + .withFixedReactivePower(150).withVariableReactivePower(50) + .add(); + + //when + LoadFlowResult loadFlowResult2 = loadFlowRunner.run(network2, parameters); + + // then + assertPowerFactor(network2); + LoadFlowResult loadFlowResultExpected2 = new LoadFlowResultBuilder(true).addMetrics("4", "CONVERGED") + .addComponentResult(0, 0, LoadFlowResult.ComponentResult.Status.CONVERGED, 6, "VLHV1_0", 0.028512434226080074) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected2, loadFlowResult2); + assertActivePowerEquals(601.441, network1.getLoad("LOAD").getTerminal()); + + // PROPORTIONAL_TO_CONFORM_LOAD and power factor constant for loads + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parametersExt.setLoadPowerFactorConstant(true); + Network network3 = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + network3.getVoltageLevel("VLLOAD").newLoad().setId("LOAD1").setP0(-10).setQ0(1).setBus("NLOAD").setConnectableBus("NLOAD").add(); + + //when + LoadFlowResult loadFlowResult3 = loadFlowRunner.run(network3, parameters); + + // then + assertPowerFactor(network3); + LoadFlowResult loadFlowResultExpected3 = new LoadFlowResultBuilder(true).addMetrics("5", "CONVERGED") + .addComponentResult(0, 0, LoadFlowResult.ComponentResult.Status.CONVERGED, 6, "VLHV1_0", 0.22734554153656106) + .build(); + assertLoadFlowResultsEquals(loadFlowResultExpected3, loadFlowResult3); + assertActivePowerEquals(611.406, network3.getLoad("LOAD").getTerminal()); + assertActivePowerEquals(-9.809, network3.getLoad("LOAD1").getTerminal()); + } + + @Test + void testNetworkWithoutConformingLoad() { + parameters + .setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD) + .getExtension(OpenLoadFlowParameters.class) + .setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.LEAVE_ON_SLACK_BUS); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + assertTrue(result.isFullyConverged()); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, componentResult.getStatus()); + assertEquals(-60., componentResult.getSlackBusResults().get(0).getActivePowerMismatch(), 1e-6); + + parameters.getExtension(OpenLoadFlowParameters.class) + .setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.FAIL); + result = loadFlowRunner.run(network, parameters); + componentResult = result.getComponentResults().get(0); + assertFalse(result.isFullyConverged()); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, componentResult.getStatus()); + assertEquals(-60., componentResult.getSlackBusResults().get(0).getActivePowerMismatch(), 1e-6); + + parameters.getExtension(OpenLoadFlowParameters.class) + .setSlackDistributionFailureBehavior(OpenLoadFlowParameters.SlackDistributionFailureBehavior.THROW); + assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters)); + } + + @Test + void testPowerFactorConstant2() { + Network network = DistributedSlackNetworkFactory.createNetworkWithLoads2(); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_CONFORM_LOAD); + parametersExt.setLoadPowerFactorConstant(true); + network.getLoad("l4").newExtension(LoadDetailAdder.class) + .withVariableActivePower(100) + .withFixedActivePower(0) + .add(); + network.getLoad("l5").newExtension(LoadDetailAdder.class) + .withVariableActivePower(200) + .withFixedActivePower(100) + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + double sumBus = 0.0; + sumBus += network.getLine("l14").getTerminal2().getQ(); + sumBus += network.getLine("l24").getTerminal2().getQ(); + sumBus += network.getLine("l34").getTerminal2().getQ(); + sumBus += network.getLoad("l4").getTerminal().getQ(); + sumBus += network.getLoad("l5").getTerminal().getQ(); + assertEquals(0.0, sumBus, 10E-6); + assertPowerFactor(network); + } + + @Test + void testPowerFactorConstant3() { + Network network = DistributedSlackNetworkFactory.createNetworkWithLoads2(); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parametersExt.setLoadPowerFactorConstant(true); + network.getGenerator("g1").setTargetP(-208); + Load l4 = network.getLoad("l4"); + l4.setP0(0.0).setQ0(-50); + l4.newExtension(LoadDetailAdder.class) + .withFixedActivePower(0) + .withFixedReactivePower(0) + .withVariableActivePower(0) + .withVariableReactivePower(-50) + .add(); + Load l5 = network.getLoad("l5"); + l5.setP0(-10.0).setQ0(0.0); + l5.newExtension(LoadDetailAdder.class) + .withFixedActivePower(-10) + .withFixedReactivePower(0) + .withVariableActivePower(0) + .withVariableReactivePower(0.0) + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + double sumBus = 0.0; + sumBus += network.getLine("l14").getTerminal2().getQ(); + sumBus += network.getLine("l24").getTerminal2().getQ(); + sumBus += network.getLine("l34").getTerminal2().getQ(); + sumBus += network.getLoad("l4").getTerminal().getQ(); + sumBus += network.getLoad("l5").getTerminal().getQ(); + assertEquals(0.0, sumBus, 10E-3); + assertPowerFactor(network); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/GeneratorRemoteControlLocalRescaleTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/GeneratorRemoteControlLocalRescaleTest.java new file mode 100644 index 0000000..7892c5e --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/GeneratorRemoteControlLocalRescaleTest.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.util.LoadFlowAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class GeneratorRemoteControlLocalRescaleTest { + + private Network network; + private Bus b1; + private Bus b2; + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + + @BeforeEach + void setUp() { + network = Network.create("GeneratorRemoteControlLocalRescaleTest", "code"); + Substation s = network.newSubstation() + .setId("s") + .add(); + VoltageLevel vl1 = s.newVoltageLevel() + .setId("vl1") + .setNominalV(20) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + b1 = vl1.getBusBreakerView().newBus() + .setId("b1") + .add(); + VoltageLevel vl2 = s.newVoltageLevel() + .setId("vl2") + .setNominalV(400) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + b2 = vl2.getBusBreakerView().newBus() + .setId("b2") + .add(); + Load l2 = vl2.newLoad() + .setId("l2") + .setBus("b2") + .setConnectableBus("b2") + .setP0(99.9) + .setQ0(80) + .add(); + b1.getVoltageLevel() + .newGenerator() + .setId("g1") + .setBus("b1") + .setConnectableBus("b1") + .setEnergySource(EnergySource.THERMAL) + .setMinP(0) + .setMaxP(200) + .setTargetP(100) + .setTargetV(413.4) + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(l2.getTerminal()) + .add(); + s.newTwoWindingsTransformer() + .setId("tr1") + .setBus1(b1.getId()) + .setConnectableBus1(b1.getId()) + .setBus2(b2.getId()) + .setConnectableBus2(b2.getId()) + .setRatedU1(20.5) + .setRatedU2(399) + .setR(1) + .setX(30) + .add(); + + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters(); + OpenLoadFlowParameters.create(parameters) + .setVoltageRemoteControl(false) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void test() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + LoadFlowAssert.assertVoltageEquals(20.67, b1); // check local targetV has been correctly rescaled + LoadFlowAssert.assertVoltageEquals(395.927, b2); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/LfBranchDisconnectionTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/LfBranchDisconnectionTest.java new file mode 100644 index 0000000..b0be500 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/LfBranchDisconnectionTest.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.ac.AcLoadFlowContext; +import com.powsybl.openloadflow.ac.AcLoadFlowParameters; +import com.powsybl.openloadflow.ac.AcLoadFlowResult; +import com.powsybl.openloadflow.ac.AcloadFlowEngine; +import com.powsybl.openloadflow.ac.solver.AcSolverStatus; +import com.powsybl.openloadflow.network.EurostagFactory; +import com.powsybl.openloadflow.network.LfBranch; +import com.powsybl.openloadflow.network.LfNetwork; +import com.powsybl.openloadflow.network.LfNetworkParameters; +import com.powsybl.openloadflow.network.impl.Networks; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class LfBranchDisconnectionTest { + + private static final double DELTA = 1E-5; + + @Test + void test() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + LfNetwork lfNetwork = Networks.load(network, new LfNetworkParameters()).get(0); + LfBranch lfl1 = lfNetwork.getBranchById("NHV1_NHV2_1"); + lfl1.setDisconnectionAllowedSide1(true); + lfl1.setDisconnectionAllowedSide2(true); + + AcLoadFlowParameters acParameters = new AcLoadFlowParameters() + .setMatrixFactory(new DenseMatrixFactory()) + .setSolverFactory(new KnitroSolverFactory(), new LoadFlowParameters()); + try (var context = new AcLoadFlowContext(lfNetwork, acParameters)) { + AcLoadFlowResult result = new AcloadFlowEngine(context) + .run(); + assertEquals(AcSolverStatus.CONVERGED, result.getSolverStatus()); + assertEquals(3.02444, lfl1.getP1().eval(), DELTA); + assertEquals(0.98740, lfl1.getQ1().eval(), DELTA); + assertEquals(-3.00434, lfl1.getP2().eval(), DELTA); + assertEquals(-1.37188, lfl1.getQ2().eval(), DELTA); + + lfl1.setConnectedSide1(false); + result = new AcloadFlowEngine(context) + .run(); + assertEquals(AcSolverStatus.CONVERGED, result.getSolverStatus()); + assertEquals(0, lfl1.getP1().eval(), 0); + assertEquals(0, lfl1.getQ1().eval(), 0); + assertEquals(1.587E-4, lfl1.getP2().eval(), DELTA); + assertEquals(-0.5432, lfl1.getQ2().eval(), DELTA); + + lfl1.setConnectedSide1(true); + result = new AcloadFlowEngine(context) + .run(); + assertEquals(AcSolverStatus.CONVERGED, result.getSolverStatus()); + assertEquals(3.02444, lfl1.getP1().eval(), DELTA); + assertEquals(0.98740, lfl1.getQ1().eval(), DELTA); + assertEquals(-3.00434, lfl1.getP2().eval(), DELTA); + assertEquals(-1.37188, lfl1.getQ2().eval(), DELTA); + + lfl1.setConnectedSide2(false); + result = new AcloadFlowEngine(context) + .run(); + assertEquals(AcSolverStatus.CONVERGED, result.getSolverStatus()); + assertEquals(1.752e-4, lfl1.getP1().eval(), DELTA); + assertEquals(-0.61995, lfl1.getQ1().eval(), DELTA); + assertEquals(0, lfl1.getP2().eval(), 0); + assertEquals(0, lfl1.getQ2().eval(), 0); + + lfl1.setConnectedSide2(true); + result = new AcloadFlowEngine(context) + .run(); + assertEquals(AcSolverStatus.CONVERGED, result.getSolverStatus()); + assertEquals(3.02444, lfl1.getP1().eval(), DELTA); + assertEquals(0.98740, lfl1.getQ1().eval(), DELTA); + assertEquals(-3.00434, lfl1.getP2().eval(), DELTA); + assertEquals(-1.37188, lfl1.getQ2().eval(), DELTA); + + lfl1.setConnectedSide1(false); + lfl1.setConnectedSide2(false); + result = new AcloadFlowEngine(context) + .run(); + assertEquals(AcSolverStatus.CONVERGED, result.getSolverStatus()); + assertTrue(Double.isNaN(lfl1.getP1().eval())); + assertTrue(Double.isNaN(lfl1.getQ1().eval())); + assertTrue(Double.isNaN(lfl1.getP2().eval())); + assertTrue(Double.isNaN(lfl1.getQ2().eval())); + } + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/LinesWithDifferentNominalVoltagesTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/LinesWithDifferentNominalVoltagesTest.java new file mode 100644 index 0000000..12111f0 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/LinesWithDifferentNominalVoltagesTest.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2022, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Line; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.TwoSides; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.LinesWithDifferentNominalVoltagesNetworkFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @see LinesWithDifferentNominalVoltagesNetworkFactory + * + * @author Damien Jeandemange {@literal } + */ +class LinesWithDifferentNominalVoltagesTest { + + private Network network; + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + + private Bus b225g; + private Bus b225l; + private Bus b220A; + private Bus b230A; + private Bus b220B; + private Bus b230B; + private Line l225to225; + private Line l225to230; + private Line l225to220; + private Line l230to225; + private Line l220to225; + + @BeforeEach + void setUp() { + network = LinesWithDifferentNominalVoltagesNetworkFactory.create(); + + b225g = network.getBusBreakerView().getBus("b225g"); + b225l = network.getBusBreakerView().getBus("b225l"); + b220A = network.getBusBreakerView().getBus("b220A"); + b230A = network.getBusBreakerView().getBus("b230A"); + b220B = network.getBusBreakerView().getBus("b220B"); + b230B = network.getBusBreakerView().getBus("b230B"); + l225to225 = network.getLine("l225-225"); + l225to230 = network.getLine("l225-230"); + l225to220 = network.getLine("l225-220"); + l230to225 = network.getLine("l230-225"); + l220to225 = network.getLine("l220-225"); + + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters().setDistributedSlack(true); + OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.LARGEST_GENERATOR) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void test() { + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(1, result.getComponentResults().size()); + + // slack bus + assertVoltageEquals(225.0, b225g); + assertAngleEquals(0.0, b225g); + + // load buses + List.of(b225l, b220A, b230A, b220B, b230B).forEach(bus -> { + assertVoltageEquals(218.294, bus); + assertAngleEquals(-3.4606395, bus); + }); + + // line flows, load side + List.of( + l225to225.getTerminal(TwoSides.TWO), + l225to220.getTerminal(TwoSides.TWO), + l225to230.getTerminal(TwoSides.TWO), + l220to225.getTerminal(TwoSides.ONE), + l230to225.getTerminal(TwoSides.ONE)).forEach(terminal -> { + assertActivePowerEquals(-100, terminal); + assertReactivePowerEquals(-40, terminal); + }); + + // line flows, generator side + List.of( + l225to225.getTerminal(TwoSides.ONE), + l225to220.getTerminal(TwoSides.ONE), + l225to230.getTerminal(TwoSides.ONE), + l220to225.getTerminal(TwoSides.TWO), + l230to225.getTerminal(TwoSides.TWO)).forEach(terminal -> { + assertActivePowerEquals(103.444104, terminal); + assertReactivePowerEquals(45.4712006, terminal); + }); + } + +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/LoadModelTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/LoadModelTest.java new file mode 100644 index 0000000..d3ce675 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/LoadModelTest.java @@ -0,0 +1,186 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Load; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.EurostagFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class LoadModelTest { + + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters(); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setUseLoadModel(true) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void zipLoadModelTest() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + Load load = network.getLoad("LOAD"); + VoltageLevel vlload = network.getVoltageLevel("VLLOAD"); + Load zipLoad = vlload.newLoad() + .setId("ZIPLOAD") + .setBus("NLOAD") + .setP0(50) + .setQ0(30) + .newZipModel() + .setC0p(0.5) + .setC0q(0.55) + .setC1p(0.3) + .setC1q(0.35) + .setC2p(0.2) + .setC2q(0.1) + .add() + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertActivePowerEquals(48.786, zipLoad.getTerminal()); + assertReactivePowerEquals(29.425, zipLoad.getTerminal()); + assertActivePowerEquals(600, load.getTerminal()); + assertReactivePowerEquals(200, load.getTerminal()); + } + + @Test + void expLoadModelTest() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + Load load = network.getLoad("LOAD"); + VoltageLevel vlload = network.getVoltageLevel("VLLOAD"); + Load expLoad = vlload.newLoad() + .setId("EXPLOAD") + .setBus("NLOAD") + .setP0(50) + .setQ0(30) + .newExponentialModel() + .setNp(0.8) + .setNq(0.9) + .add() + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertVoltageEquals(141.692, expLoad.getTerminal().getBusView().getBus()); + assertEquals(150, expLoad.getTerminal().getVoltageLevel().getNominalV(), 0); + assertActivePowerEquals(47.772, expLoad.getTerminal()); // < 50MW because v < vnom + assertReactivePowerEquals(28.5, expLoad.getTerminal()); // < 30MW because v < vnom + assertActivePowerEquals(600, load.getTerminal()); + assertReactivePowerEquals(200, load.getTerminal()); + } + + @Test + void zipAndExpLoadModelTest() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + Load load = network.getLoad("LOAD"); + VoltageLevel vlload = network.getVoltageLevel("VLLOAD"); + Load zipLoad = vlload.newLoad() + .setId("ZIPLOAD") + .setBus("NLOAD") + .setP0(50) + .setQ0(30) + .newZipModel() + .setC0p(0.5) + .setC0q(0.55) + .setC1p(0.3) + .setC1q(0.35) + .setC2p(0.2) + .setC2q(0.1) + .add() + .add(); + Load expLoad = vlload.newLoad() + .setId("EXPLOAD") + .setBus("NLOAD") + .setP0(50) + .setQ0(30) + .newExponentialModel() + .setNp(0.8) + .setNq(0.9) + .add() + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertActivePowerEquals(47.369, zipLoad.getTerminal()); + assertReactivePowerEquals(28.749, zipLoad.getTerminal()); + assertActivePowerEquals(46.902, expLoad.getTerminal()); + assertReactivePowerEquals(27.917, expLoad.getTerminal()); + assertActivePowerEquals(600, load.getTerminal()); + assertReactivePowerEquals(200, load.getTerminal()); + } + + @Test + void dummyZipLoadModelTest() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + Load load = network.getLoad("LOAD"); + load.remove(); + VoltageLevel vlload = network.getVoltageLevel("VLLOAD"); + Load zipLoad = vlload.newLoad() + .setId("ZIPLOAD") + .setBus("NLOAD") + .setP0(600.0) + .setQ0(200.0) + .newZipModel() + .setC0p(1) + .setC0q(1) + .add() + .add(); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertActivePowerEquals(600, zipLoad.getTerminal()); + assertReactivePowerEquals(200, zipLoad.getTerminal()); + } + + @Test + void zipLoadModelAndDistributedSlackOnLoadTest() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + Load load = network.getLoad("LOAD"); + VoltageLevel vlload = network.getVoltageLevel("VLLOAD"); + Load zipLoad = vlload.newLoad() + .setId("ZIPLOAD") + .setBus("NLOAD") + .setP0(50) + .setQ0(30) + .newZipModel() + .setC0p(0.5) + .setC0q(0.55) + .setC1p(0.3) + .setC1q(0.35) + .setC2p(0.2) + .setC2q(0.1) + .add() + .add(); + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertActivePowerEquals(45.326, zipLoad.getTerminal()); + assertReactivePowerEquals(29.519, zipLoad.getTerminal()); + assertActivePowerEquals(555.194, load.getTerminal()); + assertReactivePowerEquals(200, load.getTerminal()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/MultipleSlackBusesTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/MultipleSlackBusesTest.java new file mode 100644 index 0000000..48e481d --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/MultipleSlackBusesTest.java @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.ac.solver.NewtonRaphsonStoppingCriteriaType; +import com.powsybl.openloadflow.network.EurostagFactory; +import com.powsybl.openloadflow.util.LoadFlowAssert; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class MultipleSlackBusesTest { + + private Network network; + + private LoadFlow.Runner loadFlowRunner; + + private LoadFlowParameters parameters; + + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters() + .setUseReactiveLimits(false) + .setDistributedSlack(false); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setGradientComputationMode(2); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setMaxSlackBusCount(2) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + static Stream allStoppingCriteriaTypes() { + return Arrays.stream(NewtonRaphsonStoppingCriteriaType.values()).map(Arguments::of); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("allStoppingCriteriaTypes") + void multiSlackTest(NewtonRaphsonStoppingCriteriaType stoppingCriteria) { + parametersExt.setNewtonRaphsonStoppingCriteriaType(stoppingCriteria); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + List slackBusResults = componentResult.getSlackBusResults(); + assertEquals(2, slackBusResults.size()); + assertEquals(-0.716, slackBusResults.get(0).getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER); + assertEquals(-0.716, slackBusResults.get(1).getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER); + + parameters.setDistributedSlack(true); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + componentResult = result.getComponentResults().get(0); + slackBusResults = componentResult.getSlackBusResults(); + assertEquals(2, slackBusResults.size()); + assertEquals(-0.005, slackBusResults.get(0).getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER); + assertEquals(-0.005, slackBusResults.get(1).getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER); + } + + @Test + void nonImpedantBranchTest() { + network.getLine("NHV1_NHV2_1") + .setR(0) + .setX(0); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + List slackBusResults = componentResult.getSlackBusResults(); + assertEquals(3, componentResult.getIterationCount()); + assertEquals(2, slackBusResults.size()); + assertEquals(-2.755, slackBusResults.get(0).getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER); + assertEquals(-2.755, slackBusResults.get(1).getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER); + + parameters.setDistributedSlack(true); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + componentResult = result.getComponentResults().get(0); + slackBusResults = componentResult.getSlackBusResults(); + assertEquals(4, componentResult.getIterationCount()); + assertEquals(2, slackBusResults.size()); + assertEquals(-0.005, slackBusResults.get(0).getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER); + assertEquals(-0.005, slackBusResults.get(1).getActivePowerMismatch(), LoadFlowAssert.DELTA_POWER); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/NonImpedantBranchDisablingTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/NonImpedantBranchDisablingTest.java new file mode 100644 index 0000000..60418f2 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/NonImpedantBranchDisablingTest.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.commons.report.ReportNode; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Switch; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.ac.AcLoadFlowContext; +import com.powsybl.openloadflow.ac.AcLoadFlowParameters; +import com.powsybl.openloadflow.ac.AcloadFlowEngine; +import com.powsybl.openloadflow.graph.EvenShiloachGraphDecrementalConnectivityFactory; +import com.powsybl.openloadflow.graph.NaiveGraphConnectivityFactory; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.impl.LfNetworkList; +import com.powsybl.openloadflow.network.impl.LfNetworkLoaderImpl; +import com.powsybl.openloadflow.network.impl.Networks; +import com.powsybl.openloadflow.util.LoadFlowAssert; +import com.powsybl.openloadflow.util.PerUnit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertActivePowerEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class NonImpedantBranchDisablingTest { + + private LoadFlow.Runner loadFlowRunner; + + @BeforeEach + void setUp() { + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + } + + @Test + void test() { + Network network = NodeBreakerNetworkFactory.create(); + network.getSwitch("B3").setRetained(false); + network.getSwitch("C").setRetained(true); + AcLoadFlowParameters parameters = OpenLoadFlowParameters.createAcParameters(new LoadFlowParameters(), + new OpenLoadFlowParameters(), + new DenseMatrixFactory(), + new EvenShiloachGraphDecrementalConnectivityFactory<>(), + true, + false) + .setSolverFactory(new KnitroSolverFactory(), new LoadFlowParameters()); + + parameters.getNetworkParameters().setSlackBusSelector(new NameSlackBusSelector("VL1_1")); + LfNetwork lfNetwork = LfNetwork.load(network, new LfNetworkLoaderImpl(), parameters.getNetworkParameters()).get(0); + + try (AcLoadFlowContext context = new AcLoadFlowContext(lfNetwork, parameters)) { + var engine = new AcloadFlowEngine(context); + engine.run(); + assertEquals(8, context.getEquationSystem().getIndex().getSortedVariablesToFind().size()); + var l1 = lfNetwork.getBranchById("L1"); + var l2 = lfNetwork.getBranchById("L2"); + assertEquals(3.01884, l1.getP1().eval(), 10e-5); + assertEquals(3.01884, l2.getP1().eval(), 10e-5); + + lfNetwork.getBranchById("C").setDisabled(true); + + engine.run(); + assertEquals(8, context.getEquationSystem().getIndex().getSortedVariablesToFind().size()); // we have kept same variables!!! + assertEquals(0, l1.getP1().eval(), 10e-5); + assertEquals(6.07782, l2.getP1().eval(), 10e-5); + } + } + + @Test + void testOpenBranch() { + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + Network network = NodeBreakerNetworkFactory.create3Bars(); + network.getLine("L2").setR(0.0).setX(0.0); + network.getLine("L1").getTerminal1().disconnect(); + loadFlowRunner.run(network, parameters); + assertEquals(600.018, network.getLine("L2").getTerminal1().getP(), LoadFlowAssert.DELTA_POWER); + assertEquals(-600.018, network.getLine("L2").getTerminal2().getP(), LoadFlowAssert.DELTA_POWER); + assertEquals(0, network.getLine("L1").getTerminal1().getP(), 0); + + network.getLine("L1").getTerminal1().connect(); + network.getLine("L1").getTerminal2().disconnect(); + loadFlowRunner.run(network); + assertEquals(600.0, network.getLine("L2").getTerminal1().getP(), LoadFlowAssert.DELTA_POWER); + assertEquals(-600.0, network.getLine("L2").getTerminal2().getP(), LoadFlowAssert.DELTA_POWER); + assertEquals(0, network.getLine("L1").getTerminal2().getP(), 0); + } + + @Test + void testDisabledNonImpedantBranch() { + Network network = NodeBreakerNetworkFactory.create3Bars(); + Switch c1 = network.getSwitch("C1"); + c1.setOpen(true); + + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters olfParameters = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.NAME) + .setSlackBusesIds(List.of("VL2_0")) + .setAcSolverType(KnitroSolverFactory.NAME); + + loadFlowRunner.run(network, parameters); + + assertActivePowerEquals(401.757, network.getLine("L1").getTerminal1()); + assertActivePowerEquals(100.878, network.getLine("L2").getTerminal1()); + assertActivePowerEquals(100.878, network.getLine("L3").getTerminal1()); + + AcLoadFlowParameters acLoadFlowParameters + = OpenLoadFlowParameters.createAcParameters(network, + parameters, olfParameters, + new DenseMatrixFactory(), + new NaiveGraphConnectivityFactory<>(LfElement::getNum), + true, + false); + LfTopoConfig topoConfig = new LfTopoConfig(); + topoConfig.getSwitchesToClose().add(c1); + try (LfNetworkList lfNetworks = Networks.load(network, acLoadFlowParameters.getNetworkParameters(), topoConfig, ReportNode.NO_OP)) { + LfNetwork largestNetwork = lfNetworks.getLargest().orElseThrow(); + largestNetwork.getBranchById("C1").setDisabled(true); + try (AcLoadFlowContext context = new AcLoadFlowContext(largestNetwork, acLoadFlowParameters)) { + new AcloadFlowEngine(context).run(); + } + // should be the same as with previous LF + assertEquals(401.757, largestNetwork.getBranchById("L1").getP1().eval() * PerUnit.SB, LoadFlowAssert.DELTA_POWER); + assertEquals(100.878, largestNetwork.getBranchById("L2").getP1().eval() * PerUnit.SB, LoadFlowAssert.DELTA_POWER); + assertEquals(100.878, largestNetwork.getBranchById("L3").getP1().eval() * PerUnit.SB, LoadFlowAssert.DELTA_POWER); + } + } + + @Test + void testOpenAtOneSideZeroImpedanceBranch() { + Network network = NodeBreakerNetworkFactory.create3Bars(); + network.getLine("L2").setR(0.0).setX(0.0); + network.getLine("L2").getTerminal2().disconnect(); + LoadFlowResult result = loadFlowRunner.run(network); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertTrue(Double.isNaN(network.getLine("L2").getTerminal1().getP())); + assertTrue(Double.isNaN(network.getLine("L2").getTerminal2().getP())); + + LoadFlowParameters parameters = new LoadFlowParameters() + .setDc(true); + OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertTrue(Double.isNaN(network.getLine("L2").getTerminal1().getP())); + assertTrue(Double.isNaN(network.getLine("L2").getTerminal2().getP())); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/NonImpedantBranchWithBreakerIssueTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/NonImpedantBranchWithBreakerIssueTest.java new file mode 100644 index 0000000..b142c1f --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/NonImpedantBranchWithBreakerIssueTest.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2021, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.ac.AcLoadFlowContext; +import com.powsybl.openloadflow.ac.AcLoadFlowParameters; +import com.powsybl.openloadflow.ac.AcloadFlowEngine; +import com.powsybl.openloadflow.network.*; +import com.powsybl.openloadflow.network.impl.Networks; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class NonImpedantBranchWithBreakerIssueTest { + + @Test + void busBreakerAndNonImpedantBranchIssue() { + Network network = NodeBreakerNetworkFactory.create3barsAndJustOneVoltageLevel(); + network.getGenerator("G1").newMinMaxReactiveLimits().setMaxQ(100).setMinQ(-100).add(); + network.getGenerator("G2").newMinMaxReactiveLimits().setMaxQ(100).setMinQ(-100).add(); + LfNetworkParameters networkParameters = new LfNetworkParameters() + .setBreakers(true); + LfNetwork lfNetwork = Networks.load(network, networkParameters).get(0); + AcLoadFlowParameters acLoadFlowParameters = new AcLoadFlowParameters() + .setNetworkParameters(networkParameters) + .setMatrixFactory(new DenseMatrixFactory()) + .setSolverFactory(new KnitroSolverFactory(), new LoadFlowParameters()); + try (var context = new AcLoadFlowContext(lfNetwork, acLoadFlowParameters)) { + new AcloadFlowEngine(context) + .run(); + } + lfNetwork.updateState(new LfNetworkStateUpdateParameters(false, false, false, false, false, false, false, false, ReactivePowerDispatchMode.Q_EQUAL_PROPORTION, false, ReferenceBusSelectionMode.FIRST_SLACK, false)); + for (Bus bus : network.getBusView().getBuses()) { + assertEquals(400, bus.getV(), 0); + assertEquals(0, bus.getAngle(), 0); + } + assertEquals(-100, network.getGenerator("G1").getTerminal().getQ(), 0); + assertEquals(-100, network.getGenerator("G2").getTerminal().getQ(), 0); + } + + @Test + void busBreakerAndNonImpedantBranchIssueRef() { + Network network = NodeBreakerNetworkFactory.create3barsAndJustOneVoltageLevel(); + LfNetworkParameters networkParameters = new LfNetworkParameters(); + LfNetwork lfNetwork = Networks.load(network, networkParameters).get(0); + AcLoadFlowParameters acLoadFlowParameters = new AcLoadFlowParameters() + .setNetworkParameters(networkParameters) + .setMatrixFactory(new DenseMatrixFactory()) + .setSolverFactory(new KnitroSolverFactory(), new LoadFlowParameters()); + try (var context = new AcLoadFlowContext(lfNetwork, acLoadFlowParameters)) { + new AcloadFlowEngine(context) + .run(); + } + lfNetwork.updateState(new LfNetworkStateUpdateParameters(false, false, false, false, false, false, false, false, ReactivePowerDispatchMode.Q_EQUAL_PROPORTION, false, ReferenceBusSelectionMode.FIRST_SLACK, false)); + assertEquals(-100, network.getGenerator("G1").getTerminal().getQ(), 0); + assertEquals(-100, network.getGenerator("G2").getTerminal().getQ(), 0); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/NotSameNumberVariableEquationIssueTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/NotSameNumberVariableEquationIssueTest.java new file mode 100644 index 0000000..0fd1c38 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/NotSameNumberVariableEquationIssueTest.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.TopologyKind; +import com.powsybl.iidm.network.VoltageLevel; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.EurostagFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertVoltageEquals; + +/** + * @author Geoffroy Jamgotchian + */ +class NotSameNumberVariableEquationIssueTest { + + private LoadFlow.Runner loadFlowRunner; + + @BeforeEach + void setUp() { + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + } + + /** + * (local regul) + * GEN + * | L + * NGEN xxxxx ----- xxxx NGEN2 (regul NLOAD) + * | + * 8 + * | + * xxxxxxxxx NHV1 + * | | + * | | + * | | + * xxxxxxxxx NHV2 + * | + * 8 + * | + * xxxxx NLOAD + */ + @Test + void test() { + var network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + VoltageLevel vlgen2 = network.getSubstation("P1").newVoltageLevel() + .setId("VLGEN2") + .setNominalV(24) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + vlgen2.getBusBreakerView().newBus() + .setId("NGEN2") + .add(); + vlgen2.newGenerator() + .setId("GEN2") + .setConnectableBus("NGEN2") + .setBus("NGEN2") + .setMinP(0) + .setMaxP(100) + .setTargetP(1) + .setVoltageRegulatorOn(true) + .setTargetV(148) + .setRegulatingTerminal(network.getLoad("LOAD").getTerminal()) + .add(); + network.newLine() + .setId("L") + .setVoltageLevel1("VLGEN") + .setVoltageLevel2("VLGEN2") + .setConnectableBus1("NGEN") + .setBus1("NGEN") + .setConnectableBus2("NGEN2") + .setBus2("NGEN2") + .setR(0) + .setX(0) + .setG1(0) + .setG2(0) + .setB1(0) + .setB2(0) + .add(); + + LoadFlowParameters parameters = new LoadFlowParameters(); + OpenLoadFlowParameters parametersExt = OpenLoadFlowParameters.create(parameters).setAcSolverType(KnitroSolverFactory.NAME); + loadFlowRunner.run(network, parameters); + assertVoltageEquals(24.554, network.getBusBreakerView().getBus("NGEN")); + assertVoltageEquals(148.0, network.getBusBreakerView().getBus("NLOAD")); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/ReferenceBusPrioritiesTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/ReferenceBusPrioritiesTest.java new file mode 100644 index 0000000..0496356 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/ReferenceBusPrioritiesTest.java @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.Terminal; +import com.powsybl.iidm.network.extensions.ReferencePriorities; +import com.powsybl.iidm.network.extensions.ReferencePriority; +import com.powsybl.iidm.network.extensions.ReferenceTerminals; +import com.powsybl.iidm.network.extensions.SlackTerminal; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.ConnectedComponentNetworkFactory; +import com.powsybl.openloadflow.network.DistributedSlackNetworkFactory; +import com.powsybl.openloadflow.network.ReferenceBusSelectionMode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertAngleEquals; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Damien Jeandemange {@literal } + */ +class ReferenceBusPrioritiesTest { + + private Network network; + private Generator g1; + private Generator g2; + private Generator g3; + private Generator g4; + + private LoadFlow.Runner loadFlowRunner; + + private LoadFlowParameters parameters; + + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + network = DistributedSlackNetworkFactory.create(); + g1 = network.getGenerator("g1"); + g2 = network.getGenerator("g2"); + g3 = network.getGenerator("g3"); + g4 = network.getGenerator("g4"); + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters() + .setReadSlackBus(true) + .setUseReactiveLimits(false) + .setDistributedSlack(true) + .setConnectedComponentMode(LoadFlowParameters.ConnectedComponentMode.ALL); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setReferenceBusSelectionMode(ReferenceBusSelectionMode.GENERATOR_REFERENCE_PRIORITY) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void referencePriorityNotDefinedTest() { + ReferencePriorities.delete(network); + // will choose g2 which has highest Pmax + SlackTerminal.reset(network); + SlackTerminal.attach(g1.getTerminal().getBusBreakerView().getBus()); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + assertEquals("b2_vl_0", componentResult.getReferenceBusId()); + assertEquals("b1_vl_0", componentResult.getSlackBusResults().get(0).getId()); + assertAngleEquals(0, g2.getTerminal().getBusView().getBus()); + assertTrue(ReferenceTerminals.getTerminals(network).contains(g2.getTerminal())); + } + + @Test + void referencePriorityDifferentFromSlackTest() { + ReferencePriorities.delete(network); + ReferencePriority.set(g3, 1); + SlackTerminal.reset(network); + SlackTerminal.attach(g1.getTerminal().getBusBreakerView().getBus()); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + LoadFlowResult.ComponentResult componentResult = result.getComponentResults().get(0); + assertEquals("b3_vl_0", componentResult.getReferenceBusId()); + assertEquals("b1_vl_0", componentResult.getSlackBusResults().get(0).getId()); + assertAngleEquals(0, g3.getTerminal().getBusView().getBus()); + assertTrue(ReferenceTerminals.getTerminals(network).contains(g3.getTerminal())); + } + + @Test + void testMultipleComponents() { + Network network = ConnectedComponentNetworkFactory.createThreeCcLinkedByASingleBusWithTransformer(); + // open everything at bus b4 to create 3 components + network.getBusBreakerView().getBus("b4").getConnectedTerminalStream().forEach(t -> t.disconnect()); + ReferencePriorities.delete(network); + SlackTerminal.reset(network); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(3, result.getComponentResults().size()); + result.getComponentResults().forEach(cr -> assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, cr.getStatus())); + Set referenceTerminals = ReferenceTerminals.getTerminals(network); + assertEquals(3, referenceTerminals.size()); + assertTrue(referenceTerminals.contains(network.getGenerator("g2").getTerminal())); + assertTrue(referenceTerminals.contains(network.getGenerator("g6").getTerminal())); + assertTrue(referenceTerminals.contains(network.getGenerator("g10").getTerminal())); + } + + @Test + void testNotWritingReferenceTerminals() { + Network network = ConnectedComponentNetworkFactory.createThreeCcLinkedByASingleBusWithTransformer(); + // open everything at bus b4 to create 3 components + network.getBusBreakerView().getBus("b4").getConnectedTerminalStream().forEach(t -> t.disconnect()); + ReferencePriorities.delete(network); + ReferenceTerminals.reset(network); + SlackTerminal.reset(network); + parametersExt.setWriteReferenceTerminals(false); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(3, result.getComponentResults().size()); + result.getComponentResults().forEach(cr -> assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, cr.getStatus())); + Set referenceTerminals = ReferenceTerminals.getTerminals(network); + assertTrue(referenceTerminals.isEmpty()); + } + + @Test + void testNotWritingReferenceTerminals2() { + Network network = ConnectedComponentNetworkFactory.createThreeCcLinkedByASingleBusWithTransformer(); + // open everything at bus b4 to create 3 components + network.getBusBreakerView().getBus("b4").getConnectedTerminalStream().forEach(t -> t.disconnect()); + ReferencePriorities.delete(network); + ReferenceTerminals.reset(network); + SlackTerminal.reset(network); + parametersExt.setReferenceBusSelectionMode(ReferenceBusSelectionMode.FIRST_SLACK); + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertEquals(3, result.getComponentResults().size()); + result.getComponentResults().forEach(cr -> assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, cr.getStatus())); + Set referenceTerminals = ReferenceTerminals.getTerminals(network); + assertFalse(referenceTerminals.contains(network.getGenerator("g10").getTerminal())); + assertTrue(referenceTerminals.contains(network.getLine("l810").getTerminal2())); + assertFalse(referenceTerminals.contains(network.getLine("l910").getTerminal2())); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/SecondaryVoltageControlTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/SecondaryVoltageControlTest.java new file mode 100644 index 0000000..7a737c3 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/SecondaryVoltageControlTest.java @@ -0,0 +1,377 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.commons.PowsyblException; +import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory; +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.ReactiveLimits; +import com.powsybl.iidm.network.extensions.PilotPoint; +import com.powsybl.iidm.network.extensions.SecondaryVoltageControl; +import com.powsybl.iidm.network.extensions.SecondaryVoltageControlAdder; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.LfNetwork; +import com.powsybl.openloadflow.network.LfNetworkParameters; +import com.powsybl.openloadflow.network.LfSecondaryVoltageControl; +import com.powsybl.openloadflow.network.impl.LfNetworkLoaderImpl; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.concurrent.CompletionException; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.*; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Geoffroy Jamgotchian {@literal } + */ +class SecondaryVoltageControlTest { + + private Network network; + + private Bus b4; + private Bus b6; + private Bus b8; + private Bus b10; + private Generator g6; + private Generator g8; + + private LoadFlow.Runner loadFlowRunner; + + private LoadFlowParameters parameters; + + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + network = IeeeCdfNetworkFactory.create14(); + b4 = network.getBusBreakerView().getBus("B4"); + b6 = network.getBusBreakerView().getBus("B6"); + b8 = network.getBusBreakerView().getBus("B8"); + b10 = network.getBusBreakerView().getBus("B10"); + g6 = network.getGenerator("B6-G"); + g8 = network.getGenerator("B8-G"); + + // adjust reactive limit to avoid generators be to limit + g8.newMinMaxReactiveLimits() + .setMinQ(-6) + .setMaxQ(200) + .add(); + + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters(); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setMaxPlausibleTargetVoltage(1.6) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + private static double qToK(Generator g) { + ReactiveLimits limits = g.getReactiveLimits(); + double q = -g.getTerminal().getQ(); + double p = -g.getTerminal().getP(); + return (2 * q - limits.getMaxQ(p) - limits.getMinQ(p)) + / (limits.getMaxQ(p) - limits.getMinQ(p)); + } + + @Test + void testNoReactiveLimits() { + parameters.setUseReactiveLimits(false); + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(13).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B6-G").add() + .newControlUnit().withId("B8-G").add() + .add() + .add(); + SecondaryVoltageControl control = network.getExtension(SecondaryVoltageControl.class); + PilotPoint pilotPoint = control.getControlZone("z1").orElseThrow().getPilotPoint(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(3, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(12.611, b10); + assertVoltageEquals(12.84, b6); + assertVoltageEquals(21.8, b8); + assertEquals(0.248, qToK(g6), DELTA_POWER); + assertEquals(-0.77, qToK(g8), DELTA_POWER); + + parametersExt.setSecondaryVoltageControl(true); + + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(8, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(13, b10); + assertVoltageEquals(12.945, b6); + assertVoltageEquals(23.839, b8); + assertEquals(-0.412, qToK(g6), DELTA_POWER); + assertEquals(-0.412, qToK(g8), DELTA_POWER); + + pilotPoint.setTargetV(13.5); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(8, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(13.5, b10); + assertVoltageEquals(13.358, b6); + assertVoltageEquals(25.621, b8); + assertEquals(-0.094, qToK(g6), DELTA_POWER); + assertEquals(-0.094, qToK(g8), DELTA_POWER); + + pilotPoint.setTargetV(12); + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(8, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(12, b10); + assertVoltageEquals(12.151, b6); + assertVoltageEquals(20.269, b8); + assertEquals(-0.932, qToK(g6), DELTA_POWER); + assertEquals(-0.932, qToK(g8), DELTA_POWER); + } + + @Test + void testReactiveLimits() { + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(11.5).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B6-G").add() + .newControlUnit().withId("B8-G").add() + .add() + .add(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(3, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(12.606, b10); + assertVoltageEquals(12.84, b6); + assertVoltageEquals(21.8, b8); + assertReactivePowerEquals(-12.730, g6.getTerminal()); // [-6, 24] + assertReactivePowerEquals(-17.623, g8.getTerminal()); // [-6, 200] + + parametersExt.setSecondaryVoltageControl(true); + + result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(11, result.getComponentResults().get(0).getIterationCount()); + + assertVoltageEquals(11.736, b10); // 11.5 kV was not feasible + assertVoltageEquals(11.924, b6); + assertVoltageEquals(19.537, b8); + assertReactivePowerEquals(6, g6.getTerminal()); // [-6, 24] => qmin + assertReactivePowerEquals(6, g8.getTerminal()); // [-6, 200] => qmin + } + + @Test + void testUnblockGeneratorFromLimit() { + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(15).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B6-G").add() + .newControlUnit().withId("B8-G").add() + .add() + .add(); + + // to put g6 and g8 at q min + g6.setTargetV(11.8); + g8.setTargetV(19.5); + + parametersExt.setSecondaryVoltageControl(true); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setUpperVoltageBound(2.0); // for this specific test, we allow the voltage to be greater than 1.5 (knitro's default maxRealisticVoltage) for the network to converge + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + + // try to put g6 and g8 at qmax to see if they are correctly unblock from qmin + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(21, result.getComponentResults().get(0).getIterationCount()); + + assertVoltageEquals(15, b10); + assertVoltageEquals(14.604, b6); + assertVoltageEquals(30.744, b8); + assertReactivePowerEquals(-24, g6.getTerminal()); // [-6, 24] => qmax + assertReactivePowerEquals(-200, g8.getTerminal()); // [-6, 200] => qmax + } + + @Test + void multiNoReactiveLimitsZonesTest() { + parameters.setUseReactiveLimits(false); + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(142).withBusbarSectionsOrBusesIds(List.of("B4")).add() + .newControlUnit().withId("B1-G").add() + .newControlUnit().withId("B2-G").add() + .newControlUnit().withId("B3-G").add() + .add() + .newControlZone() + .withName("z2") + .newPilotPoint().withTargetV(14.5).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B6-G").add() + .newControlUnit().withId("B8-G").add() + .add() + .add(); + + parametersExt.setSecondaryVoltageControl(true); + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setUpperVoltageBound(2.0); // for this specific test, we allow the voltage to be greater than 1.5 (knitro's default maxRealisticVoltage) for the network to converge + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(9, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(142, b4); + assertVoltageEquals(14.5, b10); + } + + @Test + void testOpenBranchIssue() { + // open branch L6-13-1 on side 2 so that a neighbor branch of B6-G is disconnected to the other side + network.getLine("L6-13-1").getTerminal2().disconnect(); + + parameters.setUseReactiveLimits(false); + parametersExt.setSecondaryVoltageControl(true); + + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(14.4).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B6-G").add() + .newControlUnit().withId("B8-G").add() + .add() + .add(); + + var result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.CONVERGED, result.getComponentResults().get(0).getStatus()); + assertEquals(9, result.getComponentResults().get(0).getIterationCount()); + assertVoltageEquals(14.4, b10); + assertVoltageEquals(14.151, b6); + assertVoltageEquals(28.913, b8); + } + + @Test + void pilotPointNotFoundTest() { + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(13).withBusbarSectionsOrBusesIds(List.of("XX", "YY")).add() + .newControlUnit().withId("B6-G").add() + .newControlUnit().withId("B8-G").add() + .add(); + + LfNetworkParameters networkParameters = new LfNetworkParameters().setSecondaryVoltageControl(true); + List lfNetworks = LfNetwork.load(network, new LfNetworkLoaderImpl(), networkParameters); + LfNetwork lfNetwork = lfNetworks.get(0); + assertTrue(lfNetwork.getSecondaryVoltageControls().isEmpty()); + } + + @Test + void controlUnitNotFoundTest() { + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(13).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B99-G").add() + .newControlUnit().withId("B8-G").add() + .add() + .add(); + + LfNetworkParameters networkParameters = new LfNetworkParameters().setSecondaryVoltageControl(true); + List lfNetworks = LfNetwork.load(network, new LfNetworkLoaderImpl(), networkParameters); + LfNetwork lfNetwork = lfNetworks.get(0); + List secondaryVoltageControls = lfNetwork.getSecondaryVoltageControls(); + assertEquals(1, secondaryVoltageControls.size()); + assertEquals(1, secondaryVoltageControls.get(0).getControlledBuses().size()); // B99-G not found + } + + @Test + void testOptionalNoValueIssue() { + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(13).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B6-G").add() + .newControlUnit().withId("B9-SH").add() // this is a shunt which is not supported + .add() + .add(); + + parametersExt.setSecondaryVoltageControl(true); + + CompletionException e = assertThrows(CompletionException.class, () -> loadFlowRunner.run(network, parameters)); + assertEquals("Control unit 'B9-SH' of zone 'z1' is expected to be either a generator or a VSC converter station", e.getCause().getMessage()); + } + + @Test + void testAnotherOptionalNoValueIssue() { + Generator g6 = network.getGenerator("B6-G"); + g6.newMinMaxReactiveLimits() + .setMinQ(100) + .setMaxQ(100.000001) + .add(); + + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(13).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B6-G").add() + .newControlUnit().withId("B8-G").add() + .add(); + + parametersExt.setSecondaryVoltageControl(true); + + assertDoesNotThrow(() -> loadFlowRunner.run(network, parameters)); + } + + @Test + void disjointControlZoneTest() { + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(13).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B99-G").add() + .newControlUnit().withId("B8-G").add() + .add() + .newControlZone() + .withName("z2") + .newPilotPoint().withTargetV(13).withBusbarSectionsOrBusesIds(List.of("B10")).add() + .newControlUnit().withId("B1-G").add() + .newControlUnit().withId("B8-G").add() + .add() + .add(); + + LfNetworkParameters networkParameters = new LfNetworkParameters().setSecondaryVoltageControl(true); + LfNetworkLoaderImpl networkLoader = new LfNetworkLoaderImpl(); + PowsyblException e = assertThrows(PowsyblException.class, () -> LfNetwork.load(network, networkLoader, networkParameters)); + assertEquals("Generator voltage control of controlled bus 'VL8_0' is present in more that one control zone", e.getMessage()); + } + + @Test + void testNotPlausibleTargetV() { + // g8 generator is very far from pilot point bus b12, there is no way for generators of the zone to control + // the pilot point voltage, this is detected by secondary voltage control outer loop which fails + network.newExtension(SecondaryVoltageControlAdder.class) + .newControlZone() + .withName("z1") + .newPilotPoint().withTargetV(11.5).withBusbarSectionsOrBusesIds(List.of("B12")).add() + .newControlUnit().withId("B8-G").add() + .add() + .add(); + + parametersExt.setSecondaryVoltageControl(true); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertEquals(LoadFlowResult.ComponentResult.Status.FAILED, result.getComponentResults().get(0).getStatus()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/SwitchPqPvTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/SwitchPqPvTest.java new file mode 100644 index 0000000..09318b8 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/SwitchPqPvTest.java @@ -0,0 +1,244 @@ +/** + * Copyright (c) 2019, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.*; +import com.powsybl.iidm.network.extensions.VoltagePerReactivePowerControlAdder; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.AbstractLoadFlowNetworkFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertVoltageEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * g1 g2 g3 + * | | | + * b1 b2 b3 + * | | | + * -------b3----- + * | + * ld + * + * @author Geoffroy Jamgotchian {@literal } + */ +class SwitchPqPvTest extends AbstractLoadFlowNetworkFactory { + + private Network network; + private Bus b1; + private Bus b2; + private Bus b3; + private Bus b4; + private Generator g1; + private Generator g2; + private Generator g3; + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + + @BeforeEach + void setUp() { + network = Network.create("switch-pq-pv-test", "code"); + Substation s = network.newSubstation() + .setId("s") + .add(); + VoltageLevel vl1 = s.newVoltageLevel() + .setId("vl1") + .setNominalV(20) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + b1 = vl1.getBusBreakerView().newBus() + .setId("b1") + .add(); + VoltageLevel vl2 = s.newVoltageLevel() + .setId("vl2") + .setNominalV(20) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + b2 = vl2.getBusBreakerView().newBus() + .setId("b2") + .add(); + VoltageLevel vl3 = s.newVoltageLevel() + .setId("vl3") + .setNominalV(20) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + b3 = vl3.getBusBreakerView().newBus() + .setId("b3") + .add(); + VoltageLevel vl4 = s.newVoltageLevel() + .setId("vl4") + .setNominalV(380) + .setTopologyKind(TopologyKind.BUS_BREAKER) + .add(); + b4 = vl4.getBusBreakerView().newBus() + .setId("b4") + .add(); + vl4.newLoad() + .setId("ld") + .setBus("b4") + .setConnectableBus("b4") + .setP0(300) + .setQ0(200) + .add(); + g1 = b1.getVoltageLevel() + .newGenerator() + .setId("g1") + .setBus("b1") + .setConnectableBus("b1") + .setEnergySource(EnergySource.THERMAL) + .setMinP(0) + .setMaxP(200) + .setTargetP(100) + .setTargetV(17) + .setVoltageRegulatorOn(true) + .add(); + g1.newMinMaxReactiveLimits() + .setMinQ(-179) + .setMaxQ(1000) + .add(); + g2 = b2.getVoltageLevel() + .newGenerator() + .setId("g2") + .setBus("b2") + .setConnectableBus("b2") + .setEnergySource(EnergySource.THERMAL) + .setMinP(0) + .setMaxP(200) + .setTargetP(100) + .setTargetV(21) + .setVoltageRegulatorOn(true) + .add(); + g2.newMinMaxReactiveLimits() + .setMinQ(-1000) + .setMaxQ(411) + .add(); + g3 = b3.getVoltageLevel() + .newGenerator() + .setId("g3") + .setBus("b3") + .setConnectableBus("b3") + .setEnergySource(EnergySource.THERMAL) + .setMinP(0) + .setMaxP(200) + .setTargetP(100) + .setTargetV(20) + .setVoltageRegulatorOn(true) + .add(); + g3.newMinMaxReactiveLimits() + .setMinQ(-1000) + .setMaxQ(30) + .add(); + s.newTwoWindingsTransformer() + .setId("tr14") + .setBus1(b1.getId()) + .setConnectableBus1(b1.getId()) + .setBus2(b4.getId()) + .setConnectableBus2(b4.getId()) + .setRatedU1(20.5) + .setRatedU2(399) + .setR(1) + .setX(100) + .add(); + s.newTwoWindingsTransformer() + .setId("tr24") + .setBus1(b2.getId()) + .setConnectableBus1(b2.getId()) + .setBus2(b4.getId()) + .setConnectableBus2(b4.getId()) + .setRatedU1(20.5) + .setRatedU2(397) + .setR(0.5) + .setX(20) + .add(); + s.newTwoWindingsTransformer() + .setId("tr34") + .setBus1(b3.getId()) + .setConnectableBus1(b3.getId()) + .setBus2(b4.getId()) + .setConnectableBus2(b4.getId()) + .setRatedU1(20.5) + .setRatedU2(397) + .setR(0.5) + .setX(10) + .add(); + + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + parameters = new LoadFlowParameters() + .setDistributedSlack(false); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.MOST_MESHED) + .setAcSolverType(KnitroSolverFactory.NAME); + } + + @Test + void test() { + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + // bus 1 and 3 switch PQ at first outer loop, then at next outer loop bus 3 go back PV + assertVoltageEquals(17.032769, b1); // PQ => v != 17 + assertVoltageEquals(21, b2); // PV + assertVoltageEquals(20, b3); // PV + } + + @Test + void testWithSlope() { + g3.remove(); + double value = b3.getVoltageLevel().getNominalV() * b3.getVoltageLevel().getNominalV(); + StaticVarCompensator svc3 = b3.getVoltageLevel() + .newStaticVarCompensator() + .setId("svc3") + .setBus("b3") + .setConnectableBus("b3") + .setVoltageSetpoint(20) + .setRegulationMode(StaticVarCompensator.RegulationMode.VOLTAGE) + .setBmax(30 / value) + .setBmin(-1000 / value) + .add(); + g3 = b3.getVoltageLevel() + .newGenerator() + .setId("g3") + .setBus("b3") + .setConnectableBus("b3") + .setEnergySource(EnergySource.THERMAL) + .setMinP(0) + .setMaxP(200) + .setTargetP(100) + .setTargetQ(0) + .setVoltageRegulatorOn(false) + .add(); + g3.newMinMaxReactiveLimits() + .setMinQ(0) + .setMaxQ(0) + .add(); + + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + // bus 1 and 3 switch PQ at first outer loop, then at next outer loop bus 3 go back PV + assertVoltageEquals(17.032769, b1); // PQ => v != 17 + assertVoltageEquals(21, b2); // PV + assertVoltageEquals(20, b3); // PV + + parametersExt.setVoltagePerReactivePowerControl(true); + svc3.newExtension(VoltagePerReactivePowerControlAdder.class).withSlope(0.00001).add(); + LoadFlowResult result2 = loadFlowRunner.run(network, parameters); + assertTrue(result2.isFullyConverged()); + // bus 1 and 3 switch PQ at first outer loop, then at next outer loop bus 3 does not go back PV + assertVoltageEquals(17.034003, b1); // PQ => v != 17 + assertVoltageEquals(21, b2); // PV + assertEquals(20.00140, b3.getV(), 10E-3); // remains PQ because of slope + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/VoltageTargetPrioritiesTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/VoltageTargetPrioritiesTest.java new file mode 100644 index 0000000..b2a0598 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/VoltageTargetPrioritiesTest.java @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Network; +import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.network.EurostagFactory; +import com.powsybl.openloadflow.network.SlackBusSelectionMode; +import com.powsybl.openloadflow.network.VoltageControl; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertVoltageEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Pierre Arvy {@literal } + */ +class VoltageTargetPrioritiesTest { + + @Test + void voltageTargetPriorities() { + Network network = EurostagFactory.fix(EurostagTutorialExample1Factory.create()); + Bus genBus = network.getBusBreakerView().getBus("NGEN"); + Bus loadBus = network.getBusBreakerView().getBus("NLOAD"); + + LoadFlow.Runner loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + LoadFlowParameters parameters = new LoadFlowParameters().setUseReactiveLimits(false) + .setDistributedSlack(false); + OpenLoadFlowParameters parametersExt = OpenLoadFlowParameters.create(parameters) + .setSlackBusSelectionMode(SlackBusSelectionMode.FIRST) + .setAcSolverType(KnitroSolverFactory.NAME); + + network.getTwoWindingsTransformer("NHV2_NLOAD") + .getRatioTapChanger() + .setRegulationTerminal(network.getTwoWindingsTransformer("NGEN_NHV1").getTerminal1()) + .setTargetV(25.115); + + loadBus.getVoltageLevel().newShuntCompensator() + .setId("SC") + .setBus(loadBus.getId()) + .setConnectableBus(loadBus.getId()) + .setSectionCount(1) + .newLinearModel() + .setBPerSection(3.25 * Math.pow(10, -3)) + .setMaximumSectionCount(1) + .add() + .setVoltageRegulatorOn(true) + .setRegulatingTerminal(network.getTwoWindingsTransformer("NGEN_NHV1").getTerminal1()) + .setTargetV(23.75) + .setTargetDeadband(0) + .add(); + + parameters.setTransformerVoltageControlOn(true) + .setShuntCompensatorVoltageControlOn(true); + + // Generator has target priority by default + LoadFlowResult result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(24.5, genBus); + + // Transformer has target priority + parametersExt.setVoltageTargetPriorities(List.of(VoltageControl.Type.TRANSFORMER.name(), VoltageControl.Type.GENERATOR.name(), VoltageControl.Type.SHUNT.name())); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(25.115, genBus); + + // Shunt has target priority + parametersExt.setVoltageTargetPriorities(List.of(VoltageControl.Type.SHUNT.name(), VoltageControl.Type.GENERATOR.name(), VoltageControl.Type.TRANSFORMER.name())); + result = loadFlowRunner.run(network, parameters); + assertTrue(result.isFullyConverged()); + assertVoltageEquals(23.75, genBus); + } + +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverParametersTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverParametersTest.java new file mode 100644 index 0000000..a63ea35 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverParametersTest.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2024, Artelys (http://www.artelys.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver.utils; + +import com.powsybl.openloadflow.knitro.solver.KnitroLoadFlowParameters; +import com.powsybl.openloadflow.knitro.solver.KnitroSolverParameters; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Pierre Arvy {@literal } + * @author Jeanne Archambault {@literal } + */ +class KnitroSolverParametersTest { + + @Test + void testGradientComputationMode() { + KnitroSolverParameters parametersKnitro = new KnitroSolverParameters(); + // default value + assertEquals(1, parametersKnitro.getGradientComputationMode()); + + // set other value + parametersKnitro.setGradientComputationMode(3); + assertEquals(3, parametersKnitro.getGradientComputationMode()); + + // wrong values + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setGradientComputationMode(0)); + assertEquals("Knitro gradient computation mode must be between 1 and 3", e.getMessage()); + IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setGradientComputationMode(4)); + assertEquals("Knitro gradient computation mode must be between 1 and 3", e2.getMessage()); + } + + @Test + void testGradientUserRoutine() { + KnitroSolverParameters parametersKnitro = new KnitroSolverParameters(); + // default value + assertEquals(2, parametersKnitro.getGradientUserRoutine()); + + // set other value + parametersKnitro.setGradientUserRoutine(1); + assertEquals(1, parametersKnitro.getGradientUserRoutine()); + + // wrong values + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setGradientUserRoutine(0)); + assertEquals("User routine must be between 1 and 2", e.getMessage()); + IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setGradientUserRoutine(3)); + assertEquals("User routine must be between 1 and 2", e2.getMessage()); + } + + @Test + void getAndSetVoltageBounds() { + KnitroSolverParameters parametersKnitro = new KnitroSolverParameters(); + // default value + assertEquals(0.5, parametersKnitro.getLowerVoltageBound()); + assertEquals(1.5, parametersKnitro.getUpperVoltageBound()); + // set other value + parametersKnitro.setLowerVoltageBound(0.95); + parametersKnitro.setUpperVoltageBound(1.05); + assertEquals(0.95, parametersKnitro.getLowerVoltageBound()); + assertEquals(1.05, parametersKnitro.getUpperVoltageBound()); + // wrong values + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setLowerVoltageBound(-1.0)); + assertEquals("Realistic voltage bounds must strictly greater than 0", e.getMessage()); + IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setUpperVoltageBound(-1.0)); + assertEquals("Realistic voltage bounds must strictly greater than 0", e2.getMessage()); + IllegalArgumentException e3 = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setUpperVoltageBound(0.90)); + assertEquals("Realistic voltage upper bounds must greater than lower bounds", e3.getMessage()); + } + + @Test + void testSetAndGetConvEpsPerEq() { + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + // check default conv value + assertEquals(Math.pow(10, -6), knitroLoadFlowParameters.getConvEps()); + + // set other value + knitroLoadFlowParameters.setConvEps(Math.pow(10, -2)); + assertEquals(Math.pow(10, -2), knitroLoadFlowParameters.getConvEps()); + + // wrong values + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> knitroLoadFlowParameters.setConvEps(-1.0)); + assertEquals("Convergence stopping criteria must be greater than 0", e.getMessage()); + IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> knitroLoadFlowParameters.setConvEps(0)); + assertEquals("Convergence stopping criteria must be greater than 0", e2.getMessage()); + } + + @Test + void testToString() { + KnitroSolverParameters parameters = new KnitroSolverParameters(); + assertEquals("KnitroSolverParameters(gradientComputationMode=1, " + + "stoppingCriteria=1.0E-6, " + + "minRealisticVoltage=0.5, " + + "maxRealisticVoltage=1.5, " + + "alwaysUpdateNetwork=false, " + + "maxIterations=200" + + ")", parameters.toString()); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverStoppingCriteriaTest.java b/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverStoppingCriteriaTest.java new file mode 100644 index 0000000..9bc6883 --- /dev/null +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverStoppingCriteriaTest.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2024, Artelys (http://www.artelys.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.openloadflow.knitro.solver.utils; + +import com.powsybl.iidm.network.Bus; +import com.powsybl.iidm.network.Network; +import com.powsybl.loadflow.LoadFlow; +import com.powsybl.loadflow.LoadFlowParameters; +import com.powsybl.loadflow.LoadFlowResult; +import com.powsybl.math.matrix.DenseMatrixFactory; +import com.powsybl.openloadflow.OpenLoadFlowParameters; +import com.powsybl.openloadflow.OpenLoadFlowProvider; +import com.powsybl.openloadflow.knitro.solver.KnitroLoadFlowParameters; +import com.powsybl.openloadflow.knitro.solver.KnitroSolverFactory; +import com.powsybl.openloadflow.network.FourBusNetworkFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertAngleEquals; +import static com.powsybl.openloadflow.util.LoadFlowAssert.assertVoltageEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Jeanne Archambault {@literal } + */ +class KnitroSolverStoppingCriteriaTest { + + private Network network; + private LoadFlow.Runner loadFlowRunner; + private LoadFlowParameters parameters; + private OpenLoadFlowParameters parametersExt; + Bus b1; + Bus b2; + Bus b3; + Bus b4; + + @BeforeEach + void setUp() { + // Sparse matrix solver only + loadFlowRunner = new LoadFlow.Runner(new OpenLoadFlowProvider(new DenseMatrixFactory())); + + // ============= Setting LoadFlow parameters ============= + parameters = new LoadFlowParameters(); + parametersExt = OpenLoadFlowParameters.create(parameters) + .setAcSolverType(KnitroSolverFactory.NAME); + // No OLs + parameters.setBalanceType(LoadFlowParameters.BalanceType.PROPORTIONAL_TO_LOAD); + parameters.setDistributedSlack(false) + .setUseReactiveLimits(false); + parameters.getExtension(OpenLoadFlowParameters.class) + .setSvcVoltageMonitoring(false); + network = FourBusNetworkFactory.createWithCondenser(); + b1 = network.getBusBreakerView().getBus("b1"); + b2 = network.getBusBreakerView().getBus("b2"); + b3 = network.getBusBreakerView().getBus("b3"); + b4 = network.getBusBreakerView().getBus("b4"); + } + + @Test + void testEffectOfConvEpsPerEq() { + /* + * Checks the effect of changing Knitro's parameter convEpsPerEq on precision and values, when running Knitro solver + */ + + // ============= Model with default precision ============= + LoadFlowResult knitroResultDefault = loadFlowRunner.run(network, parameters); + assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, knitroResultDefault.getComponentResults().get(0).getStatus()); + assertTrue(knitroResultDefault.isFullyConverged()); + assertVoltageEquals(1.0, b1); + assertAngleEquals(0, b1); + assertVoltageEquals(0.983834, b2); + assertAngleEquals(-9.490705, b2); + assertVoltageEquals(0.983124, b3); + assertAngleEquals(-13.178514, b3); + assertVoltageEquals(1.0, b4); + assertAngleEquals(-6.531907, b4); + + // ============= Model with smaller precision ============= + KnitroLoadFlowParameters knitroLoadFlowParameters = new KnitroLoadFlowParameters(); // set gradient computation mode + knitroLoadFlowParameters.setConvEps(Math.pow(10, -2)); + parameters.addExtension(KnitroLoadFlowParameters.class, knitroLoadFlowParameters); + + LoadFlowResult knitroResultLessPrecise = loadFlowRunner.run(network, parameters); + + assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, knitroResultLessPrecise.getComponentResults().get(0).getStatus()); + assertTrue(knitroResultLessPrecise.isFullyConverged()); + assertVoltageEquals(1.0, b1); + assertAngleEquals(0, b1); + assertVoltageEquals(0.983834, b2); + assertAngleEquals(-9.485945, b2); + assertVoltageEquals(0.983124, b3); + assertAngleEquals(-13.170002, b3); + assertVoltageEquals(1.0, b4); + assertAngleEquals(-6.530383, b4); + } +} diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..5416126 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,19 @@ + + + + + + %-5p %d{HH:mm:ss.SSS} %-20C{1} | %m%n + + + + + +