VoltageLevelConverter.java
/**
* Copyright (c) 2021, 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.psse.converter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.util.ContainersMapping;
import com.powsybl.psse.converter.PsseImporter.PerUnitContext;
import com.powsybl.psse.model.PsseException;
import com.powsybl.psse.model.pf.PsseBus;
import com.powsybl.psse.model.pf.PssePowerFlowModel;
import com.powsybl.psse.model.pf.PsseSubstation;
import com.powsybl.psse.model.pf.PsseSubstation.PsseSubstationNode;
import com.powsybl.psse.model.pf.PsseSubstation.PsseSubstationSwitchingDevice;
import com.powsybl.psse.model.pf.PsseSubstation.PsseSubstationEquipmentTerminal;
import org.jgrapht.Graph;
import org.jgrapht.alg.connectivity.ConnectivityInspector;
import org.jgrapht.alg.util.Pair;
import org.jgrapht.graph.Pseudograph;
import static com.powsybl.psse.converter.AbstractConverter.PsseEquipmentType.PSSE_GENERATOR;
/**
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
* @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
*/
class VoltageLevelConverter extends AbstractConverter {
VoltageLevelConverter(PsseBus psseBus, ContainersMapping containerMapping, PerUnitContext perUnitContext, Network network, NodeBreakerValidation nodeBreakerValidation, NodeBreakerImport nodeBreakerImport) {
super(containerMapping, network);
this.psseBus = Objects.requireNonNull(psseBus);
this.perUnitContext = Objects.requireNonNull(perUnitContext);
this.nodeBreakerValidation = Objects.requireNonNull(nodeBreakerValidation);
this.nodeBreakerImport = Objects.requireNonNull(nodeBreakerImport);
}
VoltageLevel create(Substation substation) {
String voltageLevelId = getContainersMapping().getVoltageLevelId(psseBus.getI());
VoltageLevel voltageLevel = getNetwork().getVoltageLevel(voltageLevelId);
if (voltageLevel == null) {
double nominalV = getNominalV(psseBus, perUnitContext.ignoreBaseVoltage());
boolean isNodeBreakerValid = nodeBreakerValidation.isConsideredNodeBreaker(getContainersMapping().getBusesSet(voltageLevelId));
TopologyKind topologyKind = isNodeBreakerValid ? TopologyKind.NODE_BREAKER : TopologyKind.BUS_BREAKER;
voltageLevel = substation.newVoltageLevel()
.setId(voltageLevelId)
.setNominalV(nominalV)
.setTopologyKind(topologyKind)
.add();
if (isNodeBreakerValid) {
addNodeBreakerConnectivity(voltageLevelId, voltageLevel, nodeBreakerValidation, nodeBreakerImport);
}
}
// Add slack control data
if (voltageLevel.getTopologyKind() == TopologyKind.NODE_BREAKER && psseBus.getIde() == 3) {
nodeBreakerImport.addSlackControl(psseBus.getI(), voltageLevelId, findSlackNode(nodeBreakerValidation, psseBus.getI()));
}
return voltageLevel;
}
static double getNominalV(PsseBus psseBus, boolean isIgnoreBaseVoltage) {
return isIgnoreBaseVoltage || psseBus.getBaskv() == 0 ? 1 : psseBus.getBaskv();
}
private void addNodeBreakerConnectivity(String voltageLevelId, VoltageLevel voltageLevel, NodeBreakerValidation nodeBreakerValidation, NodeBreakerImport nodeBreakerImport) {
Set<Integer> buses = getContainersMapping().getBusesSet(voltageLevelId);
nodeBreakerImport.addTopologicalBuses(buses);
Optional<PsseSubstation> psseSubstation = nodeBreakerValidation.getTheOnlySubstation(buses);
if (psseSubstation.isPresent()) {
int lastNode = psseSubstation.get().getNodes().stream().map(PsseSubstationNode::getNi).max(Comparator.naturalOrder()).orElseThrow();
for (int bus : buses) {
lastNode = addNodeBreakerConnectivity(voltageLevelId, voltageLevel, psseSubstation.get(), bus, lastNode, nodeBreakerImport);
}
}
}
private static int addNodeBreakerConnectivity(String voltageLevelId, VoltageLevel voltageLevel, PsseSubstation psseSubstation, int bus, int lastNodeUsedForInternalConnections, NodeBreakerImport nodeBreakerImport) {
Set<Integer> nodesSet = psseSubstation.getNodes().stream().filter(psseNode -> psseNode.getI() == bus).map(PsseSubstationNode::getNi).collect(Collectors.toSet());
List<PsseSubstationSwitchingDevice> switchingDeviceList = psseSubstation.getSwitchingDevices().stream()
.filter(sd -> nodesSet.contains(sd.getNi()) && nodesSet.contains(sd.getNj()))
.sorted(Comparator.comparing(VoltageLevelConverter::switchingDeviceString))
.toList();
for (PsseSubstationSwitchingDevice switchingDevice : switchingDeviceList) {
voltageLevel.getNodeBreakerView().newSwitch()
.setId(getSwitchId(voltageLevelId, switchingDevice))
.setName(switchingDevice.getName())
.setNode1(switchingDevice.getNi())
.setNode2(switchingDevice.getNj())
.setKind(findSwitchingKind(switchingDevice.getType()))
.setOpen(switchingDevice.getStatus() != 1)
.add();
}
// Define where equipment must be connected
Set<Integer> nodesWithEquipment = new HashSet<>();
List<PsseSubstationEquipmentTerminal> equipmentTerminalList = psseSubstation.getEquipmentTerminals().stream().filter(eqt -> nodesSet.contains(eqt.getNi())).toList();
int lastNode = lastNodeUsedForInternalConnections;
// Support lines inside a voltageLevel
Map<String, List<PsseSubstationEquipmentTerminal>> equipmentTerminalsGroupedByBus = equipmentTerminalList.stream().collect(Collectors.groupingBy(VoltageLevelConverter::getEquipmentTerminalGroupingKey));
List<String> sortedKeys = equipmentTerminalsGroupedByBus.keySet().stream().sorted().toList();
for (String key : sortedKeys) {
List<PsseSubstationEquipmentTerminal> busEquipmentTerminalList = equipmentTerminalsGroupedByBus.get(key);
for (int index = 0; index < busEquipmentTerminalList.size(); index++) {
PsseSubstationEquipmentTerminal equipmentTerminal = busEquipmentTerminalList.get(index);
String equipmentId = getNodeBreakerEquipmentId(equipmentTerminal.getType(), equipmentTerminal.getI(), equipmentTerminal.getJ(), equipmentTerminal.getK(), equipmentTerminal.getId());
String equipmentIdBus = getNodeBreakerEquipmentIdBus(equipmentId, bus, getEquipmentTerminalEnd(equipmentTerminal, bus, index));
// IIDM only allows one piece of equipment by node
if (nodesWithEquipment.contains(equipmentTerminal.getNi())) {
lastNode++;
voltageLevel.getNodeBreakerView().newInternalConnection().setNode1(equipmentTerminal.getNi()).setNode2(lastNode).add();
nodeBreakerImport.addEquipment(equipmentIdBus, lastNode);
} else {
nodeBreakerImport.addEquipment(equipmentIdBus, equipmentTerminal.getNi());
nodesWithEquipment.add(equipmentTerminal.getNi());
}
}
}
// Nodes not isolated and without equipment are defined as busbar sections to improve the regulating terminal selection
psseSubstation.getNodes().stream()
.filter(psseNode -> nodesSet.contains(psseNode.getNi()) && !nodesWithEquipment.contains(psseNode.getNi()))
.forEach(psseNode -> voltageLevel.getNodeBreakerView()
.newBusbarSection()
.setId(busbarSectionId(voltageLevel.getId(), psseNode.getNi()))
.setName(psseNode.getName())
.setNode(psseNode.getNi())
.add());
// add data for control, if only the bus is specified the control will be associated with the default node
int defaultNode = nodesSet.stream().sorted().findFirst().orElseThrow();
nodeBreakerImport.addBusControl(bus, voltageLevelId, defaultNode);
return lastNode;
}
private static String getEquipmentTerminalGroupingKey(PsseSubstationEquipmentTerminal equipmentTerminal) {
return getNodeBreakerEquipmentId(equipmentTerminal.getType(), equipmentTerminal.getI(), equipmentTerminal.getJ(), equipmentTerminal.getK(), equipmentTerminal.getId()) + "." + equipmentTerminal.getI();
}
private static int getEquipmentTerminalEnd(PsseSubstationEquipmentTerminal equipmentTerminal, int bus, int busIndex) {
List<Integer> sortedNonZeroBuses = Stream.of(equipmentTerminal.getI(), equipmentTerminal.getJ(), equipmentTerminal.getK()).filter(e -> e != 0).sorted().toList();
int index = sortedNonZeroBuses.indexOf(bus);
if (index == -1) {
throw new PsseException("Unexpected bus: " + bus);
}
return index + 1 + busIndex;
}
private static String switchingDeviceString(PsseSubstationSwitchingDevice switchingDevice) {
return switchingDevice.getNi() + "-" + switchingDevice.getNj() + "-" + switchingDevice.getCkt();
}
private static SwitchKind findSwitchingKind(int type) {
return type == 2 ? SwitchKind.BREAKER : SwitchKind.DISCONNECTOR;
}
private static int findSlackNode(NodeBreakerValidation nodeBreakerValidation, int bus) {
PsseSubstation psseSubstation = nodeBreakerValidation.getTheOnlySubstation(bus).orElseThrow();
return psseSubstation.getNodes().stream().filter(n -> n.getI() == bus)
.map(PsseSubstationNode::getNi).min(Comparator.comparingInt((Integer node) -> connectedGenerators(psseSubstation, node))
.reversed().thenComparing(Comparator.naturalOrder())).orElseThrow();
}
private static int connectedGenerators(PsseSubstation psseSubstation, int node) {
return (int) psseSubstation.getEquipmentTerminals().stream().filter(eqt -> eqt.getNi() == node && eqt.getType().equals(PSSE_GENERATOR.getTextCode())).count();
}
static void updateNodeVoltage(PsseSubstation psseSubstation, Network network, ContainersMapping containersMapping) {
psseSubstation.getNodes().forEach(psseNode -> {
VoltageLevel voltageLevel = network.getVoltageLevel(containersMapping.getVoltageLevelId(psseNode.getI()));
if (voltageLevel != null) {
findConnectedBusViewNode(voltageLevel, psseNode.getNi())
.ifPresent(busView -> {
busView.setV(psseNode.getVm() * voltageLevel.getNominalV());
busView.setAngle(psseNode.getVa());
});
}
});
}
static ContextExport createContextExport(Network network, PssePowerFlowModel psseModel, boolean isFullExport) {
ContextExport contextExport = new ContextExport(isFullExport);
if (!isFullExport) {
mapVoltageLevelsAndPsseSubstation(network, psseModel, contextExport);
}
getSortedVoltageLevels(network).forEach(voltageLevel -> {
if (exportVoltageLevelAsNodeBreaker(voltageLevel)) {
boolean isCreated = createNodeBreakerContextExport(voltageLevel, contextExport);
if (!isCreated) {
createBusBranchContextExport(voltageLevel, contextExport);
}
} else {
createBusBranchContextExport(voltageLevel, contextExport);
}
});
return contextExport;
}
private static List<VoltageLevel> getSortedVoltageLevels(Network network) {
return network.getVoltageLevelStream().sorted(Comparator.comparingInt(VoltageLevelConverter::minBus)
.thenComparing(vl -> vl.getSubstation().map(Identifiable::getId).orElse(vl.getId()))
.thenComparing(Comparator.comparingDouble(VoltageLevel::getNominalV).reversed()))
.toList();
}
private static int minBus(VoltageLevel voltageLevel) {
List<Integer> buses = extractBusesFromVoltageLevelId(voltageLevel.getId());
return buses.isEmpty() ? Integer.MAX_VALUE : buses.get(0);
}
private static void mapVoltageLevelsAndPsseSubstation(Network network, PssePowerFlowModel psseModel, ContextExport contextExport) {
Map<Integer, PsseSubstation> busPsseSubstation = new HashMap<>();
psseModel.getSubstations().forEach(psseSubstation -> {
Set<Integer> buses = psseSubstation.getNodes().stream().map(PsseSubstationNode::getI).collect(Collectors.toSet());
buses.forEach(bus -> busPsseSubstation.put(bus, psseSubstation));
});
network.getVoltageLevels().forEach(voltageLevel -> getVoltageLevelPsseSubstation(voltageLevel, busPsseSubstation)
.ifPresent(psseSubstation -> contextExport.getUpdateExport().addVoltageLevelPsseSubstation(voltageLevel, psseSubstation)));
}
private static Optional<PsseSubstation> getVoltageLevelPsseSubstation(VoltageLevel voltageLevel, Map<Integer, PsseSubstation> busPsseSubstation) {
List<Integer> buses = extractBusesFromVoltageLevelId(voltageLevel.getId());
Set<PsseSubstation> psseSubstationSet = buses.stream()
.filter(busPsseSubstation::containsKey)
.map(busPsseSubstation::get)
.collect(Collectors.toSet());
if (psseSubstationSet.size() > 1) {
throw new PsseException("Only one PsseSubstation is allowed per VoltageLevel. VoltageLevelId: " + voltageLevel.getId());
}
return psseSubstationSet.stream().findFirst();
}
private static void createBusBranchContextExport(VoltageLevel voltageLevel, ContextExport contextExport) {
if (contextExport.isFullExport()) {
createBusBranchContextExportForFullExport(voltageLevel, contextExport);
} else {
createBusBranchContextExportForUpdating(voltageLevel, contextExport);
}
}
private static void createBusBranchContextExportForFullExport(VoltageLevel voltageLevel, ContextExport contextExport) {
voltageLevel.getBusView().getBuses().forEach(bus -> contextExport.getFullExport().addBusIBusView(contextExport.getFullExport().getNewPsseBusI(), bus));
voltageLevel.getDanglingLineStream().filter(danglingLine -> !danglingLine.isPaired())
.forEach(danglingLine -> contextExport.getFullExport().addDanglingLineBusI(danglingLine, contextExport.getFullExport().getNewPsseBusI()));
}
private static void createBusBranchContextExportForUpdating(VoltageLevel voltageLevel, ContextExport contextExport) {
voltageLevel.getBusBreakerView().getBuses().forEach(busBreakerViewBus ->
Optional.ofNullable(voltageLevel.getBusView().getMergedBus(busBreakerViewBus.getId()))
.ifPresent(mergedBus -> extractBusNumber(busBreakerViewBus.getId())
.ifPresent(busI -> contextExport.getUpdateExport().addBusIBusView(busI, mergedBus))
)
);
}
// All the nodes are always associated with the same busI, so the busViewId will be ok only when we do not have bus-sections
private static boolean createNodeBreakerContextExport(VoltageLevel voltageLevel, ContextExport contextExport) {
if (contextExport.isFullExport()) {
return createNodeBreakerContextExportForFullExport(voltageLevel, contextExport);
} else {
createNodeBreakerContextExportForUpdating(voltageLevel, contextExport);
return true;
}
}
private static void createNodeBreakerContextExportForUpdating(VoltageLevel voltageLevel, ContextExport contextExport) {
Map<Integer, List<Bus>> busIBusViews = new HashMap<>();
PsseSubstation psseSubstation = contextExport.getUpdateExport().getPsseSubstation(voltageLevel).orElseThrow();
for (int node : voltageLevel.getNodeBreakerView().getNodes()) {
findConnectedBusViewNode(voltageLevel, node).ifPresent(bus -> {
contextExport.getUpdateExport().addNodeBusView(voltageLevel, node, bus);
findBusI(psseSubstation, node).ifPresent(busI -> busIBusViews.computeIfAbsent(busI, k -> new ArrayList<>()).add(bus));
});
}
// we try to assign a busView inside mainConnectedComponent with the strong psse bus type and, we preserve the original bus type
busIBusViews.forEach((busI, busList) -> {
Bus selectedBus = busList.stream().min(Comparator.comparingInt(VoltageLevelConverter::findPriorityType)).orElseThrow();
contextExport.getUpdateExport().addBusIBusView(busI, selectedBus);
});
}
private static boolean createNodeBreakerContextExportForFullExport(VoltageLevel voltageLevel, ContextExport contextExport) {
// a new psseSubstation is created for each iidm substation (false) or for each iidm voltageLevel (true)
boolean psseSubstationByVoltageLevel = false;
String psseSubstationId = findPsseSubstationId(voltageLevel, psseSubstationByVoltageLevel);
Map<Integer, Integer> representativeForInternalConnectionsNodes = findRepresentativeNodes(voltageLevel);
List<Set<Integer>> connectedSetsBySwitchesAndInternalConnections = connectedSetsBySwitchesAndInternalConnections(voltageLevel);
if (connectedSetsBySwitchesAndInternalConnections.isEmpty()) {
return false;
}
// only getMaxPsseNodeBySubstation psse nodes are allowed inside a substation
if (contextExport.getFullExport().getLastPsseNode(psseSubstationId) +
newPsseNodes(connectedSetsBySwitchesAndInternalConnections, representativeForInternalConnectionsNodes)
> getMaxPsseNodeBySubstation()) {
return false;
}
contextExport.getFullExport().addPsseSubstationIdVoltageLevel(psseSubstationId, voltageLevel);
connectedSetsBySwitchesAndInternalConnections.forEach(connectedSet -> {
int busI = contextExport.getFullExport().getNewPsseBusI();
Set<Bus> busViewBusesForBusI = new HashSet<>();
connectedSet.stream()
.filter(node -> !isRepresented(representativeForInternalConnectionsNodes, node))
.forEach(nonRepresentedNode -> contextForNonRepresentedNode(voltageLevel, nonRepresentedNode, busI, busViewBusesForBusI, psseSubstationId, contextExport));
connectedSet.stream()
.filter(node -> isRepresented(representativeForInternalConnectionsNodes, node))
.forEach(representedNode -> {
int representativeNode = representativeForInternalConnectionsNodes.get(representedNode);
contextForRepresentedNode(voltageLevel, representedNode, representativeNode, contextExport);
});
Bus selectedBus = busViewBusesForBusI.stream().min(Comparator.comparingInt(VoltageLevelConverter::findPriorityType)).orElse(null);
contextExport.getFullExport().addBusIBusView(busI, selectedBus);
});
voltageLevel.getDanglingLineStream().filter(danglingLine -> !danglingLine.isPaired())
.forEach(danglingLine -> contextExport.getFullExport().addDanglingLineBusI(danglingLine, contextExport.getFullExport().getNewPsseBusI()));
// add isolated nodes, associated with terminals not previously considered
Set<Integer> mergedSet = connectedSetsBySwitchesAndInternalConnections.stream().flatMap(Set::stream).collect(Collectors.toSet());
for (int node : voltageLevel.getNodeBreakerView().getNodes()) {
if (!mergedSet.contains(node)) {
int busI = contextExport.getFullExport().getNewPsseBusI();
contextForIsolatedNode(voltageLevel, node, busI, psseSubstationId, contextExport);
contextExport.getFullExport().addBusIBusView(busI, null);
}
}
return true;
}
private static int newPsseNodes(List<Set<Integer>> connectedSetsBySwitchesAndInternalConnections, Map<Integer, Integer> representativeForInternalConnectionsNodes) {
return connectedSetsBySwitchesAndInternalConnections.stream()
.mapToInt(connectedSet -> (int) connectedSet.stream()
.filter(node -> !isRepresented(representativeForInternalConnectionsNodes, node))
.count())
.sum();
}
private static String findPsseSubstationId(VoltageLevel voltageLevel, boolean psseSubstationByVoltageLevel) {
return psseSubstationByVoltageLevel ? voltageLevel.getId() : voltageLevel.getSubstation().map(Identifiable::getId).orElse(voltageLevel.getId());
}
private static Map<Integer, Integer> findRepresentativeNodes(VoltageLevel voltageLevel) {
List<Set<Integer>> connectedSetsByInternalConnections = connectedSetsByInternalConnections(voltageLevel);
Set<Integer> nodesOfSwitches = new HashSet<>();
voltageLevel.getNodeBreakerView().getSwitches().forEach(sw -> {
nodesOfSwitches.add(voltageLevel.getNodeBreakerView().getNode1(sw.getId()));
nodesOfSwitches.add(voltageLevel.getNodeBreakerView().getNode2(sw.getId()));
});
// the best candidate is a node with switches
Map<Integer, Integer> representativeForInternalConnectionsNodes = new HashMap<>();
connectedSetsByInternalConnections.forEach(connectedSetByInternalConnections -> {
int representativeNode = findRepresentativeNode(connectedSetByInternalConnections, nodesOfSwitches);
connectedSetByInternalConnections.forEach(node -> representativeForInternalConnectionsNodes.put(node, representativeNode));
});
return representativeForInternalConnectionsNodes;
}
private static List<Set<Integer>> connectedSetsByInternalConnections(VoltageLevel voltageLevel) {
Graph<Integer, Pair<Integer, Integer>> icGraph = new Pseudograph<>(null, null, false);
addInternalConnections(voltageLevel, icGraph);
return new ConnectivityInspector<>(icGraph).connectedSets();
}
private static void addInternalConnections(VoltageLevel voltageLevel, Graph<Integer, Pair<Integer, Integer>> icGraph) {
voltageLevel.getNodeBreakerView().getInternalConnections().forEach(internalConnection -> {
int node1 = internalConnection.getNode1();
int node2 = internalConnection.getNode2();
icGraph.addVertex(node1);
icGraph.addVertex(node2);
icGraph.addEdge(node1, node2, Pair.of(node1, node2));
});
}
private static int findRepresentativeNode(Set<Integer> connectedSetByInternalConnections, Set<Integer> nodesOfSwitches) {
return connectedSetByInternalConnections.stream()
.filter(nodesOfSwitches::contains)
.min(Comparator.naturalOrder())
.orElse(connectedSetByInternalConnections.stream().min(Comparator.naturalOrder()).orElseThrow());
}
private static List<Set<Integer>> connectedSetsBySwitchesAndInternalConnections(VoltageLevel voltageLevel) {
Graph<Integer, Pair<Integer, Integer>> swIcGraph = new Pseudograph<>(null, null, false);
addInternalConnections(voltageLevel, swIcGraph);
voltageLevel.getNodeBreakerView().getSwitches().forEach(sw -> {
int node1 = voltageLevel.getNodeBreakerView().getNode1(sw.getId());
int node2 = voltageLevel.getNodeBreakerView().getNode2(sw.getId());
swIcGraph.addVertex(node1);
swIcGraph.addVertex(node2);
swIcGraph.addEdge(node1, node2, Pair.of(node1, node2));
});
return new ConnectivityInspector<>(swIcGraph).connectedSets();
}
private static boolean isRepresented(Map<Integer, Integer> representativeForInternalConnectionsNodes, int node) {
return Optional.ofNullable(representativeForInternalConnectionsNodes.get(node)).map(representativeNode -> representativeNode != node).orElse(false);
}
private static void contextForNonRepresentedNode(VoltageLevel voltageLevel, int node, int busI, Set<Bus> busViewBusesForBusI, String psseSubstationId, ContextExport contextExport) {
int psseNode = contextExport.getFullExport().getNewPsseNode(psseSubstationId);
findConnectedBusViewNode(voltageLevel, node).ifPresentOrElse(busView -> {
contextExport.getFullExport().addNodeData(voltageLevel, node, busI, psseNode, busView);
busViewBusesForBusI.add(busView);
}, () -> contextExport.getFullExport().addNodeData(voltageLevel, node, busI, psseNode, null));
}
private static void contextForRepresentedNode(VoltageLevel voltageLevel, int node, int representativeNode, ContextExport contextExport) {
int busI = contextExport.getFullExport().getBusI(voltageLevel, representativeNode).orElseThrow();
int psseNode = contextExport.getFullExport().getPsseNode(voltageLevel, representativeNode).orElseThrow();
Bus busView = contextExport.getFullExport().getVoltageBus(voltageLevel, representativeNode).orElse(null);
contextExport.getFullExport().addNodeData(voltageLevel, node, busI, psseNode, busView);
}
private static void contextForIsolatedNode(VoltageLevel voltageLevel, int node, int busI, String psseSubstationId, ContextExport contextExport) {
int psseNode = contextExport.getFullExport().getNewPsseNode(psseSubstationId);
contextExport.getFullExport().addNodeData(voltageLevel, node, busI, psseNode, null);
}
private static int findPriorityType(Bus busView) {
int type = findBusViewBusType(busView);
return switch (type) {
case 3 -> 0;
case 2 -> 1;
case 1 -> 2;
case 4 -> 3;
default -> throw new PsseException("Unexpected psse bus type: " + type);
};
}
static void createSubstations(PssePowerFlowModel psseModel, ContextExport contextExport) {
List<PsseSubstation> psseSubstations = new ArrayList<>();
contextExport.getFullExport().getSortedPsseSubstationIds().forEach(psseSubstationId -> {
List<PsseSubstation.PsseSubstationNode> nodes = new ArrayList<>();
List<PsseSubstation.PsseSubstationSwitchingDevice> switchingDevices = new ArrayList<>();
List<PsseSubstation.PsseSubstationEquipmentTerminal> equipmentTerminals = new ArrayList<>();
contextExport.getFullExport().getVoltageLevelSet(psseSubstationId).forEach(voltageLevel -> {
nodes.addAll(createPsseSubstationNodes(voltageLevel, contextExport));
switchingDevices.addAll(createPsseSubstationSwitchingDevices(voltageLevel, contextExport));
equipmentTerminals.addAll(createPsseSubstationEquipmentTerminals(voltageLevel, contextExport));
});
PsseSubstation psseSubstation = new PsseSubstation(createPsseSubstationSubstationRecord(psseSubstationId, contextExport),
nodes.stream().sorted(Comparator.comparingInt(PsseSubstation.PsseSubstationNode::getNi)).toList(),
switchingDevices.stream().sorted(Comparator.comparingInt(PsseSubstation.PsseSubstationSwitchingDevice::getNi)
.thenComparingInt(PsseSubstation.PsseSubstationSwitchingDevice::getNj)
.thenComparing(PsseSubstation.PsseSubstationSwitchingDevice::getCkt)).toList(),
equipmentTerminals.stream().sorted(Comparator.comparingInt(PsseSubstation.PsseSubstationEquipmentTerminal::getI)
.thenComparingInt(PsseSubstation.PsseSubstationEquipmentTerminal::getNi)
.thenComparingInt(PsseSubstation.PsseSubstationEquipmentTerminal::getJ)
.thenComparingInt(PsseSubstation.PsseSubstationEquipmentTerminal::getK)
.thenComparing(PsseSubstation.PsseSubstationEquipmentTerminal::getId)
.thenComparing(PsseSubstation.PsseSubstationEquipmentTerminal::getType)).toList());
psseSubstations.add(psseSubstation);
});
psseModel.addSubstations(psseSubstations);
}
private static PsseSubstation.PsseSubstationRecord createPsseSubstationSubstationRecord(String psseSubstationId, ContextExport contextExport) {
PsseSubstation.PsseSubstationRecord substationRecord = new PsseSubstation.PsseSubstationRecord();
substationRecord.setIs(contextExport.getFullExport().getNewPsseSubstationIs());
substationRecord.setName(psseSubstationId);
substationRecord.setLati(0.0);
substationRecord.setLong(0.0);
substationRecord.setSrg(0.1);
return substationRecord;
}
private static List<PsseSubstation.PsseSubstationNode> createPsseSubstationNodes(VoltageLevel voltageLevel, ContextExport contextExport) {
List<PsseSubstation.PsseSubstationNode> nodes = new ArrayList<>();
for (int node : voltageLevel.getNodeBreakerView().getNodes()) {
contextExport.getFullExport().getBusI(voltageLevel, node).ifPresent(busI -> {
int ni = contextExport.getFullExport().getPsseNode(voltageLevel, node).orElseThrow();
Bus voltageBusView = contextExport.getFullExport().getVoltageBus(voltageLevel, node).orElse(null);
boolean isDeEnergized = contextExport.getFullExport().isDeEnergized(voltageLevel, node);
PsseSubstation.PsseSubstationNode psseNode = new PsseSubstationNode();
psseNode.setNi(ni);
psseNode.setName(getNodeId(voltageLevel, node));
psseNode.setI(busI);
psseNode.setStatus(isDeEnergized ? 0 : 1);
psseNode.setVm(getVm(voltageBusView));
psseNode.setVa(getVa(voltageBusView));
nodes.add(psseNode);
});
}
return nodes;
}
// ckt must be unique inside the voltageLevel
private static List<PsseSubstation.PsseSubstationSwitchingDevice> createPsseSubstationSwitchingDevices(VoltageLevel voltageLevel, ContextExport contextExport) {
List<PsseSubstation.PsseSubstationSwitchingDevice> switchingDevices = new ArrayList<>();
voltageLevel.getSwitches().forEach(sw -> {
int ni = contextExport.getFullExport().getPsseNode(voltageLevel, sw.getVoltageLevel().getNodeBreakerView().getNode1(sw.getId())).orElseThrow();
int nj = contextExport.getFullExport().getPsseNode(voltageLevel, sw.getVoltageLevel().getNodeBreakerView().getNode2(sw.getId())).orElseThrow();
PsseSubstation.PsseSubstationSwitchingDevice switchingDevice = new PsseSubstationSwitchingDevice();
switchingDevice.setNi(ni);
switchingDevice.setNj(nj);
switchingDevice.setCkt(contextExport.getFullExport().getEquipmentCkt(voltageLevel, sw.getId(), ni, nj));
switchingDevice.setName(sw.getId());
switchingDevice.setType(getSwitchingDeviceType(sw));
switchingDevice.setStatus(sw.isOpen() ? 0 : 1);
switchingDevice.setNstat(1);
switchingDevice.setX(0.0001);
switchingDevice.setRate1(0.0);
switchingDevice.setRate2(0.0);
switchingDevice.setRate3(0.0);
switchingDevices.add(switchingDevice);
});
return switchingDevices;
}
private static int getSwitchingDeviceType(Switch sw) {
return switch (sw.getKind()) {
case BREAKER, LOAD_BREAK_SWITCH -> 2;
case DISCONNECTOR -> 3;
};
}
private static List<PsseSubstation.PsseSubstationEquipmentTerminal> createPsseSubstationEquipmentTerminals(VoltageLevel voltageLevel, ContextExport contextExport) {
List<PsseSubstation.PsseSubstationEquipmentTerminal> equipmentTerminals = new ArrayList<>();
getEquipmentListToBeExported(voltageLevel).forEach(equipmentId -> {
Identifiable<?> identifiable = getIdentifiable(voltageLevel, equipmentId);
String type = getPsseEquipmentType(identifiable);
List<Terminal> terminals = getEquipmentTerminals(voltageLevel, equipmentId);
getNodesInsideVoltageLevelPreservingOrder(voltageLevel, terminals, contextExport).forEach(nodeBusR -> {
List<Integer> otherBuses = getTwoOtherBusesPreservingOrder(identifiable, terminals, nodeBusR, contextExport);
String ckt = contextExport.getFullExport().getEquipmentCkt(equipmentId, type, nodeBusR.busI(), otherBuses.get(0), otherBuses.get(1));
PsseSubstation.PsseSubstationEquipmentTerminal equipmentTerminal = new PsseSubstationEquipmentTerminal();
equipmentTerminal.setNi(nodeBusR.psseNode);
equipmentTerminal.setType(type);
equipmentTerminal.setId(getEquipmentTerminalId(type, identifiable, ckt));
equipmentTerminal.setI(nodeBusR.busI);
equipmentTerminal.setJ(otherBuses.get(0));
equipmentTerminal.setK(otherBuses.get(1));
equipmentTerminals.add(equipmentTerminal);
});
});
return equipmentTerminals;
}
private static String getEquipmentTerminalId(String type, Identifiable<?> identifiable, String ckt) {
return switch (type) {
case "A" -> extractFactsDeviceName(identifiable.getId());
case "D" -> extractTwoTerminalDcName(identifiable.getId());
case "V" -> extractVscDcTransmissionLineName(identifiable.getId());
default -> ckt;
};
}
private static List<NodeBusR> getNodesInsideVoltageLevelPreservingOrder(VoltageLevel voltageLevel, List<Terminal> terminals, ContextExport contextExport) {
return terminals.stream()
.filter(terminal -> terminal.getVoltageLevel().equals(voltageLevel))
.map(terminal -> findNodeBusR(terminal, contextExport)).toList();
}
private static NodeBusR findNodeBusR(Terminal terminal, ContextExport contextExport) {
int node = terminal.getNodeBreakerView().getNode();
int busI = contextExport.getFullExport().getBusI(terminal.getVoltageLevel(), node).orElseThrow();
int psseNode = contextExport.getFullExport().getPsseNode(terminal.getVoltageLevel(), node).orElseThrow();
return new NodeBusR(terminal.getVoltageLevel(), node, psseNode, busI);
}
private record NodeBusR(VoltageLevel voltageLevel, int node, int psseNode, int busI) {
boolean equals(NodeBusR other) {
return voltageLevel().equals(other.voltageLevel()) && node() == other.node();
}
}
private static List<Integer> getTwoOtherBusesPreservingOrder(Identifiable<?> identifiable, List<Terminal> terminals, NodeBusR nodeBusR, ContextExport contextExport) {
List<Integer> buses = new ArrayList<>();
if (identifiable.getType() == IdentifiableType.DANGLING_LINE) {
// busJ associated with boundary side of the dangling lines
buses.add(contextExport.getFullExport().getBusI((DanglingLine) identifiable).orElseThrow());
} else {
terminals.forEach(terminal -> {
if (contextExport.getFullExport().isExportedAsNodeBreaker(terminal.getVoltageLevel())) {
NodeBusR otherNodeBusR = findNodeBusR(terminal, contextExport);
if (!nodeBusR.equals(otherNodeBusR)) {
buses.add(otherNodeBusR.busI);
}
} else {
Bus busView = getTerminalConnectableBusView(terminal);
buses.add(contextExport.getFullExport().getBusI(busView).orElseThrow());
}
});
}
completeWithZerosUntilTwoBuses(identifiable.getId(), buses);
return buses;
}
private static void completeWithZerosUntilTwoBuses(String equipmentId, List<Integer> buses) {
if (buses.isEmpty()) {
buses.add(0);
buses.add(0);
} else if (buses.size() == 1) {
buses.add(0);
} else if (buses.size() > 2) {
throw new PsseException("Unexpected number of buses for equipmentId: " + equipmentId);
}
}
private static Identifiable<?> getIdentifiable(VoltageLevel voltageLevel, String identifiableId) {
Identifiable<?> identifiable = voltageLevel.getNetwork().getIdentifiable(identifiableId);
if (identifiable != null) {
return identifiable;
} else {
throw new PsseException("Unexpected identifiableId: " + identifiableId);
}
}
private static Optional<Integer> findBusI(PsseSubstation psseSubstation, int node) {
return psseSubstation.getNodes().stream().filter(psseNode -> psseNode.getNi() == node).map(PsseSubstationNode::getI).findFirst();
}
static void updateSubstations(Network network, ContextExport contextExport) {
network.getVoltageLevels().forEach(voltageLevel -> {
if (voltageLevel.getTopologyKind() == TopologyKind.NODE_BREAKER) {
Set<Integer> buses = new HashSet<>(extractBusesFromVoltageLevelId(voltageLevel.getId()));
contextExport.getUpdateExport().getPsseSubstation(voltageLevel).ifPresent(psseSubstation -> updateSubstation(voltageLevel, psseSubstation, buses, contextExport));
}
});
}
private static void updateSubstation(VoltageLevel voltageLevel, PsseSubstation psseSubstation, Set<Integer> busesSet, ContextExport contextExport) {
Set<PsseSubstationNode> psseNodeSet = psseSubstation.getNodes().stream().filter(psseNode -> busesSet.contains(psseNode.getI())).collect(Collectors.toSet());
psseNodeSet.forEach(psseSubstationNode -> {
Optional<Bus> busView = contextExport.getUpdateExport().getBusView(voltageLevel, psseSubstationNode.getNi());
if (busView.isPresent()) {
psseSubstationNode.setVm(getVm(busView.get()));
psseSubstationNode.setVa(getVa(busView.get()));
} else {
psseSubstationNode.setVm(1.0);
psseSubstationNode.setVa(0.0);
}
});
Set<Integer> nodesSet = psseNodeSet.stream().map(PsseSubstationNode::getNi).collect(Collectors.toSet());
Set<PsseSubstationSwitchingDevice> switchingDeviceSet = psseSubstation.getSwitchingDevices().stream()
.filter(sd -> nodesSet.contains(sd.getNi()) && nodesSet.contains(sd.getNj())).collect(Collectors.toSet());
switchingDeviceSet.forEach(switchingDevice -> {
String switchId = getSwitchId(voltageLevel.getId(), switchingDevice);
Switch sw = voltageLevel.getNodeBreakerView().getSwitch(switchId);
if (sw == null) {
throw new PsseException("Unexpected null breaker: " + switchId);
}
switchingDevice.setStatus(sw.isOpen() ? 0 : 1);
});
}
private final PsseBus psseBus;
private final PerUnitContext perUnitContext;
private final NodeBreakerValidation nodeBreakerValidation;
private final NodeBreakerImport nodeBreakerImport;
}