Source code for el1xr_opt.Modules.oM_OutputData

# Developed by: Erik F. Alvarez

# Erik F. Alvarez
# Electric Power System Unit
# RISE
# erik.alvarez@ri.se

# Importing Libraries
import csv
import os
import time
import datetime
import altair as alt
import numpy as np
import pandas as pd
# import ausankey as sky
import matplotlib.pyplot as plt
import plotly.graph_objects as go
# import altair_saver
from  collections import defaultdict
from  pyomo.environ import Var, Param, Constraint
from .utils.oM_Utils import log_time
try:
    import ausankey as sky
except Exception:
    sky = None

# ============================================================================
# REUSABLE HELPER FUNCTIONS FOR CSV EXPORT
# ============================================================================

[docs] def save_to_csv(df, path, filename, index=False): """ Save a DataFrame to CSV file. Args: df (pd.DataFrame): DataFrame to save path (str): Directory path filename (str): Filename including extension index (bool): Whether to include index in CSV """ filepath = os.path.join(path, filename) df.to_csv(filepath, index=index, sep=',')
# ============================================================================ # REUSABLE HELPER FUNCTIONS FOR PLOTTING # ============================================================================
[docs] def create_line_chart(df, x_col, y_col, color_col=None, title='', x_title='', y_title='', width=800, height=400, date_format="%a, %b %d, %H:%M", strokeDash=None): """ Create a reusable Altair line chart. Args: df (pd.DataFrame): Data to plot x_col (str): Column name for x-axis y_col (str): Column name for y-axis color_col (str, optional): Column name for color encoding title (str): Chart title x_title (str): X-axis title y_title (str): Y-axis title width (int): Chart width height (int): Chart height date_format (str): Format string for date axis strokeDash (list, optional): Dash pattern for line [5, 5] Returns: alt.Chart: Altair chart object """ mark_opts = {'point': alt.OverlayMarkDef(filled=False, fill='white')} if strokeDash: mark_opts['strokeDash'] = strokeDash chart = alt.Chart(df).mark_line(**mark_opts) encoding = { 'x': alt.X(f'{x_col}:T' if ':T' not in x_col else x_col, axis=alt.Axis(title=x_title, labelAngle=-90, format=date_format, tickCount=30, labelLimit=1000)), 'y': alt.Y(f'{y_col}:Q' if ':Q' not in y_col else y_col, axis=alt.Axis(title=y_title)) } if color_col: encoding['color'] = alt.Color(f'{color_col}:N', legend=alt.Legend(title='')) return chart.encode(**encoding).properties(width=width, height=height, title=title)
[docs] def create_bar_chart(df, x_col, y_col, color_col, title='', x_title='', y_title='', width=800, height=400, date_format="%a, %b %d, %H:%M"): """ Create a reusable Altair bar chart. Args: df (pd.DataFrame): Data to plot x_col (str): Column name for x-axis y_col (str): Column name for y-axis (use 'sum(...)' for aggregation) color_col (str): Column name for color encoding title (str): Chart title x_title (str): X-axis title y_title (str): Y-axis title width (int): Chart width height (int): Chart height date_format (str): Format string for date axis Returns: alt.Chart: Altair chart object """ return (alt.Chart(df) .mark_bar() .encode( x=alt.X(f'{x_col}:T', axis=alt.Axis(title=x_title, labelAngle=-90, format=date_format, tickCount=30, labelLimit=1000)), y=alt.Y(y_col, axis=alt.Axis(title=y_title)), color=alt.Color(f'{color_col}:N', legend=alt.Legend(title='')) ) .properties(width=width, height=height, title=title))
[docs] def create_duration_curve(df, value_col, date_col, title, y_label, path, filename): """ Create and save a duration curve plot. Args: df (pd.DataFrame): Input dataframe value_col (str): Column name containing values to plot date_col (str): Column name containing dates title (str): Chart title y_label (str): Y-axis label path (str): Directory path to save chart filename (str): Output filename Returns: pd.DataFrame: DataFrame sorted by value (descending) with counter column """ df_sorted = df.sort_values(by=value_col, ascending=False).reset_index(drop=True) df_sorted['Counter'] = range(len(df_sorted)) chart = (alt.Chart(df_sorted) .mark_line(point=alt.OverlayMarkDef(filled=False, fill='white')) .encode( x=alt.X('Counter', title='Time', sort=None), y=alt.Y(value_col, title=y_label) ) .properties(title=title, width=800, height=400)) chart.save(os.path.join(path, filename)) return df_sorted
[docs] def save_chart(chart, path, filename, embed_options=None): """ Save an Altair chart to HTML file. Args: chart: Altair chart object path (str): Directory path filename (str): Filename including extension embed_options (dict, optional): Options for embedding the chart. Defaults to {'renderer': 'svg'}. """ if embed_options is None: embed_options = {'renderer': 'svg'} filepath = os.path.join(path, filename) chart.save(filepath, embed_options=embed_options)
[docs] def create_and_save_duration_curve(series_data, index_tuples, value_col_name, Date, hour_of_year, path, csv_filename, html_filename, title, y_label): """ Create and save a duration curve with both CSV and HTML outputs. Args: series_data (list): List of values index_tuples: Index tuples (e.g., from model.psn) value_col_name (str): Name for the value column Date: Starting date hour_of_year (str): Hour of year reference path (str): Output directory csv_filename (str): CSV filename html_filename (str): HTML filename title (str): Chart title y_label (str): Y-axis label Returns: pd.DataFrame: Processed dataframe with counter """ # Create series and sort df = pd.Series(series_data, index=pd.MultiIndex.from_tuples(index_tuples)) df = df.sort_values(ascending=False).to_frame(name=value_col_name) # Add date column df['Date'] = df.index.get_level_values(2).map( lambda x: Date + pd.Timedelta(hours=(int(x[1:]) - int(hour_of_year[1:]))) ).strftime('%Y-%m-%d %H:%M:%S') # Reset index and rename df = df.reset_index().rename( columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel'}, inplace=False ) # Save CSV save_to_csv(df, path, csv_filename, index=False) # Create and save plot with counter df['Counter'] = range(len(df)) chart = (alt.Chart(df) .mark_line(point=alt.OverlayMarkDef(filled=False, fill='white')) .encode( x=alt.X('Counter', title='Time', sort=None), y=alt.Y(value_col_name, title=y_label) ) .properties(title=title, width=800, height=400)) save_chart(chart, path, html_filename) return df
def _write_variable_to_csv(path, var, var_name, case_name): """Helper function to write a variable component to CSV.""" filename = f'oM_Result_{var_name}_{case_name}.csv' with open(os.path.join(path, filename), 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(['Name', 'Index', 'Value', 'Lower Bound', 'Upper Bound']) for index in var: writer.writerow([var_name, index, var[index].value, str(var[index].lb), str(var[index].ub)]) def _write_parameter_to_csv(path, par, par_name, case_name): """Helper function to write a parameter component to CSV.""" filename = f'oM_Result_{par_name}_{case_name}.csv' with open(os.path.join(path, filename), 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(['Name', 'Index', 'Value']) if par.is_indexed(): for index in par: value = par[index] if not par.mutable else par[index].value writer.writerow([par_name, index, value]) else: writer.writerow([par_name, 'NA', par.value]) def _write_constraint_to_csv(path, con, con_name, case_name, model): """Helper function to write a constraint component to CSV.""" filename = f'oM_Result_{con_name}_{case_name}.csv' with open(os.path.join(path, filename), 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(['Name', 'Index', 'Value', 'Lower Bound', 'Upper Bound']) if con.is_indexed(): for index in con: writer.writerow([con_name, index, model.dual[con[index]], str(con[index].lb), str(con[index].ub)]) else: writer.writerow([con_name, 'NA', model.dual[con], str(con.lb), str(con.ub)])
[docs] def saving_rawdata(DirName, CaseName, SolverName, model, optmodel, indlog): """ Save raw optimization model data to CSV files. This function iterates through all active variables, parameters, and constraints in the optimization model and saves their data to separate CSV files. - Variables are saved with their values, lower bounds, and upper bounds. - Parameters are saved with their values. - Constraints are saved with their dual values. Args: DirName (str): The directory where the result files will be saved. CaseName (str): The name of the case, used for subdirectory and file naming. SolverName (str): The name of the solver used. model: The optimization model object. optmodel: The concrete optimization model instance. indlog: Logging indicator. Returns: model: The original optimization model object. """ _path = os.path.join(DirName, CaseName) StartTime = time.time() # Write variables for var in optmodel.component_objects(Var, active=True): var_object = getattr(optmodel, str(var)) _write_variable_to_csv(_path, var_object, var.name, CaseName) # Write parameters for par in optmodel.component_objects(Param): par_object = getattr(optmodel, str(par)) _write_parameter_to_csv(_path, par_object, par.name, CaseName) # Write constraints (dual variables) for con in optmodel.component_objects(Constraint, active=True): con_object = getattr(optmodel, str(con)) _write_constraint_to_csv(_path, con_object, con.name, CaseName, model) log_time('-- Total time for outputting the raw data:', StartTime, ind_log=indlog) return model
[docs] def saving_results(DirName, CaseName, Date, model, optmodel, indlog): """ Save processed optimization results to CSV files and generate plots. This function processes the results from the optimization model to generate a series of CSV files and Altair plots for analysis. It covers: - Total costs (hourly and general) - Electricity balance (generation, consumption, flows) - Net and original electricity demand - State of energy for storage systems - Fixed availability of assets - A summary of all key output metrics It also generates Sankey diagrams and duration curves for various metrics. Args: DirName (str): The directory where the result files will be saved. CaseName (str): The name of the case, used for subdirectory and file naming. Date (str or datetime): The starting date for the results, used to calculate time-series data. model: The optimization model object. optmodel: The concrete optimization model instance. Returns: model: The original optimization model object. """ # %% outputting the results # make a condition if Date is a string if isinstance(Date, str): Date = datetime.datetime.strptime(Date, "%Y-%m-%d %H:%M:%S") # splitting the Date into year, month, and day # year = Date.year # month = Date.month # day = Date.day # hour = Date.hour # minute = Date.minute hour_of_year = f't{((Date.timetuple().tm_yday-1) * 24 + Date.timetuple().tm_hour):04d}' _path = os.path.join(DirName, CaseName) StartTime = time.time() print('Objective function value ', model.eTotalSCost.expr()) if sum(model.Par['pEleDemFlexible'][ed] for ed in model.ed) != 0.0: # saving the variable electricity demand and vEleDemand Output_VarMaxDemand = pd.Series(data=[model.Par['pVarMaxDemand'][ed][p,sc,n] for p,sc,n,ed in model.psned], index=pd.Index(model.psned)).to_frame(name='KWh').reset_index() Output_vEleDemand = pd.Series(data=[optmodel.vEleDemand[p,sc,n,ed]() for p,sc,n,ed in model.psned], index=pd.Index(model.psned)).to_frame(name='KWh').reset_index() Output_VarMaxDemand['Type'] = 'BaseDemand' Output_vEleDemand ['Type'] = 'ShiftedDemand' # concatenate the results Output_vDemand = pd.concat([Output_VarMaxDemand, Output_vEleDemand], axis=0).set_index(['level_0', 'level_1', 'level_2', 'level_3', 'Type'], inplace=False) Output_vDemand['Date'] = Output_vDemand.index.get_level_values(2).map(lambda x: Date + pd.Timedelta(hours=(int(x[1:]) - int(hour_of_year[1:])))).strftime('%Y-%m-%d %H:%M:%S') Output_vDemand = Output_vDemand.reset_index().rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Demand'}, inplace=False) save_to_csv(Output_vDemand, _path, f'oM_Result_00_rElectricityDemand_{CaseName}.csv') granular_components = { 'EleNCost': 'vTotalEleNCost', 'EleXCost': 'vTotalEleXCost', 'EleMCost': 'vTotalEleMCost', 'EleOCost': 'vTotalEleOCost', 'HydMCost': 'vTotalHydMCost', 'HydOCost': 'vTotalHydOCost', 'EleXRev': 'vTotalEleXRev', # 'EleOCost': 'vTotalEleOCost', 'EleDCost': 'vTotalEleDCost', 'HydMCost': 'vTotalHydMCost', # 'HydOCost': 'vTotalHydOCost', 'HydDCost': 'vTotalHydDCost', 'EleXRev': 'vTotalEleXRev', 'EleMRev': 'vTotalEleMRev', 'HydMRev': 'vTotalHydMRev', } static_vars = ['vTotalEleNCost', 'vTotalEleXCost', 'vTotalEleXRev'] static_components = {k: v for k, v in granular_components.items() if v in static_vars} dynamic_components = {k: v for k, v in granular_components.items() if v not in static_vars} # Fetch static data static_data = {} for name, attr in static_components.items(): var_object = getattr(optmodel, attr) data = [var_object[p, sc]() for p, sc in model.ps] index = pd.MultiIndex.from_tuples(model.ps, names=['Period', 'Scenario']) static_data[name] = pd.Series(data, index=index) df_static = pd.DataFrame(static_data) # Fetch dynamic data dynamic_data = {} for name, attr in dynamic_components.items(): var_object = getattr(optmodel, attr) data = [var_object[p, sc, n]() * model.Par['pDuration'][p, sc, n] for p, sc, n in model.psn] index = pd.MultiIndex.from_tuples(model.psn, names=['Period', 'Scenario', 'LoadLevel']) dynamic_data[name] = pd.Series(data, index=index) df_dynamic = pd.DataFrame(dynamic_data) # Aggregate dynamic data to static level (by Period, Scenario) df_dynamic_agg = df_dynamic.groupby(['Period', 'Scenario']).sum() # --- Create Hierarchical Aggregations --- # Level 3: Cost/Revenue Categories market_cost = df_dynamic_agg['EleMCost'] + df_dynamic_agg['HydMCost'] # operational_cost = (df_dynamic_agg['EleOCost'] + df_dynamic_agg['HydOCost'] + # df_dynamic_agg['EleDCost'] + df_dynamic_agg['HydDCost']) operational_cost = (df_dynamic_agg['EleOCost'] + df_dynamic_agg['HydOCost']) system_cost = df_static['EleNCost'] + df_static['EleXCost'] market_revenue = df_dynamic_agg['EleMRev'] + df_dynamic_agg['HydMRev'] system_revenue = df_static['EleXRev'] # Level 2: Total Cost/Revenue total_cost = market_cost + operational_cost + system_cost total_revenue = market_revenue + system_revenue # Combine all results into a single DataFrame for static output df_results = pd.DataFrame({ 'MarketCost': market_cost, 'OperationalCost': operational_cost, 'SystemCost': system_cost, 'MarketRevenue': market_revenue, 'SystemRevenue': system_revenue, 'TotalCost': total_cost, 'TotalRevenue': total_revenue }).join(df_static) # aappend original static granular components Output_TotalCost_Static = df_results.stack().to_frame(name='SEK').rename_axis(['Period', 'Scenario', 'Component']).reset_index() Output_TotalCost_Static.to_csv(f"{_path}/oM_Result_01_rTotalCost_Static_{CaseName}.csv", index=False, sep=',') # -- Plotting helper function --- def get(df, comp): out = df.loc[df.Component == comp, 'SEK'] return out.iloc[0] if not out.empty else 0.0 links = [ # COST hierarchy ("TotalCost", "MarketCost", get(Output_TotalCost_Static, "MarketCost")), ("TotalCost", "OperationalCost", get(Output_TotalCost_Static, "OperationalCost")), ("TotalCost", "SystemCost", get(Output_TotalCost_Static, "SystemCost")), ("SystemCost", "EleNCost", get(Output_TotalCost_Static, "EleNCost")), ("SystemCost", "EleXCost", get(Output_TotalCost_Static, "EleXCost")), # REVENUE hierarchy ("TotalRevenue", "MarketRevenue", get(Output_TotalCost_Static, "MarketRevenue")), ("TotalRevenue", "SystemRevenue", get(Output_TotalCost_Static, "SystemRevenue")), ("SystemRevenue", "EleXRev", get(Output_TotalCost_Static, "EleXRev")) ] df_links = pd.DataFrame(links, columns=["source", "target", "value"]) labels = pd.unique(df_links[['source', 'target']].values.ravel('K')).tolist() id_map = {label: i for i, label in enumerate(labels)} df_links['source_id'] = df_links['source'].map(id_map) df_links['target_id'] = df_links['target'].map(id_map) colors = ["#8e24aa" if "Cost" in lbl else "#2e7d32" for lbl in labels] fig = go.Figure(go.Sankey( arrangement="snap", node=dict( pad=15, thickness=12, line=dict(color="black", width=0.4), label=labels, color=colors ), link=dict( source=df_links['source_id'], target=df_links['target_id'], value=df_links['value'] ) )) fig.update_layout( title_text="Cost and Revenue Hierarchy (auto-generated from raw_data)", font=dict(size=13), height=600 ) fig.write_html(f"{_path}/oM_Plot_01_rTotalCost_Sankey_{CaseName}.html", include_plotlyjs="cdn", full_html=True) # --- Prepare Hourly (Dynamic) Output --- def compute_date(x): try: if isinstance(x, str) and x.startswith('t'): return Date + pd.Timedelta(hours=(int(x[1:]) - int(hour_of_year[1:]))) else: return pd.NaT except Exception: return pd.NaT df_dynamic_output = df_dynamic.stack().to_frame(name='SEK').rename_axis(['Period', 'Scenario', 'LoadLevel', 'Component']).reset_index() df_dynamic_output['Date'] = df_dynamic_output['LoadLevel'].map(compute_date).dt.strftime('%Y-%m-%d %H:%M:%S') Output_TotalCost_Hourly = df_dynamic_output Output_TotalCost_Hourly.to_csv(f"{_path}/oM_Result_01_rTotalCost_Hourly_{CaseName}.csv", index=False, sep=',') def extract_cost_or_rev(optmodel, model, var_name, set_name, multiplier=False, timeline=None, revenue=False, component_name=None): """ Generic extractor for cost or revenue components. Parameters: optmodel: Pyomo model with variable values model: Pyomo model with parameter sets var_name (str): Name of the variable (string) set_name (str): Name of the index set ('ps' or 'psn') multiplier (bool): Whether to multiply by duration revenue (bool): If True, multiply values by -1 component_name (str): Label for the output DataFrame Returns: pd.DataFrame: DataFrame with Period, Scenario, SEK, and Component columns """ var = getattr(optmodel, var_name) index_set = getattr(model, set_name) index_len = len(next(iter(index_set))) # Compute values if multiplier and index_len == 3 and timeline == "Hourly": data = [var[p, sc, n]() * model.Par['pDuration'][p, sc, n] for p, sc, n in index_set] df = pd.DataFrame(index_set, columns=['Period', 'Scenario', 'Hour']) elif multiplier and index_len == 3 and timeline == "Daily": data = [var[p, sc, d]() for p, sc, d in index_set] df = pd.DataFrame(index_set, columns=['Period', 'Scenario', 'Day']) else: data = [var[p, sc]() for p, sc in index_set] df = pd.DataFrame(index_set, columns=['Period', 'Scenario']) df['SEK'] = data # Aggregate if necessary (collapse over Time) if 'Hour' or 'Day' in df.columns: df = df.groupby(['Period', 'Scenario'], as_index=False)['SEK'].sum() # Apply sign convention if revenue: df['SEK'] *= -1 # Add component label df['Component'] = component_name return df # === Extract all components === Output_vTotalEleMrkDACost = extract_cost_or_rev(optmodel, model, 'vTotalEleMrkDACost', 'psn', multiplier=True, timeline="Hourly", revenue=False, component_name='Day-Ahead Market Cost' ) Output_vTotalEleNetUseFixCost = extract_cost_or_rev(optmodel, model, 'vTotalEleNetUseFixCost', 'ps', revenue=False, component_name='Network Fixed Cost' ) Output_vTotalEleNetUseVarCost = extract_cost_or_rev(optmodel, model, 'vTotalEleNetUseVarCost', 'ps', revenue=False, component_name='Network Variable Cost' ) Output_vTotalElePeakCost = extract_cost_or_rev(optmodel, model, 'vTotalElePeakCost', 'ps', revenue=False, component_name='Power Peak Cost' ) Output_vTotalEleEnergyTaxCost = extract_cost_or_rev(optmodel, model, 'vTotalEleEnergyTaxCost', 'ps', revenue=False, component_name='Energy Tax Cost' ) Output_vTotalEleDCost = extract_cost_or_rev(optmodel, model, 'vTotalEleDCost', 'psd', multiplier=True, timeline="Daily", revenue=False, component_name='Depth of Discharge Cost' ) Output_vTotalEleMrkDARev = extract_cost_or_rev(optmodel, model, 'vTotalEleMrkDARev', 'psn', multiplier=True, timeline="Hourly", revenue=True, component_name='Day-Ahead Market Revenue') Output_vTotalEleFCRDUpRev = extract_cost_or_rev(optmodel, model, 'vTotalEleFCRDUpRev', 'psn', multiplier=True, timeline="Hourly", revenue=True, component_name='FCR-D Upwards Revenue' ) Output_vTotalEleFCRDDwRev = extract_cost_or_rev(optmodel, model, 'vTotalEleFCRDDwRev', 'psn', multiplier=True, timeline="Hourly", revenue=True, component_name='FCR-D Downwards Revenue' ) Output_vTotalEleFCRNRev = extract_cost_or_rev(optmodel, model, 'vTotalEleFCRNRev', 'psn', multiplier=True, timeline="Hourly", revenue=True, component_name='FCR-N Revenue' ) # === Combine and export === Output_AdditionalCosts = pd.concat([Output_vTotalEleMrkDACost, Output_vTotalEleNetUseFixCost, Output_vTotalEleNetUseVarCost, Output_vTotalElePeakCost, Output_vTotalEleEnergyTaxCost, Output_vTotalEleDCost, Output_vTotalEleMrkDARev, Output_vTotalEleFCRDUpRev, Output_vTotalEleFCRDDwRev, Output_vTotalEleFCRNRev], ignore_index=True) Output_AdditionalCosts.to_csv(f"{_path}/oM_Result_01_rObjFunComponents_{CaseName}.csv", index=False) df = Output_AdditionalCosts.copy() # --- 1. Separate cost and revenue --- # type -- Cost is Cost is in the name of the component, and Revenue otherwise df["Type"] = df["Component"].apply(lambda x: "Cost" if "Cost" in x else "Revenue") # df["Type"] = df["SEK"].apply(lambda x: "Cost" if x >= 0 else "Revenue") df["AbsSEK"] = df["SEK"].abs() # --- 2. Compute percentages within each Type --- df["Percentage"] = df.groupby("Type")["AbsSEK"].transform(lambda x: x / x.sum()) # --- 3. Split for plotting --- df_cost = df[df["Type"] == "Cost"] df_rev = df[df["Type"] == "Revenue"] def pie_chart(df_sub, title): base = alt.Chart(df_sub).encode(theta=alt.Theta("AbsSEK:Q", type="quantitative", stack=True), color=alt.Color("Component:N", type="nominal", legend=alt.Legend(title=title))).properties(width=400, height=400) pie = base.mark_arc(outerRadius=120) text = base.mark_text(radius=150, size=15).encode(text=alt.Text("Percentage:Q", format=".1%")) chart = pie+text # chart = chart.resolve_scale(theta="independent") chart = alt.layer(pie, text, data=df_sub).resolve_scale(theta="independent") return chart # --- 5. Build charts --- chart_cost = pie_chart(df_cost, "Cost Breakdown") chart_rev = pie_chart(df_rev, "Revenue Breakdown") # --- 6. Show side by side --- main_chart = (chart_cost | chart_rev).resolve_scale(color="independent") # Save the chart save_chart(main_chart, _path, f'oM_Plot_01_rObjFunComponents_{CaseName}.html') # %% outputting the electrical energy balance #%% Power balance per period, scenario, and load level # incoming and outgoing lines (lin) (lout) lin = defaultdict(list) lout = defaultdict(list) for ni,nf,cc in model.ela: lin [nf].append((ni,cc)) lout [ni].append((nf,cc)) hin = defaultdict(list) hout = defaultdict(list) for ni,nf,cc in model.hpa: hin [nf].append((ni,cc)) hout [ni].append((nf,cc)) sPNND = [(p,sc,n,nd) for p,sc,n,nd in model.psn*model.nd ] sPNNDGT = [(p,sc,n,nd,gt) for p,sc,n,nd,gt in sPNND*model.gt ] # sPNNDEG = [(p,sc,n,nd,eg) for p,sc,n,nd,eg in sPNND*model.eg if (nd,eg ) in model.n2eg] # sPNNDED = [(p,sc,n,nd,ed) for p,sc,n,nd,ed in sPNND*model.ed if (nd, ed) in model.n2ed] # sPNNDER = [(p,sc,n,nd,er) for p,sc,n,nd,er in sPNND*model.er if (nd, er) in model.n2er] OutputResults1 = pd.Series(data=[ sum(optmodel.vEleTotalOutput [p,sc,n,eg ]() * model.Par['pDuration'][p,sc,n] for eg in model.eg if (nd,eg ) in model.n2eg and (gt,eg ) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='GenerationEle' ).reset_index().pivot_table(index=['level_0','level_1','level_2','level_3'], columns='level_4', values='GenerationEle' , aggfunc='sum') # OutputResults1 = pd.Series(data=[ sum(optmodel.vEleTotalOutput [p,sc,n,eg ]() * model.Par['pDuration'][p,sc,n] for eg in model.eg if (nd,eg ) in model.n2eg and (gt,eg ) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='GenerationEle' ).reset_index().groupby(['level_0','level_1','level_2','level_3'])[['GenerationEle']].sum().reset_index().rename(columns={'GenerationEle': 'GenerationEle'}) OutputResults2 = pd.Series(data=[-sum(optmodel.vEleTotalCharge [p,sc,n,egs ]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='ConsumptionEle' ).reset_index().pivot_table(index=['level_0','level_1','level_2','level_3'], columns='level_4', values='ConsumptionEle' , aggfunc='sum') OutputResults3 = pd.Series(data=[-sum(optmodel.vEleTotalCharge [p,sc,n,e2h ]() * model.Par['pDuration'][p,sc,n] for e2h in model.e2h if (nd,e2h) in model.n2hg and (gt,e2h) in model.t2hg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='ConsumptionEle2Hyd').reset_index().pivot_table(index=['level_0','level_1','level_2','level_3'], columns='level_4', values='ConsumptionEle2Hyd', aggfunc='sum') OutputResults4 = pd.Series(data=[ sum(optmodel.vENS [p,sc,n,ed ]() * model.Par['pDuration'][p,sc,n] for ed in model.ed if (nd,ed ) in model.n2ed ) for p,sc,n,nd in sPNND ], index=pd.Index(sPNND )).to_frame(name='ENS' ) OutputResults5 = pd.Series(data=[-sum(optmodel.vEleDemand [p,sc,n,ed ]() * model.Par['pDuration'][p,sc,n] for ed in model.ed if (nd,ed ) in model.n2ed ) for p,sc,n,nd in sPNND ], index=pd.Index(sPNND )).to_frame(name='ElectricityDemand' ) OutputResults6 = pd.Series(data=[ optmodel.vEleImport [p,sc,n,nd ]() * model.Par['pDuration'][p,sc,n] for p,sc,n,nd in sPNND ], index=pd.Index(sPNND )).to_frame(name='ElectricityImport' ) OutputResults7 = pd.Series(data=[ -optmodel.vEleExport [p,sc,n,nd ]() * model.Par['pDuration'][p,sc,n] for p,sc,n,nd in sPNND ], index=pd.Index(sPNND )).to_frame(name='ElectricityExport' ) OutputResults8 = pd.Series(data=[-sum(optmodel.vEleNetFlow [p,sc,n,nd,nf,cc]() * model.Par['pDuration'][p,sc,n] for (nf,cc) in lout [nd]) for p,sc,n,nd in sPNND ], index=pd.Index(sPNND )).to_frame(name='PowerFlowOut' ) OutputResults9 = pd.Series(data=[ sum(optmodel.vEleNetFlow [p,sc,n,ni,nd,cc]() * model.Par['pDuration'][p,sc,n] for (ni,cc) in lin [nd]) for p,sc,n,nd in sPNND ], index=pd.Index(sPNND )).to_frame(name='PowerFlowIn' ) OutputResults = pd.concat([OutputResults1, OutputResults2, OutputResults3, OutputResults4, OutputResults5, OutputResults6, OutputResults7, OutputResults8, OutputResults9], axis=1) OutputResults = OutputResults.T.groupby(level=0).sum().T OutputResults = OutputResults.stack().to_frame(name='MWh') # set the index names OutputResults.index.names = ['Period', 'Scenario', 'LoadLevel', 'Node', 'Component'] OutputResults = OutputResults.groupby(['Period', 'Scenario', 'LoadLevel', 'Node', 'Component'])[['MWh']].sum() # select the third level of the index and create a new column date using the Date as an initial date OutputResults['Date'] = OutputResults.index.get_level_values(2).map(lambda x: Date + pd.Timedelta(hours=(int(x[1:]) - int(hour_of_year[1:])))).strftime('%Y-%m-%d %H:%M:%S') Output_EleBalance = OutputResults.set_index('Date', append=True).rename_axis(['Period', 'Scenario', 'LoadLevel', 'Node', 'Component', 'Date'], axis=0).reset_index().rename(columns={0: 'MWh'}, inplace=False) # scaling the results to KWh Output_EleBalance['kWh'] = (1/model.factor1) * Output_EleBalance['MWh'] save_to_csv(Output_EleBalance, _path, f'oM_Result_02_rElectricityBalance_{CaseName}.csv') model.Output_EleBalance = Output_EleBalance # removing the component 'PowerFlowOut' and 'PowerFlowIn' from the Output_EleBalance Output_EleBalance = Output_EleBalance[~Output_EleBalance['Component'].isin(['PowerFlowOut', 'PowerFlowIn', 'Electrolyzer', 'H2ESS'])] # chart for the electricity balance using Altair and bars brush = alt.selection_interval(encodings=['x']) # Base chart for KWh with the primary y-axis main_chart = alt.Chart(Output_EleBalance).mark_bar().encode( # x='Date:T', x=alt.X('Date:T', axis=alt.Axis(title='', labelAngle=-90, format="%a, %b %d, %H:%M", tickCount=30, labelLimit=1000)), y=alt.Y('sum(kWh):Q', axis=alt.Axis(title='kWh')), color='Component:N' ).properties( width=800, height=400 ).transform_filter(brush) slider_chart = alt.Chart(Output_EleBalance).mark_bar().encode( x=alt.X('Date:T', axis=alt.Axis(title='', labelAngle=-90, format="%a, %b %d, %H:%M", tickCount=30, labelLimit=1000)), y=alt.Y('sum(kWh):Q', axis=alt.Axis(title='kWh')), color='Component:N' ).properties( width=800, height=100 ).add_params(brush) kwh_chart = main_chart & slider_chart save_chart(kwh_chart, _path, f'oM_Plot_02_rElectricityBalance_{CaseName}.html') log_time('-- Total time for outputting the electricity balance:', StartTime, ind_log=indlog) StartTime = time.time() # net demand by filtering Solar-PV, BESS, and ElectricityDemand in Output_EleBalance, column Component Output_NetDemand = Output_EleBalance[Output_EleBalance['Component'].isin(['BESS', 'Solar-PV', 'EV', 'ElectricityDemand'])] # aggregate the columns 'Period', 'Scenario', 'LoadLevel', 'Date', 'MWh' and 'KWh' Output_NetDemand = Output_NetDemand.groupby(['Period', 'Scenario', 'LoadLevel', 'Date'])[['MWh', 'kWh']].sum().reset_index() # changing the sign of the values in the column 'MWh' and 'KWh' Output_NetDemand['MWh'] = Output_NetDemand['MWh'].apply(lambda x: x) Output_NetDemand['kWh'] = Output_NetDemand['kWh'].apply(lambda x: x) # save the results to a csv file save_to_csv(Output_NetDemand, _path, f'oM_Result_03_rElectricityNetDemand_{CaseName}.csv') log_time('-- Total time for outputting the net electricity demand:', StartTime, ind_log=indlog) StartTime = time.time() model.Output_NetDemand = Output_NetDemand Output_NetDemand['Type'] ='NetDemand' Output_OrgDemand = Output_EleBalance[Output_EleBalance['Component'].isin(['ElectricityDemand'])] Output_OrgDemand = Output_OrgDemand.groupby(['Period', 'Scenario', 'LoadLevel', 'Date'])[['MWh', 'kWh']].sum().reset_index() # changing the sign of the values in the column 'MWh' and 'KWh' Output_OrgDemand['MWh'] = Output_OrgDemand['MWh'].apply(lambda x: x) Output_OrgDemand['kWh'] = Output_OrgDemand['kWh'].apply(lambda x: x) Output_OrgDemand['Type'] ='OrgDemand' # series of the electricity cost Output_EleCost = pd.Series(data=[(model.Par['pVarEnergyCost'] [er][p,sc,n] * model.Par['pEleRetBuyingRatio'][er] + model.Par['pEleRetOverforingsavgift'][er] + model.Par['pEleRetPaslag'][er] + model.Par['pEleRetEnergyTax'][er]) for p,sc,n,er in model.psner], index=pd.Index(model.psner)).to_frame(name='SEK/kWh').reset_index() Output_EleCost = Output_EleCost.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Component'}, inplace=False).set_index(['Period', 'Scenario', 'LoadLevel', 'Component'], inplace=False) # select the third level of the index and create a new column date using the Date as a initial date Output_EleCost['Date'] = Output_EleCost.index.get_level_values(2).map(lambda x: Date + pd.Timedelta(hours=(int(x[1:]) - int(hour_of_year[1:])))).strftime('%Y-%m-%d %H:%M:%S') Output_EleCost = Output_EleCost.reset_index().groupby(['Period', 'Scenario', 'LoadLevel', 'Date'])[['SEK/kWh']].sum().reset_index() Output_EleCost['Type'] ='ElectricityCost' # merge the results of the original demand with the net demand and electricity cost Output_Demand = pd.concat([Output_NetDemand, Output_OrgDemand], axis=0) # save the results to a csv file save_to_csv(Output_Demand, _path, f'oM_Result_04_rAllElectricityDemand_{CaseName}.csv') model.Output_Demand = Output_Demand log_time('-- Total time for outputting the all electricity demand:', StartTime, ind_log=indlog) StartTime = time.time() # Base chart for KWh with the primary y-axis # --- Common formatting options --- x_axis = alt.X('Date:T', axis=alt.Axis(title='', labelAngle=-90, format='%a, %b %d, %H:%M', tickCount=30, labelLimit=1000)) # --- KWh Chart (Main Energy Use) --- kwh_chart = ( alt.Chart(Output_Demand) .mark_line(color='steelblue', point=alt.OverlayMarkDef(filled=False, fill='white')) .encode( x=x_axis, y=alt.Y('KWh:Q', axis=alt.Axis(title='Energy [kWh]')), color=alt.Color('Type:N', legend=alt.Legend(title='Type')) ) ) # --- SEK/kWh Chart (Cost) --- eur_chart = ( alt.Chart(Output_EleCost) .mark_line(color='orange', strokeDash=[5, 5], point=alt.OverlayMarkDef(filled=False, fill='white')) .encode( x=x_axis, y=alt.Y('SEK/kWh:Q', axis=alt.Axis(title='Price [SEK/kWh]', orient='right')), color=alt.Color('Type:N', legend=None) ) ) # --- Combine charts with independent Y-axes --- main_chart = ( alt.layer(kwh_chart, eur_chart) .resolve_scale(y='independent') .properties(width=900, height=400, title='Electricity Demand and Price Over Time') ) # --- Save chart as HTML (SVG embedded) --- save_chart(main_chart, _path, f'oM_Plot_03_rEleDemand_{CaseName}.html') if sum(model.Par['pEleDemFlexible'][ed] for ed in model.ed) != 0.0: vDemand_chart = alt.Chart(Output_vDemand).mark_line(color='blue', point=alt.OverlayMarkDef(filled=False, fill="white")).encode( # x='Date:T', x=alt.X('Date:T', axis=alt.Axis(title='', labelAngle=-90, format="%a, %b %d, %H:%M", tickCount=30, labelLimit=1000)), y=alt.Y('kWh:Q', axis=alt.Axis(title='kWh')), color='Type:N' ) # Combine the two charts chart2 = alt.layer(vDemand_chart, eur_chart).resolve_scale( y='independent' # Ensures each chart has its own y-axis ).properties( width=800, height=400 ).interactive() # Save the chart to an HTML file chart2.save(_path + '/oM_Plot_04_rEleFlexDemand_' + CaseName + '.html', embed_options={'renderer':'svg'}) # %% outputting the state of charge of the battery energy storage system #%% State of charge of the battery energy storage system per period, scenario, and load level sPSNEGS = [(p, sc, n, egs) for p, sc, n, egs in model.ps * model.negs if (p, egs) in model.pegs] if sPSNEGS: OutputResults1 = pd.Series(data=[ optmodel.vEleInventory[p,sc,n,egs]() for p,sc,n,egs in sPSNEGS], index=pd.Index(sPSNEGS)).to_frame(name='SOC').reset_index().pivot_table(index=['level_0','level_1','level_2'], columns='level_3', values='SOC', aggfunc='sum') OutputResults1['Date'] = OutputResults1.index.get_level_values(2).map(lambda x: Date + pd.Timedelta(hours=(int(x[1:]) - int(hour_of_year[1:])))).strftime('%Y-%m-%d %H:%M:%S') Output_EleSOE = OutputResults1.set_index('Date', append=True).rename_axis(['Period', 'Scenario', 'LoadLevel', 'Date'], axis=0).stack().reset_index().rename(columns={'level_3': 'Component', 0: 'SOE'}, inplace=False) Output_EleSOE['SOE'] *= (1/model.factor1) save_to_csv(Output_EleSOE, _path, f'oM_Result_05_rEleStateOfEnergy_{CaseName}.csv') log_time('-- Total time for outputting the electrical state of energy:', StartTime, ind_log=indlog) StartTime = time.time() # plot # Base chart for SOC with the primary y-axis and dashed line style ele_soe_chart = alt.Chart(Output_EleSOE).mark_line(color='green', strokeDash=[5, 5], point=alt.OverlayMarkDef(filled=False, fill="white")).encode( x=alt.X('Date:T', axis=alt.Axis(title='', labelAngle=-90, format="%a, %b %d, %H:%M", tickCount=30, labelLimit=1000, labelFontSize=16, titleFontSize=18)), y=alt.Y('SOE:Q', axis=alt.Axis(title='SOE', labelFontSize=16, titleFontSize=18)), color=alt.Color('Component:N', legend=alt.Legend(title='Component', labelFontSize=16, titleFontSize=18)) ) if len(model.egv): # Base chart of VarFixedAvailability with the primary y-axis Output_FixedAvailability = model.Par['pVarFixedAvailability'].loc[model.psn] Output_FixedAvailability['Date'] = Output_FixedAvailability.index.get_level_values(2).map(lambda x: Date + pd.Timedelta(hours=(int(x[1:]) - int(hour_of_year[1:])))).strftime('%Y-%m-%d %H:%M:%S') Output_FixedAvailability = Output_FixedAvailability.set_index('Date', append=True).rename_axis(['Period', 'Scenario', 'LoadLevel', 'Date'], axis=0).stack().reset_index().rename(columns={'level_4': 'Component', 0: 'FixedAvailability'}, inplace=False) save_to_csv(Output_FixedAvailability, _path, f'oM_Result_06_rFixedAvailability_{CaseName}.csv') log_time('-- Total time for outputting the electrical fixed availability:', StartTime, ind_log=indlog) StartTime = time.time() # filter component 'EV_01' and 'EV_02' from the Output_FixedAvailability Output_FixedAvailability = Output_FixedAvailability[Output_FixedAvailability['Component'].isin([list(model.egs)[0]])] # Base chart for FixedAvailability with the primary y-axis and dashed line style ele_fAv_chart = alt.Chart(Output_FixedAvailability).mark_point(color='red').encode( x=alt.X('Date:T', axis=alt.Axis(title='', labelAngle=-90, format="%a, %b %d, %H:%M", tickCount=30, labelLimit=1000, labelFontSize=16, titleFontSize=18)), y=alt.Y('FixedAvailability:Q', axis=alt.Axis(title='FixedAvailability', orient='right', labelFontSize=16, titleFontSize=18)), ) chart = alt.layer(ele_soe_chart, ele_fAv_chart).resolve_scale( y='independent' # Ensures each chart has its own y-axis ).properties( width=800, height=400 ).interactive() # Save the chart to an HTML file save_chart(chart, _path, f'oM_Plot_05_rEleStateOfEnergy_{CaseName}.html') # --- Ensure 'Date' is datetime --- Output_EleSOE['Date'] = pd.to_datetime(Output_EleSOE['Date']) Output_FixedAvailability['Date'] = pd.to_datetime(Output_FixedAvailability['Date']) # --- Filter for first 7 days --- start_date = Output_EleSOE['Date'].min() end_date = start_date + pd.Timedelta(days=7) soe_data_7days = Output_EleSOE[(Output_EleSOE['Date'] >= start_date) & (Output_EleSOE['Date'] < end_date)] ava_data_7days = Output_FixedAvailability[(Output_FixedAvailability['Date'] >= start_date) & (Output_FixedAvailability['Date'] < end_date)] ele_soe_chart = alt.Chart(soe_data_7days).mark_line(color='green', strokeDash=[5, 5], point=alt.OverlayMarkDef(filled=False, fill="white")).encode( x=alt.X('Date:T', axis=alt.Axis(title='', labelAngle=-90, format="%a, %b %d, %H:%M", tickCount=30, labelLimit=1000, labelFontSize=16, titleFontSize=18)), y=alt.Y('SOE:Q', axis=alt.Axis(title='SOE', labelFontSize=16, titleFontSize=18)), color = alt.Color('Component:N', legend=alt.Legend(title='Component', labelFontSize=16, titleFontSize=18)) ) ele_fAv_chart = alt.Chart(ava_data_7days).mark_point(color='red').encode( x=alt.X('Date:T', axis=alt.Axis(title='', labelAngle=-90, format="%a, %b %d, %H:%M", tickCount=30, labelLimit=1000, labelFontSize=16, titleFontSize=18)), y=alt.Y('FixedAvailability:Q', axis=alt.Axis(title='FixedAvailability', orient='right', labelFontSize=16, titleFontSize=18)), ) chart = alt.layer(ele_soe_chart, ele_fAv_chart).resolve_scale( y='independent' # Ensures each chart has its own y-axis ).properties( width=800, height=400 ).interactive() # Save the chart to an HTML file save_chart(chart, _path, f'oM_Plot_05_rEleStateOfEnergy_7days_{CaseName}.html') # Creating dataframe with outputs like electricity buy, electricity sell, total production, total consumption, Inventory, energy outflows, VarStartUp, VarShutDown, FixedAvailability, EleDemand, ElectricityCost, ElectricityPrice # series of electricity production OutputResults1a = pd.Series(data=[ (sum((optmodel.vEleTotalOutput[p,sc,n,egr]()) * model.Par['pDuration'][p,sc,n] for egr in model.egr if (nd,egr) in model.n2eg and (gt,egr) in model.t2eg) + sum((optmodel.vEleTotalOutput2ndBlock[p,sc,n,egt]()) * model.Par['pDuration'][p,sc,n] for egt in model.egt if (nd,egt) in model.n2eg and (gt,egt) in model.t2eg) + sum(optmodel.vEleTotalOutput2ndBlock[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs ) in model.n2eg and (gt,egs ) in model.t2eg)) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleGeneration' ).reset_index() OutputResults1a['Component'] = 'Production/Discharge [kWh]' OutputResults1a['EleGeneration'] *= (1/model.factor1) OutputResults1a = OutputResults1a.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults1a = OutputResults1a.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleGeneration', aggfunc='sum') # series of electricity consumption OutputResults2a = pd.Series(data=[-sum((optmodel.vEleTotalCharge2ndBlock[p,sc,n,egs]()) * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleConsumption').reset_index() OutputResults2a['Component'] = 'Consumption/Charge [kWh]' OutputResults2a['EleConsumption'] *= (1/model.factor1) OutputResults2a = OutputResults2a.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults2a = OutputResults2a.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleConsumption', aggfunc='sum') # series of FCR-D upwards when the battery is charging OutputResults1b = pd.Series(data=[ sum(optmodel.vEleFreqContReserveDisUpwardBid[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRDUp').reset_index() OutputResults1b['Component'] = 'FCR-D Upward [kWh]' OutputResults1b['EleFCRDUp'] *= (1/model.factor1) OutputResults1b = OutputResults1b.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults1b = OutputResults1b.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRDUp', aggfunc='sum') # series of FCR-D downwards when the battery is charging OutputResults2b = pd.Series(data=[-sum(optmodel.vEleFreqContReserveDisDownwardBid[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRDDown').reset_index() OutputResults2b['Component'] = 'FCR-D Downward [kWh]' OutputResults2b['EleFCRDDown'] *= (1/model.factor1) OutputResults2b = OutputResults2b.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults2b = OutputResults2b.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRDDown', aggfunc='sum') # series of FCR-D Upward Bid OutputResults1Bid = pd.Series(data=[ sum(optmodel.vEleFreqContReserveDisUpwardBid[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRDUp').reset_index() OutputResults1Bid['Component'] = 'FCR-D Upward Bid [kW]' OutputResults1Bid['EleFCRDUp'] *= (1/model.factor1) OutputResults1Bid = OutputResults1Bid.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults1Bid = OutputResults1Bid.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRDUp', aggfunc='sum') # series of FCR-D Downward Bid OutputResults2Bid = pd.Series(data=[ sum(optmodel.vEleFreqContReserveDisDownwardBid[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRDDown').reset_index() OutputResults2Bid['Component'] = 'FCR-D Downward Bid [kW]' OutputResults2Bid['EleFCRDDown'] *= (1/model.factor1) OutputResults2Bid = OutputResults2Bid.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults2Bid = OutputResults2Bid.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRDDown', aggfunc='sum') # series of FCR-N Bid OutputResults3Bid = pd.Series(data=[ sum(optmodel.vEleFreqContReserveNorBid[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRN').reset_index() OutputResults3Bid['Component'] = 'FCR-N Bid [kW]' OutputResults3Bid['EleFCRN'] *= (1/model.factor1) OutputResults3Bid = OutputResults3Bid.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults3Bid = OutputResults3Bid.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRN', aggfunc='sum') #################################################################################### # series of FCR-D upwards when the battery is discharging OutputResults1c = pd.Series(data=[ sum(optmodel.vEleFreqContReserveDisUpDis[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRDUpDis').reset_index() OutputResults1c['Component'] = 'FCR-D Upward Discharge [kWh]' OutputResults1c['EleFCRDUpDis'] *= (1/model.factor1) OutputResults1c = OutputResults1c.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults1c = OutputResults1c.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRDUpDis', aggfunc='sum') # series of FCR-D downwards when the battery is discharging OutputResults1d = pd.Series(data=[-sum(optmodel.vEleFreqContReserveDisDownDis[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRDDwDis').reset_index() OutputResults1d['Component'] = 'FCR-D Downward Discharge [kWh]' OutputResults1d['EleFCRDDwDis'] *= (1/model.factor1) OutputResults1d = OutputResults1d.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults1d = OutputResults1d.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRDDwDis', aggfunc='sum') #################################################################################### # series of FCR-N upwards when the battery is discharging OutputResults1e = pd.Series(data=[ sum(optmodel.vEleFreqContReserveNorUpDis[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRNUpDis').reset_index() OutputResults1e['Component'] = 'FCR-N Upward Discharge [kWh]' OutputResults1e['EleFCRNUpDis'] *= (1/model.factor1) OutputResults1e = OutputResults1e.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) # series of FCR-N downwards when the battery is discharging OutputResults1e = OutputResults1e.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRNUpDis', aggfunc='sum') OutputResults1f = pd.Series(data=[-sum(optmodel.vEleFreqContReserveNorDownDis[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRNDwDis').reset_index() OutputResults1f['Component'] = 'FCR-N Downward Discharge [kWh]' OutputResults1f['EleFCRNDwDis'] *= (1/model.factor1) OutputResults1f = OutputResults1f.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults1f = OutputResults1f.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRNDwDis', aggfunc='sum') #################################################################################### # series of FCR-D upwards when the battery is charging OutputResults2c = pd.Series(data=[ sum(optmodel.vEleFreqContReserveDisUpCha[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRDUpChg').reset_index() OutputResults2c['Component'] = 'FCR-D Upward Charge [kWh]' OutputResults2c['EleFCRDUpChg'] *= (1/model.factor1) OutputResults2c = OutputResults2c.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults2c = OutputResults2c.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRDUpChg', aggfunc='sum') # series of FCR-D downwards when the battery is charging OutputResults2d = pd.Series(data=[-sum(optmodel.vEleFreqContReserveDisDownCha[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRDDwChg').reset_index() OutputResults2d['Component'] = 'FCR-D Downward Charge [kWh]' OutputResults2d['EleFCRDDwChg'] *= (1/model.factor1) OutputResults2d = OutputResults2d.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults2d = OutputResults2d.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRDDwChg', aggfunc='sum') #################################################################################### # series of FCR-N upwards when the battery is charging OutputResults2e = pd.Series(data=[ sum(optmodel.vEleFreqContReserveNorUpCha[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRNUpChg').reset_index() OutputResults2e['Component'] = 'FCR-N Upward Charge [kWh]' OutputResults2e['EleFCRNUpChg'] *= (1/model.factor1) OutputResults2e = OutputResults2e.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults2e = OutputResults2e.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRNUpChg', aggfunc='sum') # series of FCR-N downwards when the battery is charging OutputResults2f = pd.Series(data=[-sum(optmodel.vEleFreqContReserveNorDownCha[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleFCRNDwChg').reset_index() OutputResults2f['Component'] = 'FCR-N Downward Charge [kWh]' OutputResults2f['EleFCRNDwChg'] *= (1/model.factor1) OutputResults2f = OutputResults2f.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults2f = OutputResults2f.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleFCRNDwChg', aggfunc='sum') #################################################################################### # series of electricity inventory OutputResults3 = pd.Series(data=[ sum(optmodel.vEleInventory[p,sc,n,egs]() for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleInventory').reset_index() OutputResults3['Component'] = 'Inventory [kWh]' OutputResults3['EleInventory'] *= (1/model.factor1) OutputResults3 = OutputResults3.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults3 = OutputResults3.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleInventory', aggfunc='sum') # series of ENS OutputResults4 = pd.Series(data=[ sum(optmodel.vENS[p,sc,n,ed]() * model.Par['pDuration'][p,sc,n] for ed in model.ed if (nd,ed) in model.n2ed) for p,sc,n,nd in sPNND], index=pd.Index(sPNND)).to_frame(name='ENS').reset_index() OutputResults4['Component'] = 'ENS [kWh]' OutputResults4['ENS'] *= (1/model.factor1) OutputResults4 = OutputResults4.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 0: 'Value'}, inplace=False) OutputResults4 = OutputResults4.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Node'], values='ENS', aggfunc='sum') # series of energy outflows OutputResults5 = pd.Series(data=[-sum(optmodel.vEleEnergyOutflows[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleEnergyOutflows').reset_index() OutputResults5['Component'] = 'Outflows/Driving [kWh]' OutputResults5['EleEnergyOutflows'] *= (1/model.factor1) OutputResults5 = OutputResults5.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults5 = OutputResults5.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleEnergyOutflows', aggfunc='sum') # series of load home OutputResults6 = pd.Series(data=[-sum(optmodel.vEleDemand[p,sc,n,ed]() * model.Par['pDuration'][p,sc,n] for ed in model.ed if (nd,ed) in model.n2ed) for p,sc,n,nd in sPNND], index=pd.Index(sPNND)).to_frame(name='EleDemand').reset_index() OutputResults6['Component'] = 'Load/Home [kWh]' OutputResults6['EleDemand'] *= (1/model.factor1) OutputResults6 = OutputResults6.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 0: 'Value'}, inplace=False) OutputResults6 = OutputResults6.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Node'], values='EleDemand', aggfunc='sum') # series of the electricity buy OutputResults7 = pd.Series(data=[ sum(optmodel.vEleBuy[p,sc,n,er]() * model.Par['pDuration'][p,sc,n] for er in model.er if (nd,er) in model.n2er) for p,sc,n,nd in sPNND], index=pd.Index(sPNND)).to_frame(name='EleBuy').reset_index() OutputResults7['Component'] = 'Electricity Buy [kWh]' OutputResults7['EleBuy'] *= (1/model.factor1) OutputResults7 = OutputResults7.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 0: 'Value'}, inplace=False) OutputResults7 = OutputResults7.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Node'], values='EleBuy', aggfunc='sum') # series of the electricity sell OutputResults8 = pd.Series(data=[-sum(optmodel.vEleSell[p,sc,n,er]() * model.Par['pDuration'][p,sc,n] for er in model.er if (nd,er) in model.n2er) for p,sc,n,nd in sPNND], index=pd.Index(sPNND)).to_frame(name='EleSell').reset_index() OutputResults8['Component'] = 'Electricity Sell [kWh]' OutputResults8['EleSell'] *= (1/model.factor1) OutputResults8 = OutputResults8.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 0: 'Value'}, inplace=False) OutputResults8 = OutputResults8.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Node'], values='EleSell', aggfunc='sum') # series of the electricity import OutputResults7a = pd.Series(data=[ optmodel.vEleImport[p,sc,n,nd]() * model.Par['pDuration'][p,sc,n] for p,sc,n,nd in sPNND], index=pd.Index(sPNND)).to_frame(name='EleImport').reset_index() OutputResults7a['Component'] = 'Electricity Import [kWh]' OutputResults7a['EleImport'] *= (1/model.factor1) OutputResults7a = OutputResults7a.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 0: 'Value'}, inplace=False) OutputResults7a = OutputResults7a.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Node'], values='EleImport', aggfunc='sum') # series of the electricity export OutputResults8a = pd.Series(data=[-optmodel.vEleExport[p,sc,n,nd]() * model.Par['pDuration'][p,sc,n] for p,sc,n,nd in sPNND], index=pd.Index(sPNND)).to_frame(name='EleExport').reset_index() OutputResults8a['Component'] = 'Electricity Export [kWh]' OutputResults8a['EleExport'] *= (1/model.factor1) OutputResults8a = OutputResults8a.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 0: 'Value'}, inplace=False) OutputResults8a = OutputResults8a.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Node'], values='EleExport', aggfunc='sum') # series of the spot price OutputResults9 = pd.Series(data=[ model.Par['pVarEnergyCost' ] [er][p,sc,n] for p,sc,n,er in model.psner], index=pd.Index(model.psner)).to_frame(name='SEK/kWh').reset_index() OutputResults9['Component'] = 'Spot Price [SEK/kWh]' OutputResults9 = OutputResults9.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Retailer', 0: 'Value'}, inplace=False) OutputResults9 = OutputResults9.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Retailer'], values='SEK/kWh', aggfunc='sum') # series of the electricity cost OutputResults10 = pd.Series(data=[(model.Par['pVarEnergyCost'] [er][p,sc,n] * model.Par['pEleRetBuyingRatio'][er] + model.Par['pEleRetOverforingsavgift'][er] + model.Par['pEleRetPaslag'][er] + model.Par['pEleRetEnergyTax'][er]) for p,sc,n,er in model.psner], index=pd.Index(model.psner)).to_frame(name='SEK/kWh').reset_index() OutputResults10['Component'] = 'EleCost [SEK/kWh]' OutputResults10['SEK/kWh'] *= (1/model.factor1) OutputResults10 = OutputResults10.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Retailer', 0: 'Value'}, inplace=False) OutputResults10 = OutputResults10.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Retailer'], values='SEK/kWh', aggfunc='sum') # series of the electricity price OutputResults11 = pd.Series(data=[ model.Par['pVarEnergyPrice'] [er][p,sc,n] * model.Par['pEleRetSellingRatio'][er] for p,sc,n,er in model.psner], index=pd.Index(model.psner)).to_frame(name='SEK/kWh').reset_index() OutputResults11['Component'] = 'ElePrice [SEK/kWh]' OutputResults11['SEK/kWh'] *= (1/model.factor1) OutputResults11 = OutputResults11.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Retailer', 0: 'Value'}, inplace=False) OutputResults11 = OutputResults11.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Retailer'], values='SEK/kWh', aggfunc='sum') # series of FixedAvailability OutputResults12 = pd.Series(data=[ sum(model.Par['pVarFixedAvailability'][egs][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='FixedAvailability').reset_index() OutputResults12['Component'] = 'Availability [0,1]' OutputResults12 = OutputResults12.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults12 = OutputResults12.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='FixedAvailability', aggfunc='sum') # series of spillage OutputResults13 = pd.Series(data=[ sum(optmodel.vEleSpillage[p,sc,n,egs]() * model.Par['pDuration'][p,sc,n] for egs in model.egs if (nd,egs) in model.n2eg and (gt,egs) in model.t2eg) for p,sc,n,nd,gt in sPNNDGT], index=pd.Index(sPNNDGT)).to_frame(name='EleSpillage').reset_index() OutputResults13['Component'] = 'Spillage [kWh]' OutputResults13['EleSpillage'] *= (1/model.factor1) OutputResults13 = OutputResults13.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 'level_3': 'Node', 'level_4': 'Technology', 0: 'Value'}, inplace=False) OutputResults13 = OutputResults13.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='EleSpillage', aggfunc='sum') # series of FCR-D upwards prices OutputResults14 = pd.Series(data=[ model.Par['pOperatingReservePrice_FCRD_Up'][p,sc,n] for p,sc,n in model.psn], index=pd.Index(model.psn)).to_frame(name='SEK/kWh').reset_index() OutputResults14['Component'] = 'FCR-D Upward Price [SEK/kWh]' OutputResults14['Technology'] = '' OutputResults14['SEK/kWh'] *= (1/model.factor1) OutputResults14 = OutputResults14.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 0: 'Value'}, inplace=False) OutputResults14 = OutputResults14.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='SEK/kWh', aggfunc='sum') # series of FCR-D downwards prices OutputResults15 = pd.Series(data=[ model.Par['pOperatingReservePrice_FCRD_Down'][p,sc,n] for p,sc,n in model.psn], index=pd.Index(model.psn)).to_frame(name='SEK/kWh').reset_index() OutputResults15['Component'] = 'FCR-D Downward Price [SEK/kWh]' OutputResults15['Technology'] = '' OutputResults15['SEK/kWh'] *= (1/model.factor1) OutputResults15 = OutputResults15.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 0: 'Value'}, inplace=False) OutputResults15 = OutputResults15.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='SEK/kWh', aggfunc='sum') # series of FCR-D upwards activation OutputResults16 = pd.Series(data=[ model.Par['pOperatingReserveActivation_FCRD_Up'][p,sc,n] for p,sc,n in model.psn], index=pd.Index(model.psn)).to_frame(name='kWh').reset_index() OutputResults16['Component'] = 'FCR-D Upward Activation [kWh]' OutputResults16['Technology'] = '' OutputResults16['kWh'] *= (1/model.factor1) OutputResults16 = OutputResults16.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 0: 'Value'}, inplace=False) OutputResults16 = OutputResults16.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='kWh', aggfunc='sum') # series of FCR-D downwards activation OutputResults17 = pd.Series(data=[ model.Par['pOperatingReserveActivation_FCRD_Down'][p,sc,n] for p,sc,n in model.psn], index=pd.Index(model.psn)).to_frame(name='kWh').reset_index() OutputResults17['Component'] = 'FCR-D Downward Activation [kWh]' OutputResults17['Technology'] = '' OutputResults17['kWh'] *= (1/model.factor1) OutputResults17 = OutputResults17.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 0: 'Value'}, inplace=False) OutputResults17 = OutputResults17.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='kWh', aggfunc='sum') # series of FCR-D prices OutputResults18 = pd.Series(data=[ model.Par['pOperatingReservePrice_FCRN_Up'][p,sc,n] for p,sc,n in model.psn], index=pd.Index(model.psn)).to_frame(name='SEK/kWh').reset_index() OutputResults18['Component'] = 'FCR-N Price [SEK/kWh]' OutputResults18['Technology'] = '' OutputResults18['SEK/kWh'] *= (1/model.factor1) OutputResults18 = OutputResults18.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 0: 'Value'}, inplace=False) OutputResults18 = OutputResults18.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='SEK/kWh', aggfunc='sum') # series of FCR-N upwards activation OutputResults19 = pd.Series(data=[ model.Par['pOperatingReserveActivation_FCRN_Up'][p,sc,n] for p,sc,n in model.psn], index=pd.Index(model.psn)).to_frame(name='kWh').reset_index() OutputResults19['Component'] = 'FCR-N Upward Activation [kWh]' OutputResults19['Technology'] = '' OutputResults19['kWh'] *= (1/model.factor1) OutputResults19 = OutputResults19.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 0: 'Value'}, inplace=False) OutputResults19 = OutputResults19.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='kWh', aggfunc='sum') # series of FCR-N downwards activation OutputResults20 = pd.Series(data=[ model.Par['pOperatingReserveActivation_FCRN_Down'][p,sc,n] for p,sc,n in model.psn], index=pd.Index(model.psn)).to_frame(name='kWh').reset_index() OutputResults20['Component'] = 'FCR-N Downward Activation [kWh]' OutputResults20['Technology'] = '' OutputResults20['kWh'] *= (1/model.factor1) OutputResults20 = OutputResults20.rename(columns={'level_0': 'Period', 'level_1': 'Scenario', 'level_2': 'LoadLevel', 0: 'Value'}, inplace=False) OutputResults20 = OutputResults20.pivot_table(index=['Period', 'Scenario', 'LoadLevel'], columns=['Component','Technology'], values='kWh', aggfunc='sum') #################################################################################### if len(model.egs): if len(model.egv): OutputResults = pd.concat([OutputResults1Bid, OutputResults2Bid, OutputResults3Bid, OutputResults1a, OutputResults1c, OutputResults1d, OutputResults1e, OutputResults1f, OutputResults2a, OutputResults2c, OutputResults2d, OutputResults2e, OutputResults2f, OutputResults4, OutputResults6, OutputResults7, OutputResults8, OutputResults7a, OutputResults8a, OutputResults12, OutputResults3, OutputResults5, OutputResults13, OutputResults14, OutputResults15, OutputResults9, OutputResults10, OutputResults11, OutputResults16, OutputResults17, OutputResults18, OutputResults19, OutputResults20], axis=1) # OutputResults = pd.concat([OutputResults3c, OutputResults1a, OutputResults1c, OutputResults1d, OutputResults1e, OutputResults1f, OutputResults2a, OutputResults2c, OutputResults2d, OutputResults2e, OutputResults2f, OutputResults4, OutputResults6, OutputResults7, OutputResults8, OutputResults12, OutputResults3, OutputResults5, OutputResults13, OutputResults14, OutputResults15, OutputResults9, OutputResults10, OutputResults11, OutputResults16, OutputResults17, OutputResults18, OutputResults19, OutputResults20], axis=1) else: OutputResults = pd.concat([OutputResults1a, OutputResults2a, OutputResults4, OutputResults6, OutputResults7, OutputResults8, OutputResults7a, OutputResults8a, OutputResults3, OutputResults15, OutputResults9, OutputResults10, OutputResults11], axis=1) else: OutputResults = pd.concat([OutputResults1a, OutputResults4, OutputResults6, OutputResults7, OutputResults8, OutputResults9, OutputResults10, OutputResults11], axis=1) OutputResults['Date'] = OutputResults.index.get_level_values(2).map(lambda x: Date + pd.Timedelta(hours=(int(x[1:]) - int(hour_of_year[1:])))).strftime('%Y-%m-%d %H:%M:%S') OutputResults = OutputResults.set_index('Date', append=True) OutputResults.index.names = [None, None, None, None] OutputResults.columns.names = [None, None] save_to_csv(OutputResults, _path, f'oM_Result_07_rEleOutputSummary_{CaseName}.csv', index=True) # ----------------------------------------------------------- # 1) COLUMN SELECTION AND RENAMING # ----------------------------------------------------------- rename_cols = { 'Spot Price [SEK/kWh]': 'Price Spot', 'EleCost [SEK/kWh]': 'Price Import', 'FCR-D Upward Price [SEK/kWh]': 'Price FCR-D Upward', 'FCR-D Downward Price [SEK/kWh]': 'Price FCR-D Downward', 'Production/Discharge [kWh]': 'Discharge Day-Ahead', 'FCR-D Upward Discharge [kWh]': 'Discharge FCR-D Upward', 'FCR-D Downward Discharge [kWh]': 'Discharge FCR-D Downward', 'Consumption/Charge [kWh]': 'Charge Day-Ahead', 'FCR-D Upward Charge [kWh]': 'Charge FCR-D Upward', 'FCR-D Downward Charge [kWh]': 'Charge FCR-D Downward', 'Availability [0,1]': 'Availability' } existing_lvl0_cols = set(OutputResults.columns.get_level_values(0)) keep_cols = [c for c in rename_cols.keys() if c in existing_lvl0_cols] # Extract + flatten columns data = OutputResults[keep_cols] # data.columns = data.columns.get_level_values(0) # data.columns = [f"{a}_{b}" if b else a for a, b in data.columns] # Step 0 — Rebuild the MultiIndex columns (important!) lvl0 = data.columns.get_level_values(0) # Component lvl1 = data.columns.get_level_values(1) # Technology data.columns = pd.MultiIndex.from_arrays([lvl0, lvl1]) # Step 1 — Stack the first column level = Component stacked = data.stack(level=1, future_stack=True) # After stacking: # Index = (period, scenario, time, timestamp, Technology) # Columns = Component # Step 2 — Name the stacked value column stacked.name = "Value" # Step 3 — Convert to tidy DataFrame tidy = stacked.reset_index() tidy = tidy.rename(columns={"level_4": "Technology"}) # in case pandas names it level_4 # Flatten index data = tidy.rename(columns={'level_0': 'Period','level_1': 'Scenario','level_2': 'LoadLevel','level_3': 'Date'}).rename(columns=rename_cols) data.set_index(['Period', 'Scenario', 'LoadLevel', 'Date', 'Technology'], inplace=True) data = data.stack().reset_index().rename(columns={'level_5': 'Component', 0: 'Value'}) # data = data.fillna(0) # # Ensure datetime data['Date'] = pd.to_datetime(data['Date']) # ----------------------------------------------------------- # 2) RESHAPE INTO LONG FORMAT # ----------------------------------------------------------- bar_components = ['Discharge Day-Ahead', 'Charge Day-Ahead'] # bar_components = ['Discharge Day-Ahead', 'Charge Day-Ahead','Discharge FCR-D Upward', 'Discharge FCR-D Downward','Charge FCR-D Upward', 'Charge FCR-D Downward'] line_components = ['Price Spot', 'Price Import', 'Availability'] # line_components = ['Price Spot', 'Price Import','Price FCR-D Upward', 'Price FCR-D Downward'] # bar_data is created by filtering the list bar_components from columns component of data bar_data = data[['Date','Technology', 'Component','Value']][data['Component'].isin(bar_components)] line_data = data[['Date','Technology', 'Component','Value']][data['Component'].isin(line_components)] # bar_data = data[['Date', 'Technology'] + bar_components].melt(id_vars=['Date','Technology'], var_name='Component', value_name='Value') # line_data = data[['Date', 'Technology'] + line_components].melt(id_vars=['Date','Technology'], var_name='Component', value_name='Value') # # bar_data = pd.pivot_table(bar_data, index=['Date', 'Component'], values='Value', aggfunc='sum').reset_index() # line_data = pd.pivot_table(line_data, index=['Date', 'Component'], values='Value', aggfunc='mean').reset_index() # # bar_data = bar_data.sort_values('Date') # line_data = line_data.sort_values('Date') line_data_FCR = line_data[line_data['Component'].str.contains('FCR-D')] line_data_FCR = pd.pivot_table(line_data_FCR, index=['Date', 'Component'], values='Value', aggfunc='mean').reset_index() line_data_FCR = line_data_FCR.sort_values('Date') # ----------------------------------------------------------- # 3) DATE WINDOW (FIRST 7 DAYS) # ----------------------------------------------------------- start_date = data['Date'].min() end_date = start_date + pd.Timedelta(days=7) LabelSize = 16 TitleSize = 18 def filter_7d(df): return df[(df['Date'] >= start_date) & (df['Date'] < end_date)] bar_7d = filter_7d(bar_data) line_7d = filter_7d(line_data) line_FCR_7d = filter_7d(line_data_FCR) bar_7d = bar_7d.sort_values('Date') line_7d = line_7d.sort_values('Date') line_FCR_7d = line_FCR_7d.sort_values('Date') # ----------------------------------------------------------- # 4) HELPER FUNCTIONS FOR PLOT BUILDING # ----------------------------------------------------------- def make_bar_chart(df): return (alt.Chart(df) .mark_bar() .encode( x=alt.X('Date:T',axis=alt.Axis(title='',labelAngle=-90,format="%a, %b %d, %H:%M",tickCount=30,labelLimit=1000,labelFontSize=16,titleFontSize=18)), y=alt.Y('sum(Value):Q',axis=alt.Axis(title='[kWh]',labelFontSize=LabelSize,titleFontSize=TitleSize)), color=alt.Color('Component:N',scale=alt.Scale(scheme='category10'),legend=alt.Legend(title='', labelFontSize=LabelSize, titleFontSize=TitleSize), ), order=alt.Order("Date:T") ).properties(width=1200, height=400) ) def make_line_chart(df): return (alt.Chart(df) .mark_line(strokeDash=[5, 5], point=alt.OverlayMarkDef(filled=True, size=50, color='black')) .encode( x=alt.X('Date:T',axis=alt.Axis(title='',labelAngle=-90,format="%a, %b %d, %H:%M",tickCount=30,labelLimit=1000,labelFontSize=LabelSize,titleFontSize=TitleSize)), y=alt.Y('Value:Q',axis=alt.Axis(title='[SEK/kWh]',labelFontSize=LabelSize,titleFontSize=TitleSize)), color=alt.Color('Component:N',scale=alt.Scale(scheme='category10'),legend=alt.Legend(title='', labelFontSize=LabelSize, titleFontSize=TitleSize) ), order=alt.Order("Date:T") ).properties(width=1200, height=400) ) def save_plot(chart, name): chart.save(f"{_path}/oM_Plot_06_{name}_{CaseName}.html",embed_options={"renderer": "svg"}) # ----------------------------------------------------------- # 5) BUILD & SAVE ALL CHARTS (NO DUPLICATION) # ----------------------------------------------------------- # Full period save_plot(alt.layer(make_bar_chart(bar_data), make_line_chart(line_data)).resolve_scale(y='independent').interactive(),"rEleOutputSummary") save_plot(make_line_chart(line_data), "rEleOutputPrices") save_plot(make_line_chart(line_data_FCR), "rEleOutputFCRDPrices") # 7 days save_plot(alt.layer(make_bar_chart(bar_7d), make_line_chart(line_7d)).resolve_scale(y='independent').interactive(),"rEleOutputSummary_7days") save_plot(make_line_chart(line_7d), "rEleOutputPrices_7days") save_plot(make_line_chart(line_FCR_7d), "rEleOutputFCRDPrices_7days") # ----------------------------------------------------------- # 1) COMPUTE NET POWER & FILTER OUT ZERO ROWS # ----------------------------------------------------------- # # Compute Net Power directly on the processed dataset # data['NetPower'] = data['Discharge Day-Ahead'] - data['Charge Day-Ahead'] data_filtered = pd.pivot_table(data, index=['Date', 'Period', 'Scenario', 'LoadLevel'], columns=['Component'], values='Value').reset_index() # Ensure columns exist before computing NetPower for col in ['Discharge Day-Ahead', 'Charge Day-Ahead']: if col not in data_filtered.columns: data_filtered[col] = 0.0 data_filtered['NetPower'] = data_filtered['Discharge Day-Ahead'] - data_filtered['Charge Day-Ahead'] # data_filtered = data_filtered[data_filtered['NetPower'] != 0].copy() # # # Filter out rows where Discharge Day-Ahead = 0 and Charge Day-Ahead = 0 # data_filtered = data[(data['Discharge Day-Ahead'] != 0) | (data['Charge Day-Ahead'] != 0)].copy() # ----------------------------------------------------------- # 2) HELPER FUNCTION FOR SCATTER PLOTS # ----------------------------------------------------------- def make_scatter(df, x_field, y_field, x_title, y_title, color_field, tooltip_fields): return ( alt.Chart(df) .mark_circle(size=70, opacity=0.6) .encode( x=alt.X(f'{x_field}:Q', axis=alt.Axis(title=x_title, labelFontSize=14, titleFontSize=16)), y=alt.Y(f'{y_field}:Q', axis=alt.Axis(title=y_title, labelFontSize=14, titleFontSize=16)), color=alt.Color(f'{color_field}:Q', scale=alt.Scale(scheme='redyellowgreen')), tooltip=tooltip_fields) .properties(width=700, height=500) .configure_title(fontSize=18) ) scatter_discharge = make_scatter(data_filtered, x_field='Price Spot', y_field='Discharge Day-Ahead', x_title='Spot Price [SEK/kWh]', y_title='Discharge [kWh]', color_field='Discharge Day-Ahead', tooltip_fields=['Date:T', 'Price Spot:Q', 'Discharge Day-Ahead:Q']) scatter_discharge.save(f"{_path}/oM_Plot_07_rEleOutputScatter_{CaseName}.html", embed_options={'renderer': 'svg'}) scatter_charge = make_scatter(data_filtered, x_field='Price Import', y_field='Charge Day-Ahead', x_title='Import Price [SEK/kWh]', y_title='Charge [kWh]', color_field='Charge Day-Ahead', tooltip_fields=['Date:T', 'Price Import:Q', 'Charge Day-Ahead:Q']) scatter_charge.save(f"{_path}/oM_Plot_07_rEleChargeScatter_{CaseName}.html", embed_options={'renderer': 'svg'}) # ---- Index & small helpers -------------------------------------------------- I_psn = pd.MultiIndex.from_tuples(model.psn) idx_p = pd.Index(model.p) dur = {(p, sc, n): float(model.Par['pDuration'][p, sc, n]) for (p, sc, n) in model.psn} def has(name): return hasattr(optmodel, name) and len(getattr(optmodel, name)) > 0 def re_psn(s): return s.reindex(I_psn, fill_value=0.0) def pos_guard(s): return s.where(s > 0.0, 1e-5) # avoid 0-div # Availability (avoid nested loops over nodes) n2er_any = {er for (nd, er) in getattr(model, "n2er", set())} n2ed_any = {ed for (nd, ed) in getattr(model, "n2ed", set())} # ---- Totals ----------------------------------------------------------------- def total_in(): acc = defaultdict(float) if has("vEleTotalOutput"): for (p, sc, n, eg), var in optmodel.vEleTotalOutput.items(): acc[(p, sc, n)] += optmodel.vEleTotalOutput[p,sc,n,eg]() * dur[(p, sc, n)] if has("vEleBuy"): for (p, sc, n, er), var in optmodel.vEleBuy.items(): if er in n2er_any: acc[(p, sc, n)] += optmodel.vEleBuy[p,sc,n,er]() * dur[(p, sc, n)] if has("vENS"): for (p, sc, n, ed), var in optmodel.vENS.items(): if ed in n2ed_any: acc[(p, sc, n)] += optmodel.vENS[p,sc,n,ed]() * dur[(p, sc, n)] return pos_guard(re_psn(pd.Series(acc, dtype=float))) def total_out(): acc = defaultdict(float) if has("vEleTotalCharge"): for (p, sc, n, egs), var in optmodel.vEleTotalCharge.items(): acc[(p, sc, n)] += optmodel.vEleTotalCharge[p,sc,n,egs]() * dur[(p, sc, n)] if has("vEleSell"): for (p, sc, n, er), var in optmodel.vEleSell.items(): if er in n2er_any: acc[(p, sc, n)] += optmodel.vEleSell[p,sc,n,er]() * dur[(p, sc, n)] if has("vEleDemand"): for (p, sc, n, ed), var in optmodel.vEleDemand.items(): if ed in n2ed_any: acc[(p, sc, n)] += optmodel.vEleDemand[p,sc,n,ed]() * dur[(p, sc, n)] return pos_guard(re_psn(pd.Series(acc, dtype=float))) TEI, TEO = total_in(), total_out() # ---- Shares In/Out (robust to missing techs) -------------------------------- def share_gen_in(tags): num = defaultdict(float) if has("vEleTotalOutput") and hasattr(model, "egg"): wanted = {eg for eg in model.egg if any(t in str(eg) for t in tags)} if wanted: for (p, sc, n, eg), var in optmodel.vEleTotalOutput.items(): if eg in wanted: num[(p, sc, n)] += optmodel.vEleTotalOutput[p,sc,n,eg]() * dur[(p, sc, n)] return (re_psn(pd.Series(num, dtype=float)) / TEI).clip(lower=0.0) def share_market_in(): num = defaultdict(float) if has("vEleBuy"): for (p, sc, n, er), var in optmodel.vEleBuy.items(): num[(p, sc, n)] += optmodel.vEleBuy[p,sc,n,er]() * dur[(p, sc, n)] return (re_psn(pd.Series(num, dtype=float)) / TEI).clip(lower=0.0) def share_ens(): num = defaultdict(float) if has("vENS"): for (p, sc, n, ed), var in optmodel.vENS.items(): num[(p, sc, n)] += optmodel.vENS[p,sc,n,ed]() * dur[(p, sc, n)] return (re_psn(pd.Series(num, dtype=float)) / TEI).clip(lower=0.0) def share_to_storage(tags): num = defaultdict(float) if has("vEleTotalCharge") and hasattr(model, "egs"): wanted = {e for e in model.egs if any(t in str(e) for t in tags)} if wanted: for (p, sc, n, egs), var in optmodel.vEleTotalCharge.items(): if egs in wanted: num[(p, sc, n)] += optmodel.vEleTotalCharge[p,sc,n,egs]() * dur[(p, sc, n)] return (re_psn(pd.Series(num, dtype=float)) / TEO).clip(lower=0.0) def share_market_out(): num = defaultdict(float) if has("vEleSell"): for (p, sc, n, er), var in optmodel.vEleSell.items(): num[(p, sc, n)] += optmodel.vEleSell[p,sc,n,er]() * dur[(p, sc, n)] return (re_psn(pd.Series(num, dtype=float)) / TEO).clip(lower=0.0) def share_dem_out(): num = defaultdict(float) if has("vEleDemand"): for (p, sc, n, ed), var in optmodel.vEleDemand.items(): num[(p, sc, n)] += optmodel.vEleDemand[p,sc,n,ed]() * dur[(p, sc, n)] return (re_psn(pd.Series(num, dtype=float)) / TEO).clip(lower=0.0) ShareGenInFV = share_gen_in(["Solar"]) # FV ShareGenInBESS = share_gen_in(["BESS"]) ShareGenInEV = share_gen_in(["EV"]) ShareMarketIn = share_market_in() ShareENSIn = share_ens() ShareGenOutBESS = share_to_storage(["BESS"]) ShareGenOutEV = share_to_storage(["EV"]) ShareMarketOut = share_market_out() ShareDemOut = share_dem_out() # ---- Flows (kWh at (p,sc,n)) ----------------------------------------------- f1 = float(getattr(model, "factor1", 1.0)) flow = lambda src, dst: (src * dst * TEI) * (1.0 / f1) FVtoEV = flow(ShareGenInFV, ShareGenOutEV) FVtoBESS = flow(ShareGenInFV, ShareGenOutBESS) FVtoMkt = flow(ShareGenInFV, ShareMarketOut) FVtoDem = flow(ShareGenInFV, ShareDemOut) ENStoEV = flow(ShareENSIn, ShareGenOutEV) ENStoBESS = flow(ShareENSIn, ShareGenOutBESS) ENStoDem = flow(ShareENSIn, ShareDemOut) BESStoEV = flow(ShareGenInBESS, ShareGenOutEV) BESStoMkt = flow(ShareGenInBESS, ShareMarketOut) BESStoDem = flow(ShareGenInBESS, ShareDemOut) EVtoBESS = flow(ShareGenInEV, ShareGenOutBESS) EVtoMkt = flow(ShareGenInEV, ShareMarketOut) EVtoDem = flow(ShareGenInEV, ShareDemOut) MkttoEV = flow(ShareMarketIn, ShareGenOutEV) MkttoDem = flow(ShareMarketIn, ShareDemOut) MkttoBESS = flow(ShareMarketIn, ShareGenOutBESS) # ---- Aggregate to Period (p) ----------------------------------------------- def sum_by_p(s): if s.empty: return pd.Series(0.0, index=idx_p) g = s.to_frame("v").reset_index().groupby("level_0")["v"].sum() return g.reindex(idx_p, fill_value=0.0) flows = { "FV_to_EV [KWh]": FVtoEV, "FV_to_BESS [KWh]": FVtoBESS, "FV_to_Mkt [KWh]": FVtoMkt, "FV_to_Dem [KWh]": FVtoDem, "ENS_to_EV [KWh]": ENStoEV, "ENS_to_BESS [KWh]": ENStoBESS, "ENS_to_Dem [KWh]": ENStoDem, "BESS_to_EV [KWh]": BESStoEV, "BESS_to_Mkt [KWh]": BESStoMkt, "BESS_to_Dem [KWh]": BESStoDem, "EV_to_BESS [KWh]": EVtoBESS, "EV_to_Mkt [KWh]": EVtoMkt, "EV_to_Dem [KWh]": EVtoDem, "Mkt_to_EV [KWh]": MkttoEV, "Mkt_to_Dem [KWh]": MkttoDem, "Mkt_to_BESS [KWh]": MkttoBESS, } dfEnergyBalance = pd.DataFrame({"Period": idx_p}) for name, s in flows.items(): dfEnergyBalance[name] = sum_by_p(s).values # ===== Sankey: always save a figure (even if all flows are zero) ============= ALLOWED = {"SolarPV", "Market", "EV", "ENS", "BESS", "Demand"} def _normalize(df): if "Period" not in df.columns: raise ValueError("dfEnergyBalance needs 'Period'") m = df.melt(id_vars="Period", var_name="Component", value_name="flow_value") # strip units and normalize names m["Component"] = m["Component"].str.replace(r"\s*\[.*\]$", "", regex=True) m["Component"] = (m["Component"] .str.replace("FV", "SolarPV", regex=False) .str.replace("Mkt", "Market", regex=False) .str.replace("Dem", "Demand", regex=False)) # extract edges split = m["Component"].str.extract(r"^(?P<Source>[^_]+)_to_(?P<Target>.+)$") m = pd.concat([m, split], axis=1).dropna(subset=["Source", "Target"]) # keep allowed that actually appear (if any) present = set(m["Source"]).union(m["Target"]) allowed_present = ALLOWED & present if allowed_present: m = m[m["Source"].isin(allowed_present) & m["Target"].isin(allowed_present)] return m def _percentify(m): if m.empty: m = m.copy() m["Source_%"] = 0.0; m["Target_%"] = 0.0 return m g_src = m.groupby(["Period", "Source"])["flow_value"].transform("sum").replace(0, np.nan) g_tgt = m.groupby(["Period", "Target"])["flow_value"].transform("sum").replace(0, np.nan) m = m.assign(**{"Source_%": (m["flow_value"] / g_src * 100).fillna(0.0), "Target_%": (m["flow_value"] / g_tgt * 100).fillna(0.0)}) return m def save_sankey_always(dfEnergyBalance, out_dir, case_name, mode="percent", prefix="oM_Plot_rSankey"): os.makedirs(out_dir, exist_ok=True) m = _normalize(dfEnergyBalance) # Plot per period, always save a PNG for per in dfEnergyBalance["Period"]: d = m[m["Period"] == per] outfile = os.path.join(out_dir, f"{prefix}_{case_name}_{per}.png") if sky is None: # Fallback: save a placeholder plt.figure(figsize=(6, 4)) plt.title(f"Case: {case_name}, Period: {per}\n(ausankey not available)") plt.text(0.5, 0.5, "Install 'ausankey' to draw Sankey", ha='center', va='center') plt.axis('off'); plt.savefig(outfile, dpi=150, bbox_inches="tight"); plt.close() print(f"Sankey placeholder saved: {outfile}") continue d = d[d["flow_value"].fillna(0) > 0] if d.empty: # Save an empty-note figure for this period plt.figure(figsize=(6, 4)) plt.title(f"Case: {case_name}, Period: {per}") plt.text(0.5, 0.5, "No non-zero flows", ha='center', va='center') plt.axis('off'); plt.savefig(outfile, dpi=150, bbox_inches="tight"); plt.close() print(f"Sankey (no flows) saved: {outfile}") continue if mode == "percent": d = _percentify(d) vals1, vals2, unit = d["Source_%"], d["Target_%"], "%" else: vals1, vals2, unit = d["flow_value"], d["flow_value"], "KWh" sankey_data = pd.DataFrame({"Stage1": d["Source"], "Value1": vals1, "Stage2": d["Target"], "Value2": vals2}) plt.figure(figsize=(7, 5)) sky.sankey(sankey_data, sort="top", titles=["Source", "Target"], valign="center") plt.title(f"Case: {case_name}, Period: {per} ({unit})") plt.savefig(outfile, format="png", dpi=150, bbox_inches="tight"); plt.close() print(f"Sankey saved: {outfile}") # --- Save CSVs & plots ------------------------------------------------------- dfEnergyBalance.to_csv(os.path.join(_path, f"oM_Result_08_rEnergyBalance_{CaseName}.csv"), index=False) save_sankey_always(dfEnergyBalance, out_dir=_path, case_name=CaseName, mode="percent") log_time('-- Sankey diagrams output time:', StartTime, ind_log=indlog) StartTime = time.time() # Duration curve of the EV total output and the total charge EV_TotalOutput = pd.Series(data=[sum(optmodel.vEleTotalOutput[p,sc,n,egv]() for egv in model.egv) for p,sc,n in model.psn], index=pd.MultiIndex.from_tuples(model.psn)) EV_TotalCharge = pd.Series(data=[sum(optmodel.vEleTotalCharge[p,sc,n,egs]() for egs in model.egs) for p,sc,n in model.psn], index=pd.MultiIndex.from_tuples(model.psn)) EV_NetCharge = EV_TotalCharge - EV_TotalOutput create_and_save_duration_curve( series_data=EV_NetCharge.values, index_tuples=model.psn, value_col_name='NetCharge', Date=Date, hour_of_year=hour_of_year, path=_path, csv_filename=f'oM_Result_10_rDurationCurve_NetCharge_{CaseName}.csv', html_filename=f'oM_Plot_rDurationCurve_NetCharge_{CaseName}.html', title='Duration Curve of the Charge and Discharge of the EV', y_label='Charge and discharge [KWh]' ) log_time('-- Duration curves of the net charge output time:', StartTime, ind_log=indlog) StartTime = time.time() # Duration curve of the Solar PV total output SolarPV_data = [sum(optmodel.vEleTotalOutput[p,sc,n,egr]() for egr in model.egr) for p,sc,n in model.psn] create_and_save_duration_curve( series_data=SolarPV_data, index_tuples=model.psn, value_col_name='TotalOutput', Date=Date, hour_of_year=hour_of_year, path=_path, csv_filename=f'oM_Result_11_rDurationCurve_TotalOutput_{CaseName}.csv', html_filename=f'oM_Plot_rDurationCurve_TotalOutput_{CaseName}.html', title='Duration Curve of the Total Output of the Solar PV', y_label='Total Output [KWh]' ) log_time('-- Duration curves of electricity production output time:', StartTime, ind_log=indlog) StartTime = time.time() # Duration curve of the electricity demand EleDemand_data = [sum(optmodel.vEleDemand[p,sc,n,ed]() for ed in model.ed) for p,sc,n in model.psn] create_and_save_duration_curve( series_data=EleDemand_data, index_tuples=model.psn, value_col_name='Demand', Date=Date, hour_of_year=hour_of_year, path=_path, csv_filename=f'oM_Result_12_rDurationCurve_Demand_{CaseName}.csv', html_filename=f'oM_Plot_rDurationCurve_Demand_{CaseName}.html', title='Duration Curve of the Demand', y_label='Demand [KWh]' ) log_time('-- Duration curves of the electricity demand output time:', StartTime, ind_log=indlog) StartTime = time.time() # Duration curve of the electricity bought from the market EleBuy_data = [sum(optmodel.vEleBuy[p,sc,n,er]() for er in model.er) for p,sc,n in model.psn] create_and_save_duration_curve( series_data=EleBuy_data, index_tuples=model.psn, value_col_name='Buy', Date=Date, hour_of_year=hour_of_year, path=_path, csv_filename=f'oM_Result_13_rDurationCurve_EleBuy_{CaseName}.csv', html_filename=f'oM_Plot_rDurationCurve_EleBuy_{CaseName}.html', title='Duration Curve of the Buy', y_label='Buy [KWh]' ) log_time('-- Duration curves of the electricity bought output time:',StartTime) StartTime = time.time() # Duration curve of the electricity sold to the market EleSell_data = [sum(optmodel.vEleSell[p,sc,n,er]() for er in model.er) for p,sc,n in model.psn] create_and_save_duration_curve( series_data=EleSell_data, index_tuples=model.psn, value_col_name='Sell', Date=Date, hour_of_year=hour_of_year, path=_path, csv_filename=f'oM_Result_14_rDurationCurve_EleSell_{CaseName}.csv', html_filename=f'oM_Plot_rDurationCurve_EleSell_{CaseName}.html', title='Duration Curve of the Sell', y_label='Sell [KWh]' ) log_time('-- Duration curves of the electricity sold output time:', StartTime, ind_log=indlog) return model