From 4d3d99a5d0c82d8d4b7ecc22b1d42842e586c8d8 Mon Sep 17 00:00:00 2001 From: Piaulous Date: Tue, 14 Mar 2023 09:43:13 +0100 Subject: [PATCH 1/5] first attempt to calculate voltage drop in lv grid feeder using the longest feeder path --- ding0/grid/lv_grid/build_grid_on_osm_ways.py | 55 +++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/ding0/grid/lv_grid/build_grid_on_osm_ways.py b/ding0/grid/lv_grid/build_grid_on_osm_ways.py index 7c2a829e..02a398d2 100644 --- a/ding0/grid/lv_grid/build_grid_on_osm_ways.py +++ b/ding0/grid/lv_grid/build_grid_on_osm_ways.py @@ -322,9 +322,55 @@ def build_branches_on_osm_ways(lvgd): # get feeder graphs separately feeder_graph = subtree_graph.subgraph(cluster).copy() - cum_feeder_graph_load = sum([feeder_graph.nodes[node]['load'] for node in feeder_graph.nodes]) / 1e3 + if not station_id in feeder_graph.nodes: + line_shp, line_length, line_path = get_shortest_path_shp_multi_target(subtree_graph, station_id, + cluster) + feeder_graph.add_edge(line_path[0], station_id, 0, geometry=line_shp, length=line_length, + feederID=feederID) + feeder_graph.add_node(station_id, **station_attr) + cum_feeder_graph_load = sum([feeder_graph.nodes[node]['load'] for node in feeder_graph.nodes]) / 1e3 cable_type_stub = get_cable_type_by_load(lvgd, cum_feeder_graph_load, cable_lf, cos_phi_load, v_nom) + + ###### + # longest path in feeder + feeder_tree = nx.dfs_tree(feeder_graph, station_id) + nx.set_edge_attributes(feeder_tree, {(u, v): feeder_graph.edges[u, v, 0]['length'] + for u, v, d in feeder_tree.edges.data()}, 'length') + lp_feeder = nx.dag_longest_path(feeder_tree, 'length') + + # mv parameters to check voltage drop in half ring / feeder path + from math import tan, acos + from ding0.tools.pypsa_io import q_sign + cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load') + cos_phi_load_mode = cfg_ding0.get('assumptions', 'cos_phi_load_mode') + mv_max_v_level_lc_diff_normal = float(cfg_ding0.get('mv_routing_tech_constraints', + 'mv_max_v_level_lc_diff_normal')) + r_per_km = cable_type_stub['R_per_km'] + x_per_km = cable_type_stub['L_per_km'] + Q_factor = q_sign(cos_phi_load_mode, 'load') * tan(acos(cos_phi_load)) + + v_level_op = v_level_lp = v_nom * 1e3 # kV to V + r_lp = x_lp = 0 + + # calculate voltage deviation + for n1, n2 in zip(lp_feeder[0:len(lp_feeder) - 1], lp_feeder[1:len(lp_feeder)]): + r_lp += feeder_tree.edges[n1, n2]['length'] * 1e-3 * r_per_km # m to km + x_lp += feeder_tree.edges[n1, n2]['length'] * 1e-3 * x_per_km # m to km + v_level_lp -= feeder_graph.nodes[n2]['load'] * (r_lp + x_lp * Q_factor) / v_level_op + if (v_level_op - v_level_lp) > (v_level_op * mv_max_v_level_lc_diff_normal): + False + + if (v_level_op - v_level_lp) > (v_level_op * mv_max_v_level_lc_diff_normal): + print('FALSE', (v_level_op - v_level_lp) / v_level_op * 100, + nx.dag_longest_path_length(feeder_tree, 'length'), + cable_type_stub['I_max_th']) + else: + print('TRUE', (v_level_op - v_level_lp) / v_level_op * 100, + nx.dag_longest_path_length(feeder_tree, 'length'), + cable_type_stub['I_max_th']) + ###### + for node in cluster: feeder_graph.nodes[node]['feederID'] = feederID @@ -332,13 +378,6 @@ def build_branches_on_osm_ways(lvgd): feeder_graph.edges[edge]['feederID'] = feederID feeder_graph.edges[edge]['cable_type_stub'] = cable_type_stub - if not station_id in feeder_graph.nodes: - line_shp, line_length, line_path = get_shortest_path_shp_multi_target(subtree_graph, station_id, - cluster) - feeder_graph.add_edge(line_path[0], station_id, 0, geometry=line_shp, length=line_length, - feederID=feederID, cable_type_stub=cable_type_stub) - feeder_graph.add_node(station_id, **station_attr) - feeder_graph_list.append(feeder_graph) else: From 782e3ab000f0415960a147e64ccaf6309764a82a Mon Sep 17 00:00:00 2001 From: Piaulous Date: Tue, 14 Mar 2023 13:48:05 +0100 Subject: [PATCH 2/5] introduce function to transform a feeder to a path-representation while considering edge / node weights --- ding0/grid/lv_grid/build_grid_on_osm_ways.py | 46 +++++++++++++------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/ding0/grid/lv_grid/build_grid_on_osm_ways.py b/ding0/grid/lv_grid/build_grid_on_osm_ways.py index 02a398d2..8785ed0f 100644 --- a/ding0/grid/lv_grid/build_grid_on_osm_ways.py +++ b/ding0/grid/lv_grid/build_grid_on_osm_ways.py @@ -141,6 +141,26 @@ def get_shortest_path_tree(graph, station_node, building_nodes, generator_nodes= return sp_tree +def transform_feeder_to_aggregated_path(feeder, station_id): + """ aggregated representation of the feeder as path + containing weightes edges ('length') and nodes ('load') """ + + # transform to dfs tree + tree = nx.dfs_tree(feeder, station_id) + nx.set_edge_attributes(tree, {(u, v): feeder.edges[u, v, 0]['length'] + for u, v, d in tree.edges.data()}, 'length') + # longest path in feeder + lp = nx.dag_longest_path(tree, 'length') + + # aggregate branching load to path nodes + tree_h = tree.copy() + for n in reversed(lp): + branch_nodes = list(nx.dfs_postorder_nodes(tree_h, n)) + tree.nodes[n]['load'] = sum([feeder.nodes[b]['load'] for b in branch_nodes]) + tree_h.remove_nodes_from(branch_nodes) + path_tree = tree.subgraph(lp) + + return path_tree, lp ############### @@ -333,19 +353,15 @@ def build_branches_on_osm_ways(lvgd): cable_type_stub = get_cable_type_by_load(lvgd, cum_feeder_graph_load, cable_lf, cos_phi_load, v_nom) ###### - # longest path in feeder - feeder_tree = nx.dfs_tree(feeder_graph, station_id) - nx.set_edge_attributes(feeder_tree, {(u, v): feeder_graph.edges[u, v, 0]['length'] - for u, v, d in feeder_tree.edges.data()}, 'length') - lp_feeder = nx.dag_longest_path(feeder_tree, 'length') + feeder_path, lp_in_feeder = transform_feeder_to_aggregated_path(feeder_graph, station_id) # mv parameters to check voltage drop in half ring / feeder path from math import tan, acos from ding0.tools.pypsa_io import q_sign cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load') cos_phi_load_mode = cfg_ding0.get('assumptions', 'cos_phi_load_mode') - mv_max_v_level_lc_diff_normal = float(cfg_ding0.get('mv_routing_tech_constraints', - 'mv_max_v_level_lc_diff_normal')) + lv_max_v_level_lc_diff_normal = float(cfg_ding0.get('assumptions', + 'lv_max_v_level_lc_diff_normal')) r_per_km = cable_type_stub['R_per_km'] x_per_km = cable_type_stub['L_per_km'] Q_factor = q_sign(cos_phi_load_mode, 'load') * tan(acos(cos_phi_load)) @@ -354,20 +370,20 @@ def build_branches_on_osm_ways(lvgd): r_lp = x_lp = 0 # calculate voltage deviation - for n1, n2 in zip(lp_feeder[0:len(lp_feeder) - 1], lp_feeder[1:len(lp_feeder)]): - r_lp += feeder_tree.edges[n1, n2]['length'] * 1e-3 * r_per_km # m to km - x_lp += feeder_tree.edges[n1, n2]['length'] * 1e-3 * x_per_km # m to km - v_level_lp -= feeder_graph.nodes[n2]['load'] * (r_lp + x_lp * Q_factor) / v_level_op - if (v_level_op - v_level_lp) > (v_level_op * mv_max_v_level_lc_diff_normal): + for n1, n2 in zip(lp_in_feeder[0:len(lp_in_feeder) - 1], lp_in_feeder[1:len(lp_in_feeder)]): + r_lp += feeder_path.edges[n1, n2]['length'] * 1e-3 * r_per_km # m to km + x_lp += feeder_path.edges[n1, n2]['length'] * 1e-3 * x_per_km # m to km + v_level_lp -= feeder_path.nodes[n2]['load'] * (r_lp + x_lp * Q_factor) / v_level_op + if (v_level_op - v_level_lp) > (v_level_op * lv_max_v_level_lc_diff_normal): False - if (v_level_op - v_level_lp) > (v_level_op * mv_max_v_level_lc_diff_normal): + if (v_level_op - v_level_lp) > (v_level_op * lv_max_v_level_lc_diff_normal): print('FALSE', (v_level_op - v_level_lp) / v_level_op * 100, - nx.dag_longest_path_length(feeder_tree, 'length'), + nx.dag_longest_path_length(feeder_path, 'length'), cable_type_stub['I_max_th']) else: print('TRUE', (v_level_op - v_level_lp) / v_level_op * 100, - nx.dag_longest_path_length(feeder_tree, 'length'), + nx.dag_longest_path_length(feeder_path, 'length'), cable_type_stub['I_max_th']) ###### From 2cc5ba0ef27826bb01a4373643a05bdc39966ce8 Mon Sep 17 00:00:00 2001 From: Piaulous Date: Thu, 16 Mar 2023 18:15:36 +0100 Subject: [PATCH 3/5] new functionality to check for voltage violations in lv feeders - still in progress and not finished --- ding0/grid/lv_grid/build_grid_on_osm_ways.py | 180 ++++++++++++------- 1 file changed, 119 insertions(+), 61 deletions(-) diff --git a/ding0/grid/lv_grid/build_grid_on_osm_ways.py b/ding0/grid/lv_grid/build_grid_on_osm_ways.py index 8785ed0f..6b713567 100644 --- a/ding0/grid/lv_grid/build_grid_on_osm_ways.py +++ b/ding0/grid/lv_grid/build_grid_on_osm_ways.py @@ -19,12 +19,14 @@ import osmnx as ox import numpy as np +from math import tan, acos from shapely.geometry import LineString, Point from ding0.core.network import BranchDing0 from ding0.core.network.cable_distributors import LVCableDistributorDing0 from ding0.core.network.loads import LVLoadDing0 from ding0.tools import config as cfg_ding0 +from ding0.tools.pypsa_io import q_sign from ding0.grid.lv_grid.routing import identify_street_loads from ding0.grid.mv_grid.tools import get_shortest_path_shp_single_target, get_shortest_path_shp_multi_target @@ -143,24 +145,24 @@ def get_shortest_path_tree(graph, station_node, building_nodes, generator_nodes= def transform_feeder_to_aggregated_path(feeder, station_id): """ aggregated representation of the feeder as path - containing weightes edges ('length') and nodes ('load') """ + containing weighted edges ('length') and nodes ('load') """ # transform to dfs tree tree = nx.dfs_tree(feeder, station_id) nx.set_edge_attributes(tree, {(u, v): feeder.edges[u, v, 0]['length'] for u, v, d in tree.edges.data()}, 'length') # longest path in feeder - lp = nx.dag_longest_path(tree, 'length') + lp_in_feeder = nx.dag_longest_path(tree, 'length') # aggregate branching load to path nodes tree_h = tree.copy() - for n in reversed(lp): - branch_nodes = list(nx.dfs_postorder_nodes(tree_h, n)) - tree.nodes[n]['load'] = sum([feeder.nodes[b]['load'] for b in branch_nodes]) + for node in reversed(lp_in_feeder): + branch_nodes = list(nx.dfs_postorder_nodes(tree_h, node)) + tree.nodes[node]['load'] = sum([feeder.nodes[b]['load'] for b in branch_nodes]) tree_h.remove_nodes_from(branch_nodes) - path_tree = tree.subgraph(lp) + feeder_agg = tree.subgraph(lp_in_feeder) - return path_tree, lp + return feeder_agg, lp_in_feeder ############### @@ -189,21 +191,105 @@ def allocate_street_load_nodes(lv_loads_grid, shortest_tree_district_graph, stat return shortest_tree_district_graph, street_loads, household_loads +def reinforce_cable_type(lim_current, lvgd, v_nom, cable_lf): + """ finds minimum required cable type based on limiting current value""" -def get_cable_type_by_load(lvgd, capacity, cable_lf, cos_phi_load, v_nom): + # get branch parameters + v_nom *= 1e3 # kV to V + branch_parameters = lvgd.lv_grid.network.static_data['LV_cables'] + branch_parameters = branch_parameters[branch_parameters['U_n'] == v_nom].sort_values('I_max_th') + + # find suitable cable types + suitable_cables = branch_parameters[(branch_parameters['I_max_th'] * cable_lf) > lim_current] + reinforcable = True + + # find minimum required cable + if len(suitable_cables): + cable_type = suitable_cables.loc[suitable_cables['I_max_th'].idxmin(), :] + else: # TODO: what happens if no cable suitable because current limit / voltage drop too high + cable_type = branch_parameters.iloc[-1] + reinforcable = False + + return cable_type, reinforcable + + +def cable_type_by_load(lvgd, capacity, cable_lf, cos_phi_load, v_nom): """ get cable type for given capacity as param """ - I_max_load = capacity / (3 ** 0.5 * v_nom) / cos_phi_load - # determine suitable cable for this current - suitable_cables_stub = lvgd.lv_grid.network.static_data['LV_cables'][ - (lvgd.lv_grid.network.static_data['LV_cables'][ - 'I_max_th'] * cable_lf) > I_max_load] - if len(suitable_cables_stub): - cable_type_stub = suitable_cables_stub.loc[suitable_cables_stub['I_max_th'].idxmin(), :] - else: # TODO: what to do if no cable is suitable for I_max_load, e.g. many loads connected to feeder? - cable_type_stub = lvgd.lv_grid.network.static_data['LV_cables'].iloc[0] # take strongest cable if no one suits + current_max_load = capacity / (3 ** 0.5 * v_nom) / cos_phi_load + cable_type, _ = reinforce_cable_type(current_max_load, lvgd, v_nom, cable_lf) + + return cable_type + + +def check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, + v_nom, cos_phi_load, cos_phi_load_mode, + lv_max_v_level_lc_diff_normal): + + """ returns boolean if a critical voltage drop has been detected """ + + # lv parameters to check voltage drop in half ring / feeder path + r_per_km = cable_type['R_per_km'] + x_per_km = cable_type['L_per_km'] + q_factor = q_sign(cos_phi_load_mode, 'load') * tan(acos(cos_phi_load)) + + # set initial values + v_level_op = v_lp = v_nom * 1e3 # kV to V + r_lp = x_lp = 0 + crit_v_drop = False - return cable_type_stub + # calculate voltage deviation + for n1, n2 in zip(lp_in_feeder[0:len(lp_in_feeder) - 1], lp_in_feeder[1:len(lp_in_feeder)]): + r_lp += feeder_agg.edges[n1, n2]['length'] * r_per_km * 1e-3 # m to km + x_lp += feeder_agg.edges[n1, n2]['length'] * x_per_km * 1e-3 # m to km + v_lp -= feeder_agg.nodes[n2]['load'] * (r_lp + x_lp * q_factor) / v_level_op + if (v_level_op - v_lp) > (v_level_op * lv_max_v_level_lc_diff_normal): + crit_v_drop = True + break + + return crit_v_drop + +def cable_type_by_voltage(feeder_graph, station_id, cable_type, + lvgd, v_nom, lv_max_v_level_lc_diff_normal, + cos_phi_load, cos_phi_load_mode, cable_lf, + feederID): + + """ get cable type by checking for max allowed voltage deviation """ + + # get aggregated feeder representation + feeder_agg, lp_in_feeder = transform_feeder_to_aggregated_path(feeder_graph, station_id) + + # check for voltage deviation exceeding allowed limit + crit_v_drop = check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, + v_nom, cos_phi_load, cos_phi_load_mode, + lv_max_v_level_lc_diff_normal) + check = False + if not crit_v_drop: + print('no reinf necessary in', feederID) + else: + check = True + print('reinf necessary in', feederID) + print('1: ', cable_type['I_max_th']) + # reinforce cable as long as violations are detected + while crit_v_drop: + + cable_type_new, reinforcable = reinforce_cable_type(cable_type['I_max_th'], lvgd, v_nom, cable_lf) + + if reinforcable: + cable_type = cable_type_new + crit_v_drop = check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, + v_nom, cos_phi_load, cos_phi_load_mode, + lv_max_v_level_lc_diff_normal) + else: + print('max iter') + break + ##### + if check: + print('2: ', cable_type['I_max_th']) + + return cable_type + + return cable_type def get_n_feeder_mandatory(capacity, v_nom, cos_phi_load): @@ -221,11 +307,13 @@ def build_branches_on_osm_ways(lvgd): lvgd : LVGridDistrictDing0 Low-voltage grid district object """ - cable_lf = cfg_ding0.get('assumptions', - 'load_factor_lv_cable_lc_normal') - cos_phi_load = cfg_ding0.get('assumptions', - 'cos_phi_load') + + # get required static data on lv level v_nom = cfg_ding0.get('assumptions', 'lv_nominal_voltage') / 1e3 # v_nom in kV + cable_lf = cfg_ding0.get('assumptions', 'load_factor_lv_cable_lc_normal') + cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load') + cos_phi_load_mode = cfg_ding0.get('assumptions', 'cos_phi_load_mode') + lv_max_v_level_lc_diff_normal = float(cfg_ding0.get('assumptions', 'lv_max_v_level_lc_diff_normal')) # obtain shortest_tree_graph_district from graph_district # due to graph_district contains all osm ways in district @@ -349,43 +437,13 @@ def build_branches_on_osm_ways(lvgd): feederID=feederID) feeder_graph.add_node(station_id, **station_attr) + # reinforce feeder's cable type based on current limit and voltage drop cum_feeder_graph_load = sum([feeder_graph.nodes[node]['load'] for node in feeder_graph.nodes]) / 1e3 - cable_type_stub = get_cable_type_by_load(lvgd, cum_feeder_graph_load, cable_lf, cos_phi_load, v_nom) - - ###### - feeder_path, lp_in_feeder = transform_feeder_to_aggregated_path(feeder_graph, station_id) - - # mv parameters to check voltage drop in half ring / feeder path - from math import tan, acos - from ding0.tools.pypsa_io import q_sign - cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load') - cos_phi_load_mode = cfg_ding0.get('assumptions', 'cos_phi_load_mode') - lv_max_v_level_lc_diff_normal = float(cfg_ding0.get('assumptions', - 'lv_max_v_level_lc_diff_normal')) - r_per_km = cable_type_stub['R_per_km'] - x_per_km = cable_type_stub['L_per_km'] - Q_factor = q_sign(cos_phi_load_mode, 'load') * tan(acos(cos_phi_load)) - - v_level_op = v_level_lp = v_nom * 1e3 # kV to V - r_lp = x_lp = 0 - - # calculate voltage deviation - for n1, n2 in zip(lp_in_feeder[0:len(lp_in_feeder) - 1], lp_in_feeder[1:len(lp_in_feeder)]): - r_lp += feeder_path.edges[n1, n2]['length'] * 1e-3 * r_per_km # m to km - x_lp += feeder_path.edges[n1, n2]['length'] * 1e-3 * x_per_km # m to km - v_level_lp -= feeder_path.nodes[n2]['load'] * (r_lp + x_lp * Q_factor) / v_level_op - if (v_level_op - v_level_lp) > (v_level_op * lv_max_v_level_lc_diff_normal): - False - - if (v_level_op - v_level_lp) > (v_level_op * lv_max_v_level_lc_diff_normal): - print('FALSE', (v_level_op - v_level_lp) / v_level_op * 100, - nx.dag_longest_path_length(feeder_path, 'length'), - cable_type_stub['I_max_th']) - else: - print('TRUE', (v_level_op - v_level_lp) / v_level_op * 100, - nx.dag_longest_path_length(feeder_path, 'length'), - cable_type_stub['I_max_th']) - ###### + cable_type_stub = cable_type_by_load(lvgd, cum_feeder_graph_load, cable_lf, cos_phi_load, v_nom) + cable_type_stub = cable_type_by_voltage(feeder_graph, station_id, cable_type_stub, + lvgd, v_nom, lv_max_v_level_lc_diff_normal, + cos_phi_load, cos_phi_load_mode, cable_lf, + feederID) for node in cluster: feeder_graph.nodes[node]['feederID'] = feederID @@ -402,7 +460,7 @@ def build_branches_on_osm_ways(lvgd): feeder_graph = subtree_graph.copy() - cable_type_stub = get_cable_type_by_load(lvgd, cum_subtree_load, cable_lf, cos_phi_load, v_nom) + cable_type_stub = cable_type_by_load(lvgd, cum_subtree_load, cable_lf, cos_phi_load, v_nom) for node in feeder_graph.nodes: feeder_graph.nodes[node]['feederID'] = feederID @@ -445,7 +503,7 @@ def build_branches_on_osm_ways(lvgd): 'feederID': nn_attr['feederID'], } - cable_type_stub = get_cable_type_by_load(lvgd, row.capacity, cable_lf, cos_phi_load, v_nom) + cable_type_stub = cable_type_by_load(lvgd, row.capacity, cable_lf, cos_phi_load, v_nom) G.add_node(building_node, **attr) G.add_edge(building_node, row.nn, 0, geometry=LineString([row.geometry, row.nn_coords]), length=row.nn_dist, feederID=nn_attr['feederID'], cable_type_stub=cable_type_stub) @@ -477,7 +535,7 @@ def build_branches_on_osm_ways(lvgd): # route singular cable line_shp, line_length, line_path = get_shortest_path_shp_single_target(full_graph, building_node, station_id, return_path=True, nodes_as_str=False) - cable_type_stub = get_cable_type_by_load(lvgd, row.capacity, cable_lf, cos_phi_load, v_nom) + cable_type_stub = cable_type_by_load(lvgd, row.capacity, cable_lf, cos_phi_load, v_nom) G.add_edge(building_node, station_id, geometry=line_shp, length=line_length, feederID=feederID, cable_type_stub=cable_type_stub) From 8075cb3955af51b94b419115e4e0f264e595bde8 Mon Sep 17 00:00:00 2001 From: Piaulous Date: Mon, 20 Mar 2023 16:56:44 +0100 Subject: [PATCH 4/5] updated functions using config dict with lvgd parameters - as overview: new / updated functions are transform_feeder_to_aggregated_path, get_n_feeder_mandatory, reinforce_cable_type, check_voltage_drop_in_feeder, cable_type_by_load, cable_type_by_voltage --- ding0/config/config_calc.cfg | 2 + ding0/grid/lv_grid/build_grid_on_osm_ways.py | 118 ++++++++++--------- 2 files changed, 64 insertions(+), 56 deletions(-) diff --git a/ding0/config/config_calc.cfg b/ding0/config/config_calc.cfg index 3f052563..da89f503 100644 --- a/ding0/config/config_calc.cfg +++ b/ding0/config/config_calc.cfg @@ -96,6 +96,8 @@ frequency = 50 # LV nominal voltage: unit: V lv_nominal_voltage = 400 +# LV standard line: unit: - +lv_standard_line = NAYY 4x1x150 # LV grids (house holds): unit: - apartment_house_branch_ratio = 1.5 diff --git a/ding0/grid/lv_grid/build_grid_on_osm_ways.py b/ding0/grid/lv_grid/build_grid_on_osm_ways.py index 6b713567..713b7b84 100644 --- a/ding0/grid/lv_grid/build_grid_on_osm_ways.py +++ b/ding0/grid/lv_grid/build_grid_on_osm_ways.py @@ -191,44 +191,51 @@ def allocate_street_load_nodes(lv_loads_grid, shortest_tree_district_graph, stat return shortest_tree_district_graph, street_loads, household_loads -def reinforce_cable_type(lim_current, lvgd, v_nom, cable_lf): +def reinforce_cable_type(lim_current, lvgd_cfg): """ finds minimum required cable type based on limiting current value""" - # get branch parameters - v_nom *= 1e3 # kV to V - branch_parameters = lvgd.lv_grid.network.static_data['LV_cables'] - branch_parameters = branch_parameters[branch_parameters['U_n'] == v_nom].sort_values('I_max_th') + # get static data on lv level + lv_cable_lf = lvgd_cfg['lv_cable_lf'] + lv_cables_df = lvgd_cfg['lv_cables_df'] # find suitable cable types - suitable_cables = branch_parameters[(branch_parameters['I_max_th'] * cable_lf) > lim_current] + suitable_cables = lv_cables_df[(lv_cables_df['I_max_th'] * lv_cable_lf) > lim_current] reinforcable = True # find minimum required cable if len(suitable_cables): cable_type = suitable_cables.loc[suitable_cables['I_max_th'].idxmin(), :] else: # TODO: what happens if no cable suitable because current limit / voltage drop too high - cable_type = branch_parameters.iloc[-1] + cable_type = lv_cables_df.iloc[-1] reinforcable = False return cable_type, reinforcable -def cable_type_by_load(lvgd, capacity, cable_lf, cos_phi_load, v_nom): +def cable_type_by_load(capacity, lvgd_cfg): """ get cable type for given capacity as param """ + # get static data on lv level + v_nom = lvgd_cfg['v_nom'] + cos_phi_load = lvgd_cfg['cos_phi_load'] + current_max_load = capacity / (3 ** 0.5 * v_nom) / cos_phi_load - cable_type, _ = reinforce_cable_type(current_max_load, lvgd, v_nom, cable_lf) + cable_type, _ = reinforce_cable_type(current_max_load, lvgd_cfg) return cable_type -def check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, - v_nom, cos_phi_load, cos_phi_load_mode, - lv_max_v_level_lc_diff_normal): +def check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, lvgd_cfg): """ returns boolean if a critical voltage drop has been detected """ - # lv parameters to check voltage drop in half ring / feeder path + # get static data on lv level + v_nom = lvgd_cfg['v_nom'] + v_diff_max = lvgd_cfg['v_diff_max'] + cos_phi_load = lvgd_cfg['cos_phi_load'] + cos_phi_load_mode = lvgd_cfg['cos_phi_load_mode'] + + # lv cable parameters to check voltage drop in half ring / feeder path r_per_km = cable_type['R_per_km'] x_per_km = cable_type['L_per_km'] q_factor = q_sign(cos_phi_load_mode, 'load') * tan(acos(cos_phi_load)) @@ -243,16 +250,13 @@ def check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, r_lp += feeder_agg.edges[n1, n2]['length'] * r_per_km * 1e-3 # m to km x_lp += feeder_agg.edges[n1, n2]['length'] * x_per_km * 1e-3 # m to km v_lp -= feeder_agg.nodes[n2]['load'] * (r_lp + x_lp * q_factor) / v_level_op - if (v_level_op - v_lp) > (v_level_op * lv_max_v_level_lc_diff_normal): + if (v_level_op - v_lp) > (v_level_op * v_diff_max): crit_v_drop = True break return crit_v_drop -def cable_type_by_voltage(feeder_graph, station_id, cable_type, - lvgd, v_nom, lv_max_v_level_lc_diff_normal, - cos_phi_load, cos_phi_load_mode, cable_lf, - feederID): +def cable_type_by_voltage(feeder_graph, station_id, cable_type, lvgd_cfg): """ get cable type by checking for max allowed voltage deviation """ @@ -260,42 +264,35 @@ def cable_type_by_voltage(feeder_graph, station_id, cable_type, feeder_agg, lp_in_feeder = transform_feeder_to_aggregated_path(feeder_graph, station_id) # check for voltage deviation exceeding allowed limit - crit_v_drop = check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, - v_nom, cos_phi_load, cos_phi_load_mode, - lv_max_v_level_lc_diff_normal) - check = False - if not crit_v_drop: - print('no reinf necessary in', feederID) - else: - check = True - print('reinf necessary in', feederID) - print('1: ', cable_type['I_max_th']) + crit_v_drop = check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, lvgd_cfg) + # reinforce cable as long as violations are detected while crit_v_drop: - cable_type_new, reinforcable = reinforce_cable_type(cable_type['I_max_th'], lvgd, v_nom, cable_lf) + cable_type_new, reinforcable = reinforce_cable_type(cable_type['I_max_th'], lvgd_cfg) if reinforcable: cable_type = cable_type_new - crit_v_drop = check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, - v_nom, cos_phi_load, cos_phi_load_mode, - lv_max_v_level_lc_diff_normal) + crit_v_drop = check_voltage_drop_in_feeder(feeder_agg, lp_in_feeder, cable_type, lvgd_cfg) else: - print('max iter') break - ##### - if check: - print('2: ', cable_type['I_max_th']) return cable_type - return cable_type +def get_n_feeder_mandatory(capacity, lvgd_cfg): + + # get static data on lv level + v_nom = lvgd_cfg['v_nom'] + cos_phi_load = lvgd_cfg['cos_phi_load'] + lv_cables_df = lvgd_cfg['lv_cables_df'] + lv_standard_line = cfg_ding0.get('assumptions', 'lv_standard_line') # NAYY 4x1x150, source: dena Verteilnetzstudie -def get_n_feeder_mandatory(capacity, v_nom, cos_phi_load): - I_max_allowed = 275 ##TODO: get value via config from standard cable type # refers to 150 mm2 standard type ref: dena Verteilnetzstudie - I_max_load_at_feeder = capacity / (3 ** 0.5 * v_nom) / cos_phi_load - return np.ceil(I_max_load_at_feeder / I_max_allowed) + # divide feeder load by max. cable type load + current_max_cable = lv_cables_df.loc[lv_standard_line, 'I_max_th'] + current_max_feeder = capacity / (3 ** 0.5 * v_nom) / cos_phi_load + + return np.ceil(current_max_feeder / current_max_cable) def build_branches_on_osm_ways(lvgd): @@ -308,12 +305,14 @@ def build_branches_on_osm_ways(lvgd): Low-voltage grid district object """ - # get required static data on lv level - v_nom = cfg_ding0.get('assumptions', 'lv_nominal_voltage') / 1e3 # v_nom in kV - cable_lf = cfg_ding0.get('assumptions', 'load_factor_lv_cable_lc_normal') - cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load') - cos_phi_load_mode = cfg_ding0.get('assumptions', 'cos_phi_load_mode') - lv_max_v_level_lc_diff_normal = float(cfg_ding0.get('assumptions', 'lv_max_v_level_lc_diff_normal')) + # get required static config data on lv level as dict + lvgd_cfg = {} + lvgd_cfg['v_nom'] = cfg_ding0.get('assumptions', 'lv_nominal_voltage') / 1e3 + lvgd_cfg['v_diff_max'] = float(cfg_ding0.get('assumptions', 'lv_max_v_level_lc_diff_normal')) + lvgd_cfg['cos_phi_load'] = cfg_ding0.get('assumptions', 'cos_phi_load') + lvgd_cfg['cos_phi_load_mode'] = cfg_ding0.get('assumptions', 'cos_phi_load_mode') + lvgd_cfg['lv_cable_lf'] = cfg_ding0.get('assumptions', 'load_factor_lv_cable_lc_normal') + lvgd_cfg['lv_cables_df'] = lvgd.lv_grid.network.static_data['LV_cables'].sort_values('I_max_th') # obtain shortest_tree_graph_district from graph_district # due to graph_district contains all osm ways in district @@ -391,7 +390,7 @@ def build_branches_on_osm_ways(lvgd): cum_subtree_load = sum([subtree_graph.nodes[node]['load'] for node in subtree_graph.nodes]) / 1e3 - n_feeder = get_n_feeder_mandatory(cum_subtree_load, v_nom, cos_phi_load) + n_feeder = get_n_feeder_mandatory(cum_subtree_load, lvgd_cfg) # print(f"load {cum_subtree_load} needs n_feeder {n_feeder}") @@ -439,11 +438,9 @@ def build_branches_on_osm_ways(lvgd): # reinforce feeder's cable type based on current limit and voltage drop cum_feeder_graph_load = sum([feeder_graph.nodes[node]['load'] for node in feeder_graph.nodes]) / 1e3 - cable_type_stub = cable_type_by_load(lvgd, cum_feeder_graph_load, cable_lf, cos_phi_load, v_nom) - cable_type_stub = cable_type_by_voltage(feeder_graph, station_id, cable_type_stub, - lvgd, v_nom, lv_max_v_level_lc_diff_normal, - cos_phi_load, cos_phi_load_mode, cable_lf, - feederID) + + cable_type_stub = cable_type_by_load(cum_feeder_graph_load, lvgd_cfg) + cable_type_stub = cable_type_by_voltage(feeder_graph, station_id, cable_type_stub, lvgd_cfg) for node in cluster: feeder_graph.nodes[node]['feederID'] = feederID @@ -460,7 +457,8 @@ def build_branches_on_osm_ways(lvgd): feeder_graph = subtree_graph.copy() - cable_type_stub = cable_type_by_load(lvgd, cum_subtree_load, cable_lf, cos_phi_load, v_nom) + cable_type_stub = cable_type_by_load(cum_subtree_load, lvgd_cfg) + cable_type_stub = cable_type_by_voltage(feeder_graph, station_id, cable_type_stub, lvgd_cfg) for node in feeder_graph.nodes: feeder_graph.nodes[node]['feederID'] = feederID @@ -503,7 +501,7 @@ def build_branches_on_osm_ways(lvgd): 'feederID': nn_attr['feederID'], } - cable_type_stub = cable_type_by_load(lvgd, row.capacity, cable_lf, cos_phi_load, v_nom) + cable_type_stub = cable_type_by_load(row.capacity, lvgd_cfg) G.add_node(building_node, **attr) G.add_edge(building_node, row.nn, 0, geometry=LineString([row.geometry, row.nn_coords]), length=row.nn_dist, feederID=nn_attr['feederID'], cable_type_stub=cable_type_stub) @@ -535,7 +533,15 @@ def build_branches_on_osm_ways(lvgd): # route singular cable line_shp, line_length, line_path = get_shortest_path_shp_single_target(full_graph, building_node, station_id, return_path=True, nodes_as_str=False) - cable_type_stub = cable_type_by_load(lvgd, row.capacity, cable_lf, cos_phi_load, v_nom) + # 1) find cable type by current limit + cable_type_stub = cable_type_by_load(row.capacity, lvgd_cfg) + # 2.1) build feeder graph + full_graph.add_edge(station_id, building_node, geometry=line_shp, length=line_shp.length) + feeder_graph = full_graph.subgraph([station_id, building_node]) + nx.set_node_attributes(feeder_graph, {station_id: {'load': 0}, building_node: {"load": row.capacity}}) + # 2.2) find cable type by voltage drop + cable_type_stub = cable_type_by_voltage(feeder_graph, station_id, cable_type_stub, lvgd_cfg) + # add singular feeder G.add_edge(building_node, station_id, geometry=line_shp, length=line_length, feederID=feederID, cable_type_stub=cable_type_stub) From ac3c83205c862da7a67387b7663a7beea2fceee5 Mon Sep 17 00:00:00 2001 From: Piaulous Date: Mon, 27 Mar 2023 14:46:18 +0200 Subject: [PATCH 5/5] deleted unnecessary row --- ding0/grid/lv_grid/build_grid_on_osm_ways.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ding0/grid/lv_grid/build_grid_on_osm_ways.py b/ding0/grid/lv_grid/build_grid_on_osm_ways.py index 713b7b84..1a365ee6 100644 --- a/ding0/grid/lv_grid/build_grid_on_osm_ways.py +++ b/ding0/grid/lv_grid/build_grid_on_osm_ways.py @@ -380,7 +380,6 @@ def build_branches_on_osm_ways(lvgd): nodelists = list(nx.weakly_connected_components(g)) feederID = 0 # starting with feederId=0 for station. All leaving feeders will get an incremented feederId - new_lvgd_peak_load_considering_simultaneity = 0 feeder_graph_list = [] # append each feeder here # create subtrees from tree graph based on number of with station incident edges