Skip to content

Commit

Permalink
Merge branch 'develop' of https://github.com/mrifraunhofer/pandapower
Browse files Browse the repository at this point in the history
…into develop

# Conflicts:
#	CHANGELOG.rst
  • Loading branch information
mrifraunhofer committed Feb 16, 2024
2 parents 88eb520 + 30d6205 commit 517884a
Show file tree
Hide file tree
Showing 12 changed files with 138 additions and 36 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ Change Log
- [FIXED] add some safeguards for TDPF to avoid numerical issues in some cases
- [FIXED] in reindex_elements: fixed index error when reindexing line_geodata

- [FIXED] avoid attaching elements as duplicates to a group where some of the elements already exist
- [ADDED] the function :code:`run_contingency` can raise a captured error if parameter :code:`raise_errors` is passed
- [FIXED] bugfix for tap dependent impedance characteristics so that not all characteristics columns are necessary
- [FIXED] in function :code:`toolbox.replace_zero_branches_with_switches`, use absolute for the parameters of impedance elements in case they are negative nonzero values

[2.13.1] - 2023-05-12
-------------------------------
Expand Down
3 changes: 2 additions & 1 deletion pandapower/build_branch.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ def _get_vk_values(trafo_df, characteristic, trafotype="2W"):
# must cast to float64 unfortunately, because numpy.vstack casts arrays to object because it doesn't know pandas.NA, np.isnan fails
all_characteristic_idx = np.vstack([get_trafo_values(
trafo_df, f"{c}_characteristic").astype(np.float64) for c in char_columns]).T
index_column = {c: i for i, c in enumerate(char_columns)}
# now we check if any trafos that have tap_dependent_impedance have all of the characteristics missing
all_missing = np.isnan(all_characteristic_idx).all(axis=1) & tap_dependent_impedance
if np.any(all_missing):
Expand All @@ -512,7 +513,7 @@ def _get_vk_values(trafo_df, characteristic, trafotype="2W"):
if use_tap_dependent_impedance and vk_var in char_columns:
vals += (_calc_tap_dependent_value(
trafo_df, tap_pos, vk_value, vk_var, tap_dependent_impedance,
characteristic, all_characteristic_idx[:, c]),)
characteristic, all_characteristic_idx[:, index_column[vk_var]]),)
else:
vals += (vk_value,)

Expand Down
3 changes: 3 additions & 0 deletions pandapower/contingency/contingency.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def run_contingency(net, nminus1_cases, pf_options=None, pf_options_nminus1=None
"""
# set up the dict for results and relevant variables
# ".get" in case the options have been set in pp.set_user_pf_options:
raise_errors = kwargs.get("raise_errors", False)
if "recycle" in kwargs: kwargs["recycle"] = False # so that we can be sure it doesn't happen
if pf_options is None: pf_options = net.user_pf_options.get("pf_options", net.user_pf_options)
if pf_options_nminus1 is None: pf_options_nminus1 = net.user_pf_options.get("pf_options_nminus1",
Expand Down Expand Up @@ -104,6 +105,8 @@ def run_contingency(net, nminus1_cases, pf_options=None, pf_options_nminus1=None
cause_element=element, cause_index=i)
except Exception as err:
logger.error(f"{element} {i} causes {err}")
if raise_errors:
raise err
finally:
net[element].at[i, 'in_service'] = True

Expand Down
26 changes: 16 additions & 10 deletions pandapower/control/controller/const_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
class ConstControl(Controller):
"""
Class representing a generic time series controller for a specified element and variable.
Control strategy: "No Control" -> updates values of specified elements according to timeseries input data.
If ConstControl is used without timeseries input data, it will reset the controlled values to the initial values,
preserving the initial net state.
The timeseries values are written to net during time_step before the initial powerflow run and before other controllers' control_step.
It is possible to set attributes of objects that are contained in a net table, e.g. attributes of other controllers. This can be helpful
Control strategy: "No Control" -> updates values of specified elements according to timeseries
input data. If ConstControl is used without timeseries input data, it will reset the controlled
values to the initial values, preserving the initial net state.
The timeseries values are written to net during time_step before the initial powerflow run and
before other controllers' control_step. It is possible to set attributes of objects that are
contained in a net table, e.g. attributes of other controllers. This can be helpful
e.g. if a voltage setpoint of a transformer tap changer depends on the time step.
An attribute of an object in the "object" column of a table (e.g. net.controller["object"] -> net.controller.object.at[0, "vm_set_pu"]
An attribute of an object in the "object" column of a
table (e.g. net.controller["object"] -> net.controller.object.at[0, "vm_set_pu"]
can be set if the attribute is specified as "object.attribute" (e.g. "object.vm_set_pu").
INPUT:
Expand Down Expand Up @@ -81,7 +83,8 @@ def __init__(self, net, element, variable, element_index, profile_name=None, dat
self.profile_name = profile_name
self.scale_factor = scale_factor
self.applied = False
self.write_flag, self.variable = _detect_read_write_flag(net, element, element_index, variable)
self.write_flag, self.variable = _detect_read_write_flag(
net, element, element_index, variable)
self.set_recycle(net)

def set_recycle(self, net):
Expand Down Expand Up @@ -109,7 +112,8 @@ def time_step(self, net, time):
"""
Get the values of the element from data source
Write to pandapower net by calling write_to_net()
If ConstControl is used without a data_source, it will reset the controlled values to the initial values,
If ConstControl is used without a data_source, it will reset the controlled values to the
initial values,
preserving the initial net state.
"""
self.applied = False
Expand All @@ -120,7 +124,8 @@ def time_step(self, net, time):
profile_name=self.profile_name,
scale_factor=self.scale_factor)
if self.values is not None:
write_to_net(net, self.element, self.element_index, self.variable, self.values, self.write_flag)
write_to_net(net, self.element, self.element_index, self.variable, self.values,
self.write_flag)

def is_converged(self, net):
"""
Expand All @@ -130,7 +135,8 @@ def is_converged(self, net):

def control_step(self, net):
"""
Set applied to True, which means that the values set in time_step have been included in the load flow calculation.
Set applied to True, which means that the values set in time_step have been included in the
load flow calculation.
"""
self.applied = True

Expand Down
22 changes: 12 additions & 10 deletions pandapower/grid_equivalents/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,16 +362,18 @@ def match_controller_and_new_elements(net, net_org):
else:
internal_buses = []
for idx in net.controller.index.tolist():
elm = net.controller.object[idx].__dict__["element"]
var = net.controller.object[idx].__dict__["variable"]
elm_idxs = net.controller.object[idx].__dict__["element_index"]
org_elm_buses = list(net_org[elm].bus[elm_idxs].values)

new_elm_idxs = net[elm].index[net[elm].bus.isin(org_elm_buses)].tolist()
et = net.controller.object[idx].__dict__.get("element")
# var = net.controller.object[idx].__dict__.get("variable")
elm_idxs = net.controller.object[idx].__dict__.get("element_index")
if et is None or elm_idxs is None:
continue
org_elm_buses = list(net_org[et].bus[elm_idxs].values)

new_elm_idxs = net[et].index[net[et].bus.isin(org_elm_buses)].tolist()
if len(new_elm_idxs) == 0:
tobe_removed.append(idx)
else:
profile_name = [org_elm_buses.index(a) for a in net[elm].bus[new_elm_idxs].values]
profile_name = [org_elm_buses.index(a) for a in net[et].bus[new_elm_idxs].values]

net.controller.object[idx].__dict__["element_index"] = new_elm_idxs
net.controller.object[idx].__dict__["matching_params"]["element_index"] = new_elm_idxs
Expand Down Expand Up @@ -466,10 +468,10 @@ def _check_network(net):
# --- check controller names
if len(net.controller):
for i in net.controller.index:
elm = net.controller.object[i].__dict__["element"]
if len(net[elm]) != len(set(net[elm].name.values)):
et = net.controller.object[i].__dict__.get("element")
if et is not None and len(net[et]) != len(set(net[et].name.values)):
raise ValueError("if controllers are used, please give a name for every "
"element ("+elm+"), and make sure the name is unique.")
"element ("+et+"), and make sure the name is unique.")


def get_boundary_vp(net_eq, bus_lookups):
Expand Down
4 changes: 4 additions & 0 deletions pandapower/grid_equivalents/get_equivalent.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,10 @@ def get_equivalent(net, eq_type, boundary_buses, internal_buses,

if return_internal:
logger.debug("Merging of internal and equivalent network begins.")
if len(kwargs.get("central_controller_types", [])):
net_internal.controller.drop([idx for idx in net_internal.controller.index if any([
isinstance(net_internal.controller.object.at[idx], central_controller_type) for
central_controller_type in kwargs["central_controller_types"]])], inplace=True)
net_eq = merge_internal_net_and_equivalent_external_net(
net_eq, net_internal, show_computing_time=show_computing_time,
calc_volt_angles=calculate_voltage_angles)
Expand Down
3 changes: 2 additions & 1 deletion pandapower/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,8 @@ def attach_to_group(net, index, element_types, elements, reference_columns=None,
prev_elm = net.group.element.loc[group_et].at[index]
prev_elm = [prev_elm] if isinstance(prev_elm, str) or not hasattr(
prev_elm, "__iter__") else list(prev_elm)
net.group.element.loc[group_et] = [prev_elm + elm]
net.group.element.loc[group_et] = [prev_elm + list(pd.Index(elm).difference(
pd.Index(prev_elm)))]

# --- prepare adding new rows to net.group (because no other elements of element type et
# --- already belong to the group)
Expand Down
2 changes: 1 addition & 1 deletion pandapower/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def runpp(net, algorithm='nr', calculate_voltage_angles=True, init="auto",
- "fdbx" fast-decoupled (pypower implementation)
- "fdxb" fast-decoupled (pypower implementation)
**calculate_voltage_angles** (str or bool, "auto") - consider voltage angles in loadflow calculation
**calculate_voltage_angles** (str or bool, True) - consider voltage angles in loadflow calculation
If True, voltage angles of ext_grids and transformer shifts are considered in the
loadflow calculation. Considering the voltage angles is only necessary in meshed
Expand Down
42 changes: 42 additions & 0 deletions pandapower/test/loadflow/test_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,48 @@ def test_undefined_tap_dependent_impedance_characteristics():
pp.runpp(net)


def test_undefined_tap_dependent_impedance_characteristics_trafo3w():
# if some characteristic per 1 trafo are undefined, but at least 1 is defined -> OK
# if all characteristic per 1 trafo are undefined -> raise error
net = create_net()
add_trafo_connection(net, 1, "3W")
add_trafo_connection(net, 1, "3W")
net2 = create_net()
add_trafo_connection(net2, 1, "3W")
add_trafo_connection(net2, 1, "3W")

pp.control.create_trafo_characteristics(net, 'trafo3w', [0, 1], 'vk_mv_percent', [[-2, -1, 0, 1, 2], [-2, -1, 0, 1, 2]], [[0.7, 0.9, 1, 1.1, 1.3], [0.7, 0.9, 1, 1.1, 1.3]])
pp.control.create_trafo_characteristics(net, 'trafo3w', [0, 1], 'vkr_mv_percent', [[-2, -1, 0, 1, 2], [-2, -1, 0, 1, 2]], [[0.3, 0.45, 0.5, 0.55, 0.7], [0.3, 0.45, 0.5, 0.55, 0.7]])

pp.control.Characteristic(net2, [-2, -1, 0, 1, 2], [0.7, 0.9, 1, 1.1, 1.3])
pp.control.Characteristic(net2, [-2, -1, 0, 1, 2], [0.3, 0.45, 0.5, 0.55, 0.7])

pp.control.TapDependentImpedance(net2, [0], 0, trafotable="trafo3w", output_variable="vk_mv_percent")
pp.control.TapDependentImpedance(net2, [0], 1, trafotable="trafo3w", output_variable="vkr_mv_percent")
pp.control.TapDependentImpedance(net2, [1], 0, trafotable="trafo3w", output_variable="vk_mv_percent")
pp.control.TapDependentImpedance(net2, [1], 1, trafotable="trafo3w", output_variable="vkr_mv_percent")

pp.runpp(net)
pp.runpp(net2, run_control=True)
assert_res_equal(net, net2)

net.trafo3w.at[0, "vk_mv_percent_characteristic"] = None
pp.runpp(net)
net2.controller.at[0, "in_service"] = False
pp.runpp(net2, run_control=True)
assert_res_equal(net, net2)

net.trafo3w.at[0, "vkr_mv_percent_characteristic"] = None
net2.controller.at[1, "in_service"] = False
with pytest.raises(UserWarning):
pp.runpp(net)

net.trafo3w.at[0, "tap_dependent_impedance"] = False
pp.runpp(net)
pp.runpp(net2, run_control=True)
assert_res_equal(net, net2)


def test_ext_grid(result_test_network, v_tol=1e-6, va_tol=1e-2, i_tol=1e-6, s_tol=5e-3, l_tol=1e-3):
net = result_test_network
runpp_with_consistency_checks(net, calculate_voltage_angles=True)
Expand Down
31 changes: 31 additions & 0 deletions pandapower/test/timeseries/test_timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,37 @@ def test_const_control(simple_test_net):
assert np.alltrue(profiles['slack_v'].values == ow.output['res_bus.vm_pu'][0].values)


def test_switch_states_in_time_series():
net = pp.create_empty_network()
pp.create_buses(net, 3, 0.4)
pp.create_ext_grid(net, 0)
pp.create_loads(net, [1, 2], 0.1)
pp.create_lines(net, [0, 0], [1, 2], 0.1, "NAYY 4x50 SE")
pp.create_switch(net, 0, 0, "l")

n_timesteps = 5
time_steps = range(n_timesteps)
profiles = pd.DataFrame()
profiles['load1'] = np.linspace(0.05, 0.1, n_timesteps)
profiles["switch_pos"] = np.random.randint(2, size=n_timesteps, dtype=bool)
ds = DFData(profiles)

ow = setup_output_writer(net, time_steps)
ow.log_variable('res_line', 'pl_mw')
ow.log_variable('res_ext_grid', 'p_mw')

ConstControl(net, 'load', 'p_mw', element_index=0, data_source=ds, profile_name='load1')
ConstControl(net, 'switch', 'closed', element_index=0, data_source=ds, profile_name='switch_pos')

run_timeseries(net, time_steps, verbose=False)

assert np.allclose(
profiles['load1'].values * profiles["switch_pos"].values + 0.1 + \
ow.output['res_line.pl_mw'].sum(axis=1).values,
ow.output['res_ext_grid.p_mw'][0].values
)


def test_const_control_write_to_object_attribute(simple_test_net):
net = simple_test_net
profiles, ds = create_data_source()
Expand Down
13 changes: 9 additions & 4 deletions pandapower/timeseries/data_sources/frame_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

# Copyright (c) 2016-2023 by University of Kassel and Fraunhofer Institute for Energy Economics
# and Energy System Technology (IEE), Kassel. All rights reserved.
from numpy import int64
import numbers
import numpy as np
from pandapower.timeseries.data_source import DataSource

try:
Expand Down Expand Up @@ -34,8 +35,8 @@ def __init__(self, df, multi=False):
self.df = df
if multi:
# casting column and index to int for multi- columns accessing
self.df.index = self.df.index.astype(int64)
self.df.columns = self.df.columns.astype(int64)
self.df.index = self.df.index.astype(np.int64)
self.df.columns = self.df.columns.astype(np.int64)

def __repr__(self):
s = "%s with %d rows and %d columns" % (
Expand All @@ -49,7 +50,11 @@ def get_time_step_value(self, time_step, profile_name, scale_factor=1.0):
res = self.df.loc[time_step, profile_name]
if hasattr(res, 'values'):
res = res.values
res = res*scale_factor
isnumber = np.issubdtype(res.dtype, np.number)
else:
isnumber = isinstance(res, numbers.Number) and not isinstance(res, bool)
if isnumber:
res = res * scale_factor
return res

def get_time_steps_len(self):
Expand Down
21 changes: 12 additions & 9 deletions pandapower/toolbox/grid_modification.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def select_subnet(net, buses, include_switch_buses=False, include_results=False,
buses_to_add.add(fb)
buses |= buses_to_add

if keep_everything_else:
if keep_everything_else: # Info: keep_everything_else might help to keep controllers but
# does not help if a part of controllers should be kept
p2 = copy.deepcopy(net)
if not include_results:
clear_result_tables(p2)
Expand Down Expand Up @@ -760,14 +761,15 @@ def drop_controllers_at_elements(net, element_type, idx=None):
idx = ensure_iterability(idx) if idx is not None else net[element_type].index
to_drop = []
for i in net.controller.index:
elm = net.controller.object[i].__dict__["element"]
elm_idx = ensure_iterability(net.controller.object[i].__dict__["element_index"])
if element_type == elm:
et = net.controller.object[i].__dict__.get("element")
elm_idx = ensure_iterability(net.controller.object[i].__dict__.get("element_index", [0.1]))
if element_type == et:
if set(elm_idx) - set(idx) == set():
to_drop.append(i)
else:
net.controller.object[i].__dict__["element_index"] = list(set(elm_idx) - set(idx))
net.controller.object[i].__dict__["matching_params"]["element_index"] = list(set(elm_idx) - set(idx))
net.controller.object[i].__dict__["matching_params"]["element_index"] = list(
set(elm_idx) - set(idx))
net.controller.drop(to_drop, inplace=True)


Expand Down Expand Up @@ -975,10 +977,11 @@ def replace_zero_branches_with_switches(net, elements=('line', 'impedance'), zer
].index.tolist())

if elm == 'impedance' and zero_impedance:
branch_zero.update(net[elm].loc[(net[elm].rft_pu <= min_rft_pu) &
(net[elm].xft_pu <= min_xft_pu) &
(net[elm].rtf_pu <= min_rtf_pu) &
(net[elm].xtf_pu <= min_xtf_pu)].index.tolist())
# using np.abs() here because the impedance parameters can have negative values e.g. after grid reduction:
branch_zero.update(net[elm].loc[(np.abs(net[elm].rft_pu) <= min_rft_pu) &
(np.abs(net[elm].xft_pu) <= min_xft_pu) &
(np.abs(net[elm].rtf_pu) <= min_rtf_pu) &
(np.abs(net[elm].xtf_pu) <= min_xtf_pu)].index.tolist())

affected_elements = set()
for b in branch_zero:
Expand Down

0 comments on commit 517884a

Please sign in to comment.