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

Fix bus/breaker topology in case of no connected terminal present #881

Merged
merged 3 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 3 additions & 9 deletions java/src/main/java/com/powsybl/python/network/Dataframes.java
Original file line number Diff line number Diff line change
Expand Up @@ -363,15 +363,9 @@ private static DataframeMapper<VoltageLevel.BusBreakerView, Void> createBusBreak
}

private static List<BusBreakerViewBusData> getBusBreakerViewBuses(VoltageLevel voltageLevel) {
return voltageLevel.getBusBreakerView().getBusStream().map(bus -> {
Bus busViewBus = bus.getConnectedTerminalStream()
.map(t -> t.getBusView().getBus())
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
return new BusBreakerViewBusData(bus, busViewBus);
}).collect(Collectors.toList());

return voltageLevel.getBusBreakerView().getBusStream()
.map(bus -> new BusBreakerViewBusData(bus, NetworkUtil.getBusViewBus(bus)))
.toList();
}

private static DataframeMapper<VoltageLevel, Void> createBusBreakerViewBuses() {
Expand Down
34 changes: 34 additions & 0 deletions java/src/main/java/com/powsybl/python/network/NetworkUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
Expand Down Expand Up @@ -246,4 +247,37 @@ public static String getRegulatedElementId(Supplier<Terminal> regulatingTerminal
Terminal terminal = regulatingTerminalGetter.get();
return terminal.getConnectable() != null ? terminal.getConnectable().getId() : null;
}

/**
* @param b bus in Bus/Breaker view
* @return bus in bus view containing b if there is one, or null if none.
*/
public static Bus getBusViewBus(Bus b) {
// First we try the fast and easy way using connected terminals. Works for the vast majority of buses.
Bus busInBusView = b.getConnectedTerminalStream().map(t -> t.getBusView().getBus())
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
if (busInBusView != null) {
return busInBusView;
}
// Didn't find using connected terminals. There is the possibility that the bus has zero connected terminal
// on its own but is still part of a Merged Bus via a closed retained switch. We examine this case below.
VoltageLevel voltageLevel = b.getVoltageLevel();
if (voltageLevel.getTopologyKind() == TopologyKind.BUS_BREAKER) {
// Bus/Breaker. There is an easy method directly available.
return voltageLevel.getBusView().getMergedBus(b.getId());
} else {
// Node/Breaker. We should probably build something more efficient on powsybl-core side to avoid having
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
// to loop over all buses in the voltage level.
for (Bus bus : voltageLevel.getBusView().getBuses()) {
boolean found = voltageLevel.getBusBreakerView().getBusStreamFromBusViewBusId(bus.getId())
.anyMatch(b2 -> b.getId().equals(b2.getId()));
if (found) {
return bus;
}
}
return null;
flo-dup marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
51 changes: 49 additions & 2 deletions java/src/test/java/com/powsybl/python/network/NetworkUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
*/
package com.powsybl.python.network;

import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.python.commons.PyPowsyblApiHeader;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.*;

/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
Expand All @@ -28,4 +29,50 @@ void test() {
List<String> elementsIds = NetworkUtil.getElementsIds(network, PyPowsyblApiHeader.ElementType.TWO_WINDINGS_TRANSFORMER, Collections.singleton(24.0), Collections.singleton("FR"), true, true, false);
assertEquals(Collections.singletonList("NGEN_NHV1"), elementsIds);
}

@Test
void testBusFromBusBreakerViewBus() {
Network network = createTopologyTestNetwork();
var expected = Map.of(
"B1", "VL1_0",
"B2", "VL1_0",
"B3", "",
"VL2_0", "VL2_0",
"VL2_1", "VL2_0",
"VL2_2", "");
expected.forEach((busBreakerBusId, busIdExpected) -> {
Bus bus = NetworkUtil.getBusViewBus(network.getBusBreakerView().getBus(busBreakerBusId));
if (!busIdExpected.isEmpty()) {
assertNotNull(bus);
assertEquals(busIdExpected, bus.getId());
} else {
assertNull(bus);
}
});
}

private static Network createTopologyTestNetwork() {
Network network = NetworkFactory.findDefault().createNetwork("test", "code");

var vl1 = network.newVoltageLevel().setTopologyKind(TopologyKind.BUS_BREAKER).setId("VL1").setNominalV(400.).add();
vl1.getBusBreakerView().newBus().setId("B1").add();
vl1.getBusBreakerView().newBus().setId("B2").add();
vl1.getBusBreakerView().newBus().setId("B3").add();
vl1.getBusBreakerView().newSwitch().setId("CB1.1").setOpen(false).setBus1("B1").setBus2("B2").add();
vl1.getBusBreakerView().newSwitch().setId("CB1.2").setOpen(true).setBus1("B1").setBus2("B2").add();
vl1.newLoad().setId("L1").setP0(10.).setQ0(3.).setConnectableBus("B1").setBus("B1").add();

var vl2 = network.newVoltageLevel().setTopologyKind(TopologyKind.NODE_BREAKER).setId("VL2").setNominalV(400.).add();
vl2.getNodeBreakerView().newBusbarSection().setId("BBS1").setNode(0).add();
vl2.getNodeBreakerView()
.newSwitch()
.setId("CB2.1").setOpen(false).setRetained(true).setKind(SwitchKind.BREAKER).setNode1(0).setNode2(1).add();
vl2.getNodeBreakerView()
.newSwitch()
.setId("CB2.2").setOpen(true).setRetained(true).setKind(SwitchKind.BREAKER).setNode1(1).setNode2(2).add();
vl2.newLoad().setId("L2").setP0(10.).setQ0(3.).setNode(3).add();
vl2.getNodeBreakerView().newInternalConnection().setNode1(0).setNode2(3).add();

return network;
}
}
30 changes: 30 additions & 0 deletions tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -1722,6 +1722,36 @@ def test_bus_breaker_view_draw_graph():
plt.show()


def test_bus_breaker_topology_no_connected_terminal():
n = pp.network.create_empty()
n.create_substations(id='S')

n.create_voltage_levels(id='VL1', substation_id='S', nominal_v=380, topology_kind='BUS_BREAKER')
n.create_buses(id='B1', voltage_level_id='VL1')
n.create_buses(id='B2', voltage_level_id='VL1')
n.create_buses(id='B3', voltage_level_id='VL1')
n.create_loads(id='L1', voltage_level_id='VL1', p0=0, q0=0, bus_id='B1', connectable_bus_id='B1')
n.create_switches(id='CB1.1', kind='BREAKER', voltage_level_id='VL1', bus1_id='B1', bus2_id='B2', open=False)
n.create_switches(id='CB1.2', kind='BREAKER', voltage_level_id='VL1', bus1_id='B2', bus2_id='B3', open=True)

n.create_voltage_levels(id='VL2', substation_id='S', nominal_v=380, topology_kind='NODE_BREAKER')
n.create_busbar_sections(id='BB', voltage_level_id='VL2', node=0)
n.create_switches(id='CB2.1', kind='BREAKER', voltage_level_id='VL2', node1=0, node2=1, open=False, retained=True)
n.create_switches(id='CB2.2', kind='BREAKER', voltage_level_id='VL2', node1=1, node2=2, open=True, retained=True)
n.create_switches(id='SW', kind='DISCONNECTOR', voltage_level_id='VL2', node1=0, node2=3, open=False)
n.create_loads(id='L2', voltage_level_id='VL2', p0=0, q0=0, node=3)

topo_vl1 = n.get_bus_breaker_topology('VL1').buses
assert 'VL1_0' == topo_vl1.loc['B1']['bus_id']
assert 'VL1_0' == topo_vl1.loc['B2']['bus_id']
assert '' == topo_vl1.loc['B3']['bus_id']

topo_vl2 = n.get_bus_breaker_topology('VL2').buses
assert 'VL2_0' == topo_vl2.loc['VL2_0']['bus_id']
assert 'VL2_0' == topo_vl2.loc['VL2_1']['bus_id']
assert '' == topo_vl2.loc['VL2_2']['bus_id']


def test_dataframe_attributes_filtering():
n = pp.network.create_eurostag_tutorial_example1_network()
buses_selected_attributes = n.get_buses(attributes=['v_mag', 'voltage_level_id'])
Expand Down
Loading