LimitViolationDetectionTest.java
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.security;
import com.powsybl.contingency.ContingencyContext;
import com.powsybl.iidm.criteria.NetworkElementIdListCriterion;
import com.powsybl.iidm.criteria.duration.EqualityTemporaryDurationCriterion;
import com.powsybl.iidm.criteria.duration.PermanentDurationCriterion;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.limitmodification.LimitsComputer;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.test.FourSubstationsNodeBreakerFactory;
import com.powsybl.security.detectors.AbstractLimitViolationDetectionTest;
import com.powsybl.security.detectors.LoadingLimitType;
import com.powsybl.security.limitreduction.DefaultLimitReductionsApplier;
import com.powsybl.security.limitreduction.LimitReduction;
import com.powsybl.security.limitreduction.SimpleLimitsComputer;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Olivier Perrin {@literal <olivier.perrin at rte-france.com>}
*/
class LimitViolationDetectionTest extends AbstractLimitViolationDetectionTest {
@BeforeEach
void setUp() {
violationsCollector = new ArrayList<>();
}
@Override
protected void checkLimitViolation(Branch<?> branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer,
LimitType limitType, double limitReduction) {
LimitViolationDetection.checkLimitViolation(branch, side, currentValue, limitType, EnumSet.allOf(LoadingLimitType.class), new SimpleLimitsComputer(limitReduction), consumer
);
}
private void checkLimitViolation(ThreeWindingsTransformer transfo, ThreeSides side, double currentValue, Consumer<LimitViolation> consumer,
LimitType limitType) {
LimitViolationDetection.checkLimitViolation(transfo, side, currentValue, limitType, EnumSet.allOf(LoadingLimitType.class), new LimitsComputer.NoModificationsImpl(), consumer);
}
private void checkLimitViolation(Branch<?> branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer,
LimitType limitType, LimitsComputer<Identifiable<?>, LoadingLimits> limitsComputer) {
LimitViolationDetection.checkLimitViolation(branch, side, currentValue, limitType, EnumSet.allOf(LoadingLimitType.class), limitsComputer, consumer);
}
protected void checkCurrent(Branch<?> branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer, LimitsComputer<Identifiable<?>, LoadingLimits> limitsComputer) {
checkLimitViolation(branch, side, currentValue, consumer, LimitType.CURRENT, limitsComputer);
}
@Override
protected void checkCurrent(Branch<?> branch, TwoSides side, double currentValue, Consumer<LimitViolation> consumer) {
checkLimitViolation(branch, side, currentValue, consumer, LimitType.CURRENT, 1.0);
}
@Override
protected void checkCurrent(ThreeWindingsTransformer transfo, ThreeSides side, double currentValue, Consumer<LimitViolation> consumer) {
checkLimitViolation(transfo, side, currentValue, consumer, LimitType.CURRENT);
}
@Override
protected void checkActivePower(Branch<?> branch, TwoSides side, double value, Consumer<LimitViolation> consumer) {
checkLimitViolation(branch, side, value, consumer, LimitType.ACTIVE_POWER, 1.0);
}
@Override
protected void checkActivePower(ThreeWindingsTransformer transfo, ThreeSides side, double value, Consumer<LimitViolation> consumer) {
checkLimitViolation(transfo, side, value, consumer, LimitType.ACTIVE_POWER);
}
@Override
protected void checkApparentPower(Branch<?> branch, TwoSides side, double value, Consumer<LimitViolation> consumer) {
checkLimitViolation(branch, side, value, consumer, LimitType.APPARENT_POWER, 1.0);
}
@Override
protected void checkApparentPower(ThreeWindingsTransformer transfo, ThreeSides side, double value, Consumer<LimitViolation> consumer) {
checkLimitViolation(transfo, side, value, consumer, LimitType.APPARENT_POWER);
}
@Override
protected void checkVoltage(Bus b, int voltageValue, Consumer<LimitViolation> consumer) {
LimitViolationDetection.checkVoltage(b, voltageValue, consumer);
}
@Override
protected void checkVoltageAngle(VoltageAngleLimit voltageAngleLimit, double voltageAngleDifference, Consumer<LimitViolation> consumer) {
LimitViolationDetection.checkVoltageAngle(voltageAngleLimit, voltageAngleDifference, consumer);
}
@Test
void testVoltageViolationDetection() {
Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
Bus mergedBus = getMergedBusFromConfiguredBusId(network, "NHV2");
LimitViolationDetection.checkVoltage(mergedBus, 620, violationsCollector::add);
Assertions.assertThat(violationsCollector)
.hasSize(1)
.allSatisfy(l -> {
assertEquals(LimitViolationType.HIGH_VOLTAGE, violationsCollector.get(0).getLimitType());
assertEquals(620.0, violationsCollector.get(0).getValue());
assertEquals(500, violationsCollector.get(0).getLimit());
assertEquals("VLHV2", violationsCollector.get(0).getSubjectId());
});
}
@Test
void testVoltageViolationDetectionWithDetailLimitViolationId() {
Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
// Add a new bus "NHV20" which will be merged with "NHV2" in the bs view
VoltageLevel vlh2 = network.getVoltageLevel("VLHV2");
vlh2.getBusBreakerView().newBus()
.setId("NHV20")
.add();
vlh2.getBusBreakerView().newSwitch()
.setBus1("NHV2").setBus2("NHV20")
.setOpen(false).setId("DJ")
.add();
// Retrieve the merged bus containing "NHV2"
Bus mergedBus = getMergedBusFromConfiguredBusId(network, "NHV2");
// Check the voltage on this merged bus
LimitViolationDetection.checkVoltage(mergedBus, 620, violationsCollector::add);
Assertions.assertThat(violationsCollector)
.hasSize(1)
.allSatisfy(l -> {
assertEquals(LimitViolationType.HIGH_VOLTAGE, violationsCollector.get(0).getLimitType());
assertEquals(620.0, violationsCollector.get(0).getValue());
assertEquals(500, violationsCollector.get(0).getLimit());
Assertions.assertThat(violationsCollector.get(0).getViolationLocation())
.isPresent()
.get()
.isInstanceOfSatisfying(BusBreakerViolationLocation.class,
vli -> {
assertEquals(ViolationLocation.Type.BUS_BREAKER, vli.getType());
assertEquals(2, vli.getBusIds().size());
assertTrue(vli.getBusIds().containsAll(List.of("NHV2", "NHV20"))); // Both configured buses are present
Set<Bus> buses = vli.getBusBreakerView(network).getBusStream().collect(Collectors.toSet());
assertEquals(2, buses.size());
assertTrue(buses.contains(network.getBusBreakerView().getBus("NHV2")));
assertTrue(buses.contains(network.getBusBreakerView().getBus("NHV20")));
Set<Bus> mergedBuses = vli.getBusView(network).getBusStream().collect(Collectors.toSet());
assertEquals(1, mergedBuses.size());
});
});
}
private Bus getMergedBusFromConfiguredBusId(Network network, String busId) {
Bus b = (Bus) network.getIdentifiable(busId);
return b.getVoltageLevel().getBusView().getMergedBus(busId);
}
@Test
void testVoltageViolationDetectionWithBusBarIds() {
Network network = FourSubstationsNodeBreakerFactory.create();
LimitViolationDetection.checkVoltage(network.getBusView().getBus("S1VL1_0"), 300,
violationsCollector::add);
Assertions.assertThat(violationsCollector)
.hasSize(1)
.allSatisfy(l -> {
assertEquals(LimitViolationType.HIGH_VOLTAGE, violationsCollector.get(0).getLimitType());
assertEquals(300, violationsCollector.get(0).getValue());
assertEquals(240, violationsCollector.get(0).getLimit());
Assertions.assertThat(violationsCollector.get(0).getViolationLocation())
.isPresent()
.get()
.isInstanceOfSatisfying(NodeBreakerViolationLocation.class,
vli -> {
assertEquals(ViolationLocation.Type.NODE_BREAKER, vli.getType());
assertEquals("S1VL1", vli.getVoltageLevelId());
Assertions.assertThat(vli.getNodes())
.hasSize(5)
.isEqualTo(List.of(0, 1, 2, 3, 4));
assertThrows(UnsupportedOperationException.class, () -> vli.getBusBreakerView(network));
Set<Bus> mergedBuses = vli.getBusView(network).getBusStream().collect(Collectors.toSet());
assertEquals(1, mergedBuses.size());
assertTrue(mergedBuses.contains(network.getBusView().getBus("S1VL1_0")));
});
});
// Check corresponding busbar sections
ViolationLocation violationLocation = violationsCollector.get(0).getViolationLocation().orElseThrow();
NodeBreakerViolationLocation vloc = (NodeBreakerViolationLocation) violationLocation;
VoltageLevel vl = network.getVoltageLevel(vloc.getVoltageLevelId());
List<String> busbarSectionIds = vloc.getNodes().stream()
.map(node -> vl.getNodeBreakerView().getTerminal(node))
.filter(Objects::nonNull)
.map(Terminal::getConnectable)
.filter(t -> t.getType() == IdentifiableType.BUSBAR_SECTION)
.map(Identifiable::getId)
.toList();
assertEquals(1, busbarSectionIds.size());
assertEquals("S1VL1_BBS", busbarSectionIds.get(0));
}
@Test
void testPermanentLimitLimitsComputer() {
Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
List<LimitReduction> limitReductionList = new ArrayList<>();
LimitReduction reduction1 = LimitReduction.builder(LimitType.CURRENT, 0.5)
.withMonitoringOnly(false)
.withContingencyContext(ContingencyContext.none())
.withNetworkElementCriteria(new NetworkElementIdListCriterion(Set.of("NHV1_NHV2_1")))
.withLimitDurationCriteria(new PermanentDurationCriterion())
.build();
limitReductionList.add(reduction1);
DefaultLimitReductionsApplier computer = new DefaultLimitReductionsApplier(limitReductionList);
checkCurrent(network.getLine("NHV1_NHV2_1"), TwoSides.ONE, 315, violationsCollector::add, computer);
Assertions.assertThat(violationsCollector)
.hasSize(1)
.allSatisfy(l -> {
assertEquals(0.5, violationsCollector.get(0).getLimitReduction());
assertEquals(Integer.MAX_VALUE, violationsCollector.get(0).getAcceptableDuration());
assertEquals(315, violationsCollector.get(0).getValue(), 0.01);
assertEquals(500., violationsCollector.get(0).getLimit(), 0.01);
assertEquals(ThreeSides.ONE, violationsCollector.get(0).getSide());
});
}
@Test
void testTemporaryLimitLimitsComputer() {
Network network = EurostagTutorialExample1Factory.createWithFixedCurrentLimits();
List<LimitReduction> limitReductionList = new ArrayList<>();
LimitReduction reduction1 = LimitReduction.builder(LimitType.CURRENT, 0.5)
.withMonitoringOnly(false)
.withContingencyContext(ContingencyContext.none())
.withNetworkElementCriteria(new NetworkElementIdListCriterion(Set.of("NHV1_NHV2_1")))
.withLimitDurationCriteria(new EqualityTemporaryDurationCriterion(60))
.build();
limitReductionList.add(reduction1);
DefaultLimitReductionsApplier computer = new DefaultLimitReductionsApplier(limitReductionList);
checkCurrent(network.getLine("NHV1_NHV2_1"), TwoSides.TWO, 751, violationsCollector::add, computer);
assertEquals(1, violationsCollector.size());
assertEquals(0.5, violationsCollector.get(0).getLimitReduction());
assertEquals(0, violationsCollector.get(0).getAcceptableDuration());
assertEquals(751, violationsCollector.get(0).getValue(), 0.01);
assertEquals(1500, violationsCollector.get(0).getLimit(), 0.01);
assertEquals(ThreeSides.TWO, violationsCollector.get(0).getSide());
assertEquals("1'", violationsCollector.get(0).getLimitName());
}
}