Instruction:
Can you implement a function in Python 3 that
imports the generator data from the Open Energy Database.

Response:
def _import_genos_from_oedb(network):
    """Import generator data from the Open Energy Database (OEDB).

    The importer uses SQLAlchemy ORM objects.
    These are defined in ego.io,
    see https://github.com/openego/ego.io/tree/dev/egoio/db_tables

    Parameters
    ----------
    network: :class:`~.grid.network.Network`
        The eDisGo container object

    Notes
    ------
    Right now only solar and wind generators can be imported.

    """

    def _import_conv_generators():
        """Import conventional (conv) generators

        Returns
        -------
        :pandas:`pandas.DataFrame<dataframe>`
            List of medium-voltage generators

        Notes
        -----
        You can find a full list of columns in
        :func:`edisgo.data.import_data._update_grids`
        """

        # build query
        generators_sqla = session.query(
            orm_conv_generators.columns.id,
            orm_conv_generators.columns.subst_id,
            orm_conv_generators.columns.la_id,
            orm_conv_generators.columns.capacity,
            orm_conv_generators.columns.type,
            orm_conv_generators.columns.voltage_level,
            orm_conv_generators.columns.fuel,
            func.ST_AsText(func.ST_Transform(
                orm_conv_generators.columns.geom, srid))
        ). \
            filter(orm_conv_generators.columns.subst_id == network.mv_grid.id). \
            filter(orm_conv_generators.columns.voltage_level.in_([4, 5, 6, 7])). \
            filter(orm_conv_generators_version)

        # read data from db
        generators_mv = pd.read_sql_query(generators_sqla.statement,
                                          session.bind,
                                          index_col='id')

        return generators_mv

    def _import_res_generators(types_filter):
        """Import renewable (res) generators

        Returns
        -------
        :pandas:`pandas.DataFrame<dataframe>`
            List of medium-voltage generators
        :pandas:`pandas.DataFrame<dataframe>`
            List of low-voltage generators

        Notes
        -----
        You can find a full list of columns in
        :func:`edisgo.data.import_data._update_grids`

        If subtype is not specified it's set to 'unknown'.
        """

        # build basic query
        generators_sqla = session.query(
            orm_re_generators.columns.id,
            orm_re_generators.columns.subst_id,
            orm_re_generators.columns.la_id,
            orm_re_generators.columns.mvlv_subst_id,
            orm_re_generators.columns.electrical_capacity,
            orm_re_generators.columns.generation_type,
            orm_re_generators.columns.generation_subtype,
            orm_re_generators.columns.voltage_level,
            orm_re_generators.columns.w_id,
            func.ST_AsText(func.ST_Transform(
                orm_re_generators.columns.rea_geom_new, srid)).label('geom'),
            func.ST_AsText(func.ST_Transform(
            orm_re_generators.columns.geom, srid)).label('geom_em')). \
                filter(orm_re_generators.columns.subst_id == network.mv_grid.id). \
            filter(orm_re_generators_version). \
            filter(types_filter)

        # extend basic query for MV generators and read data from db
        generators_mv_sqla = generators_sqla. \
            filter(orm_re_generators.columns.voltage_level.in_([4, 5]))
        generators_mv = pd.read_sql_query(generators_mv_sqla.statement,
                                          session.bind,
                                          index_col='id')

        # define generators with unknown subtype as 'unknown'
        generators_mv.loc[generators_mv[
                              'generation_subtype'].isnull(),
                          'generation_subtype'] = 'unknown'

        # extend basic query for LV generators and read data from db
        generators_lv_sqla = generators_sqla. \
            filter(orm_re_generators.columns.voltage_level.in_([6, 7]))
        generators_lv = pd.read_sql_query(generators_lv_sqla.statement,
                                       session.bind,
                                       index_col='id')

        # define generators with unknown subtype as 'unknown'
        generators_lv.loc[generators_lv[
                              'generation_subtype'].isnull(),
                          'generation_subtype'] = 'unknown'

        return generators_mv, generators_lv

    def _update_grids(network, generators_mv, generators_lv, remove_missing=True):
        """Update imported status quo DINGO-grid according to new generator dataset

        It
            * adds new generators to grid if they do not exist
            * updates existing generators if parameters have changed
            * removes existing generators from grid which do not exist in the imported dataset

        Steps:

            * Step 1: MV generators: Update existing, create new, remove decommissioned
            * Step 2: LV generators (single units): Update existing, remove decommissioned
            * Step 3: LV generators (in aggregated MV generators): Update existing,
                remove decommissioned
                (aggregated MV generators = originally LV generators from aggregated Load
                Areas which were aggregated during import from ding0.)
            * Step 4: LV generators (single units + aggregated MV generators): Create new

        Parameters
        ----------
        network: :class:`~.grid.network.Network`
            The eDisGo container object

        generators_mv: :pandas:`pandas.DataFrame<dataframe>`
            List of MV generators
            Columns:
                * id: :obj:`int` (index column)
                * electrical_capacity: :obj:`float` (unit: kW)
                * generation_type: :obj:`str` (e.g. 'solar')
                * generation_subtype: :obj:`str` (e.g. 'solar_roof_mounted')
                * voltage level: :obj:`int` (range: 4..7,)
                * geom: :shapely:`Shapely Point object<points>`
                  (CRS see config_grid.cfg)
                * geom_em: :shapely:`Shapely Point object<points>`
                  (CRS see config_grid.cfg)

        generators_lv: :pandas:`pandas.DataFrame<dataframe>`
            List of LV generators
            Columns:
                * id: :obj:`int` (index column)
                * mvlv_subst_id: :obj:`int` (id of MV-LV substation in grid
                  = grid which the generator will be connected to)
                * electrical_capacity: :obj:`float` (unit: kW)
                * generation_type: :obj:`str` (e.g. 'solar')
                * generation_subtype: :obj:`str` (e.g. 'solar_roof_mounted')
                * voltage level: :obj:`int` (range: 4..7,)
                * geom: :shapely:`Shapely Point object<points>`
                  (CRS see config_grid.cfg)
                * geom_em: :shapely:`Shapely Point object<points>`
                  (CRS see config_grid.cfg)

        remove_missing: :obj:`bool`
            If true, remove generators from grid which are not included in the imported dataset.
        """

        # set capacity difference threshold
        cap_diff_threshold = 10 ** -4

        # get existing generators in MV and LV grids
        g_mv, g_lv, g_mv_agg = _build_generator_list(network=network)

        # print current capacity
        capacity_grid = 0
        capacity_grid += sum([row['obj'].nominal_capacity for id, row in g_mv.iterrows()])
        capacity_grid += sum([row['obj'].nominal_capacity for id, row in g_lv.iterrows()])
        capacity_grid += sum([row['obj'].nominal_capacity for id, row in g_mv_agg.iterrows()])
        logger.debug('Cumulative generator capacity (existing): {} kW'
                     .format(str(round(capacity_grid, 1)))
                     )

        # ======================================
        # Step 1: MV generators (existing + new)
        # ======================================
        logger.debug('==> MV generators')
        logger.debug('{} generators imported.'
                     .format(str(len(generators_mv))))
        # get existing genos (status quo DF format)
        g_mv_existing = g_mv[g_mv['id'].isin(list(generators_mv.index.values))]
        # get existing genos (new genos DF format)
        generators_mv_existing = generators_mv[generators_mv.index.isin(list(g_mv_existing['id']))]
        # remove existing ones from grid's geno list
        g_mv = g_mv[~g_mv.isin(g_mv_existing)].dropna()

        # TEMP: BACKUP 1 GENO FOR TESTING
        #temp_geno = generators_mv_existing.iloc[0]
        #temp_geno['geom_em'] = temp_geno['geom_em'].replace('10.667', '10.64')

        # iterate over exiting generators and check whether capacity has changed
        log_geno_count = 0
        log_geno_cap = 0
        for id, row in generators_mv_existing.iterrows():

            geno_existing = g_mv_existing[g_mv_existing['id'] == id]['obj'].iloc[0]

            # check if capacity equals; if not: update capacity
            if abs(row['electrical_capacity'] - \
                           geno_existing.nominal_capacity) < cap_diff_threshold:
                continue
            else:
                log_geno_cap += row['electrical_capacity'] - geno_existing.nominal_capacity
                log_geno_count += 1
                geno_existing.nominal_capacity = row['electrical_capacity']

                # check if cap=0 (this may happen if dp is buggy)
                if row['electrical_capacity'] <= 0:
                    geno_existing.grid.graph.remove_node(geno_existing)
                    logger.warning('Capacity of generator {} is <=0, generator removed. '
                                   'Check your data source.'
                                   .format(repr(geno_existing))
                                   )

        logger.debug('Capacities of {} of {} existing generators updated ({} kW).'
                     .format(str(log_geno_count),
                             str(len(generators_mv_existing) - log_geno_count),
                             str(round(log_geno_cap, 1))
                             )
                     )

        # new genos
        log_geno_count = 0
        log_geno_cap = 0
        generators_mv_new = generators_mv[~generators_mv.index.isin(
            list(g_mv_existing['id']))]

        # remove them from grid's geno list
        g_mv = g_mv[~g_mv.isin(list(generators_mv_new.index.values))].dropna()

        # TEMP: INSERT BACKUPPED GENO IN DF FOR TESTING
        #generators_mv_new = generators_mv_new.append(temp_geno)

        # iterate over new generators and create them
        for id, row in generators_mv_new.iterrows():
            # check if geom is available, skip otherwise
            geom = _check_geom(id, row)
            if not geom:
                logger.warning('Generator {} has no geom entry at all and will'
                               'not be imported!'.format(id))
                continue

            # create generator object and add it to MV grid's graph
            if row['generation_type'] in ['solar', 'wind']:
                network.mv_grid.graph.add_node(
                    GeneratorFluctuating(
                        id=id,
                        grid=network.mv_grid,
                        nominal_capacity=row['electrical_capacity'],
                        type=row['generation_type'],
                        subtype=row['generation_subtype'],
                        v_level=int(row['voltage_level']),
                        weather_cell_id=row['w_id'],
                        geom=wkt_loads(geom)),
                    type='generator')
            else:
                network.mv_grid.graph.add_node(
                    Generator(id=id,
                              grid=network.mv_grid,
                              nominal_capacity=row['electrical_capacity'],
                              type=row['generation_type'],
                              subtype=row['generation_subtype'],
                              v_level=int(row['voltage_level']),
                              geom=wkt_loads(geom)
                              ),
                    type='generator')
            log_geno_cap += row['electrical_capacity']
            log_geno_count += 1

        logger.debug('{} of {} new generators added ({} kW).'
                     .format(str(log_geno_count),
                             str(len(generators_mv_new)),
                             str(round(log_geno_cap, 1))
                             )
                     )

        # remove decommissioned genos
        # (genos which exist in grid but not in the new dataset)
        log_geno_cap = 0
        if not g_mv.empty and remove_missing:
            log_geno_count = 0
            for _, row in g_mv.iterrows():
                log_geno_cap += row['obj'].nominal_capacity
                row['obj'].grid.graph.remove_node(row['obj'])
                log_geno_count += 1
            logger.debug('{} of {} decommissioned generators removed ({} kW).'
                         .format(str(log_geno_count),
                                 str(len(g_mv)),
                                 str(round(log_geno_cap, 1))
                                 )
                         )

        # =============================================
        # Step 2: LV generators (single existing units)
        # =============================================
        logger.debug('==> LV generators')
        logger.debug('{} generators imported.'.format(str(len(generators_lv))))
        # get existing genos (status quo DF format)
        g_lv_existing = g_lv[g_lv['id'].isin(list(generators_lv.index.values))]
        # get existing genos (new genos DF format)
        generators_lv_existing = generators_lv[generators_lv.index.isin(list(g_lv_existing['id']))]

        # TEMP: BACKUP 1 GENO FOR TESTING
        # temp_geno = g_lv.iloc[0]

        # remove existing ones from grid's geno list
        g_lv = g_lv[~g_lv.isin(g_lv_existing)].dropna()

        # iterate over exiting generators and check whether capacity has changed
        log_geno_count = 0
        log_geno_cap = 0
        for id, row in generators_lv_existing.iterrows():

            geno_existing = g_lv_existing[g_lv_existing['id'] == id]['obj'].iloc[0]

            # check if capacity equals; if not: update capacity
            if abs(row['electrical_capacity'] - \
                           geno_existing.nominal_capacity) < cap_diff_threshold:
                continue
            else:
                log_geno_cap += row['electrical_capacity'] - geno_existing.nominal_capacity
                log_geno_count += 1
                geno_existing.nominal_capacity = row['electrical_capacity']

        logger.debug('Capacities of {} of {} existing generators (single units) updated ({} kW).'
                     .format(str(log_geno_count),
                             str(len(generators_lv_existing) - log_geno_count),
                             str(round(log_geno_cap, 1))
                             )
                     )

        # TEMP: INSERT BACKUPPED GENO IN DF FOR TESTING
        # g_lv.loc[len(g_lv)] = temp_geno

        # remove decommissioned genos
        # (genos which exist in grid but not in the new dataset)
        log_geno_cap = 0
        if not g_lv.empty and remove_missing:
            log_geno_count = 0
            for _, row in g_lv.iterrows():
                log_geno_cap += row['obj'].nominal_capacity
                row['obj'].grid.graph.remove_node(row['obj'])
                log_geno_count += 1
            logger.debug('{} of {} decommissioned generators (single units) removed ({} kW).'
                         .format(str(log_geno_count),
                                 str(len(g_lv)),
                                 str(round(log_geno_cap, 1))
                                 )
                         )

        # ====================================================================================
        # Step 3: LV generators (existing in aggregated units (originally from aggregated LA))
        # ====================================================================================
        g_lv_agg = network.dingo_import_data
        g_lv_agg_existing = g_lv_agg[g_lv_agg['id'].isin(list(generators_lv.index.values))]
        generators_lv_agg_existing = generators_lv[generators_lv.index.isin(list(g_lv_agg_existing['id']))]

        # TEMP: BACKUP 1 GENO FOR TESTING
        # temp_geno = g_lv_agg.iloc[0]

        g_lv_agg = g_lv_agg[~g_lv_agg.isin(g_lv_agg_existing)].dropna()

        log_geno_count = 0
        log_agg_geno_list = []
        log_geno_cap = 0
        for id, row in generators_lv_agg_existing.iterrows():

            # check if capacity equals; if not: update capacity off agg. geno
            cap_diff = row['electrical_capacity'] - \
                       g_lv_agg_existing[g_lv_agg_existing['id'] == id]['capacity'].iloc[0]
            if abs(cap_diff) < cap_diff_threshold:
                continue
            else:
                agg_geno = g_lv_agg_existing[g_lv_agg_existing['id'] == id]['agg_geno'].iloc[0]
                agg_geno.nominal_capacity += cap_diff
                log_geno_cap += cap_diff

                log_geno_count += 1
                log_agg_geno_list.append(agg_geno)

        logger.debug('Capacities of {} of {} existing generators (in {} of {} aggregated units) '
                     'updated ({} kW).'
                     .format(str(log_geno_count),
                             str(len(generators_lv_agg_existing) - log_geno_count),
                             str(len(set(log_agg_geno_list))),
                             str(len(g_lv_agg_existing['agg_geno'].unique())),
                             str(round(log_geno_cap, 1))
                             )
                     )

        # TEMP: INSERT BACKUPPED GENO IN DF FOR TESTING
        # g_lv_agg.loc[len(g_lv_agg)] = temp_geno

        # remove decommissioned genos
        # (genos which exist in grid but not in the new dataset)
        log_geno_cap = 0
        if not g_lv_agg.empty and remove_missing:
            log_geno_count = 0
            for _, row in g_lv_agg.iterrows():
                row['agg_geno'].nominal_capacity -= row['capacity']
                log_geno_cap += row['capacity']
                # remove LV geno id from id string of agg. geno
                id = row['agg_geno'].id.split('-')
                ids = id[2].split('_')
                ids.remove(str(int(row['id'])))
                row['agg_geno'].id = '-'.join([id[0], id[1], '_'.join(ids)])

                # after removing the LV geno from agg geno, is the agg. geno empty?
                # if yes, remove it from grid
                if not ids:
                    row['agg_geno'].grid.graph.remove_node(row['agg_geno'])

                log_geno_count += 1
            logger.debug('{} of {} decommissioned generators in aggregated generators removed ({} kW).'
                         .format(str(log_geno_count),
                                 str(len(g_lv_agg)),
                                 str(round(log_geno_cap, 1))
                                 )
                         )

        # ====================================================================
        # Step 4: LV generators (new single units + genos in aggregated units)
        # ====================================================================
        # new genos
        log_geno_count =\
            log_agg_geno_new_count =\
            log_agg_geno_upd_count = 0

        # TEMP: BACKUP 1 GENO FOR TESTING
        #temp_geno = generators_lv[generators_lv.index == g_lv_existing.iloc[0]['id']]

        generators_lv_new = generators_lv[~generators_lv.index.isin(list(g_lv_existing['id'])) &
                                          ~generators_lv.index.isin(list(g_lv_agg_existing['id']))]

        # TEMP: INSERT BACKUPPED GENO IN DF FOR TESTING
        #generators_lv_new = generators_lv_new.append(temp_geno)

        # dict for new agg. generators
        agg_geno_new = {}
        # get LV grid districts
        lv_grid_dict = _build_lv_grid_dict(network)

        # get predefined random seed and initialize random generator
        seed = int(network.config['grid_connection']['random_seed'])
        random.seed(a=seed)

        # check if none of new generators can be allocated to an existing  LV grid
        if not any([_ in lv_grid_dict.keys()
                    for _ in list(generators_lv_new['mvlv_subst_id'])]):
            logger.warning('None of the imported generators can be allocated '
                           'to an existing LV grid. Check compatibility of grid '
                           'and generator datasets.')

        # iterate over new (single unit or part of agg. unit) generators and create them
        log_geno_cap = 0
        for id, row in generators_lv_new.iterrows():
            lv_geno_added_to_agg_geno = False

            # new unit is part of agg. LA (mvlv_subst_id is different from existing
            # ones in LV grids of non-agg. load areas)
            if (row['mvlv_subst_id'] not in lv_grid_dict.keys() and
                    row['la_id'] and not isnan(row['la_id']) and
                    row['mvlv_subst_id'] and not isnan(row['mvlv_subst_id'])):

                # check if new unit can be added to existing agg. generator
                # (LA id, type and subtype match) -> update existing agg. generator.
                # Normally, this case should not occur since `subtype` of new genos
                # is set to a new value (e.g. 'solar')
                for _, agg_row in g_mv_agg.iterrows():
                    if (agg_row['la_id'] == int(row['la_id']) and
                            agg_row['obj'].type == row['generation_type'] and
                            agg_row['obj'].subtype == row['generation_subtype']):

                        agg_row['obj'].nominal_capacity += row['electrical_capacity']
                        agg_row['obj'].id += '_{}'.format(str(id))
                        log_agg_geno_upd_count += 1
                        lv_geno_added_to_agg_geno = True

                if not lv_geno_added_to_agg_geno:
                    la_id = int(row['la_id'])
                    if la_id not in agg_geno_new:
                        agg_geno_new[la_id] = {}
                    if row['voltage_level'] not in agg_geno_new[la_id]:
                        agg_geno_new[la_id][row['voltage_level']] = {}
                    if row['generation_type'] not in agg_geno_new[la_id][row['voltage_level']]:
                        agg_geno_new[la_id][row['voltage_level']][row['generation_type']] = {}
                    if row['generation_subtype'] not in \
                            agg_geno_new[la_id][row['voltage_level']][row['generation_type']]:
                        agg_geno_new[la_id][row['voltage_level']][row['generation_type']]\
                            .update({row['generation_subtype']: {'ids': [int(id)],
                                                                 'capacity': row['electrical_capacity']
                                                                 }
                             }
                        )
                    else:
                        agg_geno_new[la_id][row['voltage_level']][row['generation_type']] \
                            [row['generation_subtype']]['ids'].append(int(id))
                        agg_geno_new[la_id][row['voltage_level']][row['generation_type']] \
                            [row['generation_subtype']]['capacity'] += row['electrical_capacity']

            # new generator is a single (non-aggregated) unit
            else:
                # check if geom is available
                geom = _check_geom(id, row)

                if row['generation_type'] in ['solar', 'wind']:
                    gen = GeneratorFluctuating(
                        id=id,
                        grid=None,
                        nominal_capacity=row['electrical_capacity'],
                        type=row['generation_type'],
                        subtype=row['generation_subtype'],
                        v_level=int(row['voltage_level']),
                        weather_cell_id=row['w_id'],
                        geom=wkt_loads(geom) if geom else geom)
                else:
                    gen = Generator(id=id,
                                    grid=None,
                                    nominal_capacity=row[
                                        'electrical_capacity'],
                                    type=row['generation_type'],
                                    subtype=row['generation_subtype'],
                                    v_level=int(row['voltage_level']),
                                    geom=wkt_loads(geom) if geom else geom)

                # TEMP: REMOVE MVLV SUBST ID FOR TESTING
                #row['mvlv_subst_id'] = None

                # check if MV-LV substation id exists. if not, allocate to
                # random one
                lv_grid = _check_mvlv_subst_id(
                    generator=gen,
                    mvlv_subst_id=row['mvlv_subst_id'],
                    lv_grid_dict=lv_grid_dict)

                gen.grid = lv_grid

                lv_grid.graph.add_node(gen, type='generator')

                log_geno_count += 1
            log_geno_cap += row['electrical_capacity']

        # there are new agg. generators to be created
        if agg_geno_new:

            pfac_mv_gen = network.config['reactive_power_factor']['mv_gen']

            # add aggregated generators
            for la_id, val in agg_geno_new.items():
                for v_level, val2 in val.items():
                    for type, val3 in val2.items():
                        for subtype, val4 in val3.items():
                            if type in ['solar', 'wind']:
                                gen = GeneratorFluctuating(
                                    id='agg-' + str(la_id) + '-' + '_'.join([
                                        str(_) for _ in val4['ids']]),
                                    grid=network.mv_grid,
                                    nominal_capacity=val4['capacity'],
                                    type=type,
                                    subtype=subtype,
                                    v_level=4,
                                    # ToDo: get correct w_id
                                    weather_cell_id=row['w_id'],
                                    geom=network.mv_grid.station.geom)
                            else:
                                gen = Generator(
                                    id='agg-' + str(la_id) + '-' + '_'.join([
                                        str(_) for _ in val4['ids']]),
                                    nominal_capacity=val4['capacity'],
                                    type=type,
                                    subtype=subtype,
                                    geom=network.mv_grid.station.geom,
                                    grid=network.mv_grid,
                                    v_level=4)

                            network.mv_grid.graph.add_node(
                                gen, type='generator_aggr')

                            # select cable type
                            line_type, line_count = select_cable(
                                network=network,
                                level='mv',
                                apparent_power=gen.nominal_capacity /
                                pfac_mv_gen)

                            # connect generator to MV station
                            line = Line(id='line_aggr_generator_la_' + str(la_id) + '_vlevel_{v_level}_'
                                        '{subtype}'.format(
                                         v_level=v_level,
                                         subtype=subtype),
                                        type=line_type,
                                        kind='cable',
                                        quantity=line_count,
                                        length=1e-3,
                                        grid=network.mv_grid)

                            network.mv_grid.graph.add_edge(network.mv_grid.station,
                                                           gen,
                                                           line=line,
                                                           type='line_aggr')

                            log_agg_geno_new_count += len(val4['ids'])
                            log_geno_cap += val4['capacity']

        logger.debug('{} of {} new generators added ({} single units, {} to existing '
                     'agg. generators and {} units as new aggregated generators) '
                     '(total: {} kW).'
                     .format(str(log_geno_count +
                                 log_agg_geno_new_count +
                                 log_agg_geno_upd_count),
                             str(len(generators_lv_new)),
                             str(log_geno_count),
                             str(log_agg_geno_upd_count),
                             str(log_agg_geno_new_count),
                             str(round(log_geno_cap, 1))
                             )
                     )

    def _check_geom(id, row):
        """Checks if a valid geom is available in dataset

        If yes, this geom will be used.
        If not:

            * MV generators: use geom from EnergyMap.
            * LV generators: set geom to None. It is re-set in
                :func:`edisgo.data.import_data._check_mvlv_subst_id`
                to MV-LV station's geom. EnergyMap's geom is not used
                since it is more inaccurate than the station's geom.

        Parameters
        ----------
        id : :obj:`int`
            Id of generator
        row : :pandas:`pandas.Series<series>`
            Generator dataset

        Returns
        -------
        :shapely:`Shapely Point object<points>` or None
            Geom of generator. None, if no geom is available.
        """

        geom = None

        # check if geom is available
        if row['geom']:
            geom = row['geom']
        else:
            # MV generators: set geom to EnergyMap's geom, if available
            if int(row['voltage_level']) in [4,5]:
                # check if original geom from Energy Map is available
                if row['geom_em']:
                    geom = row['geom_em']
                    logger.debug('Generator {} has no geom entry, EnergyMap\'s geom entry will be used.'
                                 .format(id)
                                 )

        return geom

    def _check_mvlv_subst_id(generator, mvlv_subst_id, lv_grid_dict):
        """Checks if MV-LV substation id of single LV generator is missing or invalid.
        If so, a random one from existing stations in LV grids will be assigned.

        Parameters
        ----------
        generator : :class:`~.grid.components.Generator`
            LV generator
        mvlv_subst_id : :obj:`int`
            MV-LV substation id
        lv_grid_dict : :obj:`dict`
            Dict of existing LV grids
            Format: {:obj:`int`: :class:`~.grid.grids.LVGrid`}

        Returns
        -------
        :class:`~.grid.grids.LVGrid`
            LV grid of generator
        """

        if mvlv_subst_id and not isnan(mvlv_subst_id):
            # assume that given LA exists
            try:
                # get LV grid
                lv_grid = lv_grid_dict[mvlv_subst_id]

                # if no geom, use geom of station
                if not generator.geom:
                    generator.geom = lv_grid.station.geom
                    logger.debug('Generator {} has no geom entry, stations\' geom will be used.'
                                 .format(generator.id)
                                 )

                return lv_grid

            # if LA/LVGD does not exist, choose random LVGD and move generator to station of LVGD
            # this occurs due to exclusion of LA with peak load < 1kW
            except:
                lv_grid = random.choice(list(lv_grid_dict.values()))

                generator.geom = lv_grid.station.geom

                logger.warning('Generator {} cannot be assigned to '
                               'non-existent LV Grid and was '
                               'allocated to a random LV Grid ({}); '
                               'geom was set to stations\' geom.'
                               .format(repr(generator),
                                       repr(lv_grid)))
                pass
                return lv_grid

        else:
            lv_grid = random.choice(list(lv_grid_dict.values()))

            generator.geom = lv_grid.station.geom

            logger.warning('Generator {} has no mvlv_subst_id and was '
                           'allocated to a random LV Grid ({}); '
                           'geom was set to stations\' geom.'
                           .format(repr(generator),
                                   repr(lv_grid)))
            pass
            return lv_grid

    def _validate_generation():
        """Validate generators in updated grids

        The validation uses the cumulative capacity of all generators.
        """
        # ToDo: Valdate conv. genos too!

        # set capacity difference threshold
        cap_diff_threshold = 10 ** -4

        capacity_imported = generators_res_mv['electrical_capacity'].sum() + \
                            generators_res_lv['electrical_capacity'].sum() #+ \
                            #generators_conv_mv['capacity'].sum()

        capacity_grid = 0
        # MV genos
        for geno in network.mv_grid.generators:
            capacity_grid += geno.nominal_capacity

        # LV genos
        for lv_grid in network.mv_grid.lv_grids:
            for geno in lv_grid.generators:
                capacity_grid += geno.nominal_capacity

        logger.debug('Cumulative generator capacity (updated): {} kW'
                     .format(str(round(capacity_imported, 1)))
                     )

        if abs(capacity_imported - capacity_grid) > cap_diff_threshold:
            raise ValueError('Cumulative capacity of imported generators ({} kW) '
                             'differ from cumulative capacity of generators '
                             'in updated grid ({} kW) by {} kW.'
                             .format(str(round(capacity_imported, 1)),
                                     str(round(capacity_grid, 1)),
                                     str(round(capacity_imported - capacity_grid, 1))
                                     )
                             )
        else:
            logger.debug('Cumulative capacity of imported generators validated.')

    def _validate_sample_geno_location():
        if all(generators_res_lv['geom'].notnull()) \
                and all(generators_res_mv['geom'].notnull()) \
                and not generators_res_lv['geom'].empty \
                and not generators_res_mv['geom'].empty:
            # get geom of 1 random MV and 1 random LV generator and transform
            sample_mv_geno_geom_shp = transform(proj2equidistant(network),
                                                wkt_loads(generators_res_mv['geom']
                                                          .dropna()
                                                          .sample(n=1)
                                                          .item())
                                                )
            sample_lv_geno_geom_shp = transform(proj2equidistant(network),
                                                wkt_loads(generators_res_lv['geom']
                                                          .dropna()
                                                          .sample(n=1)
                                                          .item())
                                                )

            # get geom of MV grid district
            mvgd_geom_shp = transform(proj2equidistant(network),
                                      network.mv_grid.grid_district['geom']
                                      )

            # check if MVGD contains geno
            if not (mvgd_geom_shp.contains(sample_mv_geno_geom_shp) and
                        mvgd_geom_shp.contains(sample_lv_geno_geom_shp)):
                raise ValueError('At least one imported generator is not located '
                                 'in the MV grid area. Check compatibility of '
                                 'grid and generator datasets.')

    # make DB session
    conn = connection(section=network.config['db_connection']['section'])
    Session = sessionmaker(bind=conn)
    session = Session()

    srid = int(network.config['geo']['srid'])

    oedb_data_source = network.config['data_source']['oedb_data_source']
    scenario = network.generator_scenario

    if oedb_data_source == 'model_draft':

        # load ORM names
        orm_conv_generators_name = network.config['model_draft']['conv_generators_prefix'] + \
                                   scenario + \
                                   network.config['model_draft']['conv_generators_suffix']
        orm_re_generators_name = network.config['model_draft']['re_generators_prefix'] + \
                                 scenario + \
                                 network.config['model_draft']['re_generators_suffix']

        # import ORMs
        orm_conv_generators = model_draft.__getattribute__(orm_conv_generators_name)
        orm_re_generators = model_draft.__getattribute__(orm_re_generators_name)

        # set dummy version condition (select all generators)
        orm_conv_generators_version = 1 == 1
        orm_re_generators_version = 1 == 1

    elif oedb_data_source == 'versioned':

        # load ORM names
        orm_conv_generators_name = network.config['versioned']['conv_generators_prefix'] + \
                                   scenario + \
                                   network.config['versioned']['conv_generators_suffix']
        orm_re_generators_name = network.config['versioned']['re_generators_prefix'] + \
                                 scenario + \
                                 network.config['versioned']['re_generators_suffix']
        data_version = network.config['versioned']['version']

        # import ORMs
        orm_conv_generators = supply.__getattribute__(orm_conv_generators_name)
        orm_re_generators = supply.__getattribute__(orm_re_generators_name)

        # set version condition
        orm_conv_generators_version = orm_conv_generators.columns.version == data_version
        orm_re_generators_version = orm_re_generators.columns.version == data_version

    # Create filter for generation technologies
    # ToDo: This needs to be removed when all generators can be imported
    # (all generators in a scenario should be imported)
    types_condition = orm_re_generators.columns.generation_type.in_(
        ['solar', 'wind'])

    # get conventional and renewable generators
    #generators_conv_mv = _import_conv_generators()
    generators_res_mv, generators_res_lv = _import_res_generators(
        types_condition)

    #generators_mv = generators_conv_mv.append(generators_res_mv)

    _validate_sample_geno_location()

    _update_grids(network=network,
                  #generators_mv=generators_mv,
                  generators_mv=generators_res_mv,
                  generators_lv=generators_res_lv)

    _validate_generation()

    connect_mv_generators(network=network)
    connect_lv_generators(network=network)


Instruction:
Write a Python 3 function that can
build DataFrames with all generators in MV and LV grids

Response:
def _build_generator_list(network):
    """Builds DataFrames with all generators in MV and LV grids

    Returns
    -------
    :pandas:`pandas.DataFrame<dataframe>`
            A DataFrame with id of and reference to MV generators
    :pandas:`pandas.DataFrame<dataframe>`
            A DataFrame with id of and reference to LV generators
    :pandas:`pandas.DataFrame<dataframe>`
            A DataFrame with id of and reference to aggregated LV generators
    """

    genos_mv = pd.DataFrame(columns=
                            ('id', 'obj'))
    genos_lv = pd.DataFrame(columns=
                            ('id', 'obj'))
    genos_lv_agg = pd.DataFrame(columns=
                                ('la_id', 'id', 'obj'))

    # MV genos
    for geno in network.mv_grid.graph.nodes_by_attribute('generator'):
            genos_mv.loc[len(genos_mv)] = [int(geno.id), geno]
    for geno in network.mv_grid.graph.nodes_by_attribute('generator_aggr'):
            la_id = int(geno.id.split('-')[1].split('_')[-1])
            genos_lv_agg.loc[len(genos_lv_agg)] = [la_id, geno.id, geno]

    # LV genos
    for lv_grid in network.mv_grid.lv_grids:
        for geno in lv_grid.generators:
            genos_lv.loc[len(genos_lv)] = [int(geno.id), geno]

    return genos_mv, genos_lv, genos_lv_agg


Instruction:
How would you implement a function in Python 3 that
builds a dict of LV grids containing LV grids of the specified network.

Response:
def _build_lv_grid_dict(network):
    """Creates dict of LV grids

    LV grid ids are used as keys, LV grid references as values.

    Parameters
    ----------
    network: :class:`~.grid.network.Network`
        The eDisGo container object

    Returns
    -------
    :obj:`dict`
        Format: {:obj:`int`: :class:`~.grid.grids.LVGrid`}
    """

    lv_grid_dict = {}
    for lv_grid in network.mv_grid.lv_grids:
        lv_grid_dict[lv_grid.id] = lv_grid
    return lv_grid_dict


Instruction:
How would you explain what the following Python 3 function does
def import_feedin_timeseries(config_data, weather_cell_ids):

    def _retrieve_timeseries_from_oedb(config_data, weather_cell_ids):
        """Retrieve time series from oedb

        Parameters
        ----------
        config_data : dict
            Dictionary containing config data from config files.
        weather_cell_ids : :obj:`list`
        List of weather cell id's (integers) to obtain feed-in data for.

        Returns
        -------
        :pandas:`pandas.DataFrame<dataframe>`
            Feedin time series
        """
        if config_data['data_source']['oedb_data_source'] == 'model_draft':
            orm_feedin_name = config_data['model_draft']['res_feedin_data']
            orm_feedin = model_draft.__getattribute__(orm_feedin_name)
            orm_feedin_version = 1 == 1
        else:
            orm_feedin_name = config_data['versioned']['res_feedin_data']
            orm_feedin = supply.__getattribute__(orm_feedin_name)
            orm_feedin_version = orm_feedin.version == config_data['versioned']['version']

        conn = connection(section=config_data['db_connection']['section'])
        Session = sessionmaker(bind=conn)
        session = Session()

        # ToDo: add option to retrieve subset of time series
        # ToDo: find the reference power class for mvgrid/w_id and insert instead of 4
        feedin_sqla = session.query(
            orm_feedin.w_id,
            orm_feedin.source,
            orm_feedin.feedin). \
            filter(orm_feedin.w_id.in_(weather_cell_ids)). \
            filter(orm_feedin.power_class.in_([0, 4])). \
            filter(orm_feedin_version)

        feedin = pd.read_sql_query(feedin_sqla.statement,
                                   session.bind,
                                   index_col=['source', 'w_id'])

        feedin.sort_index(axis=0, inplace=True)

        timeindex = pd.date_range('1/1/2011', periods=8760, freq='H')

        recasted_feedin_dict = {}
        for type_w_id in feedin.index:
            recasted_feedin_dict[type_w_id] = feedin.loc[
                                              type_w_id, :].values[0]

        feedin = pd.DataFrame(recasted_feedin_dict, index=timeindex)

        # rename 'wind_onshore' and 'wind_offshore' to 'wind'
        new_level = [_ if _ not in ['wind_onshore']
                     else 'wind' for _ in feedin.columns.levels[0]]
        feedin.columns.set_levels(new_level, level=0, inplace=True)

        feedin.columns.rename('type', level=0, inplace=True)
        feedin.columns.rename('weather_cell_id', level=1, inplace=True)

        return feedin

    feedin = _retrieve_timeseries_from_oedb(config_data, weather_cell_ids)

    return feedin

Response: Imports RES feed - in time series data and processes it.


Instruction:
Create a Python 3 function to
import load time series from oedb.

Response:
def import_load_timeseries(config_data, data_source, mv_grid_id=None,
                           year=None):
    """
    Import load time series

    Parameters
    ----------
    config_data : dict
        Dictionary containing config data from config files.
    data_source : str
        Specify type of data source. Available data sources are

         * 'demandlib'
            Determine a load time series with the use of the demandlib.
            This calculates standard load profiles for 4 different sectors.

    mv_grid_id : :obj:`str`
        MV grid ID as used in oedb. Provide this if `data_source` is 'oedb'.
        Default: None.
    year : int
        Year for which to generate load time series. Provide this if
        `data_source` is 'demandlib'. Default: None.

    Returns
    -------
    :pandas:`pandas.DataFrame<dataframe>`
        Load time series

    """

    def _import_load_timeseries_from_oedb(config_data, mv_grid_id):
        """
        Retrieve load time series from oedb

        Parameters
        ----------
        config_data : dict
            Dictionary containing config data from config files.

        Returns
        -------
        :pandas:`pandas.DataFrame<dataframe>`
            Load time series

        Notes
        ------
        This is currently not a valid option to retrieve load time series
        since time series in the oedb are not differentiated by sector. An
        issue concerning this has been created.

        """

        if config_data['versioned']['version'] == 'model_draft':
            orm_load_name = config_data['model_draft']['load_data']
            orm_load = model_draft.__getattribute__(orm_load_name)
            orm_load_areas_name = config_data['model_draft']['load_areas']
            orm_load_areas = model_draft.__getattribute__(orm_load_areas_name)
            orm_load_version = 1 == 1
        else:
            orm_load_name = config_data['versioned']['load_data']
            # orm_load = supply.__getattribute__(orm_load_name)
            # ToDo: remove workaround
            orm_load = model_draft.__getattribute__(orm_load_name)
            # orm_load_version = orm_load.version == config.data['versioned']['version']

            orm_load_areas_name = config_data['versioned']['load_areas']
            # orm_load_areas = supply.__getattribute__(orm_load_areas_name)
            # ToDo: remove workaround
            orm_load_areas = model_draft.__getattribute__(orm_load_areas_name)
            # orm_load_areas_version = orm_load.version == config.data['versioned']['version']

            orm_load_version = 1 == 1

        conn = connection(section=config_data['db_connection']['section'])
        Session = sessionmaker(bind=conn)
        session = Session()

        load_sqla = session.query(  # orm_load.id,
            orm_load.p_set,
            orm_load.q_set,
            orm_load_areas.subst_id). \
            join(orm_load_areas, orm_load.id == orm_load_areas.otg_id). \
            filter(orm_load_areas.subst_id == mv_grid_id). \
            filter(orm_load_version). \
            distinct()

        load = pd.read_sql_query(load_sqla.statement,
                                 session.bind,
                                 index_col='subst_id')
        return load

    def _load_timeseries_demandlib(config_data, year):
        """
        Get normalized sectoral load time series

        Time series are normalized to 1 kWh consumption per year

        Parameters
        ----------
        config_data : dict
            Dictionary containing config data from config files.
        year : int
            Year for which to generate load time series.

        Returns
        -------
        :pandas:`pandas.DataFrame<dataframe>`
            Load time series

        """

        sectoral_consumption = {'h0': 1, 'g0': 1, 'i0': 1, 'l0': 1}

        cal = Germany()
        holidays = dict(cal.holidays(year))

        e_slp = bdew.ElecSlp(year, holidays=holidays)

        # multiply given annual demand with timeseries
        elec_demand = e_slp.get_profile(sectoral_consumption)

        # Add the slp for the industrial group
        ilp = profiles.IndustrialLoadProfile(e_slp.date_time_index,
                                             holidays=holidays)

        # Beginning and end of workday, weekdays and weekend days, and scaling
        # factors by default
        elec_demand['i0'] = ilp.simple_profile(
            sectoral_consumption['i0'],
            am=datetime.time(config_data['demandlib']['day_start'].hour,
                             config_data['demandlib']['day_start'].minute, 0),
            pm=datetime.time(config_data['demandlib']['day_end'].hour,
                             config_data['demandlib']['day_end'].minute, 0),
            profile_factors=
            {'week': {'day': config_data['demandlib']['week_day'],
                      'night': config_data['demandlib']['week_night']},
             'weekend': {'day': config_data['demandlib']['weekend_day'],
                         'night': config_data['demandlib']['weekend_night']}})

        # Resample 15-minute values to hourly values and sum across sectors
        elec_demand = elec_demand.resample('H').mean()

        return elec_demand

    if data_source == 'oedb':
        load = _import_load_timeseries_from_oedb(config_data, mv_grid_id)
    elif data_source == 'demandlib':
        load = _load_timeseries_demandlib(config_data, year)
        load.rename(columns={'g0': 'retail', 'h0': 'residential',
                             'l0': 'agricultural', 'i0': 'industrial'},
                    inplace=True)
    return load


Instruction:
Write a Python 3 function for
formulating and solves linear problem to find linear relation between curtailment and node voltage. Parameters ------------ feedin : :pandas:`pandas.DataFrame<dataframe>` See `feedin` parameter in :func:`edisgo.flex_opt.curtailment.voltage_based` for more information. voltage_pu : :pandas:`pandas.DataFrame<dataframe> Dataframe containing voltages in p.u. at the generator nodes. Index of the dataframe is a :pandas:`pandas.DatetimeIndex<datetimeindex>`, columns are the generator representatives. total_curtailment : :pandas:`pandas.Series<series>` Series containing the specific curtailment in kW to be allocated to the generators. The index is a :pandas:`pandas.DatetimeIndex<datetimeindex>`. voltage_threshold : :pandas:`pandas.Series<series>` Series containing the voltage thresholds in p.u. below which no generator curtailment will occur. The index is a :pandas:`pandas.DatetimeIndex<datetimeindex>`. solver : :obj:`str` The solver used to optimize the linear problem. Default: 'cbc'. Returns ------- :pandas:`pandas:DataFrame<dataframe>` Dataframe containing the curtailment in kW per generator and time step feed-in was provided for in `feedin` parameter. Index is a :pandas:`pandas.DatetimeIndex<datetimeindex>`, columns are the generator representatives.

Response:
def _optimize_voltage_based_curtailment(feedin, voltage_pu, total_curtailment,
                                        voltage_threshold, timeindex, solver):
    """
    Formulates and solves linear problem to find linear relation between
    curtailment and node voltage.

    Parameters
    ------------
    feedin : :pandas:`pandas.DataFrame<dataframe>`
        See `feedin` parameter in
        :func:`edisgo.flex_opt.curtailment.voltage_based` for more information.
    voltage_pu : :pandas:`pandas.DataFrame<dataframe>
        Dataframe containing voltages in p.u. at the generator nodes. Index
        of the dataframe is a :pandas:`pandas.DatetimeIndex<datetimeindex>`,
        columns are the generator representatives.
    total_curtailment : :pandas:`pandas.Series<series>`
        Series containing the specific curtailment in kW to be allocated to the
        generators. The index is a
        :pandas:`pandas.DatetimeIndex<datetimeindex>`.
    voltage_threshold : :pandas:`pandas.Series<series>`
        Series containing the voltage thresholds in p.u. below which no
        generator curtailment will occur. The index is a
        :pandas:`pandas.DatetimeIndex<datetimeindex>`.
    solver : :obj:`str`
        The solver used to optimize the linear problem. Default: 'cbc'.

    Returns
    -------
    :pandas:`pandas:DataFrame<dataframe>`
        Dataframe containing the curtailment in kW per generator and time step
        feed-in was provided for in `feedin` parameter. Index is a
        :pandas:`pandas.DatetimeIndex<datetimeindex>`, columns are the
        generator representatives.

    """

    logging.debug("Start curtailment optimization.")

    v_max = voltage_pu.max(axis=1)
    generators = feedin.columns

    # additional curtailment factors
    cf_add = pd.DataFrame(index=timeindex)
    for gen in generators:
        cf_add[gen] = abs(
            (voltage_pu.loc[timeindex, gen] - v_max[timeindex]) / (
                    voltage_threshold[timeindex] - v_max[timeindex]))

    # curtailment factors
    cf = pd.DataFrame(index=timeindex)
    for gen in generators:
        cf[gen] = abs(
            (voltage_pu.loc[timeindex, gen] - voltage_threshold[timeindex]) / (
                    v_max[timeindex] - voltage_threshold[timeindex]))

    # initialize model
    model = ConcreteModel()

    # add sets
    model.T = Set(initialize=timeindex)
    model.G = Set(initialize=generators)

    # add parameters
    def feedin_init(model, t, g):
        return feedin.loc[t, g]

    model.feedin = Param(model.T, model.G, initialize=feedin_init)

    def voltage_pu_init(model, t, g):
        return voltage_pu.loc[t, g]

    model.voltage_pu = Param(model.T, model.G, initialize=voltage_pu_init)

    def cf_add_init(model, t, g):
        return cf_add.loc[t, g]

    model.cf_add = Param(model.T, model.G, initialize=cf_add_init)

    def cf_init(model, t, g):
        return cf.loc[t, g]

    model.cf = Param(model.T, model.G, initialize=cf_init)

    def total_curtailment_init(model, t):
        return total_curtailment.loc[t]

    model.total_curtailment = Param(model.T, initialize=total_curtailment_init)

    # add variables
    model.offset = Var(model.T, bounds=(0, 1))
    model.cf_max = Var(model.T, bounds=(0, 1))

    def curtailment_init(model, t, g):
        return (0, feedin.loc[t, g])

    model.c = Var(model.T, model.G, bounds=curtailment_init)

    # add objective
    def obj_rule(model):
        expr = (sum(model.offset[t] * 100
                    for t in model.T))
        return expr

    model.obj = Objective(rule=obj_rule, sense=minimize)

    # add constraints
    # curtailment per generator constraints
    def curtail(model, t, g):
        return (
        model.cf[t, g] * model.cf_max[t] * model.feedin[t, g] + model.cf_add[
            t, g] * model.offset[t] * model.feedin[t, g] - model.c[t, g] == 0)

    model.curtailment = Constraint(model.T, model.G, rule=curtail)

    # total curtailment constraint
    def total_curtailment(model, t):
        return (
        sum(model.c[t, g] for g in model.G) == model.total_curtailment[t])

    model.sum_curtailment = Constraint(model.T, rule=total_curtailment)

    # solve
    solver = SolverFactory(solver)
    results = solver.solve(model, tee=False)

    # load results back into model
    model.solutions.load_from(results)

    return pd.DataFrame({g: [model.c[t, g].value for t in model.T]
                         for g in model.G}, index=model.T.value)


Instruction:
Write a Python 3 function for
implementing curtailment methodology 'feedin-proportional'. The curtailment that has to be met in each time step is allocated equally to all generators depending on their share of total feed-in in that time step. Parameters ---------- feedin : :pandas:`pandas.DataFrame<dataframe>` Dataframe holding the feed-in of each generator in kW for the technology (and weather cell) specified in `curtailment_key` parameter. Index of the dataframe is a :pandas:`pandas.DatetimeIndex<datetimeindex>`. Columns are the representatives of the fluctuating generators. generators : :pandas:`pandas.DataFrame<dataframe>` Dataframe with all generators of the type (and in weather cell) specified in `curtailment_key` parameter. See return value of :func:`edisgo.grid.tools.get_gen_info` for more information. curtailment_timeseries : :pandas:`pandas.Series<series>` The curtailment in kW to be distributed amongst the generators in `generators` parameter. Index of the series is a :pandas:`pandas.DatetimeIndex<datetimeindex>`. edisgo : :class:`edisgo.grid.network.EDisGo` curtailment_key::obj:`str` or :obj:`tuple` with :obj:`str` The technology and weather cell ID if :obj:`tuple` or only the technology if :obj:`str` the curtailment is specified for.

Response:
def feedin_proportional(feedin, generators, curtailment_timeseries, edisgo,
                        curtailment_key, **kwargs):
    """
    Implements curtailment methodology 'feedin-proportional'.

    The curtailment that has to be met in each time step is allocated
    equally to all generators depending on their share of total
    feed-in in that time step.

    Parameters
    ----------
    feedin : :pandas:`pandas.DataFrame<dataframe>`
        Dataframe holding the feed-in of each generator in kW for the
        technology (and weather cell) specified in `curtailment_key` parameter.
        Index of the dataframe is a
        :pandas:`pandas.DatetimeIndex<datetimeindex>`. Columns are the
        representatives of the fluctuating generators.
    generators : :pandas:`pandas.DataFrame<dataframe>`
        Dataframe with all generators of the type (and in weather cell)
        specified in `curtailment_key` parameter. See return value of
        :func:`edisgo.grid.tools.get_gen_info` for more information.
    curtailment_timeseries : :pandas:`pandas.Series<series>`
        The curtailment in kW to be distributed amongst the generators in
        `generators` parameter. Index of the series is a
        :pandas:`pandas.DatetimeIndex<datetimeindex>`.
    edisgo : :class:`edisgo.grid.network.EDisGo`
    curtailment_key::obj:`str` or :obj:`tuple` with :obj:`str`
        The technology and weather cell ID if :obj:`tuple` or only
        the technology if :obj:`str` the curtailment is specified for.

    """
    # calculate curtailment in each time step of each generator
    curtailment = feedin.divide(feedin.sum(axis=1), axis=0). \
        multiply(curtailment_timeseries, axis=0)

    # substitute NaNs from division with 0 by 0
    curtailment.fillna(0, inplace=True)

    # check if curtailment target was met
    _check_curtailment_target(curtailment, curtailment_timeseries,
                              curtailment_key)

    # assign curtailment to individual generators
    _assign_curtailment(curtailment, edisgo, generators, curtailment_key)


Instruction:
How would you explain what the following Python 3 function does
def _check_curtailment_target(curtailment, curtailment_target,
                              curtailment_key):
    """
    Raises an error if curtailment target was not met in any time step.

    Parameters
    -----------
    curtailment : :pandas:`pandas:DataFrame<dataframe>`
        Dataframe containing the curtailment in kW per generator and time step.
        Index is a :pandas:`pandas.DatetimeIndex<datetimeindex>`, columns are
        the generator representatives.
    curtailment_target : :pandas:`pandas.Series<series>`
        The curtailment in kW that was to be distributed amongst the
        generators. Index of the series is a
        :pandas:`pandas.DatetimeIndex<datetimeindex>`.
    curtailment_key : :obj:`str` or :obj:`tuple` with :obj:`str`
        The technology and weather cell ID if :obj:`tuple` or only
        the technology if :obj:`str` the curtailment was specified for.

    """
    if not (abs(curtailment.sum(axis=1) - curtailment_target) < 1e-1).all():
        message = 'Curtailment target not met for {}.'.format(curtailment_key)
        logging.error(message)
        raise TypeError(message)

Response: Checks that the curtailment target is met in any time step.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _assign_curtailment(curtailment, edisgo, generators, curtailment_key):

    gen_object_list = []
    for gen in curtailment.columns:
        # get generator object from representative
        gen_object = generators.loc[generators.gen_repr == gen].index[0]
        # assign curtailment to individual generators
        gen_object.curtailment = curtailment.loc[:, gen]
        gen_object_list.append(gen_object)

    # set timeseries.curtailment
    if edisgo.network.timeseries._curtailment:
        edisgo.network.timeseries._curtailment.extend(gen_object_list)
        edisgo.network.results._curtailment[curtailment_key] = \
            gen_object_list
    else:
        edisgo.network.timeseries._curtailment = gen_object_list
        # list needs to be copied, otherwise it will be extended every time
        # a new key is added to results._curtailment
        edisgo.network.results._curtailment = \
            {curtailment_key: gen_object_list.copy()}

Response: Private function to assign curtailment to individual generators and time series objects.


Instruction:
Write a Python 3 function for
extending distribution substation overloading issues.

Response:
def extend_distribution_substation_overloading(network, critical_stations):
    """
    Reinforce MV/LV substations due to overloading issues.

    In a first step a parallel transformer of the same kind is installed.
    If this is not sufficient as many standard transformers as needed are
    installed.

    Parameters
    ----------
    network : :class:`~.grid.network.Network`
    critical_stations : :pandas:`pandas.DataFrame<dataframe>`
        Dataframe containing over-loaded MV/LV stations, their apparent power
        at maximal over-loading and the corresponding time step.
        Index of the dataframe are the over-loaded stations of type
        :class:`~.grid.components.LVStation`. Columns are 's_pfa'
        containing the apparent power at maximal over-loading as float and
        'time_index' containing the corresponding time step the over-loading
        occured in as :pandas:`pandas.Timestamp<timestamp>`. See
        :func:`~.flex_opt.check_tech_constraints.mv_lv_station_load` for more
        information.

    Returns
    -------
    dict
        Dictionary with lists of added and removed transformers.

    """

    # get parameters for standard transformer
    try:
        standard_transformer = network.equipment_data['lv_trafos'].loc[
            network.config['grid_expansion_standard_equipment'][
                'mv_lv_transformer']]
    except KeyError:
        print('Standard MV/LV transformer is not in equipment list.')

    transformers_changes = {'added': {}, 'removed': {}}
    for station in critical_stations.index:

        # list of maximum power of each transformer in the station
        s_max_per_trafo = [_.type.S_nom for _ in station.transformers]

        # maximum station load from power flow analysis
        s_station_pfa = critical_stations.s_pfa[station]

        # determine missing transformer power to solve overloading issue
        case = network.timeseries.timesteps_load_feedin_case.case[
            critical_stations.time_index[station]]
        load_factor = network.config['grid_expansion_load_factors'][
            'lv_{}_transformer'.format(case)]
        s_trafo_missing = s_station_pfa - (sum(s_max_per_trafo) * load_factor)

        # check if second transformer of the same kind is sufficient
        # if true install second transformer, otherwise install as many
        # standard transformers as needed
        if max(s_max_per_trafo) >= s_trafo_missing:
            # if station has more than one transformer install a new
            # transformer of the same kind as the transformer that best
            # meets the missing power demand
            duplicated_transformer = min(
                [_ for _ in station.transformers
                 if _.type.S_nom > s_trafo_missing],
                key=lambda j: j.type.S_nom - s_trafo_missing)

            new_transformer = Transformer(
                id='LVStation_{}_transformer_{}'.format(
                    str(station.id), str(len(station.transformers) + 1)),
                geom=duplicated_transformer.geom,
                mv_grid=duplicated_transformer.mv_grid,
                grid=duplicated_transformer.grid,
                voltage_op=duplicated_transformer.voltage_op,
                type=copy.deepcopy(duplicated_transformer.type))

            # add transformer to station and return value
            station.add_transformer(new_transformer)
            transformers_changes['added'][station] = [new_transformer]

        else:
            # get any transformer to get attributes for new transformer from
            station_transformer = station.transformers[0]

            # calculate how many parallel standard transformers are needed
            number_transformers = math.ceil(
                s_station_pfa / standard_transformer.S_nom)

            # add transformer to station
            new_transformers = []
            for i in range(number_transformers):
                new_transformer = Transformer(
                    id='LVStation_{}_transformer_{}'.format(
                        str(station.id), str(i + 1)),
                    geom=station_transformer.geom,
                    mv_grid=station_transformer.mv_grid,
                    grid=station_transformer.grid,
                    voltage_op=station_transformer.voltage_op,
                    type=copy.deepcopy(standard_transformer))
                new_transformers.append(new_transformer)
            transformers_changes['added'][station] = new_transformers
            transformers_changes['removed'][station] = station.transformers
            station.transformers = new_transformers
    return transformers_changes


Instruction:
Can you tell what is the following Python 3 function doing
def extend_distribution_substation_overvoltage(network, critical_stations):

    # get parameters for standard transformer
    try:
        standard_transformer = network.equipment_data['lv_trafos'].loc[
            network.config['grid_expansion_standard_equipment'][
                'mv_lv_transformer']]
    except KeyError:
        print('Standard MV/LV transformer is not in equipment list.')

    transformers_changes = {'added': {}}
    for grid in critical_stations.keys():

        # get any transformer to get attributes for new transformer from
        station_transformer = grid.station.transformers[0]

        new_transformer = Transformer(
            id='LVStation_{}_transformer_{}'.format(
                str(grid.station.id), str(len(grid.station.transformers) + 1)),
            geom=station_transformer.geom,
            mv_grid=station_transformer.mv_grid,
            grid=station_transformer.grid,
            voltage_op=station_transformer.voltage_op,
            type=copy.deepcopy(standard_transformer))

        # add standard transformer to station and return value
        grid.station.add_transformer(new_transformer)
        transformers_changes['added'][grid.station] = [new_transformer]

    if transformers_changes['added']:
        logger.debug("==> {} LV station(s) has/have been reinforced ".format(
            str(len(transformers_changes['added']))) +
                    "due to overloading issues.")

    return transformers_changes

Response: Extend distribution substation with standard transformers that is installed for over - voltage issues.


Instruction:
How would you explain what the following Python 3 function does
def reinforce_branches_overvoltage(network, grid, crit_nodes):

    # load standard line data
    if isinstance(grid, LVGrid):
        try:
            standard_line = network.equipment_data['lv_cables'].loc[
                network.config['grid_expansion_standard_equipment']['lv_line']]
        except KeyError:
            print('Chosen standard LV line is not in equipment list.')
    else:
        try:
            standard_line = network.equipment_data['mv_cables'].loc[
                network.config['grid_expansion_standard_equipment']['mv_line']]
        except KeyError:
            print('Chosen standard MV line is not in equipment list.')

    # find first nodes of every main line as representatives
    rep_main_line = list(
        nx.predecessor(grid.graph, grid.station, cutoff=1).keys())
    # list containing all representatives of main lines that have already been
    # reinforced
    main_line_reinforced = []

    lines_changes = {}
    for node in crit_nodes.index:
        path = nx.shortest_path(grid.graph, grid.station, node)
        # raise exception if voltage issue occurs at station's secondary side
        # because voltage issues should have been solved during extension of
        # distribution substations due to overvoltage issues.
        if len(path) == 1:
            logging.error("Voltage issues at busbar in LV grid {} should have "
                          "been solved in previous steps.".format(grid))
        else:
            # check if representative of line is already in list
            # main_line_reinforced; if it is, the main line the critical node
            # is connected to has already been reinforced in this iteration
            # step
            if not path[1] in main_line_reinforced:

                main_line_reinforced.append(path[1])
                # get path length from station to critical node
                get_weight = lambda u, v, data: data['line'].length
                path_length = dijkstra_shortest_path_length(
                    grid.graph, grid.station, get_weight, target=node)
                # find first node in path that exceeds 2/3 of the line length
                # from station to critical node farthest away from the station
                node_2_3 = next(j for j in path if
                                path_length[j] >= path_length[node] * 2 / 3)

                # if LVGrid: check if node_2_3 is outside of a house
                # and if not find next BranchTee outside the house
                if isinstance(grid, LVGrid):
                    if isinstance(node_2_3, BranchTee):
                        if node_2_3.in_building:
                            # ToDo more generic (new function)
                            try:
                                node_2_3 = path[path.index(node_2_3) - 1]
                            except IndexError:
                                print('BranchTee outside of building is not ' +
                                      'in path.')
                    elif (isinstance(node_2_3, Generator) or
                              isinstance(node_2_3, Load)):
                        pred_node = path[path.index(node_2_3) - 1]
                        if isinstance(pred_node, BranchTee):
                            if pred_node.in_building:
                                # ToDo more generic (new function)
                                try:
                                    node_2_3 = path[path.index(node_2_3) - 2]
                                except IndexError:
                                    print('BranchTee outside of building is ' +
                                          'not in path.')
                    else:
                        logging.error("Not implemented for {}.".format(
                            str(type(node_2_3))))
                # if MVGrid: check if node_2_3 is LV station and if not find
                # next LV station
                else:
                    if not isinstance(node_2_3, LVStation):
                        next_index = path.index(node_2_3) + 1
                        try:
                            # try to find LVStation behind node_2_3
                            while not isinstance(node_2_3, LVStation):
                                node_2_3 = path[next_index]
                                next_index += 1
                        except IndexError:
                            # if no LVStation between node_2_3 and node with
                            # voltage problem, connect node directly to
                            # MVStation
                            node_2_3 = node

                # if node_2_3 is a representative (meaning it is already
                # directly connected to the station), line cannot be
                # disconnected and must therefore be reinforced
                if node_2_3 in rep_main_line:
                    crit_line = grid.graph.get_edge_data(
                        grid.station, node_2_3)['line']

                    # if critical line is already a standard line install one
                    # more parallel line
                    if crit_line.type.name == standard_line.name:
                        crit_line.quantity += 1
                        lines_changes[crit_line] = 1

                    # if critical line is not yet a standard line replace old
                    # line by a standard line
                    else:
                        # number of parallel standard lines could be calculated
                        # following [2] p.103; for now number of parallel
                        # standard lines is iterated
                        crit_line.type = standard_line.copy()
                        crit_line.quantity = 1
                        crit_line.kind = 'cable'
                        lines_changes[crit_line] = 1

                # if node_2_3 is not a representative, disconnect line
                else:
                    # get line between node_2_3 and predecessor node (that is
                    # closer to the station)
                    pred_node = path[path.index(node_2_3) - 1]
                    crit_line = grid.graph.get_edge_data(
                        node_2_3, pred_node)['line']
                    # add new edge between node_2_3 and station
                    new_line_data = {'line': crit_line,
                                     'type': 'line'}
                    grid.graph.add_edge(grid.station, node_2_3, new_line_data)
                    # remove old edge
                    grid.graph.remove_edge(pred_node, node_2_3)
                    # change line length and type
                    crit_line.length = path_length[node_2_3]
                    crit_line.type = standard_line.copy()
                    crit_line.kind = 'cable'
                    crit_line.quantity = 1
                    lines_changes[crit_line] = 1
                    # add node_2_3 to representatives list to not further
                    # reinforce this part off the grid in this iteration step
                    rep_main_line.append(node_2_3)
                    main_line_reinforced.append(node_2_3)

            else:
                logger.debug(
                    '==> Main line of node {} in grid {} '.format(
                        repr(node), str(grid)) +
                    'has already been reinforced.')

    if main_line_reinforced:
        logger.debug('==> {} branche(s) was/were reinforced '.format(
            str(len(lines_changes))) + 'due to over-voltage issues.')

    return lines_changes

Response: Reinforce MV and LV grids with over - voltage issues.


Instruction:
Explain what the following Python 3 code does
def reinforce_branches_overloading(network, crit_lines):

    # load standard line data
    try:
        standard_line_lv = network.equipment_data['lv_cables'].loc[
            network.config['grid_expansion_standard_equipment']['lv_line']]
    except KeyError:
        print('Chosen standard LV line is not in equipment list.')
    try:
        standard_line_mv = network.equipment_data['mv_cables'].loc[
            network.config['grid_expansion_standard_equipment']['mv_line']]
    except KeyError:
        print('Chosen standard MV line is not in equipment list.')

    lines_changes = {}
    for crit_line in crit_lines.index:
        rel_overload = crit_lines.max_rel_overload[crit_line]
        # check if line is in LV or MV and set standard line accordingly
        if isinstance(crit_line.grid, LVGrid):
            standard_line = standard_line_lv
        else:
            standard_line = standard_line_mv

        if crit_line.type.name == standard_line.name:
            # check how many parallel standard lines are needed
            number_parallel_lines = math.ceil(
                rel_overload * crit_line.quantity)
            lines_changes[crit_line] = (number_parallel_lines -
                                        crit_line.quantity)
            crit_line.quantity = number_parallel_lines
        else:
            # check if parallel line of the same kind is sufficient
            if (crit_line.quantity == 1 and rel_overload <= 2
                    and crit_line.kind == 'cable'):
                crit_line.quantity = 2
                lines_changes[crit_line] = 1
            else:
                number_parallel_lines = math.ceil(
                    crit_line.type['I_max_th'] * rel_overload /
                    standard_line['I_max_th'])
                lines_changes[crit_line] = number_parallel_lines
                crit_line.type = standard_line.copy()
                crit_line.quantity = number_parallel_lines
                crit_line.kind = 'cable'

    if not crit_lines.empty:
        logger.debug('==> {} branche(s) was/were reinforced '.format(
            crit_lines.shape[0]) + 'due to over-loading issues.')

    return lines_changes

Response: Reinforce MV or LV grid due to overloading.


Instruction:
Can you implement a function in Python 3 that
functions to create histogram for a given set of data.

Response:
def histogram(data, **kwargs):
    """
    Function to create histogram, e.g. for voltages or currents.

    Parameters
    ----------
    data : :pandas:`pandas.DataFrame<dataframe>`
        Data to be plotted, e.g. voltage or current (`v_res` or `i_res` from
        :class:`edisgo.grid.network.Results`). Index of the dataframe must be
        a :pandas:`pandas.DatetimeIndex<datetimeindex>`.
    timeindex : :pandas:`pandas.Timestamp<timestamp>` or None, optional
        Specifies time step histogram is plotted for. If timeindex is None all
        time steps provided in dataframe are used. Default: None.
    directory : :obj:`str` or None, optional
        Path to directory the plot is saved to. Is created if it does not
        exist. Default: None.
    filename : :obj:`str` or None, optional
        Filename the plot is saved as. File format is specified by ending. If
        filename is None, the plot is shown. Default: None.
    color : :obj:`str` or None, optional
        Color used in plot. If None it defaults to blue. Default: None.
    alpha : :obj:`float`, optional
        Transparency of the plot. Must be a number between 0 and 1,
        where 0 is see through and 1 is opaque. Default: 1.
    title : :obj:`str` or None, optional
        Plot title. Default: None.
    x_label : :obj:`str`, optional
        Label for x-axis. Default: "".
    y_label : :obj:`str`, optional
        Label for y-axis. Default: "".
    normed : :obj:`bool`, optional
        Defines if histogram is normed. Default: False.
    x_limits : :obj:`tuple` or None, optional
        Tuple with x-axis limits. First entry is the minimum and second entry
        the maximum value. Default: None.
    y_limits : :obj:`tuple` or None, optional
        Tuple with y-axis limits. First entry is the minimum and second entry
        the maximum value. Default: None.
    fig_size : :obj:`str` or :obj:`tuple`, optional
        Size of the figure in inches or a string with the following options:
         * 'a4portrait'
         * 'a4landscape'
         * 'a5portrait'
         * 'a5landscape'

         Default: 'a5landscape'.
    binwidth : :obj:`float`
        Width of bins. Default: None.

    """
    timeindex = kwargs.get('timeindex', None)
    directory = kwargs.get('directory', None)
    filename = kwargs.get('filename', None)
    title = kwargs.get('title', "")
    x_label = kwargs.get('x_label', "")
    y_label = kwargs.get('y_label', "")

    color = kwargs.get('color', None)
    alpha = kwargs.get('alpha', 1)
    normed = kwargs.get('normed', False)

    x_limits = kwargs.get('x_limits', None)
    y_limits = kwargs.get('y_limits', None)
    binwidth = kwargs.get('binwidth', None)

    fig_size = kwargs.get('fig_size', 'a5landscape')
    standard_sizes = {'a4portrait': (8.27, 11.69),
                      'a4landscape': (11.69, 8.27),
                      'a5portrait': (5.8, 8.3),
                      'a5landscape': (8.3, 5.8)}
    try:
        fig_size = standard_sizes[fig_size]
    except:
        fig_size = standard_sizes['a5landscape']

    if timeindex is not None:
        plot_data = data.loc[timeindex, :]
    else:
        plot_data = data.T.stack()

    if binwidth is not None:
        if x_limits is not None:
            lowerlimit = x_limits[0] - binwidth / 2
            upperlimit = x_limits[1] + binwidth / 2
        else:
            lowerlimit = plot_data.min() - binwidth / 2
            upperlimit = plot_data.max() + binwidth / 2
        bins = np.arange(lowerlimit, upperlimit, binwidth)
    else:
        bins = 10

    plt.figure(figsize=fig_size)
    ax = plot_data.hist(
        normed=normed, color=color, alpha=alpha, bins=bins, grid=True)
    plt.minorticks_on()

    if x_limits is not None:
        ax.set_xlim(x_limits[0], x_limits[1])
    if y_limits is not None:
        ax.set_ylim(y_limits[0], y_limits[1])
    if title is not None:
        plt.title(title)
    plt.xlabel(x_label)
    plt.ylabel(y_label)

    if filename is None:
        plt.show()
    else:
        if directory is not None:
            os.makedirs(directory, exist_ok=True)
            filename = os.path.join(directory, filename)
        plt.savefig(filename)
        plt.close()


Instruction:
Implement a function in Python 3 to
add basemap to a plot.

Response:
def add_basemap(ax, zoom=12):
    """
    Adds map to a plot.

    """
    url = ctx.sources.ST_TONER_LITE
    xmin, xmax, ymin, ymax = ax.axis()
    basemap, extent = ctx.bounds2img(xmin, ymin, xmax, ymax,
                                            zoom=zoom, url=url)
    ax.imshow(basemap, extent=extent, interpolation='bilinear')
    # restore original x/y limits
    ax.axis((xmin, xmax, ymin, ymax))


Instruction:
Write a Python 3 function for
getting MV grid district polygon from oedb for plotting.

Response:
def get_grid_district_polygon(config, subst_id=None, projection=4326):
    """
    Get MV grid district polygon from oedb for plotting.

    """

    # make DB session
    conn = connection(section=config['db_connection']['section'])
    Session = sessionmaker(bind=conn)
    session = Session()

    # get polygon from versioned schema
    if config['data_source']['oedb_data_source'] == 'versioned':

        version = config['versioned']['version']
        query = session.query(EgoDpMvGriddistrict.subst_id,
                              EgoDpMvGriddistrict.geom)
        Regions = [(subst_id, shape.to_shape(geom)) for subst_id, geom in
                   query.filter(EgoDpMvGriddistrict.version == version,
                                EgoDpMvGriddistrict.subst_id == subst_id).all()
                   ]

    # get polygon from model_draft
    else:
        query = session.query(EgoGridMvGriddistrict.subst_id,
                              EgoGridMvGriddistrict.geom)
        Regions = [(subst_id, shape.to_shape(geom)) for subst_id, geom in
                   query.filter(EgoGridMvGriddistrict.subst_id.in_(
                       subst_id)).all()]

    crs = {'init': 'epsg:3035'}
    region = gpd.GeoDataFrame(
        Regions, columns=['subst_id', 'geometry'], crs=crs)
    region = region.to_crs(epsg=projection)

    return region