diff --git a/src/main/java/org/gridsuite/modification/modifications/GeneratorCreation.java b/src/main/java/org/gridsuite/modification/modifications/GeneratorCreation.java index 335e8a9a..c9c4a4df 100644 --- a/src/main/java/org/gridsuite/modification/modifications/GeneratorCreation.java +++ b/src/main/java/org/gridsuite/modification/modifications/GeneratorCreation.java @@ -69,6 +69,8 @@ public void check(Network network) throws NetworkModificationException { checkIsNotNegativeValue(errorMessage, modificationInfos.getTargetV(), CREATE_GENERATOR_ERROR, "Target Voltage"); checkIsPercentage(errorMessage, modificationInfos.getDroop(), CREATE_GENERATOR_ERROR, "Droop"); checkIsNotNegativeValue(errorMessage, modificationInfos.getRatedS(), CREATE_GENERATOR_ERROR, "Rated apparent power"); + checkPowerValues(errorMessage, modificationInfos.getMinP(), modificationInfos.getMaxP(), modificationInfos.getTargetP(), + modificationInfos.getPlannedActivePowerSetPoint(), CREATE_GENERATOR_ERROR); } @Override diff --git a/src/main/java/org/gridsuite/modification/modifications/GeneratorModification.java b/src/main/java/org/gridsuite/modification/modifications/GeneratorModification.java index 040e79e0..ad945354 100644 --- a/src/main/java/org/gridsuite/modification/modifications/GeneratorModification.java +++ b/src/main/java/org/gridsuite/modification/modifications/GeneratorModification.java @@ -72,6 +72,28 @@ public void check(Network network) throws NetworkModificationException { if (modificationInfos.getTargetV() != null) { checkIsNotNegativeValue(errorMessage, modificationInfos.getTargetV().getValue(), MODIFY_GENERATOR_ERROR, TARGET_VOLTAGE); } + checkPowerValues(errorMessage, generator); + } + + private void checkPowerValues(String errorMessage, Generator generator) { + GeneratorStartup generatorStartup = generator.getExtension(GeneratorStartup.class); + Double oldValue = generatorStartup != null && !Double.isNaN(generatorStartup.getPlannedActivePowerSetpoint()) + ? generatorStartup.getPlannedActivePowerSetpoint() : null; + double minP = modificationInfos.getMinP() != null ? modificationInfos.getMinP().getValue() : generator.getMinP(); + double maxP = modificationInfos.getMaxP() != null ? modificationInfos.getMaxP().getValue() : generator.getMaxP(); + double targetP = modificationInfos.getTargetP() != null ? modificationInfos.getTargetP().getValue() : generator.getTargetP(); + Double plannedActivePowerSetPoint = modificationInfos.getPlannedActivePowerSetPoint() != null ? + modificationInfos.getPlannedActivePowerSetPoint().applyModification(oldValue) : null; + + if (modificationInfos.getPlannedActivePowerSetPoint() != null) { + checkActivePowerValue(errorMessage, FIELD_PLANNED_ACTIVE_POWER_SET_POINT, plannedActivePowerSetPoint, minP, maxP, MODIFY_GENERATOR_ERROR); + } + if (modificationInfos.getMaxP() != null) { + checkMaximumActivePower(errorMessage, minP, targetP, plannedActivePowerSetPoint, maxP, MODIFY_GENERATOR_ERROR); + } + if (modificationInfos.getMinP() != null) { + checkMinimumActivePower(errorMessage, maxP, targetP, plannedActivePowerSetPoint, minP, MODIFY_GENERATOR_ERROR); + } } private void checkActivePowerZeroOrBetweenMinAndMaxActivePowerGenerator(GeneratorModificationInfos modificationInfos, Generator generator, NetworkModificationException.Type exceptionType, String errorMessage) { diff --git a/src/main/java/org/gridsuite/modification/modifications/byfilter/AbstractModificationByAssignment.java b/src/main/java/org/gridsuite/modification/modifications/byfilter/AbstractModificationByAssignment.java index f7732a15..6eb6e6fa 100644 --- a/src/main/java/org/gridsuite/modification/modifications/byfilter/AbstractModificationByAssignment.java +++ b/src/main/java/org/gridsuite/modification/modifications/byfilter/AbstractModificationByAssignment.java @@ -9,6 +9,7 @@ import com.powsybl.commons.report.ReportNode; import com.powsybl.commons.report.TypedValue; +import com.powsybl.iidm.network.Generator; import com.powsybl.iidm.network.Identifiable; import com.powsybl.iidm.network.IdentifiableType; import com.powsybl.iidm.network.Network; @@ -34,6 +35,7 @@ import static org.gridsuite.modification.dto.byfilter.equipmentfield.FieldUtils.getFieldValue; import static org.gridsuite.modification.dto.byfilter.equipmentfield.FieldUtils.setFieldValue; +import static org.gridsuite.modification.dto.byfilter.equipmentfield.GeneratorField.*; import static org.gridsuite.modification.utils.ModificationUtils.*; /** @@ -42,6 +44,10 @@ public abstract class AbstractModificationByAssignment extends AbstractModification { public static final String VALUE_KEY_FILTER_NAME = "filterName"; public static final String VALUE_KEY_FIELD_NAME = "fieldName"; + public static final String VALUE_KEY_FIELD_VALUE = "fieldValue"; + public static final String VALUE_KEY_MIN_VALUE = "minValue"; + public static final String VALUE_KEY_MAX_VALUE = "maxValue"; + public static final String VALUE_KEY_TARGET_VALUE = "targetValue"; public static final String VALUE_KEY_EQUIPMENT_NAME = "equipmentName"; public static final String VALUE_KEY_EQUIPMENT_TYPE = "equipmentType"; public static final String VALUE_KEY_EQUIPMENT_COUNT = "equipmentCount"; @@ -103,6 +109,25 @@ protected abstract boolean preCheckValue(Identifiable equipment, protected abstract String getNewValue(Identifiable equipment, AbstractAssignmentInfos abstractAssignmentInfos); + protected boolean checkGeneratorsPowerValues(Identifiable equipment, AbstractAssignmentInfos abstractAssignmentInfos, List reports) { + if (equipment.getType() == IdentifiableType.GENERATOR) { + Generator generator = (Generator) equipment; + if (abstractAssignmentInfos.getEditedField().equals(PLANNED_ACTIVE_POWER_SET_POINT.name())) { + return validateActivePowerValue(generator, FIELD_PLANNED_ACTIVE_POWER_SET_POINT, reports, Double.parseDouble(getNewValue(equipment, abstractAssignmentInfos))); + } else if (abstractAssignmentInfos.getEditedField().equals(MINIMUM_ACTIVE_POWER.name())) { + return validateMinimumActivePower(generator, reports, Double.parseDouble(getNewValue(equipment, abstractAssignmentInfos))); + } else if (abstractAssignmentInfos.getEditedField().equals(MAXIMUM_ACTIVE_POWER.name())) { + return validateMaximumActivePower(generator, reports, Double.parseDouble(getNewValue(equipment, abstractAssignmentInfos))); + } else if (abstractAssignmentInfos.getEditedField().equals(ACTIVE_POWER_SET_POINT.name())) { + double newValue = Double.parseDouble(getNewValue(equipment, abstractAssignmentInfos)); + if (newValue != 0) { // 0 is an exception to the rule + return validateActivePowerValue(generator, FIELD_ACTIVE_POWER_TARGET, reports, newValue); + } + } + } + return true; + } + protected String getOldValue(Identifiable equipment, AbstractAssignmentInfos abstractAssignmentInfos) { return getFieldValue(equipment, abstractAssignmentInfos.getEditedField()); } diff --git a/src/main/java/org/gridsuite/modification/modifications/byfilter/ByFormulaModification.java b/src/main/java/org/gridsuite/modification/modifications/byfilter/ByFormulaModification.java index e0f69fd0..d25b38be 100644 --- a/src/main/java/org/gridsuite/modification/modifications/byfilter/ByFormulaModification.java +++ b/src/main/java/org/gridsuite/modification/modifications/byfilter/ByFormulaModification.java @@ -91,6 +91,9 @@ protected boolean preCheckValue(Identifiable equipment, AbstractAssignmentInf .build()); return false; } + if (equipment.getType() == IdentifiableType.GENERATOR) { + return checkGeneratorsPowerValues(equipment, abstractAssignmentInfos, reports); + } return true; } diff --git a/src/main/java/org/gridsuite/modification/modifications/byfilter/ModificationByAssignment.java b/src/main/java/org/gridsuite/modification/modifications/byfilter/ModificationByAssignment.java index 97b5cdd4..deae7f9d 100644 --- a/src/main/java/org/gridsuite/modification/modifications/byfilter/ModificationByAssignment.java +++ b/src/main/java/org/gridsuite/modification/modifications/byfilter/ModificationByAssignment.java @@ -78,6 +78,9 @@ protected boolean isEquipmentEditable(Identifiable equipment, AbstractAssignm @Override protected boolean preCheckValue(Identifiable equipment, AbstractAssignmentInfos abstractAssignmentInfos, List reports, List notEditableEquipments) { + if (equipment.getType() == IdentifiableType.GENERATOR) { + return checkGeneratorsPowerValues(equipment, abstractAssignmentInfos, reports); + } return true; } diff --git a/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java b/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java index 4c39ff2c..0d49e487 100644 --- a/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java +++ b/src/main/java/org/gridsuite/modification/utils/ModificationUtils.java @@ -37,6 +37,7 @@ import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.SIDE1; import static org.gridsuite.modification.dto.OperationalLimitsGroupInfos.Applicability.SIDE2; import static org.gridsuite.modification.modifications.AbstractBranchModification.*; +import static org.gridsuite.modification.modifications.byfilter.AbstractModificationByAssignment.*; /** * @author Slimane Amar @@ -71,6 +72,10 @@ public final class ModificationUtils { private static final String COULD_NOT_ACTION_EQUIPMENT_ON_SIDE = COULD_NOT_ACTION_EQUIPMENT + " on side %s"; public static final String CONNECT = "connect"; public static final String DISCONNECT = "disconnect"; + public static final String FIELD_MAX_ACTIVE_POWER = "Maximum active power"; + public static final String FIELD_MIN_ACTIVE_POWER = "Minimum active power"; + public static final String FIELD_PLANNED_ACTIVE_POWER_SET_POINT = "Planned active power set point"; + public static final String FIELD_ACTIVE_POWER_TARGET = "Active power target"; public static String applicabilityToString(OperationalLimitsGroupInfos.Applicability applicability) { return switch (applicability) { @@ -1921,6 +1926,110 @@ public static void checkLimitsGroupExist(String errorMessage, String limitsGroup } } + public static void checkActivePowerValue(String errorMessage, String fieldName, double newValue, double minP, double maxP, NetworkModificationException.Type exceptionType) throws NetworkModificationException { + if (newValue > maxP || newValue < minP) { + String message = String.format("Invalid value %.2f field %s should be within interval [%.2f; %.2f]", newValue, fieldName, minP, maxP); + throw new NetworkModificationException(exceptionType, errorMessage + message); + } + } + + public static void checkPowerValues(String errorMessage, double minP, double maxP, double targetP, Double plannedActivePowerSetPoint, NetworkModificationException.Type exceptionType) throws NetworkModificationException { + if (targetP != 0) { // exception for the rule minP <= targetP <= maxP + checkActivePowerValue(errorMessage, FIELD_ACTIVE_POWER_TARGET, targetP, minP, maxP, exceptionType); + } + if (plannedActivePowerSetPoint != null) { + checkActivePowerValue(errorMessage, FIELD_PLANNED_ACTIVE_POWER_SET_POINT, plannedActivePowerSetPoint, minP, maxP, exceptionType); + } + checkMinimumActivePower(errorMessage, maxP, targetP, plannedActivePowerSetPoint, minP, exceptionType); + checkMaximumActivePower(errorMessage, minP, targetP, plannedActivePowerSetPoint, maxP, exceptionType); + } + + public static void checkMinimumActivePower(String errorMessage, double maxP, double targetP, Double pImp, double newValue, NetworkModificationException.Type exceptionType) throws NetworkModificationException { + // targetP = 0 is an exception to the rule + double pMin = targetP != 0 ? Math.min(targetP, maxP) : maxP; + if (pImp != null) { + pMin = Math.min(pMin, pImp); + } + if (pMin < newValue) { + throw new NetworkModificationException(exceptionType, errorMessage + String.format("Invalid value %.2f of field %s should be be smaller or equal to %.2f", newValue, FIELD_MIN_ACTIVE_POWER, pMin)); + } + } + + public static void checkMaximumActivePower(String errorMessage, double minP, double targetP, Double plannedActivePowerSetPoint, double newValue, NetworkModificationException.Type exceptionType) throws NetworkModificationException { + // targetP = 0 is an exception to the rule + double pMax = targetP != 0 ? Math.max(targetP, minP) : minP; + if (plannedActivePowerSetPoint != null) { + pMax = Math.max(pMax, plannedActivePowerSetPoint); + } + if (pMax > newValue) { + throw new NetworkModificationException(exceptionType, errorMessage + String.format("Invalid value %.2f of field %s should be be greater or equal to %.2f", newValue, FIELD_MAX_ACTIVE_POWER, pMax)); + } + } + + public static boolean validateMinimumActivePower(Generator generator, List reports, double newValue) { + GeneratorStartup generatorStartup = generator.getExtension(GeneratorStartup.class); + Double plannedActivePowerSetPoint = generatorStartup != null && !Double.isNaN(generatorStartup.getPlannedActivePowerSetpoint()) ? generatorStartup.getPlannedActivePowerSetpoint() : null; + + // targetP = 0 is an exception to the rule + double minP = generator.getTargetP() != 0 ? Math.min(generator.getTargetP(), generator.getMaxP()) : generator.getMaxP(); + if (plannedActivePowerSetPoint != null) { + minP = Math.min(minP, plannedActivePowerSetPoint); + } + + if (minP < newValue) { + reports.add(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.generator.ValueShouldBeSmallerThan") + .withUntypedValue(VALUE_KEY_EQUIPMENT_NAME, generator.getId()) + .withUntypedValue(VALUE_KEY_FIELD_NAME, FIELD_MIN_ACTIVE_POWER) + .withUntypedValue(VALUE_KEY_FIELD_VALUE, newValue) + .withUntypedValue(VALUE_KEY_TARGET_VALUE, minP) + .withSeverity(TypedValue.WARN_SEVERITY) + .build()); + return false; + } + return true; + } + + public static boolean validateMaximumActivePower(Generator generator, List reports, double newValue) { + GeneratorStartup generatorStartup = generator.getExtension(GeneratorStartup.class); + Double pImp = generatorStartup != null && !Double.isNaN(generatorStartup.getPlannedActivePowerSetpoint()) ? generatorStartup.getPlannedActivePowerSetpoint() : null; + + // targetP = 0 is an exception to the rule + double maxP = generator.getTargetP() != 0 ? Math.max(generator.getTargetP(), generator.getMinP()) : generator.getMinP(); + if (pImp != null) { + maxP = Math.min(maxP, pImp); + } + + if (newValue < maxP) { + reports.add(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.generator.ValueShouldBeGreaterThan") + .withUntypedValue(VALUE_KEY_EQUIPMENT_NAME, generator.getId()) + .withUntypedValue(VALUE_KEY_FIELD_NAME, FIELD_MAX_ACTIVE_POWER) + .withUntypedValue(VALUE_KEY_FIELD_VALUE, newValue) + .withUntypedValue(VALUE_KEY_TARGET_VALUE, maxP) + .withSeverity(TypedValue.WARN_SEVERITY) + .build()); + return false; + } + return true; + } + + public static boolean validateActivePowerValue(Generator generator, String fieldName, List reports, double newValue) { + if (newValue > generator.getMaxP() || newValue < generator.getMinP()) { + reports.add(ReportNode.newRootReportNode() + .withMessageTemplate("network.modification.generator.ValueShouldBeWithinInterval") + .withUntypedValue(VALUE_KEY_EQUIPMENT_NAME, generator.getId()) + .withUntypedValue(VALUE_KEY_FIELD_NAME, fieldName) + .withUntypedValue(VALUE_KEY_FIELD_VALUE, newValue) + .withUntypedValue(VALUE_KEY_MIN_VALUE, generator.getMinP()) + .withUntypedValue(VALUE_KEY_MAX_VALUE, generator.getMaxP()) + .withSeverity(TypedValue.WARN_SEVERITY) + .build()); + return false; + } + return true; + } + public static List getOperationalLimitsGroupsOnSide(List operationalLimitsGroupInfos, OperationalLimitsGroupInfos.Applicability applicability) { if (operationalLimitsGroupInfos == null || operationalLimitsGroupInfos.isEmpty()) { diff --git a/src/main/resources/org/gridsuite/modification/reports.properties b/src/main/resources/org/gridsuite/modification/reports.properties index fc3f9203..99533e6c 100644 --- a/src/main/resources/org/gridsuite/modification/reports.properties +++ b/src/main/resources/org/gridsuite/modification/reports.properties @@ -169,6 +169,9 @@ network.modification.generatorNotFound.generatorsFrequencyReserve = Frequency re network.modification.generatorNotFound.generatorsWithFixedSupply = Generators with fixed active power: Cannot find generator ${notFoundGeneratorId} in filter ${filterName} network.modification.generatorNotFound.generatorsWithoutOutage = Generators without outage simulation: Cannot find generator ${notFoundGeneratorId} in filter ${filterName} network.modification.generatorScaling = Generator scaling +network.modification.generator.ValueShouldBeGreaterThan = Generator ${equipmentName} : Invalid value ${fieldValue} for field ${fieldName}. Value should be greater or equal to ${targetValue}. +network.modification.generator.ValueShouldBeSmallerThan = Generator ${equipmentName} : Invalid value ${fieldValue} for field ${fieldName}. Value should be smaller or equal to ${targetValue}. +network.modification.generator.ValueShouldBeWithinInterval = Generator ${equipmentName} : Invalid value ${fieldValue} for field ${fieldName}. Value should be within interval [${minValue}; ${maxValue}]. network.modification.groovyScript = Apply groovy script network.modification.groovyScriptApplied = Groovy script applied network.modification.invalidFilters = ${errorType}: There is no valid equipment ID among the provided filter(s) diff --git a/src/test/java/org/gridsuite/modification/modifications/byfilter/assignment/GeneratorModificationByAssignmentTest.java b/src/test/java/org/gridsuite/modification/modifications/byfilter/assignment/GeneratorModificationByAssignmentTest.java index 954f00aa..145c0a33 100644 --- a/src/test/java/org/gridsuite/modification/modifications/byfilter/assignment/GeneratorModificationByAssignmentTest.java +++ b/src/test/java/org/gridsuite/modification/modifications/byfilter/assignment/GeneratorModificationByAssignmentTest.java @@ -88,7 +88,7 @@ protected void createEquipments() { getNetwork().getGenerator(GENERATOR_ID_2) .setTargetP(200) .setMaxP(2000) - .setMinP(50) + .setMinP(10) .setTargetV(10) .setTargetQ(20) .newExtension(GeneratorStartupAdder.class) @@ -243,7 +243,7 @@ protected List> getAssignmentInfos() { DoubleAssignmentInfos assignmentInfos10 = DoubleAssignmentInfos.builder() .editedField(GeneratorField.MAXIMUM_ACTIVE_POWER.name()) - .value(50.) + .value(300.) .filters(List.of(filter1, filter2, filter3, filter4, filter5)) .build(); @@ -338,7 +338,7 @@ protected void assertAfterNetworkModificationApplication() { assertEquals(0.1, generatorStartup1.getPlannedOutageRate(), 0); assertEquals(0.05, generatorStartup1.getForcedOutageRate(), 0); assertEquals(10, generatorStartup1.getPlannedActivePowerSetpoint(), 0); - assertEquals(50, generator1.getMaxP(), 0); + assertEquals(300., generator1.getMaxP(), 0); assertEquals(2, generator1.getMinP(), 0); assertTrue(generator1.isVoltageRegulatorOn()); ActivePowerControl activePowerControl1 = generator1.getExtension(ActivePowerControl.class); @@ -353,7 +353,7 @@ protected void assertAfterNetworkModificationApplication() { assertEquals(0.1, generatorStartup2.getPlannedOutageRate(), 0); assertEquals(0.05, generatorStartup2.getForcedOutageRate(), 0); assertEquals(10, generatorStartup2.getPlannedActivePowerSetpoint(), 0); - assertEquals(50, generator2.getMaxP(), 0); + assertEquals(300., generator2.getMaxP(), 0); assertEquals(2, generator2.getMinP(), 0); Generator generator3 = getNetwork().getGenerator(GENERATOR_ID_3); @@ -362,7 +362,7 @@ protected void assertAfterNetworkModificationApplication() { assertEquals(300, generator3.getTargetP(), 0); assertEquals(0.2, generatorShortCircuit3.getDirectTransX(), 0); assertEquals(0.3, generatorShortCircuit3.getStepUpTransformerX(), 0); - assertEquals(50, generator3.getMaxP(), 0); + assertEquals(300., generator3.getMaxP(), 0); assertEquals(2, generator3.getMinP(), 0); Generator generator4 = getNetwork().getGenerator(GENERATOR_ID_4); @@ -371,19 +371,20 @@ protected void assertAfterNetworkModificationApplication() { assertEquals(0.2, generatorShortCircuit4.getDirectTransX(), 0); assertEquals(0.3, generatorShortCircuit4.getStepUpTransformerX(), 0); assertEquals(400, generator4.getTargetP(), 0); - assertEquals(50, generator4.getMaxP(), 0); + //targetP is 400 MaxP won't change + assertEquals(700.0, generator4.getMaxP(), 0); assertEquals(2, generator4.getMinP(), 0); Generator generator5 = getNetwork().getGenerator(GENERATOR_ID_5); ActivePowerControl activePowerControl5 = generator5.getExtension(ActivePowerControl.class); assertNotNull(activePowerControl5); - assertEquals(50, generator5.getMaxP(), 0); + assertEquals(300., generator5.getMaxP(), 0); assertEquals(2, activePowerControl5.getDroop(), 0); Generator generator6 = getNetwork().getGenerator(GENERATOR_ID_6); ActivePowerControl activePowerControl6 = generator6.getExtension(ActivePowerControl.class); assertNotNull(activePowerControl6); - assertEquals(50, generator6.getMaxP(), 0); + assertEquals(300., generator6.getMaxP(), 0); assertEquals(2, activePowerControl6.getDroop(), 0); Generator generator7 = getNetwork().getGenerator(GENERATOR_ID_7); diff --git a/src/test/java/org/gridsuite/modification/modifications/byfilter/formula/GeneratorByFormulaModificationTest.java b/src/test/java/org/gridsuite/modification/modifications/byfilter/formula/GeneratorByFormulaModificationTest.java index a2a59c71..aa1cee4a 100644 --- a/src/test/java/org/gridsuite/modification/modifications/byfilter/formula/GeneratorByFormulaModificationTest.java +++ b/src/test/java/org/gridsuite/modification/modifications/byfilter/formula/GeneratorByFormulaModificationTest.java @@ -282,7 +282,8 @@ protected void assertAfterNetworkModificationApplication() { assertEquals(0.055, generatorStartup2.getForcedOutageRate(), 0); assertEquals(50, generatorStartup2.getPlannedActivePowerSetpoint(), 0); assertEquals(2002, generator2.getMaxP(), 0); - assertEquals(100, generator2.getMinP(), 0); + //value doesn't change anymore since MinP value can't be smaller than plannedActivePowerSetpoint + assertEquals(50, generator2.getMinP(), 0); Generator generator3 = getNetwork().getGenerator(GENERATOR_ID_3); GeneratorShortCircuit generatorShortCircuit3 = generator3.getExtension(GeneratorShortCircuit.class);