diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 258c69877..d61fbae03 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,22 +3,6 @@ Change Log [upcoming release] - 2025-..-.. ------------------------------- -- [FIXED] Increasing geojson precision as the default precision might cause problems with pandahub - -[2.14.11] - 2024-07-08 -------------------------------- -- [FIXED] Lightsim2grid version - -[2.14.10] - 2024-07-08 -------------------------------- -- [FIXED] geopandas version - -[2.14.9] - 2024-06-25 -------------------------------- -- [FIXED] scipy version - -[upcoming release] - 2024-..-.. -------------------------------- - [ADDED] Static Var Compensator with Voltage Control - [ADDED] pf2pp: min/max q_mvar and min/max p_mw limits for sgens and gen will be converted @@ -40,11 +24,13 @@ Change Log - [ADDED] pf2pp: min/max q_mvar and min/max p_mw limits for sgens and gen will be converted - [ADDED] converter for European EHV grid data from JAO, the "Single Allocation Platform (SAP) for all European Transmission System Operators (TSOs) that operate in accordance to EU legislation" - [ADDED] Add GeographicalRegion and SubGeographicalRegion names and ids to bus df in cim converter +- [ADDED] API function rename_std_type() - [CHANGED] Capitalize first letter of columns busbar_id, busbar_name and substation_id in bus df for cim converter - [CHANGED] required standard type parameters are made available by function :code:`required_std_type_parameters()` - [CHANGED] toolbox replace functions (e.g. gen replacement by sgens): improved result table implementation and added profiles consideration - [FIXED] Do not modify pandas options when importing pandapower - [FIXED] fixed copy-paste error in contingency results "max_limit_nminus1" and "min_limit_nminus1" +- [FIXED] default elements in toolbox function add_zones_to_elements() - [ADDED] improved lightsim2grid documentation including compatibitliy issues - [FIXED] avoid duplicated keys in kwargs and pf_options in run_contingency() - [FIXED] cim2pp: set default xml encoding to None to avoid error after changing to lxml @@ -87,6 +73,7 @@ Change Log - [FIXED] massive performance drag in large grids due to initializing Ybus for FACTS with np.zeros instead of using sparse matrix initialization - [FIXED] further futurewarnings and deprecation warnings - [FIXED] minor issues in geojson exporter +- [CHANGED] e2n logo is updated due to its official renaming - [CHANGED] use of bus_geodata and line_geodata tables to geo column in bus and line table - [CHANGED] update most geodata dependant functions to use geo column - [ADDED] geodata to geojson converter @@ -123,7 +110,9 @@ Change Log - [CHANGED] Trafo Controllers can now be added to elements that are out of service, changed self.nothing_to_do() - [ADDED] Discrete shunt controller for local voltage regulation with shunt steps - [ADDED] fix lengths missmatch of output if ignore_zero_length is False in plotting utility function coords_from_node_geodata() and rename ignore_zero_length by ignore_no_geo_diff +- [FIXED] from_powerfactory() converter: error that crept in `obj.GetAttributes(a)` instead of `obj.GetAttribute(a)` - [ADDED] converter for European EHV grid data from JAO, the "Single Allocation Platform (SAP) for all European Transmission System Operators (TSOs) that operate in accordance to EU legislation" +- [ADDED] UCTE-DEF (UCTE Data Exchange Format) converter - [ADDED] cim2pp converter: Using lxml to parse XML files (better performance) - [FIXED] OC relay name attribute error - [FIXED] cim2pp: fixed missing nominal voltages at SeriesCompensator diff --git a/doc/converter.rst b/doc/converter.rst index 633d7a32b..1bd8d15a2 100644 --- a/doc/converter.rst +++ b/doc/converter.rst @@ -16,5 +16,6 @@ These tools are: converter/matpower converter/powerfactory converter/cgmes + converter/ucte converter/jao diff --git a/doc/converter/jao.rst b/doc/converter/jao.rst index e91995163..77e0681df 100644 --- a/doc/converter/jao.rst +++ b/doc/converter/jao.rst @@ -1,5 +1,5 @@ -Documentation for the JAO Static Grid Model Converter Function -============================================================== +JAO Static Grid Model Converter Function +======================================== The ``from_jao`` function allows users to convert the Static Grid Model provided by JAO (Joint Allocation Office) into a pandapower network by reading and processing the provided Excel and HTML files. diff --git a/doc/converter/ucte.rst b/doc/converter/ucte.rst new file mode 100644 index 000000000..ed048dfa9 --- /dev/null +++ b/doc/converter/ucte.rst @@ -0,0 +1,12 @@ +=================================================== +UCTE-DEF to pandapower +=================================================== + +panpapower functionality allows to convert grid data from **UCTE** **d**ata **e**xchange **f**ormat for load flow and three phase short circuit studies (UCTE-DEF) to pandapower. +The UCTE-DEF is a simple data format for electric transmission systems for the exchange of grid data introduced by the *Union for the Co-ordination of Transmission of Electricity (UCTE)*, the predecessor of the ENTSO-E. + +Using the Converter +-------------------- +In order to start the converter the following method is used. Only the location of the UCTE file that should be converted must be specified. + +.. autofunction:: pandapower.converter.cim.cim2pp.from_cim.from_cim diff --git a/doc/pics/e2n.png b/doc/pics/e2n.png index 72304b07c..65acd2eb9 100644 Binary files a/doc/pics/e2n.png and b/doc/pics/e2n.png differ diff --git a/pandapower/converter/__init__.py b/pandapower/converter/__init__.py index 5df505f2a..7b5db8d65 100644 --- a/pandapower/converter/__init__.py +++ b/pandapower/converter/__init__.py @@ -1,6 +1,7 @@ from pandapower.converter.matpower import * from pandapower.converter.pypower import * from pandapower.converter.pandamodels import * +from pandapower.converter.ucte import * from pandapower.converter.cim import * from pandapower.converter.powerfactory import * from pandapower.converter.jao import * diff --git a/pandapower/converter/cim/__init__.py b/pandapower/converter/cim/__init__.py index a7ef76e57..afe66a389 100644 --- a/pandapower/converter/cim/__init__.py +++ b/pandapower/converter/cim/__init__.py @@ -2,6 +2,7 @@ # Copyright (c) 2016-2023 by University of Kassel and Fraunhofer Institute for Energy Economics # and Energy System Technology (IEE), Kassel. All rights reserved. + from .cim2pp import from_cim __version__ = '3.6.10' diff --git a/pandapower/converter/cim/cim2pp/from_cim.py b/pandapower/converter/cim/cim2pp/from_cim.py index d93dd8d21..cecf1c02e 100644 --- a/pandapower/converter/cim/cim2pp/from_cim.py +++ b/pandapower/converter/cim/cim2pp/from_cim.py @@ -2,6 +2,7 @@ # Copyright (c) 2016-2023 by University of Kassel and Fraunhofer Institute for Energy Economics # and Energy System Technology (IEE), Kassel. All rights reserved. + import logging import time from typing import Union, List, Type, Dict @@ -23,7 +24,7 @@ def from_cim_dict(cim_parser: cim_classes.CimParser, log_debug=False, convert_li custom_converter_classes: Dict = None, **kwargs) -> pandapower.auxiliary.pandapowerNet: """ - Create a pandapower net from a CIM data structure. + Creates a pandapower net from a CIM data structure. :param cim_parser: The CimParser with parsed cim data. :param log_debug: Set this parameter to True to enable logging at debug level. Optional, default: False @@ -105,7 +106,7 @@ def from_cim(file_list: List[str] = None, encoding: str = None, convert_line_to_ pandapower.auxiliary.pandapowerNet: # Nur zum Testen, kann wieder gelöscht werden """ - Convert a CIM net to a pandapower net from XML files. + Converts a CIM net to a pandapower net from XML files. Additional parameters for kwargs: - create_measurements (str): Set this parameter to 'SV' to create measurements for the pandapower net from the SV profile. Set it to 'Analog' to create measurements from Analogs. If the parameter is not set or is set to None, no diff --git a/pandapower/converter/powerfactory/pp_import_functions.py b/pandapower/converter/powerfactory/pp_import_functions.py index cac736d4b..494803c1f 100644 --- a/pandapower/converter/powerfactory/pp_import_functions.py +++ b/pandapower/converter/powerfactory/pp_import_functions.py @@ -187,35 +187,35 @@ def from_pf( for n, fuse in enumerate(dict_net['RelFuse'], 1): create_coup(net=net, item=fuse, is_fuse=True) if n > 0: logger.info('imported %d fuses' % n) - + logger.debug('creating shunts') # create shunts (ElmShnt): n = 0 for n, shunt in enumerate(dict_net['ElmShnt'], 1): create_shunt(net=net, item=shunt) if n > 0: logger.info('imported %d shunts' % n) - + logger.debug('creating impedances') # create zpu (ElmZpu): n = 0 for n, zpu in enumerate(dict_net['ElmZpu'], 1): create_zpu(net=net, item=zpu) if n > 0: logger.info('imported %d impedances' % n) - + logger.debug('creating series inductivity as impedance') # create series inductivity as impedance (ElmSind): n = 0 for n, sind in enumerate(dict_net['ElmSind'], 1): create_sind(net=net, item=sind) if n > 0: logger.info('imported %d SIND' % n) - + logger.debug('creating series capacity as impedance') # create series capacity as impedance (ElmScap): n = 0 for n, scap in enumerate(dict_net['ElmScap'], 1): create_scap(net=net, item=scap) if n > 0: logger.info('imported %d SCAP' % n) - + logger.debug('creating static var compensator') # create static var compensator (SVC) with control same as voltage controlled synchron machine (ElmSvs): n = 0 @@ -223,7 +223,7 @@ def from_pf( create_svc(net=net, item=svc, pv_as_slack=pv_as_slack, pf_variable_p_gen=pf_variable_p_gen, dict_net=dict_net) if n > 0: logger.info('imported %d SVC' % n) - + # create vac (ElmVac): n = 0 for n, vac in enumerate(dict_net['ElmVac'], 1): @@ -648,7 +648,7 @@ def create_connection_switches(net, item, number_switches, et, buses, elements): closed=switch_is_closed, type=switch_usage, name=switch_name) net.res_switch.loc[cd, ['pf_closed', 'pf_in_service']] = switch_is_closed, True new_switch_idx.append(cd) - new_switch_closed.append(switch_is_closed) + new_switch_closed.append(switch_is_closed) return new_switch_idx, new_switch_closed @@ -775,11 +775,11 @@ def create_line(net, item, flag_graphics, create_sections, is_unbalanced): new_elements = (sid_list[0], sid_list[-1]) new_switch_idx, new_switch_closed = create_connection_switches(net, item, 2, 'l', (params['bus1'], params['bus2']), new_elements) - # correct in_service of lines if station switch is open - # update_in_service_depending_station_switch(net, element_type="line", - # new_elements=new_elements, + # correct in_service of lines if station switch is open + # update_in_service_depending_station_switch(net, element_type="line", + # new_elements=new_elements, # new_switch_idx=new_switch_idx, - # new_switch_closed=new_switch_closed) + # new_switch_closed=new_switch_closed) logger.debug('line <%s> created' % params['name']) @@ -1989,13 +1989,13 @@ def create_sgen_genstat(net, item, pv_as_slack, pf_variable_p_gen, dict_net, is_ logger.debug('av_mode: %s - creating as gen' % av_mode) params.vm_pu = item.usetp del params['q_mvar'] - + # add reactive and active power limits params.min_q_mvar = item.cQ_min params.max_q_mvar = item.cQ_max params.min_p_mw = item.Pmin_uc params.max_p_mw = item.Pmax_uc - + sg = pp.create_gen(net, **params) element = 'gen' else: @@ -2008,7 +2008,7 @@ def create_sgen_genstat(net, item, pv_as_slack, pf_variable_p_gen, dict_net, is_ params.max_q_mvar = item.cQ_max params.min_p_mw = item.Pmin_uc params.max_p_mw = item.Pmax_uc - + sg = pp.create_sgen(net, **params) element = 'sgen' logger.debug('created sgen at index <%d>' % sg) @@ -2214,31 +2214,31 @@ def create_sgen_sym(net, item, pv_as_slack, pf_variable_p_gen, dict_net, export_ logger.debug('creating sym %s as gen' % name) vm_pu = item.usetp if item.iqtype == 1: - type = item.typ_id + type = item.typ_id sid = pp.create_gen(net, bus=bus1, p_mw=p_mw, vm_pu=vm_pu, - min_q_mvar=type.Q_min, max_q_mvar=type.Q_max, + min_q_mvar=type.Q_min, max_q_mvar=type.Q_max, min_p_mw=item.Pmin_uc, max_p_mw=item.Pmax_uc, name=name, type=cat, in_service=in_service, scaling=global_scaling) else: sid = pp.create_gen(net, bus=bus1, p_mw=p_mw, vm_pu=vm_pu, - min_q_mvar=item.cQ_min, max_q_mvar=item.cQ_max, + min_q_mvar=item.cQ_min, max_q_mvar=item.cQ_max, min_p_mw=item.Pmin_uc, max_p_mw=item.Pmax_uc, - name=name, type=cat, in_service=in_service, scaling=global_scaling) + name=name, type=cat, in_service=in_service, scaling=global_scaling) element = 'gen' elif av_mode == 'constq': q_mvar = ngnum * item.qgini * multiplier if item.iqtype == 1: - type = item.typ_id + type = item.typ_id sid = pp.create_sgen(net, bus=bus1, p_mw=p_mw, q_mvar=q_mvar, - min_q_mvar=type.Q_min, max_q_mvar=type.Q_max, + min_q_mvar=type.Q_min, max_q_mvar=type.Q_max, min_p_mw=item.Pmin_uc, max_p_mw=item.Pmax_uc, name=name, type=cat, in_service=in_service, scaling=global_scaling) else: sid = pp.create_sgen(net, bus=bus1, p_mw=p_mw, q_mvar=q_mvar, - min_q_mvar=item.cQ_min, max_q_mvar=item.cQ_max, + min_q_mvar=item.cQ_min, max_q_mvar=item.cQ_max, min_p_mw=item.Pmin_uc, max_p_mw=item.Pmax_uc, - name=name, type=cat, in_service=in_service, scaling=global_scaling) - + name=name, type=cat, in_service=in_service, scaling=global_scaling) + element = 'sgen' if sid is None or element is None: @@ -2478,15 +2478,15 @@ def create_trafo(net, item, export_controller=True, tap_opt="nntap", is_unbalanc get_pf_trafo_results(net, item, tid, is_unbalanced) # adding switches - # False if open, True if closed, None if no switch + # False if open, True if closed, None if no switch new_elements = (tid, tid) - new_switch_idx, new_switch_closed = create_connection_switches(net, item, 2, 't', (bus1, bus2), + new_switch_idx, new_switch_closed = create_connection_switches(net, item, 2, 't', (bus1, bus2), new_elements) - # correct in_service of trafo if station switch is open - # update_in_service_depending_station_switch(net, element_type="trafo", - # new_elements=new_elements, + # correct in_service of trafo if station switch is open + # update_in_service_depending_station_switch(net, element_type="trafo", + # new_elements=new_elements, # new_switch_idx=new_switch_idx, - # new_switch_closed=new_switch_closed) + # new_switch_closed=new_switch_closed) # adding tap changer if (export_controller and pf_type.itapch and item.HasAttribute('ntrcn') and @@ -2617,7 +2617,8 @@ def create_trafo3w(net, item, tap_opt='nntap'): 'shift_mv_degree': -(pf_type.nt3ag_h - pf_type.nt3ag_m) * 30, 'shift_lv_degree': -(pf_type.nt3ag_h - pf_type.nt3ag_l) * 30, 'tap_at_star_point': pf_type.itapos == 0, - 'in_service': not bool(item.outserv) + 'in_service': not bool(item.outserv), + 'parallel': pf_type.nt3nm, } if item.nt3nm != 1: @@ -2661,12 +2662,12 @@ def create_trafo3w(net, item, tap_opt='nntap'): new_elements = (tid, tid, tid) new_switch_idx, new_switch_closed = create_connection_switches(net, item, 3, 't3', (bus1, bus2, bus3), new_elements) - - # correct in_service of trafo3w if station switch is open - # update_in_service_depending_station_switch(net, element_type="trafo3w", - # new_elements=new_elements, + + # correct in_service of trafo3w if station switch is open + # update_in_service_depending_station_switch(net, element_type="trafo3w", + # new_elements=new_elements, # new_switch_idx=new_switch_idx, - # new_switch_closed=new_switch_closed) + # new_switch_closed=new_switch_closed) logger.debug('successfully created trafo3w from parameters: %d' % tid) # testen @@ -2904,7 +2905,7 @@ def create_zpu(net, item): } logger.debug('params = %s' % params) - + # create auxilary buses aux_bus1 = pp.create_bus(net, vn_kv=net.bus.vn_kv.at[bus1], name=net.bus.name.at[bus1]+'_aux', type="b", zone=net.bus.zone.at[bus1], in_service=True) @@ -2914,16 +2915,16 @@ def create_zpu(net, item): type="b", zone=net.bus.zone.at[bus2], in_service=True) net.bus.loc[aux_bus2, 'geo'] = net.bus.geo.at[bus2] params['to_bus'] = aux_bus2 - + xid = pp.create_impedance(net, **params) add_additional_attributes(item, net, element='impedance', element_id=xid, attr_list=["cpSite.loc_name"], attr_dict={"cimRdfId": "origin_id"}) - + # consider and create station switches new_elements = (aux_bus1, aux_bus2) new_switch_idx, new_switch_closed = create_connection_switches(net, item, 2, 'b', (bus1, bus2), new_elements) - + if len(new_switch_idx)==0: net.impedance.loc[xid, 'from_bus'] = bus1 net.impedance.loc[xid, 'to_bus'] = bus2 @@ -2939,13 +2940,13 @@ def create_zpu(net, item): net.impedance.loc[xid, 'from_bus'] = bus1 # drop one auxilary bus, where no switch exists, not needed pp.drop_buses(net, buses=[aux_bus1]) - - # correct in_service of series reactor if station switch is open - # update_in_service_depending_station_switch(net, element_type="impedance", - # new_elements=new_elements, + + # correct in_service of series reactor if station switch is open + # update_in_service_depending_station_switch(net, element_type="impedance", + # new_elements=new_elements, # new_switch_idx=new_switch_idx, # new_switch_closed=new_switch_closed) - + logger.debug('created ZPU %s as impedance at index %d' % (net.impedance.at[xid, 'name'], xid)) @@ -2959,7 +2960,7 @@ def create_vac(net, item): except IndexError: logger.error("Cannot add VAC '%s': not connected" % item.loc_name) return - + in_service = monopolar_in_service(item) params = { 'name': item.loc_name, @@ -3041,25 +3042,25 @@ def create_sind(net, item): except IndexError: logger.error("Cannot add Sind '%s': not connected" % item.loc_name) return - - # create auxilary buses + + # create auxilary buses aux_bus1 = pp.create_bus(net, vn_kv=net.bus.vn_kv.at[bus1], name=net.bus.name.at[bus1]+'_aux', type="b", zone=net.bus.zone.at[bus1], in_service=True) net.bus.loc[aux_bus1, 'geo'] = net.bus.geo.at[bus1] aux_bus2 = pp.create_bus(net, vn_kv=net.bus.vn_kv.at[bus2], name=net.bus.name.at[bus2]+'_aux', - type="b", zone=net.bus.zone.at[bus2], in_service=True) - net.bus.loc[aux_bus2, 'geo'] = net.bus.geo.at[bus2] - - sind = pp.create_series_reactor_as_impedance(net, from_bus=aux_bus1, to_bus=aux_bus2, + geodata=net.bus.geo.at[bus2], type="b", zone=net.bus.zone.at[bus2], + in_service=True) + + sind = pp.create_series_reactor_as_impedance(net, from_bus=aux_bus1, to_bus=aux_bus2, r_ohm=item.rrea, x_ohm=item.xrea, sn_mva=item.Sn, name=item.loc_name, in_service=not bool(item.outserv)) - + # consider and create station switches new_elements = (aux_bus1, aux_bus2) new_switch_idx, new_switch_closed = create_connection_switches(net, item, 2, 'b', (bus1, bus2), new_elements) - + if len(new_switch_idx)==0: net.impedance.loc[sind, 'from_bus'] = bus1 net.impedance.loc[sind, 'to_bus'] = bus2 @@ -3075,13 +3076,13 @@ def create_sind(net, item): net.impedance.loc[sind, 'from_bus'] = bus1 # drop one auxilary bus, where no switch exists, not needed pp.drop_buses(net, buses=[aux_bus1]) - - # correct in_service of series reactor if station switch is open - # update_in_service_depending_station_switch(net, element_type="impedance", - # new_elements=new_elements, + + # correct in_service of series reactor if station switch is open + # update_in_service_depending_station_switch(net, element_type="impedance", + # new_elements=new_elements, # new_switch_idx=new_switch_idx, # new_switch_closed=new_switch_closed) - + logger.debug('created series reactor %s as per unit impedance at index %d' % (net.impedance.at[sind, 'name'], sind)) @@ -3100,25 +3101,25 @@ def create_scap(net, item): else: r_ohm = item.gcap/(item.gcap**2 + item.bcap**2) x_ohm = -item.bcap/(item.gcap**2 + item.bcap**2) - - # create auxilary buses + + # create auxilary buses aux_bus1 = pp.create_bus(net, vn_kv=net.bus.vn_kv.at[bus1], name=net.bus.name.at[bus1]+'_aux', type="b", zone=net.bus.zone.at[bus1], in_service=True) net.bus.loc[aux_bus1, 'geo'] = net.bus.geo.at[bus1] aux_bus2 = pp.create_bus(net, vn_kv=net.bus.vn_kv.at[bus2], name=net.bus.name.at[bus2]+'_aux', type="b", zone=net.bus.zone.at[bus2], in_service=True) net.bus.loc[aux_bus2, 'geo'] = net.bus.geo.at[bus2] - + scap = pp.create_series_reactor_as_impedance(net, from_bus=aux_bus1, to_bus=aux_bus2, r_ohm=r_ohm, x_ohm=x_ohm, sn_mva=item.Sn, name=item.loc_name, in_service=not bool(item.outserv)) - + # consider and create station switches new_elements = (aux_bus1, aux_bus2) new_switch_idx, new_switch_closed = create_connection_switches(net, item, 2, 'b', (bus1, bus2), new_elements) - + if len(new_switch_idx)==0: net.impedance.loc[scap, 'from_bus'] = bus1 net.impedance.loc[scap, 'to_bus'] = bus2 @@ -3134,32 +3135,32 @@ def create_scap(net, item): net.impedance.loc[scap, 'from_bus'] = bus1 # drop one auxilary bus, where no switch exists, not needed pp.drop_buses(net, buses=[aux_bus1]) - - # correct in_service of series capacitor if station switch is open - # update_in_service_depending_station_switch(net, element_type="impedance", - # new_elements=new_elements, + + # correct in_service of series capacitor if station switch is open + # update_in_service_depending_station_switch(net, element_type="impedance", + # new_elements=new_elements, # new_switch_idx=new_switch_idx, # new_switch_closed=new_switch_closed) logger.debug('created series capacitor %s as per unit impedance at index %d' % (net.impedance.at[scap, 'name'], scap)) - + def create_svc(net, item, pv_as_slack, pf_variable_p_gen, dict_net): # SVC is voltage controlled and therefore modelled the same way as a voltage controlled synchron machine (gen) # TODO: at least implement a uncontrolled svc as synchron machine with const. Q - # TODO: transfer item entries for usage of pp.create_svc, x_l_ohm, x_cvar_ohm, + # TODO: transfer item entries for usage of pp.create_svc, x_l_ohm, x_cvar_ohm, # thyristor_firing_angle must be computed name = item.loc_name sid = None element = None logger.debug('>> creating synchronous machine <%s>' % name) - + try: bus1 = get_connection_nodes(net, item, 1) except IndexError: logger.error("Cannot add SVC '%s': not connected" % name) return - + if item.i_ctrl==1: # 0: no control, 1: voltage control, 2: reactive power control logger.debug('creating SVC %s as gen' % name) vm_pu = item.usetp @@ -3167,15 +3168,15 @@ def create_svc(net, item, pv_as_slack, pf_variable_p_gen, dict_net): svc = pp.create_gen(net, bus=bus1, p_mw=0, vm_pu=vm_pu, name=name, type="SVC", in_service=in_service) element = 'gen' - + if svc is None or element is None: logger.error('Error! SVC not created') logger.debug('created svc at index <%s>' % svc) - + net[element].loc[svc, 'description'] = ' \n '.join(item.desc) if len(item.desc) > 0 else '' add_additional_attributes(item, net, element, svc, attr_dict={"for_name": "equipment"}, attr_list=["sernum", "chr_name", "cpSite.loc_name"]) - + if item.HasResults(0): # 'm' results... logger.debug('<%s> has results' % name) net['res_' + element].at[svc, "pf_p"] = ga(item, 'm:P:bus1') #* multiplier @@ -3183,7 +3184,7 @@ def create_svc(net, item, pv_as_slack, pf_variable_p_gen, dict_net): else: net['res_' + element].at[svc, "pf_p"] = np.nan net['res_' + element].at[svc, "pf_q"] = np.nan - else: + else: logger.info('not creating SVC for %s' % item.loc_name) diff --git a/pandapower/converter/ucte/__init__.py b/pandapower/converter/ucte/__init__.py new file mode 100644 index 000000000..dd5bf5bc5 --- /dev/null +++ b/pandapower/converter/ucte/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016-2023 by University of Kassel and Fraunhofer Institute for Energy Economics +# and Energy System Technology (IEE), Kassel. All rights reserved. + +from .from_ucte import from_ucte diff --git a/pandapower/converter/ucte/from_ucte.py b/pandapower/converter/ucte/from_ucte.py new file mode 100644 index 000000000..2e64a56e6 --- /dev/null +++ b/pandapower/converter/ucte/from_ucte.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016-2023 by University of Kassel and Fraunhofer Institute for Energy Economics +# and Energy System Technology (IEE), Kassel. All rights reserved. + +import logging +import os +import time +from typing import Union, List, Type, Dict +from pandapower.converter.ucte.ucte_converter import UCTE2pandapower +from pandapower.converter.ucte.ucte_parser import UCTEParser + +logger = logging.getLogger('ucte.from_ucte') + + +def from_ucte_dict(ucte_parser: UCTEParser): + """Creates a pandapower net from an UCTE data structure. + + Parameters + ---------- + ucte_parser : UCTEParser + The UCTEParser with parsed UCTE data. + + Returns + ------- + pandapowerNet + net + """ + + ucte_converter = UCTE2pandapower() + net = ucte_converter.convert(ucte_parser.get_data()) + + return net + + +def from_ucte(ucte_file: str): + """Converts net data stored as an UCTE file to a pandapower net. + + + Parameters + ---------- + ucte_file : str + path to the ucte file which includes all the data of the grid (EHV or HV or both) + + Returns + ------- + pandapowerNet + net + """ + + # Note: + # the converter functionality from_ucte() and internal functions are structured similar to + # the cim converter + + time_start_parsing = time.time() + + ucte_parser = UCTEParser(ucte_file) + ucte_parser.parse_file() + + time_start_converting = time.time() + + pp_net = from_ucte_dict(ucte_parser) + + time_end_converting = time.time() + + logger.info("Needed time for parsing from ucte: %s" % (time_start_converting - time_start_parsing)) + logger.info("Needed time for converting from ucte: %s" % (time_end_converting - time_start_converting)) + logger.info("Total Time (from_ucte()): %s" % (time_end_converting - time_start_parsing)) + + return pp_net diff --git a/pandapower/converter/ucte/ucte_converter.py b/pandapower/converter/ucte/ucte_converter.py new file mode 100644 index 000000000..14484c115 --- /dev/null +++ b/pandapower/converter/ucte/ucte_converter.py @@ -0,0 +1,727 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016-2024 by University of Kassel and Fraunhofer Institute for Energy Economics +# and Energy System Technology (IEE), Kassel. All rights reserved. + +import logging +import math +import time +from typing import Dict, Union + +import numpy as np +import pandapower as pp +import pandapower.auxiliary +import pandas as pd + + +class UCTE2pandapower: + def __init__(self): + """ + Convert UCTE data to pandapower. + """ + self.logger = logging.getLogger(self.__class__.__name__) + self.u_d = dict() + self.net = self._create_empty_network() + self.net.bus["node_name"] = "" + + @staticmethod + def _create_empty_network(): + net: pandapower.auxiliary.pandapowerNet = pp.create_empty_network() + new_columns = { + "trafo": { + "tap2_min": int, + "tap2_max": int, + "tap2_neutral": int, + "tap2_pos": int, + "tap2_step_percent": float, + "tap2_step_degree": float, + "tap2_side": str, + "tap2_phase_shifter": bool, + "amica_name": str, + }, + "line": {"amica_name": str}, + } + for pp_element in new_columns.keys(): + for col, dtype in new_columns[pp_element].items(): + net[pp_element][col] = pd.Series(dtype=dtype) + return net + + def convert(self, ucte_dict: Dict) -> pandapower.auxiliary.pandapowerNet: + self.logger.info("Converting UCTE data to a pandapower network.") + time_start = time.time() + # create a temporary copy from the origin input data + self.u_d = dict() + for ucte_element, items in ucte_dict.items(): + self.u_d[ucte_element] = items.copy() + # first reset the index to get indices for pandapower + for ucte_element in self.u_d.keys(): + if ucte_element == "R": + continue + self.u_d[ucte_element] = self.u_d[ucte_element].reset_index(level=0) + self.u_d[ucte_element] = self.u_d[ucte_element].rename( + columns={"index": "id"} + ) + # now replace the node1 and node2 columns with the node index at lines, transformers, ... + merge_nodes = self.u_d["N"][["id", "node"]] + self.u_d["L"] = pd.merge( + self.u_d["L"], + merge_nodes.rename(columns={"node": "node1", "id": "from_bus"}), + how="left", + on="node1", + ) + self.u_d["L"] = pd.merge( + self.u_d["L"], + merge_nodes.rename(columns={"node": "node2", "id": "to_bus"}), + how="left", + on="node2", + ) + self.u_d["L"] = self.u_d["L"].drop(columns=["node1", "node2"]) + for one_asset in ["T", "R", "TT"]: + self.u_d[one_asset] = pd.merge( + self.u_d[one_asset], + merge_nodes.rename(columns={"node": "node1", "id": "hv_bus"}), + how="left", + on="node1", + ) + self.u_d[one_asset] = pd.merge( + self.u_d[one_asset], + merge_nodes.rename(columns={"node": "node2", "id": "lv_bus"}), + how="left", + on="node2", + ) + self.u_d[one_asset] = self.u_d[one_asset].drop(columns=["node1", "node2"]) + + # prepare the element tables + self._convert_nodes() + self._convert_loads() + self._convert_gens() + self._convert_lines() + self._convert_impedances() + self._convert_switches() + self._convert_trafos() + + # copy data to the element tables of self.net + self.net = self.set_pp_col_types(self.net) + + # currently, net.bus.name contains the UCTE node name ("Node"), while + # net.bus.node_name contains the original node name "Node Name". This is changed now: + cols_in_order = list(pd.Series(self.net.bus.columns).replace("node_name", "ucte_name")) + self.net.bus = self.net.bus.rename(columns={"name": "ucte_name", "node_name": "name"})[ + cols_in_order] + + self.logger.info( + "Finished converting the input data to pandapower in %ss." + % (time.time() - time_start) + ) + return self.net + + def _copy_to_pp(self, pp_type: str, input_df: pd.DataFrame): + self.logger.debug( + "Copy %s datasets to pandapower network with type %s" + % (input_df.index.size, pp_type) + ) + if pp_type not in self.net.keys(): + self.logger.warning( + "Missing pandapower type %s in the pandapower network!" % pp_type + ) + return + self.net[pp_type] = pd.concat( + [ + self.net[pp_type], + input_df[ + list(set(self.net[pp_type].columns).intersection(input_df.columns)) + ], + ], + ignore_index=True, + sort=False, + ) + + def _convert_nodes(self): + self.logger.info("Converting the nodes.") + nodes = self.u_d[ + "N" + ] # Note: Do not use a copy, the columns 'volt_str' and 'node_name' are needed later + nodes["volt_str"] = nodes["node"].str[6:7] + volt_map = { + "0": 750, + "1": 380, + "2": 220, + "3": 150, + "4": 120, + "5": 110, + "6": 70, + "7": 27, + "8": 330, + "9": 500, + "A": 26, + "B": 25, + "C": 24, + "D": 23, + "E": 22, + "F": 21, + "G": 20, + "H": 19, + "I": 18, + "J": 17, + "K": 15.7, + "L": 15, + "M": 13.7, + "N": 13, + "O": 12, + "P": 11, + "Q": 9.8, + "R": 9, + "S": 8, + "T": 7, + "U": 6, + "V": 5, + "W": 4, + "X": 3, + "Y": 2, + "Z": 1, + } + nodes["vn_kv"] = nodes["volt_str"].map(volt_map) + nodes["node2"] = nodes["node"].str[:6] + nodes["grid_area_id"] = nodes["node"].str[:2] + # drop all voltages at non pu nodes + nodes.loc[ + (nodes["node_type"] != 2) & (nodes["node_type"] != 3), "voltage" + ] = np.nan + nodes = nodes.rename(columns={"node": "name"}) + nodes["in_service"] = True + self._copy_to_pp("bus", nodes) + self.logger.info("Finished converting the nodes.") + + def _convert_loads(self): + self.logger.info("Converting the loads.") + # select the loads from the nodes and drop not given values + loads = self.u_d["N"].dropna(subset=["p_load", "q_load"]) + # select all with p != 0 or q != 0 + loads = loads.loc[(loads["p_load"] != 0) | (loads["q_load"] != 0)] + if not len(loads): + self.logger.info("Finished converting the loads (no loads existing).") + return # Acceleration + loads = loads.rename( + columns={"id": "bus", "node": "name", "p_load": "p_mw", "q_load": "q_mvar"} + ) + # get a new index + loads = loads.reset_index(level=0, drop=True) + loads["scaling"] = 1 + loads["in_service"] = True + loads["const_z_percent"] = 0 + loads["const_i_percent"] = 0 + self._copy_to_pp("load", loads) + self.logger.info("Finished converting the loads.") + + def _convert_gens(self): + self.logger.info("Converting the generators.") + # select the gens from the nodes and drop not given values + gens = self.u_d["N"].dropna(subset=["p_gen", "q_gen"]) + # select all with p != 0 or q != 0 or voltage != 0 + gens = gens.loc[ + (gens["p_gen"] != 0) | (gens["q_gen"] != 0) | (gens["voltage"] > 0) + ] + # change the signing + gens["p_gen"] = gens["p_gen"] * -1 + gens["q_gen"] = gens["q_gen"] * -1 + gens["min_p_gen"] = gens["min_p_gen"] * -1 + gens["max_p_gen"] = gens["max_p_gen"] * -1 + gens["min_q_gen"] = gens["min_q_gen"] * -1 + gens["max_q_gen"] = gens["max_q_gen"] * -1 + # drop all voltages at non pu nodes + gens.loc[ + (gens["node_type"] != 2) & (gens["node_type"] != 3), "voltage" + ] = np.nan + gens["vm_pu"] = gens["voltage"] / gens["vn_kv"] + gens = gens.rename( + columns={ + "id": "bus", + "node": "name", + "p_gen": "p_mw", + "q_gen": "q_mvar", + "min_p_gen": "min_p_mw", + "max_p_gen": "max_p_mw", + "min_q_gen": "min_q_mvar", + "max_q_gen": "max_q_mvar", + } + ) + # get a new index + gens = gens.reset_index(level=0, drop=True) + gens["scaling"] = 1 + gens["va_degree"] = 0 + gens["slack_weight"] = 1 + gens["slack"] = False + gens["current_source"] = True + gens["in_service"] = True + self._copy_to_pp("ext_grid", gens.loc[gens["node_type"] == 3]) + self._copy_to_pp("gen", gens.loc[gens["node_type"] == 2]) + self._copy_to_pp( + "sgen", gens.loc[(gens["node_type"] == 0) | (gens["node_type"] == 1)] + ) + self.logger.info("Finished converting the generators.") + + def _convert_lines(self): + self.logger.info("Converting the lines.") + # get the lines + # status 9 & 1 stands for equivalent line that can be interpreted as impedance + # status 7 & 2 stands for busbar coupler that can be interpreted as switches + lines = self.u_d["L"].loc[self.u_d["L"].status.isin([9, 1, 7, 2]) == False, :] + # definition of busbar coupler is if r, x, b are all zero, but some statuses are wrong, check for those + lines = lines.drop( + lines.loc[(lines.r == 0) & (lines.x == 0) & (lines.b == 0)].index + ) + # also drop lines with x < 0 as they will be modeled as impedances + lines = lines.drop(lines.loc[lines.x < 0].index) + if not len(lines): + self.logger.info("Finished converting the lines (no lines existing).") + return # Acceleration + # lines = self.u_d['L'] + # create the in_service column from the UCTE status + in_service_map = dict({0: True, 1: True, 2: True, 7: False, 8: False, 9: False}) + lines["in_service"] = lines["status"].map(in_service_map) + # i in A to i in kA + lines["max_i_ka"] = lines["i"] / 1e3 + lines["max_i_ka"] = lines["max_i_ka"].fillna(9999) + lines["c_nf_per_km"] = 1e3 * lines["b"] / (2 * np.pi * 50) + lines["g_us_per_km"] = 0 + lines["df"] = 1 + lines["parallel"] = 1 + lines["length_km"] = 1 + self._fill_empty_names(lines) + self._fill_amica_names(lines, ":line") + lines.loc[lines.x == 0, "x"] = 0.01 + # rename the columns to the pandapower schema + lines = lines.rename( + columns={"r": "r_ohm_per_km", "x": "x_ohm_per_km", "name": "name"} + ) + self._copy_to_pp("line", lines) + self.logger.info("Finished converting the lines.") + + def _convert_impedances(self): + self.logger.info("Converting the impedances.") + # get the impedances + # status 9 & 1 stands for equivalent line that can be interpreted as impedance + status_9_1 = self.u_d["L"].status.isin([9, 1]) + # also all lines with negative x convert to impedances + negative_x = self.u_d["L"].x < 0 + impedances = self.u_d["L"].loc[status_9_1 | negative_x, :] + self._set_column_to_type(impedances, "from_bus", int) + impedances = pd.merge( + impedances, + self.u_d["N"][["vn_kv"]], + how="left", + left_on="from_bus", + right_index=True, + ) + + trafos_to_impedances = self._get_trafos_modelled_as_impedances() + impedances = pd.concat([impedances, trafos_to_impedances]) + + # create the in_service column from the UCTE status + in_service_map = dict({0: True, 1: True, 2: True, 7: False, 8: False, 9: False}) + impedances["in_service"] = impedances["status"].map(in_service_map) + # Convert ohm/km to per unit (pu) + impedances["sn_mva"] = 10000 # same as PowerFactory + impedances["z_ohm"] = impedances["vn_kv"] ** 2 / impedances["sn_mva"] + impedances["rft_pu"] = impedances["r"] / impedances["z_ohm"] + impedances["rtf_pu"] = impedances["r"] / impedances["z_ohm"] + impedances["xft_pu"] = impedances["x"] / impedances["z_ohm"] + impedances["xtf_pu"] = impedances["x"] / impedances["z_ohm"] + self._fill_empty_names(impedances) + self._copy_to_pp("impedance", impedances) + self.logger.info("Finished converting the impedances.") + + def _get_trafos_modelled_as_impedances(self): + ### get transformers that will be transformed to impedances and append them ### + trafos = pd.merge( + self.u_d["T"], + self.u_d["R"], + how="left", + on=["hv_bus", "lv_bus", "order_code"], + ) + + # check for trafos connecting same voltage levels + trafos_to_impedances = trafos.loc[ + trafos.loc[:, "0_x"].map(lambda s: s[6]) + == trafos.loc[:, "0_x"].map(lambda s: s[15]) + ] + + trafos_to_impedances = trafos_to_impedances.loc[ + trafos_to_impedances.phase_reg_delta_u.isnull() + ] + trafos_to_impedances = trafos_to_impedances.loc[ + trafos_to_impedances.angle_reg_theta.isnull() + ] + # calculate iron losses in kW + trafos_to_impedances["pfe_kw"] = ( + trafos_to_impedances.g * trafos_to_impedances.voltage1**2 / 1e3 + ) + # calculate open loop losses in percent of rated current + trafos_to_impedances["i0_percent"] = ( + ( + ( + (trafos_to_impedances.b * 1e-6 * trafos_to_impedances.voltage1**2) + ** 2 + + ( + trafos_to_impedances.g + * 1e-6 + * trafos_to_impedances.voltage1**2 + ) + ** 2 + ) + ** 0.5 + ) + * 100 + / trafos_to_impedances.s + ) + trafos_to_impedances = trafos_to_impedances.loc[ + (trafos_to_impedances.pfe_kw == 0) & (trafos_to_impedances.i0_percent == 0) + ] + # rename the columns to the pandapower schema, as voltages are the same we can take voltage1 as vn_kv + trafos_to_impedances = trafos_to_impedances.rename( + columns={ + "hv_bus": "from_bus", + "lv_bus": "to_bus", + "voltage1": "vn_kv", + "0_x": 0, + } + ) + return trafos_to_impedances + + def _convert_switches(self): + self.logger.info("Converting the switches.") + # get the switches + # status 7 & 2 stands for busbar coupler that can be interpreted as switch + switches_by_status = self.u_d["L"].status.isin([7, 2]) + # switches are defined by r, x and b equal 0, but some still have the wrong status (or r, x and b not zero) + lines_rxb_zero = ( + (self.u_d["L"].r == 0) & (self.u_d["L"].x == 0) & (self.u_d["L"].b == 0) + ) + switches = self.u_d["L"].loc[lines_rxb_zero | switches_by_status, :] + + # create the in_service column from the UCTE status + in_service_map = dict({0: True, 1: True, 2: True, 7: False, 8: False, 9: False}) + switches["closed"] = switches["status"].map(in_service_map) + self._set_column_to_type(switches, "from_bus", int) + switches["type"] = "LS" + switches["et"] = "b" + switches["z_ohm"] = 0 + self._fill_empty_names(switches) + switches = switches.rename(columns={"from_bus": "bus", "to_bus": "element"}) + self._copy_to_pp("switch", switches) + self.logger.info("Finished converting the switches.") + + def _convert_trafos(self): + self.logger.info("Converting the transformers.") + trafos = pd.merge( + self.u_d["T"], + self.u_d["R"], + how="left", + on=["hv_bus", "lv_bus", "order_code"], + ) + if not len(trafos): + self.logger.info("Finished converting the transformers (no transformers existing).") + return + # create the in_service column from the UCTE status + status_map = dict({0: True, 1: True, 8: False, 9: False}) + trafos["in_service"] = trafos["status"].map(status_map) + # use same value as in powerfactory for replacing s equals zero values + trafos.loc[trafos.s == 0, "s"] = 1001 + # calculate the derating factor + trafos["df"] = trafos["voltage1"] * (trafos["i"] / 1e3) * 3**0.5 / trafos["s"] + # calculate the relative short-circuit voltage + trafos["vk_percent"] = ( + np.sign(trafos.x) + * (abs(trafos.r) ** 2 + abs(trafos.x) ** 2) ** 0.5 + * (trafos.s * 1e3) + / (10.0 * trafos.voltage1**2) + ) + # calculate vkr_percent + trafos["vkr_percent"] = trafos.r * trafos.s * 100 / trafos.voltage1**2 + # calculate iron losses in kW + trafos["pfe_kw"] = trafos.g * trafos.voltage1**2 / 1e3 + # calculate open loop losses in percent of rated current + trafos["i0_percent"] = ( + ( + ( + (trafos.b * 1e-6 * trafos.voltage1**2) ** 2 + + (trafos.g * 1e-6 * trafos.voltage1**2) ** 2 + ) + ** 0.5 + ) + * 100 + / trafos.s + ) + + # phase and angle regulation have to be split up into 5 cases: + # only phase regulated -> pr + # only angle regulated symmetrical model -> ars + # only angle regulated asymmetrical model -> ara + # phase and angle regulated symmetrical model -> pars + # phase and angle regulated asymmetrical model -> para + # set values for only phase regulated transformers (pr) + has_phase_values = ( + (~trafos.phase_reg_delta_u.isnull()) + & (~trafos.phase_reg_n.isnull()) + & (~trafos.phase_reg_n2.isnull()) + ) + has_missing_angle_values = ( + trafos.angle_reg_delta_u.isnull() + | trafos.angle_reg_theta.isnull() + | trafos.angle_reg_n.isnull() + | trafos.angle_reg_n2.isnull() + ) + pr = trafos.loc[has_phase_values & has_missing_angle_values].index + + trafos.loc[pr, "tap_min"] = -trafos["phase_reg_n"] + trafos.loc[pr, "tap_max"] = trafos["phase_reg_n"] + trafos.loc[pr, "tap_pos"] = trafos["phase_reg_n2"] + trafos.loc[pr, "tap_step_percent"] = trafos.loc[pr, "phase_reg_delta_u"].abs() + trafos.loc[pr, "tap_phase_shifter"] = False + + # set values for only angle regulated transformers symmetrical and asymmetrical + has_missing_phase_values = ( + trafos.phase_reg_delta_u.isnull() + & trafos.phase_reg_n.isnull() + & trafos.phase_reg_n2.isnull() + ) + has_angle_values = ( + (~trafos.angle_reg_delta_u.isnull()) + & (~trafos.angle_reg_theta.isnull()) + & (~trafos.angle_reg_n.isnull()) + & (~trafos.angle_reg_n2.isnull()) + ) + ar = trafos.loc[has_missing_phase_values & has_angle_values].index + + symm = trafos.angle_reg_type == "SYMM" + ars = trafos.loc[has_missing_phase_values & has_angle_values & symm].index + trafos.loc[ars, "tap_min"] = -trafos.loc[ar, "angle_reg_n"] + trafos.loc[ars, "tap_max"] = trafos.loc[ar, "angle_reg_n"] + trafos.loc[ars, "tap_pos"] = trafos.loc[ar, "angle_reg_n2"] + trafos.loc[ars, "tap_step_percent"] = np.nan + # trafos.loc[ars, 'phase_reg_n'] = trafos.loc[ar, 'angle_reg_n'] + trafos.loc[ars, "tap_phase_shifter"] = True + trafos.loc[ + ars, "tap_step_degree" + ] = self._calculate_tap_step_degree_symmetrical(trafos.loc[ars]) + + asym = (trafos.angle_reg_type == "ASYM") | (trafos.angle_reg_type == "") + ara = trafos.loc[has_missing_phase_values & has_angle_values & asym].index + trafos.loc[ara, "tap2_min"] = -trafos.loc[ar, "angle_reg_n"] + trafos.loc[ara, "tap2_max"] = trafos.loc[ar, "angle_reg_n"] + trafos.loc[ara, "tap2_pos"] = trafos.loc[ar, "angle_reg_n2"] + trafos.loc[ara, "tap2_neutral"] = 0 + trafos.loc[ara, "tap2_step_percent"] = np.nan + trafos.loc[ara, "tap2_phase_shifter"] = True + trafos.loc[ + ara, "tap2_step_degree" + ] = self._calculate_tap_step_degree_asymmetrical(trafos.loc[ara]) + + trafos.loc[ara, "tap_min"] = -trafos.loc[ara, "angle_reg_n"] + trafos.loc[ara, "tap_max"] = trafos.loc[ara, "angle_reg_n"] + trafos.loc[ara, "tap_pos"] = trafos.loc[ara, "angle_reg_n2"] + trafos.loc[ara, "tap_phase_shifter"] = False + trafos.loc[ + ara, "tap_step_percent" + ] = self._calculate_tap_step_percent_asymmetrical(trafos.loc[ara]) + + # get phase and angle regulated transformers symmetrical and asymmetrical + par = trafos.loc[has_phase_values & has_angle_values].index + + trafos.loc[par, "tap_step_percent"] = trafos.loc[par, "phase_reg_delta_u"].abs() + trafos.loc[par, "tap_min"] = -trafos.loc[par, "phase_reg_n"] + trafos.loc[par, "tap_max"] = trafos.loc[par, "phase_reg_n"] + trafos.loc[par, "tap_pos"] = trafos.loc[par, "phase_reg_n2"] + trafos.loc[par, "tap_phase_shifter"] = False + + trafos.loc[par, "tap2_min"] = -trafos.loc[par, "angle_reg_n"] + trafos.loc[par, "tap2_max"] = trafos.loc[par, "angle_reg_n"] + trafos.loc[par, "tap2_neutral"] = 0 + trafos.loc[par, "tap2_pos"] = trafos.loc[par, "angle_reg_n2"] + trafos.loc[par, "tap2_step_percent"] = np.nan + trafos.loc[par, "tap2_phase_shifter"] = True + + pars = trafos.loc[has_phase_values & has_angle_values & symm].index + trafos.loc[ + pars, "tap2_step_degree" + ] = self._calculate_tap_step_degree_symmetrical(trafos.loc[pars]) + + para = trafos.loc[has_phase_values & has_angle_values & asym].index + trafos.loc[ + para, "tap2_step_degree" + ] = self._calculate_tap_step_degree_asymmetrical(trafos.loc[para]) + trafos.loc[para, "tap_step_percent"] = trafos.loc[ + para, "tap_step_percent" + ] + self._calculate_tap_step_percent_asymmetrical(trafos.loc[para]) + + # change signs of tap pos for negative degree or percentage values, since pp only allows positive values + # trafos.loc[trafos.tap_step_percent < 0, ['tap_pos', 'tap_step_percent']] = trafos.loc[trafos.tap_step_percent < 0, ['tap_pos', 'tap_step_percent']] * -1 + # trafos.loc[trafos.tap_step_degree < 0, ['tap_pos', 'tap_step_degree']] = trafos.loc[trafos.tap_step_degree < 0, ['tap_pos', 'tap_step_degree']] * -1 + # trafos.loc[trafos.tap2_step_degree < 0, ['tap2_pos', 'tap2_step_degree']] = trafos.loc[trafos.tap2_step_degree < 0, ['tap2_pos', 'tap2_step_degree']] * -1 + + # set the hv and lv voltage sides to voltage1 and voltage2 (The non-regulated transformer side is currently + # voltage1, not the hv side!) + trafos["vn_hv_kv"] = trafos[["voltage1", "voltage2"]].max(axis=1) + trafos["vn_lv_kv"] = trafos[["voltage1", "voltage2"]].min(axis=1) + # swap the 'fid_node_start' and 'fid_node_end' if need + trafos["swap"] = trafos["vn_hv_kv"] != trafos["voltage1"] + # copy the 'fid_node_start' and 'fid_node_end' + trafos["hv_bus2"] = trafos["hv_bus"].copy() + trafos["lv_bus2"] = trafos["lv_bus"].copy() + trafos.loc[trafos.swap, "hv_bus"] = trafos.loc[trafos.swap, "lv_bus2"] + trafos.loc[trafos.swap, "lv_bus"] = trafos.loc[trafos.swap, "hv_bus2"] + # set the tap side, default is lv Correct it for other windings + trafos["tap_side"] = "lv" + trafos["tap2_side"] = "lv" + trafos.loc[trafos.swap, "tap_side"] = "hv" + trafos.loc[trafos.swap, "tap2_side"] = "hv" + # now set it to nan for not existing tap changers + trafos.loc[trafos.phase_reg_n.isnull(), "tap_side"] = None + trafos["tap_neutral"] = 0 + trafos.loc[trafos.phase_reg_n.isnull(), "tap_neutral"] = np.nan + trafos["shift_degree"] = 0 + trafos["parallel"] = 1 + self._fill_empty_names(trafos, "0_x") + self._fill_amica_names(trafos, ":trf", "0_x") + trafos["tap_phase_shifter"] = trafos["tap_phase_shifter"].fillna(False).astype(bool) + trafos["tap2_phase_shifter"] = trafos["tap2_phase_shifter"].fillna(False).astype(bool) + # rename the columns to the pandapower schema + trafos = trafos.rename(columns={"s": "sn_mva"}) + # drop transformers that will be transformed to impedances + trafos_to_impedances = self._get_trafos_modelled_as_impedances() + trafos = trafos.drop(trafos_to_impedances.index) + + self._copy_to_pp("trafo", trafos) + self.logger.info("Finished converting the transformers.") + + def _calculate_tap_step_degree_symmetrical( + self, input_df: pd.DataFrame + ) -> pd.Series: + return input_df["angle_reg_delta_u"].apply(lambda du: 2 * math.atan2(du, 2)) + + def _calculate_tap_step_degree_asymmetrical( + self, input_df: pd.DataFrame + ) -> pd.Series: + numerator = input_df["angle_reg_delta_u"] * input_df["angle_reg_theta"].apply( + lambda t: math.sin(t) + ) + denominator = 1 + ( + input_df["angle_reg_delta_u"] + * input_df["angle_reg_theta"].apply(lambda t: math.cos(t)) + ) + return (numerator / denominator).apply(lambda t: math.atan2(t, 1)) + + def _calculate_tap_step_percent_asymmetrical( + self, input_df: pd.DataFrame + ) -> pd.Series: + sine_sq = ( + input_df["angle_reg_delta_u"] + * input_df["angle_reg_theta"].apply(lambda t: math.sin(t)) + ) ** 2 + cosine_sq = ( + input_df["angle_reg_delta_u"] + * input_df["angle_reg_theta"].apply(lambda t: math.cos(t)) + ) ** 2 + tab_step_percent = (sine_sq + cosine_sq).apply(lambda x: np.sqrt(x)) - 1 + return tab_step_percent + + def _set_column_to_type(self, input_df: pd.DataFrame, column: str, data_type): + try: + input_df[column] = input_df[column].astype(data_type) + except Exception as e: + self.logger.error( + "Couldn't set data type %s for column %s!" % (data_type, column) + ) + self.logger.exception(e) + + def _fill_empty_names(self, input_df: pd.DataFrame, input_column: Union[str, int] = 0): + """Fills empty names with node1_node2_order-code""" + + def get_name_from_ucte_string(ucte_string: str) -> str: + return f"{ucte_string[:8].strip()}_{ucte_string[9:17].strip()}_{ucte_string[18]}" + + new_names = input_df.loc[input_df["name"] == "", input_column].map( + get_name_from_ucte_string + ) + input_df.loc[input_df["name"] == "", "name"] = new_names + + def _fill_amica_names( + self, input_df: pd.DataFrame, suffix: str, input_column: Union[str, int] = 0 + ) -> None: + def get_name_from_ucte_string(ucte_string: str) -> str: + node1 = ucte_string[:7].replace(" ", "_") + node2 = ucte_string[9:16].replace(" ", "_") + order_code = ucte_string[18] + # for some cases the element name is taken instead of the order code, but not clear when + # taking always the order code leads to more matches, at least for lines... + return f"{node1}_{node2}_{order_code}{suffix}" + + amica_names = input_df.loc[:, input_column].map(get_name_from_ucte_string) + input_df.loc[:, "amica_name"] = amica_names + + def set_pp_col_types( + self, + net: Union[pandapower.auxiliary.pandapowerNet, Dict], + ignore_errors: bool = False, + ) -> pandapower.auxiliary.pandapowerNet: + """ + Set the data types for some columns from pandapower assets. This mainly effects bus columns (to int, e.g. + sgen.bus or line.from_bus) and in_service and other boolean columns (to bool, e.g. line.in_service or gen.slack). + :param net: The pandapower network to update the data types. + :param ignore_errors: Ignore problems if set to True (no warnings displayed). Optional, default: False. + :return: The pandapower network with updated data types. + """ + time_start = time.time() + pp_elements = [ + "bus", + "dcline", + "ext_grid", + "gen", + "impedance", + "line", + "load", + "sgen", + "shunt", + "storage", + "switch", + "trafo", + "trafo3w", + "ward", + "xward", + ] + to_int = ["bus", "element", "to_bus", "from_bus", "hv_bus", "mv_bus", "lv_bus"] + to_bool = ["in_service", "closed", "tap_phase_shifter"] + self.logger.info( + "Setting the columns data types for buses to int and in_service to bool for the following elements: " + "%s" % pp_elements + ) + int_type = int + bool_type = bool + for ele in pp_elements: + self.logger.info("Accessing pandapower element %s." % ele) + if not hasattr(net, ele): + if not ignore_errors: + self.logger.warning( + "Missing the pandapower element %s in the input pandapower network!" + % ele + ) + continue + for one_int in to_int: + if one_int in net[ele].columns: + self._set_column_to_type(net[ele], one_int, int_type) + for one_bool in to_bool: + if one_bool in net[ele].columns: + self._set_column_to_type(net[ele], one_bool, bool_type) + # some individual things + if hasattr(net, "sgen"): + self._set_column_to_type(net["sgen"], "current_source", bool_type) + if hasattr(net, "gen"): + self._set_column_to_type(net["gen"], "slack", bool_type) + if hasattr(net, "shunt"): + self._set_column_to_type(net["shunt"], "step", int_type) + self._set_column_to_type(net["shunt"], "max_step", int_type) + self.logger.info( + "Finished setting the data types for the pandapower network in %ss." + % (time.time() - time_start) + ) + return net diff --git a/pandapower/converter/ucte/ucte_parser.py b/pandapower/converter/ucte/ucte_parser.py new file mode 100644 index 000000000..6a99af96e --- /dev/null +++ b/pandapower/converter/ucte/ucte_parser.py @@ -0,0 +1,377 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016-2024 by University of Kassel and Fraunhofer Institute for Energy Economics +# and Energy System Technology (IEE), Kassel. All rights reserved. + +import logging +import datetime +import os +import tempfile +import time +from typing import Dict, List +import numpy as np +import pandas as pd + + +class UCTEParser: + def __init__(self, path_ucte_file: str = None, config: Dict = None): + """ + This class parses an UCTE file and loads its content to a dictionary of + UCTE element type (str) -> UCTE elements (DataFrame) + :param path_ucte_file: The path to the UCTE file. Optional, default: None. This parameter may be set later. + :param config: The configuration dictionary. Optional, default: None. This parameter may be set later. + """ + self.path_ucte_file: str = path_ucte_file + self.config: Dict = config if isinstance(config, dict) else dict() + self.logger = logging.getLogger(self.__class__.__name__) + self.ucte_elements = ["##C", "##N", "##L", "##T", "##R", "##TT", "##E"] + self.data: Dict[str, pd.DataFrame] = dict() + self.date: datetime.datetime = datetime.datetime.utcnow() + + def parse_file(self, path_ucte_file: str = None) -> bool: + """ + Parse a UCTE file. + :param path_ucte_file: The path to the UCTE file. By default the internal field 'self.path_ucte_file' is used. + :return: True if parsing was successful, False otherwise. + """ + time_start = time.time() + # check parameters and needed fields + if path_ucte_file is not None: + self.path_ucte_file = path_ucte_file + if self.path_ucte_file is None: + self.logger.error( + "The variable 'path_ucte_file' is None, set it before parsing it!" + ) + return False + self.logger.info("Start parsing the file %s." % self.path_ucte_file) + # parse the date, if its given by the configuration, parse it from there, otherwise from the file string + if ( + "custom" in self.config.keys() + and isinstance(self.config["custom"], dict) + and "date" in self.config["custom"].keys() + ): + self._parse_date_str(self.config["custom"]["date"]) + else: + self._parse_date_str(os.path.basename(self.path_ucte_file)[:13]) + raw_input_dict = dict() + for ucte_element in self.ucte_elements: + raw_input_dict[ucte_element] = [] + with open(self.path_ucte_file, "r") as f: + # current_element contains the UCTE element type which is actually in parse progress, e.g. '##N' + current_element = "" + # iterate through the origin input file + for row in f.readlines(): + row = row.strip() + if row in self.ucte_elements: + # the start of a new UCTE element type in the origin file + current_element = row + elif row.startswith("##C"): + # special for comments, because '##C' rows look like '##C 2007.05.01' + current_element = "##C" + raw_input_dict[current_element].append(row) + elif row.startswith("##"): + self.logger.debug("Skipping row %s" % row) + else: + raw_input_dict[current_element].append(row) + self._create_df_from_raw(raw_input_dict) + self.logger.info( + "Finished parsing file %s in %ss." + % (self.path_ucte_file, time.time() - time_start) + ) + return True + + def _parse_date_str(self, date_str: str): + try: + self.date = datetime.datetime.strptime(date_str, "%Y%m%d_%H%M") + except Exception as e: + self.logger.info( + "The given date couldn't be parsed, choosing current utc time as date for the UCTE " + "data!" + ) + self.logger.exception(e) + self.date = datetime.datetime.utcnow() + + def _create_df_from_raw(self, raw_input_dict): + # create DataFrames from the raw_input_dict + self.data = dict() + for ucte_element, items in raw_input_dict.items(): + self.data[ucte_element] = pd.DataFrame(items) + # make sure that at least some empty data exist + if 0 not in self.data["##C"].columns: + self.data["##C"][0] = "" + # split the raw input for each UCTE element type + self.data["##C"]["comments"] = self.data["##C"][0][:] + self._split_nodes_from_raw() + self._split_lines_from_raw() + self._split_trafos_from_raw() + self._split_trafos_regulation_from_raw() + self._split_trafos_specified_parameters_from_raw() + self._split_exchange_powers_from_raw() + # drop the raw input columns + for ucte_element, df in self.data.items(): + if 0 in df.columns: + df = df.drop(columns=[0], axis=1) + # set the data types + dtypes = dict() + i_t = pd.Int64Dtype() + dtypes["##N"] = dict( + { + "status": i_t, + "voltage": float, + "p_load": float, + "q_load": float, + "p_gen": float, + "q_gen": float, + "min_p_gen": float, + "max_p_gen": float, + "min_q_gen": float, + "max_q_gen": float, + "static_primary_control": float, + "p_primary_control": float, + "three_ph_short_circuit_power": float, + "x_r_ratio": float, + "node_type": i_t, + } + ) + dtypes["##L"] = dict( + {"status": i_t, "r": float, "x": float, "b": float, "i": float} + ) + dtypes["##T"] = dict( + { + "status": i_t, + "voltage1": float, + "voltage2": float, + "s": float, + "r": float, + "x": float, + "b": float, + "g": float, + "i": float, + } + ) + dtypes["##R"] = dict( + { + "phase_reg_delta_u": float, + "phase_reg_n": float, + "phase_reg_n2": float, + "phase_reg_u": float, + "angle_reg_delta_u": float, + "angle_reg_theta": float, + "angle_reg_n": float, + "angle_reg_n2": float, + "angle_reg_p": float, + } + ) + dtypes["##TT"] = dict( + { + "tap_position": float, + "r": float, + "x": float, + "delta_u": float, + "alpha": float, + } + ) + dtypes["##E"] = dict({"p": float}) + for ucte_element, one_dtypes in dtypes.items(): + for field, field_type in one_dtypes.items(): + self.data[ucte_element].loc[ + self.data[ucte_element][field] == "", field + ] = np.nan + # for integer: first convert them to float to prevent errors + if field_type == i_t: + self.data[ucte_element][field] = self.data[ucte_element][ + field + ].astype(float) + self.data[ucte_element][field] = self.data[ucte_element][field].astype( + field_type + ) + + # remove '##' at the beginning of each key + for one_key in list(self.data.keys()): + self.data[one_key[2:]] = self.data[one_key] + self.data.pop(one_key) + + del raw_input_dict + + def _split_nodes_from_raw(self): + element_type = "##N" + if element_type not in self.data.keys(): + self.logger.warning("No nodes in 'self.data' available! Didn't split them.") + return + df = self.data[element_type] + # if 0 not in df.columns: + # df[0] = "" + df["node"] = df[0].str[0:8].str.strip() + df["node_name"] = df[0].str[9:21].str.strip() + df["status"] = df[0].str[22:23].str.strip() + df["node_type"] = df[0].str[24:25].str.strip() + df["voltage"] = df[0].str[26:32].str.strip() + df["p_load"] = df[0].str[33:40].str.strip() + df["q_load"] = df[0].str[41:48].str.strip() + df["p_gen"] = df[0].str[49:56].str.strip() + df["q_gen"] = df[0].str[57:64].str.strip() + df["min_p_gen"] = df[0].str[65:72].str.strip() + df["max_p_gen"] = df[0].str[73:80].str.strip() + df["min_q_gen"] = df[0].str[81:88].str.strip() + df["max_q_gen"] = df[0].str[89:96].str.strip() + df["static_primary_control"] = df[0].str[97:102].str.strip() + df["p_primary_control"] = df[0].str[103:110].str.strip() + df["three_ph_short_circuit_power"] = df[0].str[111:118].str.strip() + df["x_r_ratio"] = df[0].str[119:126].str.strip() + df["type"] = df[0].str[127:128].str.strip() + + def _split_connections_from_raw(self, element_type: str): + # get the connections to nodes (node1 and node2) from the asset for e.g. ##L or ##T + self.data[element_type]["node1"] = ( + self.data[element_type][0].str[0:8].str.strip() + ) + self.data[element_type]["node2"] = ( + self.data[element_type][0].str[9:17].str.strip() + ) + + def _split_lines_from_raw(self): + element_type = "##L" + if element_type not in self.data.keys(): + self.logger.warning("No lines in 'self.data' available! Didn't split them.") + return + df = self.data[element_type] + if 0 not in df.columns: + df[0] = "" + self._split_connections_from_raw(element_type) + df["order_code"] = df[0].str[18:19].str.strip() + df["status"] = df[0].str[20:21].str.strip() + df["r"] = df[0].str[22:28].str.strip() + df["x"] = df[0].str[29:35].str.strip() + df["b"] = df[0].str[36:44].str.strip() + df["i"] = df[0].str[45:51].str.strip() + df["name"] = df[0].str[52:64].str.strip() + + def _split_trafos_from_raw(self): + element_type = "##T" + if element_type not in self.data.keys(): + self.logger.warning( + "No transformers in 'self.data' available! Didn't split them." + ) + return + df = self.data[element_type] + if 0 not in df.columns: + df[0] = "" + self._split_connections_from_raw(element_type) + df["order_code"] = df[0].str[18:19].str.strip() + df["status"] = df[0].str[20:21].str.strip() + df["voltage1"] = df[0].str[22:27].str.strip() + df["voltage2"] = df[0].str[28:33].str.strip() + df["s"] = df[0].str[34:39].str.strip() + df["r"] = df[0].str[40:46].str.strip() + df["x"] = df[0].str[47:53].str.strip() + df["b"] = df[0].str[54:62].str.strip() + df["g"] = df[0].str[63:69].str.strip() + df["i"] = df[0].str[70:76].str.strip() + df["name"] = df[0].str[77:89].str.strip() + + def _split_trafos_regulation_from_raw(self): + element_type = "##R" + if element_type not in self.data.keys(): + self.logger.warning( + "No tap changers in 'self.data' available! Didn't split them." + ) + return + df = self.data[element_type] + if 0 not in df.columns: + df[0] = "" + self._split_connections_from_raw(element_type) + df["order_code"] = df[0].str[18:19].str.strip() + df["phase_reg_delta_u"] = df[0].str[20:25].str.strip() + df["phase_reg_n"] = df[0].str[26:28].str.strip() + df["phase_reg_n2"] = df[0].str[29:32].str.strip() + df["phase_reg_u"] = df[0].str[33:38].str.strip() + df["angle_reg_delta_u"] = df[0].str[39:44].str.strip() + df["angle_reg_theta"] = df[0].str[45:50].str.strip() + df["angle_reg_n"] = df[0].str[51:53].str.strip() + df["angle_reg_n2"] = df[0].str[54:57].str.strip() + df["angle_reg_p"] = df[0].str[58:63].str.strip() + df["angle_reg_type"] = df[0].str[64:68].str.strip() + + def _split_trafos_specified_parameters_from_raw(self): + element_type = "##TT" + if element_type not in self.data.keys(): + self.logger.warning( + "No specified transformer parameters in 'self.data' available! Didn't split them." + ) + return + df = self.data[element_type] + if 0 not in df.columns: + df[0] = "" + self._split_connections_from_raw(element_type) + df["order_code"] = df[0].str[18:19].str.strip() + df["tap_position"] = df[0].str[22:25].str.strip() + df["r"] = df[0].str[26:32].str.strip() + df["x"] = df[0].str[33:39].str.strip() + df["delta_u"] = df[0].str[40:45].str.strip() + df["alpha"] = df[0].str[46:51].str.strip() + + def _split_exchange_powers_from_raw(self): + element_type = "##E" + if element_type not in self.data.keys(): + self.logger.warning( + "No exchange powers in 'self.data' available! Didn't split them." + ) + return + df = self.data[element_type] + if 0 not in df.columns: + df[0] = "" + df["country1"] = df[0].str[0:2].str.strip() + df["country2"] = df[0].str[3:5].str.strip() + df["p"] = df[0].str[6:13].str.strip() + df["comment"] = df[0].str[14:26].str.strip() + + def get_fields_dict(self) -> Dict[str, Dict[str, List[str]]]: + """ + Get an overview about the UCTE element types and their fields. + :return: A dictionary containing a dictionary with the UCTE element types as keys and for each UCTE element + type a list of fields and a dictionary with the UCTE element types as keys and for each UCTE element type a + list of dtypes. + """ + self.logger.info( + "Creating a dictionary containing the UCTE element types as keys and for each UCTE element " + "type a list of fields and dtypes." + ) + with tempfile.NamedTemporaryFile(delete=False) as f: + f.close() + ucte_temp = UCTEParser() + ucte_temp.set_config(dict({"custom": {"date": "20200701_1010"}})) + ucte_temp.parse_file(path_ucte_file=f.name) + data = ucte_temp.get_data() + if os.path.exists(f.name): + os.remove(f.name) + return_dict = dict() + return_dict["element_types"] = dict() + return_dict["dtypes"] = dict() + for element_type, df in data.items(): + return_dict["element_types"][element_type] = list(df.columns) + return_dict["dtypes"][element_type] = [str(x) for x in df.dtypes.values] + return return_dict + + def set_path_ucte_file(self, path_ucte_file: str): + self.path_ucte_file = path_ucte_file + + def get_path_ucte_file(self) -> str: + return self.path_ucte_file + + def set_config(self, config: Dict): + if isinstance(config, dict): + self.config = config + else: + self.logger.warning( + "The configuration is not a dictionary! Default configuration is set." + ) + self.config = dict() + + def get_config(self) -> Dict: + return self.config + + def get_data(self) -> Dict[str, pd.DataFrame]: + return self.data + + def get_date(self) -> datetime.datetime: + return self.date diff --git a/pandapower/std_types.py b/pandapower/std_types.py index 3687b94e7..ad8772741 100644 --- a/pandapower/std_types.py +++ b/pandapower/std_types.py @@ -207,6 +207,29 @@ def delete_std_type(net, name, element="line"): raise UserWarning("Unknown standard %s type %s" % (element, name)) +def rename_std_type(net, old_name, new_name, element="line"): + """Renames an existing standard type in the standard type library and the element table. + + Parameters + ---------- + net : pp.pandapowerNet + pandapower net + old_name : str + old name to be replaced + new_name : str + new name of the standard type + element : str, optional + type of element, by default "line" + """ + library = net.std_types[element] + if old_name not in library: + raise UserWarning(f"Unknown {element} standard type '{old_name}'.") + if new_name in library: + raise UserWarning(f"{element} standard type '{new_name}' already exists.") + library[new_name] = library.pop(old_name) + net[element].loc[net[element].std_type == old_name, "std_type"] = new_name + + def available_std_types(net, element="line"): """ Returns all standard types available for this network as a table. @@ -229,7 +252,7 @@ def available_std_types(net, element="line"): return std_types.convert_objects() -def parameter_from_std_type(net, parameter, element="line",fill=None): +def parameter_from_std_type(net, parameter, element="line", fill=None): """ Loads standard types data for a parameter, which can be used to add an additional parameter, that is not included in the original pandapower datastructure but is available in the standard @@ -319,6 +342,7 @@ def find_std_type_by_parameter(net, data, element="line", epsilon=0.): fitting_types.append(name) return fitting_types + def find_std_type_alternative(net, data, element = "line", voltage_rating = "", epsilon = 0.): """ Searches for a std_type that fits all values given in the standard types library with the margin of @@ -357,6 +381,7 @@ def find_std_type_alternative(net, data, element = "line", voltage_rating = "", fitting_types.append(name) return fitting_types + def add_zero_impedance_parameters(net): """ Adds all parameters required for zero sequence impedance calculations. diff --git a/pandapower/test/api/test_std_types.py b/pandapower/test/api/test_std_types.py index aedcceb6a..599481e4d 100644 --- a/pandapower/test/api/test_std_types.py +++ b/pandapower/test/api/test_std_types.py @@ -41,6 +41,7 @@ def test_create_and_load_std_type_line(): loaded_type = pp.load_std_type(net, name) assert loaded_type == typdata + def test_create_std_types_line(): net = pp.create_empty_network() c = 40 @@ -55,6 +56,7 @@ def test_create_std_types_line(): assert net.std_types["line"]["typ1"] == typdata assert net.std_types["line"]["typ1"] == typdata + def test_create_std_types_from_net_line(): net1 = pp.create_empty_network() net2 = pp.create_empty_network() @@ -70,6 +72,7 @@ def test_create_std_types_from_net_line(): pp.copy_std_types(net2, net1, element="line") assert pp.std_type_exists(net2, "test_copy") + def test_create_and_load_std_type_trafo(): net = pp.create_empty_network() sn_mva = 40 @@ -121,6 +124,7 @@ def test_create_and_load_std_type_trafo(): loaded_type = pp.load_std_type(net, name, element="trafo") assert loaded_type == typdata + def test_create_and_load_std_type_trafo3w(): net = pp.create_empty_network() sn_hv_mva = 40; sn_mv_mva = 20; sn_lv_mva = 20 @@ -178,6 +182,7 @@ def test_create_and_load_std_type_trafo3w(): loaded_type = pp.load_std_type(net, name, element="trafo3w") assert loaded_type == typdata + def test_create_std_types_trafo(): net = pp.create_empty_network() sn_mva = 40 @@ -197,6 +202,7 @@ def test_create_std_types_trafo(): assert net.std_types["trafo"]["typ1"] == typdata assert net.std_types["trafo"]["typ2"] == typdata + def test_create_std_types_trafo3w(): net = pp.create_empty_network() sn_hv_mva = 40; sn_mv_mva = 20; sn_lv_mva = 20 @@ -218,6 +224,7 @@ def test_create_std_types_trafo3w(): assert net.std_types["trafo3w"]["typ1"] == typdata assert net.std_types["trafo3w"]["typ2"] == typdata + def test_find_line_type(): net = pp.create_empty_network() c = 40000 @@ -236,6 +243,7 @@ def test_find_line_type(): assert len(fitting_type) == 1 assert fitting_type[0] == name + def test_find_std_alternative(): net = pp.create_empty_network() c = 210 @@ -257,6 +265,7 @@ def test_find_std_alternative(): fitting_type = pp.find_std_type_alternative(net, {"r_ohm_per_km":r+0.07}, voltage_rating ="MV", epsilon=0.06) assert len(fitting_type) == 0 + def test_change_type_line(): net = pp.create_empty_network() r1 = 0.01 @@ -340,5 +349,52 @@ def test_add_temperature_coefficient(): assert all(net.line.alpha == 4.03e-3) +def test_delete_std_type(): + net = pp.create_empty_network() + trafo3w_types = set(net.std_types["trafo3w"].keys()) + existing_trafo3w_std_type = sorted(trafo3w_types)[0] + pp.delete_std_type(net, existing_trafo3w_std_type, "trafo3w") + assert trafo3w_types == set(net.std_types["trafo3w"].keys()) | {existing_trafo3w_std_type} + + +def test_rename_std_type(): + net = pp.create_empty_network() + existing_line_std_type = sorted(net.std_types["line"].keys())[0] + existing_line_std_type2 = sorted(net.std_types["line"].keys())[1] + existing_trafo3w_std_type = sorted(net.std_types["trafo3w"].keys())[0] + tr3w_std_type_params = pp.load_std_type(net, existing_trafo3w_std_type, "trafo3w") + + vn_kvs = [tr3w_std_type_params["vn_hv_kv"], tr3w_std_type_params["vn_hv_kv"], + tr3w_std_type_params["vn_mv_kv"], tr3w_std_type_params["vn_lv_kv"]] + pp.create_buses(net, 4, vn_kvs) + pp.create_line(net, 0, 1, 1.2, existing_line_std_type) + pp.create_line(net, 0, 1, 1.2, existing_line_std_type2) + pp.create_transformer3w(net, 0, 2, 3, existing_trafo3w_std_type) + + pp.rename_std_type(net, existing_line_std_type, "new_line_std_type") + pp.rename_std_type(net, existing_trafo3w_std_type, "new_trafo3w_std_type", "trafo3w") + + assert existing_line_std_type not in net.std_types["line"].keys() + assert "new_line_std_type" in net.std_types["line"].keys() + assert (net.line.std_type == existing_line_std_type).sum() == 0 + assert (net.line.std_type == "new_line_std_type").sum() == 1 + + assert existing_line_std_type not in net.std_types["trafo3w"].keys() + assert "new_trafo3w_std_type" in net.std_types["trafo3w"].keys() + assert (net.trafo3w.std_type == existing_line_std_type).sum() == 0 + assert (net.trafo3w.std_type == "new_trafo3w_std_type").sum() == 1 + + try: + pp.rename_std_type(net, "abcdefghijklmnop", "new_line_std_type2") + assert False, "an error is expected" + except UserWarning: + pass + try: + pp.rename_std_type(net, existing_line_std_type2, "new_line_std_type") + assert False, "an error is expected" + except UserWarning: + pass + + if __name__ == "__main__": pytest.main([__file__, "-xs"]) diff --git a/pandapower/test/converter/test_from_ucte.py b/pandapower/test/converter/test_from_ucte.py new file mode 100644 index 000000000..d011b4cbe --- /dev/null +++ b/pandapower/test/converter/test_from_ucte.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016-2024 by University of Kassel and Fraunhofer Institute for Energy Economics +# and Energy System Technology (IEE), Kassel. All rights reserved. + +import os +import pytest +import numpy as np +import pandas as pd + +import pandapower as pp +import pandapower.converter as pc + +try: + import pandaplan.core.pplog as logging +except ImportError: + import logging + +logger = logging.getLogger(__name__) + + +def _testfiles_folder(): + return os.path.join(pp.pp_dir, 'test', 'converter', "testfiles") + + +def _results_from_powerfactory(): + pf_res = {f"res_{et}": pd.read_csv( + os.path.join(_testfiles_folder(), f"test_ucte_res_{et}.csv"), + sep=";", index_col=0) for et in ["bus", "line", "trafo"]} + return pf_res + + +def _country_code_mapping(test_case=None): + mapping = { + "test_ucte_impedance": "AL", + "test_ucte_line_trafo_load": "DE", + "test_ucte_line": "DK", + "test_ucte_bus_switch": "ES", + "test_ucte_shunt": "HR", + "test_ucte_ward": "HU", + "test_ucte_load_sgen": "IT", + "test_ucte_gen": "LU", + "test_ucte_xward": "NL", + "test_ucte_trafo": "RS", + "test_ucte_trafo3w": "FR", + } + if test_case is None: + return mapping + else: + return mapping[test_case] + + +@pytest.mark.parametrize("test_case", [ + "test_ucte_impedance", + "test_ucte_line_trafo_load", + "test_ucte_line", + "test_ucte_bus_switch", + "test_ucte_shunt", + "test_ucte_ward", + "test_ucte_load_sgen", + "test_ucte_gen", + "test_ucte_xward", + "test_ucte_trafo", + "test_ucte_trafo3w" +]) +def test_from_ucte(test_case): + """Tets the UCTE converter by + + 1. checking the number of elements per element type in the grid + 2. comparing the power flow results with expected results + + The expected power flow results and the UCTE data files are derived from the + pandapower/test/converter/testfiles/test_ucte.pfd file and power factory version 2024 service + pack 1. The test case are derived from the test cases to + check the power flow results without any convter, cf + pandapower/test/test_files/test_results.pfd and pandapower/test/loadflow/test_runpp.py. + + Parameters + ---------- + test_case : str + description on which kind of test case is tested for th UCTE conversion + """ + country_code = _country_code_mapping(test_case) + ucte_file_name = f"test_ucte_{country_code}" + ucte_file = os.path.join(_testfiles_folder(), f"{ucte_file_name}.uct") + + # --- convert UCTE data ------------------------------------------------------------------- + net = pc.from_ucte(ucte_file) + + assert isinstance(net, pp.pandapowerNet) + assert len(net.bus) + + # --- run power flow ---------------------------------------------------------------------- + pp.runpp(net) + assert net.converged + + # --- check expected element counts ------------------------------------------------------- + exp_elm_count_df = pd.read_csv(os.path.join( + _testfiles_folder(), "ucte_expected_element_counts.csv"), sep=";", index_col=0) + exp_elm_count = exp_elm_count_df.loc[country_code] + exp_elm_count = exp_elm_count.loc[exp_elm_count > 0] + assert dict(pp.count_elements(net)) == dict(exp_elm_count) + + # --- compare results --------------------------------------------------------------------- + res_target = _results_from_powerfactory() + failed = list() + atol_dict = { + "res_bus": {"vm_pu": 1e-4, "va_degree": 7e-3}, + "res_line": {"p_from_mw": 5e-2, "q_from_mvar": 2e-1}, + "res_trafo": {"p_hv_mw": 5e-2, "q_hv_mvar": 1e-1}, + } + if test_case == "test_ucte_xward": + atol_dict["res_line"]["q_from_mvar"] = 0.8 # xwards are converted as + # PV gens towards uct format -> lower tolerance (compared to powerfactory results cannot be + # expected) + + # --- for loop per result table + for res_et, df_target in res_target.items(): + et = res_et[4:] + name_col = "name" # if et != "bus" else "add_name" + missing_names = pd.Index(net[et][name_col]).difference(df_target.index) + if len(missing_names): + logger.error(f"{res_et=} comparison fails since same element names of the PowerFactory " + f"results are missing in the pandapower net: {missing_names}") + df_after_conversion = net[res_et][df_target.columns].set_axis( + pd.Index(net[et][name_col], name="name")) + + # --- prepare comparison + if test_case == "test_ucte_trafo3w" and et == "bus": + df_after_conversion = df_after_conversion.drop("tr3_star_FR") + if test_case == "test_ucte_trafo3w" and et == "trafo": + df_after_conversion = df_after_conversion.loc[ + (df_after_conversion.index.values != "trafo3w_FR") | + ~df_after_conversion.index.duplicated()] + if et == "line" and "Allgemeine I" in df_after_conversion.index: + df_after_conversion = df_after_conversion.drop("Allgemeine I") + + # --- compare the shape of the results to be compared + same_shape = df_after_conversion.shape == df_target.loc[df_after_conversion.index].shape + df_str = (f"df_after_conversion:\n{df_after_conversion}\n\ndf_target:\n" + f"{df_target.loc[df_after_conversion.index]}") + if not same_shape: + logger.error(f"{res_et=} comparison fails due to different shape.\n{df_str}") + + # --- compare the results itself + all_close = all([np.allclose( + df_after_conversion[col].values, + df_target.loc[df_after_conversion.index, col].values, atol=atol) for col, atol in + atol_dict[res_et].items()]) + if not all_close: + logger.error(f"{res_et=} comparison fails due to different values.\n{df_str}") + failed.append(res_et) + + # --- overall test evaluation + if test_is_failed := len(failed): + logger.error(f"The powerflow result comparisons of these elements failed: {failed}.") + assert not test_is_failed + + +if __name__ == '__main__': + if 1: + pytest.main([__file__, "-s"]) + else: + + ucte_file = os.path.join(_testfiles_folder(), "test_ucte_DE.uct") + net = pc.from_ucte(ucte_file) + + print(net) + print() \ No newline at end of file diff --git a/pandapower/test/converter/testfiles/test_ucte.pfd b/pandapower/test/converter/testfiles/test_ucte.pfd new file mode 100644 index 000000000..9562d8261 Binary files /dev/null and b/pandapower/test/converter/testfiles/test_ucte.pfd differ diff --git a/pandapower/test/converter/testfiles/test_ucte_AL.uct b/pandapower/test/converter/testfiles/test_ucte_AL.uct new file mode 100644 index 000000000..37c428370 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_AL.uct @@ -0,0 +1,48 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZAL +ABUS1_1L Bus1_AL 0 3 383.80 0 0 -1.1234 5.14870 9.99900 -9.9990 1.82725 -1.8273 +ABUS2_1_ Bus2_1_AL 0 0 1.00000 0.50000 0 0 +ABUS2_1L Bus2_AL 0 0 0 0 0 0 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +ABUS2_1L ABUS1_1L 1 0 0.7198 3.0866 42.16017 768 line1_AL +ABUS2_1L ABUS2_1_ 1 0 1444.0 722.00 0 3 Allgemeine I +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_DE.uct b/pandapower/test/converter/testfiles/test_ucte_DE.uct new file mode 100644 index 000000000..5c602ae08 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_DE.uct @@ -0,0 +1,50 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZDE +DEHV1_1E EHV1_DE 0 3 380.00 0 0 -114.54 -485.93 9.99900 -9.9990 1.82725 -1.8273 +DEHV2_1E EHV2_DE 0 0 1140.00 500.000 0 0 +DHV1_D5 HV1_DE 0 0 0 0 0 0 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +DEHV1_1E DEHV2_1E 1 0 0.7375 3.1625 172.7876 1920 Line1_DE +DEHV1_1E DEHV2_1E 2 0 1.4750 6.3250 86.39380 960 Line2_DE +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +DHV1_D5 DEHV1_1E 1 0 110.0 380.0 160.0 0.1891 9.2243 -6.19339 4.9587 840 trafo1_DE +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +DHV1_D5 DEHV1_1E 1 1.500 9 0 +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_DK.uct b/pandapower/test/converter/testfiles/test_ucte_DK.uct new file mode 100644 index 000000000..04a051bf0 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_DK.uct @@ -0,0 +1,48 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZDK +KBUS1_5K Bus1_DK 0 3 111.10 0 0 -1.2017 19.3415 9.99900 -9.9990 1.82725 -1.8273 +KBUS2_5K Bus2_DK 0 0 1.20000 1.10000 0 0 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +KBUS2_5K KBUS1_5K 1 0 0.3660 0.8784 1103.830 941 line1_DK +KBUS2_5K KBUS1_5K 2 8 0.7320 1.7568 551.9150 470 line2_DK +KBUS2_5K KBUS1_5K 3 8 0.7320 1.7568 551.9150 470 line3_DK +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_ES.uct b/pandapower/test/converter/testfiles/test_ucte_ES.uct new file mode 100644 index 000000000..32aae1a3c --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_ES.uct @@ -0,0 +1,48 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZES +EBUS1_1S Bus1_ES 0 3 383.80 0 0 -12.516 195.166 9.99900 -9.9990 1.82725 -1.8273 +EBUS2_1_ Bus2_!_ES 0 2 385.32 0.50000 0.25000 4.67552 -95.919 +EBUS2_1S Bus2_ES 0 2 385.32 1.00000 0.50000 6.15937 -94.538 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +EBUS2_1S EBUS1_1S 1 0 0.7198 3.0866 42.16017 768 line1_ES +EBUS2_1S EBUS2_1_ 1 2 0 0 0 999999 LS/TR Schalt +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_FR.uct b/pandapower/test/converter/testfiles/test_ucte_FR.uct new file mode 100644 index 000000000..f5937ee24 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_FR.uct @@ -0,0 +1,55 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZFR +FHV_FR1 HV_FR 0 0 0 0 0 0 +FLV_FR5 LV_FR 0 0 0.10000 0 0 0 +FMV_FR2 MV_FR 0 0 2.00000 0 0 0 +FSLACK1F Slack_FR 0 3 383.80 0 0 -2.1341 6.15018 9.99900 -9.9990 0.00183 -0.0018 +FHV_FR1S tr3_star_FR 0 0 0 0 0 0 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +FHV_FR1 FSLACK1F 1 0 0.7198 3.0866 42.16017 768 line1_FR +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +FHV_FR1S FHV_FR1 1 0 380.0 380.0 63.00 0.4788 197.60 -0.30336 0.2424 96 trafo3w_FR +FHV_FR1S FMV_FR2 1 0 380.0 220.0 25.00 1.2540 403.10 0 0 38 trafo3w_FR +FHV_FR1S FLV_FR5 1 1 380.0 110.0 38.00 -0.099 197.60 0 0 58 trafo3w_FR +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +FHV_FR1S FHV_FR1 1 1.250 10 2 +FHV_FR1S FMV_FR2 1 +FHV_FR1S FLV_FR5 1 +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_HR.uct b/pandapower/test/converter/testfiles/test_ucte_HR.uct new file mode 100644 index 000000000..23b3c995d --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_HR.uct @@ -0,0 +1,49 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZHR +HBUS1_1_ Bus1_1_HR 0 3 383.80 0 0 -0.1106 7.31543 9.99900 -9.9990 1.82725 -1.8273 +HBUS1_1K Bus1_HK 0 3 383.80 0 0 -0.2211 8.42041 9.99900 -9.9990 1.82725 -1.8273 +HBUS2_1_ Bus2_1_HR 0 0 0.11050 -1.1050 0 0 +HBUS2_1K Bus2_HK 0 0 0.22100 -2.2100 0 0 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +HBUS1_1_ HBUS2_1_ 1 0 0.7198 3.0866 42.16017 768 line2_HR +HBUS1_1K HBUS2_1K 1 0 0.7198 3.0866 42.16017 768 line1_HR +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_HU.uct b/pandapower/test/converter/testfiles/test_ucte_HU.uct new file mode 100644 index 000000000..649ca6979 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_HU.uct @@ -0,0 +1,46 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZHU +MBUS1_1U Bus1_HU 0 3 383.80 0 0 -1.7242 4.88817 9.99900 -9.9990 1.82725 -1.8273 +MBUS2_1U Bus2_HU 0 2 383.81 0 0 1.72419 1.32217 9.99900 -9.9990 0.00132 0.00132 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +MBUS1_1U MBUS2_1U 1 0 0.7198 3.0866 42.16017 768 line1_HU +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_IT.uct b/pandapower/test/converter/testfiles/test_ucte_IT.uct new file mode 100644 index 000000000..ec4dea34b --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_IT.uct @@ -0,0 +1,46 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZIT +IBUS1_1T Bus1_IT 0 3 383.80 0 0 -0.7002 5.01044 9.99900 -9.9990 1.82725 -1.8273 +IBUS2_1T Bus2_IT 0 0 1.20000 1.10000 -0.5000 0.10000 0 -9.9990 0.00100 -0.0010 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +IBUS1_1T IBUS2_1T 1 0 0.7198 3.0866 42.16017 768 line1_IT +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_LU.uct b/pandapower/test/converter/testfiles/test_ucte_LU.uct new file mode 100644 index 000000000..65d222829 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_LU.uct @@ -0,0 +1,53 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZLU +1BUS1_1_ Bus1_1_LU 0 3 383.80 0 0 -0.7003 11.1224 9.99900 -9.9990 1.82725 -1.8273 +1BUS1_1U Bus1_LU 0 3 383.80 0 0 -1.2445 -229.83 9.99900 -9.9990 1.82725 -1.8273 +1BUS2_1_ Bus2_1_LU 0 0 0 0 0 0 +1BUS2_10 Bus2_2_LU 0 0 1.20000 1.10000 -0.5000 0.20000 0 -9.9990 1.00000 -1.0000 +1BUS2_11 Bus2_3_LU 0 0 0 0 0 0 +1BUS2_1U Bus2_LU 0 2 380.00 1.20000 1.10000 -0.5000 238.692 0 -9.9990 1.00000 -1.0000 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +1BUS1_1_ 1BUS2_11 1 0 0.7198 3.0866 42.16017 768 line3_LU +1BUS1_1U 1BUS2_1_ 1 0 0.7198 3.0866 42.16017 768 line1_LU +1BUS2_1_ 1BUS2_1U 1 0 0.7198 3.0866 42.16017 768 line2_LU +1BUS2_11 1BUS2_10 1 0 0.7198 3.0866 42.16017 768 line4_LU +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_NL.uct b/pandapower/test/converter/testfiles/test_ucte_NL.uct new file mode 100644 index 000000000..8ee0bc42a --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_NL.uct @@ -0,0 +1,49 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZNL +NBUS1_1_ Bus1_1_NL 0 3 383.80 0 0 -10.021 197.160 9.99900 -9.9990 1.82725 -1.8273 +NBUS1_1L Bus1_NL 0 3 383.80 0 0 -5.2390 105.764 9.99900 -9.9990 1.82725 -1.8273 +NBUS2_1_ Bus2_1_NL 0 2 385.34 0 0 9.83602 -19.171 +NBUS2_1L Bus2_NL 0 2 384.62 0 0 5.18739 -9.9762 9.99900 -9.9990 -0.0998 -0.0998 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +NBUS1_1_ NBUS2_1_ 1 0 0.7198 3.0866 42.16017 768 line2_NL +NBUS1_1L NBUS2_1L 1 0 0.7198 3.0866 42.16017 768 line1_NL +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_RS.uct b/pandapower/test/converter/testfiles/test_ucte_RS.uct new file mode 100644 index 000000000..271a655d9 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_RS.uct @@ -0,0 +1,53 @@ +##C 2007.05.01 +UCTE Export +test_ucte-Berechnungsfall + +----- NODE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9________10________11________12________13 +1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node |Node Name |S|T|Volt |PLoad |QLoad |PGen |QGen |Pmin |Pmax |Qmin |Qmax |Sta |Pprim |Sks__ |X/R |T| + +##N +##ZRS +JBUS1_1S Bus1_RS 0 3 383.80 0 0 -200.70 -63.268 9.99900 -9.9990 1.82725 -1.8273 +JBUS2_1S Bus2_RS 0 0 0 0 0 0 +JBUS3_5S Bus3_RS 0 0 200.000 50.0000 0 0 +##C + +----- LINE BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|R(Ohm)|X(Ohm)|B(uS) |I(A) |Element Name| + +##L +JBUS1_1S JBUS2_1S 1 0 0.7198 3.0866 42.16017 768 line1_RS +##C + +----- 2 WINDING TRANSFORMER BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7_________8_________9 +123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|S|U1 |U2 |Sn |R(Ohm)|X(Ohm)|B(uS) |G(uS) |I(A) |Element Name| + +##T +JBUS3_5S JBUS2_1S 1 0 110.0 380.0 320.0 0.0945 4.6122 -12.3868 9.9174 1680 trafo1_RS +JBUS3_5S JBUS2_1S 2 8 110.0 380.0 160.0 0.1891 9.2243 -6.19339 4.9587 840 trafo2_RS +JBUS3_5S JBUS2_1S 3 8 110.0 380.0 160.0 0.1891 9.2243 -6.19339 4.9587 840 trafo3_RS +##C + +----- 2 WINDINGS TRANSFORMERS REGULATION BLOCK ----- +_________1_________2_________3_________4_________5_________6_________7 +1234567890123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O|du |mx|Tap|U |du |phitr|mx|Tap|P |Type| + +##R +JBUS3_5S JBUS2_1S 1 1.500 9 3 +JBUS3_5S JBUS2_1S 2 1.500 9 3 +JBUS3_5S JBUS2_1S 3 1.500 9 0 +##C + +----- 2 WINDINGS TRANSFORMERS DEPENDING ON THE TAP POSITION BLOCK ----- +_________1_________2_________3_________4_________5_________6 +123456789012345678901234567890123456789012345678901234567890 +Node 1 |Node 2 |O| |Tap|R(Ohm)|X(Ohm)|du |phi | + +##TT diff --git a/pandapower/test/converter/testfiles/test_ucte_res_bus.csv b/pandapower/test/converter/testfiles/test_ucte_res_bus.csv new file mode 100644 index 000000000..8c6bfd900 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_res_bus.csv @@ -0,0 +1,37 @@ +name;va_degree;vm_pu +Bus1_1_HR;0;1.00999999 +Bus1_1_LU;0;1.00999999 +Bus1_1_NL;0;1.00999999 +Bus1_AL;0;1.00999999 +Bus1_DK;0;1.00999999 +Bus1_ES;0;1.00999999 +Bus1_HK;0;1.00999999 +Bus1_HU;0;1.00999999 +Bus1_IT;0;1.00999999 +Bus1_LU;0;1.00999999 +Bus1_NL;0;1.00999999 +Bus1_RS;0;1.00999999 +Bus2_!_ES;-0.06857338;1.014003069 +Bus2_1_AL;-0.00193803;0.997580321 +Bus2_1_HR;-0.00131144;1.01008855 +Bus2_1_LU;0.06403509;1.005064739 +Bus2_1_NL;-0.06609607;1.014058128 +Bus2_2_LU;-0.00443064;1.010200979 +Bus2_3_LU;-0.00308497;1.010166211 +Bus2_AL;-0.00194328;1.010049998 +Bus2_DK;-0.01460107;1.01037483 +Bus2_ES;-0.06852776;1.014003671 +Bus2_HK;-0.00175346;1.01011139 +Bus2_HU;-0.00256921;1.010029217 +Bus2_IT;-0.00137382;1.010036859 +Bus2_LU;0.13045808;1 +Bus2_NL;-0.03495796;1.012146972 +Bus2_RS;-0.22290650;1.007612361 +Bus3_RS;-5.03020941;0.938839373 +EHV1_DE;0;1 +EHV2_DE;-0.86942948;0.988847024 +HV1_DE;-0.00127678;0.999970967 +HV_FR;-0.00341453;1.010053904 +LV_FR;-0.17549607;0.985336190 +MV_FR;-0.49691575;0.985302209 +Slack_FR;0;1.009999990 diff --git a/pandapower/test/converter/testfiles/test_ucte_res_line.csv b/pandapower/test/converter/testfiles/test_ucte_res_line.csv new file mode 100644 index 000000000..07a8d687a --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_res_line.csv @@ -0,0 +1,20 @@ +name;p_from_mw;q_from_mvar +Line1_DE;763.5555758;323.906316 +Line2_DE;381.7777879;161.953158 +line1_AL;-1.0076061;-0.503803 +line1_DK;-1.2000001;-1.1 +line1_ES;-12.37748921;189.7075643 +line1_HR;0.2211407;-8.4204123 +line1_HU;1.7242209;-4.888172 +line1_IT;0.7000202;-5.0104365 +line1_LU;1.2445389;229.8283535 +line1_NL;5.2390252;-105.7643867 +line1_RS;200.7037798;63.2678914 +line1_FR;-2.1340518;-0.0601735 +line2_DK;0;0 +line2_HR;0.1105829;-7.3154311 +line2_LU;0.9793971;234.8714139 +line2_NL;10.0205223;-197.1598822 +line3_DK;0;0 +line3_LU;0.7003349;-11.1224368 +line4_LU;0.7000184;-4.9124756 diff --git a/pandapower/test/converter/testfiles/test_ucte_res_trafo.csv b/pandapower/test/converter/testfiles/test_ucte_res_trafo.csv new file mode 100644 index 000000000..1ece94888 --- /dev/null +++ b/pandapower/test/converter/testfiles/test_ucte_res_trafo.csv @@ -0,0 +1,6 @@ +name;p_hv_mw;q_hv_mvar +trafo1_DE;0.059997;0.07493 +trafo1_RS;200.485413;68.527137 +trafo2_RS;0;0 +trafo3_RS;0;0 +trafo3w_FR;-2.13403;-0.06015863 diff --git a/pandapower/test/converter/testfiles/ucte_expected_element_counts.csv b/pandapower/test/converter/testfiles/ucte_expected_element_counts.csv new file mode 100644 index 000000000..d881a4ad0 --- /dev/null +++ b/pandapower/test/converter/testfiles/ucte_expected_element_counts.csv @@ -0,0 +1,12 @@ +country_code;bus;load;ext_grid;gen;line;trafo;switch;sgen +NL;4;0;2;2;2;0;0;0 +AL;3;1;1;0;2;0;0;0 +ES;3;2;1;2;1;0;1;0 +HR;4;2;2;0;2;0;0;0 +HU;2;0;1;1;1;0;0;0 +DE;3;1;1;0;2;1;0;0 +DK;2;1;1;0;3;0;0;0 +IT;2;1;1;0;1;0;0;1 +LU;6;2;2;1;4;0;0;1 +RS;3;1;1;0;1;3;0;0 +FR;5;2;1;0;1;3;0;0 diff --git a/pandapower/test/loadflow/result_test_network_generator.py b/pandapower/test/loadflow/result_test_network_generator.py index 21e349ff5..312b28aa0 100644 --- a/pandapower/test/loadflow/result_test_network_generator.py +++ b/pandapower/test/loadflow/result_test_network_generator.py @@ -463,7 +463,7 @@ def add_test_shunt_split(net): def add_test_two_open_switches_on_deactive_line(net): b1, b2, l1 = add_grid_connection(net, zone="two_open_switches_on_deactive_line") - b3 = pp.create_bus(net, vn_kv=20.) + b3 = pp.create_bus(net, vn_kv=20., zone="two_open_switches_on_deactive_line") l2 = create_test_line(net, b2, b3, in_service=False) create_test_line(net, b3, b1) pp.create_switch(net, b2, l2, et="l", closed=False) diff --git a/pandapower/test/loadflow/test_results.py b/pandapower/test/loadflow/test_results.py index c20882ab7..f979dbd63 100644 --- a/pandapower/test/loadflow/test_results.py +++ b/pandapower/test/loadflow/test_results.py @@ -472,7 +472,6 @@ def test_ward_split(result_test_network, v_tol=1e-6, i_tol=1e-6, s_tol=5e-3, l_t assert abs(net.res_bus.vm_pu.at[b2] - u) assert abs(net.res_ward.p_mw.loc[[w1, w2]].sum() - (-pw)) assert abs(net.res_ward.q_mvar.loc[[w1, w2]].sum() - (-qw)) - # def test_xward(result_test_network, v_tol=1e-6, i_tol=1e-6, s_tol=5e-3, l_tol=1e-3): diff --git a/pandapower/toolbox/data_modification.py b/pandapower/toolbox/data_modification.py index 87e2f6b77..8655bf90c 100644 --- a/pandapower/toolbox/data_modification.py +++ b/pandapower/toolbox/data_modification.py @@ -141,7 +141,7 @@ def add_zones_to_elements(net, replace=True, elements=None, **kwargs): """ Adds zones to elements, inferring them from the zones of buses they are connected to. """ - elements = ["line", "trafo", "ext_grid", "switch"] if elements is None else elements + elements = pp_elements(bus=False) if elements is None else elements add_column_from_node_to_elements(net, "zone", replace=replace, elements=elements, **kwargs) diff --git a/tutorials/new_optimization_model_pandamodels.ipynb b/tutorials/new_optimization_model_pandamodels.ipynb deleted file mode 100644 index 76fc97eb2..000000000 --- a/tutorials/new_optimization_model_pandamodels.ipynb +++ /dev/null @@ -1,41 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Add New Optimization Model to PandaModels.jl" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "pycharm": { - "name": "#%% md\n" - } - }, - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -}