diff --git a/src/main/java/com/powsybl/openloadflow/graph/AbstractGraphConnectivity.java b/src/main/java/com/powsybl/openloadflow/graph/AbstractGraphConnectivity.java index a1388c8441..03b055b630 100644 --- a/src/main/java/com/powsybl/openloadflow/graph/AbstractGraphConnectivity.java +++ b/src/main/java/com/powsybl/openloadflow/graph/AbstractGraphConnectivity.java @@ -20,7 +20,7 @@ public abstract class AbstractGraphConnectivity implements GraphConnectivi private final Graph graph = new Pseudograph<>(null, null, false); - private final Deque>> graphModifications = new ArrayDeque<>(); + private final Deque> modificationsContexts = new ArrayDeque<>(); protected List> componentSets; @@ -37,10 +37,14 @@ public abstract class AbstractGraphConnectivity implements GraphConnectivi @Override public void addVertex(V vertex) { Objects.requireNonNull(vertex); + if (graph.containsVertex(vertex)) { + return; + } VertexAdd vertexAdd = new VertexAdd<>(vertex); vertexAdd.apply(graph); - if (!graphModifications.isEmpty()) { - graphModifications.peekLast().add(vertexAdd); + if (!modificationsContexts.isEmpty()) { + ModificationsContext modificationsContext = modificationsContexts.peekLast(); + modificationsContext.add(vertexAdd); updateConnectivity(vertexAdd); } } @@ -55,8 +59,9 @@ public void addEdge(V vertex1, V vertex2, E edge) { } EdgeAdd edgeAdd = new EdgeAdd<>(vertex1, vertex2, edge); edgeAdd.apply(graph); - if (!graphModifications.isEmpty()) { - graphModifications.peekLast().add(edgeAdd); + if (!modificationsContexts.isEmpty()) { + ModificationsContext modificationsContext = modificationsContexts.peekLast(); + modificationsContext.add(edgeAdd); updateConnectivity(edgeAdd); } } @@ -71,25 +76,29 @@ public void removeEdge(E edge) { V vertex2 = graph.getEdgeTarget(edge); EdgeRemove edgeRemove = new EdgeRemove<>(vertex1, vertex2, edge); edgeRemove.apply(graph); - if (!graphModifications.isEmpty()) { - graphModifications.peekLast().add(edgeRemove); + if (!modificationsContexts.isEmpty()) { + ModificationsContext modificationsContext = modificationsContexts.peekLast(); + modificationsContext.add(edgeRemove); updateConnectivity(edgeRemove); } } @Override public void startTemporaryChanges() { - graphModifications.add(new ArrayDeque<>()); + ModificationsContext modificationsContext = new ModificationsContext<>(); + modificationsContexts.add(modificationsContext); + modificationsContext.setVerticesInitiallyNotInMainComponent(getSmallComponents()); } @Override public void undoTemporaryChanges() { - if (graphModifications.isEmpty()) { + if (modificationsContexts.isEmpty()) { throw new PowsyblException("Cannot reset, no remaining saved connectivity"); } - Deque> m = graphModifications.pollLast(); - resetConnectivity(m); - m.descendingIterator().forEachRemaining(gm -> gm.undo(graph)); + ModificationsContext m = modificationsContexts.pollLast(); + Deque> modifications = m.getModifications(); + resetConnectivity(modifications); + modifications.descendingIterator().forEachRemaining(gm -> gm.undo(graph)); } @Override @@ -134,12 +143,12 @@ public Graph getGraph() { return graph; } - protected Deque>> getGraphModifications() { - return graphModifications; + protected Deque> getModificationsContexts() { + return modificationsContexts; } protected void checkSaved() { - if (graphModifications.isEmpty()) { + if (modificationsContexts.isEmpty()) { throw new PowsyblException("Cannot compute connectivity without a saved state, please call GraphConnectivity::startTemporaryChanges at least once beforehand"); } } @@ -149,4 +158,32 @@ protected void checkVertex(V vertex) { throw new AssertionError("given vertex " + vertex + " is not in the graph"); } } + + @Override + public Set getVerticesAddedToMainComponent() { + Set verticesNotInMainComponent = getVerticesNotInMainComponent(); // exception thrown if modificationsContexts empty + return modificationsContexts.peekLast().getVerticesAddedToMainComponent(verticesNotInMainComponent); + } + + @Override + public Set getEdgesAddedToMainComponent() { + Set verticesNotInMainComponent = getVerticesNotInMainComponent(); // exception thrown if modificationsContexts empty + return modificationsContexts.peekLast().getEdgesAddedToMainComponent(verticesNotInMainComponent, graph); + } + + @Override + public Set getVerticesRemovedFromMainComponent() { + Set verticesNotInMainComponent = getVerticesNotInMainComponent(); // exception thrown if modificationsContexts empty + return modificationsContexts.peekLast().getVerticesRemovedFromMainComponent(verticesNotInMainComponent); + } + + @Override + public Set getEdgesRemovedFromMainComponent() { + Set verticesNotInMainComponent = getVerticesNotInMainComponent(); // exception thrown if modificationsContexts empty + return modificationsContexts.peekLast().getEdgesRemovedFromMainComponent(verticesNotInMainComponent, graph); + } + + private Set getVerticesNotInMainComponent() { + return getSmallComponents().stream().flatMap(Set::stream).collect(Collectors.toSet()); + } } diff --git a/src/main/java/com/powsybl/openloadflow/graph/EvenShiloachGraphDecrementalConnectivity.java b/src/main/java/com/powsybl/openloadflow/graph/EvenShiloachGraphDecrementalConnectivity.java index bbf427c8b6..d333e75285 100644 --- a/src/main/java/com/powsybl/openloadflow/graph/EvenShiloachGraphDecrementalConnectivity.java +++ b/src/main/java/com/powsybl/openloadflow/graph/EvenShiloachGraphDecrementalConnectivity.java @@ -33,7 +33,7 @@ protected void updateConnectivity(EdgeRemove edgeRemoval) { vertexToConnectedComponent = null; componentSets = null; - GraphProcess processA = new GraphProcessA(edgeRemoval.v1, edgeRemoval.v2, newConnectedComponents); + GraphProcessA processA = new GraphProcessA(edgeRemoval.v1, edgeRemoval.v2); GraphProcessB processB = new GraphProcessB(edgeRemoval.v1, edgeRemoval.v2); while (!processA.isHalted() && !processB.isHalted()) { processA.next(); @@ -44,11 +44,18 @@ protected void updateConnectivity(EdgeRemove edgeRemoval) { if (processA.isHalted()) { processB.undoChanges(); + updateNewConnectedComponents(processA.verticesOut); } else { // processB halted allSavedChangedLevels.add(processB.savedChangedLevels); } } + private void updateNewConnectedComponents(Set verticesOut) { + // the removed edge can be in a new connected component! + newConnectedComponents.forEach(cc -> cc.removeAll(verticesOut)); + newConnectedComponents.add(verticesOut); + } + @Override protected void updateConnectivity(EdgeAdd edgeAdd) { throw new PowsyblException("This implementation does not support incremental connectivity: edges cannot be added once that connectivity is saved"); @@ -75,8 +82,8 @@ protected void updateComponents() { @Override public void startTemporaryChanges() { - if (!getGraphModifications().isEmpty()) { - throw new PowsyblException("This implementation supports only level of temporary changes"); + if (!getModificationsContexts().isEmpty()) { + throw new PowsyblException("This implementation supports only one level of temporary changes"); } super.startTemporaryChanges(); if (levelNeighboursMap.isEmpty()) { @@ -181,36 +188,32 @@ private interface GraphProcess { private class GraphProcessA implements GraphProcess { - private final List> newConnectedComponents; private final Traverser t1; private final Traverser t2; - private boolean halted; + private Set verticesOut; - public GraphProcessA(V vertex1, V vertex2, List> newConnectedComponents) { - this.newConnectedComponents = newConnectedComponents; + public GraphProcessA(V vertex1, V vertex2) { Set visitedVerticesT1 = new LinkedHashSet<>(); Set visitedVerticesT2 = new LinkedHashSet<>(); this.t1 = new Traverser(vertex1, visitedVerticesT2, visitedVerticesT1); this.t2 = new Traverser(vertex2, visitedVerticesT1, visitedVerticesT2); - this.halted = false; + this.verticesOut = null; } @Override public void next() { - if (t1.hasEnded() || t2.hasEnded() || halted) { + if (t1.hasEnded() || t2.hasEnded() || isHalted()) { return; } if (t1.componentBreakDetected()) { - updateConnectedComponents(t1.visitedVertices); - halted = true; + verticesOut = t1.visitedVertices; return; } t1.next(); if (t2.componentBreakDetected()) { - updateConnectedComponents(t2.visitedVertices); - halted = true; + verticesOut = t2.visitedVertices; return; } t2.next(); @@ -218,12 +221,7 @@ public void next() { @Override public boolean isHalted() { - return halted; - } - - private void updateConnectedComponents(Set verticesOut) { - newConnectedComponents.forEach(cc -> cc.removeAll(verticesOut)); - newConnectedComponents.add(verticesOut); + return verticesOut != null; } } diff --git a/src/main/java/com/powsybl/openloadflow/graph/GraphConnectivity.java b/src/main/java/com/powsybl/openloadflow/graph/GraphConnectivity.java index d877256668..47fa95c8bf 100644 --- a/src/main/java/com/powsybl/openloadflow/graph/GraphConnectivity.java +++ b/src/main/java/com/powsybl/openloadflow/graph/GraphConnectivity.java @@ -73,4 +73,12 @@ public interface GraphConnectivity { Set getConnectedComponent(V vertex); Set getNonConnectedVertices(V vertex); + + Set getVerticesRemovedFromMainComponent(); + + Set getEdgesRemovedFromMainComponent(); + + Set getVerticesAddedToMainComponent(); + + Set getEdgesAddedToMainComponent(); } diff --git a/src/main/java/com/powsybl/openloadflow/graph/ModificationsContext.java b/src/main/java/com/powsybl/openloadflow/graph/ModificationsContext.java new file mode 100644 index 0000000000..07294f3d20 --- /dev/null +++ b/src/main/java/com/powsybl/openloadflow/graph/ModificationsContext.java @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2022, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.openloadflow.graph; + +import org.jgrapht.Graph; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Florian Dupuy + */ +public class ModificationsContext { + + private final Deque> modifications = new ArrayDeque<>(); + private final Set verticesNotInMainComponentBefore = new HashSet<>(); + private Set verticesAddedToMainComponent; + private Set verticesRemovedFromMainComponent; + private Set edgesAddedToMainComponent; + private Set edgesRemovedFromMainComponent; + private Map> edgeFirstModificationMap; + + public void setVerticesInitiallyNotInMainComponent(Collection> smallComponents) { + smallComponents.forEach(verticesNotInMainComponentBefore::addAll); + } + + public void add(GraphModification graphModification) { + invalidateComparisons(); + modifications.add(graphModification); + } + + private void invalidateComparisons() { + verticesAddedToMainComponent = null; + edgesAddedToMainComponent = null; + verticesRemovedFromMainComponent = null; + edgesRemovedFromMainComponent = null; + edgeFirstModificationMap = null; + } + + public Deque> getModifications() { + return modifications; + } + + public Set getEdgesRemovedFromMainComponent(Set verticesNotInMainComponent, Graph graph) { + if (edgesRemovedFromMainComponent == null) { + edgesRemovedFromMainComponent = computeEdgesRemovedFromMainComponent(verticesNotInMainComponent, graph); + } + return edgesRemovedFromMainComponent; + } + + public Set getVerticesRemovedFromMainComponent(Set verticesNotInMainComponentAfter) { + if (verticesRemovedFromMainComponent == null) { + Set result = new HashSet<>(verticesNotInMainComponentAfter); + result.removeAll(verticesNotInMainComponentBefore); + if (!result.isEmpty()) { + // remove vertices added in between + // note that there is no VertexRemove modification, thus we do not need to check if vertex is in the graph in the end + getAddedVertexStream().forEach(result::remove); + } + verticesRemovedFromMainComponent = result; + } + return verticesRemovedFromMainComponent; + } + + public Set getEdgesAddedToMainComponent(Set verticesNotInMainComponentAfter, Graph graph) { + if (edgesAddedToMainComponent == null) { + edgesAddedToMainComponent = computeEdgesAddedToMainComponent(verticesNotInMainComponentAfter, graph); + } + return edgesAddedToMainComponent; + } + + public Set getVerticesAddedToMainComponent(Set verticesNotInMainComponentAfter) { + if (verticesAddedToMainComponent == null) { + Set result = new HashSet<>(verticesNotInMainComponentBefore); + result.removeAll(verticesNotInMainComponentAfter); + // add vertices added to main component in between + // note that there is no VertexRemove modification, thus we do not need to check if vertex is in the graph before / in the end + getAddedVertexStream().filter(addedVertex -> !verticesNotInMainComponentAfter.contains(addedVertex)).forEach(result::add); + verticesAddedToMainComponent = result; + } + return verticesAddedToMainComponent; + } + + private Set computeEdgesRemovedFromMainComponent(Set verticesNotInMainComponent, Graph graph) { + Set verticesRemoved = getVerticesRemovedFromMainComponent(verticesNotInMainComponent); + Set result = verticesRemoved.stream().map(graph::edgesOf).flatMap(Set::stream).collect(Collectors.toSet()); + + // We need to look in modifications to adjust the computation of the edges above, indeed: + // - result contains the edges which were added in the small components + // - result is missing the edges removed in main component with an EdgeRemove modification + + computeEdgeFirstModificationMap(); + + // Remove the new edges + modifications.stream().filter(EdgeAdd.class::isInstance).map(m -> ((EdgeAdd) m).e) + .filter(graph::containsEdge) // the edge is in the graph: it was not removed afterwards + .filter(edgeAdded -> !graphContainedEdgeBefore(edgeAdded)) // the edge did not exist in the graph before the modifications + .forEach(result::remove); + + // Add edges explicitly removed (with an EdgeRemove modification) + modifications.stream().filter(EdgeRemove.class::isInstance).map(m -> ((EdgeRemove) m).e) + .filter(edgeRemoved -> !graph.containsEdge(edgeRemoved)) // the edge was not added afterwards + .filter(this::graphContainedEdgeBefore) // the edge was in the graph before the modifications + .filter(edgeRemoved -> !verticesNotInMainComponentBefore.contains((edgeFirstModificationMap.get(edgeRemoved)).v1)) // one of the original vertices of the edge was in the main component + .forEach(result::add); + + return result; + } + + private Set computeEdgesAddedToMainComponent(Set verticesNotInMainComponentAfter, Graph graph) { + Set verticesAdded = getVerticesAddedToMainComponent(verticesNotInMainComponentAfter); + Set result = verticesAdded.stream().map(graph::edgesOf).flatMap(Set::stream).collect(Collectors.toSet()); + + // We need to look in modifications to adjust the computation of the edges above + // Indeed result is missing the edges added in main component with an EdgeAdd modification + + computeEdgeFirstModificationMap(); + + // Add edges added to main component in between + modifications.stream().filter(EdgeAdd.class::isInstance).map(m -> ((EdgeAdd) m).e) + .filter(graph::containsEdge) // the edge is in the graph: it was not removed afterwards + .filter(edgeAdded -> !graphContainedEdgeBefore(edgeAdded)) // the edge did not exist in the graph before the modifications + .filter(edgeAdded -> !verticesNotInMainComponentAfter.contains(graph.getEdgeSource(edgeAdded))) // one of the final vertices of the edge is in the main component + .forEach(result::add); + + return result; + } + + private void computeEdgeFirstModificationMap() { + if (edgeFirstModificationMap == null) { + edgeFirstModificationMap = modifications.stream() + .filter(AbstractEdgeModification.class::isInstance) + .map(m -> (AbstractEdgeModification) m) + .collect(Collectors.toMap(m -> m.e, m -> m, (m1, m2) -> m1)); + } + } + + private boolean graphContainedEdgeBefore(E edge) { + // If first modification is a EdgeRemove, knowing that the non-effective modifications are not added in the queue, + // we can conclude that the graph contained the edge before the modifications were applied + return edgeFirstModificationMap.get(edge) instanceof EdgeRemove; + } + + private Stream getAddedVertexStream() { + return modifications.stream().filter(VertexAdd.class::isInstance).map(m -> ((VertexAdd) m).v); + } +} diff --git a/src/test/java/com/powsybl/openloadflow/graph/ConnectivityTest.java b/src/test/java/com/powsybl/openloadflow/graph/ConnectivityTest.java index d845e9f95c..11b7b4a9c0 100644 --- a/src/test/java/com/powsybl/openloadflow/graph/ConnectivityTest.java +++ b/src/test/java/com/powsybl/openloadflow/graph/ConnectivityTest.java @@ -9,7 +9,9 @@ import com.powsybl.commons.PowsyblException; import org.junit.jupiter.api.Test; +import java.util.Collections; import java.util.Set; +import java.util.stream.IntStream; import static org.junit.jupiter.api.Assertions.*; @@ -34,7 +36,7 @@ void loopCircleTest() { @Test void saveResetTest() { -// saveResetTest(new NaiveGraphConnectivity<>(v -> v - 1)); + saveResetTest(new NaiveGraphConnectivity<>(v -> v - 1)); saveResetTest(new MinimumSpanningTreeGraphConnectivity<>()); } @@ -52,6 +54,12 @@ void multipleEdgesTest() { multipleEdgesTest(new MinimumSpanningTreeGraphConnectivity<>(), true); } + @Test + void removeThenAddEdgesTest() { + removeThenAddEdgesTest(new NaiveGraphConnectivity<>(v -> v - 1)); + removeThenAddEdgesTest(new MinimumSpanningTreeGraphConnectivity<>()); + } + private void circleTest(GraphConnectivity c) { String o1 = "1"; String o2 = "2"; @@ -73,6 +81,10 @@ private void circleTest(GraphConnectivity c) { c.startTemporaryChanges(); c.removeEdge(e12); assertTrue(c.getSmallComponents().isEmpty()); + assertTrue(c.getEdgesAddedToMainComponent().isEmpty()); + assertEquals(Set.of(e12), c.getEdgesRemovedFromMainComponent()); + assertTrue(c.getVerticesAddedToMainComponent().isEmpty()); + assertTrue(c.getVerticesRemovedFromMainComponent().isEmpty()); } private void loopCircleTest(GraphConnectivity c) { @@ -94,14 +106,26 @@ private void loopCircleTest(GraphConnectivity c) { c.startTemporaryChanges(); c.removeEdge(e11); assertTrue(c.getSmallComponents().isEmpty()); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of(e11), c.getEdgesRemovedFromMainComponent()); c.undoTemporaryChanges(); c.startTemporaryChanges(); c.removeEdge(e12); assertTrue(c.getSmallComponents().isEmpty()); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of(e12), c.getEdgesRemovedFromMainComponent()); c.removeEdge(e31); assertFalse(c.getSmallComponents().isEmpty()); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(o1), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of(e11, e31, e12), c.getEdgesRemovedFromMainComponent()); } private void saveResetTest(GraphConnectivity c) { @@ -125,6 +149,9 @@ private void saveResetTest(GraphConnectivity c) { c.addEdge(v2, v3, e23); c.addEdge(v3, v1, e31); c.addEdge(v4, v5, e45); + // |-------| + // 1---2---3 4---5 + // |_| c.startTemporaryChanges(); c.removeEdge(e12); @@ -133,39 +160,88 @@ private void saveResetTest(GraphConnectivity c) { assertEquals(Set.of(v1), c.getConnectedComponent(v1)); assertEquals(Set.of(v2, v3), c.getConnectedComponent(v2)); assertEquals(Set.of(v4, v5), c.getConnectedComponent(v5)); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(v1), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of(e11, e12, e31), c.getEdgesRemovedFromMainComponent()); + // 1 2---3 4---5 + // |_| c.startTemporaryChanges(); c.removeEdge(e23); c.addEdge(v1, v2, e12); c.removeEdge(e11); - c.addEdge(v3, v4, "3-4"); + String e34 = "3-4"; + c.addEdge(v3, v4, e34); assertEquals(1, c.getSmallComponents().size()); assertEquals(Set.of(v1, v2), c.getConnectedComponent(v1)); assertEquals(Set.of(v3, v4, v5), c.getConnectedComponent(v5)); + assertEquals(Set.of(e34, e45), c.getEdgesAddedToMainComponent()); + assertEquals(Set.of(v4, v5), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(v2), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of(e23), c.getEdgesRemovedFromMainComponent()); + // 1---2 3---4---5 c.undoTemporaryChanges(); assertEquals(2, c.getSmallComponents().size()); assertEquals(Set.of(v1), c.getConnectedComponent(v1)); assertEquals(Set.of(v2, v3), c.getConnectedComponent(v2)); assertEquals(Set.of(v4, v5), c.getConnectedComponent(v5)); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(v1), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of(e11, e12, e31), c.getEdgesRemovedFromMainComponent()); + // 1 2---3 4---5 + // |_| c.startTemporaryChanges(); c.addEdge(v1, v2, e12); + assertEquals(1, c.getSmallComponents().size()); + assertEquals(Set.of(v1, v2, v3), c.getConnectedComponent(v2)); + assertEquals(Set.of(v4, v5), c.getConnectedComponent(v5)); + assertEquals(Set.of(e11, e12), c.getEdgesAddedToMainComponent()); + assertEquals(Set.of(v1), c.getVerticesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesRemovedFromMainComponent()); + assertEquals(Collections.emptySet(), c.getEdgesRemovedFromMainComponent()); + // 1---2---3 4---5 + // |_| + c.undoTemporaryChanges(); assertEquals(2, c.getSmallComponents().size()); assertEquals(Set.of(v1), c.getConnectedComponent(v1)); assertEquals(Set.of(v2, v3), c.getConnectedComponent(v2)); assertEquals(Set.of(v4, v5), c.getConnectedComponent(v5)); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(v1), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of(e11, e12, e31), c.getEdgesRemovedFromMainComponent()); + // 1 2---3 4---5 + // |_| c.startTemporaryChanges(); - c.addEdge(v1, v4, "1-4"); - c.addEdge(v3, v4, "3-4"); + String e14 = "1-4"; + c.addEdge(v1, v4, e14); + c.addEdge(v3, v4, e34); assertTrue(c.getSmallComponents().isEmpty()); + assertEquals(Set.of(e11, e14, e34, e45), c.getEdgesAddedToMainComponent()); + assertEquals(Set.of(v1, v4, v5), c.getVerticesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesRemovedFromMainComponent()); + assertEquals(Collections.emptySet(), c.getEdgesRemovedFromMainComponent()); + // |-----------| + // 1 2---3---4---5 + // |_| Integer v6 = 6; c.addVertex(v6); assertFalse(c.getSmallComponents().isEmpty()); assertEquals(Set.of(v6), c.getSmallComponents().iterator().next()); + assertEquals(Set.of(e11, e14, e34, e45), c.getEdgesAddedToMainComponent()); + assertEquals(Set.of(v1, v4, v5), c.getVerticesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesRemovedFromMainComponent()); + assertEquals(Collections.emptySet(), c.getEdgesRemovedFromMainComponent()); + // |-----------| + // 1 2---3---4---5 6 + // |_| c.undoTemporaryChanges(); c.undoTemporaryChanges(); @@ -174,6 +250,10 @@ private void saveResetTest(GraphConnectivity c) { assertEquals(1, c.getSmallComponents().size()); assertEquals(Set.of(v1, v2, v3), c.getConnectedComponent(v1)); assertEquals(Set.of(v4, v5), c.getConnectedComponent(v5)); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesRemovedFromMainComponent()); + assertEquals(Collections.emptySet(), c.getEdgesRemovedFromMainComponent()); } private void exceptionsTest(GraphConnectivity c) { @@ -201,21 +281,102 @@ private void exceptionsTest(GraphConnectivity c) { private void multipleEdgesTest(GraphConnectivity c, boolean incrementalSupport) { String o1 = "1"; String o2 = "2"; + String o3 = "3"; String e12 = "1-2"; + String e23 = "2-3"; c.addVertex(o1); c.addVertex(o2); + c.addVertex(o3); c.addEdge(o1, o2, e12); c.addEdge(o1, o2, e12); + c.addEdge(o2, o3, e23); + // 1---2---3 + c.startTemporaryChanges(); assertEquals(1, c.getNbConnectedComponents()); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesRemovedFromMainComponent()); + assertEquals(Collections.emptySet(), c.getEdgesRemovedFromMainComponent()); + // 1---2---3 + c.removeEdge(e12); assertEquals(2, c.getNbConnectedComponents()); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(o1), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of(e12), c.getEdgesRemovedFromMainComponent()); + // 1 2---3 + + // Non-effective modifications c.removeEdge(e12); c.removeEdge(e12); + c.addVertex(o1); assertEquals(2, c.getNbConnectedComponents()); + if (incrementalSupport) { c.addEdge(o1, o2, e12); assertEquals(1, c.getNbConnectedComponents()); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesRemovedFromMainComponent()); + assertEquals(Collections.emptySet(), c.getEdgesRemovedFromMainComponent()); + // 1---2---3 } } + + private void removeThenAddEdgesTest(GraphConnectivity c) { + IntStream.range(1, 6).forEach(c::addVertex); + IntStream.range(1, 5).forEach(i -> c.addEdge(i, i + 1, i + "-" + (i + 1))); + // 1---2---3---4---5 + + c.startTemporaryChanges(); + c.removeEdge("2-3"); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(1, 2), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of("1-2", "2-3"), c.getEdgesRemovedFromMainComponent()); + // 1---2 3---4---5 + + c.removeEdge("1-2"); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(1, 2), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of("1-2", "2-3"), c.getEdgesRemovedFromMainComponent()); + // 1 2 3---4---5 + + c.addEdge(1, 2, "1-2"); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(1, 2), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of("1-2", "2-3"), c.getEdgesRemovedFromMainComponent()); + // 1---2 3---4---5 + + c.addVertex(6); + c.addEdge(5, 6, "5-6"); + assertEquals(Set.of("5-6"), c.getEdgesAddedToMainComponent()); + assertEquals(Set.of(6), c.getVerticesAddedToMainComponent()); + // 1---2 3---4---5---6 + + c.removeEdge("5-6"); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(1, 2), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of("1-2", "2-3"), c.getEdgesRemovedFromMainComponent()); + // 1---2 3---4---5 6 + + c.removeEdge("1-2"); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(1, 2), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of("1-2", "2-3"), c.getEdgesRemovedFromMainComponent()); + // 1 2 3---4---5 6 + + c.addEdge(1, 2, "1-2"); + assertEquals(Collections.emptySet(), c.getEdgesAddedToMainComponent()); + assertEquals(Collections.emptySet(), c.getVerticesAddedToMainComponent()); + assertEquals(Set.of(1, 2), c.getVerticesRemovedFromMainComponent()); + assertEquals(Set.of("1-2", "2-3"), c.getEdgesRemovedFromMainComponent()); + // 1---2 3---4---5 6 + } }