Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: allow passing custom runpp-function to pp.diagnostics #2193

Merged
merged 5 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Change Log
- [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
- [ADDED] allow passing custom runpp-function to pp.diagnostic

[2.13.1] - 2023-05-12
-------------------------------
Expand Down
99 changes: 60 additions & 39 deletions pandapower/diagnostic.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

logger = logging.getLogger(__name__)

from functools import partial
from pandapower.powerflow import LoadflowNotConverged
from pandapower.run import runpp
from pandapower.toolbox import get_connected_elements
Expand All @@ -27,7 +28,7 @@

def diagnostic(net, report_style='detailed', warnings_only=False, return_result_dict=True,
overload_scaling_factor=0.001, min_r_ohm=0.001, min_x_ohm=0.001, min_r_pu=1e-05,
min_x_pu=1e-05, nom_voltage_tolerance=0.3, numba_tolerance=1e-05):
min_x_pu=1e-05, nom_voltage_tolerance=0.3, numba_tolerance=1e-05, **kwargs):
"""
Tool for diagnosis of pandapower networks. Identifies possible reasons for non converging loadflows.

Expand Down Expand Up @@ -67,6 +68,9 @@ def diagnostic(net, report_style='detailed', warnings_only=False, return_result_
- **nom_voltage_tolerance** (float, 0.3): highest allowed relative deviation between nominal \
voltages and bus voltages

- **kwargs** - Keyword arguments for the power flow function to use during tests. If "run" is \
in kwargs the default call to runpp() is replaced by the function kwargs["run"]

OUTPUT:
- **diag_results** (dict): dict that contains the indices of all elements where errors were found

Expand All @@ -77,31 +81,34 @@ def diagnostic(net, report_style='detailed', warnings_only=False, return_result_
<<< pandapower.diagnostic(net, report_style='compact', warnings_only=True)

"""
diag_functions = ["missing_bus_indices(net)",
"disconnected_elements(net)",
"different_voltage_levels_connected(net)",
"impedance_values_close_to_zero(net, min_r_ohm, min_x_ohm, min_r_pu, min_x_pu)",
"nominal_voltages_dont_match(net, nom_voltage_tolerance)",
"invalid_values(net)",
"overload(net, overload_scaling_factor)",
"wrong_switch_configuration(net)",
"multiple_voltage_controlling_elements_per_bus(net)",
"no_ext_grid(net)",
"wrong_reference_system(net)",
"deviation_from_std_type(net)",
"numba_comparison(net, numba_tolerance)",
"parallel_switches(net)"]

diag_functions = [
(missing_bus_indices, {}),
(disconnected_elements, {}),
(different_voltage_levels_connected, {}),
(impedance_values_close_to_zero, {"min_r_ohm": min_r_ohm, "min_x_ohm": min_x_ohm, "min_r_pu": min_r_pu,
"min_x_pu": min_x_pu, **kwargs}),
(nominal_voltages_dont_match, {"nom_voltage_tolerance": nom_voltage_tolerance}),
(invalid_values, {}),
(overload, {"overload_scaling_factor": overload_scaling_factor, **kwargs}),
(wrong_switch_configuration, kwargs),
(multiple_voltage_controlling_elements_per_bus, {}),
(no_ext_grid, {}),
(wrong_reference_system, {}),
(deviation_from_std_type, {}),
(numba_comparison, {"numba_tolerance": numba_tolerance, **kwargs}),
(parallel_switches, {}),
]

diag_results = {}
diag_errors = {}
for diag_function in diag_functions:
for diag_function, kwargs in diag_functions:
try:
diag_result = eval(diag_function)
if not diag_result == None:
diag_results[diag_function.split("(")[0]] = diag_result
diag_result = diag_function(net, **kwargs)
if diag_result is not None:
diag_results[diag_function.__name__] = diag_result
except Exception as e:
diag_errors[diag_function.split("(")[0]] = e

diag_errors[diag_function.__name__] = e

diag_params = {
"overload_scaling_factor": overload_scaling_factor,
Expand Down Expand Up @@ -354,41 +361,46 @@ def multiple_voltage_controlling_elements_per_bus(net):
return check_results


def overload(net, overload_scaling_factor):
def overload(net, overload_scaling_factor, **kwargs):
"""
Checks, if a loadflow calculation converges. If not, checks, if an overload is the reason for
that by scaling down the loads, gens and sgens to 0.1%.

INPUT:
**net** (pandapowerNet) - pandapower network

**kwargs** - Keyword arguments for power flow function. If "run" is in kwargs the default call to runpp()
is replaced by the function kwargs["run"]


OUTPUT:
**check_results** (dict) - dict with the results of the overload check
Format: {'load_overload': True/False
'generation_overload', True/False}

"""
# get function to run power flow
run = partial(kwargs.pop("run", runpp), **kwargs)
check_result = {}
load_scaling = copy.deepcopy(net.load.scaling)
gen_scaling = copy.deepcopy(net.gen.scaling)
sgen_scaling = copy.deepcopy(net.sgen.scaling)

try:
runpp(net)
run(net)
except LoadflowNotConverged:
check_result['load'] = False
check_result['generation'] = False
try:
net.load.scaling = overload_scaling_factor
runpp(net)
run(net)
check_result['load'] = True
except:
net.load.scaling = load_scaling
try:
net.gen.scaling = overload_scaling_factor
net.sgen.scaling = overload_scaling_factor
runpp(net)
run(net)
check_result['generation'] = True
except:
net.sgen.scaling = sgen_scaling
Expand All @@ -397,7 +409,7 @@ def overload(net, overload_scaling_factor):
net.load.scaling = overload_scaling_factor
net.gen.scaling = overload_scaling_factor
net.sgen.scaling = overload_scaling_factor
runpp(net)
run(net)
check_result['generation'] = True
check_result['load'] = True
except:
Expand All @@ -409,25 +421,29 @@ def overload(net, overload_scaling_factor):
return check_result


def wrong_switch_configuration(net):
def wrong_switch_configuration(net, **kwargs):
"""
Checks, if a loadflow calculation converges. If not, checks, if the switch configuration is
the reason for that by closing all switches

INPUT:
**net** (pandapowerNet) - pandapower network

**kwargs** - Keyword arguments for power flow function. If "run" is in kwargs the default call to runpp()
is replaced by the function kwargs["run"]

OUTPUT:
**check_result** (boolean)

"""
run = partial(kwargs.pop("run", runpp), **kwargs)
switch_configuration = copy.deepcopy(net.switch.closed)
try:
runpp(net)
run(net)
except:
try:
net.switch.closed = True
runpp(net)
run(net)
net.switch.closed = switch_configuration
return True
except:
Expand Down Expand Up @@ -502,20 +518,24 @@ def different_voltage_levels_connected(net):
return check_results


def impedance_values_close_to_zero(net, min_r_ohm, min_x_ohm, min_r_pu, min_x_pu):
def impedance_values_close_to_zero(net, min_r_ohm, min_x_ohm, min_r_pu, min_x_pu, **kwargs):
"""
Checks, if there are lines, xwards or impedances with an impedance value close to zero.

INPUT:
**net** (pandapowerNet) - pandapower network

**kwargs** - Keyword arguments for power flow function. If "run" is in kwargs the default call to runpp()
is replaced by the function kwargs["run"]

OUTPUT:
**implausible_lines** (list) - list that contains the indices of all lines with an
impedance value of zero.


"""
# get function to run power flow
run = partial(kwargs.pop("run", runpp), **kwargs)
check_results = []
implausible_elements = {}

Expand All @@ -542,7 +562,7 @@ def impedance_values_close_to_zero(net, min_r_ohm, min_x_ohm, min_r_pu, min_x_pu
line_copy = copy.deepcopy(net.line)
impedance_copy = copy.deepcopy(net.impedance)
try:
runpp(net)
run(net)
except:
try:
for key in implausible_elements:
Expand All @@ -552,7 +572,7 @@ def impedance_values_close_to_zero(net, min_r_ohm, min_x_ohm, min_r_pu, min_x_pu
net[key].in_service.loc[implausible_idx] = False
for idx in implausible_idx:
pp.create_switch(net, net[key].from_bus.at[idx], net[key].to_bus.at[idx], et="b")
runpp(net)
run(net)
switch_replacement = True
except:
switch_replacement = False
Expand Down Expand Up @@ -803,24 +823,25 @@ def wrong_reference_system(net):
return check_results


def numba_comparison(net, numba_tolerance):
def numba_comparison(net, numba_tolerance, **kwargs):
"""
Compares the results of loadflows with numba=True vs. numba=False.

INPUT:
**net** (pandapowerNet) - pandapower network

**numba_tolerance** (float) - Maximum absolute deviation allowed between
numba=True/False results.
OPTIONAL:
**tol** (float, 1e-5) - Maximum absolute deviation allowed between
numba=True/False results.

**kwargs** - Keyword arguments for power flow function. If "run" is in kwargs the default call to runpp()
is replaced by the function kwargs["run"]
OUTPUT:
**check_result** (dict) - Absolute deviations between numba=True/False results.
"""
run = partial(kwargs.pop("run", runpp), **kwargs)
check_results = {}
runpp(net, numba=True)
run(net, numba=True)
result_numba_true = copy.deepcopy(net)
runpp(net, numba=False)
run(net, numba=False)
result_numba_false = copy.deepcopy(net)
res_keys = [key for key in result_numba_true.keys() if
(key in ['res_bus', 'res_ext_grid',
Expand Down
Loading