Skip to content

Commit

Permalink
Merge branch 'develop' into feat/custom_runpp_diagnostics
Browse files Browse the repository at this point in the history
  • Loading branch information
jthurner committed Mar 26, 2024
2 parents e6e9c6b + f7d0f1c commit 2b7b1a0
Show file tree
Hide file tree
Showing 57 changed files with 925 additions and 448 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/github_test_action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ jobs:
if ${{ matrix.python-version == '3.9' }}; then python -m pip install pypower; fi
if ${{ matrix.python-version != '3.9' }}; then python -m pip install numba; fi
if ${{ matrix.python-version == '3.8' || matrix.python-version == '3.10' }}; then python -m pip install lightsim2grid; fi
if ${{ matrix.python-version == '3.8' || matrix.python-version == '3.10' }}; then python -m pip install grid2op; fi
- name: Install Julia
if: ${{ matrix.python-version == '3.9' }}
run: |
Expand Down Expand Up @@ -187,6 +188,7 @@ jobs:
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
python -m pip install .["all"]
python -m pip install lightsim2grid
python -m pip install grid2op
- name: List all installed packages
run: |
pip list
Expand Down
17 changes: 16 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,24 @@ Change Log
- [ADDED] option to use a second tap changer for the trafo element
- [CHANGED] parameters of function merge_internal_net_and_equivalent_external_net()
- [FIXED] :code:`convert_format.py`: update the attributes of the characteristic objects to match the new characteristic
- [FIXED] fixed the wrong id numbers for pypower powerflow algorithms fdxb and fdbx
- [FIXED] additional arguments from mpc saved to net._options: create "_options" if it does not exist
- [CHANGED] cim2pp: extracted getting default classes, added generic setting datatypes from CGMES XMI schema

- [ADDED] function :code:`getOTDF` to obtain Outage Transfer Distribution Factors, that can be used to analyse outages using the DC approximation of the power system
- [ADDED] function :code:`outage_results_OTDF` to obtain the matrix of results for all outage scenarios, with rows as outage scenarios and columns as branch power flows in that scenario
- [FIXED] add some safeguards for TDPF to avoid numerical issues in some cases
- [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
- [ADDED] add kwargs passing of get_equivalent() to runpp_fct()
- [ADDED] auxiliary functions ets_to_element_types() and element_types_to_ets() as well as toolbox function get_connected_buses_at_switches() and extension to get_connected_switches()
- [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
- [FIXED] in :code:`reindex_elements`: fixed index error when reindexing line_geodata
- [FIXED] bug in :code:`cim2pp`: Changed zero prioritized generators with voltage controller to sgens (like PowerFactory does)
- [ADDED] cim2pp: added description fields for each asset and added BusbarSection information to nodes
- [CHANGED] cim2pp: reformat documentation for reading in files
- [CHANGED] allow providing grid_tables as a parameter to the function that downloads net from PostgreSQL
- [FIXED] compatibility with lightsim2grid after new version 0.8.0

[2.13.1] - 2023-05-12
-------------------------------
Expand Down
11 changes: 5 additions & 6 deletions doc/converter/cgmes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ The recommended way to select the CGMES-files is via the "file_list" parameter.
It accepts a folder of xml- or zip-files, a single zip-file or several zip-files as a list.
For example:

Folder of xml or zip files ::

import os
curr_xml_dir = 'example_cim\\test\\'
cgmes_files = [curr_xml_dir + x for x in os.listdir(curr_xml_dir)]

Example of a single zip file ::

cgmes_files = r'example_cim\CGMES_v2.4.15_RealGridTestConfiguration_v2.zip'
Expand All @@ -43,6 +37,11 @@ Example of several zip files ::
cgmes_files = [r'example_cim\CGMES_v2.4.15_SmallGridTestConfiguration_Boundary_v3.0.0.zip',
r'example_cim\CGMES_v2.4.15_SmallGridTestConfiguration_BaseCase_Complete_v3.0.0.zip']

Folder of xml or zip files ::

import os
curr_xml_dir = 'example_cim\\test'
cgmes_files = [curr_xml_dir + os.sep + x for x in os.listdir(curr_xml_dir)]

To start the converter, the following line is used. As a result it returns a pandapower network. ::

Expand Down
23 changes: 23 additions & 0 deletions pandapower/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,29 @@ def plural_s(number):
else:
return ""


def ets_to_element_types(ets=None):
ser = pd.Series(["bus", "line", "trafo", "trafo3w", "impedance"],
index=["b", "l", "t", "t3", "i"])
if ets is None:
return ser
elif isinstance(ets, str):
return ser.at[ets]
else:
return list(ser.loc[ets])


def element_types_to_ets(element_types=None):
ser1 = ets_to_element_types()
ser2 = pd.Series(ser1.index, index=list(ser1))
if element_types is None:
return ser2
elif isinstance(ets, str):
return ser2.at[element_types]
else:
return list(ser2.loc[element_types])


def _preserve_dtypes(df, dtypes):
for item, dtype in list(dtypes.items()):
if df.dtypes.at[item] != dtype:
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
8 changes: 6 additions & 2 deletions pandapower/contingency/contingency.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

try:
from lightsim2grid.gridmodel import init as init_ls2g
from lightsim2grid_cpp import SecurityAnalysisCPP, SolverType
from lightsim2grid.securityAnalysis import ContingencyAnalysisCPP
from lightsim2grid_cpp import SolverType

lightsim2grid_installed = True
except ImportError:
Expand Down Expand Up @@ -72,6 +73,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 +106,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 Expand Up @@ -195,7 +199,7 @@ def run_contingency_ls2g(net, nminus1_cases, contingency_evaluation_function=pp.
n_trafos_cases = len(nminus1_cases.get("trafo", {}).get("index", []))

# todo: add option for DC power flow
s = SecurityAnalysisCPP(lightsim_grid_model)
s = ContingencyAnalysisCPP(lightsim_grid_model)
s.change_solver(solver_type)

map_index = {}
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
2 changes: 1 addition & 1 deletion pandapower/converter/cim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# and Energy System Technology (IEE), Kassel. All rights reserved.
from .cim2pp import from_cim

__version__ = '3.6.8'
__version__ = '3.6.9'
64 changes: 24 additions & 40 deletions pandapower/converter/cim/cim2pp/build_pp_net.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,51 +92,37 @@ def convert_to_pp(self, convert_line_to_switch: bool = False, line_r_limit: floa
sort=False, ignore_index=True)[['rdfId', 'nominalVoltage']]

# --------- convert busses ---------
self.classes_dict['connectivityNodesCim16'].ConnectivityNodesCim16(
cimConverter=self).convert_connectivity_nodes_cim16()
self.classes_dict['ConnectivityNodesCim16'](cimConverter=self).convert_connectivity_nodes_cim16()
# --------- convert external networks ---------
self.classes_dict['externalNetworkInjectionsCim16'].ExternalNetworkInjectionsCim16(
self.classes_dict['externalNetworkInjectionsCim16'](
cimConverter=self).convert_external_network_injections_cim16()
# --------- convert lines ---------
self.classes_dict['acLineSegmentsCim16'].AcLineSegmentsCim16(
cimConverter=self).convert_ac_line_segments_cim16(convert_line_to_switch, line_r_limit, line_x_limit)
self.classes_dict['dcLineSegmentsCim16'].DcLineSegmentsCim16(
cimConverter=self).convert_dc_line_segments_cim16()
self.classes_dict['acLineSegmentsCim16'](cimConverter=self).convert_ac_line_segments_cim16(
convert_line_to_switch, line_r_limit, line_x_limit)
self.classes_dict['dcLineSegmentsCim16'](cimConverter=self).convert_dc_line_segments_cim16()
# --------- convert switches ---------
self.classes_dict['switchesCim16'].SwitchesCim16(cimConverter=self).convert_switches_cim16()
self.classes_dict['switchesCim16'](cimConverter=self).convert_switches_cim16()
# --------- convert loads ---------
self.classes_dict['energyConcumersCim16'].EnergyConsumersCim16(
cimConverter=self).convert_energy_consumers_cim16()
self.classes_dict['conformLoadsCim16'].ConformLoadsCim16(cimConverter=self).convert_conform_loads_cim16()
self.classes_dict['nonConformLoadsCim16'].NonConformLoadsCim16(
cimConverter=self).convert_non_conform_loads_cim16()
self.classes_dict['stationSuppliesCim16'].StationSuppliesCim16(
cimConverter=self).convert_station_supplies_cim16()
self.classes_dict['energyConcumersCim16'](cimConverter=self).convert_energy_consumers_cim16()
self.classes_dict['conformLoadsCim16'](cimConverter=self).convert_conform_loads_cim16()
self.classes_dict['nonConformLoadsCim16'](cimConverter=self).convert_non_conform_loads_cim16()
self.classes_dict['stationSuppliesCim16'](cimConverter=self).convert_station_supplies_cim16()
# --------- convert generators ---------
self.classes_dict['synchronousMachinesCim16'].SynchronousMachinesCim16(
cimConverter=self).convert_synchronous_machines_cim16()
self.classes_dict['asynchronousMachinesCim16'].AsynchronousMachinesCim16(
cimConverter=self).convert_asynchronous_machines_cim16()
self.classes_dict['energySourcesCim16'].EnergySourceCim16(
cimConverter=self).convert_energy_sources_cim16()
self.classes_dict['synchronousMachinesCim16'](cimConverter=self).convert_synchronous_machines_cim16()
self.classes_dict['asynchronousMachinesCim16'](cimConverter=self).convert_asynchronous_machines_cim16()
self.classes_dict['energySourcesCim16'](cimConverter=self).convert_energy_sources_cim16()
# --------- convert shunt elements ---------
self.classes_dict['linearShuntCompensatorCim16'].LinearShuntCompensatorCim16(
cimConverter=self).convert_linear_shunt_compensator_cim16()
self.classes_dict['nonLinearShuntCompensatorCim16'].NonLinearShuntCompensatorCim16(
self.classes_dict['linearShuntCompensatorCim16'](cimConverter=self).convert_linear_shunt_compensator_cim16()
self.classes_dict['nonLinearShuntCompensatorCim16'](
cimConverter=self).convert_nonlinear_shunt_compensator_cim16()
self.classes_dict['staticVarCompensatorCim16'].StaticVarCompensatorCim16(
cimConverter=self).convert_static_var_compensator_cim16()
self.classes_dict['staticVarCompensatorCim16'](cimConverter=self).convert_static_var_compensator_cim16()
# --------- convert impedance elements ---------
self.classes_dict['equivalentBranchesCim16'].EquivalentBranchesCim16(
cimConverter=self).convert_equivalent_branches_cim16()
self.classes_dict['seriesCompensatorsCim16'].SeriesCompensatorsCim16(
cimConverter=self).convert_series_compensators_cim16()
self.classes_dict['equivalentBranchesCim16'](cimConverter=self).convert_equivalent_branches_cim16()
self.classes_dict['seriesCompensatorsCim16'](cimConverter=self).convert_series_compensators_cim16()
# --------- convert extended ward and ward elements ---------
self.classes_dict['equivalentInjectionsCim16'].EquivalentInjectionsCim16(
cimConverter=self).convert_equivalent_injections_cim16()
self.classes_dict['equivalentInjectionsCim16'](cimConverter=self).convert_equivalent_injections_cim16()
# --------- convert transformers ---------
self.classes_dict['powerTransformersCim16'].PowerTransformersCim16(
cimConverter=self).convert_power_transformers_cim16()
self.classes_dict['powerTransformersCim16'](cimConverter=self).convert_power_transformers_cim16()

# create the geo coordinates
gl_or_dl = str(self.kwargs.get('use_GL_or_DL_profile', 'both')).lower()
Expand All @@ -152,8 +138,7 @@ def convert_to_pp(self, convert_line_to_switch: bool = False, line_r_limit: floa
if self.cim['gl']['Location'].index.size > 0 and self.cim['gl']['PositionPoint'].index.size > 0 and \
use_gl_profile:
try:
self.classes_dict['geoCoordinatesFromGLCim16'].GeoCoordinatesFromGLCim16(
cimConverter=self).add_geo_coordinates_from_gl_cim16()
self.classes_dict['geoCoordinatesFromGLCim16'](cimConverter=self).add_geo_coordinates_from_gl_cim16()
except Exception as e:
self.logger.warning("Creating the geo coordinates failed, returning the net without geo coordinates!")
self.logger.exception(e)
Expand All @@ -169,8 +154,8 @@ def convert_to_pp(self, convert_line_to_switch: bool = False, line_r_limit: floa
self.cim['dl']['DiagramObjectPoint'].index.size > 0 and self.net.bus_geodata.index.size == 0 and \
use_dl_profile:
try:
self.classes_dict['coordinatesFromDLCim16'].CoordinatesFromDLCim16(
cimConverter=self).add_coordinates_from_dl_cim16(diagram_name=kwargs.get('diagram_name', None))
self.classes_dict['coordinatesFromDLCim16'](cimConverter=self).add_coordinates_from_dl_cim16(
diagram_name=kwargs.get('diagram_name', None))
except Exception as e:
self.logger.warning("Creating the coordinates failed, returning the net without coordinates!")
self.logger.exception(e)
Expand All @@ -184,8 +169,7 @@ def convert_to_pp(self, convert_line_to_switch: bool = False, line_r_limit: floa
self.net = pp_tools.set_pp_col_types(net=self.net)

# create transformer tap controller
self.classes_dict['tapController'].TapController(
cimConverter=self).create_tap_controller_for_power_transformers()
self.classes_dict['tapController'](cimConverter=self).create_tap_controller_for_power_transformers()

self.logger.info("Running a power flow.")
self.report_container.add_log(Report(
Expand Down
Loading

0 comments on commit 2b7b1a0

Please sign in to comment.