diff --git a/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolver.java b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolver.java index 1429531..f0f5a59 100644 --- a/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolver.java +++ b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolver.java @@ -155,7 +155,7 @@ public List getUpperBounds() { /** * Handles initialization of the state vector. */ - public class StateInitializer { + public static class StateInitializer { private final List initialState; public StateInitializer(LfNetwork lfNetwork, EquationSystem equationSystem, @@ -178,102 +178,6 @@ public List getInitialState() { } } - /** - * Adds constraints to the Knitro problem, classifying them as linear or non-linear. - * - * @param knitroProblem The Knitro problem to which constraints are added. - * @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. - */ - public void addLinearConstraints(KnitroProblem knitroProblem, - List> sortedEquationsToSolve, - NonLinearExternalSolverUtils solverUtils, List listNonLinearConsts) { - - int numConst = sortedEquationsToSolve.size(); - - for (int equationId = 0; equationId < numConst; equationId++) { - addConstraint(equationId, knitroProblem, 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 knitroProblem The Knitro problem to which the constraint is added. - * @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. - */ - public void addConstraint(int equationId, KnitroProblem knitroProblem, 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).getListIdVar(); - - List listCoef = solverUtils.getLinearConstraint(typeEq, terms).getListCoef(); - - for (int i = 0; i < listVar.size(); i++) { - knitroProblem.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 knitroProblem The Knitro problem instance. - * @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. - */ - public void setJacobianMatrix(KnitroProblem knitroProblem, 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); - knitroProblem.setJacNnzPattern(listNonZerosCtsDense, listNonZerosVarsDense); - } else if (knitroParameters.getGradientUserRoutine() == 2) { - // Sparse method: compute Jacobian only for variables the constraints depend on. - buildSparseJacobianMatrix(sortedEquationsToSolve, listNonLinearConsts, listNonZerosCtsSparse, listNonZerosVarsSparse); - knitroProblem.setJacNnzPattern(listNonZerosCtsSparse, listNonZerosVarsSparse); - } - // Set the callback for gradient evaluations if the user directly passes the Jacobian to the solver. - knitroProblem.setGradEvalCallback(new KnitroProblem.CallbackEvalG( - jacobianMatrix, - listNonZerosCtsDense, - listNonZerosVarsDense, - listNonZerosCtsSparse, - listNonZerosVarsSparse, - lfNetwork, - equationSystem, - knitroParameters - )); - } - } - public void buildDenseJacobianMatrix(int numVar, List listNonLinearConsts, List listNonZerosCtsDense, List listNonZerosVarsDense) { for (Integer idCt : listNonLinearConsts) { for (int i = 0; i < numVar; i++) { @@ -298,7 +202,7 @@ public void buildSparseJacobianMatrix(List listNonZerosVarsCurrentCt = new ArrayList<>(); //list of variables involved in current constraint for (EquationTerm term : terms) { - for (Variable variable : term.getVariables()) { + for (Variable variable : term.getVariables()) { listNonZerosVarsCurrentCt.add(variable.getRow()); } } @@ -315,7 +219,7 @@ private final class KnitroProblem extends KNProblem { * Callback function to evaluate non-linear parts of the objective and of constraints */ - private final class CallbackEvalFC extends KNEvalFCCallback { + private static final class CallbackEvalFC extends KNEvalFCCallback { private final List> sortedEquationsToSolve; private final List listNonLinearConsts; @@ -347,7 +251,7 @@ public void evaluateFC(final List x, final List obj, final List< 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()) { + for (EquationTerm term : equation.getTerms()) { term.setStateVector(currentState); if (term.isActive()) { valueConst += term.eval(); @@ -468,7 +372,7 @@ public void evaluateH(final List x, final double sigma, final List equationSystem, TargetVector targetVector, VoltageInitializer voltageInitializer, JacobianMatrix jacobianMatrix) throws KNException { + private KnitroProblem(LfNetwork lfNetwork, EquationSystem equationSystem, TargetVector targetVector, VoltageInitializer voltageInitializer, JacobianMatrix jacobianMatrix) throws KNException { // =============== Variables =============== // Defining variables @@ -502,7 +406,7 @@ private KnitroProblem(LfNetwork lfNetwork, EquationSystem listNonZerosCtsSparse = new ArrayList<>(); List listNonZerosVarsSparse = new ArrayList<>(); - setJacobianMatrix(this, lfNetwork, jacobianMatrix, sortedEquationsToSolve, listNonLinearConsts, + 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 { @@ -562,7 +558,7 @@ public void close() { @Override public AcSolverResult run(VoltageInitializer voltageInitializer, ReportNode reportNode) { - int nbIter = -1; + int nbIter; AcSolverStatus acStatus; KnitroProblem instance; @@ -610,7 +606,7 @@ public AcSolverResult run(VoltageInitializer voltageInitializer, ReportNode repo 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()) { + for (EquationTerm term : equation.getTerms()) { term.setStateVector(equationSystem.getStateVector()); } } diff --git a/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverParameters.java b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverParameters.java index e3c1700..310dbe5 100644 --- a/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverParameters.java +++ b/src/main/java/com/powsybl/openloadflow/knitro/solver/KnitroSolverParameters.java @@ -81,7 +81,7 @@ public double getLowerVoltageBound() { public KnitroSolverParameters setLowerVoltageBound(double lowerVoltageBound) { if (lowerVoltageBound < 0) { - throw new IllegalArgumentException("Realistic voltage bounds must strictly greater then 0"); + throw new IllegalArgumentException("Realistic voltage bounds must strictly greater than 0"); } this.lowerVoltageBound = lowerVoltageBound; return this; @@ -93,10 +93,10 @@ public double getUpperVoltageBound() { public KnitroSolverParameters setUpperVoltageBound(double upperVoltageBound) { if (upperVoltageBound < 0) { - throw new IllegalArgumentException("Realistic voltage bounds must strictly greater then 0"); + throw new IllegalArgumentException("Realistic voltage bounds must strictly greater than 0"); } if (upperVoltageBound <= lowerVoltageBound) { - throw new IllegalArgumentException("Realistic voltage upper bounds must greater then lower bounds"); + throw new IllegalArgumentException("Realistic voltage upper bounds must greater than lower bounds"); } this.upperVoltageBound = upperVoltageBound; return this; diff --git a/src/main/java/com/powsybl/openloadflow/knitro/solver/NonLinearExternalSolverUtils.java b/src/main/java/com/powsybl/openloadflow/knitro/solver/NonLinearExternalSolverUtils.java index dcf8392..b1d13d5 100644 --- a/src/main/java/com/powsybl/openloadflow/knitro/solver/NonLinearExternalSolverUtils.java +++ b/src/main/java/com/powsybl/openloadflow/knitro/solver/NonLinearExternalSolverUtils.java @@ -66,22 +66,7 @@ public VarAndCoefList getLinearConstraint(AcEquationType typeEq, List listIdVar; - private List listCoef; - - public VarAndCoefList(List listIdVar, List listCoef) { - this.listIdVar = listIdVar; - this.listCoef = listCoef; - } - - public List getListIdVar() { - return listIdVar; - } - - public List getListCoef() { - return listCoef; - } + public record VarAndCoefList(List listIdVar, List listCoef) { } public VarAndCoefList addConstraintConstantTarget(List> terms) { 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 index d31aa25..a63ea35 100644 --- a/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverParametersTest.java +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverParametersTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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/. @@ -18,7 +18,7 @@ * @author Pierre Arvy {@literal } * @author Jeanne Archambault {@literal } */ -public class KnitroSolverParametersTest { +class KnitroSolverParametersTest { @Test void testGradientComputationMode() { @@ -57,7 +57,6 @@ void testGradientUserRoutine() { @Test void getAndSetVoltageBounds() { KnitroSolverParameters parametersKnitro = new KnitroSolverParameters(); - //TODO // default value assertEquals(0.5, parametersKnitro.getLowerVoltageBound()); assertEquals(1.5, parametersKnitro.getUpperVoltageBound()); @@ -67,12 +66,12 @@ void getAndSetVoltageBounds() { assertEquals(0.95, parametersKnitro.getLowerVoltageBound()); assertEquals(1.05, parametersKnitro.getUpperVoltageBound()); // wrong values - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setLowerVoltageBound(-Math.pow(10, -6))); - assertEquals("Realistic voltage bounds must strictly greater then 0", e.getMessage()); - IllegalArgumentException e2 = assertThrows(IllegalArgumentException.class, () -> parametersKnitro.setUpperVoltageBound(-2.0)); - assertEquals("Realistic voltage bounds must strictly greater then 0", e2.getMessage()); + 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 then lower bounds", e3.getMessage()); + assertEquals("Realistic voltage upper bounds must greater than lower bounds", e3.getMessage()); } @Test @@ -86,7 +85,7 @@ void testSetAndGetConvEpsPerEq() { assertEquals(Math.pow(10, -2), knitroLoadFlowParameters.getConvEps()); // wrong values - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> knitroLoadFlowParameters.setConvEps(Math.pow(-10, -3))); + 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()); 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 index 4c32f6d..9bc6883 100644 --- a/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverStoppingCriteriaTest.java +++ b/src/test/java/com/powsybl/openloadflow/knitro/solver/utils/KnitroSolverStoppingCriteriaTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2023, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/) + * 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/. @@ -21,8 +21,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.List; - import static com.powsybl.openloadflow.util.LoadFlowAssert.assertAngleEquals; import static com.powsybl.openloadflow.util.LoadFlowAssert.assertVoltageEquals; import static org.junit.jupiter.api.Assertions.assertSame; @@ -37,6 +35,10 @@ class KnitroSolverStoppingCriteriaTest { private LoadFlow.Runner loadFlowRunner; private LoadFlowParameters parameters; private OpenLoadFlowParameters parametersExt; + Bus b1; + Bus b2; + Bus b3; + Bus b4; @BeforeEach void setUp() { @@ -53,18 +55,11 @@ void setUp() { .setUseReactiveLimits(false); parameters.getExtension(OpenLoadFlowParameters.class) .setSvcVoltageMonitoring(false); - } - - // Builds 4 bus network with condenser and returns busList - List setUp4Bus() { - // ============= Building network ============= network = FourBusNetworkFactory.createWithCondenser(); - Bus b1 = network.getBusBreakerView().getBus("b1"); - Bus b4 = network.getBusBreakerView().getBus("b4"); - Bus b2 = network.getBusBreakerView().getBus("b2"); - Bus b3 = network.getBusBreakerView().getBus("b3"); - List busList = network.getBusView().getBusStream().toList(); - return busList; + b1 = network.getBusBreakerView().getBus("b1"); + b2 = network.getBusBreakerView().getBus("b2"); + b3 = network.getBusBreakerView().getBus("b3"); + b4 = network.getBusBreakerView().getBus("b4"); } @Test @@ -73,20 +68,18 @@ void testEffectOfConvEpsPerEq() { * Checks the effect of changing Knitro's parameter convEpsPerEq on precision and values, when running Knitro solver */ - List busList = setUp4Bus(); - // ============= 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, busList.get(0)); - assertAngleEquals(0, busList.get(0)); - assertVoltageEquals(0.983834, busList.get(1)); - assertAngleEquals(-9.490705, busList.get(1)); - assertVoltageEquals(0.983124, busList.get(2)); - assertAngleEquals(-13.178514, busList.get(2)); - assertVoltageEquals(1.0, busList.get(3)); - assertAngleEquals(-6.531907, busList.get(3)); + 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 @@ -97,14 +90,13 @@ void testEffectOfConvEpsPerEq() { assertSame(LoadFlowResult.ComponentResult.Status.CONVERGED, knitroResultLessPrecise.getComponentResults().get(0).getStatus()); assertTrue(knitroResultLessPrecise.isFullyConverged()); - assertVoltageEquals(1.0, busList.get(0)); - assertAngleEquals(0, busList.get(0)); - assertVoltageEquals(0.983834, busList.get(1)); - assertAngleEquals(-9.485945, busList.get(1)); - assertVoltageEquals(0.983124, busList.get(2)); - assertAngleEquals(-13.170002, busList.get(2)); - assertVoltageEquals(1.0, busList.get(3)); - assertAngleEquals(-6.530383, busList.get(3)); - + 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); } }