NodeBreakerValidation.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.psse.converter;
import com.powsybl.psse.model.pf.*;
import com.powsybl.psse.model.pf.PsseSubstation.PsseSubstationNode;
import com.powsybl.psse.model.PsseVersion;
import org.jgrapht.Graph;
import org.jgrapht.alg.connectivity.ConnectivityInspector;
import org.jgrapht.alg.util.Pair;
import org.jgrapht.graph.Pseudograph;
import java.util.*;
import java.util.stream.Collectors;
import static com.powsybl.psse.converter.AbstractConverter.PsseEquipmentType.*;
import static com.powsybl.psse.converter.AbstractConverter.getNodeBreakerEquipmentId;
import static com.powsybl.psse.model.PsseVersion.Major.V35;
/**
* @author Luma Zamarre��o {@literal <zamarrenolm at aia.es>}
* @author Jos�� Antonio Marqu��s {@literal <marquesja at aia.es>}
*/
final class NodeBreakerValidation {
private final boolean ignoreNodeBreakerTopology;
private final Map<Integer, List<PsseSubstation>> busSubstations;
private final Map<PsseSubstation, List<Integer>> substationBuses;
private final Map<Integer, Set<Integer>> busNodesSet;
private final Map<Integer, List<String>> busEquipmentTerminals;
private final Set<PsseSubstation> invalidSubstations;
NodeBreakerValidation(boolean ignoreNodeBreakerTopology) {
this.ignoreNodeBreakerTopology = ignoreNodeBreakerTopology;
this.busSubstations = new HashMap<>();
this.substationBuses = new HashMap<>();
this.busNodesSet = new HashMap<>();
this.busEquipmentTerminals = new HashMap<>();
this.invalidSubstations = new HashSet<>();
}
void fillAndValidate(PssePowerFlowModel pssePowerFlowModel, PsseVersion version) {
if (version.major() != V35 || this.ignoreNodeBreakerTopology) {
return;
}
// Fill psse substation data, only valid psse substations are used
pssePowerFlowModel.getSubstations().forEach(psseSubstation -> {
if (validInternalConnectivity(psseSubstation)) {
fill(psseSubstation);
}
});
// Do the validations
// a bus can only be included in one psseSubstation
busSubstations.forEach((bus, substationList) -> {
if (substationList.size() >= 2) {
invalidSubstations.addAll(substationList);
}
});
// validate equipment terminals and node controls
pssePowerFlowModel.getLoads().forEach(psseLoad -> {
String id = getNodeBreakerEquipmentId(PSSE_LOAD, psseLoad.getI(), psseLoad.getId());
checkEquipment(psseLoad.getI(), id);
});
pssePowerFlowModel.getFixedShunts().forEach(psseFixedShunt -> {
String id = getNodeBreakerEquipmentId(PSSE_FIXED_SHUNT, psseFixedShunt.getI(), psseFixedShunt.getId());
checkEquipment(psseFixedShunt.getI(), id);
});
pssePowerFlowModel.getGenerators().forEach(psseGenerator -> {
String id = getNodeBreakerEquipmentId(PSSE_GENERATOR, psseGenerator.getI(), psseGenerator.getId());
checkEquipment(psseGenerator.getI(), id);
checkControl(psseGenerator.getIreg(), psseGenerator.getNreg());
});
pssePowerFlowModel.getNonTransformerBranches().forEach(psseNonTransformerBranch -> {
String id = getNodeBreakerEquipmentId(PSSE_BRANCH, psseNonTransformerBranch.getI(), psseNonTransformerBranch.getJ(), psseNonTransformerBranch.getCkt());
checkEquipment(psseNonTransformerBranch.getI(), id);
checkEquipment(psseNonTransformerBranch.getJ(), id);
});
pssePowerFlowModel.getTransformers().forEach(psseTransformer -> {
if (psseTransformer.getK() == 0) {
twoWindingsTransformerNetworkValidation(psseTransformer);
} else {
threeWindingsTransformerNetworkValidation(psseTransformer);
}
});
pssePowerFlowModel.getTwoTerminalDcTransmissionLines().forEach(psseTwoTerminalDcTransmissionLine -> {
String idRectifier = getNodeBreakerEquipmentId(PSSE_TWO_TERMINAL_DC_LINE, psseTwoTerminalDcTransmissionLine.getRectifier().getIp(), psseTwoTerminalDcTransmissionLine.getName());
String idInverter = getNodeBreakerEquipmentId(PSSE_TWO_TERMINAL_DC_LINE, psseTwoTerminalDcTransmissionLine.getInverter().getIp(), psseTwoTerminalDcTransmissionLine.getName());
checkEquipment(psseTwoTerminalDcTransmissionLine.getRectifier().getIp(), idRectifier);
checkEquipment(psseTwoTerminalDcTransmissionLine.getInverter().getIp(), idInverter);
});
pssePowerFlowModel.getSwitchedShunts().forEach(psseSwitchedShunt -> {
String id = getNodeBreakerEquipmentId(PSSE_SWITCHED_SHUNT, psseSwitchedShunt.getI(), psseSwitchedShunt.getId());
checkEquipment(psseSwitchedShunt.getI(), id);
checkControl(psseSwitchedShunt.getSwreg(), psseSwitchedShunt.getNreg());
});
}
private void twoWindingsTransformerNetworkValidation(PsseTransformer psseTransformer) {
String id = getNodeBreakerEquipmentId(PSSE_TWO_WINDING, psseTransformer.getI(), psseTransformer.getJ(), psseTransformer.getCkt());
checkEquipment(psseTransformer.getI(), id);
checkEquipment(psseTransformer.getJ(), id);
checkControl(psseTransformer.getWinding1().getCont(), psseTransformer.getWinding1().getNode());
}
private void threeWindingsTransformerNetworkValidation(PsseTransformer psseTransformer) {
String id = getNodeBreakerEquipmentId(PSSE_THREE_WINDING, psseTransformer.getI(), psseTransformer.getJ(), psseTransformer.getK(), psseTransformer.getCkt());
checkEquipment(psseTransformer.getI(), id);
checkEquipment(psseTransformer.getJ(), id);
checkEquipment(psseTransformer.getK(), id);
checkControl(psseTransformer.getWinding1().getCont(), psseTransformer.getWinding1().getNode());
checkControl(psseTransformer.getWinding2().getCont(), psseTransformer.getWinding2().getNode());
checkControl(psseTransformer.getWinding3().getCont(), psseTransformer.getWinding3().getNode());
}
// All the nodes connected by switches (without considering the status) must be a bus (topological bus)
private static boolean validInternalConnectivity(PsseSubstation psseSubstation) {
Set<Integer> nodesSet = psseSubstation.getNodes().stream().map(PsseSubstationNode::getNi).collect(Collectors.toSet());
Graph<Integer, Pair<Integer, Integer>> sGraph = new Pseudograph<>(null, null, false);
nodesSet.forEach(sGraph::addVertex);
psseSubstation.getSwitchingDevices().forEach(switchingDevice -> sGraph.addEdge(switchingDevice.getNi(), switchingDevice.getNj(), Pair.of(switchingDevice.getNi(), switchingDevice.getNj())));
List<Set<Integer>> connectedSets = new ConnectivityInspector<>(sGraph).connectedSets();
for (Set<Integer> connectedSet : connectedSets) {
Set<Integer> associatedBuses = findAssociatedBuses(psseSubstation, connectedSet);
if (associatedBuses.size() != 1) {
return false;
}
}
return true;
}
private static Set<Integer> findAssociatedBuses(PsseSubstation psseSubstation, Set<Integer> connectedSet) {
return psseSubstation.getNodes().stream().filter(psseNode -> connectedSet.contains(psseNode.getNi())).map(PsseSubstationNode::getI).collect(Collectors.toSet());
}
private void fill(PsseSubstation psseSubstation) {
Set<Integer> buses = psseSubstation.getNodes().stream().map(PsseSubstationNode::getI).collect(Collectors.toSet());
buses.forEach(bus -> {
busSubstations.computeIfAbsent(bus, k -> new ArrayList<>()).add(psseSubstation);
Set<Integer> nodesSet = psseSubstation.getNodes().stream().filter(psseNode -> psseNode.getI() == bus).map(PsseSubstationNode::getNi).collect(Collectors.toSet());
busNodesSet.put(bus, nodesSet);
});
substationBuses.put(psseSubstation, buses.stream().toList());
psseSubstation.getEquipmentTerminals().forEach(equipmentTerminal -> {
String equipmentId = getNodeBreakerEquipmentId(equipmentTerminal.getType(), equipmentTerminal.getI(), equipmentTerminal.getJ(), equipmentTerminal.getK(), equipmentTerminal.getId());
busEquipmentTerminals.computeIfAbsent(equipmentTerminal.getI(), k -> new ArrayList<>()).add(equipmentId);
});
}
private void checkEquipment(int bus, String equipmentId) {
if (!validEquipment(bus, equipmentId)) {
invalidateSubstationAssociatedWithBus(bus);
}
}
private boolean validEquipment(int bus, String equipmentId) {
return busEquipmentTerminals.containsKey(bus) && busEquipmentTerminals.get(bus).contains(equipmentId);
}
private void checkControl(int bus, int node) {
if (!validControl(bus, node)) {
invalidateSubstationAssociatedWithBus(bus);
}
}
private boolean validControl(int bus, int node) {
return bus == 0 || node == 0 || busNodesSet.containsKey(bus) && busNodesSet.get(bus).contains(node);
}
private void invalidateSubstationAssociatedWithBus(int bus) {
if (busSubstations.containsKey(bus)) {
invalidSubstations.addAll(busSubstations.get(bus));
}
}
List<PsseSubstation> getValidSubstations() {
return substationBuses.keySet().stream().filter(psseSubstation -> !invalidSubstations.contains(psseSubstation)).toList();
}
List<Integer> getBuses(PsseSubstation psseSubstation) {
return substationBuses.containsKey(psseSubstation) ? substationBuses.get(psseSubstation) : new ArrayList<>();
}
Set<Integer> getValidSubstationsIds(Set<Integer> buses) {
if (buses.stream().anyMatch(bus -> !busAssociatedWithValidSubstation(bus))) {
return new HashSet<>();
}
return buses.stream()
.filter(busSubstations::containsKey)
.flatMap(bus -> busSubstations.get(bus).stream())
.map(PsseSubstation::getIs)
.collect(Collectors.toSet());
}
private boolean busAssociatedWithValidSubstation(int bus) {
return busSubstations.containsKey(bus) && busSubstations.get(bus).stream().noneMatch(invalidSubstations::contains);
}
boolean isConsideredNodeBreaker(Set<Integer> busesSet) {
return getValidSubstationsIds(busesSet).size() == 1;
}
Optional<PsseSubstation> getTheOnlySubstation(int bus) {
return getTheOnlySubstation(Set.of(bus));
}
Optional<PsseSubstation> getTheOnlySubstation(Set<Integer> busesSet) {
Set<PsseSubstation> psseSubstationSet = busesSet.stream()
.filter(busSubstations::containsKey)
.flatMap(bus -> busSubstations.get(bus).stream())
.filter(psseSubstation -> !invalidSubstations.contains(psseSubstation))
.collect(Collectors.toSet());
return psseSubstationSet.size() == 1 ? Optional.of(psseSubstationSet.iterator().next()) : Optional.empty();
}
}