Source code for ego.tools.specs

# -*- coding: utf-8 -*-
# Copyright 2016-2018 Europa-Universität Flensburg,
# Flensburg University of Applied Sciences,
# Centre for Sustainable Energy Systems
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation; either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# File description
"""
This files contains all eGo interface functions
"""

__copyright__ = ("Europa-Universität Flensburg, "
                 "Centre for Sustainable Energy Systems")
__license__ = "GNU Affero General Public License Version 3 (AGPL-3.0)"
__author__ = "wolf_bunke,maltesc"

# Import
# General Packages
import os
import pandas as pd
import time
if not 'READTHEDOCS' in os.environ:
    from egoio.db_tables import model_draft
    from egoio.db_tables import supply
    import math

import logging
logger = logging.getLogger(__name__)


# Functions

[docs]def get_etragospecs_direct(session, bus_id, etrago_network, scn_name, grid_version, pf_post_lopf, max_cos_phi_renewable): """ Reads eTraGo Results from Database and returns and returns the interface values as a dictionary of corresponding dataframes Parameters ---------- session : sqlalchemy.orm.session.Session Handles conversations with the database. bus_id : int ID of the corresponding HV bus etrago_network: :class:`etrago.tools.io.NetworkScenario` eTraGo network object compiled by :meth:`etrago.appl.etrago` scn_name : str Name of used scenario 'Status Quo', 'NEP 2035' or 'eGo 100' Returns ------- :obj:`dict` of :pandas:`pandas.DataFrame<dataframe>` Dataframes used as eDisGo inputs """ logger.info('Specs for bus {}'.format(bus_id)) if pf_post_lopf: logger.info('Active and reactive power interface') else: logger.info('Only active power interface') specs_meta_data = {} performance = {} specs_meta_data.update({'TG Bus ID': bus_id}) if grid_version is None: logger.warning('Weather_id taken from model_draft (not tested)') ormclass_gen_single = model_draft.__getattribute__( 'EgoSupplyPfGeneratorSingle') else: ormclass_aggr_w = supply.__getattribute__( 'EgoAggrWeather') snap_idx = etrago_network.snapshots # Generators t0 = time.perf_counter() weather_dpdnt = ['wind', 'solar', 'wind_onshore', 'wind_offshore'] # DF procesing all_gens_df = etrago_network.generators[ etrago_network.generators['bus'] == str(bus_id) ] all_gens_df.index.name = 'generator_id' all_gens_df.reset_index(inplace=True) all_gens_df = all_gens_df[[ 'generator_id', 'p_nom', 'p_nom_opt', 'carrier']] all_gens_df = all_gens_df.rename(columns={"carrier": "name"}) all_gens_df = all_gens_df[all_gens_df['name'] != 'wind_offshore'] for index, row in all_gens_df.iterrows(): name = row['name'] if name == 'wind_onshore': all_gens_df.at[index, 'name'] = 'wind' # Conventionals t1 = time.perf_counter() performance.update({'Generator Data Processing': t1-t0}) conv_df = all_gens_df[~all_gens_df.name.isin(weather_dpdnt)] conv_dsptch = pd.DataFrame(0.0, index=snap_idx, columns=list(set(conv_df['name']))) conv_reactive_power = pd.DataFrame(0.0, index=snap_idx, columns=list(set(conv_df['name']))) if not conv_df.empty: conventionals = True conv_cap = conv_df[['p_nom', 'name']].groupby('name').sum().T for index, row in conv_df.iterrows(): generator_id = row['generator_id'] source = row['name'] p = etrago_network.generators_t.p[str(generator_id)] p_norm = p / conv_cap[source]['p_nom'] conv_dsptch[source] = conv_dsptch[source] + p_norm if pf_post_lopf: q = etrago_network.generators_t.q[str(generator_id)] q_norm = q / conv_cap[source]['p_nom'] # q normalized with p_nom conv_reactive_power[source] = ( conv_reactive_power[source] + q_norm) if pf_post_lopf: new_columns = [ (col, '') for col in conv_reactive_power.columns ] conv_reactive_power.columns = pd.MultiIndex.from_tuples( new_columns) else: conventionals = False logger.warning('No conventional generators at bus {}'.format(bus_id)) # Renewables t2 = time.perf_counter() performance.update({'Conventional Dispatch': t2-t1}) # Capacities ren_df = all_gens_df[all_gens_df.name.isin(weather_dpdnt)] if ren_df.empty: logger.warning('No renewable generators at bus {}'.format(bus_id)) for index, row in ren_df.iterrows(): aggr_id = row['generator_id'] if grid_version is None: w_id = session.query( ormclass_gen_single.w_id ).filter( ormclass_gen_single.aggr_id == aggr_id, ormclass_gen_single.scn_name == scn_name ).limit(1).scalar() else: w_id = session.query( ormclass_aggr_w.w_id ).filter( ormclass_aggr_w.aggr_id == aggr_id, ormclass_aggr_w.scn_name == scn_name, ormclass_aggr_w.version == grid_version ).limit(1).scalar() ren_df.at[index, 'w_id'] = w_id ren_df.dropna(inplace=True) aggr_gens = ren_df.groupby([ 'name', 'w_id' ]).agg({'p_nom': 'sum'}).reset_index() aggr_gens.rename(columns={'p_nom': 'p_nom_aggr'}, inplace=True) aggr_gens['ren_id'] = aggr_gens.index ### Dispatch and Curteilment potential = pd.DataFrame(0.0, index=snap_idx, columns=aggr_gens['ren_id']) dispatch = pd.DataFrame(0.0, index=snap_idx, columns=aggr_gens['ren_id']) curtailment = pd.DataFrame(0.0, index=snap_idx, columns=aggr_gens['ren_id']) if pf_post_lopf: reactive_power = pd.DataFrame(0.0, index=snap_idx, columns=aggr_gens['ren_id']) for index, row in ren_df.iterrows(): gen_id = row['generator_id'] name = row['name'] w_id = row['w_id'] ren_id = int(aggr_gens[ (aggr_gens['name'] == name) & (aggr_gens['w_id'] == w_id)]['ren_id']) p_nom_aggr = float( aggr_gens[aggr_gens['ren_id'] == ren_id]['p_nom_aggr']) p_nom = row['p_nom'] p_series = etrago_network.generators_t.p[str(gen_id)] p_norm_tot_series = p_series / p_nom_aggr p_max_pu_series = etrago_network.generators_t.p_max_pu[str(gen_id)] p_max_norm_tot_series = p_max_pu_series * p_nom / p_nom_aggr potential[ren_id] = potential[ren_id] + p_max_norm_tot_series dispatch[ren_id] = dispatch[ren_id] + p_norm_tot_series if pf_post_lopf: q_series = etrago_network.generators_t.q[str(gen_id)] q_norm_tot_series = q_series / p_nom_aggr reactive_power[ren_id] = ( reactive_power[ren_id] + q_norm_tot_series) curtailment = potential.sub(dispatch) new_columns = [ (aggr_gens[aggr_gens.ren_id == col].name.iloc[0], aggr_gens[aggr_gens.ren_id == col].w_id.iloc[0]) for col in potential.columns] potential.columns = pd.MultiIndex.from_tuples(new_columns) new_columns = [ (aggr_gens[aggr_gens.ren_id == col].name.iloc[0], aggr_gens[aggr_gens.ren_id == col].w_id.iloc[0]) for col in dispatch.columns] dispatch.columns = pd.MultiIndex.from_tuples(new_columns) new_columns = [ (aggr_gens[aggr_gens.ren_id == col].name.iloc[0], aggr_gens[aggr_gens.ren_id == col].w_id.iloc[0]) for col in curtailment.columns] curtailment.columns = pd.MultiIndex.from_tuples(new_columns) if pf_post_lopf: new_columns = [ (aggr_gens[aggr_gens.ren_id == col].name.iloc[0], aggr_gens[aggr_gens.ren_id == col].w_id.iloc[0]) for col in reactive_power.columns] reactive_power.columns = pd.MultiIndex.from_tuples(new_columns) # Q limit calculation if max_cos_phi_renewable: logger.info('Applying Q limit (max cos(phi)={})'.format( max_cos_phi_renewable)) phi = math.acos(max_cos_phi_renewable) for col in reactive_power: for idx in reactive_power.index: p = dispatch.loc[idx][col] q = reactive_power.loc[idx][col] q_max, q_min = p * math.tan(phi), -p * math.tan(phi) if q > q_max: q = q_max elif q < q_min: q = q_min reactive_power.at[idx, col] = q # Reactive Power concat if conventionals: all_reactive_power = pd.concat([ conv_reactive_power, reactive_power], axis=1) else: all_reactive_power = reactive_power # Storage t3 = time.perf_counter() performance.update({'Renewable Dispatch and Curt.': t3-t2}) # Capactiy min_extended = 0.3 stor_df = etrago_network.storage_units.loc[ (etrago_network.storage_units['bus'] == str(bus_id)) & (etrago_network.storage_units['p_nom_extendable'] == True) & (etrago_network.storage_units['p_nom_opt'] > min_extended) & (etrago_network.storage_units['max_hours'] <= 20.)] # Only batteries logger.warning('Minimum storage of {} MW'.format(min_extended)) ext_found = False if len(stor_df) == 1: logger.info('Extendable storage unit found') ext_found = True stor_id = stor_df.index[0] stor_p_series_kW = etrago_network.storage_units_t.p[ str(stor_id)] * 1000 if pf_post_lopf: try: stor_q_series_kvar = etrago_network.storage_units_t.q[ str(stor_id)] * 1000 except: logger.warning("No Q series found for storage unit {}".format( stor_id)) stor_q_series_kvar = etrago_network.storage_units_t.p[ str(stor_id)] * 0 if ext_found == False: logger.info( "No extendable storage unit found at bus {}".format(bus_id)) t4 = time.perf_counter() performance.update({'Storage Data Processing and Dispatch': t4-t3}) specs = { 'conv_dispatch': conv_dsptch, 'ren_dispatch': dispatch, 'ren_potential': potential, 'ren_curtailment': curtailment } if ext_found: specs['battery_p_series'] = stor_p_series_kW if pf_post_lopf: specs['battery_q_series'] = stor_q_series_kvar else: specs['battery_p_series'] = specs['battery_q_series'] = None if pf_post_lopf: specs['reactive_power'] = all_reactive_power t5 = time.perf_counter() performance.update({'Overall time': t5-t0}) return specs