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__": |
a801a5b
c5de02d
f8d1636
6d4e25e
ad7504e
3ff4016
ed1e62d
9b609dd
c9edf1d
2417f94
ba87fda
edda486
8bbe28e
f29aa83
e94f8d0
4018179
c3a7705
1b3f946
c29de02
105408f
d72468b
f66b4d3
adb8cc8
f0d650e
2406c0f
dd0b296
659eb00
9ac4d9d
8a5bf8c
36f2f7e