From 26b61524151762f69dbeeb2933b1aa9717e41eba Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 15 Oct 2021 11:57:03 +0200 Subject: [PATCH 01/10] Add impedance weighted graph --- .../graph/ImpedanceWeightedEdge.java | 37 ++++++++++++ .../graph/ImpedanceWeightedGraph.java | 59 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedEdge.java create mode 100644 src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java diff --git a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedEdge.java b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedEdge.java new file mode 100644 index 000000000..c29519a8d --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedEdge.java @@ -0,0 +1,37 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.graph; + +import static tech.units.indriya.unit.Units.OHM; + +import javax.measure.Quantity; +import javax.measure.Unit; +import javax.measure.quantity.ElectricResistance; +import org.jgrapht.graph.DefaultWeightedEdge; +import tech.units.indriya.quantity.Quantities; + +/** + * A default implementation for edges in a {@link ImpedanceWeightedGraph}. All access to the weight + * of an edge must go through the graph interface, which is why this class doesn't expose any public + * methods. + * + * @version 0.1 + * @since 04.06.20 + */ +public class ImpedanceWeightedEdge extends DefaultWeightedEdge { + private static final long serialVersionUID = -3331046813188425729L; + + protected static final Unit DEFAULT_IMPEDANCE_UNIT = OHM; + + public Quantity getImpedance() { + return Quantities.getQuantity(getWeight(), DEFAULT_IMPEDANCE_UNIT); + } + + @Override + public String toString() { + return "ImpedanceWeightedEdge{" + "impedance=" + getImpedance() + "} " + super.toString(); + } +} diff --git a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java new file mode 100644 index 000000000..f897d4d67 --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java @@ -0,0 +1,59 @@ +/* + * © 2021. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.graph; + +import edu.ie3.datamodel.models.input.NodeInput; +import java.util.function.Supplier; +import javax.measure.Quantity; +import javax.measure.quantity.ElectricResistance; +import org.jgrapht.graph.SimpleWeightedGraph; +import tech.units.indriya.ComparableQuantity; + +/** + * An impedance weighted graph that uses {@link edu.ie3.datamodel.graph.ImpedanceWeightedEdge}s as + * edge type. + */ +public class ImpedanceWeightedGraph + extends SimpleWeightedGraph { + + private static final long serialVersionUID = -2797654003980753342L; + + public ImpedanceWeightedGraph() { + super(ImpedanceWeightedEdge.class); + } + + public ImpedanceWeightedGraph( + Supplier vertexSupplier, Supplier edgeSupplier) { + super(vertexSupplier, edgeSupplier); + } + + /** + * Assigns a {@link Quantity} of type {@link ElectricResistance} to an instance of edge {@link + * ImpedanceWeightedEdge} + * + * @param edge edge whose weight should be altered + * @param weight the weight of the {@link ImpedanceWeightedEdge} + */ + public void setWeightQuantity( + ImpedanceWeightedEdge edge, ComparableQuantity weight) { + double weightDouble = + weight.to(ImpedanceWeightedEdge.DEFAULT_IMPEDANCE_UNIT).getValue().doubleValue(); + super.setEdgeWeight(edge, weightDouble); + } + + /** + * The only purpose for overriding this method is to provide a better indication of the unit that + * is expected to be passed in. It is highly advised to use the {@link this.setWeightQuantity()} + * for safety purposes that the provided edge weight is correct. + * + * @param edge the edge whose weight should be altered + * @param impedanceInOhm the weight of the {@link ImpedanceWeightedEdge} in ohm + */ + @Override + public void setEdgeWeight(ImpedanceWeightedEdge edge, double impedanceInOhm) { + super.setEdgeWeight(edge, impedanceInOhm); + } +} From 00bd76a76693b95a5f13d78b6127e6baa98b367f Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 15 Oct 2021 14:10:07 +0200 Subject: [PATCH 02/10] Add tests --- CHANGELOG.md | 1 + .../ie3/datamodel/utils/ContainerUtils.java | 152 ++++++++++++++++++ .../datamodel/utils/ContainerUtilsTest.groovy | 90 ++++++++++- 3 files changed, 236 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5978576ae..2c45c7252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - added `EvcsLocationType` support in `EvcsInput` and `EvcsInputFactory` [#406](https://github.com/ie3-institute/PowerSystemDataModel/issues/406) - Opportunity to close writer in `CsvFileSink` +- Graph with impedance weighted edges including facilities to create it [#440](https://github.com/ie3-institute/PowerSystemDataModel/issues/440) ### Fixed - adapted `LineInput` constructor to convert line length to `StandardUnits.LINE_LENGTH` [#412](https://github.com/ie3-institute/PowerSystemDataModel/issues/412) diff --git a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java index 3dfbc1c27..d9c59cbb2 100644 --- a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java @@ -5,6 +5,12 @@ */ package edu.ie3.datamodel.utils; +import static edu.ie3.util.quantities.PowerSystemUnits.KILOMETRE; +import static edu.ie3.util.quantities.PowerSystemUnits.OHM_PER_KILOMETRE; +import static java.lang.Math.pow; +import static java.lang.Math.sqrt; +import static tech.units.indriya.unit.Units.OHM; + import edu.ie3.datamodel.exceptions.InvalidGridException; import edu.ie3.datamodel.exceptions.TopologyException; import edu.ie3.datamodel.graph.*; @@ -16,13 +22,16 @@ import edu.ie3.datamodel.models.input.graphics.NodeGraphicInput; import edu.ie3.datamodel.models.input.system.*; import edu.ie3.datamodel.models.voltagelevels.VoltageLevel; +import edu.ie3.util.quantities.interfaces.SpecificResistance; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.measure.quantity.Length; import org.jgrapht.graph.DirectedMultigraph; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import tech.units.indriya.ComparableQuantity; /** Offers functionality useful for grouping different models together */ public class ContainerUtils { @@ -131,6 +140,149 @@ private static void addDistanceGraphEdge( graph.getEdge(nodeA, nodeB), GridAndGeoUtils.distanceBetweenNodes(nodeA, nodeB)); } + /** + * Returns the topology of the provided grid container as a {@link ImpedanceWeightedGraph} if the + * provided grid container's {@link RawGridElements} allows the creation of a valid topology graph + * or an empty optional otherwise. + * + * @param grid the grid container that should be converted into topology graph + * @return either an optional holding the impedance topology graph instance or an empty optional + */ + public static Optional getImpedanceTopologyGraph(GridContainer grid) { + return getImpedanceTopologyGraph(grid.getRawGrid()); + } + + /** + * Returns the topology of the provided {@link RawGridElements} as a {@link + * ImpedanceWeightedGraph}, if they allow the creation of a valid topology graph or an empty + * optional otherwise. + * + * @param rawGridElements raw grids elements as base of the distance weighted topology graph + * @return either an optional holding the impedance topology graph instance or an empty optional + */ + public static Optional getImpedanceTopologyGraph( + RawGridElements rawGridElements) { + + ImpedanceWeightedGraph graph = new ImpedanceWeightedGraph(); + + try { + rawGridElements.getNodes().forEach(graph::addVertex); + } catch (NullPointerException ex) { + log.error("At least one node entity of provided RawGridElements is null. ", ex); + return Optional.empty(); + } + + try { + rawGridElements.getLines().forEach(line -> addImpedanceGraphEdge(graph, line)); + } catch (NullPointerException | IllegalArgumentException | UnsupportedOperationException ex) { + log.error("Error adding line edges to graph: ", ex); + return Optional.empty(); + } + + try { + rawGridElements + .getSwitches() + .forEach( + switchInput -> { + if (switchInput.isClosed()) addImpedanceGraphEdge(graph, switchInput); + }); + } catch (NullPointerException | IllegalArgumentException | UnsupportedOperationException ex) { + log.error("Error adding switch edges to graph: ", ex); + return Optional.empty(); + } + + try { + rawGridElements.getTransformer2Ws().forEach(trafo2w -> addImpedanceGraphEdge(graph, trafo2w)); + } catch (NullPointerException | IllegalArgumentException | UnsupportedOperationException ex) { + log.error("Error adding 2 winding transformer edges to graph: ", ex); + return Optional.empty(); + } + + try { + rawGridElements.getTransformer3Ws().forEach(trafo3w -> addImpedanceGraphEdge(graph, trafo3w)); + } catch (NullPointerException | IllegalArgumentException | UnsupportedOperationException ex) { + log.error("Error adding 3 winding transformer edges to graph: ", ex); + return Optional.empty(); + } + + // if we reached this point, we can safely return a valid graph + return Optional.of(graph); + } + + /** + * Adds an {@link ImpedanceWeightedEdge} to the provided graph between the provided nodes a and b. + * By implementation of jGraphT this side effect cannot be removed. :( + * + * @param graph the graph to be altered + * @param connectorInput the connector input element + */ + private static void addImpedanceGraphEdge( + ImpedanceWeightedGraph graph, ConnectorInput connectorInput) { + NodeInput nodeA = connectorInput.getNodeA(); + NodeInput nodeB = connectorInput.getNodeB(); + /* Add an edge if it is not a switch or the switch is closed */ + if (!(connectorInput instanceof SwitchInput) || ((SwitchInput) connectorInput).isClosed()) + graph.addEdge(nodeA, nodeB); + + if (connectorInput instanceof LineInput) { + LineInput line = (LineInput) connectorInput; + graph.setEdgeWeight( + graph.getEdge(nodeA, nodeB), + calcImpedance(line.getType().getR(), line.getType().getX(), line.getLength())); + } + if (connectorInput instanceof SwitchInput) { + SwitchInput sw = (SwitchInput) connectorInput; + // assumption: closed switch has a resistance of 1 OHM + if (sw.isClosed()) graph.setEdgeWeight(graph.getEdge(nodeA, nodeB), 1); + } + if (connectorInput instanceof Transformer2WInput) { + Transformer2WInput trafo2w = (Transformer2WInput) connectorInput; + graph.setEdgeWeight( + graph.getEdge(nodeA, nodeB), + sqrt( + pow(trafo2w.getType().getrSc().to(OHM).getValue().doubleValue(), 2) + + pow(trafo2w.getType().getxSc().to(OHM).getValue().doubleValue(), 2))); + } + if (connectorInput instanceof Transformer3WInput) { + Transformer3WInput trafo3w = (Transformer3WInput) connectorInput; + graph.addEdge(nodeA, trafo3w.getNodeC()); + + graph.setEdgeWeight( + graph.getEdge(nodeA, nodeB), + sqrt( + pow( + trafo3w.getType().getrScA().to(OHM).getValue().doubleValue() + + trafo3w.getType().getrScB().to(OHM).getValue().doubleValue(), + 2) + + pow( + trafo3w.getType().getxScA().to(OHM).getValue().doubleValue() + + trafo3w.getType().getxScB().to(OHM).getValue().doubleValue(), + 2))); + + graph.setEdgeWeight( + graph.getEdge(nodeA, trafo3w.getNodeC()), + sqrt( + pow( + trafo3w.getType().getrScA().to(OHM).getValue().doubleValue() + + trafo3w.getType().getrScC().to(OHM).getValue().doubleValue(), + 2) + + pow( + trafo3w.getType().getxScA().to(OHM).getValue().doubleValue() + + trafo3w.getType().getxScC().to(OHM).getValue().doubleValue(), + 2))); + } + } + + private static double calcImpedance( + ComparableQuantity r, + ComparableQuantity x, + ComparableQuantity length) { + return sqrt( + pow(r.to(OHM_PER_KILOMETRE).getValue().doubleValue(), 2) + + pow(x.to(OHM_PER_KILOMETRE).getValue().doubleValue(), 2)) + * length.to(KILOMETRE).getValue().doubleValue(); + } + /** * Filters all raw grid elements for the provided subnet. For each transformer all nodes (and not * only the the node of the grid the transformer is located in) are added as well. Two winding diff --git a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy index 6e2c63500..6e622d4a7 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy @@ -7,6 +7,7 @@ package edu.ie3.datamodel.utils import edu.ie3.datamodel.exceptions.InvalidGridException import edu.ie3.datamodel.graph.DistanceWeightedGraph +import edu.ie3.datamodel.graph.ImpedanceWeightedGraph import edu.ie3.datamodel.graph.SubGridTopologyGraph import edu.ie3.datamodel.models.OperationTime import edu.ie3.datamodel.models.input.NodeInput @@ -439,25 +440,25 @@ class ContainerUtilsTest extends Specification { def "The container utils build a valid distance weighted graph model correctly"(){ given: - JointGridContainer grid = ComplexTopology.grid + def grid = ComplexTopology.grid when: - Optional resultingGraphOpt = ContainerUtils.getDistanceTopologyGraph(grid) + def resultingGraphOpt = ContainerUtils.getDistanceTopologyGraph(grid) then: resultingGraphOpt.present - DistanceWeightedGraph resultingGraph = resultingGraphOpt.get() + def resultingGraph = resultingGraphOpt.get() resultingGraph.vertexSet() == ComplexTopology.grid.getRawGrid().getNodes() resultingGraph.edgeSet().size() == 7 } - def "The container utils build a valid istance of DistanceWeightedEdge during graph generation"(){ + def "The container utils build a valid instance of DistanceWeightedEdge during graph generation"(){ given: - DistanceWeightedGraph graph = new DistanceWeightedGraph() - NodeInput nodeA = GridTestData.nodeA - NodeInput nodeB = GridTestData.nodeB + def graph = new DistanceWeightedGraph() + def nodeA = GridTestData.nodeA + def nodeB = GridTestData.nodeB when: graph.addVertex(nodeA) @@ -472,8 +473,83 @@ class ContainerUtilsTest extends Specification { assert source == nodeA assert target == nodeB } + } + + def "The container utils build a valid impedance weighted graph model correctly"(){ + given: + def grid = ComplexTopology.grid + + when: + def resultingGraphOpt = ContainerUtils.getImpedanceTopologyGraph(grid) + then: + resultingGraphOpt.present + def resultingGraph = resultingGraphOpt.get() + resultingGraph.vertexSet() == ComplexTopology.grid.getRawGrid().getNodes() + + resultingGraph.edgeSet().size() == 7 + } + + def "The container utils build a valid instance of ImpedanceWeightedEdge during graph generation"(){ + given: + def graph = new ImpedanceWeightedGraph() + + when: + /* Add a transformer */ + def transformer = GridTestData.transformerBtoD + graph.addVertex(transformer.getNodeA()) + graph.addVertex(transformer.getNodeB()) + ContainerUtils.addImpedanceGraphEdge(graph, transformer) + + and: + /* Add a line */ + def line = GridTestData.lineCtoD + graph.addVertex(line.getNodeA()) + graph.addVertex(line.getNodeB()) + ContainerUtils.addImpedanceGraphEdge(graph, line) + + and: + /* Add a closed switch */ + def swtchClosed = new SwitchInput(UUID.randomUUID(), "test_switch", GridTestData.nodeD, GridTestData.nodeE, true) + graph.addVertex(swtchClosed.getNodeA()) + graph.addVertex(swtchClosed.getNodeB()) + ContainerUtils.addImpedanceGraphEdge(graph, swtchClosed) + def swtchOpen = new SwitchInput(UUID.randomUUID(), "test_switch", GridTestData.nodeE, GridTestData.nodeF, false) + graph.addVertex(swtchOpen.getNodeA()) + graph.addVertex(swtchOpen.getNodeB()) + ContainerUtils.addImpedanceGraphEdge(graph, swtchOpen) + + and: + /* Add a three winding transformer */ + def transformer3w = GridTestData.transformerAtoBtoC + graph.addVertex(transformer3w.getNodeA()) + graph.addVertex(transformer3w.getNodeB()) + ContainerUtils.addImpedanceGraphEdge(graph, transformer3w) + + then: + graph.vertexSet().size() == 6 + graph.edgeSet().size() == 5 + + /* Check impedance of two winding transformer */ + graph.getEdge(transformer.nodeA, transformer.nodeB).with { + assert weight == 112.33121875062159d + } + /* Check impedance of three winding transformer */ + graph.getEdge(transformer3w.nodeA, transformer3w.nodeB).with { + assert weight == 1.1278408575681236d + } + graph.getEdge(transformer3w.nodeA, transformer3w.nodeC).with { + assert weight == 1.0471340124358486d + } + /* Check impedance of line */ + graph.getEdge(line.nodeA, line.nodeB).with { + assert weight == 0.0016909597866300668d + } + /* Check impedance of switch */ + graph.getEdge(swtchClosed.nodeA, swtchClosed.nodeB).with { + assert weight == 1.0d + } } /* TODO: Extend testing data so that, From 54c5fd7d388eb53286cf59d55458e23d5f3cbeac Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 15 Oct 2021 15:13:37 +0200 Subject: [PATCH 03/10] Improve JavaDoc --- .../ie3/datamodel/graph/ImpedanceWeightedGraph.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java index f897d4d67..ff19a0ff4 100644 --- a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java +++ b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java @@ -12,12 +12,8 @@ import org.jgrapht.graph.SimpleWeightedGraph; import tech.units.indriya.ComparableQuantity; -/** - * An impedance weighted graph that uses {@link edu.ie3.datamodel.graph.ImpedanceWeightedEdge}s as - * edge type. - */ -public class ImpedanceWeightedGraph - extends SimpleWeightedGraph { +/** An impedance weighted graph that uses {@link ImpedanceWeightedEdge}s as edge type. */ +public class ImpedanceWeightedGraph extends SimpleWeightedGraph { private static final long serialVersionUID = -2797654003980753342L; @@ -46,8 +42,9 @@ public void setWeightQuantity( /** * The only purpose for overriding this method is to provide a better indication of the unit that - * is expected to be passed in. It is highly advised to use the {@link this.setWeightQuantity()} - * for safety purposes that the provided edge weight is correct. + * is expected to be passed in. It is highly advised to use the {@link + * this#setWeightQuantity(ImpedanceWeightedEdge, ComparableQuantity)} for safety purposes that the + * provided edge weight is correct. * * @param edge the edge whose weight should be altered * @param impedanceInOhm the weight of the {@link ImpedanceWeightedEdge} in ohm From 5fb6202c9a377d96effba76c2971b7da4c7bb577 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Tue, 19 Oct 2021 10:47:46 +0200 Subject: [PATCH 04/10] Finally fixing JavaDoc --- .../java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java | 4 ++-- .../edu/ie3/datamodel/io/connectors/InfluxDbConnector.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java index ff19a0ff4..822bed369 100644 --- a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java +++ b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java @@ -43,8 +43,8 @@ public void setWeightQuantity( /** * The only purpose for overriding this method is to provide a better indication of the unit that * is expected to be passed in. It is highly advised to use the {@link - * this#setWeightQuantity(ImpedanceWeightedEdge, ComparableQuantity)} for safety purposes that the - * provided edge weight is correct. + * ImpedanceWeightedGraph#setWeightQuantity(ImpedanceWeightedEdge, ComparableQuantity)} for safety + * purposes that the provided edge weight is correct. * * @param edge the edge whose weight should be altered * @param impedanceInOhm the weight of the {@link ImpedanceWeightedEdge} in ohm diff --git a/src/main/java/edu/ie3/datamodel/io/connectors/InfluxDbConnector.java b/src/main/java/edu/ie3/datamodel/io/connectors/InfluxDbConnector.java index 984286cdb..685e16614 100644 --- a/src/main/java/edu/ie3/datamodel/io/connectors/InfluxDbConnector.java +++ b/src/main/java/edu/ie3/datamodel/io/connectors/InfluxDbConnector.java @@ -50,7 +50,7 @@ public class InfluxDbConnector implements DataConnector { * @param databaseName the name of the database the session should be set to * @param createDb true if the connector should create the database if it doesn't exist yet, false * otherwise - * @param logLevel log level of the {@link InfluxDB.LogLevel} logger + * @param logLevel log level of the {@link org.influxdb.InfluxDB.LogLevel} logger * @param batchOptions write options to write batch operations */ public InfluxDbConnector( From 44882d496d5e8daa2897afc9bb54bd1a21087d0f Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Wed, 10 Nov 2021 08:13:39 +0100 Subject: [PATCH 05/10] Mark edge weight setting without quantity as deprecated --- .../java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java index 822bed369..3aae92360 100644 --- a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java +++ b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java @@ -48,8 +48,11 @@ public void setWeightQuantity( * * @param edge the edge whose weight should be altered * @param impedanceInOhm the weight of the {@link ImpedanceWeightedEdge} in ohm + * @deprecated Use {@link ImpedanceWeightedGraph#setWeightQuantity(ImpedanceWeightedEdge, + * ComparableQuantity)} instead, as it provides means for proper unit handling */ @Override + @Deprecated public void setEdgeWeight(ImpedanceWeightedEdge edge, double impedanceInOhm) { super.setEdgeWeight(edge, impedanceInOhm); } From 01b8f26812b5956afb67b3b730afb5c7621f0c86 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Wed, 10 Nov 2021 08:21:52 +0100 Subject: [PATCH 06/10] Fix warnings in readthedocs --- docs/readthedocs/models/ValidationUtils.rst | 10 ++-- docs/readthedocs/models/input/grid/line.rst | 4 +- .../models/input/grid/transformer2w.rst | 4 +- .../models/input/grid/transformer3w.rst | 4 +- docs/readthedocs/models/models.rst | 51 ++++++++++--------- 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/docs/readthedocs/models/ValidationUtils.rst b/docs/readthedocs/models/ValidationUtils.rst index 93d2f1ecb..e1c46094e 100644 --- a/docs/readthedocs/models/ValidationUtils.rst +++ b/docs/readthedocs/models/ValidationUtils.rst @@ -4,16 +4,16 @@ Validation Utils This page gives an overview about the ValidationUtils in the *PowerSystemDataModel*. What are the ValidationUtils? -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +============================= The methods in ValidationUtils and subclasses can be used to check that objects are valid, meaning their parameters have valid values and they are correctly connected. What is checked? -^^^^^^^^^^^^^^^^ +================ - The check methods include checks that assigned values are valid, e.g. lines are not allowed to have negative lengths or the rated power factor of any unit must be between 0 and 1. - Furthermore, several connections are checked, e.g. that lines only connect nodes of the same voltage level or that the voltage levels indicated for the transformer sides match the voltage levels of the nodes they are connected to. How does it work? -^^^^^^^^^^^^^^^^^ +================= - The method :code:`ValidationUtils.check(Object)` is the only method that should be called by the user. - This check method identifies the object class and forwards it to a specific check method for the given object - The overall structure of the ValidationUtils methods follows a cascading scheme, orientated along the class tree @@ -26,7 +26,7 @@ How does it work? - ValidationUtils furthermore contains several utils methods used in the subclasses Which objects are checked? -^^^^^^^^^^^^^^^^^^^^^^^^^^ +========================== The ValidationUtils include validation checks for... - NodeValidationUtils @@ -81,7 +81,7 @@ The ValidationUtils include validation checks for... - SystemParticipants What should be considered? -^^^^^^^^^^^^^^^^^^^^^^^^^^ +========================== - Due to many checks with if-conditions, the usage of the ValidationUtils for many objects might be runtime relevant. - The check for a GridContainer includes the interplay of the contained entities as well as the checks of all contained entities. - If new classes are introduced to the *PowerSystemDataModel*, make sure to follow the forwarding structure of the ValidationUtils methods when writing the check methods! diff --git a/docs/readthedocs/models/input/grid/line.rst b/docs/readthedocs/models/input/grid/line.rst index 800cb3092..7a1409c2b 100644 --- a/docs/readthedocs/models/input/grid/line.rst +++ b/docs/readthedocs/models/input/grid/line.rst @@ -49,8 +49,8 @@ Entity Model | nodeB | -- | | +-------------------+------+--------------------------------------------------------+ | parallelDevices | -- | | overall amount of parallel lines to automatically | -| | -- | | construct (e.g. parallelDevices = 2 will build a | -| | -- | | total of two lines using the specified parameters) | +| | | | construct (e.g. parallelDevices = 2 will build a | +| | | | total of two lines using the specified parameters) | +-------------------+------+--------------------------------------------------------+ | type | -- | | +-------------------+------+--------------------------------------------------------+ diff --git a/docs/readthedocs/models/input/grid/transformer2w.rst b/docs/readthedocs/models/input/grid/transformer2w.rst index f9c89f1b7..9beaf4933 100644 --- a/docs/readthedocs/models/input/grid/transformer2w.rst +++ b/docs/readthedocs/models/input/grid/transformer2w.rst @@ -66,8 +66,8 @@ Entity Model | nodeB | -- | Lower voltage node | +-----------------+------+------------------------------------------------------------+ | parallelDevices | -- | | overall amount of parallel transformers to automatically | -| | -- | | construct (e.g. parallelDevices = 2 will build a | -| | -- | | total of two transformers using the specified parameters)| +| | | | construct (e.g. parallelDevices = 2 will build a | +| | | | total of two transformers using the specified parameters)| +-----------------+------+------------------------------------------------------------+ | type | -- | | +-----------------+------+------------------------------------------------------------+ diff --git a/docs/readthedocs/models/input/grid/transformer3w.rst b/docs/readthedocs/models/input/grid/transformer3w.rst index db4e67d00..5d275431f 100644 --- a/docs/readthedocs/models/input/grid/transformer3w.rst +++ b/docs/readthedocs/models/input/grid/transformer3w.rst @@ -88,8 +88,8 @@ Entity Model | nodeC | -- | Lowest voltage node | +-----------------+------+------------------------------------------------------------+ | parallelDevices | -- | | overall amount of parallel transformers to automatically | -| | -- | | construct (e.g. parallelDevices = 2 will build a | -| | -- | | total of two transformers using the specified parameters)| +| | | | construct (e.g. parallelDevices = 2 will build a | +| | | | total of two transformers using the specified parameters)| +-----------------+------+------------------------------------------------------------+ | type | -- | | +-----------------+------+------------------------------------------------------------+ diff --git a/docs/readthedocs/models/models.rst b/docs/readthedocs/models/models.rst index 62d55420b..3d9893e52 100644 --- a/docs/readthedocs/models/models.rst +++ b/docs/readthedocs/models/models.rst @@ -54,31 +54,32 @@ Equality Checks instances is not as trivial as it might seem, because there might be different understandings about the equality of quantities (e.g. there is a big difference between two instances being equal or equivalent). After long discussions how to treat quantities in the entity :code:`equals()` method, we agreed on the following rules to be applied: - - equality check is done by calling :code:`Objects.equals(, )` or - :code:`.equals()`. - Using :code:`Objects.equals(, )` is necessary especially for time series data. - As in contrast to all other places, quantity time series from real world data sometimes are not complete and - hence contain missing values. To represent missing values this is the only place where the usage of :code:`null` - is a valid choice and hence needs to be treated accordingly. Please remember that his is only allowed in very few - places and you should try to avoid using :code:`null` for quantities or any other constructor parameter whenever possible! - - equality is given if, and only if, the quantities value object and unit are exactly equal. Value objects can become - e.g. :code:`BigDecimal` or :code:`Double` instances. It is important, that the object type is also the same, otherwise - the entities :code:`equals()` method returns false. This behavior is in sync with the equals implementation - of the indriya library. Hence, you should ensure that your code always pass in the same kind of a quantity instance - with the same underlying number format and type. For this purpose you should especially be aware of the unit conversion - method :code:`AbstractQuantity.to(Quantity)` which may return seemingly unexpected types, e.g. if called on a quantity - with a :code:`double` typed value, it may return a quantity with a value of either :code:`Double` type or :code:`BigDecimal` type. - - for now, there is no default way to compare entities in a 'number equality' way provided. E.g. a line with a length - of 1km compared to a line with a length of 1000m is actually of the same length, but calling :code:`LineA.equals(LineB)` - would return :code:`false` as the equality check does NOT convert units. If you want to compare two entity instances - based on their equivalence you have (for now) check for each quantity manually using their :code:`isEquivalentTo()` - method. If you think you would benefit from a standard method that allows entity equivalence check, please consider - handing in an issue `here `_. - Furthermore, the current existing implementation of :code:`isEquivalentTo()` in indriya does not allow the provision of - a tolerance threshold that might be necessary when comparing values from floating point operations. We consider - providing such a method in our `PowerSystemUtils `_ library. - If you think you would benefit from such a method, please consider handing in an issue - `here `_. + + - equality check is done by calling :code:`Objects.equals(, )` or + :code:`.equals()`. + Using :code:`Objects.equals(, )` is necessary especially for time series data. + As in contrast to all other places, quantity time series from real world data sometimes are not complete and + hence contain missing values. To represent missing values this is the only place where the usage of :code:`null` + is a valid choice and hence needs to be treated accordingly. Please remember that his is only allowed in very few + places and you should try to avoid using :code:`null` for quantities or any other constructor parameter whenever possible! + - equality is given if, and only if, the quantities value object and unit are exactly equal. Value objects can become + e.g. :code:`BigDecimal` or :code:`Double` instances. It is important, that the object type is also the same, otherwise + the entities :code:`equals()` method returns false. This behavior is in sync with the equals implementation + of the indriya library. Hence, you should ensure that your code always pass in the same kind of a quantity instance + with the same underlying number format and type. For this purpose you should especially be aware of the unit conversion + method :code:`AbstractQuantity.to(Quantity)` which may return seemingly unexpected types, e.g. if called on a quantity + with a :code:`double` typed value, it may return a quantity with a value of either :code:`Double` type or :code:`BigDecimal` type. + - for now, there is no default way to compare entities in a 'number equality' way provided. E.g. a line with a length + of 1km compared to a line with a length of 1000m is actually of the same length, but calling :code:`LineA.equals(LineB)` + would return :code:`false` as the equality check does NOT convert units. If you want to compare two entity instances + based on their equivalence you have (for now) check for each quantity manually using their :code:`isEquivalentTo()` + method. If you think you would benefit from a standard method that allows entity equivalence check, please consider + handing in an issue `here `_. + Furthermore, the current existing implementation of :code:`isEquivalentTo()` in indriya does not allow the provision of + a tolerance threshold that might be necessary when comparing values from floating point operations. We consider + providing such a method in our `PowerSystemUtils `_ library. + If you think you would benefit from such a method, please consider handing in an issue + `here `_. Conditional Parameters Some of the models have conditional parameters. When reading model data from a data source, their respective factories for building these From a278d39886244ba3bb668af35387331b8e81e5ca Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 14 Jan 2022 15:28:31 +0100 Subject: [PATCH 07/10] Reduce log pollution for tests --- gradle/scripts/tests.gradle | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gradle/scripts/tests.gradle b/gradle/scripts/tests.gradle index 44f17c2e0..cfc5cff16 100644 --- a/gradle/scripts/tests.gradle +++ b/gradle/scripts/tests.gradle @@ -1,7 +1,12 @@ test { useJUnitPlatform() testLogging { - events "skipped", "failed", "passed" + events "skipped", "failed" + } + + // Improve logging for failed Spock tests + testLogging { + exceptionFormat "full" } } From dd2fadc44610cbc1010b26dbd92e95c29c8eda11 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 14 Jan 2022 15:34:35 +0100 Subject: [PATCH 08/10] Addressing reviewers comments --- .../graph/ImpedanceWeightedGraph.java | 8 +- .../ie3/datamodel/utils/ContainerUtils.java | 78 +++++++++++-------- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java index 3aae92360..f01bbb54a 100644 --- a/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java +++ b/src/main/java/edu/ie3/datamodel/graph/ImpedanceWeightedGraph.java @@ -33,7 +33,7 @@ public ImpedanceWeightedGraph( * @param edge edge whose weight should be altered * @param weight the weight of the {@link ImpedanceWeightedEdge} */ - public void setWeightQuantity( + public void setEdgeWeightQuantity( ImpedanceWeightedEdge edge, ComparableQuantity weight) { double weightDouble = weight.to(ImpedanceWeightedEdge.DEFAULT_IMPEDANCE_UNIT).getValue().doubleValue(); @@ -43,12 +43,12 @@ public void setWeightQuantity( /** * The only purpose for overriding this method is to provide a better indication of the unit that * is expected to be passed in. It is highly advised to use the {@link - * ImpedanceWeightedGraph#setWeightQuantity(ImpedanceWeightedEdge, ComparableQuantity)} for safety - * purposes that the provided edge weight is correct. + * ImpedanceWeightedGraph#setEdgeWeightQuantity(ImpedanceWeightedEdge, ComparableQuantity)} for + * safety purposes that the provided edge weight is correct. * * @param edge the edge whose weight should be altered * @param impedanceInOhm the weight of the {@link ImpedanceWeightedEdge} in ohm - * @deprecated Use {@link ImpedanceWeightedGraph#setWeightQuantity(ImpedanceWeightedEdge, + * @deprecated Use {@link ImpedanceWeightedGraph#setEdgeWeightQuantity(ImpedanceWeightedEdge, * ComparableQuantity)} instead, as it provides means for proper unit handling */ @Override diff --git a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java index d9c59cbb2..f17d6ac76 100644 --- a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java @@ -6,7 +6,6 @@ package edu.ie3.datamodel.utils; import static edu.ie3.util.quantities.PowerSystemUnits.KILOMETRE; -import static edu.ie3.util.quantities.PowerSystemUnits.OHM_PER_KILOMETRE; import static java.lang.Math.pow; import static java.lang.Math.sqrt; import static tech.units.indriya.unit.Units.OHM; @@ -27,11 +26,13 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.measure.quantity.ElectricResistance; import javax.measure.quantity.Length; import org.jgrapht.graph.DirectedMultigraph; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import tech.units.indriya.ComparableQuantity; +import tech.units.indriya.quantity.Quantities; /** Offers functionality useful for grouping different models together */ public class ContainerUtils { @@ -226,61 +227,72 @@ private static void addImpedanceGraphEdge( if (connectorInput instanceof LineInput) { LineInput line = (LineInput) connectorInput; - graph.setEdgeWeight( + graph.setEdgeWeightQuantity( graph.getEdge(nodeA, nodeB), calcImpedance(line.getType().getR(), line.getType().getX(), line.getLength())); } if (connectorInput instanceof SwitchInput) { SwitchInput sw = (SwitchInput) connectorInput; // assumption: closed switch has a resistance of 1 OHM - if (sw.isClosed()) graph.setEdgeWeight(graph.getEdge(nodeA, nodeB), 1); + if (sw.isClosed()) + graph.setEdgeWeightQuantity(graph.getEdge(nodeA, nodeB), Quantities.getQuantity(1d, OHM)); } if (connectorInput instanceof Transformer2WInput) { Transformer2WInput trafo2w = (Transformer2WInput) connectorInput; - graph.setEdgeWeight( + graph.setEdgeWeightQuantity( graph.getEdge(nodeA, nodeB), - sqrt( - pow(trafo2w.getType().getrSc().to(OHM).getValue().doubleValue(), 2) - + pow(trafo2w.getType().getxSc().to(OHM).getValue().doubleValue(), 2))); + calcImpedance(trafo2w.getType().getrSc(), trafo2w.getType().getxSc())); } if (connectorInput instanceof Transformer3WInput) { Transformer3WInput trafo3w = (Transformer3WInput) connectorInput; graph.addEdge(nodeA, trafo3w.getNodeC()); - graph.setEdgeWeight( + graph.setEdgeWeightQuantity( graph.getEdge(nodeA, nodeB), - sqrt( - pow( - trafo3w.getType().getrScA().to(OHM).getValue().doubleValue() - + trafo3w.getType().getrScB().to(OHM).getValue().doubleValue(), - 2) - + pow( - trafo3w.getType().getxScA().to(OHM).getValue().doubleValue() - + trafo3w.getType().getxScB().to(OHM).getValue().doubleValue(), - 2))); - - graph.setEdgeWeight( + calcImpedance( + trafo3w.getType().getrScA().add(trafo3w.getType().getrScB()), + trafo3w.getType().getxScA().add(trafo3w.getType().getxScB()))); + + graph.setEdgeWeightQuantity( graph.getEdge(nodeA, trafo3w.getNodeC()), - sqrt( - pow( - trafo3w.getType().getrScA().to(OHM).getValue().doubleValue() - + trafo3w.getType().getrScC().to(OHM).getValue().doubleValue(), - 2) - + pow( - trafo3w.getType().getxScA().to(OHM).getValue().doubleValue() - + trafo3w.getType().getxScC().to(OHM).getValue().doubleValue(), - 2))); + calcImpedance( + trafo3w.getType().getrScA().add(trafo3w.getType().getrScC()), + trafo3w.getType().getxScA().add(trafo3w.getType().getxScC()))); } } - private static double calcImpedance( + /** + * Calculate the total magnitude of the complex impedance, defined by relative resistance, + * reactance and an equivalent length + * + * @param r Relative resistance + * @param x Relative reactance + * @param length Length of the element + * @return Magnitude of the complex impedance + */ + private static ComparableQuantity calcImpedance( ComparableQuantity r, ComparableQuantity x, ComparableQuantity length) { - return sqrt( - pow(r.to(OHM_PER_KILOMETRE).getValue().doubleValue(), 2) - + pow(x.to(OHM_PER_KILOMETRE).getValue().doubleValue(), 2)) - * length.to(KILOMETRE).getValue().doubleValue(); + return calcImpedance( + r.multiply(length.to(KILOMETRE)).asType(ElectricResistance.class).to(OHM), + x.multiply(length.to(KILOMETRE)).asType(ElectricResistance.class).to(OHM)); + } + + /** + * Calculate the magnitude of the complex impedance from given resistance and reactance + * + * @param r Resistance (real part of the complex impedance) + * @param x Reactance (complex part of the complex impedance) + * @return Magnitude of the complex impedance + */ + private static ComparableQuantity calcImpedance( + ComparableQuantity r, ComparableQuantity x) { + double zValue = + sqrt( + pow(r.to(OHM).getValue().doubleValue(), 2) + + pow(x.to(OHM).getValue().doubleValue(), 2)); + return Quantities.getQuantity(zValue, OHM); } /** From 1ad7d8ee7993a8dc74debfd14ed4c94ea1cb3595 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Fri, 14 Jan 2022 15:54:03 +0100 Subject: [PATCH 09/10] Fix tests --- .../groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy index 6e622d4a7..cef91c26b 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy @@ -544,7 +544,7 @@ class ContainerUtilsTest extends Specification { } /* Check impedance of line */ graph.getEdge(line.nodeA, line.nodeB).with { - assert weight == 0.0016909597866300668d + assert weight == 0.0016909597866300665d } /* Check impedance of switch */ graph.getEdge(swtchClosed.nodeA, swtchClosed.nodeB).with { From 8493071178aa3b62ccaba7a4232ab46bbc80dab1 Mon Sep 17 00:00:00 2001 From: "Kittl, Chris" Date: Thu, 20 Jan 2022 14:05:18 +0100 Subject: [PATCH 10/10] Addressing reviewer's comments --- .../edu/ie3/datamodel/utils/ContainerUtilsTest.groovy | 11 ++++++----- .../edu/ie3/test/helper/DoubleTestHelper.groovy | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 src/test/groovy/edu/ie3/test/helper/DoubleTestHelper.groovy diff --git a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy index cef91c26b..2b76d16ed 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/ContainerUtilsTest.groovy @@ -19,6 +19,7 @@ import edu.ie3.datamodel.models.input.container.* import edu.ie3.datamodel.models.voltagelevels.VoltageLevel import edu.ie3.test.common.ComplexTopology import edu.ie3.test.common.GridTestData +import edu.ie3.test.helper.DoubleTestHelper import spock.lang.Shared import spock.lang.Specification import tech.units.indriya.quantity.Quantities @@ -533,22 +534,22 @@ class ContainerUtilsTest extends Specification { /* Check impedance of two winding transformer */ graph.getEdge(transformer.nodeA, transformer.nodeB).with { - assert weight == 112.33121875062159d + assert DoubleTestHelper.equalsWithTolerance(weight, 112.33121875062159d, 1E-6) } /* Check impedance of three winding transformer */ graph.getEdge(transformer3w.nodeA, transformer3w.nodeB).with { - assert weight == 1.1278408575681236d + assert DoubleTestHelper.equalsWithTolerance(weight, 1.1278408575681236d, 1E-6) } graph.getEdge(transformer3w.nodeA, transformer3w.nodeC).with { - assert weight == 1.0471340124358486d + assert DoubleTestHelper.equalsWithTolerance(weight, 1.0471340124358486d, 1E-6) } /* Check impedance of line */ graph.getEdge(line.nodeA, line.nodeB).with { - assert weight == 0.0016909597866300665d + assert DoubleTestHelper.equalsWithTolerance(weight, 0.0016909597866300665d, 1E-6) } /* Check impedance of switch */ graph.getEdge(swtchClosed.nodeA, swtchClosed.nodeB).with { - assert weight == 1.0d + assert DoubleTestHelper.equalsWithTolerance(weight, 1.0d, 1E-6) } } diff --git a/src/test/groovy/edu/ie3/test/helper/DoubleTestHelper.groovy b/src/test/groovy/edu/ie3/test/helper/DoubleTestHelper.groovy new file mode 100644 index 000000000..43494aaa4 --- /dev/null +++ b/src/test/groovy/edu/ie3/test/helper/DoubleTestHelper.groovy @@ -0,0 +1,7 @@ +package edu.ie3.test.helper + +class DoubleTestHelper { + static def equalsWithTolerance(double lhs, double rhs, double tolerance) { + return lhs >= rhs - tolerance && lhs <= rhs + tolerance + } +} \ No newline at end of file