Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connectivity: compute edges/vertices moves from/to main component #611

Merged
merged 10 commits into from
Sep 30, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public abstract class AbstractGraphConnectivity<V, E> implements GraphConnectivi

private final Graph<V, E> graph = new Pseudograph<>(null, null, false);

private final Deque<Deque<GraphModification<V, E>>> graphModifications = new ArrayDeque<>();
private final Deque<ModificationsContext<V, E>> modificationsContexts = new ArrayDeque<>();

protected List<Set<V>> componentSets;

Expand All @@ -37,10 +37,14 @@ public abstract class AbstractGraphConnectivity<V, E> implements GraphConnectivi
@Override
public void addVertex(V vertex) {
Objects.requireNonNull(vertex);
if (graph.containsVertex(vertex)) {
return;
}
VertexAdd<V, E> vertexAdd = new VertexAdd<>(vertex);
vertexAdd.apply(graph);
if (!graphModifications.isEmpty()) {
graphModifications.peekLast().add(vertexAdd);
if (!modificationsContexts.isEmpty()) {
ModificationsContext<V, E> modificationsContext = modificationsContexts.peekLast();
modificationsContext.add(vertexAdd);
updateConnectivity(vertexAdd);
}
}
Expand All @@ -55,8 +59,9 @@ public void addEdge(V vertex1, V vertex2, E edge) {
}
EdgeAdd<V, E> edgeAdd = new EdgeAdd<>(vertex1, vertex2, edge);
edgeAdd.apply(graph);
if (!graphModifications.isEmpty()) {
graphModifications.peekLast().add(edgeAdd);
if (!modificationsContexts.isEmpty()) {
ModificationsContext<V, E> modificationsContext = modificationsContexts.peekLast();
modificationsContext.add(edgeAdd);
updateConnectivity(edgeAdd);
}
}
Expand All @@ -71,25 +76,29 @@ public void removeEdge(E edge) {
V vertex2 = graph.getEdgeTarget(edge);
EdgeRemove<V, E> edgeRemove = new EdgeRemove<>(vertex1, vertex2, edge);
edgeRemove.apply(graph);
if (!graphModifications.isEmpty()) {
graphModifications.peekLast().add(edgeRemove);
if (!modificationsContexts.isEmpty()) {
ModificationsContext<V, E> modificationsContext = modificationsContexts.peekLast();
modificationsContext.add(edgeRemove);
updateConnectivity(edgeRemove);
}
}

@Override
public void startTemporaryChanges() {
graphModifications.add(new ArrayDeque<>());
ModificationsContext<V, E> 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<GraphModification<V, E>> m = graphModifications.pollLast();
resetConnectivity(m);
m.descendingIterator().forEachRemaining(gm -> gm.undo(graph));
ModificationsContext<V, E> m = modificationsContexts.pollLast();
Deque<GraphModification<V, E>> modifications = m.getModifications();
resetConnectivity(modifications);
modifications.descendingIterator().forEachRemaining(gm -> gm.undo(graph));
}

@Override
Expand Down Expand Up @@ -134,12 +143,12 @@ public Graph<V, E> getGraph() {
return graph;
}

protected Deque<Deque<GraphModification<V, E>>> getGraphModifications() {
return graphModifications;
protected Deque<ModificationsContext<V, E>> 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");
}
}
Expand All @@ -149,4 +158,32 @@ protected void checkVertex(V vertex) {
throw new AssertionError("given vertex " + vertex + " is not in the graph");
}
}

@Override
public Set<V> getVerticesAddedToMainComponent() {
Set<V> verticesNotInMainComponent = getVerticesNotInMainComponent(); // exception thrown if modificationsContexts empty
return modificationsContexts.peekLast().getVerticesAddedToMainComponent(verticesNotInMainComponent);
}

@Override
public Set<E> getEdgesAddedToMainComponent() {
Set<V> verticesNotInMainComponent = getVerticesNotInMainComponent(); // exception thrown if modificationsContexts empty
return modificationsContexts.peekLast().getEdgesAddedToMainComponent(verticesNotInMainComponent, graph);
}

@Override
public Set<V> getVerticesRemovedFromMainComponent() {
Set<V> verticesNotInMainComponent = getVerticesNotInMainComponent(); // exception thrown if modificationsContexts empty
return modificationsContexts.peekLast().getVerticesRemovedFromMainComponent(verticesNotInMainComponent);
}

@Override
public Set<E> getEdgesRemovedFromMainComponent() {
Set<V> verticesNotInMainComponent = getVerticesNotInMainComponent(); // exception thrown if modificationsContexts empty
return modificationsContexts.peekLast().getEdgesRemovedFromMainComponent(verticesNotInMainComponent, graph);
}

private Set<V> getVerticesNotInMainComponent() {
return getSmallComponents().stream().flatMap(Set::stream).collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ protected void updateConnectivity(EdgeRemove<V, E> 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();
Expand All @@ -44,11 +44,18 @@ protected void updateConnectivity(EdgeRemove<V, E> edgeRemoval) {

if (processA.isHalted()) {
processB.undoChanges();
updateNewConnectedComponents(processA.verticesOut);
} else { // processB halted
allSavedChangedLevels.add(processB.savedChangedLevels);
}
}

private void updateNewConnectedComponents(Set<V> 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<V, E> edgeAdd) {
throw new PowsyblException("This implementation does not support incremental connectivity: edges cannot be added once that connectivity is saved");
Expand All @@ -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()) {
Expand Down Expand Up @@ -181,49 +188,40 @@ private interface GraphProcess {

private class GraphProcessA implements GraphProcess {

private final List<Set<V>> newConnectedComponents;
private final Traverser t1;
private final Traverser t2;
private boolean halted;
private Set<V> verticesOut;

public GraphProcessA(V vertex1, V vertex2, List<Set<V>> newConnectedComponents) {
this.newConnectedComponents = newConnectedComponents;
public GraphProcessA(V vertex1, V vertex2) {
Set<V> visitedVerticesT1 = new LinkedHashSet<>();
Set<V> 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();
}

@Override
public boolean isHalted() {
return halted;
}

private void updateConnectedComponents(Set<V> verticesOut) {
newConnectedComponents.forEach(cc -> cc.removeAll(verticesOut));
newConnectedComponents.add(verticesOut);
return verticesOut != null;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,12 @@ public interface GraphConnectivity<V, E> {
Set<V> getConnectedComponent(V vertex);

Set<V> getNonConnectedVertices(V vertex);

Set<V> getVerticesRemovedFromMainComponent();

Set<E> getEdgesRemovedFromMainComponent();

Set<V> getVerticesAddedToMainComponent();

Set<E> getEdgesAddedToMainComponent();
}
152 changes: 152 additions & 0 deletions src/main/java/com/powsybl/openloadflow/graph/ModificationsContext.java
Original file line number Diff line number Diff line change
@@ -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 <florian.dupuy at rte-france.com>
*/
public class ModificationsContext<V, E> {

private final Deque<GraphModification<V, E>> modifications = new ArrayDeque<>();
private final Set<V> verticesNotInMainComponentBefore = new HashSet<>();
private Set<V> verticesAddedToMainComponent;
private Set<V> verticesRemovedFromMainComponent;
private Set<E> edgesAddedToMainComponent;
private Set<E> edgesRemovedFromMainComponent;
private Map<E, AbstractEdgeModification<V, E>> edgeFirstModificationMap;

public void setVerticesInitiallyNotInMainComponent(Collection<Set<V>> smallComponents) {
smallComponents.forEach(verticesNotInMainComponentBefore::addAll);
}

public void add(GraphModification<V, E> graphModification) {
invalidateComparisons();
modifications.add(graphModification);
}

private void invalidateComparisons() {
verticesAddedToMainComponent = null;
edgesAddedToMainComponent = null;
verticesRemovedFromMainComponent = null;
edgesRemovedFromMainComponent = null;
edgeFirstModificationMap = null;
}

public Deque<GraphModification<V, E>> getModifications() {
return modifications;
}

public Set<E> getEdgesRemovedFromMainComponent(Set<V> verticesNotInMainComponent, Graph<V, E> graph) {
if (edgesRemovedFromMainComponent == null) {
edgesRemovedFromMainComponent = computeEdgesRemovedFromMainComponent(verticesNotInMainComponent, graph);
}
return edgesRemovedFromMainComponent;
}

public Set<V> getVerticesRemovedFromMainComponent(Set<V> verticesNotInMainComponentAfter) {
if (verticesRemovedFromMainComponent == null) {
Set<V> 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<E> getEdgesAddedToMainComponent(Set<V> verticesNotInMainComponentAfter, Graph<V, E> graph) {
if (edgesAddedToMainComponent == null) {
edgesAddedToMainComponent = computeEdgesAddedToMainComponent(verticesNotInMainComponentAfter, graph);
}
return edgesAddedToMainComponent;
}

public Set<V> getVerticesAddedToMainComponent(Set<V> verticesNotInMainComponentAfter) {
if (verticesAddedToMainComponent == null) {
Set<V> 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<E> computeEdgesRemovedFromMainComponent(Set<V> verticesNotInMainComponent, Graph<V, E> graph) {
Set<V> verticesRemoved = getVerticesRemovedFromMainComponent(verticesNotInMainComponent);
Set<E> 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<V, E>) 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<V, E>) 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<E> computeEdgesAddedToMainComponent(Set<V> verticesNotInMainComponentAfter, Graph<V, E> graph) {
Set<V> verticesAdded = getVerticesAddedToMainComponent(verticesNotInMainComponentAfter);
Set<E> 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<V, E>) 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<V, E>) 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<V> getAddedVertexStream() {
return modifications.stream().filter(VertexAdd.class::isInstance).map(m -> ((VertexAdd<V, E>) m).v);
}
}
Loading