e2nIEE / pandapower
1
# -*- coding: utf-8 -*-
2

3
# Copyright (c) 2016-2021 by University of Kassel and Fraunhofer Institute for Energy Economics
4
# and Energy System Technology (IEE), Kassel. All rights reserved.
5

6

7 1
import copy
8 1
import pandas as pd
9 1
import numpy as np
10 1
import pandapower as pp
11

12 1
try:
13 1
    import pplog as logging
14 1
except ImportError:
15 1
    import logging
16

17 1
logger = logging.getLogger(__name__)
18

19 1
from pandapower.run import runpp
20 1
from pandapower.diagnostic_reports import diagnostic_report
21 1
from pandapower.toolbox import get_connected_elements
22 1
from pandapower.powerflow import LoadflowNotConverged
23

24
# separator between log messages
25 1
log_message_sep = ("\n --------\n")
26

27

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

34
    INPUT:
35
     **net** (pandapowerNet) : pandapower network
36

37
    OPTIONAL:
38
     - **report_style** (string, 'detailed') : style of the report, that gets ouput in the console
39

40
      'detailled': full report with high level of additional descriptions
41

42
      'compact'  : more compact report, containing essential information only
43

44
      'None'     : no report
45

46

47
     - **warnings_only** (boolean, False): Filters logging output for warnings
48

49
      True: logging output for errors only
50

51
      False: logging output for all checks, regardless if errors were found or not
52

53

54
     - **return_result_dict** (boolean, True): returns a dictionary containing all check results
55

56
      True: returns dict with all check results
57

58
      False: no result dict
59

60
     - **overload_scaling_factor** (float, 0.001): downscaling factor for loads and generation \
61
     for overload check
62

63
     - **lines_min_length_km** (float, 0): minimum length_km allowed for lines
64

65
     - **lines_min_z_ohm** (float, 0): minimum z_ohm allowed for lines
66

67
     - **nom_voltage_tolerance** (float, 0.3): highest allowed relative deviation between nominal \
68
     voltages and bus voltages
69

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

73
      Format: {'check_name': check_results}
74

75
    EXAMPLE:
76

77
    <<< pandapower.diagnostic(net, report_style='compact', warnings_only=True)
78

79
    """
80 1
    diag_functions = ["missing_bus_indices(net)",
81
                      "disconnected_elements(net)",
82
                      "different_voltage_levels_connected(net)",
83
                      "impedance_values_close_to_zero(net, min_r_ohm, min_x_ohm, min_r_pu, min_x_pu)",
84
                      "nominal_voltages_dont_match(net, nom_voltage_tolerance)",
85
                      "invalid_values(net)",
86
                      "overload(net, overload_scaling_factor)",
87
                      "wrong_switch_configuration(net)",
88
                      "multiple_voltage_controlling_elements_per_bus(net)",
89
                      "no_ext_grid(net)",
90
                      "wrong_reference_system(net)",
91
                      "deviation_from_std_type(net)",
92
                      "numba_comparison(net, numba_tolerance)",
93
                      "parallel_switches(net)"]
94

95 1
    diag_results = {}
96 1
    diag_errors = {}
97 1
    for diag_function in diag_functions:
98 1
        try:
99 1
            diag_result = eval(diag_function)
100 1
            if not diag_result == None:
101 1
                diag_results[diag_function.split("(")[0]] = diag_result
102 1
        except Exception as e:
103 1
            diag_errors[diag_function.split("(")[0]] = e
104

105

106 1
    diag_params = {
107
        "overload_scaling_factor": overload_scaling_factor,
108
        "min_r_ohm": min_r_ohm,
109
        "min_x_ohm": min_x_ohm,
110
        "min_r_pu": min_r_pu,
111
        "min_x_pu": min_x_pu,
112
        "nom_voltage_tolerance": nom_voltage_tolerance,
113
        "numba_tolerance": numba_tolerance
114
    }
115

116 1
    if report_style == 'detailed':
117 0
        diagnostic_report(net, diag_results, diag_errors, diag_params, compact_report=False,
118
                          warnings_only=warnings_only)
119 1
    elif report_style == 'compact':
120 0
        diagnostic_report(net, diag_results, diag_errors, diag_params, compact_report=True,
121
                          warnings_only=warnings_only)
122 1
    if return_result_dict:
123 1
        return diag_results
124

125

126 1
def check_greater_zero(element, element_index, column):
127
    """
128
     functions that check, if a certain input type restriction for attribute values of a pandapower
129
     elements are fulfilled. Exemplary description for all type check functions.
130

131
     INPUT:
132
        **element (pandas.Series)** - pandapower element instance (e.g. net.bus.loc[1])
133

134
        **element_index (int)**     - index of the element instance
135

136
        **column (string)**         - element attribute (e.g. 'vn_kv')
137

138

139
     OUTPUT:
140
        **element_index (index)**   - index of element instance, if input type restriction is not
141
                                      fulfilled
142

143

144
    """
145

146 1
    if check_number(element, element_index, column) is None:
147

148 1
        if (element[column] <= 0):
149 1
            return element_index
150

151
    else:
152 1
        return element_index
153

154

155 1
def check_greater_equal_zero(element, element_index, column):
156 1
    if check_number(element, element_index, column) is None:
157

158 1
        if (element[column] < 0):
159 1
            return element_index
160

161
    else:
162 1
        return element_index
163

164

165 1
def check_less_zero(element, element_index, column):
166 0
    if check_number(element, element_index, column) is None:
167

168 0
        if (element[column] >= 0):
169 0
            return element_index
170

171
    else:
172 0
        return element_index
173

174

175 1
def check_less_equal_zero(element, element_index, column):
176 0
    if check_number(element, element_index, column) is None:
177

178 0
        if (element[column] > 0):
179 0
            return element_index
180

181
    else:
182 0
        return element_index
183

184

185 1
def check_boolean(element, element_index, column):
186 1
    valid_values = [True, False, 0, 1, 0.0, 1.0]
187 1
    if element[column] not in valid_values:
188 1
        return element_index
189

190

191 1
def check_pos_int(element, element_index, column):
192 1
    if check_number(element, element_index, column) is None:
193 1
        if not ((element[column] % 1 == 0) and element[column] >= 0):
194 1
            return element_index
195

196
    else:
197 1
        return element_index
198

199

200 1
def check_number(element, element_index, column):
201 1
    try:
202 1
        nan_check = np.isnan(element[column])
203 1
        if nan_check or isinstance(element[column], bool):
204 1
            return element_index
205 1
    except TypeError:
206 1
        return element_index
207

208

209 1
def check_greater_zero_less_equal_one(element, element_index, column):
210 1
    if check_number(element, element_index, column) is None:
211

212 1
        if not (0 < element[column] <= 1):
213 1
            return element_index
214

215
    else:
216 0
        return element_index
217

218

219 1
def check_switch_type(element, element_index, column):
220 1
    valid_values = ['b', 'l', 't', 't3']
221 1
    if element[column] not in valid_values:
222 1
        return element_index
223

224

225 1
def invalid_values(net):
226
    """
227
    Applies type check functions to find violations of input type restrictions.
228

229
     INPUT:
230
        **net** (pandapowerNet)         - pandapower network
231

232
        **detailed_report** (boolean)   - True: detailed report of input type restriction violations
233
                                          False: summary only
234

235
     OUTPUT:
236
        **check_results** (dict)        - dict that contains all input type restriction violations
237
                                          grouped by element (keys)
238
                                          Format: {'element': [element_index, 'element_attribute',
239
                                                    attribute_value]}
240

241
    """
242

243 1
    check_results = {}
244

245
    # Contains all element attributes that are necessary to initiate a power flow calculation.
246
    # There's a tuple with the structure (attribute_name, input type restriction)
247
    # for each attribute according to pandapower data structure documantation
248
    # (see also type_checks function)
249

250 1
    important_values = {'bus': [('vn_kv', '>0'), ('in_service', 'boolean')],
251
                        'line': [('from_bus', 'positive_integer'),
252
                                 ('to_bus', 'positive_integer'),
253
                                 ('length_km', '>0'), ('r_ohm_per_km', '>=0'),
254
                                 ('x_ohm_per_km', '>=0'), ('c_nf_per_km', '>=0'),
255
                                 ('max_i_ka', '>0'), ('df', '0<x<=1'), ('in_service', 'boolean')],
256
                        'trafo': [('hv_bus', 'positive_integer'), ('lv_bus', 'positive_integer'),
257
                                  ('sn_mva', '>0'), ('vn_hv_kv', '>0'), ('vn_lv_kv', '>0'),
258
                                  ('vkr_percent', '>=0'),
259
                                  ('vk_percent', '>0'), ('pfe_kw', '>=0'), ('i0_percent', '>=0'),
260
                                  ('in_service', 'boolean')],
261
                        'trafo3w': [('hv_bus', 'positive_integer'), ('mv_bus', 'positive_integer'),
262
                                    ('lv_bus', 'positive_integer'),
263
                                    ('sn_hv_mva', '>0'), ('sn_mv_mva', '>0'), ('sn_lv_mva', '>0'),
264
                                    ('vn_hv_kv', '>0'), ('vn_mv_kv', '>0'), ('vn_lv_kv', '>0'),
265
                                    ('vkr_hv_percent', '>=0'), ('vkr_mv_percent', '>=0'),
266
                                    ('vkr_lv_percent', '>=0'), ('vk_hv_percent', '>0'),
267
                                    ('vk_mv_percent', '>0'), ('vk_lv_percent', '>0'),
268
                                    ('pfe_kw', '>=0'), ('i0_percent', '>=0'),
269
                                    ('in_service', 'boolean')],
270
                        'load': [('bus', 'positive_integer'), ('p_mw', 'number'),
271
                                 ('q_mvar', 'number'),
272
                                 ('scaling', '>=0'), ('in_service', 'boolean')],
273
                        'sgen': [('bus', 'positive_integer'), ('p_mw', 'number'),
274
                                 ('q_mvar', 'number'),
275
                                 ('scaling', '>=0'), ('in_service', 'boolean')],
276
                        'gen': [('bus', 'positive_integer'), ('p_mw', 'number'),
277
                                ('scaling', '>=0'), ('in_service', 'boolean')],
278
                        'ext_grid': [('bus', 'positive_integer'), ('vm_pu', '>0'),
279
                                     ('va_degree', 'number')],
280
                        'switch': [('bus', 'positive_integer'), ('element', 'positive_integer'),
281
                                   ('et', 'switch_type'), ('closed', 'boolean')]}
282

283
    # matches a check function to each single input type restriction
284 1
    type_checks = {'>0': check_greater_zero,
285
                   '>=0': check_greater_equal_zero,
286
                   '<0': check_less_zero,
287
                   '<=0': check_less_equal_zero,
288
                   'boolean': check_boolean,
289
                   'positive_integer': check_pos_int,
290
                   'number': check_number,
291
                   '0<x<=1': check_greater_zero_less_equal_one,
292
                   'switch_type': check_switch_type
293
                   }
294

295 1
    for key in important_values:
296 1
        if len(net[key]) > 0:
297 1
            for value in important_values[key]:
298 1
                for i, element in net[key].iterrows():
299 1
                    check_result = type_checks[value[1]](element, i, value[0])
300 1
                    if check_result is not None:
301 1
                        if key not in check_results:
302 1
                            check_results[key] = []
303
                        # converts np.nan to str for easier usage of assert in pytest
304 1
                        nan_check = pd.isnull(net[key][value[0]].at[i])
305 1
                        if nan_check:
306 1
                            check_results[key].append((i, value[0],
307
                                                       str(net[key][value[0]].at[i]), value[1]))
308
                        else:
309 1
                            check_results[key].append((i, value[0],
310
                                                       net[key][value[0]].at[i], value[1]))
311 1
    if check_results:
312 1
        return check_results
313

314

315 1
def no_ext_grid(net):
316
    """
317
    Checks, if at least one external grid exists.
318

319
     INPUT:
320
        **net** (pandapowerNet)         - pandapower network
321

322
    """
323

324 1
    if net.ext_grid.in_service.sum() + (net.gen.slack & net.gen.in_service).sum() == 0:
325 1
        return True
326

327

328 1
def multiple_voltage_controlling_elements_per_bus(net):
329
    """
330
    Checks, if there are buses with more than one generator and/or more than one external grid.
331

332
     INPUT:
333
        **net** (pandapowerNet)         - pandapower network
334

335
        **detailed_report** (boolean)   - True: detailed report of errors found
336
                                      l    False: summary only
337

338
     OUTPUT:
339
        **check_results** (dict)        - dict that contains all buses with multiple generator and
340
                                          all buses with multiple external grids
341
                                          Format: {'mult_ext_grids': [buses]
342
                                                   'buses_with_mult_gens', [buses]}
343

344
    """
345 1
    check_results = {}
346 1
    buses_with_mult_ext_grids = list(net.ext_grid.groupby("bus").count().query("vm_pu > 1").index)
347 1
    if buses_with_mult_ext_grids:
348 1
        check_results['buses_with_mult_ext_grids'] = buses_with_mult_ext_grids
349 1
    buses_with_gens_and_ext_grids = set(net.ext_grid.bus).intersection(set(net.gen.bus))
350 1
    if buses_with_gens_and_ext_grids:
351 1
        check_results['buses_with_gens_and_ext_grids'] = list(buses_with_gens_and_ext_grids)
352

353 1
    if check_results:
354 1
        return check_results
355

356

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

362
     INPUT:
363
        **net** (pandapowerNet)         - pandapower network
364

365

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

371
    """
372 1
    check_result = {}
373 1
    load_scaling = copy.deepcopy(net.load.scaling)
374 1
    gen_scaling = copy.deepcopy(net.gen.scaling)
375 1
    sgen_scaling = copy.deepcopy(net.sgen.scaling)
376

377 1
    try:
378 1
        runpp(net)
379 1
    except LoadflowNotConverged:
380 1
        check_result['load'] = False
381 1
        check_result['generation'] = False
382 1
        try:
383 1
            net.load.scaling = overload_scaling_factor
384 1
            runpp(net)
385 1
            check_result['load'] = True
386 1
        except:
387 1
            net.load.scaling = load_scaling
388 1
            try:
389 1
                net.gen.scaling = overload_scaling_factor
390 1
                net.sgen.scaling = overload_scaling_factor
391 1
                runpp(net)
392 1
                check_result['generation'] = True
393 1
            except:
394 1
                net.sgen.scaling = sgen_scaling
395 1
                net.gen.scaling = gen_scaling
396 1
                try:
397 1
                    net.load.scaling = overload_scaling_factor
398 1
                    net.gen.scaling = overload_scaling_factor
399 1
                    net.sgen.scaling = overload_scaling_factor
400 1
                    runpp(net)
401 1
                    check_result['generation'] = True
402 1
                    check_result['load'] = True
403 0
                except:
404 0
                    pass
405 1
        net.sgen.scaling = sgen_scaling
406 1
        net.gen.scaling = gen_scaling
407 1
        net.load.scaling = load_scaling
408 1
    if check_result:
409 1
        return check_result
410

411

412 1
def wrong_switch_configuration(net):
413
    """
414
    Checks, if a loadflow calculation converges. If not, checks, if the switch configuration is
415
    the reason for that by closing all switches
416

417
     INPUT:
418
        **net** (pandapowerNet)         - pandapower network
419

420
     OUTPUT:
421
        **check_result** (boolean)
422

423
    """
424 1
    switch_configuration = copy.deepcopy(net.switch.closed)
425 1
    try:
426 1
        runpp(net)
427 1
    except:
428 1
        try:
429 1
            net.switch.closed = True
430 1
            runpp(net)
431 0
            net.switch.closed = switch_configuration
432 0
            return True
433 1
        except:
434 1
            net.switch.closed = switch_configuration
435 1
            return False
436

437

438 1
def missing_bus_indices(net):
439
    """
440
        Checks for missing bus indices.
441

442
         INPUT:
443
            **net** (PandapowerNet)    - pandapower network
444

445

446
         OUTPUT:
447
            **check_results** (list)   - List of tuples each containing missing bus indices.
448
                                         Format:
449
                                         [(element_index, bus_name (e.g. "from_bus"),  bus_index]
450

451
    """
452 1
    check_results = {}
453 1
    bus_indices = set(net.bus.index)
454 1
    element_bus_names = {"ext_grid": ["bus"], "load": ["bus"], "gen": ["bus"], "sgen": ["bus"],
455
                         "trafo": ["lv_bus", "hv_bus"], "trafo3w": ["lv_bus", "mv_bus", "hv_bus"],
456
                         "switch": ["bus", "element"], "line": ["from_bus", "to_bus"]}
457 1
    for element in element_bus_names.keys():
458 1
        element_check = []
459 1
        for i, row in net[element].iterrows():
460 1
            for bus_name in element_bus_names[element]:
461 1
                if row[bus_name] not in bus_indices:
462 1
                    if not ((element == "switch") and (bus_name == "element") and (row.et in ['l', 't', 't3'])):
463 1
                        element_check.append((i, bus_name, row[bus_name]))
464 1
        if element_check:
465 1
            check_results[element] = element_check
466

467 1
    if check_results:
468 1
        return check_results
469

470

471 1
def different_voltage_levels_connected(net):
472
    """
473
    Checks, if there are lines or switches that connect different voltage levels.
474

475
     INPUT:
476
        **net** (pandapowerNet)         - pandapower network
477

478
     OUTPUT:
479
        **check_results** (dict)        - dict that contains all lines and switches that connect
480
                                          different voltage levels.
481
                                          Format: {'lines': lines, 'switches': switches}
482

483
    """
484 1
    check_results = {}
485 1
    inconsistent_lines = []
486 1
    for i, line in net.line.iterrows():
487 1
        buses = net.bus.loc[[line.from_bus, line.to_bus]]
488 1
        if buses.vn_kv.iloc[0] != buses.vn_kv.iloc[1]:
489 1
            inconsistent_lines.append(i)
490

491 1
    inconsistent_switches = []
492 1
    for i, switch in net.switch[net.switch.et == "b"].iterrows():
493 1
        buses = net.bus.loc[[switch.bus, switch.element]]
494 1
        if buses.vn_kv.iloc[0] != buses.vn_kv.iloc[1]:
495 1
            inconsistent_switches.append(i)
496

497 1
    if inconsistent_lines:
498 1
        check_results['lines'] = inconsistent_lines
499 1
    if inconsistent_switches:
500 1
        check_results['switches'] = inconsistent_switches
501 1
    if check_results:
502 1
        return check_results
503

504

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

509
     INPUT:
510
        **net** (pandapowerNet)         - pandapower network
511

512

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

517

518
    """
519 1
    check_results = []
520 1
    implausible_elements = {}
521

522 1
    line = net.line[((net.line.r_ohm_per_km * net.line.length_km) <= min_r_ohm)
523
                    | ((net.line.x_ohm_per_km * net.line.length_km) <= min_x_ohm) & net.line.in_service].index
524

525 1
    xward = net.xward[(net.xward.r_ohm <= min_r_ohm)
526
                      | (net.xward.x_ohm <= min_x_ohm) & net.xward.in_service].index
527

528 1
    impedance = net.impedance[(net.impedance.rft_pu <= min_r_pu)
529
                              | (net.impedance.xft_pu <= min_x_pu)
530
                              | (net.impedance.rtf_pu <= min_r_pu)
531
                              | (net.impedance.xtf_pu <= min_x_pu) & net.impedance.in_service].index
532 1
    if len(line) > 0:
533 1
        implausible_elements['line'] = list(line)
534 1
    if len(xward) > 0:
535 1
        implausible_elements['xward'] = list(xward)
536 1
    if len(impedance) > 0:
537 1
        implausible_elements['impedance'] = list(impedance)
538 1
    check_results.append(implausible_elements)
539
    # checks if loadflow converges when implausible lines or impedances are replaced by switches
540 1
    if ("line" in implausible_elements) or ("impedance" in implausible_elements):
541 1
        switch_copy = copy.deepcopy(net.switch)
542 1
        line_copy = copy.deepcopy(net.line)
543 1
        impedance_copy = copy.deepcopy(net.impedance)
544 1
        try:
545 1
            runpp(net)
546 1
        except:
547 1
            try:
548 1
                for key in implausible_elements:
549 1
                    if key == 'xward':
550 0
                        continue
551 1
                    implausible_idx = implausible_elements[key]
552 1
                    net[key].in_service.loc[implausible_idx] = False
553 1
                    for idx in implausible_idx:
554 1
                        pp.create_switch(net, net[key].from_bus.at[idx], net[key].to_bus.at[idx], et="b")
555 1
                runpp(net)
556 1
                switch_replacement = True
557 0
            except:
558 0
                switch_replacement = False
559 1
            check_results.append({"loadflow_converges_with_switch_replacement": switch_replacement})
560 1
        net.switch = switch_copy
561 1
        net.line = line_copy
562 1
        net.impedance = impedance_copy
563 1
    if implausible_elements:
564 1
        return check_results
565

566

567 1
def nominal_voltages_dont_match(net, nom_voltage_tolerance):
568
    """
569
    Checks, if there are components whose nominal voltages differ from the nominal voltages of the
570
    buses they're connected to. At the moment, only trafos and trafo3w are checked.
571
    Also checks for trafos with swapped hv and lv connectors.
572

573
     INPUT:
574
        **net** (pandapowerNet)         - pandapower network
575

576
     OUTPUT:
577
        **check_results** (dict)        - dict that contains all components whose nominal voltages
578
                                          differ from the nominal voltages of the buses they're
579
                                          connected to.
580

581
                                          Format:
582

583
                                          {trafo': {'hv_bus' : trafos_indices,
584
                                                    'lv_bus' : trafo_indices,
585
                                                    'hv_lv_swapped' : trafo_indices},
586
                                           trafo3w': {'hv_bus' : trafos3w_indices,
587
                                                      'mv_bus' : trafos3w_indices
588
                                                      'lv_bus' : trafo3w_indices,
589
                                                      'connectors_swapped_3w' : trafo3w_indices}}
590

591
    """
592 1
    results = {}
593 1
    trafo_results = {}
594 1
    trafo3w_results = {}
595

596 1
    hv_bus = []
597 1
    lv_bus = []
598 1
    hv_lv_swapped = []
599

600 1
    hv_bus_3w = []
601 1
    mv_bus_3w = []
602 1
    lv_bus_3w = []
603 1
    connectors_swapped_3w = []
604

605 1
    for i, trafo in net.trafo.iterrows():
606 1
        hv_bus_violation = False
607 1
        lv_bus_violation = False
608 1
        connectors_swapped = False
609 1
        hv_bus_vn_kv = net.bus.vn_kv.at[trafo.hv_bus]
610 1
        lv_bus_vn_kv = net.bus.vn_kv.at[trafo.lv_bus]
611

612 1
        if abs(1 - (trafo.vn_hv_kv / hv_bus_vn_kv)) > nom_voltage_tolerance:
613 1
            hv_bus_violation = True
614 1
        if abs(1 - (trafo.vn_lv_kv / lv_bus_vn_kv)) > nom_voltage_tolerance:
615 1
            lv_bus_violation = True
616 1
        if hv_bus_violation and lv_bus_violation:
617 1
            trafo_voltages = np.array(([trafo.vn_hv_kv, trafo.vn_lv_kv]))
618 1
            bus_voltages = np.array([hv_bus_vn_kv, lv_bus_vn_kv])
619 1
            trafo_voltages.sort()
620 1
            bus_voltages.sort()
621 1
            if all((abs(trafo_voltages - bus_voltages) / bus_voltages) < (nom_voltage_tolerance)):
622 1
                connectors_swapped = True
623

624 1
        if connectors_swapped:
625 1
            hv_lv_swapped.append(i)
626
        else:
627 1
            if hv_bus_violation:
628 1
                hv_bus.append(i)
629 1
            if lv_bus_violation:
630 1
                lv_bus.append(i)
631

632 1
    if hv_bus:
633 1
        trafo_results['hv_bus'] = hv_bus
634 1
    if lv_bus:
635 1
        trafo_results['lv_bus'] = lv_bus
636 1
    if hv_lv_swapped:
637 1
        trafo_results['hv_lv_swapped'] = hv_lv_swapped
638 1
    if trafo_results:
639 1
        results['trafo'] = trafo_results
640

641 1
    for i, trafo3w in net.trafo3w.iterrows():
642 1
        hv_bus_violation = False
643 1
        mv_bus_violation = False
644 1
        lv_bus_violation = False
645 1
        connectors_swapped = False
646 1
        hv_bus_vn_kv = net.bus.vn_kv.at[trafo3w.hv_bus]
647 1
        mv_bus_vn_kv = net.bus.vn_kv.at[trafo3w.mv_bus]
648 1
        lv_bus_vn_kv = net.bus.vn_kv.at[trafo3w.lv_bus]
649

650 1
        if abs(1 - (trafo3w.vn_hv_kv / hv_bus_vn_kv)) > nom_voltage_tolerance:
651 1
            hv_bus_violation = True
652 1
        if abs(1 - (trafo3w.vn_mv_kv / mv_bus_vn_kv)) > nom_voltage_tolerance:
653 1
            mv_bus_violation = True
654 1
        if abs(1 - (trafo3w.vn_lv_kv / lv_bus_vn_kv)) > nom_voltage_tolerance:
655 1
            lv_bus_violation = True
656 1
        if hv_bus_violation and mv_bus_violation and lv_bus_violation:
657 1
            trafo_voltages = np.array(([trafo3w.vn_hv_kv, trafo3w.vn_mv_kv, trafo3w.vn_lv_kv]))
658 1
            bus_voltages = np.array([hv_bus_vn_kv, mv_bus_vn_kv, lv_bus_vn_kv])
659 1
            trafo_voltages.sort()
660 1
            bus_voltages.sort()
661 1
            if all((abs(trafo_voltages - bus_voltages) / bus_voltages) < (nom_voltage_tolerance)):
662 1
                connectors_swapped = True
663

664 1
        if connectors_swapped:
665 1
            connectors_swapped_3w.append(i)
666
        else:
667 1
            if hv_bus_violation:
668 1
                hv_bus_3w.append(i)
669 1
            if mv_bus_violation:
670 1
                mv_bus_3w.append(i)
671 1
            if lv_bus_violation:
672 1
                lv_bus_3w.append(i)
673

674 1
    if hv_bus_3w:
675 1
        trafo3w_results['hv_bus'] = hv_bus_3w
676 1
    if mv_bus_3w:
677 1
        trafo3w_results['mv_bus'] = mv_bus_3w
678 1
    if lv_bus_3w:
679 1
        trafo3w_results['lv_bus'] = lv_bus_3w
680 1
    if connectors_swapped_3w:
681 1
        trafo3w_results['connectors_swapped_3w'] = connectors_swapped_3w
682 1
    if trafo3w_results:
683 1
        results['trafo3w'] = trafo3w_results
684

685 1
    if len(results) > 0:
686 1
        return results
687

688

689 1
def disconnected_elements(net):
690
    """
691
    Checks, if there are network sections without a connection to an ext_grid. Returns all network
692
    elements in these sections, that are in service. Elements belonging to the same disconnected
693
    networks section are grouped in lists (e.g. disconnected lines: [[1, 2, 3], [4, 5]]
694
    means, that lines 1, 2 and 3 are in one disconncted section but are connected to each other.
695
    The same stands for lines 4, 5.)
696

697
     INPUT:
698
        **net** (pandapowerNet)         - pandapower network
699

700
     OUTPUT:
701
        **disc_elements** (dict)        - list that contains all network elements, without a
702
                                          connection to an ext_grid.
703

704
                                          format: {'disconnected buses'   : bus_indices,
705
                                                   'disconnected switches' : switch_indices,
706
                                                   'disconnected lines'    : line_indices,
707
                                                   'disconnected trafos'   : trafo_indices
708
                                                   'disconnected loads'    : load_indices,
709
                                                   'disconnected gens'     : gen_indices,
710
                                                   'disconnected sgens'    : sgen_indices}
711

712
    """
713 1
    import pandapower.topology as top
714 1
    mg = top.create_nxgraph(net)
715 1
    sections = top.connected_components(mg)
716 1
    disc_elements = []
717

718 1
    for section in sections:
719 1
        section_dict = {}
720

721 1
        if not section & set(net.ext_grid.bus[net.ext_grid.in_service]).union(
722
                net.gen.bus[net.gen.slack & net.gen.in_service]) and any(
723
                net.bus.in_service.loc[section]):
724 1
            section_buses = list(net.bus[net.bus.index.isin(section)
725
                                         & (net.bus.in_service == True)].index)
726 1
            section_switches = list(net.switch[net.switch.bus.isin(section_buses)].index)
727 1
            section_lines = list(get_connected_elements(net, 'line', section_buses,
728
                                                        respect_switches=True,
729
                                                        respect_in_service=True))
730 1
            section_trafos = list(get_connected_elements(net, 'trafo', section_buses,
731
                                                         respect_switches=True,
732
                                                         respect_in_service=True))
733

734 1
            section_trafos3w = list(get_connected_elements(net, 'trafo3w', section_buses,
735
                                                           respect_switches=True,
736
                                                           respect_in_service=True))
737 1
            section_gens = list(net.gen[net.gen.bus.isin(section)
738
                                        & (net.gen.in_service == True)].index)
739 1
            section_sgens = list(net.sgen[net.sgen.bus.isin(section)
740
                                          & (net.sgen.in_service == True)].index)
741 1
            section_loads = list(net.load[net.load.bus.isin(section)
742
                                          & (net.load.in_service == True)].index)
743

744 1
            if section_buses:
745 1
                section_dict['buses'] = section_buses
746 1
            if section_switches:
747 1
                section_dict['switches'] = section_switches
748 1
            if section_lines:
749 1
                section_dict['lines'] = section_lines
750 1
            if section_trafos:
751 0
                section_dict['trafos'] = section_trafos
752 1
            if section_trafos3w:
753 1
                section_dict['trafos3w'] = section_trafos3w
754 1
            if section_loads:
755 1
                section_dict['loads'] = section_loads
756 1
            if section_gens:
757 0
                section_dict['gens'] = section_gens
758 1
            if section_sgens:
759 1
                section_dict['sgens'] = section_sgens
760

761 1
            if any(section_dict.values()):
762 1
                disc_elements.append(section_dict)
763

764 1
    open_trafo_switches = net.switch[(net.switch.et == 't') & (net.switch.closed == 0)]
765 1
    isolated_trafos = set(
766
        (open_trafo_switches.groupby("element").count().query("bus > 1").index))
767 1
    isolated_trafos_is = isolated_trafos.intersection((set(net.trafo[net.trafo.in_service == True]
768
                                                           .index)))
769 1
    if isolated_trafos_is:
770 0
        disc_elements.append({'isolated_trafos': list(isolated_trafos_is)})
771

772 1
    isolated_trafos3w = set(
773
        (open_trafo_switches.groupby("element").count().query("bus > 2").index))
774 1
    isolated_trafos3w_is = isolated_trafos3w.intersection((
775
        set(net.trafo[net.trafo.in_service == True].index)))
776 1
    if isolated_trafos3w_is:
777 0
        disc_elements.append({'isolated_trafos3w': list(isolated_trafos3w_is)})
778

779 1
    if disc_elements:
780 1
        return disc_elements
781

782

783 1
def wrong_reference_system(net):
784
    """
785
    Checks usage of wrong reference system for loads, sgens and gens.
786

787
     INPUT:
788
        **net** (pandapowerNet)    - pandapower network
789

790
     OUTPUT:
791
        **check_results** (dict)        - dict that contains the indices of all components where the
792
                                          usage of the wrong reference system was found.
793

794
                                          Format: {'element_type': element_indices}
795

796
    """
797 1
    check_results = {}
798 1
    neg_loads = list(net.load[net.load.p_mw < 0].index)
799 1
    neg_gens = list(net.gen[net.gen.p_mw < 0].index)
800 1
    neg_sgens = list(net.sgen[net.sgen.p_mw < 0].index)
801

802 1
    if neg_loads:
803 1
        check_results['loads'] = neg_loads
804 1
    if neg_gens:
805 1
        check_results['gens'] = neg_gens
806 1
    if neg_sgens:
807 1
        check_results['sgens'] = neg_sgens
808

809 1
    if check_results:
810 1
        return check_results
811

812

813 1
def numba_comparison(net, numba_tolerance):
814
    """
815
        Compares the results of loadflows with numba=True vs. numba=False.
816

817
         INPUT:
818
            **net** (pandapowerNet)    - pandapower network
819

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

824
         OUTPUT:
825
            **check_result** (dict)    - Absolute deviations between numba=True/False results.
826
    """
827 1
    check_results = {}
828 1
    runpp(net, numba=True)
829 1
    result_numba_true = copy.deepcopy(net)
830 1
    runpp(net, numba=False)
831 1
    result_numba_false = copy.deepcopy(net)
832 1
    res_keys = [key for key in result_numba_true.keys() if
833
                (key in ['res_bus', 'res_ext_grid',
834
                         'res_gen', 'res_impedance',
835
                         'res_line', 'res_load',
836
                         'res_sgen', 'res_shunt',
837
                         'res_trafo', 'res_trafo3w',
838
                         'res_ward', 'res_xward'])]
839 1
    for key in res_keys:
840 1
        diffs = abs(result_numba_true[key] - result_numba_false[key]) > numba_tolerance
841 1
        if any(diffs.any()):
842 1
            if (key not in check_results.keys()):
843 1
                check_results[key] = {}
844 1
            for col in diffs.columns:
845 1
                if (col not in check_results[key].keys()) and (diffs.any()[col]):
846 1
                    check_results[key][col] = {}
847 1
                    numba_true = result_numba_true[key][col][diffs[col]]
848 1
                    numba_false = result_numba_false[key][col][diffs[col]]
849 1
                    check_results[key][col] = abs(numba_true - numba_false)
850

851 1
    if check_results:
852 1
        return check_results
853

854

855 1
def deviation_from_std_type(net):
856
    """
857
        Checks, if element parameters match the values in the standard type library.
858

859
         INPUT:
860
            **net** (pandapowerNet)    - pandapower network
861

862

863
         OUTPUT:
864
            **check_results** (dict)   - All elements, that don't match the values in the
865
                                         standard type library
866

867
                                         Format: (element_type, element_index, parameter)
868

869

870
    """
871 1
    check_results = {}
872 1
    for key in net.std_types.keys():
873 1
        if key in net:
874 1
            for i, element in net[key].iterrows():
875 1
                std_type = element.std_type
876 1
                if std_type in net.std_types[key].keys():
877 1
                    std_type_values = net.std_types[key][std_type]
878 1
                    for param in std_type_values.keys():
879 1
                        if param == "tap_pos":
880 0
                            continue
881 1
                        if param in net[key].columns:
882 1
                            try:
883 1
                                isclose = np.isclose(element[param], std_type_values[param],
884
                                                     equal_nan=True)
885 1
                            except TypeError:
886 1
                                isclose = element[param] == std_type_values[param]
887 1
                            if not isclose:
888 1
                                if key not in check_results.keys():
889 1
                                    check_results[key] = {}
890 1
                                check_results[key][i] = {'param': param, 'e_value': element[param],
891
                                                         'std_type_value': std_type_values[param],
892
                                                         'std_type_in_lib': True}
893 1
                elif std_type is not None:
894 0
                    if key not in check_results.keys():
895 0
                        check_results[key] = {}
896 0
                    check_results[key][i] = {'std_type_in_lib': False}
897

898 1
    if check_results:
899 1
        return check_results
900

901

902 1
def parallel_switches(net):
903
    """
904
    Checks for parallel switches.
905

906
     INPUT:
907
        **net** (PandapowerNet)    - pandapower network
908

909
     OUTPUT:
910
        **parallel_switches** (list)   - List of tuples each containing parallel switches.
911
    """
912 1
    parallel_switches = []
913 1
    compare_parameters = ['bus', 'element', 'et']
914 1
    parallels_bus_and_element = list(
915
        net.switch.groupby(compare_parameters).count().query('closed > 1').index)
916 1
    for bus, element, et in parallels_bus_and_element:
917 1
        parallel_switches.append(list(net.switch.query(
918
            'bus==@bus & element==@element & et==@et').index))
919 1
    if parallel_switches:
920 1
        return parallel_switches

Read our documentation on viewing source code .

Loading