From 2cb0ed1f951108d8ca59d7c23db7e1d6fb125867 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 23 May 2024 14:53:27 +0200 Subject: [PATCH] `GridContainerValidationUtils` checks the connectivity for all defined operation time intervals. --- CHANGELOG.md | 1 + docs/readthedocs/io/ValidationUtils.md | 1 + .../GridContainerValidationUtils.java | 116 +++++++++++++++++- .../GridContainerValidationUtilsTest.groovy | 106 ++++++++++++++++ 4 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 src/test/groovy/edu/ie3/datamodel/utils/validation/GridContainerValidationUtilsTest.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index 1355759e9..6b0006698 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 - Enhancing `VoltageLevel` with `equals` method [#1063](https://github.com/ie3-institute/PowerSystemDataModel/issues/1063) - `ConnectorValidationUtils` checks if parallel devices is > 0 [#1077](https://github.com/ie3-institute/PowerSystemDataModel/issues/1077) +- `GridContainerValidationUtils` checks the connectivity for all defined operation time intervals [#1091](https://github.com/ie3-institute/PowerSystemDataModel/issues/1091) ### Fixed - Fixed `MappingEntryies` not getting processed by adding `Getter` methods for record fields [#1084](https://github.com/ie3-institute/PowerSystemDataModel/issues/1084) diff --git a/docs/readthedocs/io/ValidationUtils.md b/docs/readthedocs/io/ValidationUtils.md index 34771c745..8dc2ce73d 100644 --- a/docs/readthedocs/io/ValidationUtils.md +++ b/docs/readthedocs/io/ValidationUtils.md @@ -11,6 +11,7 @@ The methods in ValidationUtils and subclasses can be used to check that objects The general validation checks: - if 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. +- the connectivity of the given grid for all defined operation intervals The uniqueness validation checks if a collection of given objects are unique in either: - a specific field 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 9c0df8194..2b0245466 100644 --- a/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java +++ b/src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java @@ -8,19 +8,33 @@ import static edu.ie3.datamodel.utils.validation.UniquenessValidationUtils.checkAssetUniqueness; import static edu.ie3.datamodel.utils.validation.UniquenessValidationUtils.checkUniqueEntities; -import edu.ie3.datamodel.exceptions.*; +import edu.ie3.datamodel.exceptions.DuplicateEntitiesException; +import edu.ie3.datamodel.exceptions.InvalidEntityException; +import edu.ie3.datamodel.exceptions.InvalidGridException; +import edu.ie3.datamodel.exceptions.ValidationException; +import edu.ie3.datamodel.models.OperationTime; import edu.ie3.datamodel.models.input.AssetInput; import edu.ie3.datamodel.models.input.MeasurementUnitInput; import edu.ie3.datamodel.models.input.NodeInput; -import edu.ie3.datamodel.models.input.connector.*; +import edu.ie3.datamodel.models.input.connector.ConnectorInput; +import edu.ie3.datamodel.models.input.connector.LineInput; +import edu.ie3.datamodel.models.input.connector.Transformer3WInput; 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.*; +import edu.ie3.datamodel.utils.Try.Failure; +import edu.ie3.datamodel.utils.Try.Success; +import java.time.ZonedDateTime; 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; +import org.jgrapht.graph.SimpleGraph; public class GridContainerValidationUtils extends ValidationUtils { @@ -155,9 +169,105 @@ private GridContainerValidationUtils() { exceptions.add(MeasurementUnitValidationUtils.check(measurement)); }); + exceptions.addAll(checkConnectivity(rawGridElements)); + return exceptions; } + /** + * Checks the connectivity of the given grid for all defined {@link OperationTime}s. If every + * {@link AssetInput} is set to {@link OperationTime#notLimited()}, the connectivity is only + * checked once. + * + * @param rawGridElements to check + * @return a try + */ + protected static List> checkConnectivity( + RawGridElements rawGridElements) { + Set times = + rawGridElements.allEntitiesAsList().stream() + .map(AssetInput::getOperationTime) + .filter(OperationTime::isLimited) + .map(OperationTime::getOperationLimit) + .filter(Optional::isPresent) + .map(Optional::get) + .map(interval -> Set.of(interval.getLower(), interval.getUpper())) + .flatMap(Collection::stream) + .collect(Collectors.toSet()); + + if (times.isEmpty()) { + return List.of(checkConnectivity(rawGridElements, Optional.empty())); + } else { + return times.stream() + .sorted() + .map(time -> checkConnectivity(rawGridElements, Optional.of(time))) + .toList(); + } + } + + /** + * Checks if the given {@link RawGridElements} from a connected grid. + * + * @param rawGridElements to check + * @param time for operation filtering + * @return a try + */ + protected static Try checkConnectivity( + RawGridElements rawGridElements, Optional time) { + + Predicate isInOperation = + assetInput -> time.map(assetInput::inOperationOn).orElse(true); + + // build graph + Graph graph = new SimpleGraph<>(DefaultEdge.class); + + rawGridElements.getNodes().stream() + .filter(isInOperation) + .forEach(node -> graph.addVertex(node.getUuid())); + rawGridElements.getLines().stream() + .filter(isInOperation) + .forEach( + connector -> + graph.addEdge(connector.getNodeA().getUuid(), connector.getNodeB().getUuid())); + rawGridElements.getTransformer2Ws().stream() + .filter(isInOperation) + .forEach( + connector -> + graph.addEdge(connector.getNodeA().getUuid(), connector.getNodeB().getUuid())); + rawGridElements.getTransformer3Ws().stream() + .filter(isInOperation) + .forEach( + connector -> + graph.addEdge(connector.getNodeA().getUuid(), connector.getNodeB().getUuid())); + rawGridElements.getSwitches().stream() + .filter(isInOperation) + .forEach( + connector -> + graph.addEdge(connector.getNodeA().getUuid(), connector.getNodeB().getUuid())); + + ConnectivityInspector inspector = new ConnectivityInspector<>(graph); + + if (inspector.isConnected()) { + return Success.empty(); + } else { + List> sets = inspector.connectedSets(); + + List unconnected = + sets.stream() + .max(Comparator.comparing(Set::size)) + .map(set -> graph.vertexSet().stream().filter(v -> !set.contains(v)).toList()) + .orElse(List.of()); + + String message = "The grid contains unconnected elements"; + + if (time.isPresent()) { + message += " for time " + time.get(); + } + + return Failure.of(new InvalidGridException(message + ": " + unconnected)); + } + } + /** * Checks the validity of each and every system participant. Moreover, it checks, if the systems * are connected to a node that is not in the provided set diff --git a/src/test/groovy/edu/ie3/datamodel/utils/validation/GridContainerValidationUtilsTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/validation/GridContainerValidationUtilsTest.groovy new file mode 100644 index 000000000..013bad1a4 --- /dev/null +++ b/src/test/groovy/edu/ie3/datamodel/utils/validation/GridContainerValidationUtilsTest.groovy @@ -0,0 +1,106 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.datamodel.utils.validation + +import edu.ie3.datamodel.exceptions.InvalidGridException +import edu.ie3.datamodel.models.OperationTime +import edu.ie3.datamodel.models.input.container.RawGridElements +import edu.ie3.datamodel.utils.Try +import edu.ie3.test.common.GridTestData as GTD +import spock.lang.Shared +import spock.lang.Specification + +import java.time.ZonedDateTime + +class GridContainerValidationUtilsTest extends Specification { + @Shared + private ZonedDateTime start + + @Shared + private RawGridElements limitedElements + + def setupSpec() { + start = ZonedDateTime.now() + + def operationTimeFrame1 = OperationTime.builder().withStart(start).withEnd(start.plusHours(2)).build() + def operationTimeFrame2 = OperationTime.builder().withStart(start.plusHours(1)).withEnd(start.plusHours(3)).build() + + def nodes = [ + GTD.nodeC, + GTD.nodeD, + GTD.nodeE, + GTD.nodeF, + GTD.nodeG + ] as Set + + def lines = [ + GTD.lineCtoD.copy().operationTime(operationTimeFrame1).build(), + GTD.lineFtoG.copy().operationTime(operationTimeFrame2).build() + ] as Set + + def transformers = [ + GTD.transformerCtoF.copy().operationTime(operationTimeFrame1).build(), + GTD.transformerCtoE.copy().operationTime(operationTimeFrame2).build() + ] as Set + + limitedElements = new RawGridElements(nodes, lines, transformers, [] as Set, [] as Set, [] as Set) + } + + def "The GridContainerValidationUtils should check the connectivity for all operation intervals correctly"() { + when: + def actual = GridContainerValidationUtils.checkConnectivity(limitedElements) + + then: + actual.size() == 4 + actual.get(0).failure + actual.get(1).success + actual.get(2).success + actual.get(3).failure + + actual.get(0).exception.get().message == "The grid contains unconnected elements for time " + start + ": " + [ + GTD.nodeE.uuid, + GTD.nodeG.uuid + ] + actual.get(3).exception.get().message == "The grid contains unconnected elements for time " + start.plusHours(3) + ": " + [ + GTD.nodeD.uuid, + GTD.nodeF.uuid, + GTD.nodeG.uuid + ] + } + + def "The GridContainerValidationUtils should check the connectivity correctly"() { + when: + def actual = GridContainerValidationUtils.checkConnectivity(limitedElements, time as Optional) + + then: + actual == expectedResult + + where: + time || expectedResult + Optional.empty() || Try.Success.empty() + Optional.of(start.plusHours(1)) || Try.Success.empty() + } + + def "The GridContainerValidationUtils should return an exception if the grid is not properly connected"() { + when: + def actual = GridContainerValidationUtils.checkConnectivity(limitedElements, time as Optional) + + then: + actual.exception.get().message == expectedException.message + + where: + time || expectedException + Optional.of(start) || new InvalidGridException("The grid contains unconnected elements for time " + start + ": " + [ + GTD.nodeE.uuid, + GTD.nodeG.uuid + ]) + Optional.of(start.plusHours(3)) || new InvalidGridException("The grid contains unconnected elements for time " + start.plusHours(3) + ": " + [ + GTD.nodeD.uuid, + GTD.nodeF.uuid, + GTD.nodeG.uuid + ]) + } +}