diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f775f950..5bba767da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - SQL time series sources (`SqlTimeSeriesSource` and `SqlTimeSeriesMappingSource`) [#467](https://github.com/ie3-institute/PowerSystemDataModel/issues/467) +- Graph with impedance weighted edges including facilities to create it [#440](https://github.com/ie3-institute/PowerSystemDataModel/issues/440) ### Fixed - Reduced code smells [#492](https://github.com/ie3-institute/PowerSystemDataModel/issues/492) 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 diff --git a/gradle/scripts/tests.gradle b/gradle/scripts/tests.gradle index 78732aa55..f3f714b05 100644 --- a/gradle/scripts/tests.gradle +++ b/gradle/scripts/tests.gradle @@ -4,6 +4,7 @@ test { events "skipped", "failed" } + // Improve logging for failed Spock tests testLogging { exceptionFormat "Full" } 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..f01bbb54a --- /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 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 setEdgeWeightQuantity( + 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 + * 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#setEdgeWeightQuantity(ImpedanceWeightedEdge, + * ComparableQuantity)} instead, as it provides means for proper unit handling + */ + @Override + @Deprecated + public void setEdgeWeight(ImpedanceWeightedEdge edge, double impedanceInOhm) { + super.setEdgeWeight(edge, impedanceInOhm); + } +} 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( diff --git a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java index 9b70a51b6..ec7401d5f 100644 --- a/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/ContainerUtils.java @@ -5,6 +5,11 @@ */ package edu.ie3.datamodel.utils; +import static edu.ie3.util.quantities.PowerSystemUnits.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 +21,18 @@ 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.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 { @@ -131,6 +141,160 @@ 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.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.setEdgeWeightQuantity(graph.getEdge(nodeA, nodeB), Quantities.getQuantity(1d, OHM)); + } + if (connectorInput instanceof Transformer2WInput) { + Transformer2WInput trafo2w = (Transformer2WInput) connectorInput; + graph.setEdgeWeightQuantity( + graph.getEdge(nodeA, nodeB), + calcImpedance(trafo2w.getType().getrSc(), trafo2w.getType().getxSc())); + } + if (connectorInput instanceof Transformer3WInput) { + Transformer3WInput trafo3w = (Transformer3WInput) connectorInput; + graph.addEdge(nodeA, trafo3w.getNodeC()); + + graph.setEdgeWeightQuantity( + graph.getEdge(nodeA, nodeB), + calcImpedance( + trafo3w.getType().getrScA().add(trafo3w.getType().getrScB()), + trafo3w.getType().getxScA().add(trafo3w.getType().getxScB()))); + + graph.setEdgeWeightQuantity( + graph.getEdge(nodeA, trafo3w.getNodeC()), + calcImpedance( + trafo3w.getType().getrScA().add(trafo3w.getType().getrScC()), + trafo3w.getType().getxScA().add(trafo3w.getType().getxScC()))); + } + } + + /** + * 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 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); + } + /** * 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..2b76d16ed 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 @@ -18,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 @@ -439,25 +441,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 +474,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 DoubleTestHelper.equalsWithTolerance(weight, 112.33121875062159d, 1E-6) + } + /* Check impedance of three winding transformer */ + graph.getEdge(transformer3w.nodeA, transformer3w.nodeB).with { + assert DoubleTestHelper.equalsWithTolerance(weight, 1.1278408575681236d, 1E-6) + } + graph.getEdge(transformer3w.nodeA, transformer3w.nodeC).with { + assert DoubleTestHelper.equalsWithTolerance(weight, 1.0471340124358486d, 1E-6) + } + /* Check impedance of line */ + graph.getEdge(line.nodeA, line.nodeB).with { + assert DoubleTestHelper.equalsWithTolerance(weight, 0.0016909597866300665d, 1E-6) + } + /* Check impedance of switch */ + graph.getEdge(swtchClosed.nodeA, swtchClosed.nodeB).with { + assert DoubleTestHelper.equalsWithTolerance(weight, 1.0d, 1E-6) + } } /* TODO: Extend testing data so that, 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