Skip to content

Commit

Permalink
Add Network area model support (#840)
Browse files Browse the repository at this point in the history
Signed-off-by: Damien Jeandemange <[email protected]>
  • Loading branch information
jeandemanged authored Oct 22, 2024
1 parent 0dfb587 commit 46a8cf5
Show file tree
Hide file tree
Showing 23 changed files with 1,074 additions and 8 deletions.
5 changes: 4 additions & 1 deletion cpp/pypowsybl-cpp/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,10 @@ PYBIND11_MODULE(_pypowsybl, m) {
.value("INJECTION", element_type::INJECTION)
.value("BRANCH", element_type::BRANCH)
.value("TERMINAL", element_type::TERMINAL)
.value("SUB_NETWORK", element_type::SUB_NETWORK);
.value("SUB_NETWORK", element_type::SUB_NETWORK)
.value("AREA", element_type::AREA)
.value("AREA_VOLTAGE_LEVELS", element_type::AREA_VOLTAGE_LEVELS)
.value("AREA_BOUNDARIES", element_type::AREA_BOUNDARIES);

py::enum_<filter_attributes_type>(m, "FilterAttributesType")
.value("ALL_ATTRIBUTES", filter_attributes_type::ALL_ATTRIBUTES)
Expand Down
5 changes: 4 additions & 1 deletion cpp/pypowsybl-java/powsybl-api.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,10 @@ typedef enum {
INJECTION,
BRANCH,
TERMINAL,
SUB_NETWORK
SUB_NETWORK,
AREA,
AREA_VOLTAGE_LEVELS,
AREA_BOUNDARIES
} element_type;

typedef enum {
Expand Down
7 changes: 7 additions & 0 deletions docs/reference/network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ All network elements are accessible as dataframes, using the following getters.
Network.get_2_windings_transformers
Network.get_3_windings_transformers
Network.get_aliases
Network.get_areas
Network.get_areas_boundaries
Network.get_areas_voltage_levels
Network.get_batteries
Network.get_branches
Network.get_busbar_sections
Expand Down Expand Up @@ -137,6 +140,7 @@ Network elements can be modified using dataframes:

Network.update_2_windings_transformers
Network.update_3_windings_transformers
Network.update_areas
Network.update_batteries
Network.update_buses
Network.update_dangling_lines
Expand Down Expand Up @@ -172,6 +176,9 @@ Network elements can be created or removed using the following methods:

Network.create_2_windings_transformers
Network.create_3_windings_transformers
Network.create_areas
Network.create_areas_voltage_levels
Network.create_areas_boundaries
Network.create_batteries
Network.create_busbar_sections
Network.create_buses
Expand Down
1 change: 1 addition & 0 deletions docs/user_guide/network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ Reading network elements data
All network elements data can be read as :class:`DataFrames <pandas.DataFrame>`.
Supported elements are:

- areas
- buses (from bus view)
- buses from bus/breaker view
- lines
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,8 @@ public enum DataframeElementType {
INJECTION,
BRANCH,
TERMINAL,
SUB_NETWORK
SUB_NETWORK,
AREA,
AREA_VOLTAGE_LEVELS,
AREA_BOUNDARIES
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.powsybl.dataframe.network.extensions.ExtensionDataframeKey;
import com.powsybl.dataframe.update.UpdatingDataframe;
import com.powsybl.iidm.network.*;
import com.powsybl.python.commons.PyPowsyblApiHeader;
import com.powsybl.python.network.NetworkUtil;
import com.powsybl.python.network.SideEnum;
import com.powsybl.python.network.TemporaryLimitData;
Expand Down Expand Up @@ -54,6 +55,9 @@ public static NetworkDataframeMapper getDataframeMapper(DataframeElementType typ
private static Map<DataframeElementType, NetworkDataframeMapper> createMappers() {
Map<DataframeElementType, NetworkDataframeMapper> mappers = new EnumMap<>(DataframeElementType.class);
mappers.put(DataframeElementType.SUB_NETWORK, subNetworks());
mappers.put(DataframeElementType.AREA, areas());
mappers.put(DataframeElementType.AREA_VOLTAGE_LEVELS, areaVoltageLevels());
mappers.put(DataframeElementType.AREA_BOUNDARIES, areaBoundaries());
mappers.put(DataframeElementType.BUS, buses(false));
mappers.put(DataframeElementType.BUS_FROM_BUS_BREAKER_VIEW, buses(true));
mappers.put(DataframeElementType.LINE, lines());
Expand Down Expand Up @@ -304,6 +308,21 @@ private static NetworkDataframeMapper subNetworks() {
.build();
}

private static NetworkDataframeMapper areas() {
return NetworkDataframeMapperBuilder.ofStream(Network::getAreaStream,
getOrThrow(Network::getArea, "Area"))
.stringsIndex("id", Identifiable::getId)
.strings("name", a -> a.getOptionalName().orElse(""), Identifiable::setName)
.strings("area_type", Area::getAreaType)
.doubles("interchange_target", (a, context) -> perUnitPQ(context, a.getInterchangeTarget().orElse(Double.NaN)), (a, p, context) -> a.setInterchangeTarget(unPerUnitPQ(context, p)))
.doubles("interchange", (a, context) -> perUnitPQ(context, a.getInterchange()))
.doubles("ac_interchange", (a, context) -> perUnitPQ(context, a.getAcInterchange()))
.doubles("dc_interchange", (a, context) -> perUnitPQ(context, a.getDcInterchange()))
.booleans("fictitious", Identifiable::isFictitious, Identifiable::setFictitious, false)
.addProperties()
.build();
}

static NetworkDataframeMapper buses(boolean busBreakerView) {
return NetworkDataframeMapperBuilder.ofStream(n -> busBreakerView ? n.getBusBreakerView().getBusStream() : n.getBusView().getBusStream(),
getOrThrow((b, id) -> b.getBusView().getBus(id), "Bus"))
Expand Down Expand Up @@ -1104,6 +1123,26 @@ private static String getTerminalSideStr(Branch<?> branch, Terminal terminal) {
return "";
}

private static String getTerminalSideStr(ThreeWindingsTransformer t3wt, Terminal terminal) {
if (terminal == t3wt.getLeg1().getTerminal()) {
return ThreeSides.ONE.name();
} else if (terminal == t3wt.getLeg2().getTerminal()) {
return ThreeSides.TWO.name();
} else if (terminal == t3wt.getLeg3().getTerminal()) {
return ThreeSides.THREE.name();
}
return "";
}

private static String getTerminalSideStr(Identifiable<?> identifiable, Terminal terminal) {
if (identifiable instanceof Branch<?> branch) {
return getTerminalSideStr(branch, terminal);
} else if (identifiable instanceof ThreeWindingsTransformer t3wt) {
return getTerminalSideStr(t3wt, terminal);
}
return "";
}

static void setPhaseTapChangerRegulatedSide(TwoWindingsTransformer transformer, String side) {
transformer.getPhaseTapChanger().setRegulationTerminal(getBranchTerminal(transformer, side));
}
Expand Down Expand Up @@ -1255,5 +1294,59 @@ private static Stream<Pair<Identifiable<?>, String>> getAliasesData(Network netw
.map(alias -> Pair.of(identifiable, alias)));
}

private static NetworkDataframeMapper areaVoltageLevels() {
return NetworkDataframeMapperBuilder.ofStream(NetworkDataframes::areaVoltageLevelsData)
.stringsIndex("id", pair -> pair.getLeft().getId())
.strings("voltage_level_id", pair -> pair.getRight().getId())
.build();
}

private static Stream<Pair<Area, VoltageLevel>> areaVoltageLevelsData(Network network) {
return network.getAreaStream()
.flatMap(area -> area.getVoltageLevelStream()
.map(voltageLevel -> Pair.of(area, voltageLevel)));
}

private static NetworkDataframeMapper areaBoundaries() {
return NetworkDataframeMapperBuilder.ofStream(NetworkDataframes::areaBoundariesData)
.stringsIndex("id", pair -> pair.getLeft().getId())
.strings("boundary_type", pair -> getAreaBoundaryType(pair.getRight()), false)
.strings("element", pair -> getAreaBoundaryElement(pair.getRight()))
.strings("side", pair -> getAreaBoundarySide(pair.getRight()), false)
.booleans("ac", pair -> pair.getRight().isAc())
.doubles("p", (pair, context) -> perUnitPQ(context, pair.getRight().getP()))
.doubles("q", (pair, context) -> perUnitPQ(context, pair.getRight().getQ()))
.build();
}

private static String getAreaBoundaryType(AreaBoundary areaBoundary) {
Objects.requireNonNull(areaBoundary);
return areaBoundary.getBoundary().map(
b -> PyPowsyblApiHeader.ElementType.DANGLING_LINE.name()
).orElse(PyPowsyblApiHeader.ElementType.TERMINAL.name());
}

private static String getAreaBoundaryElement(AreaBoundary areaBoundary) {
Objects.requireNonNull(areaBoundary);
return areaBoundary.getBoundary().map(
b -> b.getDanglingLine().getId()
).orElseGet(() -> areaBoundary.getTerminal().orElseThrow().getConnectable().getId());
}

private static String getAreaBoundarySide(AreaBoundary areaBoundary) {
Objects.requireNonNull(areaBoundary);
if (areaBoundary.getBoundary().isPresent()) {
return "";
}
Terminal terminal = areaBoundary.getTerminal().orElseThrow();
return getTerminalSideStr(terminal.getConnectable(), terminal);
}

private static Stream<Pair<Area, AreaBoundary>> areaBoundariesData(Network network) {
return network.getAreaStream()
.flatMap(area -> area.getAreaBoundaryStream()
.map(areaBoundary -> Pair.of(area, areaBoundary)));
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* Copyright (c) 2024, Coreso SA (https://www.coreso.eu/) and TSCNET Services GmbH (https://www.tscnet.eu/)
* 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.PowsyblException;
import com.powsybl.dataframe.SeriesMetadata;
import com.powsybl.dataframe.update.IntSeries;
import com.powsybl.dataframe.update.StringSeries;
import com.powsybl.dataframe.update.UpdatingDataframe;
import com.powsybl.iidm.network.*;
import com.powsybl.python.commons.PyPowsyblApiHeader;
import org.apache.commons.lang3.tuple.Pair;

import java.util.*;

import static com.powsybl.dataframe.network.adders.SeriesUtils.getRequiredStrings;

/**
* @author Damien Jeandemange <[email protected]>
*/
public class AreaBoundariesDataframeAdder implements NetworkElementAdder {

private static final List<SeriesMetadata> METADATA = List.of(
SeriesMetadata.stringIndex("id"),
SeriesMetadata.strings("boundary_type"),
SeriesMetadata.strings("element"),
SeriesMetadata.strings("side"),
SeriesMetadata.booleans("ac")
);

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

private static final class AreaBoundaries {

private final StringSeries ids;
private final StringSeries boundaryTypes;
private final StringSeries elements;
private final StringSeries sides;
private final IntSeries acs;

AreaBoundaries(UpdatingDataframe dataframe) {
this.ids = getRequiredStrings(dataframe, "id");
this.boundaryTypes = dataframe.getStrings("boundary_type");
this.elements = getRequiredStrings(dataframe, "element");
this.sides = dataframe.getStrings("side");
this.acs = dataframe.getInts("ac");
}

public StringSeries getIds() {
return ids;
}

public StringSeries getBoundaryTypes() {
return boundaryTypes;
}

public StringSeries getElements() {
return elements;
}

public StringSeries getSides() {
return sides;
}

public IntSeries getAcs() {
return acs;
}
}

@Override
public void addElements(Network network, List<UpdatingDataframe> dataframes) {
UpdatingDataframe primaryTable = dataframes.get(0);
AreaBoundaries series = new AreaBoundaries(primaryTable);

Map<Area, List<Pair<DanglingLine, Boolean>>> danglingLineBoundaries = new HashMap<>();
Map<Area, List<Pair<Terminal, Boolean>>> terminalBoundaries = new HashMap<>();
for (int i = 0; i < primaryTable.getRowCount(); i++) {
String areaId = series.getIds().get(i);
String boundaryTypeStr = series.getBoundaryTypes() == null ? "DANGLING_LINE" : series.getBoundaryTypes().get(i);
String element = series.getElements().get(i);
String side = series.getSides() == null ? "" : series.getSides().get(i);
boolean ac = series.getAcs() == null || series.getAcs().get(i) == 1;
Area area = NetworkUtils.getAreaOrThrow(network, areaId);
// an empty element alone for an area indicates remove all boundaries in area
Connectable<?> connectable = element.isEmpty() ? null : NetworkUtils.getConnectableOrThrow(network, element);
if (connectable == null) {
// add an entry so that everything will be deleted for the area
danglingLineBoundaries.computeIfAbsent(area, k -> new ArrayList<>()).add(Pair.of(null, null));
terminalBoundaries.computeIfAbsent(area, k -> new ArrayList<>()).add(Pair.of(null, null));
continue;
}
PyPowsyblApiHeader.ElementType boundaryType = PyPowsyblApiHeader.ElementType.valueOf(boundaryTypeStr);
if (Objects.equals(boundaryType, PyPowsyblApiHeader.ElementType.DANGLING_LINE)) {
// Boundary modeled by a dangling line
DanglingLine danglingLine = NetworkUtils.getDanglingLineOrThrow(network, element);
danglingLineBoundaries.computeIfAbsent(area, k -> new ArrayList<>()).add(Pair.of(danglingLine, ac));
} else if (Objects.equals(boundaryType, PyPowsyblApiHeader.ElementType.TERMINAL)) {
// Boundary modeled by a terminal
Terminal terminal = NetworkUtils.getTerminalOrThrow(network, element, side);
terminalBoundaries.computeIfAbsent(area, k -> new ArrayList<>()).add(Pair.of(terminal, ac));
} else {
throw new PowsyblException("Area boundary boundary_type must be either DANGLING_LINE or TERMINAL");
}
}
// delete boundaries of involved areas
// If a given area has *only* a null boundary in the updating dataframe, this results
// in the area having all its boundaries unlinked.
Set<Area> areas = new HashSet<>(danglingLineBoundaries.keySet());
areas.addAll(terminalBoundaries.keySet());
areas.forEach(a -> a.getAreaBoundaryStream().toList()
.forEach(areaBoundary -> {
areaBoundary.getBoundary().ifPresent(a::removeAreaBoundary);
areaBoundary.getTerminal().ifPresent(a::removeAreaBoundary);
}));
// create new boundaries
danglingLineBoundaries.forEach((area, list) -> list.stream()
.filter(pair -> pair.getLeft() != null)
.forEach(pair -> area.newAreaBoundary()
.setBoundary(pair.getLeft().getBoundary())
.setAc(pair.getRight())
.add()));
terminalBoundaries.forEach((area, list) -> list.stream()
.filter(pair -> pair.getLeft() != null)
.forEach(pair -> area.newAreaBoundary()
.setTerminal(pair.getLeft())
.setAc(pair.getRight())
.add()));
}
}
Loading

0 comments on commit 46a8cf5

Please sign in to comment.