Skip to content

Commit

Permalink
Bus/Breaker view buses: add column with bus ID in bus view (#880)
Browse files Browse the repository at this point in the history
Signed-off-by: Damien Jeandemange <[email protected]>
  • Loading branch information
jeandemanged authored Nov 20, 2024
1 parent 9c6a0fa commit b42ca61
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 34 deletions.
1 change: 1 addition & 0 deletions docs/reference/network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ All network elements are accessible as dataframes, using the following getters.
Network.get_branches
Network.get_busbar_sections
Network.get_buses
Network.get_bus_breaker_view_buses
Network.get_current_limits
Network.get_dangling_lines
Network.get_generators
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ private static NetworkDataframeMapper areas() {
}

static NetworkDataframeMapper buses(boolean busBreakerView) {
return NetworkDataframeMapperBuilder.ofStream(n -> busBreakerView ? n.getBusBreakerView().getBusStream() : n.getBusView().getBusStream(),
var builder = NetworkDataframeMapperBuilder.ofStream(n -> busBreakerView ? n.getBusBreakerView().getBusStream() : n.getBusView().getBusStream(),
getOrThrow((b, id) -> b.getBusView().getBus(id), "Bus"))
.stringsIndex("id", Bus::getId)
.strings("name", b -> b.getOptionalName().orElse(""), Identifiable::setName)
Expand All @@ -333,8 +333,11 @@ static NetworkDataframeMapper buses(boolean busBreakerView) {
.doubles("v_angle", (b, context) -> perUnitAngle(context, b.getAngle()), (b, vAngle, context) -> b.setAngle(unPerUnitAngle(context, vAngle)))
.ints("connected_component", ifExistsInt(Bus::getConnectedComponent, Component::getNum))
.ints("synchronous_component", ifExistsInt(Bus::getSynchronousComponent, Component::getNum))
.strings("voltage_level_id", b -> b.getVoltageLevel().getId())
.booleans("fictitious", Identifiable::isFictitious, Identifiable::setFictitious, false)
.strings("voltage_level_id", b -> b.getVoltageLevel().getId());
if (busBreakerView) {
builder.strings("bus_id", b -> NetworkUtil.getBusViewBus(b).map(Bus::getId).orElse(""));
}
return builder.booleans("fictitious", Identifiable::isFictitious, Identifiable::setFictitious, false)
.addProperties()
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ private static DataframeMapper<VoltageLevel.BusBreakerView, Void> createBusBreak

private static List<BusBreakerViewBusData> getBusBreakerViewBuses(VoltageLevel voltageLevel) {
return voltageLevel.getBusBreakerView().getBusStream()
.map(bus -> new BusBreakerViewBusData(bus, NetworkUtil.getBusViewBus(bus)))
.map(bus -> new BusBreakerViewBusData(bus, NetworkUtil.getBusViewBus(bus).orElse(null)))
.toList();
}

Expand Down
21 changes: 8 additions & 13 deletions java/src/main/java/com/powsybl/python/network/NetworkUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@
import com.powsybl.iidm.network.extensions.ConnectablePosition;
import com.powsybl.python.commons.PyPowsyblApiHeader;

import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -250,21 +247,20 @@ public static String getRegulatedElementId(Supplier<Terminal> regulatingTerminal

/**
* @param b bus in Bus/Breaker view
* @return bus in bus view containing b if there is one, or null if none.
* @return bus in bus view containing b if there is one.
*/
public static Bus getBusViewBus(Bus b) {
public static Optional<Bus> getBusViewBus(Bus b) {
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());
return Optional.ofNullable(voltageLevel.getBusView().getMergedBus(b.getId()));
} else {
// Node/Breaker.
// 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())
Optional<Bus> busInBusView = b.getConnectedTerminalStream().map(t -> t.getBusView().getBus())
.filter(Objects::nonNull)
.findFirst()
.orElse(null);
if (busInBusView != null) {
.findFirst();
if (busInBusView.isPresent()) {
return busInBusView;
}
// Didn't find using connected terminals. There is the possibility that the bus has zero connected terminal
Expand All @@ -274,8 +270,7 @@ public static Bus getBusViewBus(Bus b) {
return voltageLevel.getBusView().getBusStream()
.filter(busViewBus -> voltageLevel.getBusBreakerView().getBusStreamFromBusViewBusId(busViewBus.getId())
.anyMatch(b2 -> b.getId().equals(b2.getId())))
.findFirst()
.orElse(null);
.findFirst();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.powsybl.iidm.network.extensions.*;
import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
import com.powsybl.iidm.network.test.HvdcTestNetwork;
import com.powsybl.iidm.network.test.TwoVoltageLevelNetworkFactory;
import com.powsybl.python.network.NetworkUtilTest;
import com.powsybl.python.network.Networks;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -100,16 +102,64 @@ void buses() {
.extracting(Series::getName)
.containsExactly("id", "name", "v_mag", "v_angle", "connected_component", "synchronous_component",
"voltage_level_id");
assertThat(series.get(0).getStrings())
.containsExactly("VLGEN_0", "VLHV1_0", "VLHV2_0", "VLLOAD_0");
assertThat(series.get(2).getDoubles())
.containsExactly(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
assertThat(series.get(4).getInts())
.containsExactly(0, 0, 0, 0);
assertThat(series.get(4).getInts())
assertThat(series.get(5).getInts())
.containsExactly(0, 0, 0, 0);
assertThat(series.get(6).getStrings())
.containsExactly("VLGEN", "VLHV1", "VLHV2", "VLLOAD");
}

@Test
void busBreakerViewBuses() {
// VL1 is Node/Breaker, VL2 is Bus/Breaker
Network network = TwoVoltageLevelNetworkFactory.create();
List<Series> series = createDataFrame(BUS_FROM_BUS_BREAKER_VIEW, network);
assertThat(series)
.extracting(Series::getName)
.containsExactly("id", "name", "v_mag", "v_angle", "connected_component", "synchronous_component",
"voltage_level_id", "bus_id");
assertThat(series.get(0).getStrings())
.containsExactly("BUS1", "BUS2", "VL1_0", "VL1_3");
assertThat(series.get(2).getDoubles())
.containsExactly(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
assertThat(series.get(4).getInts())
.containsExactly(0, 0, 1, -99999);
assertThat(series.get(5).getInts())
.containsExactly(0, 0, 1, -99999);
assertThat(series.get(6).getStrings())
.containsExactly("VL2", "VL2", "VL1", "VL1");
assertThat(series.get(7).getStrings())
.containsExactly("VL2_0", "VL2_0", "VL1_0", "");
}

@Test
void busBreakerViewBusesNoConnectedTerminalOnBus() {
Network network = NetworkUtilTest.createTopologyTestNetwork();

List<Series> series = createDataFrame(BUS_FROM_BUS_BREAKER_VIEW, network);
assertThat(series)
.extracting(Series::getName)
.containsExactly("id", "name", "v_mag", "v_angle", "connected_component", "synchronous_component",
"voltage_level_id", "bus_id");
assertThat(series.get(0).getStrings())
.containsExactly("B1", "B2", "B3", "VL2_0", "VL2_1", "VL2_2");
assertThat(series.get(2).getDoubles())
.containsExactly(Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN);
assertThat(series.get(4).getInts())
.containsExactly(0, 0, -99999, 1, 1, -99999);
assertThat(series.get(5).getInts())
.containsExactly(0, 0, -99999, 1, 1, -99999);
assertThat(series.get(6).getStrings())
.containsExactly("VL1", "VL1", "VL1", "VL2", "VL2", "VL2");
assertThat(series.get(7).getStrings())
.containsExactly("VL1_0", "VL1_0", "", "VL2_0", "VL2_0", "");
}

@Test
void generators() {
Network network = EurostagTutorialExample1Factory.create();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;

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

/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
class NetworkUtilTest {
public class NetworkUtilTest {

@Test
void test() {
Expand All @@ -41,17 +42,17 @@ void testBusFromBusBreakerViewBus() {
"VL2_1", "VL2_0",
"VL2_2", "");
expected.forEach((busBreakerBusId, busIdExpected) -> {
Bus bus = NetworkUtil.getBusViewBus(network.getBusBreakerView().getBus(busBreakerBusId));
Optional<Bus> bus = NetworkUtil.getBusViewBus(network.getBusBreakerView().getBus(busBreakerBusId));
if (!busIdExpected.isEmpty()) {
assertNotNull(bus);
assertEquals(busIdExpected, bus.getId());
assertTrue(bus.isPresent());
assertEquals(busIdExpected, bus.orElseThrow().getId());
} else {
assertNull(bus);
assertTrue(bus.isEmpty());
}
});
}

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

var vl1 = network.newVoltageLevel().setTopologyKind(TopologyKind.BUS_BREAKER).setId("VL1").setNominalV(400.).add();
Expand Down
80 changes: 75 additions & 5 deletions pypowsybl/network/impl/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,11 +538,14 @@ def get_buses(self, all_attributes: bool = False, attributes: List[str] = None,
- **v_mag**: Get the voltage magnitude of the bus (in kV)
- **v_angle**: the voltage angle of the bus (in degree)
- **connected_component**: the number of terminals connected to this bus
- **synchronous_component**: the number of synchronous components that the bus is part of
- **connected_component**: The connected component to which the bus belongs
- **synchronous_component**: The synchronous component to which the bus belongs
- **voltage_level_id**: at which substation the bus is connected
This dataframe is indexed on the bus ID.
This dataframe is indexed on the bus ID in the bus view.
See Also:
:meth:`get_bus_breaker_view_buses`
Examples:
Expand Down Expand Up @@ -605,8 +608,75 @@ def get_buses(self, all_attributes: bool = False, attributes: List[str] = None,
def get_bus_breaker_view_buses(self, all_attributes: bool = False, attributes: List[str] = None,
**kwargs: ArrayLike) -> DataFrame:
r"""
Get a dataframe of buses from the bus/breaker view.
See :meth:`get_buses` for documentation as attributes are the same.
Get a dataframe of buses from the bus/breaker view.
Args:
all_attributes: flag for including all attributes in the dataframe, default is false
attributes: attributes to include in the dataframe. The 2 parameters are mutually exclusive.
If no parameter is specified, the dataframe will include the default attributes.
kwargs: the data to be selected, as named arguments.
Returns:
A dataframe of buses from the bus/breaker view
Notes:
The resulting dataframe, depending on the parameters, will include the following columns:
- **v_mag**: Get the voltage magnitude of the bus (in kV)
- **v_angle**: the voltage angle of the bus (in degree)
- **connected_component**: The connected component to which the bus belongs
- **synchronous_component**: The synchronous component to which the bus belongs
- **voltage_level_id**: at which substation the bus is connected
- **bus_id**: the bus ID in the bus view
This dataframe is indexed on the bus ID in the bus/breaker view.
See Also:
:meth:`get_buses`
Examples:
.. code-block:: python
net = pp.network.create_four_substations_node_breaker_network()
net.get_bus_breaker_view_buses()
It outputs something like:
======== ==== ========= ======== ==================== ====================== ================ ========
\ name v_mag v_angle connected_component synchronous_component voltage_level_id bus_id
======== ==== ========= ======== ==================== ====================== ================ ========
id
S1VL1_0 224.6139 2.2822 0 1 S1VL1 S1VL1_0
S1VL1_2 224.6139 2.2822 0 1 S1VL1 S1VL1_0
S1VL1_4 224.6139 2.2822 0 1 S1VL1 S1VL1_0
S1VL2_0 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_1 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_3 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_5 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_7 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_9 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_11 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_13 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_15 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_17 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_19 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S1VL2_21 400.0000 0.0000 0 1 S1VL2 S1VL2_0
S2VL1_0 408.8470 0.7347 0 0 S2VL1 S2VL1_0
S2VL1_2 408.8470 0.7347 0 0 S2VL1 S2VL1_0
S2VL1_4 408.8470 0.7347 0 0 S2VL1 S2VL1_0
S2VL1_6 408.8470 0.7347 0 0 S2VL1 S2VL1_0
S3VL1_0 400.0000 0.0000 0 0 S3VL1 S3VL1_0
S3VL1_2 400.0000 0.0000 0 0 S3VL1 S3VL1_0
S3VL1_4 400.0000 0.0000 0 0 S3VL1 S3VL1_0
S3VL1_6 400.0000 0.0000 0 0 S3VL1 S3VL1_0
S3VL1_8 400.0000 0.0000 0 0 S3VL1 S3VL1_0
S3VL1_10 400.0000 0.0000 0 0 S3VL1 S3VL1_0
S4VL1_0 400.0000 -1.1259 0 0 S4VL1 S4VL1_0
S4VL1_6 400.0000 -1.1259 0 0 S4VL1 S4VL1_0
S4VL1_2 400.0000 -1.1259 0 0 S4VL1 S4VL1_0
S4VL1_4 400.0000 -1.1259 0 0 S4VL1 S4VL1_0
======== ==== ========= ======== ==================== ====================== ================ ========
"""
return self.get_elements(ElementType.BUS_FROM_BUS_BREAKER_VIEW, all_attributes, attributes, **kwargs)

Expand Down
10 changes: 5 additions & 5 deletions tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -1636,11 +1636,11 @@ def test_bus_breaker_view_buses():
expected_buses = pd.DataFrame(
index=pd.Series(name='id', data=['NGEN', 'NHV1', 'NHV2', 'NLOAD']),
columns=['name', 'v_mag', 'v_angle', 'connected_component', 'synchronous_component',
'voltage_level_id'],
data=[['', nan, nan, 0, 0, 'VLGEN'],
['', 380, nan, 0, 0, 'VLHV1'],
['', 380, nan, 0, 0, 'VLHV2'],
['', nan, nan, 0, 0, 'VLLOAD']])
'voltage_level_id', 'bus_id'],
data=[['', nan, nan, 0, 0, 'VLGEN', 'VLGEN_0'],
['', 380, nan, 0, 0, 'VLHV1', 'VLHV1_0'],
['', 380, nan, 0, 0, 'VLHV2', 'VLHV2_0'],
['', nan, nan, 0, 0, 'VLLOAD', 'VLLOAD_0']])
pd.testing.assert_frame_equal(expected_buses, buses, check_dtype=False)


Expand Down

0 comments on commit b42ca61

Please sign in to comment.