Skip to content

Commit

Permalink
Merge branch 'e2nIEE:develop' into tennet_nl_2024_09_06_status
Browse files Browse the repository at this point in the history
  • Loading branch information
mfisch42 authored Oct 22, 2024
2 parents f23006c + ae39c69 commit ccdb0e5
Show file tree
Hide file tree
Showing 11 changed files with 423 additions and 270 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
Change Log
=============

[upcoming release] - 2024-..-..
-------------------------------
- [FIXED] Increasing geojson precision as the default precision might cause problems with pandahub

[2.14.11] - 2024-07-08
-------------------------------
- [FIXED] Lightsim2grid version
Expand Down Expand Up @@ -86,6 +90,7 @@ Change Log
- [ADDED] Add VSC element, dc buses, dc lines, and hybrid AC/DC power flow calculation
- [CHANGED] accelerate _integrate_power_elements_connected_with_switch_buses() in get_equivalent()
- [CHANGED] accelerate distributed slack power flow calculation by using sparse-aware operations in _subnetworks()
- [CHANGED] Trafo Controllers can now be added to elements that are out of service, changed self.nothing_to_do()
- [ADDED] Discrete shunt controller for local voltage regulation with shunt steps
- [ADDED] cim2pp converter: Using lxml to parse XML files (better performance)

Expand Down
3 changes: 3 additions & 0 deletions pandapower/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
from pandapower.runpm import *
from pandapower.pf.runpp_3ph import runpp_3ph

import geojson
geojson.geometry.DEFAULT_PRECISION = 8

import pandas as pd
pd.options.mode.chained_assignment = None # default='warn'

Expand Down
20 changes: 10 additions & 10 deletions pandapower/control/controller/trafo/ContinuousTapControl.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ def __init__(self, net, element_index, vm_set_pu, tol=1e-3, side="lv", element="
self.vm_set_pu = vm_set_pu

def _set_t_nom(self, net):
vn_hv_kv = read_from_net(net, self.element, self.controlled_element_index, 'vn_hv_kv', self._read_write_flag)
hv_bus = read_from_net(net, self.element, self.controlled_element_index, 'hv_bus', self._read_write_flag)
vn_hv_kv = read_from_net(net, self.element, self.element_index, 'vn_hv_kv', self._read_write_flag)
hv_bus = read_from_net(net, self.element, self.element_index, 'hv_bus', self._read_write_flag)
vn_hv_bus_kv = read_from_net(net, "bus", hv_bus, 'vn_kv', self._read_write_flag)

if self.element == "trafo3w" and self.side == "mv":
vn_mv_kv = read_from_net(net, self.element, self.controlled_element_index, 'vn_mv_kv', self._read_write_flag)
mv_bus = read_from_net(net, self.element, self.controlled_element_index, 'mv_bus', self._read_write_flag)
vn_mv_kv = read_from_net(net, self.element, self.element_index, 'vn_mv_kv', self._read_write_flag)
mv_bus = read_from_net(net, self.element, self.element_index, 'mv_bus', self._read_write_flag)
vn_mv_bus_kv = read_from_net(net, "bus", mv_bus, 'vn_kv', self._read_write_flag)
self.t_nom = vn_mv_kv / vn_hv_kv * vn_hv_bus_kv / vn_mv_bus_kv
else:
vn_lv_kv = read_from_net(net, self.element, self.controlled_element_index, 'vn_lv_kv', self._read_write_flag)
lv_bus = read_from_net(net, self.element, self.controlled_element_index, 'lv_bus', self._read_write_flag)
vn_lv_kv = read_from_net(net, self.element, self.element_index, 'vn_lv_kv', self._read_write_flag)
lv_bus = read_from_net(net, self.element, self.element_index, 'lv_bus', self._read_write_flag)
vn_lv_bus_kv = read_from_net(net, "bus", lv_bus, 'vn_kv', self._read_write_flag)
self.t_nom = vn_lv_kv / vn_hv_kv * vn_hv_bus_kv / vn_lv_bus_kv

Expand All @@ -77,7 +77,7 @@ def control_step(self, net):
if self.nothing_to_do(net):
return

delta_vm_pu = read_from_net(net, "res_bus", self.controlled_bus, 'vm_pu', self._read_write_flag) - self.vm_set_pu
delta_vm_pu = read_from_net(net, "res_bus", self.trafobus, 'vm_pu', self._read_write_flag) - self.vm_set_pu
tc = delta_vm_pu / self.tap_step_percent * 100 / self.t_nom
self.tap_pos = self.tap_pos + tc * self.tap_side_coeff * self.tap_sign
if self.check_tap_bounds:
Expand All @@ -87,7 +87,7 @@ def control_step(self, net):
# necessary in case the dtype of the column is int
if net[self.element].tap_pos.dtype != "float":
net[self.element].tap_pos = net[self.element].tap_pos.astype(float)
write_to_net(net, self.element, self.controlled_element_index, "tap_pos", self.tap_pos, self._read_write_flag)
write_to_net(net, self.element, self.element_index, "tap_pos", self.tap_pos, self._read_write_flag)

def is_converged(self, net):
"""
Expand All @@ -97,10 +97,10 @@ def is_converged(self, net):
if self.nothing_to_do(net):
return True

vm_pu = read_from_net(net, "res_bus", self.controlled_bus, "vm_pu", self._read_write_flag)
vm_pu = read_from_net(net, "res_bus", self.trafobus, "vm_pu", self._read_write_flag)
# this is possible in case the trafo is set out of service by the connectivity check
is_nan = np.isnan(vm_pu)
self.tap_pos = read_from_net(net, self.element, self.controlled_element_index, "tap_pos", self._read_write_flag)
self.tap_pos = read_from_net(net, self.element, self.element_index, "tap_pos", self._read_write_flag)
difference = 1 - self.vm_set_pu / vm_pu

if self.check_tap_bounds:
Expand Down
10 changes: 5 additions & 5 deletions pandapower/control/controller/trafo/DiscreteTapControl.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ def control_step(self, net):
if self.nothing_to_do(net):
return

vm_pu = read_from_net(net, "res_bus", self.controlled_bus, "vm_pu", self._read_write_flag)
vm_pu = read_from_net(net, "res_bus", self.trafobus, "vm_pu", self._read_write_flag)
self.tap_pos = read_from_net(
net, self.element, self.controlled_element_index, "tap_pos", self._read_write_flag)
net, self.element, self.element_index, "tap_pos", self._read_write_flag)

increment = np.where(
self.tap_side_coeff * self.tap_sign == 1,
Expand All @@ -126,7 +126,7 @@ def control_step(self, net):
self._hunting_taps = self._hunting_taps[1:, :]

# WRITE TO NET
write_to_net(net, self.element, self.controlled_element_index, 'tap_pos',
write_to_net(net, self.element, self.element_index, 'tap_pos',
self.tap_pos, self._read_write_flag)

def is_converged(self, net):
Expand All @@ -136,11 +136,11 @@ def is_converged(self, net):
if self.nothing_to_do(net):
return True

vm_pu = read_from_net(net, "res_bus", self.controlled_bus, "vm_pu", self._read_write_flag)
vm_pu = read_from_net(net, "res_bus", self.trafobus, "vm_pu", self._read_write_flag)
# this is possible in case the trafo is set out of service by the connectivity check
is_nan = np.isnan(vm_pu)
self.tap_pos = read_from_net(
net, self.element, self.controlled_element_index, "tap_pos", self._read_write_flag)
net, self.element, self.element_index, "tap_pos", self._read_write_flag)

reached_limit = np.where(self.tap_side_coeff * self.tap_sign == 1,
(vm_pu < self.vm_lower_pu) & (self.tap_pos == self.tap_min) |
Expand Down
59 changes: 34 additions & 25 deletions pandapower/control/controller/trafo_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,16 @@ def __init__(self, net, element_index, side, tol, in_service, element, level=0,

self._set_side(side)
self._set_read_write_flag(net)
self._set_valid_controlled_index_and_bus(net)
# self._set_valid_controlled_index_and_bus(net)
self._set_tap_parameters(net)
self._set_tap_side_coeff(net)

self.tol = tol

self.set_recycle(net)

self.trafobus = read_from_net(net, self.element, self.element_index, self.side + '_bus', self._read_write_flag)

def _set_read_write_flag(self, net):
# if someone changes indices of the controller from single index to array and vice versa
self._read_write_flag, _ = _detect_read_write_flag(net, self.element, self.element_index, "tap_pos")
Expand All @@ -67,25 +69,36 @@ def initialize_control(self, net):
# update trafo tap parameters
# we assume side does not change after the controller is created
self._set_read_write_flag(net)
self._set_valid_controlled_index_and_bus(net)
# self._set_valid_controlled_index_and_bus(net)
if self.nothing_to_do(net):
return
self._set_tap_parameters(net)
self._set_tap_side_coeff(net)

def nothing_to_do(self, net):
# if the controller shouldn't do anything, return True
if self.controlled_element_index is None or (
self._read_write_flag != 'single_index' and len(self.controlled_element_index) == 0):
return True
return False
element_in_service = read_from_net(net, self.element, self.element_index, 'in_service', self._read_write_flag)
ext_grid_bus = np.isin(self.trafobus, net.ext_grid.loc[net.ext_grid.in_service, 'bus'].values)
element_index_in_net = np.isin(self.element_index, net[self.element].index.values)
self.controlled = np.logical_and(np.logical_and(element_in_service, element_index_in_net), np.logical_not(ext_grid_bus))
if isinstance(self.element_index, np.int64) or isinstance(self.element_index, int):
# if the controller shouldn't do anything, return True
if not element_in_service or ext_grid_bus or not element_index_in_net or (
self._read_write_flag != 'single_index' and len(self.element_index) == 0):
return True
return False
else:
# if the controller shouldn't do anything, return True
if np.all(~element_in_service[self.controlled]) or np.all(ext_grid_bus[self.controlled]) or np.all(~element_index_in_net[self.controlled]) or (
self._read_write_flag != 'single_index' and len(self.element_index) == 0):
return True
return False

def _set_tap_side_coeff(self, net):
tap_side = read_from_net(net, self.element, self.controlled_element_index, 'tap_side', self._read_write_flag)
tap_side = read_from_net(net, self.element, self.element_index, 'tap_side', self._read_write_flag)
if (len(np.setdiff1d(tap_side, ['hv', 'lv'])) > 0 and self.element == "trafo") or \
(len(np.setdiff1d(tap_side, ['hv', 'lv', 'mv'])) > 0 and self.element == "trafo3w"):
raise ValueError("Trafo tap side (in net.%s) has to be either hv or lv, "
"but received: %s for trafo %s" % (self.element, tap_side, self.controlled_element_index))
"but received: %s for trafo %s" % (self.element, tap_side, self.element_index))

if self._read_write_flag == "single_index":
self.tap_side_coeff = 1 if tap_side == 'hv' else -1
Expand Down Expand Up @@ -113,29 +126,25 @@ def _set_side(self, side):
self.side = side

def _set_valid_controlled_index_and_bus(self, net):
self.trafobus = read_from_net(net, self.element, self.element_index, self.side + '_bus', self._read_write_flag)
element_in_service = read_from_net(net, self.element, self.element_index, 'in_service', self._read_write_flag)
ext_grid_bus = np.isin(self.trafobus, net.ext_grid.loc[net.ext_grid.in_service, 'bus'].values)
element_index_in_net = np.isin(self.element_index, net[self.element].index.values)
controlled = np.logical_and(np.logical_and(element_in_service, element_index_in_net), np.logical_not(ext_grid_bus))
if self._read_write_flag == 'single_index':
self.controlled_element_index = self.element_index if controlled else None
self.controlled_bus = self.trafobus if controlled else None
else:
self.controlled_element_index = self.element_index[controlled]
self.controlled_bus = self.trafobus[controlled]
self.controlled = np.logical_and(np.logical_and(element_in_service, element_index_in_net), np.logical_not(ext_grid_bus))
if self._read_write_flag != 'single_index':
self.element_index = self.element_index[self.controlled]
self.trafobus = self.trafobus[self.controlled]

if np.all(~controlled):
if np.all(~self.controlled):
logger.warning("All controlled buses are not valid: controller has no effect")

def _set_tap_parameters(self, net):
self.tap_min = read_from_net(net, self.element, self.controlled_element_index, "tap_min", self._read_write_flag)
self.tap_max = read_from_net(net, self.element, self.controlled_element_index, "tap_max", self._read_write_flag)
self.tap_neutral = read_from_net(net, self.element, self.controlled_element_index, "tap_neutral", self._read_write_flag)
self.tap_step_percent = read_from_net(net, self.element, self.controlled_element_index, "tap_step_percent", self._read_write_flag)
self.tap_step_degree = read_from_net(net, self.element, self.controlled_element_index, "tap_step_degree", self._read_write_flag)
self.tap_min = read_from_net(net, self.element, self.element_index, "tap_min", self._read_write_flag)
self.tap_max = read_from_net(net, self.element, self.element_index, "tap_max", self._read_write_flag)
self.tap_neutral = read_from_net(net, self.element, self.element_index, "tap_neutral", self._read_write_flag)
self.tap_step_percent = read_from_net(net, self.element, self.element_index, "tap_step_percent", self._read_write_flag)
self.tap_step_degree = read_from_net(net, self.element, self.element_index, "tap_step_degree", self._read_write_flag)

self.tap_pos = read_from_net(net, self.element, self.controlled_element_index, "tap_pos", self._read_write_flag)
self.tap_pos = read_from_net(net, self.element, self.element_index, "tap_pos", self._read_write_flag)
if self._read_write_flag == "single_index":
self.tap_sign = 1 if np.isnan(self.tap_step_degree) else np.sign(np.cos(np.deg2rad(self.tap_step_degree)))
if (self.tap_sign == 0) | (np.isnan(self.tap_sign)):
Expand Down Expand Up @@ -163,7 +172,7 @@ def set_recycle(self, net):
net.controller.at[self.index, 'recycle'] = recycle

# def timestep(self, net):
# self.tap_pos = net[self.element].at[self.controlled_element_index, "tap_pos"]
# self.tap_pos = net[self.element].at[self.element_index, "tap_pos"]

def __repr__(self):
s = '%s of %s %s' % (self.__class__.__name__, self.element, self.element_index)
Expand Down
2 changes: 2 additions & 0 deletions pandapower/convert_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ def _convert_trafo_controller_parameter_names(net):
elif "trafotype" in controller.__dict__.keys():
controller.__dict__["element"] = controller.__dict__.pop("trafotype")

if "controlled_bus" in controller.__dict__.keys():
controller.__dict__["trafobus"] = controller.__dict__.pop("controlled_bus")

def _convert_bus_pq_meas_to_load_reference(net, elements_to_deserialize):
if _check_elements_to_deserialize('measurement', elements_to_deserialize):
Expand Down
5 changes: 5 additions & 0 deletions pandapower/test/control/test_continuous_tap_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,11 @@ def test_continuous_tap_control_side_hv_reversed_3w():
assert np.allclose(net.res_trafo3w.vm_hv_pu.values, 1.02, atol=tol)
assert not np.allclose(net.trafo3w.tap_pos.values, 0)

def test_continuous_trafo_control_with_oos_trafo():
net = pp.networks.mv_oberrhein()
# switch transformer out of service
net.trafo.loc[114, 'in_service'] = False
ContinuousTapControl(net=net, element_index=114, vm_set_pu=1.0, tol=0.001)

if __name__ == '__main__':
pytest.main([__file__, "-xs"])
5 changes: 5 additions & 0 deletions pandapower/test/control/test_discrete_tap_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,11 @@ def test_continuous_tap_control_side_mv():
assert all(_vm_in_desired_area(net, 1.01, 1.03, "mv", trafo_table="trafo3w"))
assert not np.allclose(net.trafo3w.tap_pos.values, 0)

def test_discrete_trafo_control_with_oos_trafo():
net = pp.networks.mv_oberrhein()
# switch transformer out of service
net.trafo.loc[114, 'in_service'] = False
DiscreteTapControl(net=net, element_index=114, vm_lower_pu=1.01, vm_upper_pu=1.03)

if __name__ == '__main__':
pytest.main([__file__, "-xs"])
20 changes: 10 additions & 10 deletions pandapower/test/control/test_vm_set_tap_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,37 @@ def test_continuous_p():
pp.runpp(net, run_control=True)

# we expect the tap to converge at upper voltage limit
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 1.05) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 1.05) < eps

# power sums up to 15kW
net.sgen.at[gid, "p_mw"] = 5
pp.runpp(net, run_control=True)

# we expect the tap to converge at 1.0 pu
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 1.) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 1.) < eps

# generation now cancels load
net.sgen.at[gid, "p_mw"] = 10
pp.runpp(net, run_control=True)

# we expect the tap to converge at lower voltage limit
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 0.95) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 0.95) < eps

# testing limits
# power flowing back
net.sgen.at[gid, "p_mw"] = 30
pp.runpp(net, run_control=True)

# we expect the tap to converge at lower voltage limit and not drop even lower
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 0.95) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 0.95) < eps

# excessive load
net.sgen.at[gid, "p_mw"] = 0
net.load.at[lid, "p_mw"] = 30
pp.runpp(net, run_control=True)

# we expect the tap to converge at upper voltage limit and not to go beyond
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 1.05) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 1.05) < eps


def test_continuous_i():
Expand All @@ -81,37 +81,37 @@ def test_continuous_i():
pp.runpp(net, run_control=True)

# we expect the tap to converge at upper voltage limit
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 1.05) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 1.05) < eps

# power sums up to 15kW
net.sgen.at[gid, "p_mw"] = 5
pp.runpp(net, run_control=True)

# we expect the tap to converge at 1.0 pu
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 1.) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 1.) < eps

# generation now cancels load
net.sgen.at[gid, "p_mw"] = 10
pp.runpp(net, run_control=True)

# we expect the tap to converge at lower voltage limit
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 0.95) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 0.95) < eps

# testing limits
# power flowing back
net.sgen.at[gid, "p_mw"] = 30
pp.runpp(net, run_control=True)

# we expect the tap to converge at lower voltage limit and not drop even lower
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 0.95) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 0.95) < eps

# excessive load
net.sgen.at[gid, "p_mw"] = 0
net.load.at[lid, "p_mw"] = 30
pp.runpp(net, run_control=True)

# we expect the tap to converge at upper voltage limit and not to go beyond
assert abs(net.res_bus.vm_pu.at[c.controlled_bus] - 1.05) < eps
assert abs(net.res_bus.vm_pu.at[c.trafobus] - 1.05) < eps

if __name__ == '__main__':
pytest.main([__file__, "-xs"])
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Changelog = "https://github.com/e2nIEE/pandapower/blob/develop/CHANGELOG.rst"
docs = ["numpydoc", "matplotlib", "sphinx", "sphinx_rtd_theme", "sphinx-pyproject"]
plotting = ["plotly>=3.1.1", "matplotlib", "igraph", "geopandas>=1.0"]
test = ["pytest~=8.1", "pytest-xdist", "nbmake"]
performance = ["ortools", "numba>=0.25", "lightsim2grid>=0.9.0"]
performance = ["ortools", "numba>=0.25", "lightsim2grid==0.9.0"]
fileio = ["xlsxwriter", "openpyxl", "cryptography", "geopandas", "psycopg2"]
converter = ["matpowercaseframes"]
pgm = ["power-grid-model-io"]
Expand All @@ -71,7 +71,7 @@ all = [
"numpydoc", "sphinx", "sphinx_rtd_theme", "sphinx-pyproject",
"plotly>=3.1.1", "matplotlib", "igraph", "geopandas>=1.0",
"pytest~=8.1", "pytest-xdist", "nbmake",
"ortools", "numba>=0.25", "lightsim2grid>=0.9.0",
"ortools", "numba>=0.25", "lightsim2grid==0.9.0",
"xlsxwriter", "openpyxl", "cryptography", "psycopg2",
"matpowercaseframes",
"power-grid-model-io"
Expand Down
Loading

0 comments on commit ccdb0e5

Please sign in to comment.