Skip to content

Commit

Permalink
Merge branch 'develop' into feature/additional_patches
Browse files Browse the repository at this point in the history
  • Loading branch information
jwiemer112 authored Sep 15, 2023
2 parents 869dfef + 1437671 commit b0d9cba
Show file tree
Hide file tree
Showing 29 changed files with 3,800 additions and 909 deletions.
12 changes: 8 additions & 4 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@ Change Log
- [FIXED] some fixes and small updates at cim2pp
- [CHANGED] add numba in the dependencies for Python 3.11 for GitHub test and release actions; revise numba version checks
- [ADDED] improved documentation for short-circuit calculation (description of the function and the element results)
- [FIXED] fixed bug in :code:`pp.select_subnet` when using tap dependent impedance
- [FIXED] bug in :code:`pp.select_subnet` when using tap dependent impedance
- [ADDED] extensive unit tests for cim2pp converter (element parameter and load flow results)
- [FIXED] fixed bug in :code:`cim2pp.build_pp_net` when setting default values for converted xwards
- [FIXED] fixed bug in :code:`cim2pp.build_pp_net` when controller for gen is at TopologicalNode instead of ConnectivityNode
- [FIXED] bug in :code:`cim2pp.build_pp_net` when setting default values for converted xwards
- [FIXED] bug in :code:`cim2pp.build_pp_net` when controller for gen is at TopologicalNode instead of ConnectivityNode
- [CHANGED] adjust default iterations for runpp_3ph
- [CHANGED] always convert RATE_A to ppc in build_branch (not only when mode == 'opf' as before)
- [FIXED] in converter from PowerFactory, collect all buses (even not relevant for the calculation) for connectivity issues
- [FIXED] fixed bug in coords conversion in cim2pp, small fixes
- [FIXED] bug in coords conversion in cim2pp, small fixes
- [CHANGED] cim2pp: added support for multi diagram usage for DL profiles
- [FIXED] error handling in :code:`plotly/mapbox_plot.py` not raising :code`ImportError` if :code:`geopy` or :code:`pyproj` are missing
- [FIXED] powerfactory2pandapower-converter error if a line has two identical coordinates
- [ADDED] logger messages about the probabilistic load flow calculation (simultaneities) in the powerfactory2pandapower-converter for low voltage loads
- [ADDED] matplotlib v3.8.0 support (fixed :code:`plotting_colormaps.ipynb`)


[2.13.1] - 2023-05-12
Expand Down
1 change: 1 addition & 0 deletions doc/protection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ As of now pandapower is capable of simulating fault scenario using:
:maxdepth: 2

protection/oc_relay
protection/fuse

.. seealso::
- *Netzschutztechnik-Anlagentechnik für elektrische Verteilungsnetze* by Walter Schossig, Thomas Schossig, 2018.
Expand Down
21 changes: 21 additions & 0 deletions doc/protection/fuse.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
###################################################
Fuse
###################################################

A fuse is a protection device frequently used in low-voltage networks. When exposed to a sufficiently high current, the fuse melts and an arc forms. As the fuse continues to melt, the arc distance increases until it eventually is extinguished, and no more current flows through the fuse.

pandapower allows users to add fuses to networks and analyze their performance in short-circuit and power flow scenarios. It includes a standard library of fuses with rated currents ranging from 16A to 1000A.

Fuses can be created using the Fuse class:

.. autoclass:: pandapower.protection.protection_devices.fuse.Fuse
:members:
:class-doc-from: class

To run protection calculations, use the calculate_protection_times function:

.. autofunction:: pandapower.protection.run_protection.calculate_protection_times
:noindex:

Kindly follow the Fuse tutorial on Github for more details.
https://github.com/e2nIEE/pandapower/blob/develop/tutorials/protection/fuse.ipynb
45 changes: 11 additions & 34 deletions doc/protection/oc_relay.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,23 @@ DTOC relay that operates after a definite time once the current exceeds the pick
IDMT relay's operating time is inversely proportional to the fault current; hence higher the fault current shorter the operating time.
To obtain the appropriate operating time for all relays, a time delay mechanism is included.

The oc relay parameters are created using oc_parameters function:
OC Relays can be created using the OCRelay class

.. autofunction:: pandapower.protection.oc_relay_model.oc_parameters

Running a fault scenario with oc relay protection is carried out with run_fault_scenario_oc function:
..autoclass:: pandapower.protection.protection_devices.oc_relay.OCRelay
:members:
:class-doc-from: class

.. autofunction:: pandapower.protection.oc_relay_model.run_fault_scenario_oc
To run protection calculations, use the calculate_protection_times function:

EXAMPLE- DTOC Relay:
.. autofunction:: pandapower.protection.run_protection.calculate_protection_times

.. code:: python
import pandapower.protection.oc_relay_model as oc_protection
import pandapower.protection.example_grids as nw
net = nw.dtoc_relay_net()
relay_settings=oc_protection.oc_parameters(net,time_settings= [0.07, 0.5, 0.3], relay_type='DTOC')
trip_decisions,net_sc= oc_protection.run_fault_scenario_oc(net,sc_line_id=4,sc_location =0.5,relay_settings)
EXAMPLE- IDMT Relay:

.. code:: python
import pandapower.protection.oc_relay_model as oc_protection
import pandapower.protection.example_grids as nw
net = nw.idmt_relay_net()
relay_settings=oc_protection.oc_parameters(net,time_settings= [1,0.5], relay_type='IDMT', curve_type='standard_inverse')
trip_decisions,net_sc= oc_protection.run_fault_scenario_oc(net,sc_line_id=4,sc_location =0.5,relay_settings)
EXAMPLE- IDTOC Relay:

.. code:: python
import pandapower.protection.oc_relay_model as oc_protection
import pandapower.protection.example_grids as nw
net = nw.idtoc_relay_net()
relay_settings=oc_protection.oc_parameters(net,time_settings= [0.07, 0.5, 0.3,1, 0.5], relay_type='IDTOC',curve_type='standard_inverse' )
trip_decisions,net_sc= oc_protection.run_fault_scenario_oc(net,sc_line_id=4,sc_location =0.5,relay_settings)
Kindly follow the tutorial of the Over Current Relay (OC relay) for details:
https://github.com/e2nIEE/pandapower/blob/develop/tutorials/protection/oc_relay.ipynb
https://github.com/e2nIEE/pandapower/blob/develop/tutorials/protection/oc_relay.ipynb

Warning! Under pandapower.protection.oc_relay_model.py, oc_relay_settings(), oc_get_trip_decision(), and
run_fault_scenario_oc() are now legacy. In order to maintain compatibility with other protection devices,
please use the new OC Relay class.

.. seealso::
- *Protective Relays: Their Theory and Practice Volume One* by A. R. van. C. Warrington, 2012.
Expand Down
67 changes: 60 additions & 7 deletions pandapower/control/util/characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@

from builtins import zip
from builtins import object
import numpy as np

from numpy import interp
from scipy.interpolate import interp1d
from scipy.interpolate import interp1d, PchipInterpolator
from pandapower.io_utils import JSONSerializableClass

try:
import pandaplan.core.pplog as pplog
except ImportError:
import logging as pplog

logger = pplog.getLogger(__name__)


class Characteristic(JSONSerializableClass):
"""
Expand Down Expand Up @@ -72,6 +80,14 @@ def __init__(self, net, x_values, y_values, **kwargs):
self.y_vals = y_values
self.index = super().add_to_net(net, "characteristic")

# @property
# def x_vals(self):
# return self._x_vals
#
# @property
# def y_vals(self):
# return self._y_vals

@classmethod
def from_points(cls, net, points, **kwargs):
unzipped = list(zip(*points))
Expand Down Expand Up @@ -146,10 +162,10 @@ class SplineCharacteristic(Characteristic):
"""
json_excludes = ["self", "__class__", "_interpolator"]

def __init__(self, net, x_values, y_values, kind="quadratic", fill_value="extrapolate", **kwargs):
super().__init__(net, x_values=x_values, y_values=y_values, **kwargs)
self.fill_value = fill_value
self.kind = kind
def __init__(self, net, x_values, y_values, interpolator_kind="interp1d", **kwargs):
super().__init__(net, x_values=x_values, y_values=y_values)
self.kwargs = kwargs
self.interpolator_kind = interpolator_kind

@property
def interpolator(self):
Expand All @@ -166,8 +182,12 @@ def interpolator(self):
@interpolator.getter
def interpolator(self):
if not hasattr(self, '_interpolator'):
self._interpolator = interp1d(self.x_vals, self.y_vals, kind=self.kind, bounds_error=False,
fill_value=self.fill_value)
if self.interpolator_kind == "interp1d":
self._interpolator = default_interp1d(self.x_vals, self.y_vals, **self.kwargs)
elif self.interpolator_kind == "Pchip":
self._interpolator = PchipInterpolator(self.x_vals, self.y_vals, **self.kwargs)
else:
raise NotImplementedError(f"Interpolator {self.interpolator_kind} not implemented!")
return self._interpolator

def __call__(self, x):
Expand All @@ -182,3 +202,36 @@ def __call__(self, x):
The interpolated y-value.
"""
return self.interpolator(x)


class LogSplineCharacteristic(SplineCharacteristic):

def __init__(self, net, x_values, y_values, **kwargs):
super().__init__(net, x_values, y_values, **kwargs)

@property
def x_vals(self):
return self._x_vals

@property
def y_vals(self):
return self._y_vals

@x_vals.setter
def x_vals(self, x_values):
if np.any(x_values == 0):
logger.warning("zero-values not supported in x_values")
self._x_vals = np.log10(x_values)

@y_vals.setter
def y_vals(self, y_values):
if np.any(y_values == 0):
logger.warning("zero-values not supported in y_values")
self._y_vals = np.log10(y_values)

def __call__(self, x):
return np.power(10, self.interpolator(np.log10(x)))


def default_interp1d(x, y, kind="quadratic", bounds_error=False, fill_value="extrapolate", **kwargs):
return interp1d(x, y, kind=kind, bounds_error=bounds_error, fill_value=fill_value, **kwargs)
8 changes: 7 additions & 1 deletion pandapower/convert_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def convert_format(net, elements_to_deserialize=None):
set_data_type_of_columns_to_default(net)
_convert_objects(net, elements_to_deserialize)
correct_dtypes(net, error=False)
_add_missing_std_type_tables(net)
net.format_version = __format_version__
net.version = __version__
_restore_index_names(net)
Expand Down Expand Up @@ -103,7 +104,7 @@ def correct_dtypes(net, error):
def _convert_bus_pq_meas_to_load_reference(net, elements_to_deserialize):
if _check_elements_to_deserialize('measurement', elements_to_deserialize):
bus_pq_meas_mask = net.measurement.measurement_type.isin(["p", "q"]) & \
(net.measurement.element_type == "bus")
(net.measurement.element_type == "bus")
net.measurement.loc[bus_pq_meas_mask, "value"] *= -1


Expand Down Expand Up @@ -447,3 +448,8 @@ def _check_elements_to_deserialize(element, elements_to_deserialize):
return True
else:
return element in elements_to_deserialize


def _add_missing_std_type_tables(net):
if "fuse" not in net.std_types:
net.std_types["fuse"] = {}
4 changes: 4 additions & 0 deletions pandapower/converter/powerfactory/pf_export_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ def run_load_flow(app, scale_feeder_loads=False, load_scaling=None, gen_scaling=
# com_ldf.errlf = 0.001
# com_ldf.erreq = 0.01

if com_ldf.iopt_sim == 1:
logger.warning(f'Calculation method probabilistic loadflow of lv-loads is activated!'
f' The validation will not succeed.')
if load_scaling is not None:
logger.debug('scaling loads at %.2f' % load_scaling)
com_ldf.scLoadFac = load_scaling
Expand All @@ -201,6 +204,7 @@ def run_load_flow(app, scale_feeder_loads=False, load_scaling=None, gen_scaling=
logger.info('PowerFactory load flow settings:')
# Active power regulation
logger.info('Calculation method (AC balanced, AC unbalanced): %s' % com_ldf.iopt_net)
logger.info(f'Calculation method probabilistic loadflow of lv-loads: {com_ldf.iopt_sim}')
logger.info('Automatic tap adjustment of phase shifters: %s' % com_ldf.iPST_at)
logger.info('Consider active power limits: %s' % com_ldf.iopt_plim)
# Voltage and reactive power regulation
Expand Down
5 changes: 5 additions & 0 deletions pandapower/converter/powerfactory/pp_import_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,11 @@ def get_section_coords(coords, sec_len, start_len, scale_factor):
for i in range(len(coords) - 1):
len_i += point_len(coords[i], coords[i + 1])
logger.debug('i: %d, len_i: %.3f' % (i, len_i * scale_factor))
# catch if line has identical coords
if not len_i:
sec_coords = coords
return sec_coords

if len_i * scale_factor > start_len or abs(len_i * scale_factor - start_len) <= tol:
logger.debug('len_i>start_len: cut coords segment')
logger.debug('coords[i]: %s, coods[i+1]: %s' % (coords[i], coords[i + 1]))
Expand Down
8 changes: 7 additions & 1 deletion pandapower/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,12 @@ def create_empty_network(name="", f_hz=50., sn_mva=1, add_stdtypes=True):
("q_mvar", "f8"),
("va_degree", "f8"),
("vm_pu", "f8")],
"_empty_res_protection": [("switch_id", "f8"),
("prot_type", dtype(object)),
("trip_melt", "bool"),
("act_param", dtype(object)),
("act_param_val", "f8"),
("trip_melt_time_s", "f8")],

# internal
"_ppc": None,
Expand Down Expand Up @@ -579,7 +585,7 @@ def create_empty_network(name="", f_hz=50., sn_mva=1, add_stdtypes=True):
if add_stdtypes:
add_basic_std_types(net)
else:
net.std_types = {"line": {}, "trafo": {}, "trafo3w": {}}
net.std_types = {"line": {}, "trafo": {}, "trafo3w": {}, "fuse": {}}
for mode in ["pf", "se", "sc", "pf_3ph"]:
reset_results(net, mode)
net['user_pf_options'] = dict()
Expand Down
20 changes: 17 additions & 3 deletions pandapower/io_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from pandapower.auxiliary import _preserve_dtypes
import networkx
import numpy
from io import StringIO
import pandas as pd
from networkx.readwrite import json_graph
from numpy import ndarray, generic, equal, isnan, allclose, any as anynp
Expand Down Expand Up @@ -123,9 +124,13 @@ def to_dict_of_dfs(net, include_results=False, include_std_types=True, include_p
elif item == "std_types":
if not include_std_types:
continue
for t in net.std_types.keys(): # which are ["line", "trafo", "trafo3w"]
for t in net.std_types.keys(): # which are ["line", "trafo", "trafo3w", "fuse"]
if net.std_types[t]: # avoid empty excel sheets for std_types if empty
dodfs["%s_std_types" % t] = pd.DataFrame(net.std_types[t]).T
type_df = pd.DataFrame(net.std_types[t]).T
if t == "fuse":
for c in type_df.columns:
type_df[c] = type_df[c].apply(lambda x: str(x) if isinstance(x, list) else x)
dodfs["%s_std_types" % t] = type_df
continue
elif item == "profiles":
for t in net.profiles.keys(): # which could be e.g. "sgen", "gen", "load", ...
Expand Down Expand Up @@ -228,6 +233,11 @@ def from_dict_of_dfs(dodfs, net=None):
table.rename_axis(net[item].index.name, inplace=True)
df_to_coords(net, item, table)
elif item.endswith("_std_types"):
# when loaded from Excel, the lists in the DataFrame cells are strings -> we want to convert them back
# to lists here. There is probably a better way to deal with it.
if item.startswith("fuse"):
for c in table.columns:
table[c] = table[c].apply(lambda x: json.loads(x) if isinstance(x, str) and x.startswith("[") else x)
net["std_types"][item[:-10]] = table.T.to_dict()
continue # don't go into try..except
elif item.endswith("_profiles"):
Expand Down Expand Up @@ -506,7 +516,11 @@ def DataFrame(self):
column_name = self.d.pop('column_name', None)
column_names = self.d.pop('column_names', None)

df = pd.read_json(self.obj, precise_float=True, convert_axes=False, **self.d)
obj = self.obj
if isinstance(obj, str):
obj = StringIO(obj)

df = pd.read_json(obj, precise_float=True, convert_axes=False, **self.d)

if not df.shape[0] or self.d.get("orient", False) == "columns":
try:
Expand Down
10 changes: 8 additions & 2 deletions pandapower/plotting/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ def _create_complex_branch_collection(coords, patch_maker, size=1, infos=None, r

def create_bus_collection(net, buses=None, size=5, patch_type="circle", color=None, z=None,
cmap=None, norm=None, infofunc=None, picker=False, bus_geodata=None,
cbar_title="Bus Voltage [pu]", **kwargs):
cbar_title="Bus Voltage [pu]", clim=None, plot_colormap=True, **kwargs):
"""
Creates a matplotlib patch collection of pandapower buses.
Expand Down Expand Up @@ -392,6 +392,10 @@ def create_bus_collection(net, buses=None, size=5, patch_type="circle", color=No
If None, net["bus_geodata"] is used
**cbar_title** (str, "Bus Voltage [pu]") - colormap bar title in case of given cmap
**clim** (tuple of floats, None) - setting the norm limits for image scaling
**plot_colormap** (bool, True) - flag whether the colormap is actually drawn
**kwargs** - key word arguments are passed to the patch function
Expand All @@ -415,7 +419,7 @@ def create_bus_collection(net, buses=None, size=5, patch_type="circle", color=No
if cmap is not None:
if z is None:
z = net.res_bus.vm_pu.loc[buses]
add_cmap_to_collection(pc, cmap, norm, z, cbar_title)
add_cmap_to_collection(pc, cmap, norm, z, cbar_title, plot_colormap, clim)

return pc

Expand Down Expand Up @@ -457,6 +461,8 @@ def create_line_collection(net, lines=None, line_geodata=None, bus_geodata=None,
**clim** (tuple of floats, None) - setting the norm limits for image scaling
**plot_colormap** (bool, True) - flag whether the colormap is actually drawn
**kwargs** - key word arguments are passed to the patch function
OUTPUT:
Expand Down
Loading

0 comments on commit b0d9cba

Please sign in to comment.