446 |
451 |
|
|
447 |
452 |
|
|
448 |
453 |
|
def _validate_diff_res(diff_res, max_diff_values): |
449 |
|
- |
to_iterate = set(max_diff_values.keys()) & {'gen_q_mvar', 'branch_p_mw', 'branch_q_mvar', |
450 |
|
- |
'gen_p_mw', 'bus_va_degree', 'bus_vm_pu'} |
451 |
|
- |
if not len(to_iterate): |
452 |
|
- |
logger.warning("There are no keys to validate.") |
453 |
454 |
|
val = True |
454 |
|
- |
for i in to_iterate: |
455 |
|
- |
elm = i.split("_")[0] |
456 |
|
- |
sought = ["p", "q"] if elm != "bus" else ["vm", "va"] |
457 |
|
- |
col = int(np.array([0, 1])[[j in i for j in sought]][0]) if elm != "branch" else \ |
458 |
|
- |
list(np.array([[0, 2], [1, 3]])[[j in i for j in sought]][0]) |
459 |
|
- |
val &= bool(np.max(abs(diff_res[elm][:, col])) < max_diff_values[i]) |
|
455 |
+ |
for et_val in ['gen_q_mvar', 'branch_p_mw', 'branch_q_mvar', 'gen_p_mw', 'bus_va_degree', |
|
456 |
+ |
'bus_vm_pu']: |
|
457 |
+ |
if max_diff_values[et_val] is not None: |
|
458 |
+ |
et = et_val.split("_")[0] |
|
459 |
+ |
log_key = et if et != "gen" else "gen_p" if "p" in et_val else "gen_q_sum_per_bus" |
|
460 |
+ |
i_col = _log_dict(log_key)[0] |
|
461 |
+ |
val &= bool(np.max(abs(diff_res[log_key][:, i_col])) < max_diff_values[et_val]) |
460 |
462 |
|
return val |
461 |
463 |
|
|
462 |
464 |
|
|
463 |
|
- |
def _gen_bus_info(ppc, idx_gen): |
464 |
|
- |
bus_name = int(ppc["gen"][idx_gen, GEN_BUS]) |
465 |
|
- |
# assumption: there is only one bus with this bus_name: |
466 |
|
- |
idx_bus = int(np.where(ppc["bus"][:, BUS_I] == bus_name)[0][0]) |
467 |
|
- |
current_bus_type = int(ppc["bus"][idx_bus, 1]) |
|
465 |
+ |
def _gen_q_per_bus_sum(q_array, ppc): |
|
466 |
+ |
return pd.DataFrame( |
|
467 |
+ |
np.c_[q_array, ppc["gen"][:, GEN_BUS].astype(int)], |
|
468 |
+ |
columns=["q_mvar", "bus"]).groupby("bus").sum() |
468 |
469 |
|
|
469 |
|
- |
same_bus_gen = np.where(ppc["gen"][:, GEN_BUS] == ppc["gen"][idx_gen, GEN_BUS])[0].astype(int) |
470 |
|
- |
same_bus_gen = same_bus_gen[np.where(ppc["gen"][same_bus_gen, GEN_STATUS] > 0)] |
471 |
|
- |
first_same_bus = same_bus_gen[0] if len(same_bus_gen) else None |
472 |
470 |
|
|
473 |
|
- |
return current_bus_type, idx_bus, first_same_bus |
|
471 |
+ |
def _log_dict(key=None): |
|
472 |
+ |
log_dict = { |
|
473 |
+ |
"bus": [[0, 1], ["voltage magnitude", "voltage angle"], ["pu", "degree"]], |
|
474 |
+ |
"branch": [[[0, 2], [1, 3]], ["branch flow active power", "branch flow reactive power"], |
|
475 |
+ |
["MW", "Mvar"]], |
|
476 |
+ |
"gen_p": [[0], ["active power generation"], ["MW"]], |
|
477 |
+ |
"gen_q_sum_per_bus": [[0], ["reactive power generation sum per bus"], ["Mvar"]]} |
|
478 |
+ |
if key is None: |
|
479 |
+ |
return log_dict |
|
480 |
+ |
else: |
|
481 |
+ |
return log_dict[key] |
474 |
482 |
|
|
475 |
483 |
|
|
476 |
|
- |
def validate_from_ppc(ppc_net, net, pf_type="runpp", max_diff_values={ |
477 |
|
- |
"bus_vm_pu": 1e-6, "bus_va_degree": 1e-5, "branch_p_mw": 1e-6, "branch_q_mvar": 1e-6, |
478 |
|
- |
"gen_p_mw": 1e-6, "gen_q_mvar": 1e-6}, run=True): |
|
484 |
+ |
def validate_from_ppc(ppc, net, max_diff_values={ |
|
485 |
+ |
"bus_vm_pu": 1e-6, "bus_va_degree": 1e-5, "branch_p_mw": 1e-6, "branch_q_mvar": 1e-6, |
|
486 |
+ |
"gen_p_mw": 1e-6, "gen_q_mvar": 1e-6}): |
479 |
487 |
|
""" |
480 |
|
- |
This function validates the pypower case files to pandapower net structure conversion via a \ |
481 |
|
- |
comparison of loadflow calculation results. (Hence the opf cost conversion is not validated.) |
482 |
|
- |
|
483 |
|
- |
INPUT: |
484 |
|
- |
|
485 |
|
- |
**ppc_net** - The pypower case file, which must already contain the pypower powerflow |
486 |
|
- |
results or pypower must be importable. |
487 |
|
- |
|
488 |
|
- |
**net** - The pandapower network. |
489 |
|
- |
|
490 |
|
- |
OPTIONAL: |
491 |
|
- |
|
492 |
|
- |
**pf_type** ("runpp", string) - Type of validated power flow. Possible are ("runpp", |
493 |
|
- |
"rundcpp", "runopp", "rundcopp") |
494 |
|
- |
|
495 |
|
- |
**max_diff_values** - Dict of maximal allowed difference values. The keys must be |
496 |
|
- |
'vm_pu', 'va_degree', 'p_branch_mw', 'q_branch_mvar', 'p_gen_mw' and 'q_gen_mvar' and |
497 |
|
- |
the values floats. |
498 |
|
- |
|
499 |
|
- |
**run** (True, bool or list of two bools) - changing the value to False avoids trying to run |
500 |
|
- |
(optimal) loadflows. Giving a list of two bools addresses first pypower and second |
501 |
|
- |
pandapower. |
502 |
|
- |
|
503 |
|
- |
OUTPUT: |
504 |
|
- |
|
505 |
|
- |
**conversion_success** - conversion_success is returned as False if pypower or pandapower |
506 |
|
- |
cannot calculate a powerflow or if the maximum difference values (max_diff_values ) |
507 |
|
- |
cannot be hold. |
508 |
|
- |
|
509 |
|
- |
EXAMPLE: |
510 |
|
- |
|
511 |
|
- |
import pandapower.converter as pc |
512 |
|
- |
|
513 |
|
- |
net = cv.from_ppc(ppc_net, f_hz=50) |
514 |
|
- |
|
515 |
|
- |
conversion_success = cv.validate_from_ppc(ppc_net, net) |
516 |
|
- |
|
517 |
|
- |
NOTE: |
518 |
|
- |
|
519 |
|
- |
The user has to take care that the loadflow results already are included in the provided \ |
520 |
|
- |
ppc_net or pypower is importable. |
|
488 |
+ |
This function validates the conversion of a pypower case file (ppc) to a pandapower net. |
|
489 |
+ |
It compares the power flow calculation results which must be provided within the ppc and the net. |
|
490 |
+ |
|
|
491 |
+ |
Parameters |
|
492 |
+ |
---------- |
|
493 |
+ |
ppc : dict |
|
494 |
+ |
_description_ |
|
495 |
+ |
net : pandapower.pandapowerNet |
|
496 |
+ |
_description_ |
|
497 |
+ |
max_diff_values : dict, optional |
|
498 |
+ |
_description_, by default { "bus_vm_pu": 1e-6, "bus_va_degree": 1e-5, "branch_p_mw": 1e-6, |
|
499 |
+ |
"branch_q_mvar": 1e-6, "gen_p_mw": 1e-6, "gen_q_mvar": 1e-6} |
|
500 |
+ |
|
|
501 |
+ |
Returns |
|
502 |
+ |
------- |
|
503 |
+ |
bool |
|
504 |
+ |
Whether the power flow results matches. |
|
505 |
+ |
|
|
506 |
+ |
Examples |
|
507 |
+ |
------ |
|
508 |
+ |
>>> import pandapower |
|
509 |
+ |
>>> from pandapower.test.converter.test_from_ppc import get_testgrids |
|
510 |
+ |
>>> ppc = get_testgrids('pypower_cases', 'case4gs.json') |
|
511 |
+ |
>>> net = pandapower.converter.from_ppc(ppc, f_hz=50) |
|
512 |
+ |
>>> pandapower.runpp(net) |
|
513 |
+ |
>>> pf_match = pandapower.converter.validate_from_ppc(ppc, net) |
521 |
514 |
|
""" |
|
515 |
+ |
if "_from_ppc_lookups" not in net.keys() or \ |
|
516 |
+ |
("gen" not in net._from_ppc_lookups.keys() and len(ppc["gen"]) > 0) or \ |
|
517 |
+ |
("branch" not in net._from_ppc_lookups.keys() and len(ppc["branch"]) > 0): |
|
518 |
+ |
raise ValueError( |
|
519 |
+ |
"net._from_ppc_lookups must contain a lookup (dict of keys 'branch' and 'gen')") |
522 |
520 |
|
|
523 |
|
- |
# check in case of optimal powerflow comparison whether cost information exist |
524 |
|
- |
if "opp" in pf_type: |
525 |
|
- |
if not (len(net.polynomial_cost) | len(net.piecewise_linear_cost)): |
526 |
|
- |
if "gencost" in ppc_net: |
527 |
|
- |
if not len(ppc_net["gencost"]): |
528 |
|
- |
logger.debug('ppc and pandapower net do not include cost information.') |
529 |
|
- |
return True |
530 |
|
- |
else: |
531 |
|
- |
logger.error('The pandapower net does not include cost information.') |
532 |
|
- |
return False |
533 |
|
- |
else: |
534 |
|
- |
logger.debug('ppc and pandapower net do not include cost information.') |
535 |
|
- |
return True |
536 |
|
- |
|
537 |
|
- |
# guarantee run parameter as list, for pypower and pandapower (optimal) powerflow run |
538 |
|
- |
run = [run, run] if isinstance(run, bool) else run |
539 |
|
- |
|
540 |
|
- |
# --- check pypower powerflow success, if possible |
541 |
|
- |
if pypower_import and run[0]: |
542 |
|
- |
try: |
543 |
|
- |
if pf_type == "runpp": |
544 |
|
- |
ppc_net = runpf.runpf(ppc_net, ppopt)[0] |
545 |
|
- |
elif pf_type == "rundcpp": |
546 |
|
- |
ppc_net = rundcpf.rundcpf(ppc_net, ppopt)[0] |
547 |
|
- |
elif pf_type == "runopp": |
548 |
|
- |
ppc_net = runopf.runopf(ppc_net, ppopt) |
549 |
|
- |
elif pf_type == "rundcopp": |
550 |
|
- |
ppc_net = rundcopf.rundcopf(ppc_net, ppopt) |
551 |
|
- |
else: |
552 |
|
- |
raise ValueError(f"The pf_type {pf_type} is unknown") |
553 |
|
- |
except: |
554 |
|
- |
logger.debug("The pypower run did not work.") |
555 |
|
- |
ppc_success = True |
556 |
|
- |
if 'success' in ppc_net.keys(): |
557 |
|
- |
if ppc_net['success'] != 1: |
558 |
|
- |
ppc_success = False |
559 |
|
- |
logger.error("The given ppc data indicates an unsuccessful pypower powerflow: " + |
560 |
|
- |
"'ppc_net['success'] != 1'") |
561 |
|
- |
if (ppc_net['branch'].shape[1] < 17): |
562 |
|
- |
ppc_success = False |
563 |
|
- |
logger.error("The shape of given ppc data indicates missing pypower powerflow results.") |
564 |
|
- |
|
565 |
|
- |
# --- try to run a pandapower powerflow |
566 |
|
- |
if run[1]: |
567 |
|
- |
if pf_type == "runpp": |
568 |
|
- |
try: |
569 |
|
- |
runpp(net, init="dc", calculate_voltage_angles=True, trafo_model="pi") |
570 |
|
- |
except LoadflowNotConverged: |
571 |
|
- |
try: |
572 |
|
- |
runpp(net, calculate_voltage_angles=True, init="flat", trafo_model="pi") |
573 |
|
- |
except LoadflowNotConverged: |
574 |
|
- |
try: |
575 |
|
- |
runpp(net, trafo_model="pi", calculate_voltage_angles=False) |
576 |
|
- |
if "bus_va_degree" in max_diff_values.keys(): |
577 |
|
- |
max_diff_values["bus_va_degree"] = 1e2 if max_diff_values[ |
578 |
|
- |
"bus_va_degree"] < 1e2 else max_diff_values["bus_va_degree"] |
579 |
|
- |
logger.info("voltage_angles could be calculated.") |
580 |
|
- |
except LoadflowNotConverged: |
581 |
|
- |
logger.error('The pandapower powerflow does not converge.') |
582 |
|
- |
elif pf_type == "rundcpp": |
583 |
|
- |
try: |
584 |
|
- |
rundcpp(net, trafo_model="pi") |
585 |
|
- |
except LoadflowNotConverged: |
586 |
|
- |
logger.error('The pandapower dc powerflow does not converge.') |
587 |
|
- |
elif pf_type == "runopp": |
588 |
|
- |
try: |
589 |
|
- |
runopp(net, init="flat", calculate_voltage_angles=True) |
590 |
|
- |
except OPFNotConverged: |
591 |
|
- |
try: |
592 |
|
- |
runopp(net, init="pf", calculate_voltage_angles=True) |
593 |
|
- |
except (OPFNotConverged, LoadflowNotConverged, KeyError): |
594 |
|
- |
try: |
595 |
|
- |
runopp(net, init="flat", calculate_voltage_angles=False) |
596 |
|
- |
logger.info("voltage_angles could be calculated.") |
597 |
|
- |
if "bus_va_degree" in max_diff_values.keys(): |
598 |
|
- |
max_diff_values["bus_va_degree"] = 1e2 if max_diff_values[ |
599 |
|
- |
"bus_va_degree"] < 1e2 else max_diff_values["bus_va_degree"] |
600 |
|
- |
except OPFNotConverged: |
601 |
|
- |
try: |
602 |
|
- |
runopp(net, init="pf", calculate_voltage_angles=False) |
603 |
|
- |
if "bus_va_degree" in max_diff_values.keys(): |
604 |
|
- |
max_diff_values["bus_va_degree"] = 1e2 if max_diff_values[ |
605 |
|
- |
"bus_va_degree"] < 1e2 else max_diff_values["bus_va_degree"] |
606 |
|
- |
logger.info("voltage_angles could be calculated.") |
607 |
|
- |
except (OPFNotConverged, LoadflowNotConverged, KeyError): |
608 |
|
- |
logger.error('The pandapower optimal powerflow does not converge.') |
609 |
|
- |
elif pf_type == "rundcopp": |
610 |
|
- |
try: |
611 |
|
- |
rundcopp(net) |
612 |
|
- |
except LoadflowNotConverged: |
613 |
|
- |
logger.error('The pandapower dc optimal powerflow does not converge.') |
614 |
|
- |
else: |
615 |
|
- |
raise ValueError("The pf_type %s is unknown" % pf_type) |
616 |
|
- |
|
617 |
|
- |
# --- prepare powerflow result comparison by reordering pp results as they are in ppc results |
618 |
|
- |
if not ppc_success: |
619 |
|
- |
return False |
620 |
|
- |
if "opp" in pf_type: |
621 |
|
- |
if not net.OPF_converged: |
622 |
|
- |
return |
623 |
|
- |
elif not net.converged: |
624 |
|
- |
return False |
625 |
|
- |
|
626 |
|
- |
# --- store pypower powerflow results |
|
521 |
+ |
if net.res_bus.shape[0] == 0 and net.bus.shape[0] > 0: |
|
522 |
+ |
logger.debug("runpp() is performed by validate_from_ppc() since res_bus is empty.") |
|
523 |
+ |
runpp(net, calculate_voltage_angles=True, trafo_model="pi") |
|
524 |
+ |
|
|
525 |
+ |
# --- pypower powerflow results -> ppc_res ----------------------------------------------------- |
627 |
526 |
|
ppc_res = dict.fromkeys(ppc_elms) |
628 |
|
- |
ppc_res["branch"] = ppc_net['branch'][:, 13:17] |
629 |
|
- |
ppc_res["bus"] = ppc_net['bus'][:, 7:9] |
630 |
|
- |
ppc_res["gen"] = ppc_net['gen'][:, 1:3] |
|
527 |
+ |
ppc_res["bus"] = ppc['bus'][:, 7:9] |
|
528 |
+ |
ppc_res["branch"] = ppc['branch'][:, 13:17] |
|
529 |
+ |
ppc_res["gen"] = ppc['gen'][:, 1:3] |
|
530 |
+ |
ppc_res["gen_p"] = ppc_res["gen"][:, :1] |
|
531 |
+ |
ppc_res["gen_q_sum_per_bus"] = _gen_q_per_bus_sum(ppc_res["gen"][:, -1:], ppc) |
631 |
532 |
|
|
632 |
|
- |
# --- pandapower bus result table |
|
533 |
+ |
# --- pandapower powerflow results -> pp_res --------------------------------------------------- |
633 |
534 |
|
pp_res = dict.fromkeys(ppc_elms) |
634 |
|
- |
pp_res["bus"] = np.array(net.res_bus.sort_index()[['vm_pu', 'va_degree']]) |
635 |
535 |
|
|
636 |
|
- |
# --- pandapower gen result table |
637 |
|
- |
pp_res["gen"] = np.zeros([1, 2]) |
638 |
|
- |
# consideration of parallel generators via storing how much generators have been considered |
639 |
|
- |
# each node |
640 |
|
- |
# if in ppc is only one gen -> numpy initially uses one dim array -> change to two dim array |
641 |
|
- |
if len(ppc_net["gen"].shape) == 1: |
642 |
|
- |
ppc_net["gen"] = np.array(ppc_net["gen"], ndmin=2) |
643 |
|
- |
GENS = pd.DataFrame(ppc_net['gen'][:, [0]].astype(int)) |
644 |
|
- |
GEN_uniq = GENS.drop_duplicates() |
645 |
|
- |
already_used_gen = pd.Series(np.zeros(GEN_uniq.shape[0]).astype(int), |
646 |
|
- |
index=[int(v) for v in GEN_uniq.values]) |
647 |
|
- |
change_q_compare = [] |
648 |
|
- |
for i, j in GENS.iterrows(): |
649 |
|
- |
current_bus_type, current_bus_idx, first_same_bus_in_service_gen_idx, = _gen_bus_info( |
650 |
|
- |
ppc_net, i) |
651 |
|
- |
if current_bus_type == 3 and i == first_same_bus_in_service_gen_idx: |
652 |
|
- |
pp_res["gen"] = np.append(pp_res["gen"], np.array(net.res_ext_grid[ |
653 |
|
- |
net.ext_grid.bus == current_bus_idx][['p_mw', 'q_mvar']]).reshape((1, 2)), 0) |
654 |
|
- |
elif current_bus_type == 2 and i == first_same_bus_in_service_gen_idx: |
655 |
|
- |
pp_res["gen"] = np.append(pp_res["gen"], np.array(net.res_gen[ |
656 |
|
- |
net.gen.bus == current_bus_idx][['p_mw', 'q_mvar']]).reshape((1, 2)), 0) |
|
536 |
+ |
# --- bus |
|
537 |
+ |
pp_res["bus"] = net.res_bus.loc[ppc["bus"][:, BUS_I].astype(int), ['vm_pu', 'va_degree']].values |
|
538 |
+ |
|
|
539 |
+ |
# --- branch |
|
540 |
+ |
pp_res["branch"] = np.zeros(ppc_res["branch"].shape) |
|
541 |
+ |
from_to_buses = -np.ones((ppc_res["branch"].shape[0], 2), dtype=int) |
|
542 |
+ |
for et in net._from_ppc_lookups["branch"].element_type.unique(): |
|
543 |
+ |
if et == "line": |
|
544 |
+ |
from_to_cols = ["from_bus", "to_bus"] |
|
545 |
+ |
res_cols = ['p_from_mw', 'q_from_mvar', 'p_to_mw', 'q_to_mvar'] |
|
546 |
+ |
elif et == "trafo": |
|
547 |
+ |
from_to_cols = ["hv_bus", "lv_bus"] |
|
548 |
+ |
res_cols = ['p_hv_mw', 'q_hv_mvar', 'p_lv_mw', 'q_lv_mvar'] |
657 |
549 |
|
else: |
658 |
|
- |
pp_res["gen"] = np.append(pp_res["gen"], np.array(net.res_sgen[ |
659 |
|
- |
net.sgen.bus == current_bus_idx][['p_mw', 'q_mvar']])[ |
660 |
|
- |
already_used_gen.at[int(j)]].reshape((1, 2)), 0) |
661 |
|
- |
already_used_gen.at[int(j)] += 1 |
662 |
|
- |
change_q_compare += [int(j)] |
663 |
|
- |
pp_res["gen"] = pp_res["gen"][1:, :] # delete initial zero row |
664 |
|
- |
|
665 |
|
- |
# --- pandapower branch result table |
666 |
|
- |
pp_res["branch"] = np.zeros([1, 4]) |
667 |
|
- |
# consideration of parallel branches via storing how often branches were considered |
668 |
|
- |
# each node-to-node-connection |
669 |
|
- |
try: |
670 |
|
- |
init1 = pd.concat([net.line.from_bus, net.line.to_bus], axis=1, |
671 |
|
- |
sort=True).drop_duplicates() |
672 |
|
- |
init2 = pd.concat([net.trafo.hv_bus, net.trafo.lv_bus], axis=1, |
673 |
|
- |
sort=True).drop_duplicates() |
674 |
|
- |
except TypeError: |
675 |
|
- |
# legacy pandas < 0.21 |
676 |
|
- |
init1 = pd.concat([net.line.from_bus, net.line.to_bus], axis=1).drop_duplicates() |
677 |
|
- |
init2 = pd.concat([net.trafo.hv_bus, net.trafo.lv_bus], axis=1).drop_duplicates() |
678 |
|
- |
init1['hv_bus'] = np.nan |
679 |
|
- |
init1['lv_bus'] = np.nan |
680 |
|
- |
init2['from_bus'] = np.nan |
681 |
|
- |
init2['to_bus'] = np.nan |
682 |
|
- |
try: |
683 |
|
- |
already_used_branches = pd.concat([init1, init2], axis=0, sort=True) |
684 |
|
- |
except TypeError: |
685 |
|
- |
# pandas < 0.21 legacy |
686 |
|
- |
already_used_branches = pd.concat([init1, init2], axis=0) |
687 |
|
- |
already_used_branches['number'] = np.zeros([already_used_branches.shape[0], 1]).astype(int) |
688 |
|
- |
BRANCHES = pd.DataFrame(ppc_net['branch'][:, [0, 1, TAP, SHIFT]]) |
689 |
|
- |
for i in BRANCHES.index: |
690 |
|
- |
from_bus = get_element_index(net, 'bus', name=int(ppc_net['branch'][i, 0])) |
691 |
|
- |
to_bus = get_element_index(net, 'bus', name=int(ppc_net['branch'][i, 1])) |
692 |
|
- |
from_vn_kv = ppc_net['bus'][from_bus, BASE_KV] |
693 |
|
- |
to_vn_kv = ppc_net['bus'][to_bus, BASE_KV] |
694 |
|
- |
ratio = BRANCHES[2].at[i] |
695 |
|
- |
angle = BRANCHES[3].at[i] |
696 |
|
- |
# from line results |
697 |
|
- |
if (from_vn_kv == to_vn_kv) & ((ratio == 0) | (ratio == 1)) & (angle == 0): |
698 |
|
- |
pp_res["branch"] = np.append(pp_res["branch"], np.array(net.res_line[ |
699 |
|
- |
(net.line.from_bus == from_bus) & |
700 |
|
- |
(net.line.to_bus == to_bus)] |
701 |
|
- |
[['p_from_mw', 'q_from_mvar', 'p_to_mw', 'q_to_mvar']])[ |
702 |
|
- |
int(already_used_branches.number.loc[ |
703 |
|
- |
(already_used_branches.from_bus == from_bus) & |
704 |
|
- |
(already_used_branches.to_bus == to_bus)].values)].reshape(1, 4), 0) |
705 |
|
- |
already_used_branches.number.loc[(already_used_branches.from_bus == from_bus) & |
706 |
|
- |
(already_used_branches.to_bus == to_bus)] += 1 |
707 |
|
- |
# from trafo results |
708 |
|
- |
else: |
709 |
|
- |
if from_vn_kv >= to_vn_kv: |
710 |
|
- |
pp_res["branch"] = np.append(pp_res["branch"], np.array(net.res_trafo[ |
711 |
|
- |
(net.trafo.hv_bus == from_bus) & |
712 |
|
- |
(net.trafo.lv_bus == to_bus)] |
713 |
|
- |
[['p_hv_mw', 'q_hv_mvar', 'p_lv_mw', 'q_lv_mvar']])[ |
714 |
|
- |
int(already_used_branches.number.loc[ |
715 |
|
- |
(already_used_branches.hv_bus == from_bus) & |
716 |
|
- |
(already_used_branches.lv_bus == to_bus)].values)].reshape(1, 4), 0) |
717 |
|
- |
already_used_branches.number.loc[(already_used_branches.hv_bus == from_bus) & |
718 |
|
- |
(already_used_branches.lv_bus == to_bus)] += 1 |
719 |
|
- |
else: # switch hv-lv-connection of pypower connection buses |
720 |
|
- |
pp_res["branch"] = np.append(pp_res["branch"], np.array(net.res_trafo[ |
721 |
|
- |
(net.trafo.hv_bus == to_bus) & |
722 |
|
- |
(net.trafo.lv_bus == from_bus)] |
723 |
|
- |
[['p_lv_mw', 'q_lv_mvar', 'p_hv_mw', 'q_hv_mvar']])[ |
724 |
|
- |
int(already_used_branches.number.loc[ |
725 |
|
- |
(already_used_branches.hv_bus == to_bus) & |
726 |
|
- |
(already_used_branches.lv_bus == from_bus)].values)].reshape(1, 4), 0) |
727 |
|
- |
already_used_branches.number.loc[ |
728 |
|
- |
(already_used_branches.hv_bus == to_bus) & |
729 |
|
- |
(already_used_branches.lv_bus == from_bus)] += 1 |
730 |
|
- |
pp_res["branch"] = pp_res["branch"][1:, :] # delete initial zero row |
731 |
|
- |
|
732 |
|
- |
# --- do the powerflow result comparison |
733 |
|
- |
diff_res = dict.fromkeys(ppc_elms) |
734 |
|
- |
diff_res["bus"] = ppc_res["bus"] - pp_res["bus"] |
735 |
|
- |
diff_res["bus"][:, 1] -= diff_res["bus"][0, 1] # remove va_degree offset |
736 |
|
- |
diff_res["branch"] = ppc_res["branch"] - pp_res["branch"] |
737 |
|
- |
diff_res["gen"] = ppc_res["gen"] - pp_res["gen"] |
738 |
|
- |
# comparison of buses with several generator units only as q sum |
739 |
|
- |
for i in GEN_uniq.loc[GEN_uniq[0].isin(change_q_compare)].index: |
740 |
|
- |
next_is = GEN_uniq.index[GEN_uniq.index > i] |
741 |
|
- |
if len(next_is) > 0: |
742 |
|
- |
next_i = next_is[0] |
743 |
|
- |
else: |
744 |
|
- |
next_i = GENS.index[-1] + 1 |
745 |
|
- |
if (next_i - i) > 1: |
746 |
|
- |
diff_res["gen"][i:next_i, 1] = sum(diff_res["gen"][i:next_i, 1]) |
747 |
|
- |
# logger info |
748 |
|
- |
logger.debug("Maximum voltage magnitude difference between pypower and pandapower: " |
749 |
|
- |
"%.2e pu" % np.max(abs(diff_res["bus"][:, 0]))) |
750 |
|
- |
logger.debug("Maximum voltage angle difference between pypower and pandapower: " |
751 |
|
- |
"%.2e degree" % np.max(abs(diff_res["bus"][:, 1]))) |
752 |
|
- |
logger.debug("Maximum branch flow active power difference between pypower and pandapower: " |
753 |
|
- |
"%.2e MW" % np.max(abs(diff_res["branch"][:, [0, 2]]))) |
754 |
|
- |
logger.debug("Maximum branch flow reactive power difference between pypower and " |
755 |
|
- |
"pandapower: %.2e MVAr" % np.max(abs(diff_res["branch"][:, [1, 3]]))) |
756 |
|
- |
logger.debug("Maximum active power generation difference between pypower and pandapower: " |
757 |
|
- |
"%.2e MW" % np.max(abs(diff_res["gen"][:, 0]))) |
758 |
|
- |
logger.debug("Maximum reactive power generation difference between pypower and pandapower: " |
759 |
|
- |
"%.2e MVAr" % np.max(abs(diff_res["gen"][:, 1]))) |
760 |
|
- |
if _validate_diff_res(diff_res, {"bus_vm_pu": 1e-3, "bus_va_degree": 1e-3, "branch_p_mw": 1e-6, |
761 |
|
- |
"branch_q_mvar": 1e-6}) and \ |
762 |
|
- |
(np.max(abs(diff_res["gen"])) > 1e-1).any(): |
763 |
|
- |
logger.debug("The active/reactive power generation difference possibly results " |
764 |
|
- |
"because of a pypower error. Please validate " |
765 |
|
- |
"the results via pypower loadflow.") # this occurs e.g. at ppc case9 |
766 |
|
- |
# give a return |
767 |
|
- |
if isinstance(max_diff_values, dict): |
768 |
|
- |
return _validate_diff_res(diff_res, max_diff_values) |
769 |
|
- |
else: |
770 |
|
- |
logger.debug("'max_diff_values' must be a dict.") |
|
550 |
+ |
raise NotImplementedError( |
|
551 |
+ |
f"result columns for element type {et} are not implemented.") |
|
552 |
+ |
is_et = net._from_ppc_lookups["branch"].element_type == et |
|
553 |
+ |
pp_res["branch"][is_et] += net[f"res_{et}"].loc[ |
|
554 |
+ |
net._from_ppc_lookups["branch"].element.loc[is_et], res_cols].values |
|
555 |
+ |
from_to_buses[is_et] = net[et].loc[ |
|
556 |
+ |
net._from_ppc_lookups["branch"].element.loc[is_et], from_to_cols].values |
|
557 |
+ |
|
|
558 |
+ |
# switch direction as in ppc |
|
559 |
+ |
correct_from_to = np.all(from_to_buses == ppc["branch"][:, F_BUS:T_BUS+1].astype(int), axis=1) |
|
560 |
+ |
switch_from_to = np.all(from_to_buses[:, ::-1] == ppc["branch"][:, F_BUS:T_BUS+1].astype( |
|
561 |
+ |
int), axis=1) |
|
562 |
+ |
if not np.all(correct_from_to | switch_from_to): |
|
563 |
+ |
raise ValueError("ppc branch from and to buses don't fit to pandapower from and to + " |
|
564 |
+ |
"hv and lv buses.") |
|
565 |
+ |
if np.any(switch_from_to): |
|
566 |
+ |
pp_res["branch"][switch_from_to, :] = pp_res["branch"][switch_from_to, :][:, [2, 3, 0, 1]] |
|
567 |
+ |
|
|
568 |
+ |
# --- gen |
|
569 |
+ |
pp_res["gen"] = np.zeros(ppc_res["gen"].shape) |
|
570 |
+ |
res_cols = ['p_mw', 'q_mvar'] |
|
571 |
+ |
for et in net._from_ppc_lookups["gen"].element_type.unique(): |
|
572 |
+ |
is_et = net._from_ppc_lookups["gen"].element_type == et |
|
573 |
+ |
pp_res["gen"][is_et] += net[f"res_{et}"].loc[ |
|
574 |
+ |
net._from_ppc_lookups["gen"].element.loc[is_et], res_cols].values |
|
575 |
+ |
|
|
576 |
+ |
pp_res["gen_p"] = ppc_res["gen"][:, :1] |
|
577 |
+ |
pp_res["gen_q_sum_per_bus"] = _gen_q_per_bus_sum(pp_res["gen"][:, -1:], ppc) |
|
578 |
+ |
|
|
579 |
+ |
# --- log maximal differences the powerflow result comparison |
|
580 |
+ |
diff_res = dict() |
|
581 |
+ |
comp_keys = ["bus", "branch", "gen_p", "gen_q_sum_per_bus"] |
|
582 |
+ |
for comp_key in comp_keys: |
|
583 |
+ |
diff_res[comp_key] = ppc_res[comp_key] - pp_res[comp_key] |
|
584 |
+ |
if isinstance(diff_res[comp_key], pd.DataFrame): |
|
585 |
+ |
diff_res[comp_key] = diff_res[comp_key].values |
|
586 |
+ |
for i_col, var_str, unit in zip(*_log_dict(comp_key)): |
|
587 |
+ |
diff = diff_res[comp_key][:, i_col] |
|
588 |
+ |
logger.debug(f"Maximum {var_str} difference between pandapower and pypower: " |
|
589 |
+ |
"%.2e %s" % (np.max(abs(diff)), unit)) |
|
590 |
+ |
|
|
591 |
+ |
# --- do the powerflow result comparison ------------------------------------------------------- |
|
592 |
+ |
pf_match = _validate_diff_res(diff_res, max_diff_values) |
|
593 |
+ |
|
|
594 |
+ |
# --- case of missmatch: result comparison with different max_diff (unwanted behaviour of |
|
595 |
+ |
# pypower possible) |
|
596 |
+ |
if not pf_match: |
|
597 |
+ |
other_max_diff = { |
|
598 |
+ |
"bus_vm_pu": 1e-3, "bus_va_degree": 1e-3, "branch_p_mw": 1e-6, "branch_q_mvar": 1e-6, |
|
599 |
+ |
"gen_p_mw": None, "gen_q_mvar": None} |
|
600 |
+ |
if _validate_diff_res(diff_res, other_max_diff) and \ |
|
601 |
+ |
(np.max(abs(pp_res["gen"] - ppc_res["gen"])) > 1e-1).any(): |
|
602 |
+ |
logger.debug("The active/reactive power generation difference possibly results " |
|
603 |
+ |
"because of a pypower error. Please validate " |
|
604 |
+ |
"the results via pypower loadflow.") # this occurs e.g. at ppc case9 |
|
605 |
+ |
|
|
606 |
+ |
return pf_match |
771 |
607 |
|
|
772 |
608 |
|
|
773 |
609 |
|
if __name__ == "__main__": |
8d46c9c
dfff74c
064182d
d789dd6
a801a5b
c5de02d
3e7dcff
f8d1636
6d4e25e
ad7504e
36f2f7e
a18f177
52808e1
022c048
3ff4016
ed1e62d
9b609dd
c9edf1d
2417f94
ba87fda
edda486
8bbe28e
f29aa83
e94f8d0
4018179
c3a7705
1b3f946
c29de02
105408f
d72468b
f66b4d3
adb8cc8
f0d650e
2406c0f
dd0b296
659eb00
9ac4d9d
8a5bf8c
1b7032b