diff --git a/glsk/glsk-document-api/src/main/java/com/powsybl/glsk/api/util/converters/GlskPointScalableConverter.java b/glsk/glsk-document-api/src/main/java/com/powsybl/glsk/api/util/converters/GlskPointScalableConverter.java index 8e367ee8..727261c8 100644 --- a/glsk/glsk-document-api/src/main/java/com/powsybl/glsk/api/util/converters/GlskPointScalableConverter.java +++ b/glsk/glsk-document-api/src/main/java/com/powsybl/glsk/api/util/converters/GlskPointScalableConverter.java @@ -47,10 +47,13 @@ public static Scalable convert(Network network, GlskPoint glskPoint) { public static Scalable convert(Network network, List shiftKeys) { Objects.requireNonNull(shiftKeys); - List percentages = new ArrayList<>(); - List scalables = new ArrayList<>(); + + List shiftKeyScalables = new ArrayList<>(); + List shiftKeyPercentages = new ArrayList<>(); for (GlskShiftKey glskShiftKey : shiftKeys) { + List percentages = new ArrayList<>(); + List scalables = new ArrayList<>(); if (glskShiftKey.getBusinessType().equals("B42") && glskShiftKey.getRegisteredResourceArrayList().isEmpty()) { //B42 country convertCountryProportional(network, glskShiftKey, percentages, scalables); @@ -66,8 +69,17 @@ public static Scalable convert(Network network, List shiftKeys) { } else { throw new GlskException("In convert glskShiftKey business type not supported"); } + Double percentageSum = percentages.stream().mapToDouble(Double::doubleValue).sum(); + shiftKeyPercentages.add(percentageSum); + + // each scalable needs to have a sum of percentages equal to 100% + List normalizedPercentages = percentages.stream().map(p -> p * 100 / percentageSum).toList(); + // the limit of the scalables is the power they can reach, not how much they can be scaled by + Double currentPower = scalables.stream().mapToDouble(scalable -> scalable.getSteadyStatePower(network, 1, Scalable.ScalingConvention.GENERATOR)).sum(); + shiftKeyScalables.add(Scalable.proportional(normalizedPercentages, scalables, -Double.MAX_VALUE, currentPower + glskShiftKey.getMaximumShift())); } - return Scalable.proportional(percentages, scalables); // iterative must be set in scalingParameters during scale + + return Scalable.proportional(shiftKeyPercentages, shiftKeyScalables); // iterative must be set in scalingParameters during scale } private static void convertRemainingCapacity(Network network, GlskShiftKey glskShiftKey, List percentages, List scalables) { @@ -211,8 +223,7 @@ private static void convertExplicitProportional(Network network, GlskShiftKey gl percentages.add(100.0 * factor); // In case of global shift key limitation we will limit the generator proportionally to // its participation in the global proportional scalable - double maxGeneratorValue = NetworkUtil.pseudoTargetP(generator) + factor * glskShiftKey.getMaximumShift(); - scalables.add(Scalable.onGenerator(generator.getId(), -Double.MAX_VALUE, maxGeneratorValue)); + scalables.add(Scalable.onGenerator(generator.getId())); } } else if (glskShiftKey.getPsrType().equals("A05")) { LOGGER.debug("GLSK Type B42, not empty registered resources list --> (explicit/manual) proportional LSK"); diff --git a/glsk/glsk-document-cim/pom.xml b/glsk/glsk-document-cim/pom.xml index 810876fb..2c2b43a1 100644 --- a/glsk/glsk-document-cim/pom.xml +++ b/glsk/glsk-document-cim/pom.xml @@ -15,6 +15,7 @@ Model of GLSK according to CIM format with its importer. + ${project.groupId} powsybl-glsk-document-api diff --git a/glsk/glsk-document-cse/src/test/java/com/powsybl/glsk/cse/CseGlskDocumentImporterTest.java b/glsk/glsk-document-cse/src/test/java/com/powsybl/glsk/cse/CseGlskDocumentImporterTest.java index 021c1540..d4ccffdb 100644 --- a/glsk/glsk-document-cse/src/test/java/com/powsybl/glsk/cse/CseGlskDocumentImporterTest.java +++ b/glsk/glsk-document-cse/src/test/java/com/powsybl/glsk/cse/CseGlskDocumentImporterTest.java @@ -14,6 +14,7 @@ import com.powsybl.glsk.commons.GlskException; import com.powsybl.glsk.commons.ZonalData; import com.powsybl.iidm.modification.scalable.Scalable; +import com.powsybl.iidm.modification.scalable.ScalingParameters; import com.powsybl.iidm.network.Network; import org.apache.commons.lang3.NotImplementedException; import org.junit.jupiter.api.Test; @@ -311,6 +312,29 @@ void checkCseGlskDocumentImporterWithHybridGskAboveMaximumShift() { assertEquals(3500., network.getGenerator("FFR3AA1 _generator").getTargetP(), EPSILON); } + @Test + void checkCseGlskDocumentImporterWithHybridGskGeneratorSaturated() { + Network network = Network.read("testCase.xiidm", getClass().getResourceAsStream("/testCase.xiidm")); + + //max P is 9000: generator 2 should go up to 8750 when shifting by 1000 + network.getGenerator("FFR1AA1 _generator").setTargetP(8750.); + network.getGenerator("FFR2AA1 _generator").setTargetP(8000.); + + GlskDocument glskDocument = GlskDocumentImporters.importGlsk(getClass().getResourceAsStream("/testGlskHybrid.xml")); + Scalable meritOrderGskScalable = glskDocument.getZonalScalable(network).getData("10YCH-SWISSGRIDZ"); + + assertNotNull(meritOrderGskScalable); + assertEquals(8750., network.getGenerator("FFR1AA1 _generator").getTargetP(), EPSILON); + assertEquals(8000., network.getGenerator("FFR2AA1 _generator").getTargetP(), EPSILON); + assertEquals(3000., network.getGenerator("FFR3AA1 _generator").getTargetP(), EPSILON); + + ScalingParameters scalingParameters = new ScalingParameters().setPriority(ScalingParameters.Priority.RESPECT_OF_VOLUME_ASKED); + meritOrderGskScalable.scale(network, 1000., scalingParameters); + assertEquals(9000., network.getGenerator("FFR1AA1 _generator").getTargetP(), EPSILON); + assertEquals(8750., network.getGenerator("FFR2AA1 _generator").getTargetP(), EPSILON); + assertEquals(3000., network.getGenerator("FFR3AA1 _generator").getTargetP(), EPSILON); + } + @Test void checkCseGlskDocumentImporterWithHybridGskGoingDown() { Network network = Network.read("testCase.xiidm", getClass().getResourceAsStream("/testCase.xiidm"));