diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b8252909..0753bcd67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328) - Extend azimuth angle range to [-180°, 180°] for PV inputs [#1330](https://github.com/ie3-institute/PowerSystemDataModel/issues/1330) +- Changed `SubgridContainer` to represent galvanically seperated grids [#1226](https://github.com/ie3-institute/PowerSystemDataModel/issues/1226) ## [7.0.0] - 2025-05-08 diff --git a/docs/readthedocs/models/input/grid/gridcontainer.md b/docs/readthedocs/models/input/grid/gridcontainer.md index 24f8e66c4..d6411b318 100644 --- a/docs/readthedocs/models/input/grid/gridcontainer.md +++ b/docs/readthedocs/models/input/grid/gridcontainer.md @@ -20,19 +20,6 @@ Why predominant? As of convention, the `SubGridContainers` hold also reference to the transformers leading to higher sub grids and their higher voltage coupling point. -![Sub grid boundary definition for transformers with upstream switchgear](../../../_static/figures/transformerWithSwitchGear.png) - -Let's shed a more detailed light on the boundaries of a sub grid as of our definition. -This especially is important, if the switchgear of the transformer is modeled in detail. -We defined, that all nodes in upstream direction of the transformer, that are connected by switches *only* (therefore -are within the switchgear) are counted towards the inferior sub grid structure (here "2"), although they belong to a -different voltage level. -This decision is taken, because we assume, that the interest to operate on the given switchgear will most likely be -placed in the inferior grid structure. - -The "real" coupling node A is not comprised in the sub grids node collection, but obviously has reference through the -switch between nodes A and B. - A synoptic overview of both classes' attributes is given here: ## Attributes, Units and Remarks diff --git a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java index 5033221a2..522dfeaa4 100644 --- a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java @@ -426,41 +426,11 @@ public static GraphicElements filterForSubnet(GraphicElements input, int subnet) */ public static VoltageLevel determinePredominantVoltLvl(RawGridElements rawGrid, int subnet) throws InvalidGridException { - /* Exclude all nodes, that are at the high voltage side of the transformer */ - Set gridNodes = new HashSet<>(rawGrid.getNodes()); - gridNodes.removeAll( - /* Remove all nodes, that are upstream of transformers, this comprises all those, that are connected by - * switches */ - rawGrid.getTransformer2Ws().stream() - .flatMap( - transformer -> - ContainerUtils.traverseAlongSwitchChain(transformer.getNodeA(), rawGrid) - .stream()) - .collect(Collectors.toSet())); - gridNodes.removeAll( - rawGrid.getTransformer3Ws().stream() - .flatMap( - transformer -> { - if (transformer.getNodeA().getSubnet() == subnet) - return Stream.of(transformer.getNodeB(), transformer.getNodeC()); - else if (transformer.getNodeB().getSubnet() == subnet) - return Stream.concat( - ContainerUtils.traverseAlongSwitchChain(transformer.getNodeA(), rawGrid) - .stream(), - Stream.of(transformer.getNodeC(), transformer.getNodeInternal())); - else - return Stream.concat( - ContainerUtils.traverseAlongSwitchChain(transformer.getNodeA(), rawGrid) - .stream(), - Stream.of(transformer.getNodeB(), transformer.getNodeInternal())); - }) - .collect(Collectors.toSet())); - /* Build a mapping, which voltage level appears how often */ Map voltageLevelCount = - gridNodes.stream() - .map(NodeInput::getVoltLvl) - .collect(Collectors.groupingBy(voltLvl -> voltLvl, Collectors.counting())); + rawGrid.getNodes().stream() + .filter(n -> n.getSubnet() == subnet) + .collect(Collectors.groupingBy(NodeInput::getVoltLvl, Collectors.counting())); /* At this point only one voltage level should be apparent */ int amountOfVoltLvl = voltageLevelCount.size(); @@ -677,14 +647,8 @@ private static TransformerSubGridContainers getSubGridContainers( RawGridElements rawGridElements, Map subGrids) throws TopologyException { - /* Get the sub grid container at port A - travel upstream as long as nodes are connected - * _only_ by switches */ - NodeInput topNode = traverseAlongSwitchChain(transformer.getNodeA(), rawGridElements).getLast(); - if (Objects.isNull(topNode)) - throw new TopologyException( - "Cannot find most upstream node of transformer '" + transformer + "'"); - - SubGridContainer containerA = subGrids.get(topNode.getSubnet()); + /* Get the sub grid container at port A */ + SubGridContainer containerA = subGrids.get(transformer.getNodeA().getSubnet()); /* Get the sub grid container at port B */ SubGridContainer containerB = subGrids.get(transformer.getNodeB().getSubnet()); @@ -696,81 +660,6 @@ private static TransformerSubGridContainers getSubGridContainers( } else return new TransformerSubGridContainers(containerA, containerB); } - /** - * Traversing along a chain of switches and return the traveled nodes. The end thereby is defined - * by a node, that either is a dead end or is connected to any other type of connector (e.g. - * lines, transformers) and therefore leads to other parts of a "real" grid. If the starting node - * is not part of any switch, the starting node is returned. - * - * @param startNode Node that is meant to be the start of the switch chain - * @param rawGridElements Elements of the pure grid structure. - * @return The end node of the switch chain - */ - public static LinkedList traverseAlongSwitchChain( - NodeInput startNode, RawGridElements rawGridElements) { - Set possibleJunctions = - Stream.concat( - Stream.concat( - rawGridElements.getLines().parallelStream(), - rawGridElements.getTransformer2Ws().parallelStream()), - rawGridElements.getTransformer3Ws().parallelStream()) - .flatMap(connector -> connector.allNodes().parallelStream()) - .collect(Collectors.toSet()); - return traverseAlongSwitchChain(startNode, rawGridElements.getSwitches(), possibleJunctions); - } - - /** - * Traversing along a chain of switches and return the traveled nodes. The end thereby is defined - * by a node, that either is a dead end or part of the provided node set. If the starting node is - * not part of any switch, the starting node is returned. - * - * @param startNode Node that is meant to be the start of the switch chain - * @param switches Set of available switches - * @param possibleJunctions Set of nodes that denote possible junctions to "real" grid - * @return The end node of the switch chain - */ - private static LinkedList traverseAlongSwitchChain( - NodeInput startNode, Set switches, Set possibleJunctions) { - LinkedList traveledNodes = new LinkedList<>(); - traveledNodes.addFirst(startNode); - - /* Get the switch, that is connected to the starting node and determine the next node */ - List nextSwitches = - switches.stream().filter(switcher -> switcher.allNodes().contains(startNode)).toList(); - switch (nextSwitches.size()) { - case 0: - /* No further switch found -> Return the starting node */ - break; - case 1: - /* One next switch has been found -> Travel in this direction */ - SwitchInput nextSwitch = nextSwitches.get(0); - Optional candidateNodes = - nextSwitch.allNodes().stream().filter(node -> node != startNode).findFirst(); - NodeInput nextNode = - candidateNodes.orElseThrow( - () -> - new IllegalArgumentException( - "There is no further node available at switch " + nextSwitch)); - if (possibleJunctions.contains(nextNode)) { - /* This is a junction, leading to another Connector than a switch */ - traveledNodes.addLast(nextNode); - } else { - /* Add the traveled nodes to the nodes to be excluded, to avoid endless loops in cyclic switch topologies */ - HashSet newNodesToExclude = new HashSet<>(possibleJunctions); - newNodesToExclude.add(nextNode); - HashSet newSwitches = new HashSet<>(switches); - newSwitches.remove(nextSwitch); - traveledNodes.addAll(traverseAlongSwitchChain(nextNode, newSwitches, newNodesToExclude)); - } - break; - default: - throw new IllegalArgumentException( - "Cannot traverse along switch chain, as there is a junction included at node " - + startNode); - } - return traveledNodes; - } - /** * Combines a given collection of sub grid containers to a joint model. If the single models do * not fit together, exceptions are thrown. diff --git a/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java b/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java index 7d9e9b26b..344e1a7cb 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java @@ -20,7 +20,6 @@ import edu.ie3.datamodel.models.input.container.*; import edu.ie3.datamodel.models.input.graphics.GraphicInput; import edu.ie3.datamodel.models.input.system.SystemParticipantInput; -import edu.ie3.datamodel.utils.ContainerUtils; import edu.ie3.datamodel.utils.Try; import edu.ie3.datamodel.utils.Try.Failure; import edu.ie3.datamodel.utils.Try.Success; @@ -28,7 +27,6 @@ import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.jgrapht.Graph; import org.jgrapht.alg.connectivity.ConnectivityInspector; import org.jgrapht.graph.DefaultEdge; @@ -135,26 +133,12 @@ private GridContainerValidationUtils() { exceptions.addAll(ConnectorValidationUtils.check(transformer)); }); - /* Checking switches - * Because of the fact, that a transformer with switch gear in "upstream" direction has its corresponding node in - * upper grid connected to a switch, instead of to the transformer directly: Collect all nodes at the end of the - * upstream switch chain and add them to the set of allowed nodes */ - HashSet validSwitchNodes = new HashSet<>(nodes); - validSwitchNodes.addAll( - Stream.of(rawGridElements.getTransformer2Ws(), rawGridElements.getTransformer2Ws()) - .flatMap(Set::stream) - .parallel() - .map( - transformer -> - ContainerUtils.traverseAlongSwitchChain(transformer.getNodeA(), rawGridElements) - .getLast()) - .toList()); - + /* Checking switches */ rawGridElements .getSwitches() .forEach( switcher -> { - exceptions.add(checkNodeAvailability(switcher, validSwitchNodes)); + exceptions.add(checkNodeAvailability(switcher, nodes)); exceptions.addAll(ConnectorValidationUtils.check(switcher)); }); diff --git a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy index c07ff0d54..240312437 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy @@ -553,175 +553,6 @@ class ContainerUtilsTest extends Specification { * - filtering of system participants can be tested * - filtering of graphic elements can be tested */ - def "Traversing along a simple switch chain returns the correct list of traveled nodes"() { - given: - def nodeA = Mock(NodeInput) - def nodeB = Mock(NodeInput) - def nodeC = Mock(NodeInput) - def nodeD = Mock(NodeInput) - - def switchAB = Mock(SwitchInput) - switchAB.getNodeA() >> nodeA - switchAB.getNodeB() >> nodeB - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getNodeA() >> nodeB - switchBC.getNodeB() >> nodeC - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchCD = Mock(SwitchInput) - switchCD.getNodeA() >> nodeC - switchCD.getNodeB() >> nodeD - switchCD.allNodes() >> List.of(nodeC, nodeD) - - def switches = new HashSet() - switches.add(switchAB) - switches.add(switchBC) - switches.add(switchCD) - - def possibleJunctions = new HashSet() - - def expected = new LinkedList() - expected.addFirst(nodeA) - expected.addLast(nodeB) - expected.addLast(nodeC) - expected.addLast(nodeD) - - when: - def actual = ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - actual == expected - } - - def "Traversing along a switch chain with intermediate junction returns the correct list of traveled nodes"() { - given: - def nodeA = Mock(NodeInput) - def nodeB = Mock(NodeInput) - def nodeC = Mock(NodeInput) - def nodeD = Mock(NodeInput) - - def switchAB = Mock(SwitchInput) - switchAB.getNodeA() >> nodeA - switchAB.getNodeB() >> nodeB - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getNodeA() >> nodeB - switchBC.getNodeB() >> nodeC - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchCD = Mock(SwitchInput) - switchCD.getNodeA() >> nodeC - switchCD.getNodeB() >> nodeD - switchCD.allNodes() >> List.of(nodeC, nodeD) - - def switches = new HashSet() - switches.add(switchAB) - switches.add(switchBC) - switches.add(switchCD) - - def possibleJunctions = new HashSet() - possibleJunctions.add(nodeC) - - def expected = new LinkedList() - expected.addFirst(nodeA) - expected.addLast(nodeB) - expected.addLast(nodeC) - - when: - def actual = ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - actual == expected - } - - def "Traversing along a non existing switch chain returns the correct list of traveled nodes"() { - given: - def nodeA = Mock(NodeInput) - - def switches = new HashSet() - - def possibleJunctions = new HashSet() - - def expected = new LinkedList() - expected.addFirst(nodeA) - - when: - def actual = ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - actual == expected - } - - def "Traversing along a cyclic switch chain throws an exception"() { - given: - def nodeA = Mock(NodeInput) - def nodeB = Mock(NodeInput) - def nodeC = Mock(NodeInput) - - def switchAB = Mock(SwitchInput) - switchAB.getNodeA() >> nodeA - switchAB.getNodeB() >> nodeB - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getNodeA() >> nodeB - switchBC.getNodeB() >> nodeC - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchCA = Mock(SwitchInput) - switchCA.getNodeA() >> nodeC - switchCA.getNodeB() >> nodeA - switchCA.allNodes() >> List.of(nodeC, nodeA) - - def switches = new HashSet() - switches.add(switchAB) - switches.add(switchBC) - switches.add(switchCA) - - def possibleJunctions = new HashSet() - - when: - ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - IllegalArgumentException ex = thrown() - ex.message == "Cannot traverse along switch chain, as there is a junction included at node Mock for type " + - "'NodeInput' named 'nodeA'" - } - - def "Traversing along a switch chain with switch junction throws an exception"() { - given: - def nodeA = Mock(NodeInput) - def nodeB = Mock(NodeInput) - def nodeC = Mock(NodeInput) - def nodeD = Mock(NodeInput) - - def switchAB = Mock(SwitchInput) - switchAB.getNodeA() >> nodeA - switchAB.getNodeB() >> nodeB - switchAB.allNodes() >> List.of(nodeA, nodeB) - def switchBC = Mock(SwitchInput) - switchBC.getNodeA() >> nodeB - switchBC.getNodeB() >> nodeC - switchBC.allNodes() >> List.of(nodeB, nodeC) - def switchBD = Mock(SwitchInput) - switchBD.getNodeA() >> nodeB - switchBD.getNodeB() >> nodeD - switchBD.allNodes() >> List.of(nodeB, nodeD) - - def switches = new HashSet() - switches.add(switchAB) - switches.add(switchBC) - switches.add(switchBD) - - def possibleJunctions = new HashSet() - - when: - ContainerUtils.traverseAlongSwitchChain(nodeA, switches, possibleJunctions) - - then: - IllegalArgumentException ex = thrown() - ex.message == "Cannot traverse along switch chain, as there is a junction included at node Mock for type " + - "'NodeInput' named 'nodeB'" - } - def "Determining the surrounding sub grid containers of a two winding transformer w/o switchgear works fine"() { given: def nodeD = Mock(NodeInput) @@ -762,13 +593,13 @@ class ContainerUtilsTest extends Specification { nodeA.getSubnet() >> 1 def nodeB = Mock(NodeInput) nodeB.getUuid() >> UUID.fromString("8361b082-9d4c-4c54-97d0-2df9ac35333c") - nodeB.getSubnet() >> 2 + nodeB.getSubnet() >> 1 def nodeC = Mock(NodeInput) nodeC.getUuid() >> UUID.fromString("b9e4f16b-0317-4794-9f53-339db45a2092") - nodeC.getSubnet() >> 2 + nodeC.getSubnet() >> 1 def nodeD = Mock(NodeInput) nodeD.getUuid() >> UUID.fromString("ae4869d5-3551-4cce-a101-d61629716c4f") - nodeD.getSubnet() >> 2 + nodeD.getSubnet() >> 1 def nodeE = Mock(NodeInput) nodeE.getUuid() >> UUID.fromString("5d4107b2-385b-40fe-a668-19414bf45d9d") nodeE.getSubnet() >> 2