예제 #1
0
def select_grid_model_residential(lvgd):
    """Selects typified model grid based on population

    Parameters
    ----------
    lvgd : LVGridDistrictDing0
        Low-voltage grid district object

    Returns
    -------
    :pandas:`pandas.DataFrame<dataframe>`
        Selected string of typified model grid
    :pandas:`pandas.DataFrame<dataframe>`
        Parameters of chosen Transformer

    Note
    -----
    In total 196 distinct LV grid topologies are available that are chosen
    by population in the LV grid district. Population is translated to
    number of house branches. Each grid model fits a number of house
    branches. If this number exceeds 196, still the grid topology of 196
    house branches is used. The peak load of the LV grid district is
    uniformly distributed across house branches.
    """

    # Load properties of LV typified model grids
    string_properties = lvgd.lv_grid.network.static_data[
        'LV_model_grids_strings']
    # Load relational table of apartment count and strings of model grid
    apartment_string = lvgd.lv_grid.network.static_data[
        'LV_model_grids_strings_per_grid']

    # load assumtions
    apartment_house_branch_ratio = cfg_ding0.get(
        "assumptions", "apartment_house_branch_ratio")
    population_per_apartment = cfg_ding0.get("assumptions",
                                             "population_per_apartment")

    # calc count of apartments to select string types
    apartments = round(lvgd.population / population_per_apartment)

    if apartments > 196:
        apartments = 196

    # select set of strings that represent one type of model grid
    strings = apartment_string.loc[apartments]
    selected_strings = [int(s) for s in strings[strings >= 1].index.tolist()]

    # slice dataframe of string parameters
    selected_strings_df = string_properties.loc[selected_strings]

    # add number of occurences of each branch to df
    occurence_selector = [str(i) for i in selected_strings]
    selected_strings_df['occurence'] = strings.loc[occurence_selector].tolist()

    return selected_strings_df
예제 #2
0
def get_voltage_at_bus_bar(grid, tree):
    """
    Determine voltage level at bus bar of MV-LV substation

    Parameters
    ----------
    grid : LVGridDing0
        Ding0 grid object
    tree : :networkx:`NetworkX Graph Obj< >`
        Tree of grid topology:

    Returns
    -------
    :any:`list`
        Voltage at bus bar. First item refers to load case, second item refers
        to voltage in feedin (generation) case
    """

    # voltage at substation bus bar
    r_mv_grid, x_mv_grid = get_mv_impedance(grid)

    r_trafo = sum([tr.r for tr in grid._station._transformers])
    x_trafo = sum([tr.x for tr in grid._station._transformers])

    cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')
    cos_phi_feedin = cfg_ding0.get('assumptions', 'cos_phi_gen')
    v_nom = cfg_ding0.get('assumptions', 'lv_nominal_voltage')

    # loads and generators connected to bus bar
    bus_bar_load = sum([
        node.peak_load for node in tree.successors(grid._station)
        if isinstance(node, LVLoadDing0)
    ]) / cos_phi_load
    bus_bar_generation = sum([
        node.capacity for node in tree.successors(grid._station)
        if isinstance(node, GeneratorDing0)
    ]) / cos_phi_feedin

    v_delta_load_case_bus_bar = voltage_delta_vde(v_nom, bus_bar_load,
                                                  (r_mv_grid + r_trafo),
                                                  (x_mv_grid + x_trafo),
                                                  cos_phi_load)
    v_delta_gen_case_bus_bar = voltage_delta_vde(v_nom, bus_bar_generation,
                                                 (r_mv_grid + r_trafo),
                                                 -(x_mv_grid + x_trafo),
                                                 cos_phi_feedin)

    return v_delta_load_case_bus_bar, v_delta_gen_case_bus_bar
예제 #3
0
def get_voltage_delta_branch(grid, tree, node, r_preceeding, x_preceeding):
    """
    Determine voltage for a preceeding branch (edge) of node

    Parameters
    ----------
    grid : LVGridDing0
        Ding0 grid object
    tree : :networkx:`NetworkX Graph Obj< >`
        Tree of grid topology
    node : graph node
        Node to determine voltage level at
    r_preceeding : float
        Resitance of preceeding grid
    x_preceeding : float
        Reactance of preceeding grid

    Return
    ------
    :any:`float`
        Delta voltage for node
    """
    cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')
    cos_phi_feedin = cfg_ding0.get('assumptions', 'cos_phi_gen')
    v_nom = cfg_ding0.get('assumptions', 'lv_nominal_voltage')
    omega = 2 * math.pi * 50

    # add resitance/ reactance to preceeding
    in_edge = [
        _ for _ in grid.graph_branches_from_node(node)
        if _[0] in tree.predecessors(node)
    ][0][1]
    r = r_preceeding + (in_edge['branch'].type['R'] * in_edge['branch'].length)
    x = x_preceeding + (in_edge['branch'].type['L'] / 1e3 * omega *
                        in_edge['branch'].length)

    # get apparent power for load and generation case
    peak_load, gen_capacity = get_house_conn_gen_load(tree, node)
    s_max_load = peak_load / cos_phi_load
    s_max_feedin = gen_capacity / cos_phi_feedin

    # determine voltage increase/ drop a node
    voltage_delta_load = voltage_delta_vde(v_nom, s_max_load, r, x,
                                           cos_phi_load)
    voltage_delta_gen = voltage_delta_vde(v_nom, s_max_feedin, r, -x,
                                          cos_phi_feedin)

    return [voltage_delta_load, voltage_delta_gen, r, x]
예제 #4
0
def get_mv_impedance_at_voltage_level(grid, voltage_level):
    """
    Determine MV grid impedance (resistance and reactance separately)

    Parameters
    ----------
    grid : :class:`~.ding0.core.network.grids.LVGridDing0`
    voltage_level: float
        voltage level to which impedance is rescaled (normally 0.4 kV for LV)

    Returns
    -------
    :obj:`list`
        List containing resistance and reactance of MV grid
    """

    freq = cfg_ding0.get('assumptions', 'frequency')
    omega = 2 * math.pi * freq

    mv_grid = grid.grid_district.lv_load_area.mv_grid_district.mv_grid
    edges = mv_grid.find_path(grid._station, mv_grid._station, type='edges')
    r_mv_grid = sum([
        e[2]['branch'].type['R_per_km'] * e[2]['branch'].length / 1e3
        for e in edges
    ])
    x_mv_grid = sum([
        e[2]['branch'].type['L_per_km'] / 1e3 * omega * e[2]['branch'].length /
        1e3 for e in edges
    ])
    # rescale to voltage level
    r_mv_grid_vl = r_mv_grid * (voltage_level / mv_grid.v_level)**2
    x_mv_grid_vl = x_mv_grid * (voltage_level / mv_grid.v_level)**2
    return [r_mv_grid_vl, x_mv_grid_vl]
예제 #5
0
파일: animation.py 프로젝트: mhdella/ding0
    def __init__(self, **kwargs):
        output_animation_file_prefix = cfg_ding0.get('output',
                                                     'animation_file_prefix')

        self.file_path = os.path.join(get_default_home_dir(), 'animation/')
        self.file_prefix = output_animation_file_prefix
        self.counter = 1
예제 #6
0
파일: geo.py 프로젝트: killthekitten/ding0
def calc_geo_dist_vincenty(node_source, node_target):
    """ Calculates the geodesic distance between `node_source` and `node_target`
    incorporating the detour factor specified in :file:`ding0/ding0/config/config_calc.cfg`.

    Parameters
    ----------
    node_source: LVStationDing0, GeneratorDing0, or CableDistributorDing0
        source node, member of GridDing0._graph
    node_target: LVStationDing0, GeneratorDing0, or CableDistributorDing0
        target node, member of GridDing0._graph

    Returns
    -------
    :any:`float`
        Distance in m
    """

    branch_detour_factor = cfg_ding0.get('assumptions', 'branch_detour_factor')

    # notice: vincenty takes (lat,lon)
    branch_length = branch_detour_factor * vincenty(
        (node_source.geo_data.y, node_source.geo_data.x),
        (node_target.geo_data.y, node_target.geo_data.x)).m

    # ========= BUG: LINE LENGTH=0 WHEN CONNECTING GENERATORS ===========
    # When importing generators, the geom_new field is used as position. If it is empty, EnergyMap's geom
    # is used and so there are a couple of generators at the same position => length of interconnecting
    # line is 0. See issue #76
    if branch_length == 0:
        branch_length = 1
        logger.warning('Geo distance is zero, check objects\' positions. '
                       'Distance is set to 1m')
    # ===================================================================

    return branch_length
예제 #7
0
    def solve(self, graph, savings_solution, timeout, debug=False, anim=None):
        """Improve initial savings solution using local search

        Parameters
        ----------
        graph: :networkx:`NetworkX Graph Obj< >`
            Graph instance
        savings_solution: SavingsSolution
            initial solution of CVRP problem (instance of `SavingsSolution` class)
        timeout: :obj:`int`
            max processing time in seconds
        debug: bool, defaults to False
            If True, information is printed while routing
        anim: AnimationDing0
            AnimationDing0 object
        
        Returns
        -------
        LocalSearchSolution
           A solution (LocalSearchSolution class)

        """
        # TODO: If necessary, use timeout to set max processing time of local search

        # load threshold for operator (see exchange or relocate operator's description for more information)
        op_diff_round_digits = int(
            cfg_ding0.get('mv_routing', 'operator_diff_round_digits'))

        solution = LocalSearchSolution(graph, savings_solution)

        # FOR BENCHMARKING OF OPERATOR'S ORDER:
        #self.benchmark_operator_order(graph, savings_solution, op_diff_round_digits)

        for run in range(10):
            start = time.time()
            solution = self.operator_exchange(graph, solution,
                                              op_diff_round_digits, anim)
            time1 = time.time()
            if debug:
                logger.debug('Elapsed time (exchange, run {1}): {0}, '
                             'Solution\'s length: {2}'.format(
                                 time1 - start, str(run), solution.length()))

            solution = self.operator_relocate(graph, solution,
                                              op_diff_round_digits, anim)
            time2 = time.time()
            if debug:
                logger.debug('Elapsed time (relocate, run {1}): {0}, '
                             'Solution\'s length: {2}'.format(
                                 time2 - time1, str(run), solution.length()))

            solution = self.operator_oropt(graph, solution,
                                           op_diff_round_digits, anim)
            time3 = time.time()
            if debug:
                logger.debug('Elapsed time (oropt, run {1}): {0}, '
                             'Solution\'s length: {2}'.format(
                                 time3 - time2, str(run), solution.length()))

        return solution
예제 #8
0
    def can_add_lv_load_area(self, node):
        # TODO: check docstring
        """Sums up peak load of LV stations 
        
        That is, total peak load for satellite string
        
        Parameters
        ----------
        node: :class:`~.ding0.core.GridDing0`
            Descr
        
        Returns
        -------
        :obj: `bool`
            True if ????
        
        """
        # get power factor for loads
        cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')

        lv_load_area = node.lv_load_area
        if lv_load_area not in self.lv_load_areas(
        ):  # and isinstance(lv_load_area, LVLoadAreaDing0):
            path_length_to_root = lv_load_area.mv_grid_district.mv_grid.graph_path_length(
                self.root_node, node)
            if ((path_length_to_root <= self.branch_length_max) and
                (lv_load_area.peak_load + self.peak_load) / cos_phi_load <=
                    self.peak_load_max):
                return True
            else:
                return False
예제 #9
0
def extend_substation(grid, critical_stations, grid_level):
    """
    Reinforce MV or LV substation by exchanging the existing trafo and
    installing a parallel one if necessary.

    First, all available transformers in a `critical_stations` are extended to
    maximum power. If this does not solve all present issues, additional
    transformers are build.

    Parameters
    ----------
    grid: GridDing0
        Ding0 grid container
    critical_stations : :any:`list`
        List of stations with overloading
    grid_level : str
        Either "LV" or "MV". Basis to select right equipment.
    
    Notes
    -----
    Curently straight forward implemented for LV stations

    Returns
    -------
    type 
        #TODO: Description of return. Change type in the previous line accordingly
    """
    load_factor_lv_trans_lc_normal = cfg_ding0.get(
        'assumptions',
        'load_factor_lv_trans_lc_normal')
    load_factor_lv_trans_fc_normal = cfg_ding0.get(
        'assumptions',
        'load_factor_lv_trans_fc_normal')

    trafo_params = grid.network._static_data['{grid_level}_trafos'.format(
        grid_level=grid_level)]
    trafo_s_max_max = max(trafo_params['S_nom'])


    for station in critical_stations:
        # determine if load or generation case and apply load factor
        if station['s_max'][0] > station['s_max'][1]:
            case = 'load'
            lf_lv_trans_normal = load_factor_lv_trans_lc_normal
        else:
            case = 'gen'
            lf_lv_trans_normal = load_factor_lv_trans_fc_normal
예제 #10
0
    def __init__(self, **kwargs):
        output_animation_file_prefix = cfg_ding0.get('output',
                                                     'animation_file_prefix')
        package_path = ding0.__path__[0]

        self.file_path = os.path.join(package_path, 'output/animation/')
        self.file_prefix = output_animation_file_prefix
        self.counter = 1
예제 #11
0
    def set_operation_voltage_level(self):
        """Set operation voltage level
    
        """

        mv_station_v_level_operation = float(cfg_ding0.get('mv_routing_tech_constraints',
                                                           'mv_station_v_level_operation'))

        self.v_level_operation = mv_station_v_level_operation * self.grid.v_level
예제 #12
0
    def __init__(self, **kwargs):
        self.id_db = kwargs.get('id_db', None)
        self.mv_grid_district = kwargs.get('mv_grid_district', None)
        self._lv_load_areas = []
        self.peak_load = 0
        self.branch_length_sum = 0
        # threshold: max. allowed peak load of satellite string
        self.peak_load_max = cfg_ding0.get(
            'mv_connect', 'load_area_sat_string_load_threshold')
        self.branch_length_max = cfg_ding0.get(
            'mv_connect', 'load_area_sat_string_length_threshold')
        self.root_node = kwargs.get(
            'root_node', None
        )  # root node (Ding0 object) = start of string on MV main route
        # TODO: Value is read from file every time a LV load_area is created -> move to associated NetworkDing0 class?

        # get id from count of load area groups in associated MV grid district
        self.id_db = self.mv_grid_district.lv_load_area_groups_count() + 1
예제 #13
0
def reinforce_lv_branches_overloading(grid, crit_branches):
    """
    Choose appropriate cable type for branches with line overloading

    Parameters
    ----------
    grid : LVGridDing0
        Ding0 LV grid object
    crit_branches : :any:`list`
        List of critical branches incl. its line loading

    Notes
    -----
    If maximum size cable is not capable to resolve issue due to line
    overloading largest available cable type is assigned to branch.

    Returns
    -------
    :any:`list`
        unsolved_branches : List of braches no suitable cable could be found
    """
    unsolved_branches = []

    cable_lf = cfg_ding0.get('assumptions',
                             'load_factor_lv_cable_lc_normal')

    cables = grid.network.static_data['LV_cables']

    # resolve overloading issues for each branch segment
    for branch in crit_branches:
        I_max_branch_load = branch['s_max'][0]
        I_max_branch_gen = branch['s_max'][1]
        I_max_branch = max([I_max_branch_load, I_max_branch_gen])

        suitable_cables = cables[(cables['I_max_th'] * cable_lf)
                          > I_max_branch]

        if not suitable_cables.empty:
            cable_type = suitable_cables.ix[suitable_cables['I_max_th'].idxmin()]
            branch['branch'].type = cable_type
            crit_branches.remove(branch)
        else:
            cable_type_max = cables.ix[cables['I_max_th'].idxmax()]
            unsolved_branches.append(branch)
            branch['branch'].type = cable_type_max
            logger.error("No suitable cable type could be found for {branch} "
                          "with I_th_max = {current}. "
                          "Cable of type {cable} is chosen during "
                          "reinforcement.".format(
                branch=branch['branch'],
                cable=cable_type_max.name,
                current=I_max_branch
            ))

    return unsolved_branches
예제 #14
0
def get_voltage_delta_branch(tree, node, r, x):
    """
    Determine voltage for a branch with impedance r + jx

    Parameters
    ----------
    tree : :networkx:`NetworkX Graph Obj< >`
        Tree of grid topology
    node : graph node
        Node to determine voltage level at
    r : float
        Resistance of preceeding branch
    x : float
        Reactance of preceeding branch

    Return
    ------
    :any:`float`
        Delta voltage for branch
    """
    cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')
    cos_phi_feedin = cfg_ding0.get('assumptions', 'cos_phi_gen')
    cos_phi_load_mode = cfg_ding0.get('assumptions', 'cos_phi_load_mode')
    cos_phi_feedin_mode = cfg_ding0.get(
        'assumptions', 'cos_phi_gen_mode'
    )  #ToDo: Check if this is true. Why would generator run in a way that aggravates voltage issues?
    v_nom = cfg_ding0.get('assumptions', 'lv_nominal_voltage')

    # get apparent power for load and generation case
    peak_load, gen_capacity = get_cumulated_conn_gen_load(tree, node)
    s_max_load = peak_load / cos_phi_load
    s_max_feedin = gen_capacity / cos_phi_feedin

    # determine voltage increase/ drop a node
    x_sign_load = q_sign(cos_phi_load_mode, 'load')
    voltage_delta_load = voltage_delta_vde(v_nom, s_max_load, r,
                                           x_sign_load * x, cos_phi_load)
    x_sign_gen = q_sign(cos_phi_feedin_mode, 'load')
    voltage_delta_gen = voltage_delta_vde(v_nom, s_max_feedin, r,
                                          x_sign_gen * x, cos_phi_feedin)

    return [voltage_delta_load, voltage_delta_gen]
예제 #15
0
def get_default_home_dir():
    """
    Return default home directory of Ding0

    Returns
    -------
    :any:`str`
        Default home directory including its path
    """
    ding0_dir = str(cfg_ding0.get('config', 'config_dir'))
    return os.path.join(os.path.expanduser('~'), ding0_dir)
예제 #16
0
파일: geo.py 프로젝트: killthekitten/ding0
def calc_geo_dist_matrix_vincenty(nodes_pos):
    """ Calculates the geodesic distance between all nodes in `nodes_pos` incorporating the detour factor in config_calc.cfg.
        
    For every two points/coord it uses geopy's vincenty function (formula devised by Thaddeus Vincenty, 
    with an accurate ellipsoidal model of the earth). As default ellipsoidal model of the earth WGS-84 is used.
    For more options see
    
    https://geopy.readthedocs.org/en/1.10.0/index.html?highlight=vincenty#geopy.distance.vincenty

    Parameters
    ----------
    nodes_pos: dict 
        dictionary of nodes with positions, with x=longitude, y=latitude, and the following format::
        
        {
            'node_1': (x_1, y_1), 
            ...,
            'node_n': (x_n, y_n)
        }
   
    Returns
    -------
    :obj:`dict`
        dictionary with distances between all nodes (in km), with the following format::
        
        {
            'node_1': {'node_1': dist_11, ..., 'node_n': dist_1n},
            ..., 
            'node_n': {'node_1': dist_n1, ..., 'node_n': dist_nn}
        }

    """

    branch_detour_factor = cfg_ding0.get('assumptions', 'branch_detour_factor')

    matrix = {}

    for i in nodes_pos:
        pos_origin = tuple(nodes_pos[i])

        matrix[i] = {}

        for j in nodes_pos:
            pos_dest = tuple(nodes_pos[j])
            # notice: vincenty takes (lat,lon), thus the (x,y)/(lon,lat) tuple is reversed
            distance = branch_detour_factor * vincenty(
                tuple(reversed(pos_origin)), tuple(reversed(pos_dest))).km
            matrix[i][j] = distance

    return matrix
예제 #17
0
    def __init__(self, **kwargs):
        # inherit branch parameters from Region
        super().__init__(**kwargs)

        # more params
        self._lv_grid_districts = []
        self.ring = kwargs.get('ring', None)
        self.mv_grid_district = kwargs.get('mv_grid_district', None)
        self.lv_load_area_centre = kwargs.get('lv_load_area_centre', None)
        self.lv_load_area_group = kwargs.get('lv_load_area_group', None)
        self.is_satellite = kwargs.get('is_satellite', False)
        self.is_aggregated = kwargs.get('is_aggregated', False)

        # threshold: load area peak load, if peak load < threshold => treat load area as satellite
        load_area_sat_load_threshold = cfg_ding0.get(
            'mv_connect', 'load_area_sat_load_threshold')
        # TODO: Value is read from file every time a LV load_area is created -> move to associated NetworkDing0 class?

        db_data = kwargs.get('db_data', None)

        # dangerous: attributes are created for any passed argument in `db_data`
        # load values into attributes
        if db_data is not None:
            for attribute in list(db_data.keys()):
                setattr(self, attribute, db_data[attribute])

        # convert geo attributes to to shapely objects
        if hasattr(self, 'geo_area'):
            self.geo_area = wkt_loads(self.geo_area)
        if hasattr(self, 'geo_centre'):
            self.geo_centre = wkt_loads(self.geo_centre)

        # convert load values (rounded floats) to int
        if hasattr(self, 'peak_load_residential'):
            self.peak_load_residential = self.peak_load_residential
        if hasattr(self, 'peak_load_retail'):
            self.peak_load_retail = self.peak_load_retail
        if hasattr(self, 'peak_load_industrial'):
            self.peak_load_industrial = self.peak_load_industrial
        if hasattr(self, 'peak_load_agricultural'):
            self.peak_load_agricultural = self.peak_load_agricultural
        if hasattr(self, 'peak_load'):
            self.peak_load = self.peak_load

            # if load area has got a peak load less than load_area_sat_threshold, it's a satellite
            if self.peak_load < load_area_sat_load_threshold:
                self.is_satellite = True
예제 #18
0
def get_delta_voltage_preceding_line(grid, tree, node):
    """
    Parameters
    ----------
    grid : :class:`~.ding0.core.network.grids.LVGridDing0`
        Ding0 grid object
    tree: :networkx:`NetworkX Graph Obj< >`
        Tree of grid topology
    node: graph node
        Node at end of line
    Return
    ------
    :any:`float`
        Voltage drop over preceding line of node
    """

    # get impedance of preceding line
    freq = cfg_ding0.get('assumptions', 'frequency')
    omega = 2 * math.pi * freq

    # choose preceding branch
    branch = [
        _ for _ in grid.graph_branches_from_node(node)
        if _[0] in list(tree.predecessors(node))
    ][0][1]

    # calculate impedance of preceding branch
    r_line = (branch['branch'].type['R_per_km'] * branch['branch'].length /
              1e3)
    x_line = (branch['branch'].type['L_per_km'] / 1e3 * omega *
              branch['branch'].length / 1e3)

    # get voltage drop over preceeding line
    voltage_delta_load, voltage_delta_gen = \
        get_voltage_delta_branch(tree, node, r_line, x_line)

    return voltage_delta_load, voltage_delta_gen
예제 #19
0
def transformer(grid):
    """ Choose transformer and add to grid's station

    Parameters
    ----------
    grid: LVGridDing0
        LV grid data
    """
    v_nom = cfg_ding0.get('assumptions',
                          'lv_nominal_voltage') / 1e3  # v_nom in kV
    # choose size and amount of transformers
    transformer, transformer_cnt = select_transformers(grid)

    # create transformers and add them to station of LVGD
    for t in range(0, transformer_cnt):
        lv_transformer = TransformerDing0(grid=grid,
                                          id_db=id,
                                          v_level=v_nom,
                                          s_max_longterm=transformer['S_nom'],
                                          r_pu=transformer['r_pu'],
                                          x_pu=transformer['x_pu'])

        # add each transformer to its station
        grid._station.add_transformer(lv_transformer)
예제 #20
0
def select_grid_model_ria(lvgd, sector):
    """Select a typified grid for retail/industrial and agricultural

    Parameters
    ----------
    lvgd : ding0.core.structure.regions.LVGridDistrictDing0
        Low-voltage grid district object
    sector : :obj:`str`
        Either 'retail/industrial' or 'agricultural'. Depending on choice
        different parameters to grid topology apply

    Returns
    -------
    :obj:`dict`
        Parameters that describe branch lines of a sector
    """

    cable_lf = cfg_ding0.get('assumptions', 'load_factor_lv_cable_lc_normal')

    cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')

    max_lv_branch_line_load = cfg_ding0.get('assumptions',
                                            'max_lv_branch_line')

    # make a distinction between sectors
    if sector == 'retail/industrial':
        max_branch_length = cfg_ding0.get(
            "assumptions", "branch_line_length_retail_industrial")
        peak_load = lvgd.peak_load_retail + \
                    lvgd.peak_load_industrial
        count_sector_areas = lvgd.sector_count_retail + \
                             lvgd.sector_count_industrial
    elif sector == 'agricultural':
        max_branch_length = cfg_ding0.get("assumptions",
                                          "branch_line_length_agricultural")
        peak_load = lvgd.peak_load_agricultural
        count_sector_areas = lvgd.sector_count_agricultural
    else:
        raise ValueError('Sector {} does not exist!'.format(sector))

    # determine size of a single load
    single_peak_load = peak_load / count_sector_areas

    # if this single load exceeds threshold of 300 kVA it is splitted
    while single_peak_load > (max_lv_branch_line_load *
                              (cable_lf * cos_phi_load)):
        single_peak_load = single_peak_load / 2
        count_sector_areas = count_sector_areas * 2

    grid_model = {}

    # determine parameters of branches and loads connected to the branch
    # line
    if 0 < single_peak_load:
        grid_model['max_loads_per_branch'] = math.floor(
            (max_lv_branch_line_load *
             (cable_lf * cos_phi_load)) / single_peak_load)
        grid_model['single_peak_load'] = single_peak_load
        grid_model['full_branches'] = math.floor(
            count_sector_areas / grid_model['max_loads_per_branch'])
        grid_model['remaining_loads'] = count_sector_areas - (
            grid_model['full_branches'] * grid_model['max_loads_per_branch'])
        grid_model['load_distance'] = max_branch_length / (
            grid_model['max_loads_per_branch'] + 1)
        grid_model['load_distance_remaining'] = max_branch_length / (
            grid_model['remaining_loads'] + 1)
    else:
        if count_sector_areas > 0:
            logger.warning(
                'LVGD {lvgd} has in sector {sector} no load but area count'
                'is {count}. This is maybe related to #153'.format(
                    lvgd=lvgd, sector=sector, count=count_sector_areas))
            grid_model = None

    # add consumption to grid_model for assigning it to the load object
    # consumption is given per sector and per individual load
    if sector == 'retail/industrial':
        grid_model['consumption'] = {
            'retail':
            lvgd.sector_consumption_retail /
            (grid_model['full_branches'] * grid_model['max_loads_per_branch'] +
             grid_model['remaining_loads']),
            'industrial':
            lvgd.sector_consumption_industrial /
            (grid_model['full_branches'] * grid_model['max_loads_per_branch'] +
             grid_model['remaining_loads'])
        }
    elif sector == 'agricultural':
        grid_model['consumption'] = {
            'agricultural':
            lvgd.sector_consumption_agricultural /
            (grid_model['full_branches'] * grid_model['max_loads_per_branch'] +
             grid_model['remaining_loads'])
        }

    return grid_model
예제 #21
0
파일: grids.py 프로젝트: mhdella/ding0
    def set_default_branch_type(self, debug=False):
        """ Determines default branch type according to grid district's peak load and standard equipment.

        Args
        ----
        debug: bool, defaults to False
            If True, information is printed during process

        Returns
        -------
        :pandas:`pandas.Series<series>`   
            default branch type: pandas Series object. If no appropriate type is found, return largest possible one.
        :pandas:`pandas.Series<series>`    
            default branch type max: pandas Series object. Largest available line/cable type

        Note
        -----
        Parameter values for cables and lines are taken from [#]_, [#]_ and [#]_.

        Lines are chosen to have 60 % load relative to their nominal capacity according to [#]_.

        Decision on usage of overhead lines vs. cables is determined by load density of the considered region. Urban
        areas usually are equipped with underground cables whereas rural areas often have overhead lines as MV
        distribution system [#]_.

        References
        ----------
        .. [#] Klaus Heuck et al., "Elektrische Energieversorgung", Vieweg+Teubner, Wiesbaden, 2007
        .. [#] René Flosdorff et al., "Elektrische Energieverteilung", Vieweg+Teubner, 2005
        .. [#] Südkabel GmbH, "Einadrige VPE-isolierte Mittelspannungskabel",
            http://www.suedkabel.de/cms/upload/pdf/Garnituren/Einadrige_VPE-isolierte_Mittelspannungskabel.pdf, 2017
        .. [#] Deutsche Energie-Agentur GmbH (dena), "dena-Verteilnetzstudie. Ausbau- und Innovationsbedarf der
            Stromverteilnetze in Deutschland bis 2030.", 2012
        .. [#] Tao, X., "Automatisierte Grundsatzplanung von
            Mittelspannungsnetzen", Dissertation, RWTH Aachen, 2007
        """

        # decide whether cable or line is used (initially for entire grid) and set grid's attribute
        if self.v_level == 20:
            self.default_branch_kind = 'line'
        elif self.v_level == 10:
            self.default_branch_kind = 'cable'

        # get power factor for loads
        cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')

        # get max. count of half rings per MV grid district
        mv_half_ring_count_max = int(
            cfg_ding0.get('mv_routing_tech_constraints',
                          'mv_half_ring_count_max'))
        #mv_half_ring_count_max=20

        # load cable/line assumptions, file_names and parameter
        if self.default_branch_kind == 'line':
            load_factor_normal = float(
                cfg_ding0.get('assumptions', 'load_factor_mv_line_lc_normal'))
            branch_parameters = self.network.static_data['MV_overhead_lines']

            # load cables as well to use it within settlements
            branch_parameters_settle = self.network.static_data['MV_cables']
            # select types with appropriate voltage level
            branch_parameters_settle = branch_parameters_settle[
                branch_parameters_settle['U_n'] == self.v_level]

        elif self.default_branch_kind == 'cable':
            load_factor_normal = float(
                cfg_ding0.get('assumptions', 'load_factor_mv_cable_lc_normal'))
            branch_parameters = self.network.static_data['MV_cables']
        else:
            raise ValueError(
                'Grid\'s default_branch_kind is invalid, could not set branch parameters.'
            )

        # select appropriate branch params according to voltage level, sorted ascending by max. current
        # use <240mm2 only (ca. 420A) for initial rings and for disambiguation of agg. LA
        branch_parameters = branch_parameters[branch_parameters['U_n'] ==
                                              self.v_level]
        branch_parameters = branch_parameters[
            branch_parameters['reinforce_only'] == 0].sort_values('I_max_th')

        # get largest line/cable type
        branch_type_max = branch_parameters.loc[
            branch_parameters['I_max_th'].idxmax()]

        # set aggregation flag using largest available line/cable
        self.set_nodes_aggregation_flag(branch_type_max['I_max_th'] *
                                        load_factor_normal)

        # calc peak current sum (= "virtual" current) of whole grid (I = S / sqrt(3) / U) excluding load areas of type
        # satellite and aggregated
        peak_current_sum = (
            (self.grid_district.peak_load -
             self.grid_district.peak_load_satellites -
             self.grid_district.peak_load_aggregated) / cos_phi_load /
            (3**0.5) / self.v_level)  # units: kVA / kV = A

        branch_type_settle = branch_type_settle_max = None

        # search the smallest possible line/cable for MV grid district in equipment datasets for all load areas
        # excluding those of type satellite and aggregated
        for idx, row in branch_parameters.iterrows():
            # calc number of required rings using peak current sum of grid district,
            # load factor and max. current of line/cable
            half_ring_count = round(peak_current_sum /
                                    (row['I_max_th'] * load_factor_normal))

            if debug:
                logger.debug(
                    '=== Selection of default branch type in {} ==='.format(
                        self))
                logger.debug('Peak load= {} kVA'.format(
                    self.grid_district.peak_load))
                logger.debug('Peak current={}'.format(peak_current_sum))
                logger.debug('I_max_th={}'.format(row['I_max_th']))
                logger.debug('Half ring count={}'.format(half_ring_count))

            # if count of half rings is below or equal max. allowed count, use current branch type as default
            if half_ring_count <= mv_half_ring_count_max:
                if self.default_branch_kind == 'line':

                    # take only cables that can handle at least the current of the line
                    branch_parameters_settle_filter = branch_parameters_settle[\
                                                      branch_parameters_settle['I_max_th'] - row['I_max_th'] > 0]

                    # get cable type with similar (but greater) I_max_th
                    # note: only grids with lines as default branch kind get cables in settlements
                    # (not required in grids with cables as default branch kind)
                    branch_type_settle = branch_parameters_settle_filter.loc[\
                                         branch_parameters_settle_filter['I_max_th'].idxmin()]

                return row, branch_type_max, branch_type_settle

        # no equipment was found, return largest available line/cable

        if debug:
            logger.debug(
                'No appropriate line/cable type could be found for '
                '{}, declare some load areas as aggregated.'.format(self))

        if self.default_branch_kind == 'line':
            branch_type_settle_max = branch_parameters_settle.loc[
                branch_parameters_settle['I_max_th'].idxmax()]

        return branch_type_max, branch_type_max, branch_type_settle_max
예제 #22
0
파일: grids.py 프로젝트: mhdella/ding0
    def set_voltage_level(self, mode='distance'):
        """ Sets voltage level of MV grid according to load density of MV Grid District or max.
        distance between station and Load Area.

        Parameters
        ----------
        mode: :obj:`str`
            method to determine voltage level
            
            * 'load_density': Decision on voltage level is determined by load density
              of the considered region. Urban areas (load density of
              >= 1 MW/km2 according to [#]_) usually got a voltage of
              10 kV whereas rural areas mostly use 20 kV.

            * 'distance' (default): Decision on voltage level is determined by the max.
              distance between Grid District's HV-MV station and Load
              Areas (LA's centre is used). According to [#]_ a value of
              1kV/kV can be assumed. The `voltage_per_km_threshold`
              defines the distance threshold for distinction.
              (default in config = (20km+10km)/2 = 15km)

        References
        ----------
        .. [#] Falk Schaller et al., "Modellierung realitätsnaher zukünftiger Referenznetze im Verteilnetzsektor zur
            Überprüfung der Elektroenergiequalität", Internationaler ETG-Kongress Würzburg, 2011
        .. [#] Klaus Heuck et al., "Elektrische Energieversorgung", Vieweg+Teubner, Wiesbaden, 2007

        """

        if mode == 'load_density':

            # get power factor for loads
            cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')

            # get load density
            load_density_threshold = float(
                cfg_ding0.get('assumptions', 'load_density_threshold'))

            # transform MVGD's area to epsg 3035
            # to achieve correct area calculation
            projection = partial(
                pyproj.transform,
                pyproj.Proj(init='epsg:4326'),  # source coordinate system
                pyproj.Proj(init='epsg:3035'))  # destination coordinate system

            # calculate load density
            kw2mw = 1e-3
            sqm2sqkm = 1e6
            load_density = (
                (self.grid_district.peak_load * kw2mw / cos_phi_load) /
                (transform(projection, self.grid_district.geo_data).area /
                 sqm2sqkm))  # unit MVA/km^2

            # identify voltage level
            if load_density < load_density_threshold:
                self.v_level = 20
            elif load_density >= load_density_threshold:
                self.v_level = 10
            else:
                raise ValueError('load_density is invalid!')

        elif mode == 'distance':

            # get threshold for 20/10kV disambiguation
            voltage_per_km_threshold = float(
                cfg_ding0.get('assumptions', 'voltage_per_km_threshold'))

            # initial distance
            dist_max = 0
            import time
            start = time.time()
            for node in self.graph_nodes_sorted():
                if isinstance(node, LVLoadAreaCentreDing0):
                    # calc distance from MV-LV station to LA centre
                    dist_node = calc_geo_dist_vincenty(self.station(),
                                                       node) / 1e3
                    if dist_node > dist_max:
                        dist_max = dist_node

            # max. occurring distance to a Load Area exceeds threshold => grid operates at 20kV
            if dist_max >= voltage_per_km_threshold:
                self.v_level = 20
            # not: grid operates at 10kV
            else:
                self.v_level = 10

        else:
            raise ValueError('parameter \'mode\' is invalid!')
예제 #23
0
    def tech_constraints_satisfied(self):
        """ Check route validity according to technical constraints (voltage and current rating)
        
        It considers constraints as
        
        * current rating of cable/line
        * voltage stability at all nodes

        Notes
        -----
            The validation is done for every tested MV grid configuration during CVRP algorithm. The current rating is
            checked using load factors from [#]_. Due to the high amount of steps the voltage rating cannot be checked
            using load flow calculation. Therefore we use a simple method which determines the voltage change between
            two consecutive nodes according to [#]_.
            Furthermore it is checked if new route has got more nodes than allowed (typ. 2*10 according to [#]_).

        References
        ----------
            
        .. [#] Deutsche Energie-Agentur GmbH (dena), "dena-Verteilnetzstudie. Ausbau- und Innovationsbedarf der
            Stromverteilnetze in Deutschland bis 2030.", 2012
        .. [#] M. Sakulin, W. Hipp, "Netzaspekte von dezentralen Erzeugungseinheiten,
            Studie im Auftrag der E-Control GmbH", TU Graz, 2004
        .. [#] Klaus Heuck et al., "Elektrische Energieversorgung", Vieweg+Teubner, Wiesbaden, 2007
        .. [#] FGH e.V.: "Technischer Bericht 302: Ein Werkzeug zur Optimierung der Störungsbeseitigung
            für Planung und Betrieb von Mittelspannungsnetzen", Tech. rep., 2008
        """

        # load parameters
        load_area_count_per_ring = float(
            cfg_ding0.get('mv_routing', 'load_area_count_per_ring'))

        max_half_ring_length = float(
            cfg_ding0.get('mv_routing', 'max_half_ring_length'))

        if self._problem._branch_kind == 'line':
            load_factor_normal = float(
                cfg_ding0.get('assumptions', 'load_factor_mv_line_lc_normal'))
            load_factor_malfunc = float(
                cfg_ding0.get('assumptions', 'load_factor_mv_line_lc_malfunc'))
        elif self._problem._branch_kind == 'cable':
            load_factor_normal = float(
                cfg_ding0.get('assumptions', 'load_factor_mv_cable_lc_normal'))
            load_factor_malfunc = float(
                cfg_ding0.get('assumptions',
                              'load_factor_mv_cable_lc_malfunc'))
        else:
            raise ValueError(
                'Grid\'s _branch_kind is invalid, could not use branch parameters.'
            )

        mv_max_v_level_lc_diff_normal = float(
            cfg_ding0.get('mv_routing_tech_constraints',
                          'mv_max_v_level_lc_diff_normal'))
        mv_max_v_level_lc_diff_malfunc = float(
            cfg_ding0.get('mv_routing_tech_constraints',
                          'mv_max_v_level_lc_diff_malfunc'))
        cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')

        # step 0: check if route has got more nodes than allowed
        if len(self._nodes) > load_area_count_per_ring:
            return False

        # step 1: calc circuit breaker position
        position = self.calc_circuit_breaker_position()

        # step 2: calc required values for checking current & voltage
        # get nodes of half-rings
        nodes_hring1 = [self._problem._depot] + self._nodes[0:position]
        nodes_hring2 = list(
            reversed(self._nodes[position:len(self._nodes)] +
                     [self._problem._depot]))
        # get all nodes of full ring for both directions
        nodes_ring1 = [self._problem._depot] + self._nodes
        nodes_ring2 = list(reversed(self._nodes + [self._problem._depot]))
        # factor to calc reactive from active power
        Q_factor = tan(acos(cos_phi_load))
        # line/cable params per km
        r = self._problem._branch_type['R']  # unit for r: ohm/km
        x = self._problem._branch_type[
            'L'] * 2 * pi * 50 / 1e3  # unit for x: ohm/km

        # step 3: check if total lengths of half-rings exceed max. allowed distance
        if (self.length_from_nodelist(nodes_hring1) > max_half_ring_length
                or self.length_from_nodelist(nodes_hring2) >
                max_half_ring_length):
            return False

        # step 4a: check if current rating of default cable/line is violated
        # (for every of the 2 half-rings using load factor for normal operation)
        demand_hring_1 = sum(
            [node.demand() for node in self._nodes[0:position]])
        demand_hring_2 = sum(
            [node.demand() for node in self._nodes[position:len(self._nodes)]])
        peak_current_sum_hring1 = demand_hring_1 / (
            3**0.5) / self._problem._v_level  # units: kVA / kV = A
        peak_current_sum_hring2 = demand_hring_2 / (
            3**0.5) / self._problem._v_level  # units: kVA / kV = A

        if (peak_current_sum_hring1 >
            (self._problem._branch_type['I_max_th'] * load_factor_normal)
                or peak_current_sum_hring2 >
            (self._problem._branch_type['I_max_th'] * load_factor_normal)):
            return False

        # step 4b: check if current rating of default cable/line is violated
        # (for full ring using load factor for malfunction operation)
        peak_current_sum_ring = self._demand / (
            3**0.5) / self._problem._v_level  # units: kVA / kV = A
        if peak_current_sum_ring > (self._problem._branch_type['I_max_th'] *
                                    load_factor_malfunc):
            return False

        # step 5a: check voltage stability at all nodes
        # (for every of the 2 half-rings using max. voltage difference for normal operation)

        # get operation voltage level from station
        v_level_hring1 =\
            v_level_hring2 =\
            v_level_ring_dir1 =\
            v_level_ring_dir2 =\
            v_level_op =\
            self._problem._v_level * 1e3

        # set initial r and x
        r_hring1 =\
            r_hring2 =\
            x_hring1 =\
            x_hring2 =\
            r_ring_dir1 =\
            r_ring_dir2 =\
            x_ring_dir1 =\
            x_ring_dir2 = 0

        for n1, n2 in zip(nodes_hring1[0:len(nodes_hring1) - 1],
                          nodes_hring1[1:len(nodes_hring1)]):
            r_hring1 += self._problem.distance(n1, n2) * r
            x_hring1 += self._problem.distance(n1, n2) * x
            v_level_hring1 -= n2.demand() * 1e3 * (
                r_hring1 + x_hring1 * Q_factor) / v_level_op
            if (v_level_op - v_level_hring1) > (v_level_op *
                                                mv_max_v_level_lc_diff_normal):
                return False

        for n1, n2 in zip(nodes_hring2[0:len(nodes_hring2) - 1],
                          nodes_hring2[1:len(nodes_hring2)]):
            r_hring2 += self._problem.distance(n1, n2) * r
            x_hring2 += self._problem.distance(n1, n2) * x
            v_level_hring2 -= n2.demand() * 1e3 * (
                r_hring2 + x_hring2 * Q_factor) / v_level_op
            if (v_level_op - v_level_hring2) > (v_level_op *
                                                mv_max_v_level_lc_diff_normal):
                return False

        # step 5b: check voltage stability at all nodes
        # (for full ring calculating both directions simultaneously using max. voltage diff. for malfunction operation)
        for (n1, n2), (n3, n4) in zip(
                zip(nodes_ring1[0:len(nodes_ring1) - 1],
                    nodes_ring1[1:len(nodes_ring1)]),
                zip(nodes_ring2[0:len(nodes_ring2) - 1],
                    nodes_ring2[1:len(nodes_ring2)])):
            r_ring_dir1 += self._problem.distance(n1, n2) * r
            r_ring_dir2 += self._problem.distance(n3, n4) * r
            x_ring_dir1 += self._problem.distance(n1, n2) * x
            x_ring_dir2 += self._problem.distance(n3, n4) * x
            v_level_ring_dir1 -= (n2.demand() * 1e3 *
                                  (r_ring_dir1 + x_ring_dir1 * Q_factor) /
                                  v_level_op)
            v_level_ring_dir2 -= (n4.demand() * 1e3 *
                                  (r_ring_dir2 + x_ring_dir2 * Q_factor) /
                                  v_level_op)
            if ((v_level_op - v_level_ring_dir1) >
                (v_level_op * mv_max_v_level_lc_diff_malfunc)
                    or (v_level_op - v_level_ring_dir2) >
                (v_level_op * mv_max_v_level_lc_diff_malfunc)):
                return False

        return True
예제 #24
0
def nodes_to_dict_of_dataframes(grid, nodes, lv_transformer=True):
    """
    Creates dictionary of dataframes containing grid

    Parameters
    ----------
    grid: ding0.Network
    nodes: list of ding0 grid components objects
        Nodes of the grid graph
    lv_transformer: bool, True
        Toggle transformer representation in power flow analysis

    Returns:
    components: dict of pandas.DataFrame
        DataFrames contain components attributes. Dict is keyed by components
        type
    components_data: dict of pandas.DataFrame
        DataFrame containing components time-varying data
    """

    generator_instances = [MVStationDing0, GeneratorDing0]
    # TODO: MVStationDing0 has a slack generator

    cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')
    cos_phi_feedin = cfg_ding0.get('assumptions', 'cos_phi_gen')
    srid = int(cfg_ding0.get('geo', 'srid'))

    load_in_generation_case = cfg_ding0.get('assumptions',
                                            'load_in_generation_case')
    generation_in_load_case = cfg_ding0.get('assumptions',
                                            'generation_in_load_case')

    Q_factor_load = tan(acos(cos_phi_load))
    Q_factor_generation = tan(acos(cos_phi_feedin))

    voltage_set_slack = cfg_ding0.get("mv_routing_tech_constraints",
                                      "mv_station_v_level_operation")

    kw2mw = 1e-3

    # define dictionaries
    buses = {'bus_id': [], 'v_nom': [], 'geom': [], 'grid_id': []}
    bus_v_mag_set = {
        'bus_id': [],
        'temp_id': [],
        'v_mag_pu_set': [],
        'grid_id': []
    }
    generator = {
        'generator_id': [],
        'bus': [],
        'control': [],
        'grid_id': [],
        'p_nom': []
    }
    generator_pq_set = {
        'generator_id': [],
        'temp_id': [],
        'p_set': [],
        'grid_id': [],
        'q_set': []
    }
    load = {'load_id': [], 'bus': [], 'grid_id': []}
    load_pq_set = {
        'load_id': [],
        'temp_id': [],
        'p_set': [],
        'grid_id': [],
        'q_set': []
    }

    # # TODO: consider other implications of `lv_transformer is True`
    # if lv_transformer is True:
    #     bus_instances.append(Transformer)

    # # TODO: only for debugging, remove afterwards
    # import csv
    # nodeslist = sorted([node.__repr__() for node in nodes
    #                     if node not in grid.graph_isolated_nodes()])
    # with open('/home/guido/ding0_debug/nodes_via_dataframe.csv', 'w', newline='') as csvfile:
    #     writer = csv.writer(csvfile, delimiter='\n')
    #     writer.writerow(nodeslist)

    for node in nodes:
        if node not in grid.graph_isolated_nodes():
            # buses only
            if isinstance(node, MVCableDistributorDing0):
                buses['bus_id'].append(node.pypsa_id)
                buses['v_nom'].append(grid.v_level)
                buses['geom'].append(from_shape(node.geo_data, srid=srid))
                buses['grid_id'].append(grid.id_db)

                bus_v_mag_set['bus_id'].append(node.pypsa_id)
                bus_v_mag_set['temp_id'].append(1)
                bus_v_mag_set['v_mag_pu_set'].append([1, 1])
                bus_v_mag_set['grid_id'].append(grid.id_db)

            # bus + generator
            elif isinstance(node, tuple(generator_instances)):
                # slack generator
                if isinstance(node, MVStationDing0):
                    logger.info('Only MV side bus of MVStation will be added.')
                    generator['generator_id'].append('_'.join(
                        ['MV', str(grid.id_db), 'slack']))
                    generator['control'].append('Slack')
                    generator['p_nom'].append(0)
                    bus_v_mag_set['v_mag_pu_set'].append(
                        [voltage_set_slack, voltage_set_slack])

                # other generators
                if isinstance(node, GeneratorDing0):
                    generator['generator_id'].append('_'.join(
                        ['MV', str(grid.id_db), 'gen',
                         str(node.id_db)]))
                    generator['control'].append('PQ')
                    generator['p_nom'].append(node.capacity *
                                              node.capacity_factor)

                    generator_pq_set['generator_id'].append('_'.join(
                        ['MV', str(grid.id_db), 'gen',
                         str(node.id_db)]))
                    generator_pq_set['temp_id'].append(1)
                    generator_pq_set['p_set'].append([
                        node.capacity * node.capacity_factor * kw2mw *
                        generation_in_load_case,
                        node.capacity * node.capacity_factor * kw2mw
                    ])
                    generator_pq_set['q_set'].append([
                        node.capacity * node.capacity_factor * kw2mw *
                        Q_factor_generation * generation_in_load_case,
                        node.capacity * node.capacity_factor * kw2mw *
                        Q_factor_generation
                    ])
                    generator_pq_set['grid_id'].append(grid.id_db)
                    bus_v_mag_set['v_mag_pu_set'].append([1, 1])

                buses['bus_id'].append(node.pypsa_id)
                buses['v_nom'].append(grid.v_level)
                buses['geom'].append(from_shape(node.geo_data, srid=srid))
                buses['grid_id'].append(grid.id_db)

                bus_v_mag_set['bus_id'].append(node.pypsa_id)
                bus_v_mag_set['temp_id'].append(1)
                bus_v_mag_set['grid_id'].append(grid.id_db)

                generator['grid_id'].append(grid.id_db)
                generator['bus'].append(node.pypsa_id)

            # aggregated load at hv/mv substation
            elif isinstance(node, LVLoadAreaCentreDing0):
                load['load_id'].append(node.pypsa_id)
                load['bus'].append('_'.join(['HV', str(grid.id_db), 'trd']))
                load['grid_id'].append(grid.id_db)

                load_pq_set['load_id'].append(node.pypsa_id)
                load_pq_set['temp_id'].append(1)
                load_pq_set['p_set'].append([
                    node.lv_load_area.peak_load * kw2mw,
                    node.lv_load_area.peak_load * kw2mw *
                    load_in_generation_case
                ])
                load_pq_set['q_set'].append([
                    node.lv_load_area.peak_load * kw2mw * Q_factor_load,
                    node.lv_load_area.peak_load * kw2mw * Q_factor_load *
                    load_in_generation_case
                ])
                load_pq_set['grid_id'].append(grid.id_db)

                # generator representing generation capacity of aggregate LA
                # analogously to load, generation is connected directly to
                # HV-MV substation
                generator['generator_id'].append('_'.join(
                    ['MV', str(grid.id_db), 'lcg',
                     str(node.id_db)]))
                generator['control'].append('PQ')
                generator['p_nom'].append(node.lv_load_area.peak_generation)
                generator['grid_id'].append(grid.id_db)
                generator['bus'].append('_'.join(
                    ['HV', str(grid.id_db), 'trd']))

                generator_pq_set['generator_id'].append('_'.join(
                    ['MV', str(grid.id_db), 'lcg',
                     str(node.id_db)]))
                generator_pq_set['temp_id'].append(1)
                generator_pq_set['p_set'].append([
                    node.lv_load_area.peak_generation * kw2mw *
                    generation_in_load_case,
                    node.lv_load_area.peak_generation * kw2mw
                ])
                generator_pq_set['q_set'].append([
                    node.lv_load_area.peak_generation * kw2mw *
                    Q_factor_generation * generation_in_load_case,
                    node.lv_load_area.peak_generation * kw2mw *
                    Q_factor_generation
                ])
                generator_pq_set['grid_id'].append(grid.id_db)

            # bus + aggregate load of lv grids (at mv/ls substation)
            elif isinstance(node, LVStationDing0):
                # Aggregated load representing load in LV grid
                load['load_id'].append('_'.join(
                    ['MV', str(grid.id_db), 'loa',
                     str(node.id_db)]))
                load['bus'].append(node.pypsa_id)
                load['grid_id'].append(grid.id_db)

                load_pq_set['load_id'].append('_'.join(
                    ['MV', str(grid.id_db), 'loa',
                     str(node.id_db)]))
                load_pq_set['temp_id'].append(1)
                load_pq_set['p_set'].append([
                    node.peak_load * kw2mw,
                    node.peak_load * kw2mw * load_in_generation_case
                ])
                load_pq_set['q_set'].append([
                    node.peak_load * kw2mw * Q_factor_load, node.peak_load *
                    kw2mw * Q_factor_load * load_in_generation_case
                ])
                load_pq_set['grid_id'].append(grid.id_db)

                # bus at primary MV-LV transformer side
                buses['bus_id'].append(node.pypsa_id)
                buses['v_nom'].append(grid.v_level)
                buses['geom'].append(from_shape(node.geo_data, srid=srid))
                buses['grid_id'].append(grid.id_db)

                bus_v_mag_set['bus_id'].append(node.pypsa_id)
                bus_v_mag_set['temp_id'].append(1)
                bus_v_mag_set['v_mag_pu_set'].append([1, 1])
                bus_v_mag_set['grid_id'].append(grid.id_db)

                # generator representing generation capacity in LV grid
                generator['generator_id'].append('_'.join(
                    ['MV', str(grid.id_db), 'gen',
                     str(node.id_db)]))
                generator['control'].append('PQ')
                generator['p_nom'].append(node.peak_generation)
                generator['grid_id'].append(grid.id_db)
                generator['bus'].append(node.pypsa_id)

                generator_pq_set['generator_id'].append('_'.join(
                    ['MV', str(grid.id_db), 'gen',
                     str(node.id_db)]))
                generator_pq_set['temp_id'].append(1)
                generator_pq_set['p_set'].append([
                    node.peak_generation * kw2mw * generation_in_load_case,
                    node.peak_generation * kw2mw
                ])
                generator_pq_set['q_set'].append([
                    node.peak_generation * kw2mw * Q_factor_generation *
                    generation_in_load_case,
                    node.peak_generation * kw2mw * Q_factor_generation
                ])
                generator_pq_set['grid_id'].append(grid.id_db)

            elif isinstance(node, CircuitBreakerDing0):
                # TODO: remove this elif-case if CircuitBreaker are removed from graph
                continue
            else:
                raise TypeError("Node of type", node, "cannot be handled here")
        else:
            if not isinstance(node, CircuitBreakerDing0):
                add_info = "LA is aggr. {0}".format(
                    node.lv_load_area.is_aggregated)
            else:
                add_info = ""
            logger.warning("Node {0} is not connected to the graph and will " \
                  "be omitted in power flow analysis. {1}".format(
                node, add_info))

    components = {
        'Bus': DataFrame(buses).set_index('bus_id'),
        'Generator': DataFrame(generator).set_index('generator_id'),
        'Load': DataFrame(load).set_index('load_id')
    }

    components_data = {
        'Bus': DataFrame(bus_v_mag_set).set_index('bus_id'),
        'Generator': DataFrame(generator_pq_set).set_index('generator_id'),
        'Load': DataFrame(load_pq_set).set_index('load_id')
    }

    # with open('/home/guido/ding0_debug/number_of_nodes_buses.csv', 'a') as csvfile:
    #     csvfile.write(','.join(['\n', str(len(nodes)), str(len(grid.graph_isolated_nodes())), str(len(components['Bus']))]))

    return components, components_data
예제 #25
0
    def lv_graph_attach_branch():
        """Attach a single branch including its equipment (cable dist, loads
        and line segments) to graph of `lv_grid`
        """

        # determine maximum current occuring due to peak load
        # of this load load_no
        I_max_load = val['single_peak_load'] / (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]
        cable_type_stub = suitable_cables_stub.loc[
            suitable_cables_stub['I_max_th'].idxmin(), :]

        # cable distributor to divert from main branch
        lv_cable_dist = LVCableDistributorDing0(grid=lvgd.lv_grid,
                                                branch_no=branch_no,
                                                load_no=load_no)
        # add lv_cable_dist to graph
        lvgd.lv_grid.add_cable_dist(lv_cable_dist)

        # cable distributor within building (to connect load+geno)
        lv_cable_dist_building = LVCableDistributorDing0(grid=lvgd.lv_grid,
                                                         branch_no=branch_no,
                                                         load_no=load_no,
                                                         in_building=True)
        # add lv_cable_dist_building to graph
        lvgd.lv_grid.add_cable_dist(lv_cable_dist_building)

        # create an instance of Ding0 LV load
        lv_load = LVLoadDing0(grid=lvgd.lv_grid,
                              branch_no=branch_no,
                              load_no=load_no,
                              peak_load=val['single_peak_load'],
                              consumption=val['consumption'])

        # add lv_load to graph
        lvgd.lv_grid.add_load(lv_load)

        # create branch line segment between either (a) station
        # and cable distributor or (b) between neighboring cable
        # distributors
        if load_no == 1:
            # case a: cable dist <-> station
            lvgd.lv_grid._graph.add_edge(
                lvgd.lv_grid.station(),
                lv_cable_dist,
                branch=BranchDing0(
                    length=val['load_distance'],
                    kind='cable',
                    type=cable_type,
                    id_db='branch_{sector}{branch}_{load}'.format(
                        branch=branch_no, load=load_no, sector=sector_short)))
        else:
            # case b: cable dist <-> cable dist
            lvgd.lv_grid._graph.add_edge(
                lvgd.lv_grid._cable_distributors[-4],
                lv_cable_dist,
                branch=BranchDing0(
                    length=val['load_distance'],
                    kind='cable',
                    type=cable_type,
                    id_db='branch_{sector}{branch}_{load}'.format(
                        branch=branch_no, load=load_no, sector=sector_short)))

        # create branch stub that connects the load to the
        # lv_cable_dist located in the branch line
        lvgd.lv_grid._graph.add_edge(
            lv_cable_dist,
            lv_cable_dist_building,
            branch=BranchDing0(length=cfg_ding0.get(
                'assumptions', 'lv_ria_branch_connection_distance'),
                               kind='cable',
                               type=cable_type_stub,
                               id_db='stub_{sector}{branch}_{load}'.format(
                                   branch=branch_no,
                                   load=load_no,
                                   sector=sector_short)))

        lvgd.lv_grid._graph.add_edge(
            lv_cable_dist_building,
            lv_load,
            branch=BranchDing0(length=1,
                               kind='cable',
                               type=cable_type_stub,
                               id_db='stub_{sector}{branch}_{load}'.format(
                                   branch=branch_no,
                                   load=load_no,
                                   sector=sector_short)))
예제 #26
0
def build_lv_graph_ria(lvgd, grid_model_params):
    """Build graph for LV grid of sectors retail/industrial and agricultural

    Based on structural description of LV grid topology for sectors
    retail/industrial and agricultural (RIA) branches for these sectors are
    created and attached to the LV grid's MV-LV substation bus bar.

    LV loads of the sectors retail/industrial and agricultural are located
    in separat branches for each sector (in case of large load multiple of
    these).
    These loads are distributed across the branches by an equidistant
    distribution.

    This function accepts the dict `grid_model_params` with particular
    structure

    >>> grid_model_params = {
    >>> ... 'agricultural': {
    >>> ...     'max_loads_per_branch': 2
    >>> ...     'single_peak_load': 140,
    >>> ...     'full_branches': 2,
    >>> ...     'remaining_loads': 1,
    >>> ...     'load_distance': 800/3,
    >>> ...     'load_distance_remaining': 400}}

    Parameters
    ----------
    lvgd : LVGridDistrictDing0
        Low-voltage grid district object
    grid_model_params : dict
        Dict of structural information of sectoral LV grid branchwith particular 
        structure, e.g.::

            grid_model_params = {
                'agricultural': {
                    'max_loads_per_branch': 2
                    'single_peak_load': 140,
                    'full_branches': 2,
                    'remaining_loads': 1,
                    'load_distance': 800/3,
                    'load_distance_remaining': 400
                }
            }

    Note
    -----
    We assume a distance from the load to the branch it is connected to of
    30 m. This assumption is defined in the config files.
    """
    def lv_graph_attach_branch():
        """Attach a single branch including its equipment (cable dist, loads
        and line segments) to graph of `lv_grid`
        """

        # determine maximum current occuring due to peak load
        # of this load load_no
        I_max_load = val['single_peak_load'] / (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]
        cable_type_stub = suitable_cables_stub.loc[
            suitable_cables_stub['I_max_th'].idxmin(), :]

        # cable distributor to divert from main branch
        lv_cable_dist = LVCableDistributorDing0(grid=lvgd.lv_grid,
                                                branch_no=branch_no,
                                                load_no=load_no)
        # add lv_cable_dist to graph
        lvgd.lv_grid.add_cable_dist(lv_cable_dist)

        # cable distributor within building (to connect load+geno)
        lv_cable_dist_building = LVCableDistributorDing0(grid=lvgd.lv_grid,
                                                         branch_no=branch_no,
                                                         load_no=load_no,
                                                         in_building=True)
        # add lv_cable_dist_building to graph
        lvgd.lv_grid.add_cable_dist(lv_cable_dist_building)

        # create an instance of Ding0 LV load
        lv_load = LVLoadDing0(grid=lvgd.lv_grid,
                              branch_no=branch_no,
                              load_no=load_no,
                              peak_load=val['single_peak_load'],
                              consumption=val['consumption'])

        # add lv_load to graph
        lvgd.lv_grid.add_load(lv_load)

        # create branch line segment between either (a) station
        # and cable distributor or (b) between neighboring cable
        # distributors
        if load_no == 1:
            # case a: cable dist <-> station
            lvgd.lv_grid._graph.add_edge(
                lvgd.lv_grid.station(),
                lv_cable_dist,
                branch=BranchDing0(
                    length=val['load_distance'],
                    kind='cable',
                    type=cable_type,
                    id_db='branch_{sector}{branch}_{load}'.format(
                        branch=branch_no, load=load_no, sector=sector_short)))
        else:
            # case b: cable dist <-> cable dist
            lvgd.lv_grid._graph.add_edge(
                lvgd.lv_grid._cable_distributors[-4],
                lv_cable_dist,
                branch=BranchDing0(
                    length=val['load_distance'],
                    kind='cable',
                    type=cable_type,
                    id_db='branch_{sector}{branch}_{load}'.format(
                        branch=branch_no, load=load_no, sector=sector_short)))

        # create branch stub that connects the load to the
        # lv_cable_dist located in the branch line
        lvgd.lv_grid._graph.add_edge(
            lv_cable_dist,
            lv_cable_dist_building,
            branch=BranchDing0(length=cfg_ding0.get(
                'assumptions', 'lv_ria_branch_connection_distance'),
                               kind='cable',
                               type=cable_type_stub,
                               id_db='stub_{sector}{branch}_{load}'.format(
                                   branch=branch_no,
                                   load=load_no,
                                   sector=sector_short)))

        lvgd.lv_grid._graph.add_edge(
            lv_cable_dist_building,
            lv_load,
            branch=BranchDing0(length=1,
                               kind='cable',
                               type=cable_type_stub,
                               id_db='stub_{sector}{branch}_{load}'.format(
                                   branch=branch_no,
                                   load=load_no,
                                   sector=sector_short)))

    cable_lf = cfg_ding0.get('assumptions', 'load_factor_lv_cable_lc_normal')
    cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')
    v_nom = cfg_ding0.get('assumptions',
                          'lv_nominal_voltage') / 1e3  # v_nom in kV
    # iterate over branches for sectors retail/industrial and agricultural
    for sector, val in grid_model_params.items():
        if sector == 'retail/industrial':
            sector_short = 'RETIND'
        elif sector == 'agricultural':
            sector_short = 'AGR'
        else:
            sector_short = ''
        if val is not None:
            for branch_no in list(range(1, val['full_branches'] + 1)):

                # determine maximum current occuring due to peak load of branch
                I_max_branch = (val['max_loads_per_branch'] *
                                val['single_peak_load']) / (3**0.5 * v_nom) / (
                                    cos_phi_load)

                # determine suitable cable for this current
                suitable_cables = lvgd.lv_grid.network.static_data[
                    'LV_cables'][(lvgd.lv_grid.network.static_data['LV_cables']
                                  ['I_max_th'] * cable_lf) > I_max_branch]
                cable_type = suitable_cables.loc[
                    suitable_cables['I_max_th'].idxmin(), :]

                # create Ding0 grid objects and add to graph
                for load_no in list(range(1, val['max_loads_per_branch'] + 1)):
                    # create a LV grid string and attached to station
                    lv_graph_attach_branch()

            # add remaining branch
            if val['remaining_loads'] > 0:
                if 'branch_no' not in locals():
                    branch_no = 0
                # determine maximum current occuring due to peak load of branch
                I_max_branch = (val['max_loads_per_branch'] *
                                val['single_peak_load']) / (3**0.5 * v_nom) / (
                                    cos_phi_load)

                # determine suitable cable for this current
                suitable_cables = lvgd.lv_grid.network.static_data[
                    'LV_cables'][(lvgd.lv_grid.network.static_data['LV_cables']
                                  ['I_max_th'] * cable_lf) > I_max_branch]
                cable_type = suitable_cables.loc[
                    suitable_cables['I_max_th'].idxmin(), :]

                branch_no += 1

                for load_no in list(range(1, val['remaining_loads'] + 1)):
                    # create a LV grid string and attach to station
                    lv_graph_attach_branch()
예제 #27
0
def select_transformers(grid, s_max=None):
    """Selects LV transformer according to peak load of LV grid district.

    The transformers are chosen according to max. of load case and feedin-case
    considering load factors and power factor.
    The MV-LV transformer with the next higher available nominal apparent power is
    chosen. Therefore, a max. allowed transformer loading of 100% is implicitly
    assumed. If the peak load exceeds the max. power of a single available
    transformer, multiple transformer are build.

    By default `peak_load` and `peak_generation` are taken from `grid` instance.
    The behavior can be overridden providing `s_max` as explained in
    ``Arguments``.

    Parameters
    ----------
    grid: LVGridDing0
        LV grid data

    Arguments
    ---------
    s_max : dict
        dict containing maximum apparent power of load or generation case and
        str describing the case. For example

        .. code-block:: python

            {
                's_max': 480,
                'case': 'load'
            }

        or

        .. code-block:: python

            {
                's_max': 120,
                'case': 'gen'
            }

        s_max passed overrides `grid.grid_district.peak_load` respectively
        `grid.station().peak_generation`.

    Returns
    -------
    :pandas:`pandas.DataFrame<dataframe>`
        Parameters of chosen Transformer
    :obj:`int` 
        Count of transformers

    Note
    -----
    The LV transformer with the next higher available nominal apparent power is
    chosen. Therefore, a max. allowed transformer loading of 100% is implicitly
    assumed. If the peak load exceeds the max. power of a single available
    transformer, use multiple trafos.
    """

    load_factor_lv_trans_lc_normal = cfg_ding0.get(
        'assumptions', 'load_factor_lv_trans_lc_normal')
    load_factor_lv_trans_fc_normal = cfg_ding0.get(
        'assumptions', 'load_factor_lv_trans_fc_normal')

    cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')
    cos_phi_gen = cfg_ding0.get('assumptions', 'cos_phi_gen')

    # get equipment parameters of LV transformers
    trafo_parameters = grid.network.static_data['LV_trafos']

    # determine s_max from grid object if not provided via arguments
    if s_max is None:
        # get maximum from peak load and peak generation
        s_max_load = grid.grid_district.peak_load / cos_phi_load
        s_max_gen = grid.station().peak_generation / cos_phi_gen

        # check if load or generation is greater respecting corresponding load factor
        if s_max_load > s_max_gen:
            # use peak load and load factor from load case
            load_factor_lv_trans = load_factor_lv_trans_lc_normal
            s_max = s_max_load
        else:
            # use peak generation and load factor for feedin case
            load_factor_lv_trans = load_factor_lv_trans_fc_normal
            s_max = s_max_gen
    else:
        if s_max['case'] == 'load':
            load_factor_lv_trans = load_factor_lv_trans_lc_normal
        elif s_max['case'] == 'gen':
            load_factor_lv_trans = load_factor_lv_trans_fc_normal
        else:
            logger.error('No proper \'case\' provided for argument s_max')
            raise ValueError('Please provide proper \'case\' for argument '
                             '`s_max`.')
        s_max = s_max['s_max']

    # get max. trafo
    transformer_max = trafo_parameters.iloc[trafo_parameters['S_nom'].idxmax()]

    # peak load is smaller than max. available trafo
    if s_max < (transformer_max['S_nom'] * load_factor_lv_trans):
        # choose trafo
        transformer = trafo_parameters.iloc[
            trafo_parameters[trafo_parameters['S_nom'] *
                             load_factor_lv_trans > s_max]['S_nom'].idxmin()]
        transformer_cnt = 1
    # peak load is greater than max. available trafo -> use multiple trafos
    else:
        transformer_cnt = 2
        # increase no. of trafos until peak load can be supplied
        while not any(trafo_parameters['S_nom'] *
                      load_factor_lv_trans > (s_max / transformer_cnt)):
            transformer_cnt += 1
        transformer = trafo_parameters.iloc[trafo_parameters[
            trafo_parameters['S_nom'] * load_factor_lv_trans > (
                s_max / transformer_cnt)]['S_nom'].idxmin()]

    return transformer, transformer_cnt
예제 #28
0
    def select_transformers(self):
        """ Selects appropriate transformers for the HV-MV substation.

        The transformers are chosen according to max. of load case and feedin-case
        considering load factors.
        The HV-MV transformer with the next higher available nominal apparent power is
        chosen. If one trafo is not sufficient, multiple trafos are used. Additionally,
        in a second step an redundant trafo is installed with max. capacity of the
        selected trafos of the first step according to general planning principles for
        MV distribution grids (n-1).

        Parameters
        ----------
        transformers : dict
            Contains technical information of p hv/mv transformers
        **kwargs : dict
            Should contain a value behind the key 'peak_load'


        Notes
        -----
        Parametrization of transformers bases on [#]_.
        
        Potential hv-mv-transformers are chosen according to [#]_.
        

        References
        ----------
        .. [#] Deutsche Energie-Agentur GmbH (dena), "dena-Verteilnetzstudie.
            Ausbau- und Innovationsbedarf der Stromverteilnetze in Deutschland
            bis 2030.", 2012
        .. [#] X. Tao, "Automatisierte Grundsatzplanung von
            Mittelspannungsnetzen", Dissertation, 2006

        """

        # get power factor for loads and generators
        cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')
        cos_phi_feedin = cfg_ding0.get('assumptions', 'cos_phi_gen')

        # get trafo load factors
        load_factor_mv_trans_lc_normal = float(cfg_ding0.get('assumptions',
                                                             'load_factor_mv_trans_lc_normal'))
        load_factor_mv_trans_fc_normal = float(cfg_ding0.get('assumptions',
                                                             'load_factor_mv_trans_fc_normal'))

        # get equipment parameters of MV transformers
        trafo_parameters = self.grid.network.static_data['MV_trafos']

        # get peak load and peak generation
        cum_peak_load = self.peak_load / cos_phi_load
        cum_peak_generation = self.peak_generation(mode='MVLV') / cos_phi_feedin

        # check if load or generation is greater respecting corresponding load factor
        if (cum_peak_load / load_factor_mv_trans_lc_normal) > \
           (cum_peak_generation / load_factor_mv_trans_fc_normal):
            # use peak load and load factor from load case
            load_factor_mv_trans = load_factor_mv_trans_lc_normal
            residual_apparent_power = cum_peak_load
        else:
            # use peak generation and load factor for feedin case
            load_factor_mv_trans = load_factor_mv_trans_fc_normal
            residual_apparent_power = cum_peak_generation

        # determine number and size of required transformers

        # get max. trafo
        transformer_max = trafo_parameters.iloc[trafo_parameters['S_nom'].idxmax()]

        while residual_apparent_power > 0:
            if residual_apparent_power > load_factor_mv_trans * transformer_max['S_nom']:
                transformer = transformer_max
            else:
                # choose trafo
                transformer = trafo_parameters.iloc[
                    trafo_parameters[trafo_parameters['S_nom'] * load_factor_mv_trans >
                                     residual_apparent_power]['S_nom'].idxmin()]

            # add transformer on determined size with according parameters
            self.add_transformer(TransformerDing0(**{'grid': self.grid,
                                                     'v_level': self.grid.v_level,
                                                     's_max_longterm': transformer['S_nom']}))
            # calc residual load
            residual_apparent_power -= (load_factor_mv_trans *
                                        transformer['S_nom'])

        # if no transformer was selected (no load in grid district), use smallest one
        if len(self._transformers) == 0:
            transformer = trafo_parameters.iloc[trafo_parameters['S_nom'].idxmin()]

            self.add_transformer(
                TransformerDing0(grid=self.grid,
                                 v_level=self.grid.v_level,
                                 s_max_longterm=transformer['S_nom']))

        # add redundant transformer of the size of the largest transformer
        s_max_max = max((o.s_max_a for o in self._transformers))
        self.add_transformer(TransformerDing0(**{'grid': self.grid,
                                                 'v_level': self.grid.v_level,
                                                 's_max_longterm': s_max_max}))
예제 #29
0
def ding0_graph_to_routing_specs(graph):
    """ Build data dictionary from graph nodes for routing (translation)

    Args
    ----
    graph: :networkx:`NetworkX Graph Obj< >`
        NetworkX graph object with nodes

    Returns
    -------
    :obj:`dict`
        Data dictionary for routing.
        
    See Also
    --------
    ding0.grid.mv_grid.models.models.Graph : for keys of return dict
        
    """

    # get power factor for loads
    cos_phi_load = cfg_ding0.get('assumptions', 'cos_phi_load')

    specs = {}
    nodes_demands = {}
    nodes_pos = {}
    nodes_agg = {}

    # check if there are only load areas of type aggregated and satellite
    # -> treat satellites as normal load areas (allow for routing)
    satellites_only = True
    for node in graph.nodes():
        if isinstance(node, LVLoadAreaCentreDing0):
            if not node.lv_load_area.is_satellite and not node.lv_load_area.is_aggregated:
                satellites_only = False

    for node in graph.nodes():
        # station is LV station
        if isinstance(node, LVLoadAreaCentreDing0):
            # only major stations are connected via MV ring
            # (satellites in case of there're only satellites in grid district)
            if not node.lv_load_area.is_satellite or satellites_only:
                # get demand and position of node
                # convert node's demand to int for performance purposes and to avoid that node
                # allocation with subsequent deallocation results in demand<0 due to rounding errors.
                nodes_demands[str(node)] = int(node.lv_load_area.peak_load /
                                               cos_phi_load)
                nodes_pos[str(node)] = (node.geo_data.x, node.geo_data.y)
                # get aggregation flag
                if node.lv_load_area.is_aggregated:
                    nodes_agg[str(node)] = True
                else:
                    nodes_agg[str(node)] = False

        # station is MV station
        elif isinstance(node, MVStationDing0):
            nodes_demands[str(node)] = 0
            nodes_pos[str(node)] = (node.geo_data.x, node.geo_data.y)
            specs['DEPOT'] = str(node)
            specs['BRANCH_KIND'] = node.grid.default_branch_kind
            specs['BRANCH_TYPE'] = node.grid.default_branch_type
            specs['V_LEVEL'] = node.grid.v_level

    specs['NODE_COORD_SECTION'] = nodes_pos
    specs['DEMAND'] = nodes_demands
    specs['MATRIX'] = calc_geo_dist_matrix_vincenty(nodes_pos)
    specs['IS_AGGREGATED'] = nodes_agg

    return specs
예제 #30
0
def extend_substation_voltage(crit_stations, grid_level='LV'):
    """
    Extend substation if voltage issues at the substation occur

    Follows a two-step procedure:
    
    i) Existing transformers are extended by replacement with large nominal
       apparent power
      
    ii) New additional transformers added to substation (see 'Notes')

    Parameters
    ----------
    crit_stations : :any:`list`
        List of stations with overloading or voltage issues.
    grid_level : str
        Specifiy grid level: 'MV' or 'LV'

    Notes
    -----
    At maximum 2 new of largest (currently 630 kVA) transformer are additionally
    built to resolve voltage issues at MV-LV substation bus bar.
    """
    grid = crit_stations[0]['node'].grid
    trafo_params = grid.network._static_data['{grid_level}_trafos'.format(
        grid_level=grid_level)]
    trafo_s_max_max = max(trafo_params['S_nom'])
    trafo_min_size = trafo_params.ix[trafo_params['S_nom'].idxmin()]

    v_diff_max_fc = cfg_ding0.get('assumptions', 'lv_max_v_level_fc_diff_normal')
    v_diff_max_lc = cfg_ding0.get('assumptions', 'lv_max_v_level_lc_diff_normal')

    tree = nx.dfs_tree(grid._graph, grid._station)

    for station in crit_stations:
        v_delta = max(station['v_diff'])

        # get list of nodes of main branch in right order
        extendable_trafos = [_ for _ in station['node']._transformers
                             if _.s_max_a < trafo_s_max_max]

        v_delta_initially_lc = v_delta[0]
        v_delta_initially_fc = v_delta[1]

        new_transformers_cnt = 0

        # extend existing trafo power while voltage issues exist and larger trafos
        # are available
        while (v_delta[0] > v_diff_max_lc) or (v_delta[1] > v_diff_max_fc):
            if extendable_trafos:
                # extend power of first trafo to next higher size available
                extend_trafo_power(extendable_trafos, trafo_params)
            elif new_transformers_cnt < 2:
                # build a new transformer
                lv_transformer = TransformerDing0(
                    grid=grid,
                    id_db=id,
                    v_level=0.4,
                    s_max_longterm=trafo_min_size['S_nom'],
                    r=trafo_min_size['R'],
                    x=trafo_min_size['X'])

                # add each transformer to its station
                grid._station.add_transformer(lv_transformer)

                new_transformers_cnt += 1

            # update break criteria
            v_delta = get_voltage_at_bus_bar(grid, tree)
            extendable_trafos = [_ for _ in station['node']._transformers
                                 if _.s_max_a < trafo_s_max_max]

            if (v_delta[0] == v_delta_initially_lc) or (
                v_delta[1] == v_delta_initially_fc):
                logger.warning("Extension of {station} has no effect on "
                               "voltage delta at bus bar. Transformation power "
                               "extension is halted.".format(
                    station=station['node']))
                break