Instruction:
Here you have a function in Python 3, explain what it does
def metadata(self, run_id=None):

        # Get latest version and/or git commit hash
        try:
            version = subprocess.check_output(
                ["git", "describe", "--tags", "--always"]).decode('utf8')
        except:
            version = None

        # Collect names of database table used to run Ding0 and data version
        if self.config['input_data_source']['input_data'] == 'versioned':
            data_version = self.config['versioned']['version']
            database_tables = self.config['versioned']
        elif self.config['input_data_source']['input_data'] == 'model_draft':
            data_version = 'model_draft'
            database_tables = self.config['model_draft']
        else:
            data_version = 'unknown'
            database_tables = 'unknown'

        # Collect assumptions
        assumptions = {}
        assumptions.update(self.config['assumptions'])
        assumptions.update(self.config['mv_connect'])
        assumptions.update(self.config['mv_routing'])
        assumptions.update(self.config['mv_routing_tech_constraints'])

        # Determine run_id if not set
        if not run_id:
            run_id = datetime.now().strftime("%Y%m%d%H%M%S")

        # Set instance attribute run_id
        if not self._run_id:
            self._run_id = run_id

        # Assing data to dict
        metadata = dict(
            version=version,
            mv_grid_districts=[int(_.id_db) for _ in self._mv_grid_districts],
            database_tables=database_tables,
            data_version=data_version,
            assumptions=assumptions,
            run_id=self._run_id
        )

        return metadata

Response: Provide metadata on a Ding0 run.


Instruction:
Write a Python 3 script for
listing all generators in the database.

Response:
def list_generators(self, session):
        """
        List renewable (res) and conventional (conv) generators

        Args
        ----
        session : sqlalchemy.orm.session.Session
            Database session

        Returns
        -------
        DataFrame
        """
        srid = str(int(cfg_ding0.get('geo', 'srid')))

        # build dicts to map MV grid district and Load Area ids to related objects
        mv_grid_districts_dict,\
        lv_load_areas_dict,\
        lv_grid_districts_dict,\
        lv_stations_dict = self.get_mvgd_lvla_lvgd_obj_from_id()

        # import renewable generators
        # build query
        generators_sqla = session.query(
                self.orm['orm_re_generators'].columns.id,
                self.orm['orm_re_generators'].columns.subst_id,
                self.orm['orm_re_generators'].columns.la_id,
                self.orm['orm_re_generators'].columns.mvlv_subst_id,
                self.orm['orm_re_generators'].columns.electrical_capacity,
                self.orm['orm_re_generators'].columns.generation_type,
                self.orm['orm_re_generators'].columns.generation_subtype,
                self.orm['orm_re_generators'].columns.voltage_level,
                func.ST_AsText(func.ST_Transform(
                   self.orm['orm_re_generators'].columns.rea_geom_new, srid)).label('geom_new'),
                func.ST_AsText(func.ST_Transform(
                   self.orm['orm_re_generators'].columns.geom, srid)).label('geom')
                ).filter(
                self.orm['orm_re_generators'].columns.subst_id.in_(list(mv_grid_districts_dict))). \
                filter(self.orm['orm_re_generators'].columns.voltage_level.in_([4, 5, 6, 7])). \
                filter(self.orm['version_condition_re'])

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

        generators_res.columns = ['GenCap' if c=='electrical_capacity' else
                                  'type' if c=='generation_type' else
                                  'subtype' if c=='generation_subtype' else
                                  'v_level' if c=='voltage_level' else
                                  c for c in generators_res.columns]
        ###########################
        # Imports conventional (conv) generators
        # build query
        generators_sqla = session.query(
                self.orm['orm_conv_generators'].columns.id,
                self.orm['orm_conv_generators'].columns.subst_id,
                self.orm['orm_conv_generators'].columns.name,
                self.orm['orm_conv_generators'].columns.capacity,
                self.orm['orm_conv_generators'].columns.fuel,
                self.orm['orm_conv_generators'].columns.voltage_level,
                func.ST_AsText(func.ST_Transform(
                   self.orm['orm_conv_generators'].columns.geom, srid)).label('geom')
                ).filter(
                self.orm['orm_conv_generators'].columns.subst_id.in_(list(mv_grid_districts_dict))). \
                filter(self.orm['orm_conv_generators'].columns.voltage_level.in_([4, 5, 6])). \
                filter(self.orm['version_condition_conv'])

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

        generators_conv.columns = ['GenCap' if c=='capacity' else
                                   'type' if c=='fuel' else
                                   'v_level' if c=='voltage_level' else
                                   c for c in generators_conv.columns]
        ###########################
        generators = pd.concat([generators_conv, generators_res], axis=0)
        generators = generators.fillna('other')
        return generators


Instruction:
Write a Python 3 script to
list load areas peak load from database for a single MV grid_district

Response:
def list_load_areas(self, session, mv_districts):
        """list load_areas (load areas) peak load from database for a single MV grid_district

        Parameters
        ----------
        session : sqlalchemy.orm.session.Session
            Database session
        mv_districts:
            List of MV districts
        """

        # threshold: load area peak load, if peak load < threshold => disregard
        # load area
        lv_loads_threshold = cfg_ding0.get('mv_routing', 'load_area_threshold')
        #lv_loads_threshold = 0

        gw2kw = 10 ** 6  # load in database is in GW -> scale to kW

        #filter list for only desired MV districts
        stations_list = [d.mv_grid._station.id_db for d in mv_districts]

        # build SQL query
        lv_load_areas_sqla = session.query(
            self.orm['orm_lv_load_areas'].id.label('id_db'),
            (self.orm['orm_lv_load_areas'].sector_peakload_residential * gw2kw).\
                label('peak_load_residential'),
            (self.orm['orm_lv_load_areas'].sector_peakload_retail * gw2kw).\
                label('peak_load_retail'),
            (self.orm['orm_lv_load_areas'].sector_peakload_industrial * gw2kw).\
                label('peak_load_industrial'),
            (self.orm['orm_lv_load_areas'].sector_peakload_agricultural * gw2kw).\
                label('peak_load_agricultural'),
            #self.orm['orm_lv_load_areas'].subst_id
            ). \
            filter(self.orm['orm_lv_load_areas'].subst_id.in_(stations_list)).\
            filter(((self.orm['orm_lv_load_areas'].sector_peakload_residential  # only pick load areas with peak load > lv_loads_threshold
                     + self.orm['orm_lv_load_areas'].sector_peakload_retail
                     + self.orm['orm_lv_load_areas'].sector_peakload_industrial
                     + self.orm['orm_lv_load_areas'].sector_peakload_agricultural)
                       * gw2kw) > lv_loads_threshold). \
            filter(self.orm['version_condition_la'])

        # read data from db
        lv_load_areas = pd.read_sql_query(lv_load_areas_sqla.statement,
                                          session.bind,
                                          index_col='id_db')

        return lv_load_areas


Instruction:
Can you write a function in Python 3 where it
imports all LV grid districts within given LV stations.

Response:
def list_lv_grid_districts(self, session, lv_stations):
        """Imports all lv grid districts within given load area

        Parameters
        ----------
        session : sqlalchemy.orm.session.Session
            Database session
        lv_stations:
            List required LV_stations==LV districts.

        Returns
        -------
        pandas Dataframe
            Table of lv_grid_districts
        """
        gw2kw = 10 ** 6  # load in database is in GW -> scale to kW

        # 1. filter grid districts of relevant load area
        lv_grid_districs_sqla = session.query(
            self.orm['orm_lv_grid_district'].mvlv_subst_id,
            (self.orm[
                 'orm_lv_grid_district'].sector_peakload_residential * gw2kw).
                label('peak_load_residential'),
            (self.orm['orm_lv_grid_district'].sector_peakload_retail * gw2kw).
                label('peak_load_retail'),
            (self.orm[
                 'orm_lv_grid_district'].sector_peakload_industrial * gw2kw).
                label('peak_load_industrial'),
            (self.orm[
                 'orm_lv_grid_district'].sector_peakload_agricultural * gw2kw).
                label('peak_load_agricultural'),
            ). \
            filter(self.orm['orm_lv_grid_district'].mvlv_subst_id.in_(
            lv_stations)). \
            filter(self.orm['version_condition_lvgd'])

        # read data from db
        lv_grid_districts = pd.read_sql_query(lv_grid_districs_sqla.statement,
                                              session.bind,
                                              index_col='mvlv_subst_id')

        return lv_grid_districts


Instruction:
Can you tell what is the following Python 3 function doing
def create_dir(dirpath):

    if not os.path.isdir(dirpath):
        os.mkdir(dirpath)

        print("We create a directory for you and your Ding0 data: {}".format(
            dirpath))

Response: Create a directory and report about it


Instruction:
Given the following Python 3 function, write the documentation
def get_default_home_dir():
    ding0_dir = str(cfg_ding0.get('config',
                                  'config_dir'))
    return os.path.join(os.path.expanduser('~'), ding0_dir)

Response: Returns default home directory of Ding0


Instruction:
Given the following Python 3 function, write the documentation
def setup_logger(log_dir=None, loglevel=logging.DEBUG):

    create_home_dir()
    create_dir(os.path.join(get_default_home_dir(), 'log'))

    if log_dir is None:
        log_dir = os.path.join(get_default_home_dir(), 'log')

    logger = logging.getLogger('ding0') # use filename as name in log
    logger.setLevel(loglevel)

    # create a file handler
    handler = logging.FileHandler(os.path.join(log_dir, 'ding0.log'))
    handler.setLevel(logging.DEBUG)
    formatter = logging.Formatter(
        '%(asctime)s-%(funcName)s-%(message)s (%(levelname)s)')
    handler.setFormatter(formatter)

    # create a stream handler (print to prompt)
    stream = logging.StreamHandler()
    stream.setLevel(logging.INFO)
    stream_formatter = logging.Formatter(
        '%(message)s (%(levelname)s)')
    stream.setFormatter(stream_formatter)

    # add the handlers to the logger
    logger.addHandler(handler)
    logger.addHandler(stream)

    logger.info('########## New run of Ding0 issued #############')

    return logger

Response: Create a logger object and return it


Instruction:
How would you code a function in Python 3 to
return a deep copy of the instance

Response:
def clone(self):
        """Returns a deep copy of self

        Function clones:

        * routes
        * allocation
        * nodes

        Returns
        -------
        SavingsSolution
            A clone (deepcopy) of the instance itself
        """

        new_solution = self.__class__(self._problem)

        # Clone routes
        for index, r in enumerate(self._routes):
            new_route = new_solution._routes[index] = models.Route(self._problem)
            for node in r.nodes():
                # Insert new node on new route
                new_node = new_solution._nodes[node.name()]
                new_route.allocate([new_node])

        # remove empty routes from new solution
        new_solution._routes = [route for route in new_solution._routes if route._nodes]

        return new_solution


Instruction:
How would you explain what the following Python 3 function does
def is_complete(self):
        allocated = all(
            [node.route_allocation() is not None for node in list(self._nodes.values()) if node.name() != self._problem.depot().name()]
        )

        valid_routes = len(self._routes) == 1 #workaround: try to use only one route (otherwise process will stop if no of vehicles is reached)

        return allocated and valid_routes

Response: Returns True if this solution is a complete solution i. e. all nodes are allocated and all routes are valid


Instruction:
Make a summary of the following Python 3 code
def process(self, pair):
        # TODO: check docstring
        """Processes a pair of nodes into the current solution

        MUST CREATE A NEW INSTANCE, NOT CHANGE ANY INSTANCE ATTRIBUTES

        Returns a new instance (deep copy) of self object

        Args
        ----
        pair : type
            description

        Returns
        -------
        type
            Description (Copy of self?)
        """
        a, b = pair

        new_solution = self.clone()

        i, j = new_solution.get_pair((a, b))

        route_i = i.route_allocation()
        route_j = j.route_allocation()

        inserted = False

        if ((route_i is not None and route_j is not None) and (route_i != route_j)):
            if route_i._nodes.index(i) == 0 and route_j._nodes.index(j) == len(route_j._nodes) - 1:
                if route_j.can_allocate(route_i._nodes):
                    route_j.allocate(route_i._nodes)

                    if i.route_allocation() != j.route_allocation():
                        raise Exception('wtf')

                    inserted = True
            elif route_j._nodes.index(j) == 0 and route_i._nodes.index(i) == len(route_i._nodes) - 1:
                if route_i.can_allocate(route_j._nodes):
                    route_i.allocate(route_j._nodes)

                    if i.route_allocation() != j.route_allocation():
                        raise Exception('wtf j')

                    inserted = True

        new_solution._routes = [route for route in new_solution._routes if route._nodes]

        return new_solution, inserted

Response: Processes a pair of nodes into the current solution


Instruction:
Here you have a function in Python 3, explain what it does
def can_process(self, pairs):
        i, j = pairs

        # Neither points are in a route
        if i.route_allocation() is None or j.route_allocation() is None:
            return True

        if self._allocated == len(list(self._problem.nodes())) - 1: # All nodes in a route
            return False

        return False

Response: Returns True if this solution can process pairs.


Instruction:
Can you generate the documentation for the following Python 3 function
def compute_savings_list(self, graph):

        savings_list = {}

        for i, j in graph.edges():
            # t = (i, j)
            if repr(i) < repr(j):
                t = (i, j)
            else:
                t = (j, i)

            if i == graph.depot() or j == graph.depot():
                continue

            savings_list[t] = graph.distance(graph.depot(), i) + graph.distance(graph.depot(), j) - graph.distance(i, j)

        sorted_savings_list = sorted(list(savings_list.items()), key=operator.itemgetter(1), reverse=True)

        return [nodes for nodes, saving in sorted_savings_list]

Response: Compute Clarke and Wright savings list


Instruction:
Here you have a function in Python 3, explain what it does
def solve(self, graph, timeout, debug=False, anim=None):

        savings_list = self.compute_savings_list(graph)

        solution = SavingsSolution(graph)

        start = time.time()

        for i, j in savings_list[:]:
            if solution.is_complete():
                break

            if solution.can_process((i, j)):
                solution, inserted = solution.process((i, j))

                if inserted:
                    savings_list.remove((i, j))

                    if anim:
                        solution.draw_network(anim)

            if time.time() - start > timeout:
                break

        return solution

Response: Solves the CVRP problem using Clarke and Wright Savings methods.


Instruction:
Explain what the following Python 3 code does
def export_to_dir(network, export_dir):

    package_path = ding0.__path__[0]

    network.export_to_csv_folder(os.path.join(package_path,
                                              'output',
                                              'debug',
                                              'grid',
                                              export_dir))

Response: Exports PyPSA network as CSV files to directory export_dir


Instruction:
Create a Python 3 function for
returning a dictionary of dataframes containing the given list of nodes.

Response:
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


Instruction:
Implement a Python 3 function for
exporting edges to dict of dataframes.

Response:
def edges_to_dict_of_dataframes(grid, edges):
    """
    Export edges to DataFrame

    Parameters
    ----------
    grid: ding0.Network
    edges: list
        Edges of Ding0.Network graph

    Returns
    -------
    edges_dict: dict
    """
    omega = 2 * pi * 50
    srid = int(cfg_ding0.get('geo', 'srid'))

    lines = {'line_id': [], 'bus0': [], 'bus1': [], 'x': [], 'r': [],
             's_nom': [], 'length': [], 'cables': [], 'geom': [],
             'grid_id': []}

    # iterate over edges and add them one by one
    for edge in edges:

        line_name = '_'.join(['MV',
                              str(grid.id_db),
                              'lin',
                              str(edge['branch'].id_db)])

        # TODO: find the real cause for being L, C, I_th_max type of Series
        if (isinstance(edge['branch'].type['L'], Series) or
                isinstance(edge['branch'].type['C'], Series)):
            x = omega * edge['branch'].type['L'].values[0] * 1e-3
        else:

            x = omega * edge['branch'].type['L'] * 1e-3

        if isinstance(edge['branch'].type['R'], Series):
            r = edge['branch'].type['R'].values[0]
        else:
            r = edge['branch'].type['R']

        if (isinstance(edge['branch'].type['I_max_th'], Series) or
                isinstance(edge['branch'].type['U_n'], Series)):
            s_nom = sqrt(3) * edge['branch'].type['I_max_th'].values[0] * \
                    edge['branch'].type['U_n'].values[0]
        else:
            s_nom = sqrt(3) * edge['branch'].type['I_max_th'] * \
                    edge['branch'].type['U_n']

        # get lengths of line
        l = edge['branch'].length / 1e3

        lines['line_id'].append(line_name)
        lines['bus0'].append(edge['adj_nodes'][0].pypsa_id)
        lines['bus1'].append(edge['adj_nodes'][1].pypsa_id)
        lines['x'].append(x * l)
        lines['r'].append(r * l)
        lines['s_nom'].append(s_nom)
        lines['length'].append(l)
        lines['cables'].append(3)
        lines['geom'].append(from_shape(
            LineString([edge['adj_nodes'][0].geo_data,
                        edge['adj_nodes'][1].geo_data]),
            srid=srid))
        lines['grid_id'].append(grid.id_db)

    return {'Line': DataFrame(lines).set_index('line_id')}


Instruction:
Write a Python 3 function that can
run powerflow test for a single set of components.

Response:
def run_powerflow_onthefly(components, components_data, grid, export_pypsa_dir=None, debug=False):
    """
    Run powerflow to test grid stability

    Two cases are defined to be tested here:
     i) load case
     ii) feed-in case

    Parameters
    ----------
    components: dict of pandas.DataFrame
    components_data: dict of pandas.DataFrame
    export_pypsa_dir: str
        Sub-directory in output/debug/grid/ where csv Files of PyPSA network are exported to.
        Export is omitted if argument is empty.
    """

    scenario = cfg_ding0.get("powerflow", "test_grid_stability_scenario")
    start_hour = cfg_ding0.get("powerflow", "start_hour")
    end_hour = cfg_ding0.get("powerflow", "end_hour")

    # choose temp_id
    temp_id_set = 1
    timesteps = 2
    start_time = datetime(1970, 1, 1, 00, 00, 0)
    resolution = 'H'

    # inspect grid data for integrity
    if debug:
        data_integrity(components, components_data)

    # define investigated time range
    timerange = DatetimeIndex(freq=resolution,
                              periods=timesteps,
                              start=start_time)

    # TODO: Instead of hard coding PF config, values from class PFConfigDing0 can be used here.

    # create PyPSA powerflow problem
    network, snapshots = create_powerflow_problem(timerange, components)

    # import pq-sets
    for key in ['Load', 'Generator']:
        for attr in ['p_set', 'q_set']:
            # catch MV grid districts without generators
            if not components_data[key].empty:
                series = transform_timeseries4pypsa(components_data[key][
                                                        attr].to_frame(),
                                                    timerange,
                                                    column=attr)
                import_series_from_dataframe(network,
                                             series,
                                             key,
                                             attr)
    series = transform_timeseries4pypsa(components_data['Bus']
                                        ['v_mag_pu_set'].to_frame(),
                                        timerange,
                                        column='v_mag_pu_set')

    import_series_from_dataframe(network,
                                 series,
                                 'Bus',
                                 'v_mag_pu_set')

    # add coordinates to network nodes and make ready for map plotting
    # network = add_coordinates(network)

    # start powerflow calculations
    network.pf(snapshots)

    # # make a line loading plot
    # # TODO: make this optional
    # plot_line_loading(network, timestep=0,
    #                   filename='Line_loading_load_case.png')
    # plot_line_loading(network, timestep=1,
    #                   filename='Line_loading_feed-in_case.png')

    # process results
    bus_data, line_data = process_pf_results(network)

    # assign results data to graph
    assign_bus_results(grid, bus_data)
    assign_line_results(grid, line_data)

    # export network if directory is specified
    if export_pypsa_dir:
        export_to_dir(network, export_dir=export_pypsa_dir)


Instruction:
How would you implement a function in Python 3 that
checks grid data for integrity

Response:
def data_integrity(components, components_data):
    """
    Check grid data for integrity

    Parameters
    ----------
    components: dict
        Grid components
    components_data: dict
        Grid component data (such as p,q and v set points)

    Returns
    -------
    """

    data_check = {}

    for comp in ['Bus', 'Load']:  # list(components_data.keys()):
        data_check[comp] = {}
        data_check[comp]['length_diff'] = len(components[comp]) - len(
            components_data[comp])

    # print short report to user and exit program if not integer
    for comp in list(data_check.keys()):
        if data_check[comp]['length_diff'] != 0:
            logger.exception("{comp} data is invalid. You supplied {no_comp} {comp} "
                  "objects and {no_data} datasets. Check you grid data "
                  "and try again".format(comp=comp,
                                         no_comp=len(components[comp]),
                                         no_data=len(components_data[comp])))
            sys.exit(1)


Instruction:
Write a Python 3 function for
assigning voltage levels obtained from PF to graph.

Response:
def assign_bus_results(grid, bus_data):
    """
    Write results obtained from PF to graph

    Parameters
    ----------
    grid: ding0.network
    bus_data: pandas.DataFrame
        DataFrame containing voltage levels obtained from PF analysis
    """

    # iterate of nodes and assign voltage obtained from power flow analysis
    for node in grid._graph.nodes():
        # check if node is connected to graph
        if (node not in grid.graph_isolated_nodes()
            and not isinstance(node,
                               LVLoadAreaCentreDing0)):
            if isinstance(node, LVStationDing0):
                node.voltage_res = bus_data.loc[node.pypsa_id, 'v_mag_pu']
            elif isinstance(node, (LVStationDing0, LVLoadAreaCentreDing0)):
                if node.lv_load_area.is_aggregated:
                    node.voltage_res = bus_data.loc[node.pypsa_id, 'v_mag_pu']
            elif not isinstance(node, CircuitBreakerDing0):
                node.voltage_res = bus_data.loc[node.pypsa_id, 'v_mag_pu']
            else:
                logger.warning("Object {} has been skipped while importing "
                               "results!")


Instruction:
Can you tell what is the following Python 3 function doing
def assign_line_results(grid, line_data):

    package_path = ding0.__path__[0]

    edges = [edge for edge in grid.graph_edges()
             if (edge['adj_nodes'][0] in grid._graph.nodes() and not isinstance(
            edge['adj_nodes'][0], LVLoadAreaCentreDing0))
             and (
             edge['adj_nodes'][1] in grid._graph.nodes() and not isinstance(
                 edge['adj_nodes'][1], LVLoadAreaCentreDing0))]

    decimal_places = 6
    for edge in edges:
        s_res = [
            round(sqrt(
                max(abs(line_data.loc["MV_{0}_lin_{1}".format(grid.id_db, edge[
                    'branch'].id_db), 'p0'][0]),
                    abs(line_data.loc["MV_{0}_lin_{1}".format(grid.id_db, edge[
                        'branch'].id_db), 'p1'][0])) ** 2 +
                max(abs(line_data.loc["MV_{0}_lin_{1}".format(grid.id_db, edge[
                    'branch'].id_db), 'q0'][0]),
                    abs(line_data.loc["MV_{0}_lin_{1}".format(grid.id_db, edge[
                        'branch'].id_db), 'q1'][0])) ** 2),decimal_places),
            round(sqrt(
                max(abs(line_data.loc["MV_{0}_lin_{1}".format(grid.id_db, edge[
                    'branch'].id_db), 'p0'][1]),
                    abs(line_data.loc["MV_{0}_lin_{1}".format(grid.id_db, edge[
                        'branch'].id_db), 'p1'][1])) ** 2 +
                max(abs(line_data.loc["MV_{0}_lin_{1}".format(grid.id_db, edge[
                    'branch'].id_db), 'q0'][1]),
                    abs(line_data.loc["MV_{0}_lin_{1}".format(grid.id_db, edge[
                        'branch'].id_db), 'q1'][1])) ** 2),decimal_places)]

        edge['branch'].s_res = s_res

Response: Assigns line results obtained from PF to graph


Instruction:
Can you tell what is the following Python 3 function doing
def init_pypsa_network(time_range_lim):
    network = Network()
    network.set_snapshots(time_range_lim)
    snapshots = network.snapshots

    return network, snapshots

Response: Instantiate PyPSA network and set snapshots


Instruction:
Can you generate a brief explanation for the following Python 3 code
def transform_timeseries4pypsa(timeseries, timerange, column=None):
    timeseries.index = [str(i) for i in timeseries.index]

    if column is None:
        pypsa_timeseries = timeseries.apply(
            Series).transpose().set_index(timerange)
    else:
        pypsa_timeseries = timeseries[column].apply(
            Series).transpose().set_index(timerange)

    return pypsa_timeseries

Response: Transform pq - set timeseries to PyPSA compatible format


Instruction:
Can you write a function in Python 3 where it
creates PyPSA powerflow problem object and fill with data

Response:
def create_powerflow_problem(timerange, components):
    """
    Create PyPSA network object and fill with data
    Parameters
    ----------
    timerange: Pandas DatetimeIndex
        Time range to be analyzed by PF
    components: dict
    Returns
    -------
    network: PyPSA powerflow problem object
    """

    # initialize powerflow problem
    network, snapshots = init_pypsa_network(timerange)

    # add components to network
    for component in components.keys():
        network.import_components_from_dataframe(components[component],
                                                 component)

    return network, snapshots


Instruction:
Can you tell what is the following Python 3 function doing
def parallel_run(districts_list, n_of_processes, n_of_districts, run_id,
                 base_path=None):
    '''Organize parallel runs of ding0.

    The function take all districts in a list and divide them into
    n_of_processes parallel processes. For each process, the assigned districts
    are given to the function process_runs() with the argument n_of_districts

    Parameters
    ----------
    districts_list: list of int
        List with all districts to be run.
    n_of_processes: int
        Number of processes to run in parallel
    n_of_districts: int
        Number of districts to be run in each cluster given as argument to
        process_stats()
    run_id: str
        Identifier for a run of Ding0. For example it is used to create a
        subdirectory of os.path.join(`base_path`, 'results')
    base_path : str
        Base path for ding0 data (input, results and logs).
        Default is `None` which sets it to :code:`~/.ding0` (may deviate on
        windows systems).
        Specify your own but keep in mind that it a required a particular
        structure of subdirectories.

    See Also
    --------
    ding0_runs

    '''

    # define base path
    if base_path is None:
        base_path = BASEPATH

    if not os.path.exists(os.path.join(base_path, run_id)):
        os.makedirs(os.path.join(base_path, run_id))

    start = time.time()
    #######################################################################
    # Define an output queue
    output_info = mp.Queue()
    #######################################################################
    # Setup a list of processes that we want to run
    max_dist = len(districts_list)
    threat_long = floor(max_dist / n_of_processes)

    if threat_long == 0:
        threat_long = 1

    threats = [districts_list[x:x + threat_long] for x in range(0, len(districts_list), threat_long)]

    processes = []
    for th in threats:
        mv_districts = th
        processes.append(mp.Process(target=process_runs,
                                    args=(mv_districts, n_of_districts,
                                          output_info, run_id, base_path)))
    #######################################################################
    # Run processes
    for p in processes:
        p.start()
    # Resque output_info from processes
    output = [output_info.get() for p in processes]
    output = list(itertools.chain.from_iterable(output))
    # Exit the completed processes
    for p in processes:
        p.join()

    #######################################################################
    print('Elapsed time for', str(max_dist),
          'MV grid districts (seconds): {}'.format(time.time() - start))

    return output

Response: This function processes the districts in a list and runs them in parallel.


Instruction:
Make a summary of the following Python 3 code
def process_runs(mv_districts, n_of_districts, output_info, run_id, base_path):
    '''Runs a process organized by parallel_run()

    The function take all districts mv_districts and divide them into clusters
    of n_of_districts each. For each cluster, ding0 is run and the resulting
    network is saved as a pickle

    Parameters
    ----------
    mv_districts: list of int
        List with all districts to be run.
    n_of_districts: int
        Number of districts in a cluster
    output_info:
        Info about how the run went
    run_id: str
        Identifier for a run of Ding0. For example it is used to create a
        subdirectory of os.path.join(`base_path`, 'results')
    base_path : str
        Base path for ding0 data (input, results and logs).
        Default is `None` which sets it to :code:`~/.ding0` (may deviate on
        windows systems).
        Specify your own but keep in mind that it a required a particular
        structure of subdirectories.

    See Also
    --------
    parallel_run

    '''
    #######################################################################
    # database connection/ session
    engine = db.connection(section='oedb')
    session = sessionmaker(bind=engine)()

    #############################
    clusters = [mv_districts[x:x + n_of_districts] for x in range(0, len(mv_districts), n_of_districts)]
    output_clusters= []

    for cl in clusters:
        print('\n########################################')
        print('  Running ding0 for district', cl)
        print('########################################')

        nw_name = 'ding0_grids_' + str(cl[0])
        if not cl[0] == cl[-1]:
            nw_name = nw_name+'_to_'+str(cl[-1])
        nw = NetworkDing0(name=nw_name)
        try:
            msg = nw.run_ding0(session=session, mv_grid_districts_no=cl)
            if msg:
                status = 'run error'
            else:
                msg = ''
                status = 'OK'
                results.save_nd_to_pickle(nw, os.path.join(base_path, run_id))
            output_clusters.append((nw_name,status,msg, nw.metadata))
        except Exception as e:
            output_clusters.append((nw_name,  'corrupt dist', e, nw.metadata))
            continue

    output_info.put(output_clusters)

Response: Runs a process organized by parallel_run


Instruction:
Given the following Python 3 function, write the documentation
def process_metadata(meta):
    mvgds = []

    metadata = meta[0]

    for mvgd in meta:
        if isinstance(mvgd['mv_grid_districts'], list):
            mvgds.extend(mvgd['mv_grid_districts'])
        else:
            mvgds.append(mvgd['mv_grid_districts'])

    metadata['mv_grid_districts'] = mvgds

    return metadata

Response: Process metadata of run on multiple MV grid districts


Instruction:
How would you explain what the following Python 3 function does
def reinforce_grid(grid, mode):
    #TODO: finish docstring
    """ Evaluates grid reinforcement needs and performs measures

    Grid reinforcement according to methods described in [VNSRP]_ supplemented
    by [DENA]_.

    Parameters
    ----------
    grid: GridDing0
        Grid instance
    mode: str
        Choose of: 'MV' or 'LV'

    Notes
    -----
    Currently only MV branch reinforcement is implemented. HV-MV stations are not
    reinforced since not required for status-quo scenario.

    References
    ----------
    .. [DENA] Deutsche Energie-Agentur GmbH (dena), "dena-Verteilnetzstudie. Ausbau- und Innovationsbedarf der
            Stromverteilnetze in Deutschland bis 2030.", 2012
    .. [VNSRP] Ackermann, T., Untsch, S., Koch, M., & Rothfuchs, H. (2014).
            Verteilnetzstudie Rheinland-Pfalz. Hg. v. Ministerium für
            Wirtschaft, Klimaschutz, Energie und Landesplanung Rheinland-Pfalz
            (MWKEL). energynautics GmbH.

    """

    # kind of grid to be evaluated (MV or LV)
    if mode == 'MV':
        crit_branches, crit_stations = check_load(grid, mode)

        # STEP 1: reinforce branches

        # do reinforcement
        reinforce_branches_current(grid, crit_branches)

        # if branches or stations have been reinforced: run PF again to check for voltage issues
        if crit_branches or crit_stations:
            grid.network.run_powerflow(conn=None, method='onthefly')

        crit_nodes = check_voltage(grid, mode)
        crit_nodes_count_prev_step = len(crit_nodes)

        # as long as there are voltage issues, do reinforcement
        while crit_nodes:
            # determine all branches on the way from HV-MV substation to crit. nodes
            crit_branches_v = grid.find_and_union_paths(grid.station(), crit_nodes)

            # do reinforcement
            reinforce_branches_voltage(grid, crit_branches_v)

            # run PF
            grid.network.run_powerflow(session=None, method='onthefly')

            crit_nodes = check_voltage(grid, mode)

            # if there are critical nodes left but no larger cable available, stop reinforcement
            if len(crit_nodes) == crit_nodes_count_prev_step:
                logger.warning('==> There are {0} branches that cannot be '
                               'reinforced (no appropriate cable '
                               'available).'.format(
                    len(grid.find_and_union_paths(grid.station(),
                                                        crit_nodes))))
                break

            crit_nodes_count_prev_step = len(crit_nodes)

        if not crit_nodes:
            logger.info('==> All voltage issues in {mode} grid could be '
                        'solved using reinforcement.'.format(mode=mode))

        # STEP 2: reinforce HV-MV station
        # NOTE: HV-MV station reinforcement is not required for status-quo
        # scenario since HV-MV trafos already sufficient for load+generation
        # case as done in MVStationDing0.choose_transformers()

    elif mode == 'LV':
        # get overloaded branches
        # overloading issues
        critical_branches, critical_stations = get_critical_line_loading(grid)

        # reinforce overloaded lines by increasing size
        unresolved = reinforce_lv_branches_overloading(grid, critical_branches)
        logger.info(
            "Out of {crit_branches} with overloading {unresolved} remain "
            "with unresolved issues due to line overloading. "
            "LV grid: {grid}".format(
                crit_branches=len(critical_branches),
                unresolved=len(unresolved),
                grid=grid))

        # reinforce substations
        extend_substation(grid, critical_stations, mode)

        # get node with over-voltage
        crit_nodes = get_critical_voltage_at_nodes(grid) #over-voltage issues

        crit_nodes_count_prev_step = len(crit_nodes)

        logger.info('{cnt_crit_branches} in {grid} have voltage issues'.format(
            cnt_crit_branches=crit_nodes_count_prev_step,
            grid=grid))

        # as long as there are voltage issues, do reinforcement
        while crit_nodes:
            # determine all branches on the way from HV-MV substation to crit. nodes
            crit_branches_v = grid.find_and_union_paths(
                grid.station(),
                [_['node'] for _ in crit_nodes])

            # do reinforcement
            reinforce_branches_voltage(grid, crit_branches_v, mode)

            # get node with over-voltage
            crit_nodes = get_critical_voltage_at_nodes(grid)

            # if there are critical nodes left but no larger cable available, stop reinforcement
            if len(crit_nodes) == crit_nodes_count_prev_step:
                logger.warning('==> There are {0} branches that cannot be '
                               'reinforced (no appropriate cable '
                               'available).'.format(
                    len(crit_branches_v)))
                break

            crit_nodes_count_prev_step = len(crit_nodes)

        if not crit_nodes:
            logger.info('==> All voltage issues in {mode} grid could be '
                        'solved using reinforcement.'.format(mode=mode))

        # reinforcement of LV stations on voltage issues
        crit_stations_voltage = [_ for _ in crit_nodes
                        if isinstance(_['node'], LVStationDing0)]
        if crit_stations_voltage:
            extend_substation_voltage(crit_stations_voltage, grid_level='LV')

Response: Evaluate and perform reinforcement of a single grid.


Instruction:
How would you explain what the following Python 3 function does
def example_stats(filename):

    nd = results.load_nd_from_pickle(filename=filename)

    nodes_df, edges_df = nd.to_dataframe()

    # get statistical numbers about grid
    stats = results.calculate_mvgd_stats(nd)

    # plot distribution of load/generation of subjacent LV grids
    stations = nodes_df[nodes_df['type'] == 'LV Station']
    f, axarr = plt.subplots(2, sharex=True)
    f.suptitle("Peak load (top) / peak generation capacity (bottom) at LV "
               "substations in kW")
    stations['peak_load'].hist(bins=20, alpha=0.5, ax=axarr[0])
    axarr[0].set_title("Peak load in kW")
    stations['generation_capacity'].hist(bins=20, alpha=0.5, ax=axarr[1])
    axarr[1].set_title("Peak generation capacity in kW")
    plt.show()

    # Introduction of report
    print("You are analyzing MV grid district {mvgd}\n".format(
        mvgd=int(stats.index.values)))

    # print all the calculated stats
    # this isn't a particularly beautiful format but it is
    # information rich
    with option_context('display.max_rows', None,
                        'display.max_columns', None,
                        'display.max_colwidth', -1):
        print(stats.T)

Response: This function generates a simple example of how to calculate statistics about a MV grid.


Instruction:
Can you generate the documentation for the following Python 3 function
def compare_graphs(graph1, mode, graph2=None):

    # get path
    package_path = ding0.__path__[0]
    file = path.join(package_path, 'output', 'debug', 'graph1.gpickle')

    if mode == 'write':
        try:
            nx.write_gpickle(graph1, file)
            print('=====> DEBUG: Graph written to', file)
        except:
            raise FileNotFoundError('Could not write to file', file)

    elif mode == 'compare':
        if graph2 is None:
            try:
                graph2 = nx.read_gpickle(file)
                print('=====> DEBUG: Graph read from', file)
            except:
                raise FileNotFoundError('File not found:', file)

        # get data
        nodes1 = sorted(graph1.nodes(), key=lambda _: repr(_))
        nodes2 = sorted(graph2.nodes(), key=lambda _: repr(_))
        edges1 = sorted(graph1.edges(), key=lambda _: repr(_))
        edges2 = sorted(graph2.edges(), key=lambda _: repr(_))

        graphs_are_isomorphic = True

        # check nodes
        if len(nodes1) > len(nodes2):
            print('Node count in graph 1 > node count in graph 2')
            print('Difference:', [node for node in nodes1 if repr(node) not in repr(nodes2)])
            graphs_are_isomorphic = False
        elif len(nodes2) > len(nodes1):
            print('Node count in graph 2 > node count in graph 1')
            print('Difference:', [node for node in nodes2 if repr(node) not in repr(nodes1)])
            graphs_are_isomorphic = False

        # check edges
        if len(edges1) > len(edges2):
            print('Edge count in graph 1 > edge count in graph 2')
            print('Difference:', [edge for edge in edges1 if (repr(edge) not in repr(edges2)) and
                                  (repr(tuple(reversed(edge))) not in repr(edges2))])
            graphs_are_isomorphic = False
        elif len(edges2) > len(edges1):
            print('Edge count in graph 2 > edge count in graph 1')
            print('Difference:', [edge for edge in edges2 if (repr(edge) not in repr(edges1)) and
                                  (repr(tuple(reversed(edge))) not in repr(edges1))])
            graphs_are_isomorphic = False
        elif (len(edges1) == len(edges1)) and (len([edge for edge in edges1 if (repr(edge) not in repr(edges2)) and
            (repr(tuple(reversed(edge))) not in repr(edges2))]) > 0):
            print('Edge count in graph 1 = edge count in graph 2')
            print('Difference:', [edge for edge in edges2 if (repr(edge) not in repr(edges1)) and
                                  (repr(tuple(reversed(edge))) not in repr(edges1))])
            graphs_are_isomorphic = False

        if graphs_are_isomorphic:
            print('=====> DEBUG: Graphs are isomorphic')
        else:
            print('=====> DEBUG: Graphs are NOT isomorphic')

    else:
        raise ValueError('Invalid value for mode, use mode=\'write\' or \'compare\'')

    exit(0)

Response:
Compare two graphs with saved one which is loaded via networkx gpickle and return the new one which is loaded via networkx gpickle
 ArcGIS.


Instruction:
Write a Python 3 script to
calculate cumulative peak generation of generators connected to underlying grids.

Response:
def peak_generation(self, mode):
        """Calculates cumulative peak generation of generators connected to underlying grids

        This is done instantaneously using bottom-up approach.

        Parameters
        ----------
        mode: str
            determines which generators are included::

            'MV':   Only generation capacities of MV level are considered.

            'MVLV': Generation capacities of MV and LV are considered
                    (= cumulative generation capacities in entire MVGD).

        Returns
        -------
        float
            Cumulative peak generation
        """

        if mode == 'MV':
            return sum([_.capacity for _ in self.grid.generators()])

        elif mode == 'MVLV':
            # calc MV geno capacities
            cum_mv_peak_generation = sum([_.capacity for _ in self.grid.generators()])
            # calc LV geno capacities
            cum_lv_peak_generation = 0
            for load_area in self.grid.grid_district.lv_load_areas():
                cum_lv_peak_generation += load_area.peak_generation

            return cum_mv_peak_generation + cum_lv_peak_generation

        else:
            raise ValueError('parameter \'mode\' is invalid!')


Instruction:
Implement a Python 3 function for
setting operation voltage level

Response:
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


Instruction:
Implement a function in Python 3 to
select appropriate transformers for the HV - MV substation.

Response:
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}))


Instruction:
Can you tell what is the following Python 3 function doing
def pypsa_id(self):
        #TODO: docstring
        """ Description
        """
        return '_'.join(['MV', str(
            self.grid.grid_district.lv_load_area.mv_grid_district.mv_grid.\
                id_db), 'tru', str(self.id_db)])

Response: Return pypsa id for this object.


Instruction:
Can you write a function in Python 3 where it
creates base path dir and subdirectories for raw and processed results

Response:
def create_results_dirs(base_path):
    """Create base path dir and subdirectories

    Parameters
    ----------
    base_path : str
        The base path has subdirectories for raw and processed results
    """

    if not os.path.exists(base_path):
        print("Creating directory {} for results data.".format(base_path))
        os.mkdir(base_path)
    if not os.path.exists(os.path.join(base_path, 'results')):
        os.mkdir(os.path.join(base_path, 'results'))
    if not os.path.exists(os.path.join(base_path, 'plots')):
        os.mkdir(os.path.join(base_path, 'plots'))
    if not os.path.exists(os.path.join(base_path, 'info')):
        os.mkdir(os.path.join(base_path, 'info'))
    if not os.path.exists(os.path.join(base_path, 'log')):
        os.mkdir(os.path.join(base_path, 'log'))


Instruction:
Here you have a function in Python 3, explain what it does
def run_multiple_grid_districts(mv_grid_districts, run_id, failsafe=False,
                                base_path=None):
    """
    Perform ding0 run on given grid districts

    Parameters
    ----------
    mv_grid_districs : list
        Integers describing grid districts
    run_id: str
        Identifier for a run of Ding0. For example it is used to create a
        subdirectory of os.path.join(`base_path`, 'results')
    failsafe : bool
        Setting to True enables failsafe mode where corrupt grid districts
        (mostly due to data issues) are reported and skipped. Report is to be
         found in the log dir under :code:`~/.ding0` . Default is False.
    base_path : str
        Base path for ding0 data (input, results and logs).
        Default is `None` which sets it to :code:`~/.ding0` (may deviate on
        windows systems).
        Specify your own but keep in mind that it a required a particular
        structure of subdirectories.

    Returns
    -------
    msg : str
        Traceback of error computing corrupt MV grid district
        .. TODO: this is only true if try-except environment is moved into this
            fundion and traceback return is implemented

    Notes
    -----
    Consider that a large amount of MV grid districts may take hours or up to
    days to compute. A computational run for a single grid district may consume
    around 30 secs.
    """
    start = time.time()

    # define base path
    if base_path is None:
        base_path = BASEPATH

    # database connection/ session
    engine = db.connection(section='oedb')
    session = sessionmaker(bind=engine)()

    corrupt_grid_districts = pd.DataFrame(columns=['id', 'message'])

    for mvgd in mv_grid_districts:
        # instantiate ding0  network object
        nd = NetworkDing0(name='network', run_id=run_id)

        if not os.path.exists(os.path.join(base_path, "grids")):
            os.mkdir(os.path.join(base_path, "grids"))

        if not failsafe:
            # run DING0 on selected MV Grid District
            msg = nd.run_ding0(session=session,
                               mv_grid_districts_no=[mvgd])

            # save results
            results.save_nd_to_pickle(nd, os.path.join(base_path, "grids"))
        else:
            # try to perform ding0 run on grid district
            try:
                msg = nd.run_ding0(session=session,
                                   mv_grid_districts_no=[mvgd])
                # if not successful, put grid district to report
                if msg:
                    corrupt_grid_districts = corrupt_grid_districts.append(
                        pd.Series({'id': mvgd,
                                   'message': msg[0]}),
                        ignore_index=True)
                # if successful, save results
                else:
                    results.save_nd_to_pickle(nd, os.path.join(base_path,
                                                               "grids"))
            except Exception as e:
                corrupt_grid_districts = corrupt_grid_districts.append(
                    pd.Series({'id': mvgd,
                               'message': e}),
                    ignore_index=True)

                continue

        # Merge metadata of multiple runs
        if 'metadata' not in locals():
            metadata = nd.metadata

        else:
            if isinstance(mvgd, list):
                metadata['mv_grid_districts'].extend(mvgd)
            else:
                metadata['mv_grid_districts'].append(mvgd)

    # Save metadata to disk
    with open(os.path.join(base_path, "grids", 'Ding0_{}.meta'.format(run_id)),
              'w') as f:
        json.dump(metadata, f)

    # report on unsuccessful runs
    corrupt_grid_districts.to_csv(
        os.path.join(
            base_path,
            "grids",
            'corrupt_mv_grid_districts.txt'),
        index=False,
        float_format='%.0f')

    print('Elapsed time for', str(len(mv_grid_districts)),
          'MV grid districts (seconds): {}'.format(time.time() - start))

    return msg

Response: This function runs ding0 on a list of MV grid districts and returns a single MV grid district.


Instruction:
Here you have a function in Python 3, explain what it does
def plot_mv_topology(grid, subtitle='', filename=None, testcase='load',
                     line_color=None, node_color='type',
                     limits_cb_lines=None, limits_cb_nodes=None,
                     background_map=True):
    """ Draws MV grid graph using networkx

    Parameters
    ----------
    grid : :obj:`MVGridDing0`
        MV grid to plot.
    subtitle : str
        Extend plot's title by this string.
    filename : str
        If provided, the figure will be saved and not displayed (default path: ~/.ding0/).
        A prefix is added to the file name.
    testcase : str
        Defines which case is to be used. Refer to config_calc.cfg to see further
        assumptions for the cases. Possible options are:

        * 'load' (default)
          Heavy-load-flow case
        * 'feedin'
          Feedin-case
    line_color : str
        Defines whereby to choose line colors. Possible options are:

        * 'loading'
          Line color is set according to loading of the line in heavy load case.
          You can use parameter `limits_cb_lines` to adjust the color range.
        * None (default)
          Lines are plotted in black. Is also the fallback option in case of
          wrong input.
    node_color : str
        Defines whereby to choose node colors. Possible options are:

        * 'type' (default)
          Node color as well as size is set according to type of node
          (generator, MV station, etc.). Is also the fallback option in case of
          wrong input.
        * 'voltage'
          Node color is set according to voltage deviation from 1 p.u..
          You can use parameter `limits_cb_nodes` to adjust the color range.
    limits_cb_lines : :obj:`tuple`
        Tuple with limits for colorbar of line color. First entry is the
        minimum and second entry the maximum value. E.g. pass (0, 1) to
        adjust the colorbar to 0..100% loading.
        Default: None (min and max loading are used).
    limits_cb_nodes : :obj:`tuple`
        Tuple with limits for colorbar of nodes. First entry is the
        minimum and second entry the maximum value. E.g. pass (0.9, 1) to
        adjust the colorbar to 90%..100% voltage.
        Default: None (min and max loading are used).
    background_map : bool, optional
        If True, a background map is plotted (default: stamen toner light).
        The additional package `contextily` is needed for this functionality.
        Default: True

    Notes
    -----
    WGS84 pseudo mercator (epsg:3857) is used as coordinate reference system (CRS).
    Therefore, the drawn graph representation may be falsified!
    """

    def set_nodes_style_and_position(nodes):

        # TODO: MOVE settings to config
        # node types (name of classes)
        node_types = ['MVStationDing0',
                      'LVStationDing0',
                      'LVLoadAreaCentreDing0',
                      'MVCableDistributorDing0',
                      'GeneratorDing0',
                      'GeneratorFluctuatingDing0',
                      'CircuitBreakerDing0',
                      'n/a']

        # node styles
        colors_dict = {'MVStationDing0': '#f2ae00',
                       'LVStationDing0': 'grey',
                       'LVLoadAreaCentreDing0': '#fffc3d',
                       'MVCableDistributorDing0': '#000000',
                       'GeneratorDing0': '#00b023',
                       'GeneratorFluctuatingDing0': '#0078b0',
                       'CircuitBreakerDing0': '#c20000',
                       'n/a': 'orange'}
        sizes_dict = {'MVStationDing0': 120,
                      'LVStationDing0': 7,
                      'LVLoadAreaCentreDing0': 30,
                      'MVCableDistributorDing0': 5,
                      'GeneratorDing0': 50,
                      'GeneratorFluctuatingDing0': 50,
                      'CircuitBreakerDing0': 50,
                      'n/a': 5}
        zindex_by_type = {'MVStationDing0': 16,
                          'LVStationDing0': 12,
                          'LVLoadAreaCentreDing0': 11,
                          'MVCableDistributorDing0': 13,
                          'GeneratorDing0': 14,
                          'GeneratorFluctuatingDing0': 14,
                          'CircuitBreakerDing0': 15,
                          'n/a': 10}

        # dict of node class names: list of nodes
        nodes_by_type = {_: [] for _ in node_types}
        # dict of node class names: list of node-individual color
        node_colors_by_type = {_: [] for _ in node_types}
        # dict of node class names: list of node-individual size
        node_sizes_by_type = {_: [] for _ in node_types}
        node_sizes_by_type['all'] = []
        # dict of nodes:node-individual positions
        nodes_pos = {}

        for n in nodes:
            if type(n).__name__ in node_types:
                nodes_by_type[type(n).__name__].append(n)
                node_colors_by_type[type(n).__name__].append(colors_dict[type(n).__name__])
                node_sizes_by_type[type(n).__name__].append(sizes_dict[type(n).__name__])
                node_sizes_by_type['all'].append(sizes_dict[type(n).__name__])
            else:
                nodes_by_type['n/a'].append(n)
                node_colors_by_type['n/a'].append(colors_dict['n/a'])
                node_sizes_by_type['n/a'].append(sizes_dict['n/a'])
                node_sizes_by_type['all'].append(sizes_dict['n/a'])
            nodes_pos[n] = (n.geo_data.x, n.geo_data.y)

        return node_types, nodes_by_type, node_colors_by_type,\
               node_sizes_by_type, zindex_by_type, nodes_pos

    def reproject_nodes(nodes_pos, model_proj='4326'):
        inProj = Proj(init='epsg:{srid}'.format(srid=model_proj))
        outProj = Proj(init='epsg:3857')
        nodes_pos2 = {}
        for k, v in nodes_pos.items():
            x2, y2 = transform(inProj, outProj,
                               v[0],
                               v[1])
            nodes_pos2[k] = (x2, y2)
        return nodes_pos2

    def plot_background_map(ax):
        url = ctx.sources.ST_TONER_LITE
        xmin, xmax, ymin, ymax = ax.axis()
        basemap, extent = ctx.bounds2img(xmin, ymin, xmax, ymax,
                                         zoom=12, url=url)
        ax.imshow(basemap, extent=extent, interpolation='bilinear', zorder=0)
        ax.axis((xmin, xmax, ymin, ymax))

    def plot_region_data(ax):
        # get geoms of MV grid district, load areas and LV grid districts
        mv_grid_district = gpd.GeoDataFrame({'geometry': grid.grid_district.geo_data},
                                            crs={'init': 'epsg:{srid}'.format(srid=model_proj)})
        load_areas = gpd.GeoDataFrame({'geometry': [la.geo_area for la in grid.grid_district.lv_load_areas()]},
                                      crs={'init': 'epsg:{srid}'.format(srid=model_proj)})
        lv_grid_districts = gpd.GeoDataFrame({'geometry': [lvgd.geo_data
                                                           for la in grid.grid_district.lv_load_areas()
                                                           for lvgd in la.lv_grid_districts()]},
                                             crs={'init': 'epsg:{srid}'.format(srid=model_proj)})

        # reproject to WGS84 pseudo mercator
        mv_grid_district = mv_grid_district.to_crs(epsg=3857)
        load_areas = load_areas.to_crs(epsg=3857)
        lv_grid_districts = lv_grid_districts.to_crs(epsg=3857)

        # plot
        mv_grid_district.plot(ax=ax, color='#ffffff', alpha=0.2, edgecolor='k', linewidth=2, zorder=2)
        load_areas.plot(ax=ax, color='#fffea3', alpha=0.1, edgecolor='k', linewidth=0.5, zorder=3)
        lv_grid_districts.plot(ax=ax, color='#ffffff', alpha=0.05, edgecolor='k', linewidth=0.5, zorder=4)

    if not isinstance(grid, MVGridDing0):
        logger.warning('Sorry, but plotting is currently only available for MV grids but you did not pass an'
                       'instance of MVGridDing0. Plotting is skipped.')
        return

    g = grid._graph
    model_proj = grid.network.config['geo']['srid']

    if testcase == 'feedin':
        case_idx = 1
    else:
        case_idx = 0

    nodes_types, nodes_by_type, node_colors_by_type, node_sizes_by_type, zindex_by_type, nodes_pos =\
        set_nodes_style_and_position(g.nodes())

    # reproject to WGS84 pseudo mercator
    nodes_pos = reproject_nodes(nodes_pos, model_proj=model_proj)

    plt.figure(figsize=(9, 6))
    ax = plt.gca()

    if line_color == 'loading':
        edges_color = []
        for n1, n2 in g.edges():
            edge = g.adj[n1][n2]
            if hasattr(edge['branch'], 's_res'):
                edges_color.append(edge['branch'].s_res[case_idx] * 1e3 /
                                   (3 ** 0.5 * edge['branch'].type['U_n'] * edge['branch'].type['I_max_th']))
            else:
                edges_color.append(0)
        edges_cmap = plt.get_cmap('jet')
        #edges_cmap.set_over('#952eff')
    else:
        edges_color = ['black'] * len(list(grid.graph_edges()))
        edges_cmap = None

    # plot nodes by voltage
    if node_color == 'voltage':
        voltage_station = grid._station.voltage_res[case_idx]
        nodes_color = []
        for n in g.nodes():
            if hasattr(n, 'voltage_res'):
                nodes_color.append(n.voltage_res[case_idx])
            else:
                nodes_color.append(voltage_station)

        if testcase == 'feedin':
            nodes_cmap = plt.get_cmap('Reds')
            nodes_vmax = voltage_station + float(grid.network.config
                                                 ['mv_routing_tech_constraints']
                                                 ['mv_max_v_level_fc_diff_normal'])
            nodes_vmin = voltage_station
        else:
            nodes_cmap = plt.get_cmap('Reds_r')
            nodes_vmin = voltage_station - float(grid.network.config
                                                 ['mv_routing_tech_constraints']
                                                 ['mv_max_v_level_lc_diff_normal'])
            nodes_vmax = voltage_station

        nodes = nx.draw_networkx_nodes(g,
                                       pos=nodes_pos,
                                       node_color=nodes_color,
                                       # node_shape='o', # TODO: Add additional symbols here
                                       cmap=nodes_cmap,
                                       vmin=nodes_vmin,
                                       vmax=nodes_vmax,
                                       node_size=node_sizes_by_type['all'],
                                       linewidths=0.25,
                                       ax=ax)
        nodes.set_zorder(10)
        nodes.set_edgecolor('k')

        # colorbar nodes
        if limits_cb_nodes is None:
            limits_cb_nodes = (math.floor(min(nodes_color)*100)/100,
                               math.ceil(max(nodes_color)*100)/100)
        v_range = np.linspace(limits_cb_nodes[0], limits_cb_nodes[1], 101)
        cb_voltage = plt.colorbar(nodes, boundaries=v_range,
                                  ticks=v_range[0:101:10],
                                  fraction=0.04, pad=0.1)
        cb_voltage.set_clim(vmin=limits_cb_nodes[0],
                            vmax=limits_cb_nodes[1])
        cb_voltage.set_label('Node voltage deviation in %', size='smaller')
        cb_voltage.ax.tick_params(labelsize='smaller')

    # plot nodes by type
    else:
        for node_type in nodes_types:
            if len(nodes_by_type[node_type]) != 0:
                nodes = nx.draw_networkx_nodes(g,
                                               nodelist=nodes_by_type[node_type],
                                               pos=nodes_pos,
                                               # node_shape='o', # TODO: Add additional symbols here
                                               node_color=node_colors_by_type[node_type],
                                               cmap=None,
                                               vmin=None,
                                               vmax=None,
                                               node_size=node_sizes_by_type[node_type],
                                               linewidths=0.25,
                                               label=node_type,
                                               ax=ax)
                nodes.set_zorder(zindex_by_type[node_type])
                nodes.set_edgecolor('k')

    edges = nx.draw_networkx_edges(g,
                                   pos=nodes_pos,
                                   edge_color=edges_color,
                                   edge_cmap=edges_cmap,
                                   edge_vmin=0,
                                   edge_vmax=1,
                                   #width=1,
                                   ax=ax)
    edges.set_zorder(5)

    if line_color == 'loading':
        # colorbar edges
        if limits_cb_lines is None:
            limits_cb_lines = (math.floor(min(edges_color)*100)/100,
                               math.ceil(max(edges_color)*100)/100)
        loading_range = np.linspace(limits_cb_lines[0], limits_cb_lines[1], 101)
        cb_loading = plt.colorbar(edges, boundaries=loading_range,
                                  ticks=loading_range[0:101:10],
                                  fraction=0.04, pad=0.04)
        cb_loading.set_clim(vmin=limits_cb_lines[0],
                            vmax=limits_cb_lines[1])
        cb_loading.set_label('Line loading in % of nominal capacity', size='smaller')
        cb_loading.ax.tick_params(labelsize='smaller')

    if use_ctx and background_map:
        plot_background_map(ax=ax)
    if use_gpd:
        plot_region_data(ax=ax)

    plt.legend(fontsize=7)
    plt.title('MV Grid District {id} - {st}'.format(id=grid.id_db,
                                                    st=subtitle))

    # hide axes labels (coords)
    ax.get_xaxis().set_visible(False)
    ax.get_yaxis().set_visible(False)

    if filename is None:
        plt.tight_layout()
        plt.show()
    else:
        path = os.path.join(get_default_home_dir(), 'ding0_grid_{id}_{filename}'.format(id=str(grid.id_db),
                                                                                filename=filename))
        plt.savefig(path, dpi=300, bbox_inches='tight')
        plt.close()
        logger.info('==> Figure saved to {path}'.format(path=path))

Response: Plots MV grid graph using networkx


Instruction:
Can you create a Python 3 function that
loads config file specified by filename.

Response:
def load_config(filename):
    """ Read config file specified by `filename`

    Parameters
    ----------
    filename : str
        Description of filename
    """
    package_path = ding0.__path__[0]
    FILE = path.join(package_path, 'config', filename)

    try:
        cfg.read(FILE)
        global _loaded
        _loaded = True
    except:
        logger.exception("configfile not found.")


Instruction:
Make a summary of the following Python 3 code
def set(section, key, value):

    if not _loaded:
        init()

    if not cfg.has_section(section):
        cfg.add_section(section)

    cfg.set(section, key, value)

    with open(FILE, 'w') as configfile:
        cfg.write(configfile)

Response: Sets a value to a section key - pair.


Instruction:
Given the following Python 3 function, write the documentation
def _parse_depot_section(f):
    depots = []

    for line in f:
        line = strip(line)
        if line == '-1' or line == 'EOF': # End of section
            break
        else:
            depots.append(line)

    if len(depots) != 1:
        raise ParseException('One and only one depot is supported')

    return int(depots[0])

Response:
Parse TSPLIB DEPOT_SECTION data part from file descriptor f

    Returns a list of depots


Instruction:
Can you create a Python 3 function that
parses TSPLIB NODE_COORD_SECTION or DEMAND_SECTION from file descript f Returns a dict containing the node as key

Response:
def _parse_nodes_section(f, current_section, nodes):
    """Parse TSPLIB NODE_COORD_SECTION or DEMAND_SECTION from file descript f

    Returns a dict containing the node as key
    """
    section = {}
    dimensions = None

    if current_section == 'NODE_COORD_SECTION':
        dimensions = 3 # i: (i, j)
    elif current_section == 'DEMAND_SECTION':
        dimensions = 2 # i: q
    else:
        raise ParseException('Invalid section {}'.format(current_section))

    n = 0
    for line in f:
        line = strip(line)

        # Check dimensions
        definitions = re.split(r'\s*', line)
        if len(definitions) != dimensions:
            raise ParseException('Invalid dimensions from section {}. Expected: {}'.format(current_section, dimensions))

        node = int(definitions[0])
        values = [int(v) for v in definitions[1:]]

        if len(values) == 1:
            values = values[0]

        section[node] = values

        n = n + 1
        if n == nodes:
            break

    # Assert all nodes were read
    if n != nodes:
        raise ParseException('Missing {} nodes definition from section {}'.format(nodes - n, current_section))

    return section


Instruction:
How would you implement a function in Python 3 that
parses TSPLIB EDGE_WEIGHT_SECTION from file f

Response:
def _parse_edge_weight(f, nodes):
    """Parse TSPLIB EDGE_WEIGHT_SECTION from file f

    Supports only FULL_MATRIX for now
    """
    matrix = []

    n = 0

    for line in f:
        line = strip(line)

        regex = re.compile(r'\s+')

        row = regex.split(line)

        matrix.append(row)

        n = n + 1

        if n == nodes:
            break

    if n != nodes:
        raise ParseException('Missing {} nodes definition from section EDGE_WEIGHT_SECTION'.format(nodes - n))

    return matrix


Instruction:
How would you implement a function in Python 3 that
calculates the Eclidian distance between two points a and b.

Response:
def calculate_euc_distance(a, b):
    """Calculates Eclidian distances from two points a and b

    Args
    ----
    a : (:obj:`float`, :obj:`float`)
        Two-dimension tuple (x1,y1)
    b : (:obj:`float`, :obj:`float`)
        Two-dimension tuple (x2,y2)

    Returns
    -------
    float
        the distance.
    """
    x1, y1 = a
    x2, y2 = b

    return int(round(math.sqrt(((x1 - x2) ** 2) + (((y1 - y2) ** 2)))))


Instruction:
Write a Python 3 script to
post - process specs after pure parsing

Response:
def _post_process_specs(specs):
    """Post-process specs after pure parsing

    Casts any number expected values into integers

    Args
    ----
    specs :

    Notes
    -----
    Modifies the specs object
    """
    integer_specs = ['DIMENSION', 'CAPACITY']

    for s in integer_specs:
        specs[s] = int(specs[s])


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _create_node_matrix_from_coord_section(specs):
    distances = specs['NODE_COORD_SECTION']

    specs['MATRIX'] = {}

    for i in distances:
        origin = tuple(distances[i])

        specs['MATRIX'][i] = {}

        for j in specs['NODE_COORD_SECTION']:
            destination = tuple(distances[j])

            distance = calculate_euc_distance(origin, destination)

            #
            # Upper triangular matrix
            # if i > j, ij = 0
            #
            #if i > j:
            #    continue

            specs['MATRIX'][i][j] = distance

Response: Transformed parsed data from NODE_COORD_SECTION into an upper triangular matrix where each element in the matrix is a tuple of the origin and destination nodes.


Instruction:
How would you code a function in Python 3 to
transform parsed data from EDGE_WEIGHT_SECTION into an upper triangular matrix 'MATRIX' key added to specs

Response:
def _create_node_matrix_from_full_matrix(specs):
    """Transform parsed data from EDGE_WEIGHT_SECTION into an upper triangular matrix

    'MATRIX' key added to `specs`
    """
    old_matrix = specs['EDGE_WEIGHT_SECTION']
    nodes = specs['DIMENSION']

    specs['MATRIX'] = {}

    for i in range(nodes):
        specs['MATRIX'][i + 1] = {}

        for j in range(nodes):
            if i > j:
                continue

            specs['MATRIX'][i + 1][j + 1] = int(old_matrix[i][j])


Instruction:
Given the following Python 3 function, write the documentation
def _parse_tsplib(f):
    line = ''

    specs = {}

    used_specs = ['NAME', 'COMMENT', 'DIMENSION', 'CAPACITY', 'TYPE', 'EDGE_WEIGHT_TYPE']
    used_data = ['DEMAND_SECTION', 'DEPOT_SECTION']

    # Parse specs part
    for line in f:
        line = strip(line)

        # Arbitrary sort, so we test everything out
        s = None
        for s in used_specs:
            if line.startswith(s):
                specs[s] = line.split('{} :'.format(s))[-1].strip() # get value data part
                break

        if s == 'EDGE_WEIGHT_TYPE' and s in specs and specs[s] == 'EXPLICIT':
            used_specs.append('EDGE_WEIGHT_FORMAT')

        # All specs read
        if len(specs) == len(used_specs):
            break

    if len(specs) != len(used_specs):
        missing_specs = set(used_specs).symmetric_difference(set(specs))
        raise ParseException('Error parsing TSPLIB data: specs {} missing'.format(missing_specs))

    print(specs)

    if specs['EDGE_WEIGHT_TYPE'] == 'EUC_2D':
        used_data.append('NODE_COORD_SECTION')
    elif specs['EDGE_WEIGHT_FORMAT'] == 'FULL_MATRIX':
        used_data.append('EDGE_WEIGHT_SECTION')
    else:
        raise ParseException('EDGE_WEIGHT_TYPE or EDGE_WEIGHT_FORMAT not supported')

    _post_process_specs(specs)

    # Parse data part
    for line in f:
        line = strip(line)

        for d in used_data:
            if line.startswith(d):
                if d == 'DEPOT_SECTION':
                    specs[d] = _parse_depot_section(f)
                elif d in ['NODE_COORD_SECTION', 'DEMAND_SECTION']:
                    specs[d] = _parse_nodes_section(f, d, specs['DIMENSION'])
                elif d == 'EDGE_WEIGHT_SECTION':
                    specs[d] = _parse_edge_weight(f, specs['DIMENSION'])

        if len(specs) == len(used_specs) + len(used_data):
            break

    if len(specs) != len(used_specs) + len(used_data):
        missing_specs = set(specs).symmetric_difference(set(used_specs).union(set(used_data)))
        raise ParseException('Error parsing TSPLIB data: specs {} missing'.format(missing_specs))

    _post_process_data(specs)

    return specs

Response: Parses a TSPLIB file descriptor and returns a dict containing the problem definition


Instruction:
Given the following Python 3 function, write the documentation
def read_file(filename):
    sanitized_filename = sanitize(filename)

    f = open(sanitized_filename)

    specs = None

    try:
        specs = _parse_tsplib(f)
    except ParseException:
        raise
    finally: # 'finally' is executed even when we re-raise exceptions
        f.close()

    if specs['TYPE'] != 'CVRP':
        raise Exception('Not a CVRP TSPLIB problem. Found: {}'.format(specs['TYPE']))

    #additional params for graph/network (temporary)
    specs['VOLTAGE'] = 20000
    specs['CABLETYPE'] = 1

    #return (Graph(specs), specs)
    return Graph(specs)

Response: Reads a TSPLIB file and returns the problem data.


Instruction:
Here you have a function in Python 3, explain what it does
def add_circuit_breaker(self, circ_breaker):
        if circ_breaker not in self._circuit_breakers and isinstance(circ_breaker, CircuitBreakerDing0):
            self._circuit_breakers.append(circ_breaker)
            self.graph_add_node(circ_breaker)

Response: Adds a circuit breaker to the internal list of circuit breakers.


Instruction:
Write a Python 3 script to
add MV station to the MV object.

Response:
def add_station(self, mv_station, force=False):
        """Adds MV station if not already existing

        Args
        ----
        mv_station: MVStationDing0
            Description #TODO
        force: bool
            If True, MV Station is set even though it's not empty (override)
        """
        if not isinstance(mv_station, MVStationDing0):
            raise Exception('Given MV station is not a MVStationDing0 object.')
        if self._station is None:
            self._station = mv_station
            self.graph_add_node(mv_station)
        else:
            if force:
                self._station = mv_station
            else:
                raise Exception('MV Station already set, use argument `force=True` to override.')


Instruction:
How would you code a function in Python 3 to
add a MV load to _loads and grid graph if not already existing

Response:
def add_load(self, lv_load):
        """Adds a MV load to _loads and grid graph if not already existing

        Args
        ----
        lv_load : float
            Desription #TODO
        """
        if lv_load not in self._loads and isinstance(lv_load,
                                                     MVLoadDing0):
            self._loads.append(lv_load)
            self.graph_add_node(lv_load)


Instruction:
Can you generate the documentation for the following Python 3 function
def add_cable_distributor(self, cable_dist):
        if cable_dist not in self.cable_distributors() and isinstance(cable_dist,
                                                                      MVCableDistributorDing0):
            # add to array and graph
            self._cable_distributors.append(cable_dist)
            self.graph_add_node(cable_dist)

Response: Adds a cable distributor to the internal list of _cable_distributors.


Instruction:
How would you implement a function in Python 3 that
removes a cable distributor from _cable_distributors and _graph.

Response:
def remove_cable_distributor(self, cable_dist):
        """Removes a cable distributor from _cable_distributors if existing"""
        if cable_dist in self.cable_distributors() and isinstance(cable_dist,
                                                                  MVCableDistributorDing0):
            # remove from array and graph
            self._cable_distributors.remove(cable_dist)
            if self._graph.has_node(cable_dist):
                self._graph.remove_node(cable_dist)


Instruction:
How would you code a function in Python 3 to
add a ring to the list of rings

Response:
def add_ring(self, ring):
        """Adds a ring to _rings if not already existing"""
        if ring not in self._rings and isinstance(ring, RingDing0):
            self._rings.append(ring)


Instruction:
Write a Python 3 function for
returning a generator for iterating over rings and satellite nodes.

Response:
def rings_nodes(self, include_root_node=False, include_satellites=False):
        """ Returns a generator for iterating over rings (=routes of MVGrid's graph)

        Args
        ----
        include_root_node: bool, defaults to False
            If True, the root node is included in the list of ring nodes.
        include_satellites: bool, defaults to False
            If True, the satellite nodes (nodes that diverge from ring nodes) is included in the list of ring nodes.

        Yields
        ------
        :any:`list` of :obj:`GridDing0`
            List with nodes of each ring of _graph in- or excluding root node (HV/MV station) (arg `include_root_node`),
            format::

            [ ring_m_node_1, ..., ring_m_node_n ]

        Notes
        -----
            Circuit breakers must be closed to find rings, this is done automatically.
        """
        for circ_breaker in self.circuit_breakers():
            if circ_breaker.status is 'open':
                circ_breaker.close()
                logger.info('Circuit breakers were closed in order to find MV '
                            'rings')

        for ring in nx.cycle_basis(self._graph, root=self._station):
            if not include_root_node:
                ring.remove(self._station)

            if include_satellites:
                ring_nodes = ring
                satellites = []
                for ring_node in ring:
                    # determine all branches diverging from each ring node
                    satellites.append(
                        self.graph_nodes_from_subtree(
                            ring_node,
                            include_root_node=include_root_node
                        )
                    )
                # return ring and satellite nodes (flatted list of lists)
                yield ring + [_ for sublist in satellites for _ in sublist]
            else:
                yield ring


Instruction:
Can you write a function in Python 3 where it
returns a generator for iterating over each ring and edges and nodes in the ring.

Response:
def rings_full_data(self):
        """ Returns a generator for iterating over each ring

        Yields
        ------
            For each ring, tuple composed by ring ID, list of edges, list of nodes
        Notes
        -----
            Circuit breakers must be closed to find rings, this is done automatically.
        """
        #close circuit breakers
        for circ_breaker in self.circuit_breakers():
            if not circ_breaker.status == 'closed':
                circ_breaker.close()
                logger.info('Circuit breakers were closed in order to find MV '
                            'rings')
        #find True rings (cycles from station through breaker and back to station)
        for ring_nodes in nx.cycle_basis(self._graph, root=self._station):
            edges_ring = []
            for node in ring_nodes:
                for edge in self.graph_branches_from_node(node):
                    nodes_in_the_branch = self.graph_nodes_from_branch(edge[1]['branch'])
                    if (nodes_in_the_branch[0] in ring_nodes and
                        nodes_in_the_branch[1] in ring_nodes
                        ):
                        if not edge[1]['branch'] in edges_ring:
                            edges_ring.append(edge[1]['branch'])
            yield (edges_ring[0].ring,edges_ring,ring_nodes)