com.powsybl.iidm.modification.topology.TopologyModificationUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of powsybl-iidm-modification Show documentation
Show all versions of powsybl-iidm-modification Show documentation
The network modification API and a set of classes implementing it
/**
* Copyright (c) 2022, 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.iidm.modification.topology;
import com.google.common.collect.ImmutableList;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.commons.report.TypedValue;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.extensions.BusbarSectionPosition;
import com.powsybl.iidm.network.extensions.ConnectablePosition;
import com.powsybl.math.graph.TraverseResult;
import org.apache.commons.lang3.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.powsybl.iidm.modification.util.ModificationReports.*;
/**
* @author Miora Vedelago {@literal }
*/
public final class TopologyModificationUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(TopologyModificationUtils.class);
private TopologyModificationUtils() {
}
public static final class LoadingLimitsBags {
private final LoadingLimitsBag activePowerLimits;
private final LoadingLimitsBag apparentPowerLimits;
private final LoadingLimitsBag currentLimits;
public LoadingLimitsBags(Supplier> activePowerLimitsGetter, Supplier> apparentPowerLimitsGetter,
Supplier> currentLimitsGetter) {
activePowerLimits = activePowerLimitsGetter.get().map(LoadingLimitsBag::new).orElse(null);
apparentPowerLimits = apparentPowerLimitsGetter.get().map(LoadingLimitsBag::new).orElse(null);
currentLimits = currentLimitsGetter.get().map(LoadingLimitsBag::new).orElse(null);
}
LoadingLimitsBags(LoadingLimitsBag activePowerLimits, LoadingLimitsBag apparentPowerLimits, LoadingLimitsBag currentLimits) {
this.activePowerLimits = activePowerLimits;
this.apparentPowerLimits = apparentPowerLimits;
this.currentLimits = currentLimits;
}
Optional getActivePowerLimits() {
return Optional.ofNullable(activePowerLimits);
}
Optional getApparentPowerLimits() {
return Optional.ofNullable(apparentPowerLimits);
}
Optional getCurrentLimits() {
return Optional.ofNullable(currentLimits);
}
}
private static final class LoadingLimitsBag {
private final double permanentLimit;
private List temporaryLimits = new ArrayList<>();
private LoadingLimitsBag(LoadingLimits limits) {
this.permanentLimit = limits.getPermanentLimit();
for (LoadingLimits.TemporaryLimit tl : limits.getTemporaryLimits()) {
temporaryLimits.add(new TemporaryLimitsBag(tl));
}
}
private LoadingLimitsBag(double permanentLimit, List temporaryLimitsBags) {
this.permanentLimit = permanentLimit;
this.temporaryLimits = temporaryLimitsBags;
}
private double getPermanentLimit() {
return permanentLimit;
}
private List getTemporaryLimits() {
return ImmutableList.copyOf(temporaryLimits);
}
}
private static final class TemporaryLimitsBag {
private final String name;
private final int acceptableDuration;
private final boolean fictitious;
private final double value;
TemporaryLimitsBag(LoadingLimits.TemporaryLimit temporaryLimit) {
this.name = temporaryLimit.getName();
this.acceptableDuration = temporaryLimit.getAcceptableDuration();
this.fictitious = temporaryLimit.isFictitious();
this.value = temporaryLimit.getValue();
}
private String getName() {
return name;
}
private int getAcceptableDuration() {
return acceptableDuration;
}
private boolean isFictitious() {
return fictitious;
}
private double getValue() {
return value;
}
}
static LineAdder createLineAdder(double percent, String id, String name, String voltageLevelId1, String voltageLevelId2, Network network, Line line) {
return network.newLine()
.setId(id)
.setName(name)
.setVoltageLevel1(voltageLevelId1)
.setVoltageLevel2(voltageLevelId2)
.setR(line.getR() * percent / 100)
.setX(line.getX() * percent / 100)
.setG1(line.getG1() * percent / 100)
.setB1(line.getB1() * percent / 100)
.setG2(line.getG2() * percent / 100)
.setB2(line.getB2() * percent / 100);
}
static LineAdder createLineAdder(String id, String name, String voltageLevelId1, String voltageLevelId2, Network network, Line line1, Line line2) {
return network.newLine()
.setId(id)
.setName(name)
.setVoltageLevel1(voltageLevelId1)
.setVoltageLevel2(voltageLevelId2)
.setR(line1.getR() + line2.getR())
.setX(line1.getX() + line2.getX())
.setG1(line1.getG1() + line2.getG1())
.setB1(line1.getB1() + line2.getB1())
.setG2(line1.getG2() + line2.getG2())
.setB2(line1.getB2() + line2.getB2());
}
static void attachLine(Terminal terminal, LineAdder adder, BiConsumer connectableBusSetter,
BiConsumer busSetter, BiConsumer nodeSetter) {
if (terminal.getVoltageLevel().getTopologyKind() == TopologyKind.BUS_BREAKER) {
connectableBusSetter.accept(terminal.getBusBreakerView().getConnectableBus(), adder);
Bus bus = terminal.getBusBreakerView().getBus();
if (bus != null) {
busSetter.accept(bus, adder);
}
} else if (terminal.getVoltageLevel().getTopologyKind() == TopologyKind.NODE_BREAKER) {
int node = terminal.getNodeBreakerView().getNode();
nodeSetter.accept(node, adder);
} else {
throw new IllegalStateException();
}
}
public static void addLoadingLimits(Line created, LoadingLimitsBags limits, TwoSides side) {
if (side == TwoSides.ONE) {
limits.getActivePowerLimits().ifPresent(lim -> addLoadingLimits(created.newActivePowerLimits1(), lim));
limits.getApparentPowerLimits().ifPresent(lim -> addLoadingLimits(created.newApparentPowerLimits1(), lim));
limits.getCurrentLimits().ifPresent(lim -> addLoadingLimits(created.newCurrentLimits1(), lim));
} else {
limits.getActivePowerLimits().ifPresent(lim -> addLoadingLimits(created.newActivePowerLimits2(), lim));
limits.getApparentPowerLimits().ifPresent(lim -> addLoadingLimits(created.newApparentPowerLimits2(), lim));
limits.getCurrentLimits().ifPresent(lim -> addLoadingLimits(created.newCurrentLimits2(), lim));
}
}
private static > void addLoadingLimits(A adder, LoadingLimitsBag limits) {
adder.setPermanentLimit(limits.getPermanentLimit());
for (TemporaryLimitsBag tl : limits.getTemporaryLimits()) {
adder.beginTemporaryLimit()
.setName(tl.getName())
.setAcceptableDuration(tl.getAcceptableDuration())
.setFictitious(tl.isFictitious())
.setValue(tl.getValue())
.endTemporaryLimit();
}
adder.add();
}
static void removeVoltageLevelAndSubstation(VoltageLevel voltageLevel, ReportNode reportNode) {
Optional substation = voltageLevel.getSubstation();
String vlId = voltageLevel.getId();
boolean noMoreEquipments = voltageLevel.getConnectableStream().noneMatch(c -> c.getType() != IdentifiableType.BUSBAR_SECTION);
if (!noMoreEquipments) {
voltageLevelRemovingEquipmentsLeftReport(reportNode, vlId);
LOGGER.warn("Voltage level {} still contains equipments", vlId);
}
voltageLevel.remove();
voltageLevelRemovedReport(reportNode, vlId);
LOGGER.info("Voltage level {} removed", vlId);
substation.ifPresent(s -> {
if (s.getVoltageLevelStream().count() == 0) {
String substationId = s.getId();
s.remove();
substationRemovedReport(reportNode, substationId);
LOGGER.info("Substation {} removed", substationId);
}
});
}
static void createNBBreaker(int node1, int node2, String id, VoltageLevel.NodeBreakerView view, boolean open) {
view.newSwitch()
.setId(id)
.setEnsureIdUnicity(true)
.setKind(SwitchKind.BREAKER)
.setOpen(open)
.setRetained(true)
.setNode1(node1)
.setNode2(node2)
.add();
}
static void createNBDisconnector(int node1, int node2, String id, VoltageLevel.NodeBreakerView view, boolean open) {
view.newSwitch()
.setId(id)
.setEnsureIdUnicity(true)
.setKind(SwitchKind.DISCONNECTOR)
.setOpen(open)
.setNode1(node1)
.setNode2(node2)
.add();
}
static void createBusBreakerSwitch(String busId1, String busId2, String id, VoltageLevel.BusBreakerView view) {
view.newSwitch()
.setId(id)
.setEnsureIdUnicity(true)
.setOpen(false)
.setBus1(busId1)
.setBus2(busId2)
.add();
}
/**
* Utility method that associates a busbar section position index to the orders taken by all the connectables
* of the busbar sections of this index.
**/
static NavigableMap> getSliceOrdersMap(VoltageLevel voltageLevel) {
// Compute the map of connectables by busbar sections
Map>> connectablesByBbs = new LinkedHashMap<>();
voltageLevel.getConnectableStream(BusbarSection.class)
.forEach(bbs -> fillConnectablesMap(bbs, connectablesByBbs));
// Merging the map by section index
Map>> connectablesBySectionIndex = new LinkedHashMap<>();
connectablesByBbs.forEach((bbs, connectables) -> {
BusbarSectionPosition bbPosition = bbs.getExtension(BusbarSectionPosition.class);
if (bbPosition != null) {
connectablesBySectionIndex.merge(bbPosition.getSectionIndex(), connectables, (l1, l2) -> {
l1.addAll(l2);
return l1;
});
}
});
// Get the orders corresponding map
TreeMap> ordersBySectionIndex = new TreeMap<>();
connectablesBySectionIndex.forEach((sectionIndex, connectables) -> {
List orders = new ArrayList<>();
connectables.forEach(connectable -> addOrderPositions(connectable, voltageLevel, orders));
ordersBySectionIndex.put(sectionIndex, orders);
});
return ordersBySectionIndex;
}
/**
* Method that fills the map connectablesByBbs with all the connectables of a busbar section.
*/
static void fillConnectablesMap(BusbarSection bbs, Map>> connectablesByBbs) {
BusbarSectionPosition bbPosition = bbs.getExtension(BusbarSectionPosition.class);
int bbSection = bbPosition.getSectionIndex();
if (connectablesByBbs.containsKey(bbs)) {
return;
}
Set> connectables = connectablesByBbs.compute(bbs, (k, v) -> new LinkedHashSet<>());
bbs.getTerminal().traverse(new Terminal.TopologyTraverser() {
@Override
public TraverseResult traverse(Terminal terminal, boolean connected) {
if (terminal.getVoltageLevel() != bbs.getTerminal().getVoltageLevel()) {
return TraverseResult.TERMINATE_PATH;
}
Connectable> connectable = terminal.getConnectable();
if (connectable instanceof BusbarSection otherBbs) {
BusbarSectionPosition otherBbPosition = otherBbs.getExtension(BusbarSectionPosition.class);
if (otherBbPosition.getSectionIndex() == bbSection) {
connectablesByBbs.put(otherBbs, connectables);
} else {
return TraverseResult.TERMINATE_PATH;
}
}
connectables.add(connectable);
return TraverseResult.CONTINUE;
}
@Override
public TraverseResult traverse(Switch aSwitch) {
return TraverseResult.CONTINUE;
}
});
}
/**
* Get the list of parallel busbar sections on a given busbar section position
*
* @param voltageLevel Voltage level in which to find the busbar sections
* @param position busbar section position according to which busbar sections are found
* @return the list of busbar sections in the voltage level that have the same section position as the given position
*/
static List getParallelBusbarSections(VoltageLevel voltageLevel, BusbarSectionPosition position) {
// List of the bars for the second section
return voltageLevel.getNodeBreakerView().getBusbarSectionStream()
.filter(b -> b.getExtension(BusbarSectionPosition.class) != null)
.filter(b -> b.getExtension(BusbarSectionPosition.class).getSectionIndex() == position.getSectionIndex()).toList();
}
/**
* Creates a breaker and a disconnector between the connectable and the specified busbar
*/
static void createNodeBreakerSwitchesTopology(VoltageLevel voltageLevel, int connectableNode, int forkNode, NamingStrategy namingStrategy, String baseId, BusbarSection bbs) {
createNodeBreakerSwitchesTopology(voltageLevel, connectableNode, forkNode, namingStrategy, baseId, List.of(bbs), bbs);
}
/**
* Creates open disconnectors between the fork node and every busbar section of the list in a voltage level
*/
static void createNodeBreakerSwitchesTopology(VoltageLevel voltageLevel, int connectableNode, int forkNode, NamingStrategy namingStrategy, String baseId, List bbsList, BusbarSection bbs) {
// Closed breaker
createNBBreaker(connectableNode, forkNode, namingStrategy.getBreakerId(baseId), voltageLevel.getNodeBreakerView(), false);
// Disconnectors - only the one on the chosen busbarsection is closed
createDisconnectorTopology(voltageLevel, forkNode, namingStrategy, baseId, bbsList, bbs);
}
/**
* Creates disconnectors between the fork node and every busbar section of the list in a voltage level. Each disconnector will be closed if it is connected to the given bar, else opened
*/
static void createDisconnectorTopology(VoltageLevel voltageLevel, int forkNode, NamingStrategy namingStrategy, String baseId, List bbsList, BusbarSection bbs) {
createDisconnectorTopology(voltageLevel, forkNode, namingStrategy, baseId, bbsList, bbs, 0);
}
/**
* Creates disconnectors between the fork node and every busbar section of the list in a voltage level. Each disconnector will be closed if it is connected to the given bar, else opened
*/
static void createDisconnectorTopology(VoltageLevel voltageLevel, int forkNode, NamingStrategy namingStrategy, String baseId, List bbsList, BusbarSection bbs, int side) {
// Disconnectors - only the one on the chosen busbarsection is closed
bbsList.forEach(b -> {
int bbsNode = b.getTerminal().getNodeBreakerView().getNode();
createNBDisconnector(forkNode, bbsNode, namingStrategy.getDisconnectorId(b, baseId, forkNode, bbsNode, side), voltageLevel.getNodeBreakerView(), b != bbs);
});
}
/**
* Get all the unused positions before the lowest used position on the busbar section bbs.
* It is a range between the maximum used position on the busbar section with the highest section index lower than the section
* index of the given busbar section and the minimum position on the given busbar section.
* For two busbar sections with following indexes BBS1 with used orders 1,2,3 and BBS2 with used orders 7,8, this method
* applied to BBS2 will return a range from 4 to 6.
*/
public static Optional> getUnusedOrderPositionsBefore(BusbarSection bbs) {
BusbarSectionPosition busbarSectionPosition = bbs.getExtension(BusbarSectionPosition.class);
if (busbarSectionPosition == null) {
throw new PowsyblException("busbarSection has no BusbarSectionPosition extension");
}
VoltageLevel voltageLevel = bbs.getTerminal().getVoltageLevel();
NavigableMap> allOrders = getSliceOrdersMap(voltageLevel);
int sectionIndex = busbarSectionPosition.getSectionIndex();
Optional previousSliceMax = getMaxOrderUsedBefore(allOrders, sectionIndex);
Optional sliceMin = allOrders.get(sectionIndex).stream().min(Comparator.naturalOrder());
int min = previousSliceMax.map(o -> o + 1).orElse(0);
int max = sliceMin.or(() -> getMinOrderUsedAfter(allOrders, sectionIndex)).map(o -> o - 1).orElse(Integer.MAX_VALUE);
return Optional.ofNullable(min <= max ? Range.of(min, max) : null);
}
/**
* Get all the unused positions after the highest used position on the busbar section bbs.
* It is a range between the minimum used position on the busbar section with the lowest section index higher than the section
* index of the given busbar section and the maximum position on the given busbar section.
* For two busbar sections with following indexes BBS1 with used orders 1,2,3 and BBS2 with used orders 7,8, this method
* applied to BBS1 will return a range from 4 to 6.
*/
public static Optional> getUnusedOrderPositionsAfter(BusbarSection bbs) {
BusbarSectionPosition busbarSectionPosition = bbs.getExtension(BusbarSectionPosition.class);
if (busbarSectionPosition == null) {
throw new PowsyblException("busbarSection has no BusbarSectionPosition extension");
}
VoltageLevel voltageLevel = bbs.getTerminal().getVoltageLevel();
NavigableMap> allOrders = getSliceOrdersMap(voltageLevel);
int sectionIndex = busbarSectionPosition.getSectionIndex();
Optional nextSliceMin = getMinOrderUsedAfter(allOrders, sectionIndex);
Optional sliceMax = allOrders.get(sectionIndex).stream().max(Comparator.naturalOrder());
int min = sliceMax.or(() -> getMaxOrderUsedBefore(allOrders, sectionIndex)).map(o -> o + 1).orElse(0);
int max = nextSliceMin.map(o -> o - 1).orElse(Integer.MAX_VALUE);
return Optional.ofNullable(min <= max ? Range.of(min, max) : null);
}
/**
* Get the range of connectable positions delimited by neighbouring busbar sections, for a given busbar section.
* If the range is empty (for instance if positions max on left side is above position min on right side), the range returned is empty.
* Note that the connectable positions needs to be in ascending order in the voltage level for ascending busbar section index positions.
*/
public static Optional> getPositionRange(BusbarSection bbs) {
BusbarSectionPosition positionExtension = bbs.getExtension(BusbarSectionPosition.class);
if (positionExtension != null) {
VoltageLevel voltageLevel = bbs.getTerminal().getVoltageLevel();
NavigableMap> allOrders = getSliceOrdersMap(voltageLevel);
int sectionIndex = positionExtension.getSectionIndex();
int max = getMinOrderUsedAfter(allOrders, sectionIndex).map(o -> o - 1).orElse(Integer.MAX_VALUE);
int min = getMaxOrderUsedBefore(allOrders, sectionIndex).map(o -> o + 1).orElse(0);
return Optional.ofNullable(min <= max ? Range.of(min, max) : null);
}
return Optional.of(Range.of(0, Integer.MAX_VALUE));
}
/**
* Method returning the maximum order in the slice with the highest section index lower to the given section.
* For two busbar sections with following indexes BBS1 with used orders 1,2,3 and BBS2 with used orders 7,8, this method
* applied to BBS2 will return 3.
*/
public static Optional getMaxOrderUsedBefore(NavigableMap> allOrders, int section) {
int s = section;
Map.Entry> lowerEntry;
do {
lowerEntry = allOrders.lowerEntry(s);
if (lowerEntry == null) {
break;
}
s = lowerEntry.getKey();
} while (lowerEntry.getValue().isEmpty());
return Optional.ofNullable(lowerEntry)
.flatMap(entry -> entry.getValue().stream().max(Comparator.naturalOrder()));
}
/**
* Method returning the minimum order in the slice with the lowest section index higher to the given section.
* For two busbar sections with following indexes BBS1 with used orders 1,2,3 and BBS2 with used orders 7,8, this method
* applied to BBS1 will return 7.
*/
public static Optional getMinOrderUsedAfter(NavigableMap> allOrders, int section) {
int s = section;
Map.Entry> higherEntry;
do {
higherEntry = allOrders.higherEntry(s);
if (higherEntry == null) {
break;
}
s = higherEntry.getKey();
} while (higherEntry.getValue().isEmpty());
return Optional.ofNullable(higherEntry)
.flatMap(entry -> entry.getValue().stream().min(Comparator.naturalOrder()));
}
/**
* Utility method to get all the taken feeder positions on a voltage level.
*/
public static Set getFeederPositions(VoltageLevel voltageLevel) {
Set feederPositionsOrders = new HashSet<>();
voltageLevel.getConnectables().forEach(connectable -> addOrderPositions(connectable, voltageLevel, feederPositionsOrders));
return feederPositionsOrders;
}
/**
* Utility method to get all the taken feeder positions on a voltage level by connectable.
*/
public static Map> getFeederPositionsByConnectable(VoltageLevel voltageLevel) {
Map> feederPositionsOrders = new HashMap<>();
getFeedersByConnectable(voltageLevel).forEach((k, v) -> {
List orders = new ArrayList<>();
v.forEach(feeder -> feeder.getOrder().ifPresent(orders::add));
if (orders.size() > 1) {
Collections.sort(orders);
}
feederPositionsOrders.put(k, orders);
});
return feederPositionsOrders;
}
private static void addOrderPositions(Connectable> connectable, VoltageLevel voltageLevel, Collection feederPositionsOrders) {
addOrderPositions(connectable, voltageLevel, feederPositionsOrders, false, ReportNode.NO_OP);
}
/**
* Method adding order position(s) of a connectable on a given voltage level to the given collection.
*/
private static void addOrderPositions(Connectable> connectable, VoltageLevel voltageLevel, Collection feederPositionsOrders, boolean throwException, ReportNode reportNode) {
ConnectablePosition> position = (ConnectablePosition>) connectable.getExtension(ConnectablePosition.class);
if (position != null) {
List orders = getOrderPositions(position, voltageLevel, connectable, throwException, reportNode);
feederPositionsOrders.addAll(orders);
}
}
/**
* Utility method to get all the feeders on a voltage level by connectable.
*/
public static Map> getFeedersByConnectable(VoltageLevel voltageLevel) {
Map> feedersByConnectable = new HashMap<>();
voltageLevel.getConnectables().forEach(connectable -> {
ConnectablePosition> position = (ConnectablePosition>) connectable.getExtension(ConnectablePosition.class);
if (position != null) {
List feeder = getFeeders(position, voltageLevel, connectable, false, ReportNode.NO_OP);
feedersByConnectable.put(connectable.getId(), feeder);
}
});
return feedersByConnectable;
}
private static List getOrderPositions(ConnectablePosition> position, VoltageLevel voltageLevel, Connectable> connectable, boolean throwException, ReportNode reportNode) {
List feeders;
if (connectable instanceof Injection) {
feeders = getInjectionFeeder(position);
} else if (connectable instanceof Branch) {
feeders = getBranchFeeders(position, voltageLevel, (Branch>) connectable);
} else if (connectable instanceof ThreeWindingsTransformer twt) {
feeders = get3wtFeeders(position, voltageLevel, twt);
} else {
LOGGER.error("Given connectable not supported: {}", connectable.getClass().getName());
connectableNotSupported(reportNode, connectable);
if (throwException) {
throw new IllegalStateException("Given connectable not supported: " + connectable.getClass().getName());
}
return Collections.emptyList();
}
List orders = new ArrayList<>();
feeders.forEach(feeder -> feeder.getOrder().ifPresent(orders::add));
if (orders.size() > 1) {
Collections.sort(orders);
}
return orders;
}
private static List getFeeders(ConnectablePosition> position, VoltageLevel voltageLevel, Connectable> connectable, boolean throwException, ReportNode reportNode) {
if (connectable instanceof Injection) {
return getInjectionFeeder(position);
} else if (connectable instanceof Branch) {
return getBranchFeeders(position, voltageLevel, (Branch>) connectable);
} else if (connectable instanceof ThreeWindingsTransformer twt) {
return get3wtFeeders(position, voltageLevel, twt);
} else {
LOGGER.error("Given connectable not supported: {}", connectable.getClass().getName());
connectableNotSupported(reportNode, connectable);
if (throwException) {
throw new IllegalStateException("Given connectable not supported: " + connectable.getClass().getName());
}
}
return Collections.emptyList();
}
private static List getInjectionFeeder(ConnectablePosition> position) {
return Optional.ofNullable(position.getFeeder()).map(List::of).orElse(Collections.emptyList());
}
private static List getBranchFeeders(ConnectablePosition> position, VoltageLevel voltageLevel, Branch> branch) {
List feeders = new ArrayList<>();
if (branch.getTerminal1().getVoltageLevel() == voltageLevel) {
Optional.ofNullable(position.getFeeder1()).ifPresent(feeders::add);
}
if (branch.getTerminal2().getVoltageLevel() == voltageLevel) {
Optional.ofNullable(position.getFeeder2()).ifPresent(feeders::add);
}
return feeders;
}
private static List get3wtFeeders(ConnectablePosition> position, VoltageLevel voltageLevel, ThreeWindingsTransformer twt) {
List feeders = new ArrayList<>();
if (twt.getLeg1().getTerminal().getVoltageLevel() == voltageLevel) {
Optional.ofNullable(position.getFeeder1()).ifPresent(feeders::add);
}
if (twt.getLeg2().getTerminal().getVoltageLevel() == voltageLevel) {
Optional.ofNullable(position.getFeeder2()).ifPresent(feeders::add);
}
if (twt.getLeg3().getTerminal().getVoltageLevel() == voltageLevel) {
Optional.ofNullable(position.getFeeder3()).ifPresent(feeders::add);
}
return feeders;
}
/**
* Method returning the first busbar section with the lowest BusbarSectionIndex if there are the BusbarSectionPosition extensions and the first busbar section found otherwise.
*/
public static BusbarSection getFirstBusbarSection(VoltageLevel voltageLevel) {
BusbarSection bbs;
if (voltageLevel.getNodeBreakerView().getBusbarSectionStream().anyMatch(b -> b.getExtension(BusbarSectionPosition.class) != null)) {
bbs = voltageLevel.getNodeBreakerView().getBusbarSectionStream()
.min(Comparator.comparingInt((BusbarSection b) -> {
BusbarSectionPosition position = b.getExtension(BusbarSectionPosition.class);
return position == null ? Integer.MAX_VALUE : position.getSectionIndex();
}).thenComparingInt((BusbarSection b) -> {
BusbarSectionPosition position = b.getExtension(BusbarSectionPosition.class);
return position == null ? Integer.MAX_VALUE : position.getBusbarIndex();
})).orElse(null);
} else {
bbs = voltageLevel.getNodeBreakerView().getBusbarSectionStream().findFirst().orElse(null);
}
if (bbs == null) {
throw new PowsyblException(String.format("Voltage level %s has no busbar section.", voltageLevel.getId()));
}
return bbs;
}
private static Optional mergeLimits(String lineId,
Optional limits1,
Optional limitsTeePointSide,
ReportNode reportNode) {
Optional limits;
double permanentLimit = limits1.map(LoadingLimitsBag::getPermanentLimit).orElse(Double.NaN);
List temporaryLimits1 = limits1.map(LoadingLimitsBag::getTemporaryLimits).orElse(new ArrayList<>());
List temporaryLimitsTeePointSide = limitsTeePointSide.map(LoadingLimitsBag::getTemporaryLimits).orElse(new ArrayList<>());
List temporaryLimits = new ArrayList<>();
if (!limitsTeePointSide.isPresent()) { // no limits on tee point side : we keep limits on other side
limits = limits1;
} else {
// permanent limit : we keep the minimum permanent limit from both sides
if (Double.isNaN(permanentLimit)) {
permanentLimit = limitsTeePointSide.get().getPermanentLimit();
} else if (!Double.isNaN(limitsTeePointSide.get().getPermanentLimit())) {
permanentLimit = Math.min(permanentLimit, limitsTeePointSide.get().getPermanentLimit());
}
// temporary limits on both sides : they are ignored, otherwise, we keep temporary limits on side where they are defined
if (!temporaryLimits1.isEmpty() && !temporaryLimitsTeePointSide.isEmpty()) {
LOGGER.warn("Temporary limits on both sides for line {} : They are ignored", lineId);
reportNode.newReportNode()
.withMessageTemplate("limitsLost", "Temporary limits on both sides for line ${lineId} : They are ignored")
.withUntypedValue("lineId", lineId)
.withSeverity(TypedValue.WARN_SEVERITY)
.add();
} else {
temporaryLimits = !temporaryLimits1.isEmpty() ? temporaryLimits1 : temporaryLimitsTeePointSide;
}
limits = Optional.of(new LoadingLimitsBag(permanentLimit, temporaryLimits));
}
return limits;
}
public static LoadingLimitsBags mergeLimits(String lineId, LoadingLimitsBags limits, LoadingLimitsBags limitsTeePointSide, ReportNode reportNode) {
Optional activePowerLimits = mergeLimits(lineId, limits.getActivePowerLimits(), limitsTeePointSide.getActivePowerLimits(), reportNode);
Optional apparentPowerLimits = mergeLimits(lineId, limits.getApparentPowerLimits(), limitsTeePointSide.getApparentPowerLimits(), reportNode);
Optional currentLimits = mergeLimits(lineId, limits.getCurrentLimits(), limitsTeePointSide.getCurrentLimits(), reportNode);
return new LoadingLimitsBags(activePowerLimits.orElse(null), apparentPowerLimits.orElse(null), currentLimits.orElse(null));
}
/**
* Find tee point connecting the 3 given lines, if any
* @return the tee point connecting the 3 given lines or null if none
*/
public static VoltageLevel findTeePoint(Line line1, Line line2, Line line3) {
Map countVoltageLevels = Stream.of(line1, line2, line3)
.map(Line::getTerminals)
.flatMap(List::stream)
.collect(Collectors.groupingBy(Terminal::getVoltageLevel, Collectors.counting()));
var commonVlMapEntry = Collections.max(countVoltageLevels.entrySet(), Map.Entry.comparingByValue());
// If the lines are connected by a tee point, there should be 4 distinct voltage levels and one of them should be found 3 times
if (countVoltageLevels.size() == 4 && commonVlMapEntry.getValue() == 3) {
return commonVlMapEntry.getKey();
} else {
return null;
}
}
}