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

Network modification connect and disconnect #2831

Merged
merged 24 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
*/
package com.powsybl.iidm.network;

import com.powsybl.commons.reporter.Reporter;

import java.util.List;
import java.util.function.Predicate;

/**
* An equipment that is part of a substation topology.
Expand All @@ -21,4 +24,20 @@ public interface Connectable<I extends Connectable<I>> extends Identifiable<I> {
* Remove the connectable from the voltage level (dangling switches are kept).
*/
void remove();

boolean connect();

boolean connect(Predicate<Switch> isTypeSwitchToOperate);

boolean connect(Reporter reporter);
flo-dup marked this conversation as resolved.
Show resolved Hide resolved

boolean connect(Predicate<Switch> isTypeSwitchToOperate, Reporter reporter);

boolean disconnect();

boolean disconnect(Predicate<Switch> isSwitchOpenable);

boolean disconnect(Reporter reporter);

boolean disconnect(Predicate<Switch> isSwitchOpenable, Reporter reporter);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ private SwitchPredicates() { }
public static final Predicate<Switch> IS_CLOSED_BREAKER = switchObject -> switchObject != null && switchObject.getKind() == SwitchKind.BREAKER && !switchObject.isOpen();
public static final Predicate<Switch> IS_BREAKER_OR_DISCONNECTOR = switchObject -> switchObject != null && (switchObject.getKind() == SwitchKind.BREAKER || switchObject.getKind() == SwitchKind.DISCONNECTOR);
public static final Predicate<Switch> IS_OPEN_DISCONNECTOR = switchObject -> switchObject != null && switchObject.getKind() == SwitchKind.DISCONNECTOR && switchObject.isOpen();
public static final Predicate<Switch> IS_BREAKER = switchObject -> switchObject != null && switchObject.getKind() == SwitchKind.BREAKER;
public static final Predicate<Switch> IS_NONFICTIONAL = switchObject -> switchObject != null && !switchObject.isFictitious();
public static final Predicate<Switch> IS_OPEN = switchObject -> switchObject != null && switchObject.isOpen();
public static final Predicate<Switch> IS_NON_NULL = Objects::nonNull;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@
package com.powsybl.iidm.network.impl;

import com.powsybl.commons.PowsyblException;
import com.powsybl.iidm.network.Bus;
import com.powsybl.iidm.network.Connectable;
import com.powsybl.iidm.network.TopologyKind;
import com.powsybl.iidm.network.TopologyPoint;
import com.powsybl.commons.reporter.Report;
import com.powsybl.commons.reporter.Reporter;
import com.powsybl.commons.reporter.TypedValue;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.impl.util.Ref;
import com.powsybl.iidm.network.util.SwitchPredicates;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static com.powsybl.iidm.network.TopologyKind.BUS_BREAKER;
import static com.powsybl.iidm.network.TopologyKind.NODE_BREAKER;

/**
*
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
Expand Down Expand Up @@ -137,7 +140,7 @@ protected void move(TerminalExt oldTerminal, TopologyPoint oldTopologyPoint, int
}

// check bus topology
if (voltageLevel.getTopologyKind() != TopologyKind.NODE_BREAKER) {
if (voltageLevel.getTopologyKind() != NODE_BREAKER) {
String msg = String.format(
"Trying to move connectable %s to node %d of voltage level %s, which is a bus breaker voltage level",
getId(), node, voltageLevel.getId());
Expand Down Expand Up @@ -190,4 +193,135 @@ private void attachTerminal(TerminalExt oldTerminal, TopologyPoint oldTopologyPo

notifyUpdate("terminal" + (iSide + 1), oldTopologyPoint, terminalExt.getTopologyPoint());
}

public boolean connect() {
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
return connect(SwitchPredicates.IS_NONFICTIONAL_BREAKER, Reporter.NO_OP);
}

public boolean connect(Reporter reporter) {
return connect(SwitchPredicates.IS_NONFICTIONAL_BREAKER, reporter);
}

public boolean connect(Predicate<Switch> isTypeSwitchToOperate) {
return connect(isTypeSwitchToOperate, Reporter.NO_OP);
}

public boolean connect(Predicate<Switch> isTypeSwitchToOperate, Reporter reporter) {
// Booleans
boolean isAlreadyConnected = true;
boolean isNowConnected = true;

// Initialisation of a list to open in case some terminals are in node-breaker view
Set<SwitchImpl> switchForDisconnection = new HashSet<>();

// We try to connect each terminal
for (TerminalExt terminal : getTerminals()) {
// Check if the terminal is already connected
if (terminal.isConnected()) {
reporter.report(Report.builder()
.withKey("alreadyConnectedTerminal")
.withDefaultMessage("A terminal of connectable ${connectable} is already connected.")
.withValue("connectable", this.getId())
.withSeverity(TypedValue.WARN_SEVERITY)
.build());
continue;
} else {
isAlreadyConnected = false;
}

// If it's a node-breaker terminal, the switches to connect are added to a set
if (terminal.getVoltageLevel() instanceof NodeBreakerVoltageLevel nodeBreakerVoltageLevel) {
isNowConnected = nodeBreakerVoltageLevel.getConnectingSwitches(terminal, isTypeSwitchToOperate, switchForDisconnection);
}
// If it's a bus-breaker terminal, there is nothing to do

// Exit if the terminal cannot be connected
if (!isNowConnected) {
return false;
}
}

// Exit if the connectable is already fully connected
if (isAlreadyConnected) {
return false;
}

// Connect all bus-breaker terminals
for (TerminalExt terminal : getTerminals()) {
if (!terminal.isConnected()
&& terminal.getVoltageLevel().getTopologyKind() == BUS_BREAKER) {
// At this point, isNowConnected should always stay true but let's be careful
isNowConnected = isNowConnected && terminal.connect(isTypeSwitchToOperate);
}
}

// Disconnect all switches on node-breaker terminals
switchForDisconnection.forEach(sw -> sw.setOpen(false));
return isNowConnected;
}

public boolean disconnect() {
return disconnect(SwitchPredicates.IS_CLOSED_BREAKER, Reporter.NO_OP);
}

public boolean disconnect(Predicate<Switch> isSwitchOpenable) {
return disconnect(isSwitchOpenable, Reporter.NO_OP);
}

public boolean disconnect(Reporter reporter) {
return disconnect(SwitchPredicates.IS_CLOSED_BREAKER, reporter);
}

public boolean disconnect(Predicate<Switch> isSwitchOpenable, Reporter reporter) {
// Booleans
boolean isAlreadyDisconnected = true;
boolean isNowDisconnected = true;
flo-dup marked this conversation as resolved.
Show resolved Hide resolved

// Initialisation of a list to open in case some terminals are in node-breaker view
Set<SwitchImpl> switchForDisconnection = new HashSet<>();

// We try to disconnect each terminal
for (TerminalExt terminal : getTerminals()) {
// Check if the terminal is already disconnected
if (!terminal.isConnected()) {
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
reporter.report(Report.builder()
.withKey("alreadyDisconnectedTerminal")
.withDefaultMessage("A terminal of connectable ${connectable} is already disconnected.")
.withValue("connectable", this.getId())
.withSeverity(TypedValue.WARN_SEVERITY)
.build());
continue;
} else {
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
isAlreadyDisconnected = false;
}

// If it's a node-breaker terminal, the switches to disconnect are added to a set
if (terminal.getVoltageLevel() instanceof NodeBreakerVoltageLevel nodeBreakerVoltageLevel) {
isNowDisconnected = nodeBreakerVoltageLevel.getDisconnectingSwitches(terminal, isSwitchOpenable, switchForDisconnection);
}
// If it's a bus-breaker terminal, there is nothing to do

// Exit if the terminal cannot be disconnected
if (!isNowDisconnected) {
return false;
}
}

// Exit if the connectable is already fully disconnected
if (isAlreadyDisconnected) {
return false;
}
Comment on lines +301 to +304
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

true? I would expect the connectable to be non-disconnectable if disconnect returns false. Oh but it's the same for the Terminal... and we didn't put any javadoc to tell what it should return 🙁
If we return true, can't we avoid this? I think the code after should be proof against that case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some discussion, this breaking change should not be done here.
But could we put in the javadoc that returning false means no changes?


// Disconnect all bus-breaker terminals
for (TerminalExt terminal : getTerminals()) {
if (terminal.isConnected()
&& terminal.getVoltageLevel().getTopologyKind() == BUS_BREAKER) {
// At this point, isNowDisconnected should always stay true but let's be careful
isNowDisconnected = isNowDisconnected && terminal.disconnect(isSwitchOpenable);
}
}
// Disconnect all switches on node-breaker terminals
switchForDisconnection.forEach(sw -> sw.setOpen(true));
return isNowDisconnected;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,11 @@ private boolean checkNonClosableSwitch(SwitchImpl sw, Predicate<? super SwitchIm
return SwitchPredicates.IS_OPEN.test(sw) && isSwitchOperable.negate().test(sw);
}

private void checkTopologyKind(TerminalExt terminal) {
if (!(terminal instanceof NodeTerminal)) {
throw new IllegalStateException(WRONG_TERMINAL_TYPE_EXCEPTION_MESSAGE + terminal.getClass().getName());
}
}

/**
* Connect the terminal to a busbar section in its voltage level.
Expand All @@ -1197,22 +1202,38 @@ public boolean connect(TerminalExt terminal) {
*/
@Override
public boolean connect(TerminalExt terminal, Predicate<? super SwitchImpl> isSwitchOperable) {
if (!(terminal instanceof NodeTerminal)) {
throw new IllegalStateException(WRONG_TERMINAL_TYPE_EXCEPTION_MESSAGE + terminal.getClass().getName());
}
// Check the topology kind
checkTopologyKind(terminal);

// already connected?
if (terminal.isConnected()) {
return false;
}

// Initialisation of a list to open in case some terminals are in node-breaker view
Set<SwitchImpl> switchForDisconnection = new HashSet<>();
flo-dup marked this conversation as resolved.
Show resolved Hide resolved

// Get the list of switches to close
if (getConnectingSwitches(terminal, isSwitchOperable, switchForDisconnection)) {
// Close the switches
switchForDisconnection.forEach(sw -> sw.setOpen(false));
return true;
} else {
return false;
}
}

boolean getConnectingSwitches(TerminalExt terminal, Predicate<? super SwitchImpl> isSwitchOperable, Set<SwitchImpl> switchForConnection) {
// Check the topology kind
checkTopologyKind(terminal);

int node = ((NodeTerminal) terminal).getNode();
// find all paths starting from the current terminal to a busbar section that does not contain an open switch
// that is not of the type of switch the user wants to operate
// Paths are already sorted by the number of open switches and by the size of the paths
List<TIntArrayList> paths = graph.findAllPaths(node, NodeBreakerVoltageLevel::isBusbarSection, sw -> checkNonClosableSwitch(sw, isSwitchOperable),
Comparator.comparing((TIntArrayList o) -> o.grep(idx -> SwitchPredicates.IS_OPEN.test(graph.getEdgeObject(idx))).size())
.thenComparing(TIntArrayList::size));
boolean connected = false;
if (!paths.isEmpty()) {
// the shortest path is the best
TIntArrayList shortestPath = paths.get(0);
Expand All @@ -1223,13 +1244,12 @@ public boolean connect(TerminalExt terminal, Predicate<? super SwitchImpl> isSwi
SwitchImpl sw = graph.getEdgeObject(e);
if (SwitchPredicates.IS_OPEN.test(sw)) {
// Since the paths were constructed using the method checkNonClosableSwitches, only operable switches can be open
sw.setOpen(false);
switchForConnection.add(sw);
}
}
// Check that the terminal is connected (it should always be true, given how the paths are found)
connected = terminal.isConnected();
return true;
}
return connected;
return false;
}

@Override
Expand All @@ -1240,35 +1260,46 @@ public boolean disconnect(TerminalExt terminal) {

@Override
public boolean disconnect(TerminalExt terminal, Predicate<? super SwitchImpl> isSwitchOpenable) {
if (!(terminal instanceof NodeTerminal)) {
throw new IllegalStateException(WRONG_TERMINAL_TYPE_EXCEPTION_MESSAGE + terminal.getClass().getName());
}
// Check the topology kind
checkTopologyKind(terminal);

// already disconnected?
if (!terminal.isConnected()) {
return false;
}

// Set of switches that are to be opened
Set<SwitchImpl> switchesToOpen = new HashSet<>();

// Get the list of switches to open
if (getDisconnectingSwitches(terminal, isSwitchOpenable, switchesToOpen)) {
// Open the switches
switchesToOpen.forEach(sw -> sw.setOpen(true));
return true;
} else {
return false;
}
}

boolean getDisconnectingSwitches(TerminalExt terminal, Predicate<? super SwitchImpl> isSwitchOpenable, Set<SwitchImpl> switchForDisconnection) {
// Check the topology kind
checkTopologyKind(terminal);

int node = ((NodeTerminal) terminal).getNode();
// find all paths starting from the current terminal to a busbar section that does not contain an open switch
List<TIntArrayList> paths = graph.findAllPaths(node, NodeBreakerVoltageLevel::isBusbarSection, SwitchPredicates.IS_OPEN);
if (paths.isEmpty()) {
return false;
}

// Set of switches that are to be opened
Set<SwitchImpl> switchesToOpen = new HashSet<>(paths.size());

// Each path is visited and for each, the first openable switch found is added in the set of switches to open
for (TIntArrayList path : paths) {
// Identify the first openable switch on the path
if (!identifySwitchToOpenPath(path, isSwitchOpenable, switchesToOpen)) {
if (!identifySwitchToOpenPath(path, isSwitchOpenable, switchForDisconnection)) {
// If no such switch was found, return false immediately
return false;
}
}

// The switches are now opened
switchesToOpen.forEach(sw -> sw.setOpen(true));
return true;
}

Expand All @@ -1277,7 +1308,7 @@ public boolean disconnect(TerminalExt terminal, Predicate<? super SwitchImpl> is
* @param path the path to open
* @param isSwitchOpenable predicate used to know if a switch can be opened
* @param switchesToOpen set of switches to be opened
* @return true if the path has been opened, else false
* @return true if the path can be opened, else false
*/
boolean identifySwitchToOpenPath(TIntArrayList path, Predicate<? super SwitchImpl> isSwitchOpenable, Set<SwitchImpl> switchesToOpen) {
for (int i = 0; i < path.size(); i++) {
Expand All @@ -1293,9 +1324,9 @@ boolean identifySwitchToOpenPath(TIntArrayList path, Predicate<? super SwitchImp
}

boolean isConnected(TerminalExt terminal) {
if (!(terminal instanceof NodeTerminal)) {
throw new IllegalStateException(WRONG_TERMINAL_TYPE_EXCEPTION_MESSAGE + terminal.getClass().getName());
}
// Check the topology kind
checkTopologyKind(terminal);

return terminal.getBusView().getBus() != null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2023, 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/.
* SPDX-License-Identifier: MPL-2.0
*/
package com.powsybl.iidm.network.impl.tck;

import com.powsybl.iidm.network.tck.AbstractConnectableTest;

/**
* @author Nicolas Rol {@literal <nicolas.rol at rte-france.com>}
*/
class ConnectableTest extends AbstractConnectableTest {
}
Loading