From c2b5b442ae4116a7484e55f21371db574fb746d5 Mon Sep 17 00:00:00 2001 From: YenguiSeddik <101111441+YenguiSeddik@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:41:46 +0100 Subject: [PATCH] Add by formula modification (#359) Signed-off-by: Seddik Yengui --- .../modification/server/ModificationType.java | 3 +- .../server/NetworkModificationException.java | 3 +- .../dto/ByFormulaModificationInfos.java | 63 +++ .../server/dto/ModificationInfos.java | 3 +- .../server/dto/formula/FormulaInfos.java | 53 ++ .../server/dto/formula/Operator.java | 20 + .../dto/formula/ReferenceFieldOrValue.java | 62 +++ .../formula/equipmentfield/BatteryField.java | 49 ++ .../equipmentfield/GeneratorField.java | 149 +++++ .../ByFormulaModificationEntity.java | 72 +++ .../equipment/modification/FormulaEntity.java | 90 +++ .../modification/VariationFilterEntity.java | 3 +- .../modifications/ByFormulaModification.java | 187 +++++++ .../modifications/ModificationUtils.java | 15 + .../server/service/FilterService.java | 14 + .../changesets/changelog_20231108T124514Z.xml | 49 ++ .../db/changelog/db.changelog-master.yaml | 3 + .../AbstractByFormulaModificationTest.java | 169 ++++++ .../BatteryByFormulaModificationTest.java | 308 +++++++++++ .../GeneratorByFormulaModificationTest.java | 519 ++++++++++++++++++ 20 files changed, 1830 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/gridsuite/modification/server/dto/ByFormulaModificationInfos.java create mode 100644 src/main/java/org/gridsuite/modification/server/dto/formula/FormulaInfos.java create mode 100644 src/main/java/org/gridsuite/modification/server/dto/formula/Operator.java create mode 100644 src/main/java/org/gridsuite/modification/server/dto/formula/ReferenceFieldOrValue.java create mode 100644 src/main/java/org/gridsuite/modification/server/dto/formula/equipmentfield/BatteryField.java create mode 100644 src/main/java/org/gridsuite/modification/server/dto/formula/equipmentfield/GeneratorField.java create mode 100644 src/main/java/org/gridsuite/modification/server/entities/equipment/modification/ByFormulaModificationEntity.java create mode 100644 src/main/java/org/gridsuite/modification/server/entities/equipment/modification/FormulaEntity.java create mode 100644 src/main/java/org/gridsuite/modification/server/modifications/ByFormulaModification.java create mode 100644 src/main/resources/db/changelog/changesets/changelog_20231108T124514Z.xml create mode 100644 src/test/java/org/gridsuite/modification/server/modifications/AbstractByFormulaModificationTest.java create mode 100644 src/test/java/org/gridsuite/modification/server/modifications/BatteryByFormulaModificationTest.java create mode 100644 src/test/java/org/gridsuite/modification/server/modifications/GeneratorByFormulaModificationTest.java diff --git a/src/main/java/org/gridsuite/modification/server/ModificationType.java b/src/main/java/org/gridsuite/modification/server/ModificationType.java index f4fa07c09..bf40eb271 100644 --- a/src/main/java/org/gridsuite/modification/server/ModificationType.java +++ b/src/main/java/org/gridsuite/modification/server/ModificationType.java @@ -46,7 +46,8 @@ public enum ModificationType { VOLTAGE_INIT_MODIFICATION(PreloadingStrategy.COLLECTION), VSC_CREATION(PreloadingStrategy.NONE), CONVERTER_STATION_CREATION(PreloadingStrategy.NONE), - TABULAR_MODIFICATION(PreloadingStrategy.COLLECTION); + TABULAR_MODIFICATION(PreloadingStrategy.COLLECTION), + BY_FORMULA_MODIFICATION(PreloadingStrategy.COLLECTION); private final PreloadingStrategy strategy; diff --git a/src/main/java/org/gridsuite/modification/server/NetworkModificationException.java b/src/main/java/org/gridsuite/modification/server/NetworkModificationException.java index 4e8d14780..b01eabb27 100644 --- a/src/main/java/org/gridsuite/modification/server/NetworkModificationException.java +++ b/src/main/java/org/gridsuite/modification/server/NetworkModificationException.java @@ -105,7 +105,8 @@ public enum Type { CREATE_VSC_ERROR(HttpStatus.INTERNAL_SERVER_ERROR), HVDC_LINE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST), VSC_CONVERTER_STATION_NOT_FOUND(HttpStatus.NOT_FOUND), - CREATE_CONVERTER_STATION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR); + CREATE_CONVERTER_STATION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR), + BY_FORMULA_MODIFICATION_ERROR(HttpStatus.INTERNAL_SERVER_ERROR); public final HttpStatus status; private final String message; diff --git a/src/main/java/org/gridsuite/modification/server/dto/ByFormulaModificationInfos.java b/src/main/java/org/gridsuite/modification/server/dto/ByFormulaModificationInfos.java new file mode 100644 index 000000000..9d21feb87 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/dto/ByFormulaModificationInfos.java @@ -0,0 +1,63 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.dto; + +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.powsybl.commons.reporter.Reporter; +import com.powsybl.commons.reporter.ReporterModel; +import com.powsybl.iidm.network.IdentifiableType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; +import org.gridsuite.modification.server.ModificationType; +import org.gridsuite.modification.server.dto.annotation.ModificationErrorTypeName; +import org.gridsuite.modification.server.dto.formula.FormulaInfos; +import org.gridsuite.modification.server.entities.equipment.modification.ByFormulaModificationEntity; +import org.gridsuite.modification.server.modifications.ByFormulaModification; + +import java.util.List; + +/** + * @author Seddik Yengui + */ + +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@JsonTypeName("BY_FORMULA_MODIFICATION") +@ModificationErrorTypeName("BY_FORMULA_MODIFICATION_ERROR") +@ToString(callSuper = true) +@Schema(description = "Modification by formula") +public class ByFormulaModificationInfos extends ModificationInfos { + @Schema(description = "Identifiable type") + private IdentifiableType identifiableType; + + @Schema(description = "list of formulas") + private List formulaInfosList; + + @Override + public ByFormulaModificationEntity toEntity() { + return new ByFormulaModificationEntity(this); + } + + @Override + public ByFormulaModification toModification() { + return new ByFormulaModification(this); + } + + @Override + public Reporter createSubReporter(ReporterModel reporter) { + return reporter.createSubReporter(ModificationType.BY_FORMULA_MODIFICATION.name(), "By formula modification"); + } +} diff --git a/src/main/java/org/gridsuite/modification/server/dto/ModificationInfos.java b/src/main/java/org/gridsuite/modification/server/dto/ModificationInfos.java index f011b12bd..15abed468 100644 --- a/src/main/java/org/gridsuite/modification/server/dto/ModificationInfos.java +++ b/src/main/java/org/gridsuite/modification/server/dto/ModificationInfos.java @@ -66,7 +66,8 @@ @JsonSubTypes.Type(value = VoltageInitModificationInfos.class), @JsonSubTypes.Type(value = VscCreationInfos.class), @JsonSubTypes.Type(value = ConverterStationCreationInfos.class), - @JsonSubTypes.Type(value = TabularModificationInfos.class) + @JsonSubTypes.Type(value = TabularModificationInfos.class), + @JsonSubTypes.Type(value = ByFormulaModificationInfos.class) }) @SuperBuilder @NoArgsConstructor diff --git a/src/main/java/org/gridsuite/modification/server/dto/formula/FormulaInfos.java b/src/main/java/org/gridsuite/modification/server/dto/formula/FormulaInfos.java new file mode 100644 index 000000000..736556a4d --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/dto/formula/FormulaInfos.java @@ -0,0 +1,53 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.dto.formula; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.gridsuite.modification.server.dto.FilterInfos; +import org.gridsuite.modification.server.entities.equipment.modification.FormulaEntity; + +import java.util.List; +import java.util.UUID; + +/** + * @author Seddik Yengui + */ + +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class FormulaInfos { + @Schema(description = "id") + private UUID id; + + @Schema(description = "List of filters") + private List filters; + + @Schema(description = "Edited field") + private String editedField; + + @Schema(description = "First reference field or value") + private ReferenceFieldOrValue fieldOrValue1; + + @Schema(description = "Second reference field or value") + private ReferenceFieldOrValue fieldOrValue2; + + @Schema(description = "Operator") + private Operator operator; + + public FormulaEntity toEntity() { + return new FormulaEntity(this); + } +} diff --git a/src/main/java/org/gridsuite/modification/server/dto/formula/Operator.java b/src/main/java/org/gridsuite/modification/server/dto/formula/Operator.java new file mode 100644 index 000000000..d00f5e65d --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/dto/formula/Operator.java @@ -0,0 +1,20 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.dto.formula; + +/** + * @author Seddik Yengui + */ + +public enum Operator { + ADDITION, + SUBTRACTION, + MULTIPLICATION, + DIVISION, + PERCENTAGE +} diff --git a/src/main/java/org/gridsuite/modification/server/dto/formula/ReferenceFieldOrValue.java b/src/main/java/org/gridsuite/modification/server/dto/formula/ReferenceFieldOrValue.java new file mode 100644 index 000000000..018d3fe9b --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/dto/formula/ReferenceFieldOrValue.java @@ -0,0 +1,62 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.dto.formula; + +import com.powsybl.iidm.network.Battery; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.IdentifiableType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.gridsuite.modification.server.NetworkModificationException; +import org.gridsuite.modification.server.dto.formula.equipmentfield.BatteryField; +import org.gridsuite.modification.server.dto.formula.equipmentfield.GeneratorField; + +/** + * @author Seddik Yengui + */ + +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ReferenceFieldOrValue { + private String equipmentField; + + private Double value; + + public Double getRefOrValue(Identifiable identifiable) { + if (value == null && equipmentField == null) { + throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, + "There is no value or reference to any of the equipment fields"); + } + + if (value != null && !Double.isNaN(value)) { + return value; + } + + IdentifiableType identifiableType = identifiable.getType(); + Double referenceValue = switch (identifiableType) { + case GENERATOR -> GeneratorField.getReferenceValue((Generator) identifiable, equipmentField); + case BATTERY -> BatteryField.getReferenceValue((Battery) identifiable, equipmentField); + default -> throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, + String.format("Unsupported equipment type : %s", identifiableType.name())); + }; + + if (referenceValue == null) { + throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, + String.format("value of %s is null", equipmentField)); + } + + return referenceValue; + } +} diff --git a/src/main/java/org/gridsuite/modification/server/dto/formula/equipmentfield/BatteryField.java b/src/main/java/org/gridsuite/modification/server/dto/formula/equipmentfield/BatteryField.java new file mode 100644 index 000000000..079dd8a12 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/dto/formula/equipmentfield/BatteryField.java @@ -0,0 +1,49 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.dto.formula.equipmentfield; + +import com.powsybl.iidm.network.Battery; +import com.powsybl.iidm.network.extensions.ActivePowerControl; +import com.powsybl.iidm.network.extensions.ActivePowerControlAdder; + +/** + * @author Seddik Yengui + */ + +public enum BatteryField { + MINIMUM_ACTIVE_POWER, + MAXIMUM_ACTIVE_POWER, + ACTIVE_POWER_SET_POINT, + REACTIVE_POWER_SET_POINT, + DROOP; + + public static Double getReferenceValue(Battery battery, String batteryField) { + ActivePowerControl activePowerControl = battery.getExtension(ActivePowerControl.class); + BatteryField field = BatteryField.valueOf(batteryField); + return switch (field) { + case MINIMUM_ACTIVE_POWER -> battery.getMinP(); + case MAXIMUM_ACTIVE_POWER -> battery.getMaxP(); + case ACTIVE_POWER_SET_POINT -> battery.getTargetP(); + case REACTIVE_POWER_SET_POINT -> battery.getTargetQ(); + case DROOP -> activePowerControl != null ? activePowerControl.getDroop() : null; + }; + } + + public static void setNewValue(Battery battery, String batteryField, Double newValue) { + BatteryField field = BatteryField.valueOf(batteryField); + switch (field) { + case MINIMUM_ACTIVE_POWER -> battery.setMinP(newValue); + case MAXIMUM_ACTIVE_POWER -> battery.setMaxP(newValue); + case ACTIVE_POWER_SET_POINT -> battery.setTargetP(newValue); + case REACTIVE_POWER_SET_POINT -> battery.setTargetQ(newValue); + case DROOP -> battery.newExtension(ActivePowerControlAdder.class) + .withDroop(newValue) + .add(); + } + } +} diff --git a/src/main/java/org/gridsuite/modification/server/dto/formula/equipmentfield/GeneratorField.java b/src/main/java/org/gridsuite/modification/server/dto/formula/equipmentfield/GeneratorField.java new file mode 100644 index 000000000..7c864cbfd --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/dto/formula/equipmentfield/GeneratorField.java @@ -0,0 +1,149 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.dto.formula.equipmentfield; + +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.extensions.ActivePowerControl; +import com.powsybl.iidm.network.extensions.ActivePowerControlAdder; +import com.powsybl.iidm.network.extensions.CoordinatedReactiveControl; +import com.powsybl.iidm.network.extensions.CoordinatedReactiveControlAdder; +import com.powsybl.iidm.network.extensions.GeneratorShortCircuit; +import com.powsybl.iidm.network.extensions.GeneratorShortCircuitAdder; +import com.powsybl.iidm.network.extensions.GeneratorStartup; +import com.powsybl.iidm.network.extensions.GeneratorStartupAdder; + +/** + * @author Seddik Yengui + */ + +public enum GeneratorField { + MINIMUM_ACTIVE_POWER, + MAXIMUM_ACTIVE_POWER, + RATED_NOMINAL_POWER, + ACTIVE_POWER_SET_POINT, + REACTIVE_POWER_SET_POINT, + VOLTAGE_SET_POINT, + PLANNED_ACTIVE_POWER_SET_POINT, + MARGINAL_COST, + PLANNED_OUTAGE_RATE, + FORCED_OUTAGE_RATE, + DROOP, + TRANSIENT_REACTANCE, + STEP_UP_TRANSFORMER_REACTANCE, + Q_PERCENT; + + public static Double getReferenceValue(Generator generator, String generatorField) { + ActivePowerControl activePowerControl = generator.getExtension(ActivePowerControl.class); + GeneratorStartup generatorStartup = generator.getExtension(GeneratorStartup.class); + GeneratorShortCircuit generatorShortCircuit = generator.getExtension(GeneratorShortCircuit.class); + CoordinatedReactiveControl coordinatedReactiveControl = generator.getExtension(CoordinatedReactiveControl.class); + GeneratorField field = GeneratorField.valueOf(generatorField); + return switch (field) { + case MAXIMUM_ACTIVE_POWER -> generator.getMaxP(); + case MINIMUM_ACTIVE_POWER -> generator.getMinP(); + case ACTIVE_POWER_SET_POINT -> generator.getTargetP(); + case RATED_NOMINAL_POWER -> generator.getRatedS(); + case REACTIVE_POWER_SET_POINT -> generator.getTargetQ(); + case VOLTAGE_SET_POINT -> generator.getTargetV(); + case PLANNED_ACTIVE_POWER_SET_POINT -> generatorStartup != null ? generatorStartup.getPlannedActivePowerSetpoint() : null; + case MARGINAL_COST -> generatorStartup != null ? generatorStartup.getMarginalCost() : null; + case PLANNED_OUTAGE_RATE -> generatorStartup != null ? generatorStartup.getPlannedOutageRate() : null; + case FORCED_OUTAGE_RATE -> generatorStartup != null ? generatorStartup.getForcedOutageRate() : null; + case DROOP -> activePowerControl != null ? activePowerControl.getDroop() : null; + case TRANSIENT_REACTANCE -> generatorShortCircuit != null ? generatorShortCircuit.getDirectTransX() : null; + case STEP_UP_TRANSFORMER_REACTANCE -> generatorShortCircuit != null ? generatorShortCircuit.getStepUpTransformerX() : null; + case Q_PERCENT -> coordinatedReactiveControl != null ? coordinatedReactiveControl.getQPercent() : null; + }; + } + + public static void setNewValue(Generator generator, String generatorField, Double newValue) { + if (!Double.isNaN(newValue)) { + GeneratorStartup generatorStartup = generator.getExtension(GeneratorStartup.class); + GeneratorShortCircuit generatorShortCircuit = generator.getExtension(GeneratorShortCircuit.class); + GeneratorField field = GeneratorField.valueOf(generatorField); + switch (field) { + case MAXIMUM_ACTIVE_POWER -> generator.setMaxP(newValue); + case MINIMUM_ACTIVE_POWER -> generator.setMinP(newValue); + case ACTIVE_POWER_SET_POINT -> generator.setTargetP(newValue); + case RATED_NOMINAL_POWER -> generator.setRatedS(newValue); + case REACTIVE_POWER_SET_POINT -> generator.setTargetQ(newValue); + case VOLTAGE_SET_POINT -> generator.setTargetV(newValue); + case PLANNED_ACTIVE_POWER_SET_POINT -> { + if (generatorStartup == null) { + generator.newExtension(GeneratorStartupAdder.class) + .withPlannedActivePowerSetpoint(newValue) + .add(); + } else { + generator.newExtension(GeneratorStartupAdder.class) + .withMarginalCost(generatorStartup.getMarginalCost()) + .withPlannedActivePowerSetpoint(newValue) + .withPlannedOutageRate(generatorStartup.getPlannedOutageRate()) + .withForcedOutageRate(generatorStartup.getForcedOutageRate()) + .add(); + } + } + case MARGINAL_COST -> { + if (generatorStartup == null) { + generator.newExtension(GeneratorStartupAdder.class) + .withMarginalCost(newValue) + .add(); + } else { + generator.newExtension(GeneratorStartupAdder.class) + .withMarginalCost(newValue) + .withPlannedActivePowerSetpoint(generatorStartup.getPlannedActivePowerSetpoint()) + .withPlannedOutageRate(generatorStartup.getPlannedOutageRate()) + .withForcedOutageRate(generatorStartup.getForcedOutageRate()) + .add(); + } + } + case PLANNED_OUTAGE_RATE -> { + if (generatorStartup == null) { + generator.newExtension(GeneratorStartupAdder.class) + .withPlannedOutageRate(newValue) + .add(); + } else { + generator.newExtension(GeneratorStartupAdder.class) + .withMarginalCost(generatorStartup.getMarginalCost()) + .withPlannedActivePowerSetpoint(generatorStartup.getPlannedActivePowerSetpoint()) + .withPlannedOutageRate(newValue) + .withForcedOutageRate(generatorStartup.getForcedOutageRate()) + .add(); + } + } + case FORCED_OUTAGE_RATE -> { + if (generatorStartup == null) { + generator.newExtension(GeneratorStartupAdder.class) + .withForcedOutageRate(newValue) + .add(); + } else { + generator.newExtension(GeneratorStartupAdder.class) + .withMarginalCost(generatorStartup.getMarginalCost()) + .withPlannedActivePowerSetpoint(generatorStartup.getPlannedActivePowerSetpoint()) + .withPlannedOutageRate(generatorStartup.getForcedOutageRate()) + .withForcedOutageRate(newValue) + .add(); + } + } + case DROOP -> generator.newExtension(ActivePowerControlAdder.class) + .withDroop(newValue) + .add(); + case TRANSIENT_REACTANCE -> generator.newExtension(GeneratorShortCircuitAdder.class) + .withDirectTransX(newValue) + .withStepUpTransformerX(generatorShortCircuit == null ? Double.NaN : generatorShortCircuit.getStepUpTransformerX()) + .add(); + case STEP_UP_TRANSFORMER_REACTANCE -> generator.newExtension(GeneratorShortCircuitAdder.class) + .withDirectTransX(generatorShortCircuit == null ? 0.0D : generatorShortCircuit.getDirectTransX()) + .withStepUpTransformerX(newValue) + .add(); + case Q_PERCENT -> generator.newExtension(CoordinatedReactiveControlAdder.class) + .withQPercent(newValue) + .add(); + } + } + } +} diff --git a/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/ByFormulaModificationEntity.java b/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/ByFormulaModificationEntity.java new file mode 100644 index 000000000..6b0a945d7 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/ByFormulaModificationEntity.java @@ -0,0 +1,72 @@ +package org.gridsuite.modification.server.entities.equipment.modification; + +import com.powsybl.iidm.network.IdentifiableType; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; +import org.gridsuite.modification.server.dto.ByFormulaModificationInfos; +import org.gridsuite.modification.server.dto.ModificationInfos; +import org.gridsuite.modification.server.dto.formula.FormulaInfos; +import org.gridsuite.modification.server.entities.ModificationEntity; + +import java.util.List; +import java.util.stream.Collectors; + +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "byFormulaModification") +public class ByFormulaModificationEntity extends ModificationEntity { + @Column + private IdentifiableType identifiableType; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @JoinColumn(name = "by_formula_modification_id", + foreignKey = @ForeignKey(name = "by_formula_modification_id_fk")) + private List formulaEntities; + + public ByFormulaModificationEntity(ByFormulaModificationInfos byFormulaModificationInfos) { + super(byFormulaModificationInfos); + assignAttributes(byFormulaModificationInfos); + } + + @Override + public void update(@NonNull ModificationInfos modificationInfos) { + super.update(modificationInfos); + assignAttributes((ByFormulaModificationInfos) modificationInfos); + } + + private void assignAttributes(ByFormulaModificationInfos byFormulaModificationInfos) { + this.identifiableType = byFormulaModificationInfos.getIdentifiableType(); + if (formulaEntities == null) { + formulaEntities = byFormulaModificationInfos.getFormulaInfosList() + .stream() + .map(FormulaInfos::toEntity) + .toList(); + } else { + formulaEntities.clear(); + formulaEntities.addAll(byFormulaModificationInfos.getFormulaInfosList() + .stream() + .map(FormulaInfos::toEntity) + .toList()); + } + } + + @Override + public ByFormulaModificationInfos toModificationInfos() { + return toByFormulaModificationInfosBuilder().build(); + } + + private ByFormulaModificationInfos.ByFormulaModificationInfosBuilder toByFormulaModificationInfosBuilder() { + return ByFormulaModificationInfos.builder() + .uuid(getId()) + .date(getDate()) + .identifiableType(getIdentifiableType()) + .formulaInfosList(getFormulaEntities().stream() + .map(FormulaEntity::toFormulaInfos) + .collect(Collectors.toList())); + } +} diff --git a/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/FormulaEntity.java b/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/FormulaEntity.java new file mode 100644 index 000000000..1ea147165 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/FormulaEntity.java @@ -0,0 +1,90 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.entities.equipment.modification; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.gridsuite.modification.server.dto.FilterInfos; +import org.gridsuite.modification.server.dto.formula.FormulaInfos; +import org.gridsuite.modification.server.dto.formula.Operator; +import org.gridsuite.modification.server.dto.formula.ReferenceFieldOrValue; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author Seddik Yengui + */ + +@NoArgsConstructor +@Getter +@Setter +@Entity +@Table(name = "formula", indexes = @Index(name = "by_formula_modification_id_idx", columnList = "by_formula_modification_id")) +public class FormulaEntity { + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "id") + private UUID id; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @JoinColumn(name = "formula_id", + foreignKey = @ForeignKey(name = "formula_id_fk")) + private List filters; + + @Column + private String editedField; + + @Column + private String equipmentField1; + + @Column + private Double value1; + + @Column + private String equipmentField2; + + @Column + private Double value2; + + @Column + private Operator operator; + + public FormulaEntity(FormulaInfos formulaInfos) { + this.id = null; + this.filters = formulaInfos.getFilters().stream().map(FilterInfos::toEntity).collect(Collectors.toList()); + this.editedField = formulaInfos.getEditedField(); + this.equipmentField1 = formulaInfos.getFieldOrValue1().getEquipmentField(); + this.equipmentField2 = formulaInfos.getFieldOrValue2().getEquipmentField(); + this.value1 = formulaInfos.getFieldOrValue1().getValue(); + this.value2 = formulaInfos.getFieldOrValue2().getValue(); + this.operator = formulaInfos.getOperator(); + } + + public FormulaInfos toFormulaInfos() { + return FormulaInfos.builder() + .id(getId()) + .filters(getFilters().stream() + .map(filterEntity -> new FilterInfos(filterEntity.getFilterId(), filterEntity.getName())) + .collect(Collectors.toList())) + .editedField(getEditedField()) + .fieldOrValue1(ReferenceFieldOrValue.builder() + .equipmentField(getEquipmentField1()) + .value(getValue1()) + .build()) + .fieldOrValue2(ReferenceFieldOrValue.builder() + .equipmentField(getEquipmentField2()) + .value(getValue2()) + .build()) + .operator(getOperator()) + .build(); + } +} diff --git a/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/VariationFilterEntity.java b/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/VariationFilterEntity.java index 5d5c8ee79..ee9e4c15f 100644 --- a/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/VariationFilterEntity.java +++ b/src/main/java/org/gridsuite/modification/server/entities/equipment/modification/VariationFilterEntity.java @@ -6,6 +6,7 @@ */ package org.gridsuite.modification.server.entities.equipment.modification; +import jakarta.persistence.Index; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -27,7 +28,7 @@ @Getter @Setter @Entity -@Table(name = "VariationFilter") +@Table(name = "VariationFilter", indexes = @Index(name = "formula_id_idx", columnList = "formula_id")) public class VariationFilterEntity { @Id diff --git a/src/main/java/org/gridsuite/modification/server/modifications/ByFormulaModification.java b/src/main/java/org/gridsuite/modification/server/modifications/ByFormulaModification.java new file mode 100644 index 000000000..b64ce2f58 --- /dev/null +++ b/src/main/java/org/gridsuite/modification/server/modifications/ByFormulaModification.java @@ -0,0 +1,187 @@ +package org.gridsuite.modification.server.modifications; + +import com.powsybl.commons.reporter.Report; +import com.powsybl.commons.reporter.Reporter; +import com.powsybl.commons.reporter.TypedValue; +import com.powsybl.iidm.network.Battery; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.Identifiable; +import com.powsybl.iidm.network.Network; +import org.gridsuite.modification.server.NetworkModificationException; +import org.gridsuite.modification.server.dto.ByFormulaModificationInfos; +import org.gridsuite.modification.server.dto.FilterEquipments; +import org.gridsuite.modification.server.dto.FilterInfos; +import org.gridsuite.modification.server.dto.formula.FormulaInfos; +import org.gridsuite.modification.server.dto.formula.Operator; +import org.gridsuite.modification.server.dto.formula.equipmentfield.BatteryField; +import org.gridsuite.modification.server.dto.formula.equipmentfield.GeneratorField; +import org.gridsuite.modification.server.service.FilterService; +import org.jetbrains.annotations.Nullable; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.gridsuite.modification.server.modifications.ModificationUtils.createReport; +import static org.gridsuite.modification.server.modifications.ModificationUtils.distinctByKey; + +public class ByFormulaModification extends AbstractModification { + private final ByFormulaModificationInfos modificationInfos; + protected FilterService filterService; + + public ByFormulaModification(ByFormulaModificationInfos modificationInfos) { + this.modificationInfos = modificationInfos; + } + + @Override + public void initApplicationContext(NetworkModificationApplicator modificationApplicator) { + filterService = modificationApplicator.getFilterService(); + } + + @Override + public void check(Network network) throws NetworkModificationException { + if (modificationInfos == null) { + throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, "Missing required attributes to modify the equipment"); + } + + if (CollectionUtils.isEmpty(modificationInfos.getFormulaInfosList())) { + throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, "At least one formula is required"); + } + + if (modificationInfos.getFormulaInfosList().stream().anyMatch(formulaInfos -> CollectionUtils.isEmpty(formulaInfos.getFilters()))) { + throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, "Every formula must have at least one filter"); + } + } + + @Override + public void apply(Network network, Reporter subReporter) { + // collect all filters from all variations + Map filters = modificationInfos.getFormulaInfosList().stream() + .flatMap(v -> v.getFilters().stream()) + .filter(distinctByKey(FilterInfos::getId)) + .collect(Collectors.toMap(FilterInfos::getId, FilterInfos::getName)); + + Map exportFilters = getUuidFilterEquipmentsMap(network, subReporter, filters); + + if (exportFilters != null) { + Reporter formulaSubReporter = subReporter.createSubReporter("appliedFormulasModifications", "Formulas"); + List formulaReports = new ArrayList<>(); + modificationInfos.getFormulaInfosList().forEach(formulaInfos -> + formulaInfos.getFilters().forEach(filterInfos -> + applyFormulaOnFilterEquipments(network, exportFilters, formulaReports, formulaInfos, filterInfos))); + + createReport(subReporter, "byFormulaModification", "new modification by formula", TypedValue.INFO_SEVERITY); + formulaSubReporter.report(Report.builder() + .withKey("appliedFormulasModifications") + .withDefaultMessage(" Formulas") + .withSeverity(TypedValue.INFO_SEVERITY) + .build()); + formulaReports.forEach(formulaSubReporter::report); + } + } + + private void applyFormulaOnFilterEquipments(Network network, + Map exportFilters, + List formulaReports, + FormulaInfos formulaInfos, + FilterInfos filterInfos) { + FilterEquipments filterEquipments = exportFilters.get(filterInfos.getId()); + + formulaReports.add(Report.builder() + .withKey("byFormulaModificationFormulaFilter_" + formulaReports.size()) + .withDefaultMessage(String.format("Successful application of new modification by formula on filter %s", + filterInfos.getName())) + .withSeverity(TypedValue.INFO_SEVERITY) + .build()); + + formulaReports.add(Report.builder() + .withKey("numberOfValidEquipment" + formulaReports.size()) + .withDefaultMessage(String.format(" Number of equipment modified : %s", filterEquipments.getIdentifiableAttributes().size())) + .withSeverity(TypedValue.INFO_SEVERITY) + .build()); + + if (!CollectionUtils.isEmpty(filterEquipments.getNotFoundEquipments())) { + String equipmentIds = String.join(", ", filterEquipments.getNotFoundEquipments()); + formulaReports.add(Report.builder() + .withKey("filterEquipmentsNotFound_" + formulaReports.size()) + .withDefaultMessage(String.format(" Equipment not found : %s", + equipmentIds)) + .withSeverity(TypedValue.WARN_SEVERITY) + .build()); + } + + formulaReports.add(Report.builder() + .withKey("editedFieldFilter_" + formulaReports.size()) + .withDefaultMessage(String.format(" Edited field : %s", formulaInfos.getEditedField())) + .withSeverity(TypedValue.INFO_SEVERITY) + .build()); + + filterEquipments.getIdentifiableAttributes().forEach(attributes -> applyFormula(network, + attributes.getId(), + formulaInfos, + formulaReports)); + } + + @Nullable + private Map getUuidFilterEquipmentsMap(Network network, Reporter subReporter, Map filters) { + // export filters from filter server + Map exportFilters = filterService.getUuidFilterEquipmentsMap(network, filters); + + boolean isValidFilter = ModificationUtils.getInstance().isValidFilter(subReporter, modificationInfos.getErrorType(), exportFilters); + return isValidFilter ? exportFilters : null; + } + + private void applyFormula(Network network, + String identifiableId, + FormulaInfos formulaInfos, + List reports) { + Identifiable identifiable = network.getIdentifiable(identifiableId); + Double value1 = formulaInfos.getFieldOrValue1().getRefOrValue(identifiable); + Double value2 = formulaInfos.getFieldOrValue2().getRefOrValue(identifiable); + final Double newValue = applyOperation(formulaInfos.getOperator(), value1, value2); + switch (identifiable.getType()) { + case GENERATOR -> GeneratorField.setNewValue((Generator) identifiable, + formulaInfos.getEditedField(), + newValue); + case BATTERY -> BatteryField.setNewValue((Battery) identifiable, + formulaInfos.getEditedField(), + newValue); + default -> throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, "Unsupported equipment"); + } + + reports.add(Report.builder() + .withKey("EquipmentModifiedReport_" + reports.size()) + .withDefaultMessage(String.format(" %s id : %s, new value of %s : %s", + modificationInfos.getIdentifiableType(), + identifiable.getId(), + formulaInfos.getEditedField(), + newValue)) + .withSeverity(TypedValue.TRACE_SEVERITY) + .build()); + } + + private Double applyOperation(Operator operator, Double value1, Double value2) { + if (value1 == null || + value2 == null) { + throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, "at least one of the value or referenced field is null"); + } else { + return switch (operator) { + case ADDITION -> value1 + value2; + case SUBTRACTION -> value1 - value2; + case MULTIPLICATION -> value1 * value2; + case DIVISION -> { + if (value2 == 0) { + throw new NetworkModificationException(NetworkModificationException.Type.BY_FORMULA_MODIFICATION_ERROR, + "there is a division by zero in a formula"); + } else { + yield value1 / value2; + } + } + case PERCENTAGE -> value1 * (value2 / 100); + }; + } + } +} diff --git a/src/main/java/org/gridsuite/modification/server/modifications/ModificationUtils.java b/src/main/java/org/gridsuite/modification/server/modifications/ModificationUtils.java index 0e6f0ad54..ac4e423bc 100644 --- a/src/main/java/org/gridsuite/modification/server/modifications/ModificationUtils.java +++ b/src/main/java/org/gridsuite/modification/server/modifications/ModificationUtils.java @@ -974,5 +974,20 @@ private void createReactiveCapabilityCurvePoint(ReactiveCapabilityCurveAdder add addToReports(reports, point.getQminP(), "QminP" + fieldSuffix); addToReports(reports, point.getQmaxP(), "QmaxP" + fieldSuffix); } + + public boolean isValidFilter(Reporter subReporter, + NetworkModificationException.Type errorType, + Map exportFilters) { + boolean noValidEquipmentId = exportFilters.values().stream() + .allMatch(filterEquipments -> filterEquipments.getIdentifiableAttributes().isEmpty()); + + if (noValidEquipmentId) { + String errorMsg = errorType + ": There is no valid equipment ID among the provided filter(s)"; + createReport(subReporter, "invalidFilters", errorMsg, TypedValue.ERROR_SEVERITY); + return false; + } + + return true; + } } diff --git a/src/main/java/org/gridsuite/modification/server/service/FilterService.java b/src/main/java/org/gridsuite/modification/server/service/FilterService.java index 1760b6b94..f10ba4762 100644 --- a/src/main/java/org/gridsuite/modification/server/service/FilterService.java +++ b/src/main/java/org/gridsuite/modification/server/service/FilterService.java @@ -9,6 +9,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.powsybl.iidm.network.Network; +import com.powsybl.network.store.iidm.impl.NetworkImpl; import org.gridsuite.modification.server.NetworkModificationException; import org.gridsuite.modification.server.dto.FilterEquipments; import org.slf4j.Logger; @@ -21,8 +23,11 @@ import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Collectors; import static org.gridsuite.modification.server.NetworkModificationException.Type.FILTERS_NOT_FOUND; @@ -65,6 +70,15 @@ public List exportFilters(List filtersUuids, UUID networ } } + public Map getUuidFilterEquipmentsMap(Network network, Map filters) { + String workingVariantId = network.getVariantManager().getWorkingVariantId(); + UUID uuid = ((NetworkImpl) network).getUuid(); + return exportFilters(new ArrayList<>(filters.keySet()), uuid, workingVariantId) + .stream() + .peek(t -> t.setFilterName(filters.get(t.getFilterId()))) + .collect(Collectors.toMap(FilterEquipments::getFilterId, Function.identity())); + } + private NetworkModificationException handleChangeError(HttpStatusCodeException httpException, NetworkModificationException.Type type) { String responseBody = httpException.getResponseBodyAsString(); if (responseBody.isEmpty()) { diff --git a/src/main/resources/db/changelog/changesets/changelog_20231108T124514Z.xml b/src/main/resources/db/changelog/changesets/changelog_20231108T124514Z.xml new file mode 100644 index 000000000..5e86f9fe3 --- /dev/null +++ b/src/main/resources/db/changelog/changesets/changelog_20231108T124514Z.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index ae445f959..cbc97a3a8 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -216,3 +216,6 @@ databaseChangeLog: - include: file: changesets/changelog_20231103T101339Z.xml relativeToChangelogFile: true + - include: + file: changesets/changelog_20231108T124514Z.xml + relativeToChangelogFile: true diff --git a/src/test/java/org/gridsuite/modification/server/modifications/AbstractByFormulaModificationTest.java b/src/test/java/org/gridsuite/modification/server/modifications/AbstractByFormulaModificationTest.java new file mode 100644 index 000000000..45e2b6fdf --- /dev/null +++ b/src/test/java/org/gridsuite/modification/server/modifications/AbstractByFormulaModificationTest.java @@ -0,0 +1,169 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.modifications; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.matching.StringValuePattern; +import com.powsybl.iidm.network.IdentifiableType; +import com.powsybl.iidm.network.Network; +import org.gridsuite.modification.server.dto.ByFormulaModificationInfos; +import org.gridsuite.modification.server.dto.FilterEquipments; +import org.gridsuite.modification.server.dto.FilterInfos; +import org.gridsuite.modification.server.dto.IdentifiableAttributes; +import org.gridsuite.modification.server.dto.NetworkModificationResult; +import org.gridsuite.modification.server.dto.formula.FormulaInfos; +import org.gridsuite.modification.server.dto.formula.Operator; +import org.gridsuite.modification.server.dto.formula.ReferenceFieldOrValue; +import org.gridsuite.modification.server.service.FilterService; +import org.gridsuite.modification.server.utils.NetworkCreation; +import org.junit.Before; +import org.junit.Test; +import org.junit.jupiter.api.Tag; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Seddik Yengui + */ + +@Tag("IntegrationTest") +public abstract class AbstractByFormulaModificationTest extends AbstractNetworkModificationTest { + public static final String PATH = "/v1/filters/export"; + + @Before + public void specificSetUp() { + FilterService.setFilterServerBaseUri(wireMockServer.baseUrl()); + + createEquipments(); + } + + @Test + @Override + public void testCreate() throws Exception { + List filters = getTestFilters(); + UUID stubId = wireMockServer.stubFor(WireMock.get(WireMock.urlMatching(getPath(getNetworkUuid(), true) + "(.+,){4}.*")) + .willReturn(WireMock.ok() + .withBody(mapper.writeValueAsString(filters)) + .withHeader("Content-Type", "application/json"))).getId(); + + super.testCreate(); + + wireMockUtils.verifyGetRequest(stubId, PATH, handleQueryParams(getNetworkUuid(), filters.stream().map(FilterEquipments::getFilterId).collect(Collectors.toList())), false); + + } + + @Test + @Override + public void testCopy() throws Exception { + + List filters = getTestFilters(); + UUID stubId = wireMockServer.stubFor(WireMock.get(WireMock.urlMatching(getPath(getNetworkUuid(), true) + "(.+,){4}.*")) + .willReturn(WireMock.ok() + .withBody(mapper.writeValueAsString(filters)) + .withHeader("Content-Type", "application/json"))).getId(); + + super.testCopy(); + + wireMockUtils.verifyGetRequest(stubId, PATH, handleQueryParams(getNetworkUuid(), filters.stream().map(FilterEquipments::getFilterId).collect(Collectors.toList())), false); + } + + void checkCreationApplicationStatus(ByFormulaModificationInfos byFormulaModificationInfos, + NetworkModificationResult.ApplicationStatus applicationStatus) throws Exception { + String modificationToCreateJson = mapper.writeValueAsString(byFormulaModificationInfos); + + MvcResult mvcResult = mockMvc.perform(post(getNetworkModificationUri()).content(modificationToCreateJson).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()).andReturn(); + + Optional networkModificationResult = mapper.readValue(mvcResult.getResponse().getContentAsString(), new TypeReference<>() { }); + assertTrue(networkModificationResult.isPresent()); + assertEquals(applicationStatus, networkModificationResult.get().getApplicationStatus()); + } + + @Override + protected Network createNetwork(UUID networkUuid) { + return NetworkCreation.create(networkUuid, true); + } + + @Override + protected ByFormulaModificationInfos buildModification() { + return ByFormulaModificationInfos.builder() + .identifiableType(IdentifiableType.GENERATOR) + .formulaInfosList(getFormulaInfos()) + .build(); + } + + @Override + protected ByFormulaModificationInfos buildModificationUpdate() { + return ByFormulaModificationInfos.builder() + .identifiableType(IdentifiableType.GENERATOR) + .formulaInfosList(getUpdatedFormulaInfos()) + .build(); + } + + IdentifiableAttributes getIdentifiableAttributes(String id, Double distributionKey) { + return IdentifiableAttributes.builder() + .id(id) + .type(IdentifiableType.GENERATOR) + .distributionKey(distributionKey) + .build(); + } + + FilterEquipments getFilterEquipments(UUID filterID, String filterName, List identifiableAttributes, List notFoundEquipments) { + return FilterEquipments.builder() + .filterId(filterID) + .filterName(filterName) + .identifiableAttributes(identifiableAttributes) + .notFoundEquipments(notFoundEquipments) + .build(); + } + + FormulaInfos getFormulaInfo(String editedField, + List filters, + Operator operator, + ReferenceFieldOrValue fieldOrValue1, + ReferenceFieldOrValue fieldOrValue2) { + return FormulaInfos.builder() + .editedField(editedField) + .filters(filters) + .operator(operator) + .fieldOrValue1(fieldOrValue1) + .fieldOrValue2(fieldOrValue2) + .build(); + } + + Map handleQueryParams(UUID networkUuid, List filterIds) { + return Map.of("networkUuid", WireMock.equalTo(String.valueOf(networkUuid)), "variantId", WireMock.equalTo("variant_1"), "ids", WireMock.matching(filterIds.stream().map(uuid -> ".+").collect(Collectors.joining(",")))); + } + + private String getPath(UUID networkUuid, boolean isRegexPhat) { + if (isRegexPhat) { + return "/v1/filters/export\\?networkUuid=" + networkUuid + "\\&variantId=variant_1\\&ids="; + } + return "/v1/filters/export?networkUuid=" + networkUuid + "&variantId=variant_1&ids="; + } + + abstract void createEquipments(); + + abstract List getTestFilters(); + + abstract List getFormulaInfos(); + + abstract List getUpdatedFormulaInfos(); +} diff --git a/src/test/java/org/gridsuite/modification/server/modifications/BatteryByFormulaModificationTest.java b/src/test/java/org/gridsuite/modification/server/modifications/BatteryByFormulaModificationTest.java new file mode 100644 index 000000000..9c88baf83 --- /dev/null +++ b/src/test/java/org/gridsuite/modification/server/modifications/BatteryByFormulaModificationTest.java @@ -0,0 +1,308 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.modifications; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.powsybl.iidm.network.IdentifiableType; +import com.powsybl.iidm.network.extensions.ActivePowerControl; +import com.powsybl.iidm.network.extensions.ActivePowerControlAdder; +import org.gridsuite.modification.server.dto.ByFormulaModificationInfos; +import org.gridsuite.modification.server.dto.FilterEquipments; +import org.gridsuite.modification.server.dto.FilterInfos; +import org.gridsuite.modification.server.dto.IdentifiableAttributes; +import org.gridsuite.modification.server.dto.NetworkModificationResult; +import org.gridsuite.modification.server.dto.formula.FormulaInfos; +import org.gridsuite.modification.server.dto.formula.Operator; +import org.gridsuite.modification.server.dto.formula.ReferenceFieldOrValue; +import org.gridsuite.modification.server.dto.formula.equipmentfield.BatteryField; +import org.junit.Test; + +import java.util.List; +import java.util.UUID; + +import static org.gridsuite.modification.server.utils.NetworkUtil.createBattery; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Seddik Yengui + */ + +public class BatteryByFormulaModificationTest extends AbstractByFormulaModificationTest { + private static final UUID FILTER_ID_1 = UUID.randomUUID(); + private static final UUID FILTER_ID_2 = UUID.randomUUID(); + private static final UUID FILTER_ID_3 = UUID.randomUUID(); + private static final UUID FILTER_ID_4 = UUID.randomUUID(); + private static final UUID FILTER_ID_5 = UUID.randomUUID(); + private static final String BATTERY_ID_1 = "v3Battery"; + private static final String BATTERY_ID_2 = "battery2"; + private static final String BATTERY_ID_3 = "battery3"; + private static final String BATTERY_ID_4 = "battery4"; + private static final String BATTERY_ID_5 = "battery5"; + private static final String BATTERY_ID_6 = "battery6"; + + @Test + public void testCreateWithWarning() throws Exception { + UUID filterId = UUID.randomUUID(); + String equipmentId = "v3Battery"; + IdentifiableAttributes identifiableAttributes1 = getIdentifiableAttributes(equipmentId, 1.0); + FilterEquipments filter = getFilterEquipments(filterId, "filterWithWrongId", List.of(identifiableAttributes1), List.of("wrongId")); + var filterInfo = FilterInfos.builder() + .id(filterId) + .name("filterWithWrongId") + .build(); + + UUID stubId = wireMockServer.stubFor(WireMock.get(WireMock.urlMatching("/v1/filters/export\\?networkUuid=" + getNetworkUuid() + "&variantId=variant_1&ids=" + filterId)) + .willReturn(WireMock.ok() + .withBody(mapper.writeValueAsString(List.of(filter))) + .withHeader("Content-Type", "application/json"))).getId(); + + FormulaInfos formulaInfos = FormulaInfos.builder() + .filters(List.of(filterInfo)) + .editedField(BatteryField.ACTIVE_POWER_SET_POINT.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().value(55.).build()) + .operator(Operator.ADDITION) + .fieldOrValue2(ReferenceFieldOrValue.builder().value(20.).build()) + .build(); + + ByFormulaModificationInfos byFormulaModificationInfos = ByFormulaModificationInfos.builder() + .formulaInfosList(List.of(formulaInfos)) + .identifiableType(IdentifiableType.BATTERY) + .build(); + + checkCreationApplicationStatus(byFormulaModificationInfos, NetworkModificationResult.ApplicationStatus.WITH_WARNINGS); + assertEquals(75, getNetwork().getBattery(equipmentId).getTargetP(), 0); + + wireMockUtils.verifyGetRequest(stubId, PATH, handleQueryParams(getNetworkUuid(), List.of(filterId)), false); + } + + @Test + public void testCreateWithError() throws Exception { + UUID filterId = UUID.randomUUID(); + FilterEquipments filter = getFilterEquipments(filterId, "filterWithWrongId", List.of(), List.of("wrongId1", "wrongId2")); + var filterInfo = FilterInfos.builder() + .id(filterId) + .name("filterWithWrongId") + .build(); + + UUID stubId = wireMockServer.stubFor(WireMock.get(WireMock.urlMatching("/v1/filters/export\\?networkUuid=" + getNetworkUuid() + "&variantId=variant_1&ids=" + filterId)) + .willReturn(WireMock.ok() + .withBody(mapper.writeValueAsString(List.of(filter))) + .withHeader("Content-Type", "application/json"))).getId(); + + FormulaInfos formulaInfos = FormulaInfos.builder() + .filters(List.of(filterInfo)) + .editedField(BatteryField.ACTIVE_POWER_SET_POINT.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().value(55.).build()) + .operator(Operator.ADDITION) + .fieldOrValue2(ReferenceFieldOrValue.builder().value(20.).build()) + .build(); + + ByFormulaModificationInfos byFormulaModificationInfos = ByFormulaModificationInfos.builder() + .formulaInfosList(List.of(formulaInfos)) + .identifiableType(IdentifiableType.BATTERY) + .build(); + + checkCreationApplicationStatus(byFormulaModificationInfos, NetworkModificationResult.ApplicationStatus.WITH_ERRORS); + + wireMockUtils.verifyGetRequest(stubId, PATH, handleQueryParams(getNetworkUuid(), List.of(filterId)), false); + } + + @Override + void createEquipments() { + getNetwork().getVariantManager().setWorkingVariant("variant_1"); + getNetwork().getBattery(BATTERY_ID_1).setTargetP(100).setMaxP(500).setMinP(0).setTargetQ(80); + getNetwork().getBattery(BATTERY_ID_1).newExtension(ActivePowerControlAdder.class).withDroop(1).add(); + + createBattery(getNetwork().getVoltageLevel("v2"), BATTERY_ID_2, "v2Battery2", 20, 50, 2000, 200, 50); + createBattery(getNetwork().getVoltageLevel("v3"), BATTERY_ID_3, "v3Battery3", 30, 70, 400, 300, 50); + + createBattery(getNetwork().getVoltageLevel("v4"), BATTERY_ID_4, "v4Battery4", 40, 25, 350, 70, 50); + + createBattery(getNetwork().getVoltageLevel("v5"), BATTERY_ID_5, "v5Battery5", 50, 50, 600, 55, 140); + getNetwork().getBattery(BATTERY_ID_5).newExtension(ActivePowerControlAdder.class).withDroop(4).add(); + + createBattery(getNetwork().getVoltageLevel("v6"), BATTERY_ID_6, "v6Battery6", 60, 200, 700, 250, 210); + } + + @Override + List getTestFilters() { + IdentifiableAttributes battery1 = getIdentifiableAttributes(BATTERY_ID_1, 1.0); + IdentifiableAttributes battery2 = getIdentifiableAttributes(BATTERY_ID_2, 2.0); + IdentifiableAttributes battery3 = getIdentifiableAttributes(BATTERY_ID_3, 2.0); + IdentifiableAttributes battery4 = getIdentifiableAttributes(BATTERY_ID_4, 5.0); + IdentifiableAttributes battery5 = getIdentifiableAttributes(BATTERY_ID_5, 6.0); + IdentifiableAttributes battery6 = getIdentifiableAttributes(BATTERY_ID_6, 7.0); + + FilterEquipments filter1 = getFilterEquipments(FILTER_ID_1, "filter1", List.of(battery1, battery2), List.of()); + FilterEquipments filter2 = getFilterEquipments(FILTER_ID_2, "filter2", List.of(battery3, battery4), List.of()); + FilterEquipments filter3 = getFilterEquipments(FILTER_ID_3, "filter3", List.of(battery5, battery6), List.of()); + FilterEquipments filter4 = getFilterEquipments(FILTER_ID_4, "filter4", List.of(battery1, battery5), List.of()); + FilterEquipments filter5 = getFilterEquipments(FILTER_ID_5, "filter5", List.of(battery2, battery3), List.of()); + + return List.of(filter1, filter2, filter3, filter4, filter5); + } + + @Override + List getFormulaInfos() { + var filter1 = FilterInfos.builder() + .id(FILTER_ID_1) + .name("filter1") + .build(); + + var filter2 = FilterInfos.builder() + .id(FILTER_ID_2) + .name("filter2") + .build(); + + var filter3 = FilterInfos.builder() + .id(FILTER_ID_3) + .name("filter3") + .build(); + + var filter4 = FilterInfos.builder() + .id(FILTER_ID_4) + .name("filter4") + .build(); + + var filter5 = FilterInfos.builder() + .id(FILTER_ID_5) + .name("filter5") + .build(); + + ReferenceFieldOrValue maxActivePowerRef = ReferenceFieldOrValue.builder().equipmentField(BatteryField.MAXIMUM_ACTIVE_POWER.name()).build(); + ReferenceFieldOrValue minActivePowerRef = ReferenceFieldOrValue.builder().equipmentField(BatteryField.MINIMUM_ACTIVE_POWER.name()).build(); + + FormulaInfos formulaInfos1 = getFormulaInfo(BatteryField.MAXIMUM_ACTIVE_POWER.name(), + List.of(filter1, filter2), + Operator.ADDITION, + maxActivePowerRef, + ReferenceFieldOrValue.builder().value(50.).build()); + + FormulaInfos formulaInfos2 = getFormulaInfo(BatteryField.MINIMUM_ACTIVE_POWER.name(), + List.of(filter3), + Operator.PERCENTAGE, + ReferenceFieldOrValue.builder().value(30.).build(), + minActivePowerRef); + + FormulaInfos formulaInfos3 = getFormulaInfo(BatteryField.ACTIVE_POWER_SET_POINT.name(), + List.of(filter5), + Operator.SUBTRACTION, + maxActivePowerRef, + minActivePowerRef); + + FormulaInfos formulaInfos4 = getFormulaInfo(BatteryField.REACTIVE_POWER_SET_POINT.name(), + List.of(filter4), + Operator.DIVISION, + ReferenceFieldOrValue.builder().equipmentField(BatteryField.REACTIVE_POWER_SET_POINT.name()).build(), + ReferenceFieldOrValue.builder().value(2.).build()); + + FormulaInfos formulaInfos5 = getFormulaInfo(BatteryField.DROOP.name(), + List.of(filter4), + Operator.MULTIPLICATION, + ReferenceFieldOrValue.builder().equipmentField(BatteryField.DROOP.name()).build(), + ReferenceFieldOrValue.builder().value(2.).build()); + + return List.of(formulaInfos1, formulaInfos2, formulaInfos3, formulaInfos4, formulaInfos5); + } + + @Override + List getUpdatedFormulaInfos() { + var filter1 = FilterInfos.builder() + .id(FILTER_ID_1) + .name("filter1") + .build(); + + var filter2 = FilterInfos.builder() + .id(FILTER_ID_2) + .name("filter2") + .build(); + + var filter3 = FilterInfos.builder() + .id(FILTER_ID_3) + .name("filter3") + .build(); + + var filter5 = FilterInfos.builder() + .id(FILTER_ID_5) + .name("filter5") + .build(); + + FormulaInfos formulaInfos1 = FormulaInfos.builder() + .editedField(BatteryField.MAXIMUM_ACTIVE_POWER.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().value(200.).build()) + .fieldOrValue2(ReferenceFieldOrValue.builder().equipmentField(BatteryField.MAXIMUM_ACTIVE_POWER.name()).build()) + .operator(Operator.ADDITION) + .filters(List.of(filter1, filter2)) + .build(); + + FormulaInfos formulaInfos2 = FormulaInfos.builder() + .editedField(BatteryField.MINIMUM_ACTIVE_POWER.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().value(35.).build()) + .fieldOrValue2(ReferenceFieldOrValue.builder().equipmentField(BatteryField.MINIMUM_ACTIVE_POWER.name()).build()) + .operator(Operator.PERCENTAGE) + .filters(List.of(filter3)) + .build(); + + FormulaInfos formulaInfos3 = FormulaInfos.builder() + .editedField(BatteryField.ACTIVE_POWER_SET_POINT.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().equipmentField(BatteryField.ACTIVE_POWER_SET_POINT.name()).build()) + .fieldOrValue2(ReferenceFieldOrValue.builder().value(10.).build()) + .operator(Operator.ADDITION) + .filters(List.of(filter5)) + .build(); + + return List.of(formulaInfos1, formulaInfos2, formulaInfos3); + } + + @Override + protected void assertAfterNetworkModificationCreation() { + assertEquals(550, getNetwork().getBattery(BATTERY_ID_1).getMaxP(), 0); + assertEquals(40, getNetwork().getBattery(BATTERY_ID_1).getTargetQ(), 0); + ActivePowerControl activePowerControl1 = getNetwork().getBattery(BATTERY_ID_1).getExtension(ActivePowerControl.class); + assertNotNull(activePowerControl1); + assertEquals(2, activePowerControl1.getDroop(), 0); + + assertEquals(2050, getNetwork().getBattery(BATTERY_ID_2).getMaxP(), 0); + assertEquals(2000, getNetwork().getBattery(BATTERY_ID_2).getTargetP(), 0); + assertEquals(450, getNetwork().getBattery(BATTERY_ID_3).getMaxP(), 0); + assertEquals(380, getNetwork().getBattery(BATTERY_ID_3).getTargetP(), 0); + assertEquals(400, getNetwork().getBattery(BATTERY_ID_4).getMaxP(), 0); + + assertEquals(15, getNetwork().getBattery(BATTERY_ID_5).getMinP(), 0); + assertEquals(70, getNetwork().getBattery(BATTERY_ID_5).getTargetQ(), 0); + ActivePowerControl activePowerControl5 = getNetwork().getBattery(BATTERY_ID_5).getExtension(ActivePowerControl.class); + assertNotNull(activePowerControl5); + assertEquals(8, activePowerControl5.getDroop(), 0); + + assertEquals(60, getNetwork().getBattery(BATTERY_ID_6).getMinP(), 0); + } + + @Override + protected void assertAfterNetworkModificationDeletion() { + assertEquals(500, getNetwork().getBattery(BATTERY_ID_1).getMaxP(), 0); + assertEquals(80, getNetwork().getBattery(BATTERY_ID_1).getTargetQ(), 0); + ActivePowerControl activePowerControl1 = getNetwork().getBattery(BATTERY_ID_1).getExtension(ActivePowerControl.class); + assertNotNull(activePowerControl1); + assertEquals(1, activePowerControl1.getDroop(), 0); + + assertEquals(2000, getNetwork().getBattery(BATTERY_ID_2).getMaxP(), 0); + assertEquals(200, getNetwork().getBattery(BATTERY_ID_2).getTargetP(), 0); + assertEquals(400, getNetwork().getBattery(BATTERY_ID_3).getMaxP(), 0); + assertEquals(300, getNetwork().getBattery(BATTERY_ID_3).getTargetP(), 0); + assertEquals(350, getNetwork().getBattery(BATTERY_ID_4).getMaxP(), 0); + + assertEquals(50, getNetwork().getBattery(BATTERY_ID_5).getMinP(), 0); + assertEquals(140, getNetwork().getBattery(BATTERY_ID_5).getTargetQ(), 0); + ActivePowerControl activePowerControl5 = getNetwork().getBattery(BATTERY_ID_5).getExtension(ActivePowerControl.class); + assertNotNull(activePowerControl5); + assertEquals(4, activePowerControl5.getDroop(), 0); + + assertEquals(200, getNetwork().getBattery(BATTERY_ID_6).getMinP(), 0); + } +} diff --git a/src/test/java/org/gridsuite/modification/server/modifications/GeneratorByFormulaModificationTest.java b/src/test/java/org/gridsuite/modification/server/modifications/GeneratorByFormulaModificationTest.java new file mode 100644 index 000000000..b6130fcd1 --- /dev/null +++ b/src/test/java/org/gridsuite/modification/server/modifications/GeneratorByFormulaModificationTest.java @@ -0,0 +1,519 @@ +/** + * 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/. + */ + +package org.gridsuite.modification.server.modifications; + +import com.github.tomakehurst.wiremock.client.WireMock; +import com.powsybl.iidm.network.Generator; +import com.powsybl.iidm.network.IdentifiableType; +import com.powsybl.iidm.network.extensions.ActivePowerControl; +import com.powsybl.iidm.network.extensions.ActivePowerControlAdder; +import com.powsybl.iidm.network.extensions.ConnectablePosition; +import com.powsybl.iidm.network.extensions.CoordinatedReactiveControl; +import com.powsybl.iidm.network.extensions.CoordinatedReactiveControlAdder; +import com.powsybl.iidm.network.extensions.GeneratorShortCircuit; +import com.powsybl.iidm.network.extensions.GeneratorShortCircuitAdder; +import com.powsybl.iidm.network.extensions.GeneratorStartup; +import com.powsybl.iidm.network.extensions.GeneratorStartupAdder; +import org.gridsuite.modification.server.dto.ByFormulaModificationInfos; +import org.gridsuite.modification.server.dto.FilterEquipments; +import org.gridsuite.modification.server.dto.FilterInfos; +import org.gridsuite.modification.server.dto.IdentifiableAttributes; +import org.gridsuite.modification.server.dto.NetworkModificationResult; +import org.gridsuite.modification.server.dto.formula.FormulaInfos; +import org.gridsuite.modification.server.dto.formula.Operator; +import org.gridsuite.modification.server.dto.formula.ReferenceFieldOrValue; +import org.gridsuite.modification.server.dto.formula.equipmentfield.GeneratorField; +import org.junit.Test; +import org.junit.jupiter.api.Tag; + +import java.util.List; +import java.util.UUID; + +import static org.gridsuite.modification.server.utils.NetworkUtil.createGenerator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author Seddik Yengui + */ + +@Tag("IntegrationTest") +public class GeneratorByFormulaModificationTest extends AbstractByFormulaModificationTest { + private static final UUID FILTER_ID_1 = UUID.randomUUID(); + private static final UUID FILTER_ID_2 = UUID.randomUUID(); + private static final UUID FILTER_ID_3 = UUID.randomUUID(); + private static final UUID FILTER_ID_4 = UUID.randomUUID(); + private static final UUID FILTER_ID_5 = UUID.randomUUID(); + private static final String GENERATOR_ID_1 = "idGenerator"; + private static final String GENERATOR_ID_2 = "v5generator"; + private static final String GENERATOR_ID_3 = "v6generator"; + private static final String GENERATOR_ID_4 = "gen4"; + private static final String GENERATOR_ID_5 = "gen5"; + private static final String GENERATOR_ID_6 = "gen6"; + private static final String GENERATOR_ID_7 = "gen7"; + private static final String GENERATOR_ID_8 = "gen8"; + private static final String GENERATOR_ID_9 = "gen9"; + private static final String GENERATOR_ID_10 = "gen10"; + + @Test + public void testCreateWithWarning() throws Exception { + UUID filterId = UUID.randomUUID(); + String equipmentId = "idGenerator"; + IdentifiableAttributes identifiableAttributes1 = getIdentifiableAttributes(equipmentId, 1.0); + FilterEquipments filter = getFilterEquipments(filterId, "filterWithWrongId", List.of(identifiableAttributes1), List.of("wrongId")); + var filterInfo = FilterInfos.builder() + .id(filterId) + .name("filterWithWrongId") + .build(); + + UUID stubId = wireMockServer.stubFor(WireMock.get(WireMock.urlMatching("/v1/filters/export\\?networkUuid=" + getNetworkUuid() + "&variantId=variant_1&ids=" + filterId)) + .willReturn(WireMock.ok() + .withBody(mapper.writeValueAsString(List.of(filter))) + .withHeader("Content-Type", "application/json"))).getId(); + + FormulaInfos formulaInfos = FormulaInfos.builder() + .filters(List.of(filterInfo)) + .editedField(GeneratorField.ACTIVE_POWER_SET_POINT.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().value(55.).build()) + .operator(Operator.ADDITION) + .fieldOrValue2(ReferenceFieldOrValue.builder().value(20.).build()) + .build(); + + ByFormulaModificationInfos byFormulaModificationInfos = ByFormulaModificationInfos.builder() + .formulaInfosList(List.of(formulaInfos)) + .identifiableType(IdentifiableType.GENERATOR) + .build(); + + checkCreationApplicationStatus(byFormulaModificationInfos, NetworkModificationResult.ApplicationStatus.WITH_WARNINGS); + assertEquals(75, getNetwork().getGenerator(equipmentId).getTargetP(), 0); + + wireMockUtils.verifyGetRequest(stubId, PATH, handleQueryParams(getNetworkUuid(), List.of(filterId)), false); + } + + @Test + public void testCreateWithError() throws Exception { + UUID filterId = UUID.randomUUID(); + FilterEquipments filter = getFilterEquipments(filterId, "filterWithWrongId", List.of(), List.of("wrongId1", "wrongId2")); + var filterInfo = FilterInfos.builder() + .id(filterId) + .name("filterWithWrongId") + .build(); + + UUID stubId = wireMockServer.stubFor(WireMock.get(WireMock.urlMatching("/v1/filters/export\\?networkUuid=" + getNetworkUuid() + "&variantId=variant_1&ids=" + filterId)) + .willReturn(WireMock.ok() + .withBody(mapper.writeValueAsString(List.of(filter))) + .withHeader("Content-Type", "application/json"))).getId(); + + FormulaInfos formulaInfos = FormulaInfos.builder() + .filters(List.of(filterInfo)) + .editedField(GeneratorField.ACTIVE_POWER_SET_POINT.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().value(55.).build()) + .operator(Operator.ADDITION) + .fieldOrValue2(ReferenceFieldOrValue.builder().value(20.).build()) + .build(); + + ByFormulaModificationInfos byFormulaModificationInfos = ByFormulaModificationInfos.builder() + .formulaInfosList(List.of(formulaInfos)) + .identifiableType(IdentifiableType.GENERATOR) + .build(); + + checkCreationApplicationStatus(byFormulaModificationInfos, NetworkModificationResult.ApplicationStatus.WITH_ERRORS); + + wireMockUtils.verifyGetRequest(stubId, PATH, handleQueryParams(getNetworkUuid(), List.of(filterId)), false); + } + + void createEquipments() { + getNetwork().getVariantManager().setWorkingVariant("variant_1"); + getNetwork().getGenerator(GENERATOR_ID_1) + .setTargetP(100) + .setMaxP(500) + .setMinP(0) + .newExtension(GeneratorStartupAdder.class) + .withMarginalCost(30.) + .withPlannedOutageRate(25.) + .withPlannedActivePowerSetpoint(40.) + .withForcedOutageRate(55.) + .add(); + + getNetwork().getGenerator(GENERATOR_ID_2) + .setTargetP(200) + .setMaxP(2000) + .setMinP(50) + .newExtension(GeneratorStartupAdder.class) + .withMarginalCost(30.) + .withPlannedOutageRate(25.) + .withPlannedActivePowerSetpoint(40.) + .withForcedOutageRate(55.) + .add(); + + getNetwork().getGenerator(GENERATOR_ID_3) + .setTargetP(300) + .setMaxP(2000) + .setMinP(70) + .newExtension(GeneratorShortCircuitAdder.class) + .withDirectTransX(40.) + .withStepUpTransformerX(38.) + .add(); + + createGenerator(getNetwork().getVoltageLevel("v1"), GENERATOR_ID_4, 3, 400, 1.0, "cn10", 11, ConnectablePosition.Direction.TOP, 700, 110); + getNetwork().getGenerator(GENERATOR_ID_4) + .newExtension(GeneratorShortCircuitAdder.class) + .withDirectTransX(46.) + .withStepUpTransformerX(50.) + .add(); + + createGenerator(getNetwork().getVoltageLevel("v1"), GENERATOR_ID_5, 20, 200, 1.0, "cn10", 12, ConnectablePosition.Direction.TOP, 2000, 50); + getNetwork().getGenerator(GENERATOR_ID_5).newExtension(ActivePowerControlAdder.class).withDroop(2).add(); + + createGenerator(getNetwork().getVoltageLevel("v2"), GENERATOR_ID_6, 11, 100, 1.0, "cn10", 13, ConnectablePosition.Direction.TOP, 500, 20); + getNetwork().getGenerator(GENERATOR_ID_6).newExtension(ActivePowerControlAdder.class).withDroop(3).add(); + + createGenerator(getNetwork().getVoltageLevel("v6"), GENERATOR_ID_7, 10, 200, 1.0, "cn10", 14, ConnectablePosition.Direction.TOP, 2000, 50); + getNetwork().getGenerator(GENERATOR_ID_7).newExtension(CoordinatedReactiveControlAdder.class) + .withQPercent(6) + .add(); + getNetwork().getGenerator(GENERATOR_ID_7).newExtension(GeneratorStartupAdder.class).withMarginalCost(50).add(); + + createGenerator(getNetwork().getVoltageLevel("v3"), GENERATOR_ID_8, 10, 100, 1.0, "cn10", 15, ConnectablePosition.Direction.TOP, 500, 20); + getNetwork().getGenerator(GENERATOR_ID_8).newExtension(CoordinatedReactiveControlAdder.class) + .withQPercent(12) + .add(); + getNetwork().getGenerator(GENERATOR_ID_8).newExtension(GeneratorStartupAdder.class).withMarginalCost(60).add(); + + createGenerator(getNetwork().getVoltageLevel("v4"), GENERATOR_ID_9, 10, 200, 1.0, "cn10", 16, ConnectablePosition.Direction.TOP, 2000, 50); + getNetwork().getGenerator(GENERATOR_ID_9).setRatedS(60.); + + createGenerator(getNetwork().getVoltageLevel("v5"), GENERATOR_ID_10, 10, 100, 1.0, "cn10", 17, ConnectablePosition.Direction.TOP, 500, 20); + getNetwork().getGenerator(GENERATOR_ID_10).setRatedS(30.); + } + + List getTestFilters() { + IdentifiableAttributes gen1 = getIdentifiableAttributes(GENERATOR_ID_1, 1.0); + IdentifiableAttributes gen2 = getIdentifiableAttributes(GENERATOR_ID_2, 2.0); + IdentifiableAttributes gen3 = getIdentifiableAttributes(GENERATOR_ID_3, 2.0); + IdentifiableAttributes gen4 = getIdentifiableAttributes(GENERATOR_ID_4, 5.0); + IdentifiableAttributes gen5 = getIdentifiableAttributes(GENERATOR_ID_5, 6.0); + IdentifiableAttributes gen6 = getIdentifiableAttributes(GENERATOR_ID_6, 7.0); + IdentifiableAttributes gen7 = getIdentifiableAttributes(GENERATOR_ID_7, 3.0); + IdentifiableAttributes gen8 = getIdentifiableAttributes(GENERATOR_ID_8, 8.0); + IdentifiableAttributes gen9 = getIdentifiableAttributes(GENERATOR_ID_9, 0.0); + IdentifiableAttributes gen10 = getIdentifiableAttributes(GENERATOR_ID_10, 9.0); + + FilterEquipments filter1 = getFilterEquipments(FILTER_ID_1, "filter1", List.of(gen1, gen2), List.of()); + FilterEquipments filter2 = getFilterEquipments(FILTER_ID_2, "filter2", List.of(gen3, gen4), List.of()); + FilterEquipments filter3 = getFilterEquipments(FILTER_ID_3, "filter3", List.of(gen5, gen6), List.of()); + FilterEquipments filter4 = getFilterEquipments(FILTER_ID_4, "filter4", List.of(gen7, gen8), List.of()); + FilterEquipments filter5 = getFilterEquipments(FILTER_ID_5, "filter5", List.of(gen9, gen10), List.of()); + + return List.of(filter1, filter2, filter3, filter4, filter5); + } + + @Override + List getFormulaInfos() { + var filter1 = FilterInfos.builder() + .id(FILTER_ID_1) + .name("filter1") + .build(); + + var filter2 = FilterInfos.builder() + .id(FILTER_ID_2) + .name("filter2") + .build(); + + var filter3 = FilterInfos.builder() + .id(FILTER_ID_3) + .name("filter3") + .build(); + + var filter4 = FilterInfos.builder() + .id(FILTER_ID_4) + .name("filter4") + .build(); + + var filter5 = FilterInfos.builder() + .id(FILTER_ID_5) + .name("filter5") + .build(); + + FormulaInfos formulaInfos1 = getFormulaInfo(GeneratorField.ACTIVE_POWER_SET_POINT.name(), + List.of(filter1, filter2), + Operator.ADDITION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.MINIMUM_ACTIVE_POWER.name()).build(), + ReferenceFieldOrValue.builder().value(50.).build()); + + FormulaInfos formulaInfos2 = getFormulaInfo(GeneratorField.DROOP.name(), + List.of(filter3), + Operator.MULTIPLICATION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.DROOP.name()).build(), + ReferenceFieldOrValue.builder().value(2.).build()); + + FormulaInfos formulaInfos3 = getFormulaInfo(GeneratorField.RATED_NOMINAL_POWER.name(), + List.of(filter5), + Operator.DIVISION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.MAXIMUM_ACTIVE_POWER.name()).build(), + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.MINIMUM_ACTIVE_POWER.name()).build()); + + FormulaInfos formulaInfos4 = getFormulaInfo(GeneratorField.MARGINAL_COST.name(), + List.of(filter1), + Operator.DIVISION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.MARGINAL_COST.name()).build(), + ReferenceFieldOrValue.builder().value(2.).build()); + + FormulaInfos formulaInfos5 = getFormulaInfo(GeneratorField.VOLTAGE_SET_POINT.name(), + List.of(filter4), + Operator.MULTIPLICATION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.VOLTAGE_SET_POINT.name()).build(), + ReferenceFieldOrValue.builder().value(2.).build()); + + FormulaInfos formulaInfos6 = getFormulaInfo(GeneratorField.PLANNED_ACTIVE_POWER_SET_POINT.name(), + List.of(filter1), + Operator.ADDITION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.PLANNED_ACTIVE_POWER_SET_POINT.name()).build(), + ReferenceFieldOrValue.builder().value(10.).build()); + + FormulaInfos formulaInfos7 = getFormulaInfo(GeneratorField.MINIMUM_ACTIVE_POWER.name(), + List.of(filter1, filter2), + Operator.ADDITION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.MINIMUM_ACTIVE_POWER.name()).build(), + ReferenceFieldOrValue.builder().value(50.).build()); + + FormulaInfos formulaInfos8 = getFormulaInfo(GeneratorField.PLANNED_OUTAGE_RATE.name(), + List.of(filter1), + Operator.MULTIPLICATION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.PLANNED_OUTAGE_RATE.name()).build(), + ReferenceFieldOrValue.builder().value(0.1).build()); + + FormulaInfos formulaInfos9 = getFormulaInfo(GeneratorField.FORCED_OUTAGE_RATE.name(), + List.of(filter1), + Operator.DIVISION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.FORCED_OUTAGE_RATE.name()).build(), + ReferenceFieldOrValue.builder().value(0.05).build()); + + FormulaInfos formulaInfos10 = getFormulaInfo(GeneratorField.MAXIMUM_ACTIVE_POWER.name(), + List.of(filter1, filter2, filter3, filter4, filter5), + Operator.ADDITION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.MAXIMUM_ACTIVE_POWER.name()).build(), + ReferenceFieldOrValue.builder().value(2.).build()); + + FormulaInfos formulaInfos11 = getFormulaInfo(GeneratorField.TRANSIENT_REACTANCE.name(), + List.of(filter2), + Operator.SUBTRACTION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.TRANSIENT_REACTANCE.name()).build(), + ReferenceFieldOrValue.builder().value(0.2).build()); + + FormulaInfos formulaInfos12 = getFormulaInfo(GeneratorField.STEP_UP_TRANSFORMER_REACTANCE.name(), + List.of(filter2), + Operator.MULTIPLICATION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.STEP_UP_TRANSFORMER_REACTANCE.name()).build(), + ReferenceFieldOrValue.builder().value(0.3).build()); + + FormulaInfos formulaInfos13 = getFormulaInfo(GeneratorField.Q_PERCENT.name(), + List.of(filter4), + Operator.DIVISION, + ReferenceFieldOrValue.builder().equipmentField(GeneratorField.Q_PERCENT.name()).build(), + ReferenceFieldOrValue.builder().value(0.25).build()); + + return List.of(formulaInfos1, + formulaInfos2, + formulaInfos3, + formulaInfos4, + formulaInfos5, + formulaInfos6, + formulaInfos7, + formulaInfos8, + formulaInfos9, + formulaInfos10, + formulaInfos11, + formulaInfos12, + formulaInfos13); + } + + @Override + List getUpdatedFormulaInfos() { + var filter1 = FilterInfos.builder() + .id(FILTER_ID_1) + .name("filter1") + .build(); + + var filter2 = FilterInfos.builder() + .id(FILTER_ID_2) + .name("filter2") + .build(); + + var filter3 = FilterInfos.builder() + .id(FILTER_ID_3) + .name("filter3") + .build(); + + FormulaInfos formulaInfos1 = FormulaInfos.builder() + .editedField(GeneratorField.REACTIVE_POWER_SET_POINT.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().value(2.).build()) + .fieldOrValue2(ReferenceFieldOrValue.builder().value(50.).build()) + .operator(Operator.MULTIPLICATION) + .filters(List.of(filter1, filter2)) + .build(); + + FormulaInfos formulaInfos2 = FormulaInfos.builder() + .editedField(GeneratorField.MINIMUM_ACTIVE_POWER.name()) + .fieldOrValue1(ReferenceFieldOrValue.builder().equipmentField(GeneratorField.MINIMUM_ACTIVE_POWER.name()).build()) + .fieldOrValue2(ReferenceFieldOrValue.builder().value(0.5).build()) + .operator(Operator.DIVISION) + .filters(List.of(filter3)) + .build(); + + return List.of(formulaInfos1, formulaInfos2); + } + + @Override + protected void assertAfterNetworkModificationCreation() { + Generator generator1 = getNetwork().getGenerator(GENERATOR_ID_1); + GeneratorStartup generatorStartup1 = generator1.getExtension(GeneratorStartup.class); + assertNotNull(generatorStartup1); + assertEquals(50, generator1.getTargetP(), 0); + assertEquals(15, generatorStartup1.getMarginalCost(), 0); + assertEquals(55, generatorStartup1.getPlannedOutageRate(), 0); + assertEquals(1100, generatorStartup1.getForcedOutageRate(), 0); + assertEquals(50, generatorStartup1.getPlannedActivePowerSetpoint(), 0); + assertEquals(502, generator1.getMaxP(), 0); + assertEquals(50, generator1.getMinP(), 0); + + Generator generator2 = getNetwork().getGenerator(GENERATOR_ID_2); + GeneratorStartup generatorStartup2 = generator2.getExtension(GeneratorStartup.class); + assertNotNull(generatorStartup2); + assertEquals(100, generator2.getTargetP(), 0); + assertEquals(15, generatorStartup2.getMarginalCost(), 0); + assertEquals(55, generatorStartup2.getPlannedOutageRate(), 0); + assertEquals(1100, generatorStartup2.getForcedOutageRate(), 0); + assertEquals(50, generatorStartup2.getPlannedActivePowerSetpoint(), 0); + assertEquals(2002, generator2.getMaxP(), 0); + assertEquals(100, generator2.getMinP(), 0); + + Generator generator3 = getNetwork().getGenerator(GENERATOR_ID_3); + GeneratorShortCircuit generatorShortCircuit3 = generator3.getExtension(GeneratorShortCircuit.class); + assertNotNull(generatorShortCircuit3); + assertEquals(120, generator3.getTargetP(), 0); + assertEquals(39.8, generatorShortCircuit3.getDirectTransX(), 0); + assertEquals(11.4, generatorShortCircuit3.getStepUpTransformerX(), 0); + assertEquals(2002, generator3.getMaxP(), 0); + assertEquals(120, generator3.getMinP(), 0); + + Generator generator4 = getNetwork().getGenerator(GENERATOR_ID_4); + GeneratorShortCircuit generatorShortCircuit4 = generator4.getExtension(GeneratorShortCircuit.class); + assertNotNull(generatorShortCircuit4); + assertEquals(45.8, generatorShortCircuit4.getDirectTransX(), 0); + assertEquals(15.0, generatorShortCircuit4.getStepUpTransformerX(), 0); + assertEquals(160, generator4.getTargetP(), 0); + assertEquals(702, generator4.getMaxP(), 0); + assertEquals(160, generator4.getMinP(), 0); + + Generator generator5 = getNetwork().getGenerator(GENERATOR_ID_5); + ActivePowerControl activePowerControl5 = generator5.getExtension(ActivePowerControl.class); + assertNotNull(activePowerControl5); + assertEquals(2002, generator5.getMaxP(), 0); + assertEquals(4, activePowerControl5.getDroop(), 0); + + Generator generator6 = getNetwork().getGenerator(GENERATOR_ID_6); + ActivePowerControl activePowerControl6 = generator6.getExtension(ActivePowerControl.class); + assertNotNull(activePowerControl6); + assertEquals(502, generator6.getMaxP(), 0); + assertEquals(6, activePowerControl6.getDroop(), 0); + + Generator generator7 = getNetwork().getGenerator(GENERATOR_ID_7); + CoordinatedReactiveControl coordinatedReactiveControl7 = generator7.getExtension(CoordinatedReactiveControl.class); + assertNotNull(coordinatedReactiveControl7); + GeneratorStartup generatorStartup7 = generator7.getExtension(GeneratorStartup.class); + assertNotNull(generatorStartup7); + assertEquals(50, generatorStartup7.getMarginalCost(), 0); + assertEquals(24, coordinatedReactiveControl7.getQPercent(), 0); + + Generator generator8 = getNetwork().getGenerator(GENERATOR_ID_8); + CoordinatedReactiveControl coordinatedReactiveControl8 = generator8.getExtension(CoordinatedReactiveControl.class); + assertNotNull(coordinatedReactiveControl8); + GeneratorStartup generatorStartup8 = generator8.getExtension(GeneratorStartup.class); + assertNotNull(generatorStartup8); + assertEquals(60, generatorStartup8.getMarginalCost(), 0); + assertEquals(48, coordinatedReactiveControl8.getQPercent(), 0); + + assertEquals(40, getNetwork().getGenerator(GENERATOR_ID_9).getRatedS(), 0); + assertEquals(25, getNetwork().getGenerator(GENERATOR_ID_10).getRatedS(), 0); + } + + @Override + protected void assertAfterNetworkModificationDeletion() { + Generator generator1 = getNetwork().getGenerator(GENERATOR_ID_1); + GeneratorStartup generatorStartup1 = generator1.getExtension(GeneratorStartup.class); + assertNotNull(generatorStartup1); + assertEquals(100, generator1.getTargetP(), 0); + assertEquals(30, generatorStartup1.getMarginalCost(), 0); + assertEquals(25, generatorStartup1.getPlannedOutageRate(), 0); + assertEquals(55, generatorStartup1.getForcedOutageRate(), 0); + assertEquals(40, generatorStartup1.getPlannedActivePowerSetpoint(), 0); + assertEquals(500, generator1.getMaxP(), 0); + assertEquals(0, generator1.getMinP(), 0); + + Generator generator2 = getNetwork().getGenerator(GENERATOR_ID_2); + GeneratorStartup generatorStartup2 = generator2.getExtension(GeneratorStartup.class); + assertNotNull(generatorStartup2); + assertEquals(200, generator2.getTargetP(), 0); + assertEquals(30, generatorStartup2.getMarginalCost(), 0); + assertEquals(25, generatorStartup2.getPlannedOutageRate(), 0); + assertEquals(55, generatorStartup2.getForcedOutageRate(), 0); + assertEquals(40, generatorStartup2.getPlannedActivePowerSetpoint(), 0); + assertEquals(2000, generator2.getMaxP(), 0); + assertEquals(50, generator2.getMinP(), 0); + + Generator generator3 = getNetwork().getGenerator(GENERATOR_ID_3); + GeneratorShortCircuit generatorShortCircuit3 = generator3.getExtension(GeneratorShortCircuit.class); + assertNotNull(generatorShortCircuit3); + assertEquals(300, generator3.getTargetP(), 0); + assertEquals(40, generatorShortCircuit3.getDirectTransX(), 0); + assertEquals(38, generatorShortCircuit3.getStepUpTransformerX(), 0); + assertEquals(2000, generator3.getMaxP(), 0); + assertEquals(70, generator3.getMinP(), 0); + + Generator generator4 = getNetwork().getGenerator(GENERATOR_ID_4); + GeneratorShortCircuit generatorShortCircuit4 = generator4.getExtension(GeneratorShortCircuit.class); + assertNotNull(generatorShortCircuit4); + assertEquals(46, generatorShortCircuit4.getDirectTransX(), 0); + assertEquals(50, generatorShortCircuit4.getStepUpTransformerX(), 0); + assertEquals(400, generator4.getTargetP(), 0); + assertEquals(700, generator4.getMaxP(), 0); + assertEquals(110, generator4.getMinP(), 0); + + Generator generator5 = getNetwork().getGenerator(GENERATOR_ID_5); + ActivePowerControl activePowerControl5 = generator5.getExtension(ActivePowerControl.class); + assertNotNull(activePowerControl5); + assertEquals(2000, generator5.getMaxP(), 0); + assertEquals(2, activePowerControl5.getDroop(), 0); + + Generator generator6 = getNetwork().getGenerator(GENERATOR_ID_6); + ActivePowerControl activePowerControl6 = generator6.getExtension(ActivePowerControl.class); + assertNotNull(activePowerControl6); + assertEquals(500, generator6.getMaxP(), 0); + assertEquals(3, activePowerControl6.getDroop(), 0); + + Generator generator7 = getNetwork().getGenerator(GENERATOR_ID_7); + CoordinatedReactiveControl coordinatedReactiveControl7 = generator7.getExtension(CoordinatedReactiveControl.class); + assertNotNull(coordinatedReactiveControl7); + GeneratorStartup generatorStartup7 = generator7.getExtension(GeneratorStartup.class); + assertNotNull(generatorStartup7); + assertEquals(50, generatorStartup7.getMarginalCost(), 0); + assertEquals(6, coordinatedReactiveControl7.getQPercent(), 0); + + Generator generator8 = getNetwork().getGenerator(GENERATOR_ID_8); + CoordinatedReactiveControl coordinatedReactiveControl8 = generator8.getExtension(CoordinatedReactiveControl.class); + assertNotNull(coordinatedReactiveControl8); + GeneratorStartup generatorStartup8 = generator8.getExtension(GeneratorStartup.class); + assertNotNull(generatorStartup8); + assertEquals(60, generatorStartup8.getMarginalCost(), 0); + assertEquals(12, coordinatedReactiveControl8.getQPercent(), 0); + + assertEquals(60, getNetwork().getGenerator(GENERATOR_ID_9).getRatedS(), 0); + assertEquals(30, getNetwork().getGenerator(GENERATOR_ID_10).getRatedS(), 0); + } +}