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