Skip to content

Commit

Permalink
Mapping grounds to python (#898)
Browse files Browse the repository at this point in the history
Signed-off-by: Hugo KULESZA <hugo.kulesza@rte-france.com>
  • Loading branch information
HugoKulesza authored Nov 21, 2024
1 parent 6d78722 commit 7aecdea
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 0 deletions.
1 change: 1 addition & 0 deletions cpp/pypowsybl-cpp/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ PYBIND11_MODULE(_pypowsybl, m) {
.value("THREE_WINDINGS_TRANSFORMER", element_type::THREE_WINDINGS_TRANSFORMER)
.value("GENERATOR", element_type::GENERATOR)
.value("LOAD", element_type::LOAD)
.value("GROUND", element_type::GROUND)
.value("BATTERY", element_type::BATTERY)
.value("SHUNT_COMPENSATOR", element_type::SHUNT_COMPENSATOR)
.value("NON_LINEAR_SHUNT_COMPENSATOR_SECTION", element_type::NON_LINEAR_SHUNT_COMPENSATOR_SECTION)
Expand Down
1 change: 1 addition & 0 deletions cpp/pypowsybl-java/powsybl-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ typedef enum {
THREE_WINDINGS_TRANSFORMER,
GENERATOR,
LOAD,
GROUND,
BATTERY,
SHUNT_COMPENSATOR,
NON_LINEAR_SHUNT_COMPENSATOR_SECTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public enum DataframeElementType {
THREE_WINDINGS_TRANSFORMER,
GENERATOR,
LOAD,
GROUND,
BATTERY,
SHUNT_COMPENSATOR,
NON_LINEAR_SHUNT_COMPENSATOR_SECTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ private static Map<DataframeElementType, NetworkDataframeMapper> createMappers()
mappers.put(DataframeElementType.GENERATOR, generators());
mappers.put(DataframeElementType.LOAD, loads());
mappers.put(DataframeElementType.BATTERY, batteries());
mappers.put(DataframeElementType.GROUND, grounds());
mappers.put(DataframeElementType.SHUNT_COMPENSATOR, shunts());
mappers.put(DataframeElementType.NON_LINEAR_SHUNT_COMPENSATOR_SECTION, shuntsNonLinear());
mappers.put(DataframeElementType.LINEAR_SHUNT_COMPENSATOR_SECTION, linearShuntsSections());
Expand Down Expand Up @@ -389,6 +390,19 @@ static NetworkDataframeMapper batteries() {
.build();
}

static NetworkDataframeMapper grounds() {
return NetworkDataframeMapperBuilder.ofStream(Network::getGroundStream, getOrThrow(Network::getGround, "Ground"))
.stringsIndex("id", Ground::getId)
.strings("name", b -> b.getOptionalName().orElse(""), Identifiable::setName)
.strings("voltage_level_id", getVoltageLevelId())
.strings("bus_id", b -> getBusId(b.getTerminal()))
.strings("bus_breaker_bus_id", getBusBreakerViewBusId(), NetworkDataframes::setBusBreakerViewBusId, false)
.ints("node", b -> getNode(b.getTerminal()), false)
.booleans("connected", b -> b.getTerminal().isConnected(), connectInjection())
.addProperties()
.build();
}

static NetworkDataframeMapper shunts() {
return NetworkDataframeMapperBuilder.ofStream(Network::getShuntCompensatorStream, getOrThrow(Network::getShuntCompensator, "Shunt compensator"))
.stringsIndex("id", ShuntCompensator::getId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ private void add(InjectionAdder<?, ?> injectionAdder) {
lccConverterStationAdder.add();
} else if (injectionAdder instanceof VscConverterStationAdder vscConverterStationAdder) {
vscConverterStationAdder.add();
} else if (injectionAdder instanceof GroundAdder groundAdder) {
groundAdder.add();
} else {
throw new AssertionError("Given InjectionAdder not supported: " + injectionAdder.getClass().getName());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2024, 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.dataframe.network.adders;

import com.powsybl.commons.report.ReportNode;
import com.powsybl.dataframe.SeriesMetadata;
import com.powsybl.dataframe.update.StringSeries;
import com.powsybl.dataframe.update.UpdatingDataframe;
import com.powsybl.iidm.network.*;

import java.util.List;
import java.util.Optional;

import static com.powsybl.dataframe.network.adders.NetworkUtils.getVoltageLevelOrThrowWithBusOrBusbarSectionId;

/**
* @author Hugo Kulesza {@literal <hugo.kulesza@rte-france.com>}
*/
public class GroundDataframeAdder extends AbstractSimpleAdder {

private static final List<SeriesMetadata> METADATA = List.of(
SeriesMetadata.stringIndex("id"),
SeriesMetadata.strings("voltage_level_id"),
SeriesMetadata.strings("bus_id"),
SeriesMetadata.strings("connectable_bus_id"),
SeriesMetadata.ints("node"),
SeriesMetadata.strings("name")
);

@Override
public List<List<SeriesMetadata>> getMetadata() {
return List.of(METADATA);
}

private static class GroundSeries extends InjectionSeries {

private final StringSeries voltageLevels;
private final StringSeries busOrBusbarSections;

GroundSeries(UpdatingDataframe dataframe) {
super(dataframe);
this.voltageLevels = dataframe.getStrings("voltage_level_id");
this.busOrBusbarSections = dataframe.getStrings("bus_id");
}

Optional<GroundAdder> createAdder(Network network, int row, boolean throwException) {
Optional<VoltageLevel> vl = getVoltageLevelOrThrowWithBusOrBusbarSectionId(network, row, voltageLevels, busOrBusbarSections, throwException);
if (vl.isPresent()) {
GroundAdder adder = vl.get().newGround();
setInjectionAttributes(adder, row);
return Optional.of(adder);
}
return Optional.empty();
}
}

@Override
public void addElements(Network network, UpdatingDataframe dataframe, AdditionStrategy addition, boolean throwException, ReportNode reportNode) {
GroundSeries series = new GroundSeries(dataframe);
for (int row = 0; row < dataframe.getRowCount(); row++) {
Optional<GroundAdder> adder = series.createAdder(network, row, throwException);
if (adder.isPresent()) {
addition.add(network, dataframe, adder.get(), row, throwException, reportNode);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public final class NetworkElementAdders {
Map.entry(TWO_WINDINGS_TRANSFORMER, new TwtDataframeAdder()),
Map.entry(THREE_WINDINGS_TRANSFORMER, new ThreeWindingsTransformerDataframeAdder()),
Map.entry(LOAD, new LoadDataframeAdder()),
Map.entry(GROUND, new GroundDataframeAdder()),
Map.entry(VSC_CONVERTER_STATION, new VscStationDataframeAdder()),
Map.entry(LCC_CONVERTER_STATION, new LccStationDataframeAdder()),
Map.entry(BUSBAR_SECTION, new BusBarDataframeAdder()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ public enum ElementType {
THREE_WINDINGS_TRANSFORMER,
GENERATOR,
LOAD,
GROUND,
BATTERY,
SHUNT_COMPENSATOR,
NON_LINEAR_SHUNT_COMPENSATOR_SECTION,
Expand Down
2 changes: 2 additions & 0 deletions java/src/main/java/com/powsybl/python/commons/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ public static PyPowsyblApiHeader.ElementType convert(DataframeElementType type)
case THREE_WINDINGS_TRANSFORMER -> PyPowsyblApiHeader.ElementType.THREE_WINDINGS_TRANSFORMER;
case GENERATOR -> PyPowsyblApiHeader.ElementType.GENERATOR;
case LOAD -> PyPowsyblApiHeader.ElementType.LOAD;
case GROUND -> PyPowsyblApiHeader.ElementType.GROUND;
case BATTERY -> PyPowsyblApiHeader.ElementType.BATTERY;
case SHUNT_COMPENSATOR -> PyPowsyblApiHeader.ElementType.SHUNT_COMPENSATOR;
case DANGLING_LINE -> PyPowsyblApiHeader.ElementType.DANGLING_LINE;
Expand Down Expand Up @@ -237,6 +238,7 @@ public static DataframeElementType convert(PyPowsyblApiHeader.ElementType type)
case THREE_WINDINGS_TRANSFORMER -> DataframeElementType.THREE_WINDINGS_TRANSFORMER;
case GENERATOR -> DataframeElementType.GENERATOR;
case LOAD -> DataframeElementType.LOAD;
case GROUND -> DataframeElementType.GROUND;
case BATTERY -> DataframeElementType.BATTERY;
case SHUNT_COMPENSATOR -> DataframeElementType.SHUNT_COMPENSATOR;
case DANGLING_LINE -> DataframeElementType.DANGLING_LINE;
Expand Down
1 change: 1 addition & 0 deletions pypowsybl/_pypowsybl.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ class ElementType:
LINE: ClassVar[ElementType] = ...
LINEAR_SHUNT_COMPENSATOR_SECTION: ClassVar[ElementType] = ...
LOAD: ClassVar[ElementType] = ...
GROUND: ClassVar[ElementType] = ...
MINMAX_REACTIVE_LIMITS: ClassVar[ElementType] = ...
NON_LINEAR_SHUNT_COMPENSATOR_SECTION: ClassVar[ElementType] = ...
PHASE_TAP_CHANGER: ClassVar[ElementType] = ...
Expand Down
90 changes: 90 additions & 0 deletions pypowsybl/network/impl/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,34 @@ def get_loads(self, all_attributes: bool = False, attributes: List[str] = None,
"""
return self.get_elements(ElementType.LOAD, all_attributes, attributes, **kwargs)

def get_grounds(self, all_attributes: bool = False, attributes: List[str] = None,
**kwargs: ArrayLike) -> DataFrame:
r"""
Get a dataframe of grounds.
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:
the grounds dataframe
Notes:
The resulting dataframe, depending on the parameters, will include the following columns:
- **voltage_level_id**: at which substation this ground is connected
- **bus_id**: bus where this ground is connected
- **bus_breaker_bus_id** (optional): bus of the bus-breaker view where this ground is connected
- **node** (optional): node where this ground is connected, in node-breaker voltage levels
- **connected**: ``True`` if the ground is connected to a bus
This dataframe is indexed on the ground ID.
"""
return self.get_elements(ElementType.GROUND, all_attributes, attributes, **kwargs)

def get_batteries(self, all_attributes: bool = False, attributes: List[str] = None,
**kwargs: ArrayLike) -> DataFrame:
r"""
Expand Down Expand Up @@ -2809,6 +2837,33 @@ def update_loads(self, df: DataFrame = None, **kwargs: ArrayLike) -> None:
"""
return self._update_elements(ElementType.LOAD, df, **kwargs)

def update_grounds(self, df: DataFrame = None, **kwargs: ArrayLike) -> None:
"""
Update grounds with data provided as a :class:`~pandas.DataFrame` or as named arguments.
Args:
df: the data to be updated, as a dataframe.
kwargs: the data to be updated, as named arguments.
Arguments can be single values or any type of sequence.
In the case of sequences, all arguments must have the same length.
Notes:
Attributes that can be updated are:
- `connected`
See Also:
:meth:`get_grounds`
Examples:
Some examples using keyword arguments:
.. code-block:: python
network.update_grounds(id='L-1', connected=False)
"""
return self._update_elements(ElementType.GROUND, df, **kwargs)

def update_batteries(self, df: DataFrame = None, **kwargs: ArrayLike) -> None:
"""
Update batteries with data provided as a :class:`~pandas.DataFrame` or as named arguments.
Expand Down Expand Up @@ -4030,6 +4085,41 @@ def create_loads(self, df: DataFrame = None, **kwargs: ArrayLike) -> None:
"""
return self._create_elements(ElementType.LOAD, [df], **kwargs)

def create_grounds(self, df: DataFrame = None, **kwargs: ArrayLike) -> None:
"""
Create grounds.
Args:
df: Attributes as a dataframe.
kwargs: Attributes as keyword arguments.
Notes:
Data may be provided as a dataframe or as keyword arguments.
In the latter case, all arguments must have the same length.
Valid attributes are:
- **id**: the identifier of the new ground
- **voltage_level_id**: the voltage level where the new ground will be created.
The voltage level must already exist.
- **bus_id**: the bus where the new ground will be connected,
if the voltage level has a bus-breaker topology kind.
- **connectable_bus_id**: the bus where the new ground will be connectable,
if the voltage level has a bus-breaker topology kind.
- **node**: the node where the new ground will be connected,
if the voltage level has a node-breaker topology kind.
- **name**: an optional human-readable name
Examples:
Using keyword arguments:
.. code-block:: python
network.create_loads(id='GROUND-1', voltage_level_id='VL1', bus_id='B1')
"""
return self._create_elements(ElementType.GROUND, [df], **kwargs)

def create_batteries(self, df: DataFrame = None, **kwargs: ArrayLike) -> None:
"""
Creates batteries.
Expand Down
10 changes: 10 additions & 0 deletions tests/test_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,16 @@ def test_loads_data_frame():
data=[['S1VL1_2', 2], ['S1VL2_13', 13], ['S1VL2_15', 15], ['S1VL2_17', 17], ['S3VL1_4', 4], ['S4VL1_2', 2]])
pd.testing.assert_frame_equal(expected, loads, check_dtype=False, atol=1e-2)

def test_grounds():
n = pp.network.create_eurostag_tutorial_example1_network()
grounds = n.get_grounds(all_attributes=True)
assert grounds.empty

n.create_grounds(id='GROUND', voltage_level_id='VLHV1', bus_id='NHV1')
expected = pd.DataFrame(index=pd.Series(name='id', data=['GROUND']),
columns=['name', 'voltage_level_id', 'bus_id', 'connected'],
data=[['', 'VLHV1', 'VLHV1_0', True]])
pd.testing.assert_frame_equal(expected, n.get_grounds(), check_dtype=False, atol=1e-2)

def test_batteries_data_frame():
n = pp.network.load(str(TEST_DIR.joinpath('battery.xiidm')))
Expand Down

0 comments on commit 7aecdea

Please sign in to comment.