sdom 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sdom/common/utilities.py +18 -0
- sdom/config_sdom.py +17 -0
- sdom/constants.py +29 -0
- sdom/initializations.py +111 -0
- sdom/io_manager.py +468 -0
- sdom/models/formulations_hydro.py +14 -0
- sdom/models/formulations_imports_exports.py +6 -0
- sdom/models/formulations_load.py +11 -0
- sdom/models/formulations_nuclear.py +13 -0
- sdom/models/formulations_other_renewables.py +14 -0
- sdom/models/formulations_resiliency.py +43 -0
- sdom/models/formulations_storage.py +142 -0
- sdom/models/formulations_system.py +81 -0
- sdom/models/formulations_thermal.py +80 -0
- sdom/models/formulations_vre.py +115 -0
- sdom/models/models_utils.py +9 -0
- sdom/optimization_main.py +264 -0
- sdom-0.0.1.dist-info/METADATA +123 -0
- sdom-0.0.1.dist-info/RECORD +20 -0
- sdom-0.0.1.dist-info/WHEEL +4 -0
sdom/common/utilities.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pyomo.environ import *
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
def safe_pyomo_value(var):
|
|
6
|
+
"""Return the value of a variable or expression if it is initialized, else return None."""
|
|
7
|
+
try:
|
|
8
|
+
return value(var) if var is not None else None
|
|
9
|
+
except ValueError:
|
|
10
|
+
return None
|
|
11
|
+
|
|
12
|
+
def check_file_exists(filepath, name_file = ""):
|
|
13
|
+
"""Check if the expected file exists. Raise FileNotFoundError if not."""
|
|
14
|
+
if not os.path.isfile(filepath):
|
|
15
|
+
logging.error(f"Expected {name_file} file not found: {filepath}")
|
|
16
|
+
raise FileNotFoundError(f"Expected {name_file} file not found: {filepath}")
|
|
17
|
+
return False
|
|
18
|
+
return True
|
sdom/config_sdom.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
def configure_logging(level=logging.INFO, log_file=None):
|
|
4
|
+
"""
|
|
5
|
+
Configures logging for the module.
|
|
6
|
+
Args:
|
|
7
|
+
level: Logging level (default: logging.INFO)
|
|
8
|
+
log_file: Optional file path to log to a file
|
|
9
|
+
"""
|
|
10
|
+
handlers = [logging.StreamHandler()]
|
|
11
|
+
if log_file:
|
|
12
|
+
handlers.append(logging.FileHandler(log_file))
|
|
13
|
+
logging.basicConfig(
|
|
14
|
+
level=level,
|
|
15
|
+
format='%(asctime)s %(levelname)s %(message)s',
|
|
16
|
+
handlers=handlers
|
|
17
|
+
)
|
sdom/constants.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# INCLUDE HERE ALL THE CONSTATS AND USE UPPER CASE NAMES
|
|
2
|
+
|
|
3
|
+
INPUT_CSV_NAMES = {
|
|
4
|
+
'solar_plants': 'Set_k_SolarPV.csv', #TODO this set should be optional since are col names CFSolar_2050.csv
|
|
5
|
+
'wind_plants': 'Set_w_Wind.csv', #TODO this set should be optional since are col names CFWind_2050.csv
|
|
6
|
+
'load_data': 'Load_hourly_2050.csv',
|
|
7
|
+
'nuclear_data': 'Nucl_hourly_2019.csv',
|
|
8
|
+
'large_hydro_data': 'lahy_hourly_2019.csv',
|
|
9
|
+
'other_renewables_data': 'otre_hourly_2019.csv',
|
|
10
|
+
'cf_solar': 'CFSolar_2050.csv',
|
|
11
|
+
'cf_wind': 'CFWind_2050.csv',
|
|
12
|
+
'cap_solar': 'CapSolar_2050.csv',
|
|
13
|
+
'cap_wind': 'CapWind_2050.csv',
|
|
14
|
+
'storage_data': 'StorageData_2050.csv',
|
|
15
|
+
'scalars': 'Scalars.csv',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
VRE_PROPERTIES_NAMES = ['trans_cap_cost', 'CAPEX_M', 'FOM_M']
|
|
19
|
+
STORAGE_PROPERTIES_NAMES = ['P_Capex', 'E_Capex', 'Eff', 'Min_Duration',
|
|
20
|
+
'Max_Duration', 'Max_P', 'FOM', 'VOM', 'Lifetime', 'CostRatio']
|
|
21
|
+
|
|
22
|
+
#TODO this set is the col names of the StorageData_2050.csv file
|
|
23
|
+
STORAGE_SET_J_TECHS = ['Li-Ion', 'CAES', 'PHS', 'H2']
|
|
24
|
+
STORAGE_SET_B_TECHS = ['Li-Ion', 'PHS'] #TODO INCLUDE DECOUPLED STORAGE FLAG IN THE CSV FILE
|
|
25
|
+
|
|
26
|
+
#RESILIENCY CONSTANTS HARD-CODED
|
|
27
|
+
# PCLS - Percentage of Critical Load Served - Constraint : Resilience
|
|
28
|
+
CRITICAL_LOAD_PERCENTAGE = 1 # 10% of the total load
|
|
29
|
+
PCLS_TARGET = 0.9 # 90% of the total load
|
sdom/initializations.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyomo.environ import *
|
|
3
|
+
from .constants import STORAGE_PROPERTIES_NAMES, STORAGE_SET_J_TECHS, STORAGE_SET_B_TECHS
|
|
4
|
+
from .models.formulations_vre import add_vre_parameters
|
|
5
|
+
from .models.formulations_thermal import add_gascc_parameters
|
|
6
|
+
from .models.formulations_nuclear import add_nuclear_parameters
|
|
7
|
+
from .models.formulations_hydro import add_large_hydro_parameters
|
|
8
|
+
from .models.formulations_other_renewables import add_other_renewables_parameters
|
|
9
|
+
from .models.formulations_load import add_load_parameters
|
|
10
|
+
from .models.formulations_storage import add_storage_parameters
|
|
11
|
+
from .models.formulations_resiliency import add_resiliency_parameters
|
|
12
|
+
from .models.models_utils import crf_rule
|
|
13
|
+
|
|
14
|
+
def initialize_sets( model, data, n_hours = 8760 ):
|
|
15
|
+
"""
|
|
16
|
+
Initialize model sets from the provided data dictionary.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
model: The optimization model instance to initialize.
|
|
20
|
+
data: A dictionary containing model parameters and data.
|
|
21
|
+
"""
|
|
22
|
+
# Solar plant ID alignment
|
|
23
|
+
solar_plants_cf = data['cf_solar'].columns[1:].astype(str).tolist()
|
|
24
|
+
solar_plants_cap = data['cap_solar']['sc_gid'].astype(str).tolist()
|
|
25
|
+
common_solar_plants = list(set(solar_plants_cf) & set(solar_plants_cap))
|
|
26
|
+
|
|
27
|
+
# Filter solar data and initialize model set
|
|
28
|
+
complete_solar_data = data["cap_solar"][data["cap_solar"]['sc_gid'].astype(str).isin(common_solar_plants)]
|
|
29
|
+
complete_solar_data = complete_solar_data.dropna(subset=['CAPEX_M', 'trans_cap_cost', 'FOM_M', 'capacity'])
|
|
30
|
+
common_solar_plants_filtered = complete_solar_data['sc_gid'].astype(str).tolist()
|
|
31
|
+
model.k = Set( initialize = common_solar_plants_filtered )
|
|
32
|
+
|
|
33
|
+
# Load the solar capacities
|
|
34
|
+
cap_solar_dict = complete_solar_data.set_index('sc_gid')['capacity'].to_dict()
|
|
35
|
+
|
|
36
|
+
# Filter the dictionary to ensure only valid keys are included
|
|
37
|
+
default_capacity_value = 0.0
|
|
38
|
+
filtered_cap_solar_dict = {k: cap_solar_dict.get(k, default_capacity_value) for k in model.k}
|
|
39
|
+
|
|
40
|
+
# Wind plant ID alignment
|
|
41
|
+
wind_plants_cf = data['cf_wind'].columns[1:].astype(str).tolist()
|
|
42
|
+
wind_plants_cap = data['cap_wind']['sc_gid'].astype(str).tolist()
|
|
43
|
+
common_wind_plants = list( set( wind_plants_cf ) & set( wind_plants_cap ) )
|
|
44
|
+
|
|
45
|
+
# Filter wind data and initialize model set
|
|
46
|
+
complete_wind_data = data["cap_wind"][data["cap_wind"]['sc_gid'].astype(str).isin(common_wind_plants)]
|
|
47
|
+
complete_wind_data = complete_wind_data.dropna(subset=['CAPEX_M', 'trans_cap_cost', 'FOM_M', 'capacity'])
|
|
48
|
+
common_wind_plants_filtered = complete_wind_data['sc_gid'].astype(str).tolist()
|
|
49
|
+
model.w = Set(initialize=common_wind_plants_filtered)
|
|
50
|
+
|
|
51
|
+
# Load the wind capacities
|
|
52
|
+
cap_wind_dict = complete_wind_data.set_index('sc_gid')['capacity'].to_dict()
|
|
53
|
+
|
|
54
|
+
# Filter the dictionary to ensure only valid keys are included
|
|
55
|
+
filtered_cap_wind_dict = {w: cap_wind_dict.get(w, default_capacity_value) for w in model.w}
|
|
56
|
+
|
|
57
|
+
#add to data dict new data pre-procesing dicts
|
|
58
|
+
data['filtered_cap_solar_dict'] = filtered_cap_solar_dict
|
|
59
|
+
data['filtered_cap_wind_dict'] = filtered_cap_wind_dict
|
|
60
|
+
data['complete_solar_data'] = complete_solar_data
|
|
61
|
+
data['complete_wind_data'] = complete_wind_data
|
|
62
|
+
|
|
63
|
+
# Define sets
|
|
64
|
+
model.h = RangeSet(1, n_hours)
|
|
65
|
+
model.j = Set( initialize = STORAGE_SET_J_TECHS )
|
|
66
|
+
model.b = Set( initialize = STORAGE_SET_B_TECHS )
|
|
67
|
+
|
|
68
|
+
# Initialize storage properties
|
|
69
|
+
model.sp = Set( initialize = STORAGE_PROPERTIES_NAMES )
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def initialize_params(model, data):
|
|
74
|
+
"""
|
|
75
|
+
Initialize model parameters from the provided data dictionary.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
model: The optimization model instance to initialize.
|
|
79
|
+
data: A dictionary containing model parameters and data.
|
|
80
|
+
filtered_cap_solar_dict
|
|
81
|
+
"""
|
|
82
|
+
model.r = Param( initialize = float(data["scalars"].loc["r"].Value) ) # Discount rate
|
|
83
|
+
|
|
84
|
+
logging.debug("--Initializing VRE parameters...")
|
|
85
|
+
add_vre_parameters(model, data)
|
|
86
|
+
|
|
87
|
+
logging.debug("--Initializing gas combined cycle parameters...")
|
|
88
|
+
add_gascc_parameters(model,data)
|
|
89
|
+
|
|
90
|
+
logging.debug("--Initializing load parameters...")
|
|
91
|
+
add_load_parameters(model, data)
|
|
92
|
+
|
|
93
|
+
logging.debug("--Initializing nuclear parameters...")
|
|
94
|
+
add_nuclear_parameters(model, data)
|
|
95
|
+
|
|
96
|
+
logging.debug("--Initializing large hydro parameters...")
|
|
97
|
+
add_large_hydro_parameters(model, data)
|
|
98
|
+
|
|
99
|
+
logging.debug("--Initializing other renewables parameters...")
|
|
100
|
+
add_other_renewables_parameters(model, data)
|
|
101
|
+
|
|
102
|
+
logging.debug("--Initializing storage parameters...")
|
|
103
|
+
add_storage_parameters(model, data)
|
|
104
|
+
|
|
105
|
+
# GenMix_Target, mutable to change across multiple runs
|
|
106
|
+
model.GenMix_Target = Param( initialize = float(data["scalars"].loc["GenMix_Target"].Value), mutable=True)
|
|
107
|
+
model.CRF = Param( model.j, initialize = crf_rule ) #Capital Recovery Factor
|
|
108
|
+
|
|
109
|
+
logging.debug("--Initializing resiliency parameters...")
|
|
110
|
+
add_resiliency_parameters(model, data)
|
|
111
|
+
#model.CRF.display()
|
sdom/io_manager.py
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import os
|
|
4
|
+
import csv
|
|
5
|
+
|
|
6
|
+
from pyomo.environ import *
|
|
7
|
+
|
|
8
|
+
from .common.utilities import safe_pyomo_value, check_file_exists
|
|
9
|
+
from .constants import INPUT_CSV_NAMES
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_data( input_data_dir = '.\\Data\\' ):
|
|
13
|
+
"""
|
|
14
|
+
Loads the required SDOM datasets from CSV files located in the specified input directory.
|
|
15
|
+
Parameters:
|
|
16
|
+
input_data_dir (str): Path to the directory containing the input CSV files. Defaults to '.\\Data\\'.
|
|
17
|
+
Returns:
|
|
18
|
+
dict: A dictionary containing the following keys and their corresponding loaded data:
|
|
19
|
+
- "solar_plants" (list): List of solar plant identifiers.
|
|
20
|
+
- "wind_plants" (list): List of wind plant identifiers.
|
|
21
|
+
- "load_data" (pd.DataFrame): Hourly load data for the year 2050.
|
|
22
|
+
- "nuclear_data" (pd.DataFrame): Hourly nuclear generation data for 2019.
|
|
23
|
+
- "large_hydro_data" (pd.DataFrame): Hourly large hydro generation data for 2019.
|
|
24
|
+
- "other_renewables_data" (pd.DataFrame): Hourly other renewables generation data for 2019.
|
|
25
|
+
- "cf_solar" (pd.DataFrame): Solar capacity factors for 2050.
|
|
26
|
+
- "cf_wind" (pd.DataFrame): Wind capacity factors for 2050.
|
|
27
|
+
- "cap_solar" (pd.DataFrame): Solar plant capacities for 2050.
|
|
28
|
+
- "cap_wind" (pd.DataFrame): Wind plant capacities for 2050.
|
|
29
|
+
- "storage_data" (pd.DataFrame): Storage data for 2050, indexed by the first column.
|
|
30
|
+
- "scalars" (pd.DataFrame): Scalar parameters, indexed by the "Parameter" column.
|
|
31
|
+
Notes:
|
|
32
|
+
- All numeric data is rounded to 5 decimal places.
|
|
33
|
+
- Some columns are explicitly converted to string type for consistency.
|
|
34
|
+
"""
|
|
35
|
+
logging.info("Loading SDOM input data...")
|
|
36
|
+
#os.chdir('./Data/.')
|
|
37
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["solar_plants"])
|
|
38
|
+
if check_file_exists(input_file_path, "solar plants ids"):
|
|
39
|
+
solar_plants = pd.read_csv( input_file_path, header=None )[0].tolist()
|
|
40
|
+
|
|
41
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["wind_plants"])
|
|
42
|
+
if check_file_exists(input_file_path, "wind plants ids"):
|
|
43
|
+
wind_plants = pd.read_csv( input_file_path, header=None )[0].tolist()
|
|
44
|
+
|
|
45
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["load_data"])
|
|
46
|
+
if check_file_exists(input_file_path, "load data"):
|
|
47
|
+
load_data = pd.read_csv( input_file_path ).round(5)
|
|
48
|
+
|
|
49
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["nuclear_data"])
|
|
50
|
+
if check_file_exists(input_file_path, "nuclear data"):
|
|
51
|
+
nuclear_data = pd.read_csv( input_file_path ).round(5)
|
|
52
|
+
|
|
53
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["large_hydro_data"])
|
|
54
|
+
if check_file_exists(input_file_path, "large hydro data"):
|
|
55
|
+
large_hydro_data = pd.read_csv( input_file_path ).round(5)
|
|
56
|
+
|
|
57
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["other_renewables_data"])
|
|
58
|
+
if check_file_exists(input_file_path, "other renewables data"):
|
|
59
|
+
other_renewables_data = pd.read_csv( input_file_path ).round(5)
|
|
60
|
+
|
|
61
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["cf_solar"])
|
|
62
|
+
if check_file_exists(input_file_path, "Capacity factors for pv solar"):
|
|
63
|
+
cf_solar = pd.read_csv( input_file_path ).round(5)
|
|
64
|
+
cf_solar.columns = cf_solar.columns.astype(str)
|
|
65
|
+
|
|
66
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["cf_wind"])
|
|
67
|
+
if check_file_exists(input_file_path, "Capacity factors for wind"):
|
|
68
|
+
cf_wind = pd.read_csv( input_file_path ).round(5)
|
|
69
|
+
cf_wind.columns = cf_wind.columns.astype(str)
|
|
70
|
+
|
|
71
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["cap_solar"])
|
|
72
|
+
if check_file_exists(input_file_path, "Capex information for solar"):
|
|
73
|
+
cap_solar = pd.read_csv( input_file_path ).round(5)
|
|
74
|
+
cap_solar['sc_gid'] = cap_solar['sc_gid'].astype(str)
|
|
75
|
+
|
|
76
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["cap_wind"])
|
|
77
|
+
if check_file_exists(input_file_path, "Capex information for wind"):
|
|
78
|
+
cap_wind = pd.read_csv( input_file_path ).round(5)
|
|
79
|
+
cap_wind['sc_gid'] = cap_wind['sc_gid'].astype(str)
|
|
80
|
+
|
|
81
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["storage_data"])
|
|
82
|
+
if check_file_exists(input_file_path, "Storage data"):
|
|
83
|
+
storage_data = pd.read_csv( input_file_path, index_col=0 ).round(5)
|
|
84
|
+
|
|
85
|
+
input_file_path = os.path.join(input_data_dir, INPUT_CSV_NAMES["scalars"])
|
|
86
|
+
if check_file_exists(input_file_path, "scalars"):
|
|
87
|
+
scalars = pd.read_csv( input_file_path, index_col="Parameter" )
|
|
88
|
+
#os.chdir('../')
|
|
89
|
+
return {
|
|
90
|
+
"solar_plants": solar_plants,
|
|
91
|
+
"wind_plants": wind_plants,
|
|
92
|
+
"load_data": load_data,
|
|
93
|
+
"nuclear_data": nuclear_data,
|
|
94
|
+
"large_hydro_data": large_hydro_data,
|
|
95
|
+
"other_renewables_data": other_renewables_data,
|
|
96
|
+
"cf_solar": cf_solar,
|
|
97
|
+
"cf_wind": cf_wind,
|
|
98
|
+
"cap_solar": cap_solar,
|
|
99
|
+
"cap_wind": cap_wind,
|
|
100
|
+
"storage_data": storage_data,
|
|
101
|
+
"scalars": scalars,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------------
|
|
108
|
+
# Export results to CSV files
|
|
109
|
+
# ---------------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
def export_results( model, case, output_dir = './results_pyomo/' ):
|
|
112
|
+
"""
|
|
113
|
+
Exports optimization results from a Pyomo model to CSV files.
|
|
114
|
+
This function extracts generation, storage, and summary results from the provided Pyomo model instance,
|
|
115
|
+
organizes them into dictionaries and pandas DataFrames, and writes them to CSV files in the specified output directory.
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
model : pyomo.environ.ConcreteModel
|
|
119
|
+
The Pyomo model instance containing the optimization results.
|
|
120
|
+
case : str or int
|
|
121
|
+
Identifier for the current simulation case or scenario. Used in output filenames.
|
|
122
|
+
output_dir : str, optional
|
|
123
|
+
Directory path where the output CSV files will be saved. Defaults to './results_pyomo/'.
|
|
124
|
+
Outputs
|
|
125
|
+
-------
|
|
126
|
+
OutputGeneration_{case}.csv : CSV file
|
|
127
|
+
Contains hourly generation and curtailment results for each technology and scenario.
|
|
128
|
+
OutputStorage_{case}.csv : CSV file
|
|
129
|
+
Contains hourly storage operation results (charging, discharging, state of charge) for each storage technology.
|
|
130
|
+
OutputSummary_{case}.csv : CSV file
|
|
131
|
+
Contains summary metrics including total cost, capacities, generation, demand, CAPEX, OPEX, and other key results.
|
|
132
|
+
Notes
|
|
133
|
+
-----
|
|
134
|
+
- The function assumes the model contains specific variables and sets (e.g., GenPV, CurtPV, GenWind, PC, PD, SOC, etc.).
|
|
135
|
+
- The function creates the output directory if it does not exist.
|
|
136
|
+
- Results are only written if relevant data is available (e.g., non-empty results).
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
logging.info("Exporting SDOM results...")
|
|
140
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
141
|
+
|
|
142
|
+
# Initialize results dictionaries column: [values]
|
|
143
|
+
logging.debug("--Initializing results dictionaries...")
|
|
144
|
+
gen_results = {'Scenario':[],'Hour': [], 'Solar PV Generation (MW)': [], 'Solar PV Curtailment (MW)': [],
|
|
145
|
+
'Wind Generation (MW)': [], 'Wind Curtailment (MW)': [],
|
|
146
|
+
'Gas CC Generation (MW)': [], 'Storage Charge/Discharge (MW)': []}
|
|
147
|
+
|
|
148
|
+
storage_results = {'Hour': [], 'Technology': [], 'Charging power (MW)': [],
|
|
149
|
+
'Discharging power (MW)': [], 'State of charge (MWh)': []}
|
|
150
|
+
|
|
151
|
+
summary_results_columns = ['Metric', 'Technology', 'Run', 'Optimal Value', 'Unit']
|
|
152
|
+
summary_results = pd.DataFrame(columns=summary_results_columns)
|
|
153
|
+
|
|
154
|
+
# Extract generation results
|
|
155
|
+
# for run in range(num_runs):
|
|
156
|
+
logging.debug("--Extracting generation results...")
|
|
157
|
+
for h in model.h:
|
|
158
|
+
solar_gen = safe_pyomo_value(model.GenPV[h])
|
|
159
|
+
solar_curt = safe_pyomo_value(model.CurtPV[h])
|
|
160
|
+
wind_gen = safe_pyomo_value(model.GenWind[h])
|
|
161
|
+
wind_curt = safe_pyomo_value(model.CurtWind[h])
|
|
162
|
+
gas_cc_gen = safe_pyomo_value(model.GenCC[h])
|
|
163
|
+
|
|
164
|
+
if None not in [solar_gen, solar_curt, wind_gen, wind_curt, gas_cc_gen]:
|
|
165
|
+
# gen_results['Scenario'].append(run)
|
|
166
|
+
gen_results['Hour'].append(h)
|
|
167
|
+
gen_results['Solar PV Generation (MW)'].append(solar_gen)
|
|
168
|
+
gen_results['Solar PV Curtailment (MW)'].append(solar_curt)
|
|
169
|
+
gen_results['Wind Generation (MW)'].append(wind_gen)
|
|
170
|
+
gen_results['Wind Curtailment (MW)'].append(wind_curt)
|
|
171
|
+
gen_results['Gas CC Generation (MW)'].append(gas_cc_gen)
|
|
172
|
+
|
|
173
|
+
power_to_storage = sum(safe_pyomo_value(model.PC[h, j]) or 0 for j in model.j) - sum(safe_pyomo_value(model.PD[h, j]) or 0 for j in model.j)
|
|
174
|
+
gen_results['Storage Charge/Discharge (MW)'].append(power_to_storage)
|
|
175
|
+
gen_results['Scenario'].append(case)
|
|
176
|
+
|
|
177
|
+
# Extract storage results
|
|
178
|
+
logging.debug("--Extracting storage results...")
|
|
179
|
+
for h in model.h:
|
|
180
|
+
for j in model.j:
|
|
181
|
+
charge_power = safe_pyomo_value(model.PC[h, j])
|
|
182
|
+
discharge_power = safe_pyomo_value(model.PD[h, j])
|
|
183
|
+
soc = safe_pyomo_value(model.SOC[h, j])
|
|
184
|
+
if None not in [charge_power, discharge_power, soc]:
|
|
185
|
+
storage_results['Hour'].append(h)
|
|
186
|
+
storage_results['Technology'].append(j)
|
|
187
|
+
storage_results['Charging power (MW)'].append(charge_power)
|
|
188
|
+
storage_results['Discharging power (MW)'].append(discharge_power)
|
|
189
|
+
storage_results['State of charge (MWh)'].append(soc)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# Summary results (total capacities and costs)
|
|
194
|
+
## Total cost
|
|
195
|
+
logging.debug("--Extracting summary results...")
|
|
196
|
+
total_cost = pd.DataFrame.from_dict({'Total cost':[None, 1,safe_pyomo_value(model.Obj()), '$US']}, orient='index',
|
|
197
|
+
columns=['Technology','Run','Optimal Value', 'Unit'])
|
|
198
|
+
total_cost = total_cost.reset_index(names='Metric')
|
|
199
|
+
summary_results = pd.concat([summary_results, total_cost], ignore_index=True)
|
|
200
|
+
## Total capacity
|
|
201
|
+
cap = {}
|
|
202
|
+
cap['GasCC'] = safe_pyomo_value(model.CapCC)
|
|
203
|
+
cap['Solar PV'] = sum(safe_pyomo_value(model.Ypv[k]) * model.CapSolar_CAPEX_M[k] for k in model.k)
|
|
204
|
+
cap['Wind'] = sum(safe_pyomo_value(model.Ywind[w]) * model.CapWind_CAPEX_M[w] for w in model.w)
|
|
205
|
+
cap['All'] = cap['GasCC'] + cap['Solar PV'] + cap['Wind']
|
|
206
|
+
capacities = pd.DataFrame.from_dict(cap, orient='index', columns=['Optimal Value'])
|
|
207
|
+
capacities = capacities.reset_index(names=['Technology'])
|
|
208
|
+
capacities['Run'] = 1
|
|
209
|
+
capacities['Unit'] = 'MW'
|
|
210
|
+
capacities['Metric'] = 'Capacity'
|
|
211
|
+
summary_results = pd.concat([summary_results, capacities], ignore_index=True)
|
|
212
|
+
## Generation
|
|
213
|
+
gen = {}
|
|
214
|
+
gen['GasCC'] = safe_pyomo_value(sum(model.GenCC[h] for h in model.h))
|
|
215
|
+
gen['Solar PV'] = sum(safe_pyomo_value(model.GenPV[h]) for h in model.h)
|
|
216
|
+
gen['Wind'] = sum(safe_pyomo_value(model.GenWind[h]) for h in model.h)
|
|
217
|
+
gen['Other renewables'] = safe_pyomo_value(sum(model.OtherRenewables[h]for h in model.h))
|
|
218
|
+
gen['Hydro'] = safe_pyomo_value(sum(model.LargeHydro[h] for h in model.h))
|
|
219
|
+
gen['Nuclear'] = safe_pyomo_value(sum(model.Nuclear[h] for h in model.h))
|
|
220
|
+
gen['LiIon'] = safe_pyomo_value(sum(model.PD[h, 'Li-Ion'] for h in model.h))
|
|
221
|
+
gen['CAES'] = safe_pyomo_value(sum(model.PD[h, 'CAES'] for h in model.h) )
|
|
222
|
+
gen['PHS'] = safe_pyomo_value(sum(model.PD[h, 'PHS'] for h in model.h) )
|
|
223
|
+
gen['H2'] = safe_pyomo_value(sum(model.PD[h, 'H2'] for h in model.h) )
|
|
224
|
+
gen['All'] = gen['GasCC'] + gen['Solar PV'] + gen['Wind'] + gen['Other renewables'] + gen['Hydro'] + \
|
|
225
|
+
gen['Nuclear'] + gen['LiIon'] + gen['CAES'] + gen['PHS'] + gen['H2']
|
|
226
|
+
generation = pd.DataFrame.from_dict(gen, orient='index', columns=['Optimal Value'])
|
|
227
|
+
generation = generation.reset_index(names=['Technology'])
|
|
228
|
+
generation['Run'] = 1
|
|
229
|
+
generation['Unit'] = 'MWh'
|
|
230
|
+
generation['Metric'] = 'Total generation'
|
|
231
|
+
summary_results = pd.concat([summary_results, generation], ignore_index=True)
|
|
232
|
+
## Storage energy charging
|
|
233
|
+
stoch = {}
|
|
234
|
+
stoch['LiIon'] = safe_pyomo_value(sum(model.PC[h, 'Li-Ion'] for h in model.h))
|
|
235
|
+
stoch['CAES'] = safe_pyomo_value(sum(model.PC[h, 'CAES'] for h in model.h) )
|
|
236
|
+
stoch['PHS'] = safe_pyomo_value(sum(model.PC[h, 'PHS'] for h in model.h) )
|
|
237
|
+
stoch['H2'] = safe_pyomo_value(sum(model.PC[h, 'H2'] for h in model.h) )
|
|
238
|
+
stoch['All'] = stoch['LiIon'] + stoch['CAES'] + stoch['PHS'] + stoch['H2']
|
|
239
|
+
storage_charging = pd.DataFrame.from_dict(stoch, orient='index', columns=['Optimal Value'])
|
|
240
|
+
storage_charging = storage_charging.reset_index(names=['Technology'])
|
|
241
|
+
storage_charging['Run'] = 1
|
|
242
|
+
storage_charging['Unit'] = 'MWh'
|
|
243
|
+
storage_charging['Metric'] = 'Storage energy charging'
|
|
244
|
+
summary_results = pd.concat([summary_results, storage_charging], ignore_index=True)
|
|
245
|
+
## Demand
|
|
246
|
+
dem = {}
|
|
247
|
+
dem['demand'] = sum(model.Load[h] for h in model.h)
|
|
248
|
+
demand = pd.DataFrame.from_dict(dem, orient='index', columns=['Optimal Value'])
|
|
249
|
+
demand = demand.reset_index(names=['Technology'])
|
|
250
|
+
demand['Run'] = 1
|
|
251
|
+
demand['Unit'] = 'MWh'
|
|
252
|
+
demand['Metric'] = 'Total demand'
|
|
253
|
+
summary_results = pd.concat([summary_results, demand], ignore_index=True)
|
|
254
|
+
## CAPEX
|
|
255
|
+
capex = {}
|
|
256
|
+
capex['Solar PV'] = sum(safe_pyomo_value((model.FCR_VRE * (1000 * model.CapSolar_CAPEX_M[k] + model.CapSolar_trans_cap_cost[k]))\
|
|
257
|
+
* model.CapSolar_capacity[k] * model.Ypv[k]) for k in model.k)
|
|
258
|
+
capex['Wind'] = sum(safe_pyomo_value((model.FCR_VRE * (1000 * model.CapWind_CAPEX_M[w] + model.CapWind_trans_cap_cost[w])) \
|
|
259
|
+
* model.CapWind_capacity[w] * model.Ywind[w]) for w in model.w)
|
|
260
|
+
capex['GasCC'] = safe_pyomo_value(model.FCR_GasCC*1000*model.CapexGasCC*model.CapCC)
|
|
261
|
+
capex['All'] = capex['Solar PV'] + capex['Wind'] + capex['GasCC']
|
|
262
|
+
capital_costs = pd.DataFrame.from_dict(capex, orient='index', columns=['Optimal Value'])
|
|
263
|
+
capital_costs = capital_costs.reset_index(names=['Technology'])
|
|
264
|
+
capital_costs['Run'] = 1
|
|
265
|
+
capital_costs['Unit'] = '$US'
|
|
266
|
+
capital_costs['Metric'] = 'CAPEX'
|
|
267
|
+
summary_results = pd.concat([summary_results, capital_costs], ignore_index=True)
|
|
268
|
+
## Power CAPEX
|
|
269
|
+
pcapex = {}
|
|
270
|
+
pcapex['LiIon'] = safe_pyomo_value(model.CRF['Li-Ion']*(1000*model.StorageData['CostRatio', 'Li-Ion'] * \
|
|
271
|
+
model.StorageData['P_Capex', 'Li-Ion']*model.Pcha['Li-Ion']
|
|
272
|
+
+ 1000*(1 - model.StorageData['CostRatio', 'Li-Ion']) * \
|
|
273
|
+
model.StorageData['P_Capex', 'Li-Ion']*model.Pdis['Li-Ion']))
|
|
274
|
+
pcapex['CAES'] = safe_pyomo_value(model.CRF['CAES']*(1000*model.StorageData['CostRatio', 'CAES'] * \
|
|
275
|
+
model.StorageData['P_Capex', 'CAES']*model.Pcha['CAES']
|
|
276
|
+
+ 1000*(1 - model.StorageData['CostRatio', 'CAES']) * \
|
|
277
|
+
model.StorageData['P_Capex', 'CAES']*model.Pdis['CAES']))
|
|
278
|
+
pcapex['PHS'] = safe_pyomo_value(model.CRF['PHS']*(1000*model.StorageData['CostRatio', 'PHS'] * \
|
|
279
|
+
model.StorageData['P_Capex', 'PHS']*model.Pcha['PHS']
|
|
280
|
+
+ 1000*(1 - model.StorageData['CostRatio', 'PHS']) * \
|
|
281
|
+
model.StorageData['P_Capex', 'PHS']*model.Pdis['PHS']))
|
|
282
|
+
pcapex['H2'] = safe_pyomo_value(model.CRF['H2']*(1000*model.StorageData['CostRatio', 'H2'] * \
|
|
283
|
+
model.StorageData['P_Capex', 'H2']*model.Pcha['H2']
|
|
284
|
+
+ 1000*(1 - model.StorageData['CostRatio', 'H2']) * \
|
|
285
|
+
model.StorageData['P_Capex', 'H2']*model.Pdis['H2']))
|
|
286
|
+
pcapex['All'] = pcapex['LiIon'] + pcapex['CAES'] + pcapex['PHS'] + pcapex['H2']
|
|
287
|
+
power_capex = pd.DataFrame.from_dict(pcapex, orient='index',columns=['Optimal Value'])
|
|
288
|
+
power_capex = power_capex.reset_index(names=['Technology'])
|
|
289
|
+
power_capex['Run'] = 1
|
|
290
|
+
power_capex['Unit'] = '$US'
|
|
291
|
+
power_capex['Metric'] = 'Power-CAPEX'
|
|
292
|
+
summary_results = pd.concat([summary_results, power_capex], ignore_index=True)
|
|
293
|
+
## Energy CAPEX
|
|
294
|
+
ecapex = {}
|
|
295
|
+
ecapex['LiIon'] = safe_pyomo_value(model.CRF['Li-Ion']*1000*model.StorageData['E_Capex', 'Li-Ion']*model.Ecap['Li-Ion'])
|
|
296
|
+
ecapex['CAES'] = safe_pyomo_value(model.CRF['CAES']*1000*model.StorageData['E_Capex', 'CAES']*model.Ecap['CAES'])
|
|
297
|
+
ecapex['PHS'] = safe_pyomo_value(model.CRF['PHS']*1000*model.StorageData['E_Capex', 'PHS']*model.Ecap['PHS'])
|
|
298
|
+
ecapex['H2'] = safe_pyomo_value(model.CRF['H2']*1000*model.StorageData['E_Capex', 'H2']*model.Ecap['H2'])
|
|
299
|
+
ecapex['All'] = ecapex['LiIon'] + ecapex['CAES'] + ecapex['PHS'] + ecapex['H2']
|
|
300
|
+
energy_capex = pd.DataFrame.from_dict(ecapex, orient='index',columns=['Optimal Value'])
|
|
301
|
+
energy_capex = energy_capex.reset_index(names=['Technology'])
|
|
302
|
+
energy_capex['Run'] = 1
|
|
303
|
+
energy_capex['Unit'] = '$US'
|
|
304
|
+
energy_capex['Metric'] = 'Energy-CAPEX'
|
|
305
|
+
summary_results = pd.concat([summary_results, energy_capex], ignore_index=True)
|
|
306
|
+
## Total CAPEX
|
|
307
|
+
tcapex = {}
|
|
308
|
+
tcapex['LiIon'] = pcapex['LiIon'] + ecapex['LiIon']
|
|
309
|
+
tcapex['CAES'] = pcapex['CAES'] + ecapex['CAES']
|
|
310
|
+
tcapex['PHS'] = pcapex['PHS'] + ecapex['PHS']
|
|
311
|
+
tcapex['H2'] = pcapex['H2'] + ecapex['H2']
|
|
312
|
+
tcapex['All'] = tcapex['LiIon'] + tcapex['CAES'] + tcapex['PHS'] + tcapex['H2']
|
|
313
|
+
total_capex = pd.DataFrame.from_dict(tcapex, orient='index',columns=['Optimal Value'])
|
|
314
|
+
total_capex = total_capex.reset_index(names=['Technology'])
|
|
315
|
+
total_capex['Run'] = 1
|
|
316
|
+
total_capex['Unit'] = '$US'
|
|
317
|
+
total_capex['Metric'] = 'Total-CAPEX'
|
|
318
|
+
summary_results = pd.concat([summary_results, total_capex], ignore_index=True)
|
|
319
|
+
## FOM
|
|
320
|
+
fom = {}
|
|
321
|
+
fom['GasCC'] = safe_pyomo_value(1000*model.FOM_GasCC*model.CapCC)
|
|
322
|
+
fom['Solar PV'] = sum(safe_pyomo_value((model.FCR_VRE * 1000*model.CapSolar_FOM_M[k]) * model.CapSolar_capacity[k] * model.Ypv[k]) for k in model.k)
|
|
323
|
+
fom['Wind'] = sum(safe_pyomo_value((model.FCR_VRE * 1000*model.CapWind_FOM_M[w]) * model.CapWind_capacity[w] * model.Ywind[w]) for w in model.w)
|
|
324
|
+
fom['LiIon'] = safe_pyomo_value(1000*model.StorageData['CostRatio', 'Li-Ion'] * model.StorageData['FOM', 'Li-Ion']*model.Pcha['Li-Ion']
|
|
325
|
+
+ 1000*(1 - model.StorageData['CostRatio', 'Li-Ion']) * model.StorageData['FOM', 'Li-Ion']*model.Pdis['Li-Ion'])
|
|
326
|
+
fom['CAES'] = safe_pyomo_value(1000*model.StorageData['CostRatio', 'CAES'] * model.StorageData['FOM', 'CAES']*model.Pcha['CAES']
|
|
327
|
+
+ 1000*(1 - model.StorageData['CostRatio', 'CAES']) * model.StorageData['FOM', 'CAES']*model.Pdis['CAES'])
|
|
328
|
+
fom['PHS'] = safe_pyomo_value(1000*model.StorageData['CostRatio', 'PHS'] * model.StorageData['FOM', 'PHS']*model.Pcha['PHS']
|
|
329
|
+
+ 1000*(1 - model.StorageData['CostRatio', 'PHS']) * model.StorageData['FOM', 'PHS']*model.Pdis['PHS'])
|
|
330
|
+
fom['H2'] = safe_pyomo_value(1000*model.StorageData['CostRatio', 'H2'] * model.StorageData['FOM', 'H2']*model.Pcha['H2']
|
|
331
|
+
+ 1000*(1 - model.StorageData['CostRatio', 'H2']) * model.StorageData['FOM', 'H2']*model.Pdis['H2'])
|
|
332
|
+
fom['All'] = fom['GasCC'] + fom['Solar PV'] + fom['Wind'] + fom['LiIon'] + fom['CAES'] + fom['PHS'] + fom['H2']
|
|
333
|
+
fixedom = pd.DataFrame.from_dict(fom, orient='index', columns=['Optimal Value'])
|
|
334
|
+
fixedom = fixedom.reset_index(names=['Technology'])
|
|
335
|
+
fixedom['Run'] = 1
|
|
336
|
+
fixedom['Unit'] = '$US'
|
|
337
|
+
fixedom['Metric'] = 'FOM'
|
|
338
|
+
summary_results = pd.concat([summary_results, fixedom], ignore_index=True)
|
|
339
|
+
## VOM
|
|
340
|
+
vom = {}
|
|
341
|
+
vom['GasCC'] = safe_pyomo_value((model.GasPrice * model.HR + model.VOM_GasCC) *sum(model.GenCC[h] for h in model.h))
|
|
342
|
+
vom['LiIon'] = safe_pyomo_value(model.StorageData['VOM', 'Li-Ion'] * sum(model.PD[h, 'Li-Ion'] for h in model.h))
|
|
343
|
+
vom['CAES'] = safe_pyomo_value(model.StorageData['VOM', 'CAES'] * sum(model.PD[h, 'CAES'] for h in model.h) )
|
|
344
|
+
vom['PHS'] = safe_pyomo_value(model.StorageData['VOM', 'PHS'] * sum(model.PD[h, 'PHS'] for h in model.h) )
|
|
345
|
+
vom['H2'] = safe_pyomo_value(model.StorageData['VOM', 'H2'] * sum(model.PD[h, 'H2'] for h in model.h) )
|
|
346
|
+
vom['All'] = vom['GasCC'] + vom['LiIon'] + vom['CAES'] + vom['PHS'] + vom['H2']
|
|
347
|
+
variableom = pd.DataFrame.from_dict(vom, orient='index', columns=['Optimal Value'])
|
|
348
|
+
variableom = variableom.reset_index(names=['Technology'])
|
|
349
|
+
variableom['Run'] = 1
|
|
350
|
+
variableom['Unit'] = '$US'
|
|
351
|
+
variableom['Metric'] = 'VOM'
|
|
352
|
+
summary_results = pd.concat([summary_results, variableom], ignore_index=True)
|
|
353
|
+
## OPEX
|
|
354
|
+
opex = {}
|
|
355
|
+
opex['GasCC'] = fom['GasCC'] + vom['GasCC']
|
|
356
|
+
opex['Solar PV'] = fom['Solar PV']
|
|
357
|
+
opex['Wind'] = fom['Wind']
|
|
358
|
+
opex['LiIon'] = fom['LiIon'] + vom['LiIon']
|
|
359
|
+
opex['CAES'] = fom['CAES'] + vom['CAES']
|
|
360
|
+
opex['PHS'] = fom['PHS'] + vom['PHS']
|
|
361
|
+
opex['H2'] = fom['H2'] + vom['H2']
|
|
362
|
+
opex['All'] = opex['GasCC'] + opex['Solar PV'] + opex['Wind'] + opex['LiIon'] + opex['CAES'] + opex['PHS'] + opex['H2']
|
|
363
|
+
operating_cost = pd.DataFrame.from_dict(opex, orient='index', columns=['Optimal Value'])
|
|
364
|
+
operating_cost = operating_cost.reset_index(names=['Technology'])
|
|
365
|
+
operating_cost['Run'] = 1
|
|
366
|
+
operating_cost['Unit'] = '$US'
|
|
367
|
+
operating_cost['Metric'] = 'OPEX'
|
|
368
|
+
summary_results = pd.concat([summary_results, operating_cost], ignore_index=True)
|
|
369
|
+
## Charge power capacity
|
|
370
|
+
charge = {}
|
|
371
|
+
charge['LiIon'] = safe_pyomo_value(model.Pcha['Li-Ion'])
|
|
372
|
+
charge['CAES'] = safe_pyomo_value(model.Pcha['CAES'])
|
|
373
|
+
charge['PHS'] = safe_pyomo_value(model.Pcha['PHS'])
|
|
374
|
+
charge['H2'] = safe_pyomo_value(model.Pcha['H2'])
|
|
375
|
+
charge['All'] = charge['LiIon'] + charge['CAES'] + charge['PHS'] + charge['H2']
|
|
376
|
+
charge_power = pd.DataFrame.from_dict(charge, orient='index', columns=['Optimal Value'])
|
|
377
|
+
charge_power = charge_power.reset_index(names=['Technology'])
|
|
378
|
+
charge_power['Run'] = 1
|
|
379
|
+
charge_power['Unit'] = 'MW'
|
|
380
|
+
charge_power['Metric'] = 'Charge power capacity'
|
|
381
|
+
summary_results = pd.concat([summary_results, charge_power], ignore_index=True)
|
|
382
|
+
## Discharge power capacity
|
|
383
|
+
dcharge = {}
|
|
384
|
+
dcharge['LiIon'] = safe_pyomo_value(model.Pdis['Li-Ion'] )
|
|
385
|
+
dcharge['CAES'] = safe_pyomo_value(model.Pdis['CAES'])
|
|
386
|
+
dcharge['PHS'] = safe_pyomo_value(model.Pdis['PHS'])
|
|
387
|
+
dcharge['H2'] = safe_pyomo_value(model.Pdis['H2'])
|
|
388
|
+
dcharge['All'] = dcharge['LiIon'] + dcharge['CAES'] + dcharge['PHS'] + dcharge['H2']
|
|
389
|
+
dcharge_power = pd.DataFrame.from_dict(dcharge, orient='index', columns=['Optimal Value'])
|
|
390
|
+
dcharge_power = dcharge_power.reset_index(names=['Technology'])
|
|
391
|
+
dcharge_power['Run'] = 1
|
|
392
|
+
dcharge_power['Unit'] = 'MW'
|
|
393
|
+
dcharge_power['Metric'] = 'Discharge power capacity'
|
|
394
|
+
summary_results = pd.concat([summary_results, dcharge_power], ignore_index=True)
|
|
395
|
+
## Average power capacity
|
|
396
|
+
avgpocap = {}
|
|
397
|
+
avgpocap['LiIon'] = (charge['LiIon'] + dcharge['LiIon'])/2
|
|
398
|
+
avgpocap['CAES'] = (charge['CAES'] + dcharge['CAES'])/2
|
|
399
|
+
avgpocap['PHS'] = (charge['PHS'] + dcharge['PHS'])/2
|
|
400
|
+
avgpocap['H2'] = (charge['H2'] + dcharge['H2'])/2
|
|
401
|
+
avgpocap['All'] = avgpocap['LiIon'] + avgpocap['CAES'] + avgpocap['PHS'] + avgpocap['H2']
|
|
402
|
+
average_power = pd.DataFrame.from_dict(avgpocap, orient='index', columns=['Optimal Value'])
|
|
403
|
+
average_power = average_power.reset_index(names=['Technology'])
|
|
404
|
+
average_power['Run'] = 1
|
|
405
|
+
average_power['Unit'] = 'MW'
|
|
406
|
+
average_power['Metric'] = 'Average power capacity'
|
|
407
|
+
summary_results = pd.concat([summary_results, average_power], ignore_index=True)
|
|
408
|
+
## Energy capacity
|
|
409
|
+
encap = {}
|
|
410
|
+
encap['LiIon'] = safe_pyomo_value(model.Ecap['Li-Ion'] )
|
|
411
|
+
encap['CAES'] = safe_pyomo_value(model.Ecap['CAES'])
|
|
412
|
+
encap['PHS'] = safe_pyomo_value(model.Ecap['PHS'])
|
|
413
|
+
encap['H2'] = safe_pyomo_value(model.Ecap['H2'])
|
|
414
|
+
encap['All'] = encap['LiIon'] + encap['CAES'] + encap['PHS'] + encap['H2']
|
|
415
|
+
energy_cap = pd.DataFrame.from_dict(encap, orient='index', columns=['Optimal Value'])
|
|
416
|
+
energy_cap = energy_cap.reset_index(names=['Technology'])
|
|
417
|
+
energy_cap['Run'] = 1
|
|
418
|
+
energy_cap['Unit'] = 'MWh'
|
|
419
|
+
energy_cap['Metric'] = 'Energy capacity'
|
|
420
|
+
summary_results = pd.concat([summary_results, energy_cap], ignore_index=True)
|
|
421
|
+
## Discharge duration
|
|
422
|
+
dur = {}
|
|
423
|
+
dur['LiIon'] = safe_pyomo_value(sqrt(model.StorageData['Eff','Li-Ion']*model.Ecap['Li-Ion']/(model.Pdis['Li-Ion'] + 1e-15)))
|
|
424
|
+
dur['CAES'] = safe_pyomo_value(sqrt(model.StorageData['Eff','CAES']*model.Ecap['CAES']/(model.Pdis['CAES'] + 1e-15)))
|
|
425
|
+
dur['PHS'] = safe_pyomo_value(sqrt(model.StorageData['Eff','PHS']*model.Ecap['PHS']/(model.Pdis['PHS'] + 1e-15)))
|
|
426
|
+
dur['H2'] = safe_pyomo_value(sqrt(model.StorageData['Eff','H2']*model.Ecap['H2']/(model.Pdis['H2'] + 1e-15)))
|
|
427
|
+
duration = pd.DataFrame.from_dict(dur, orient='index', columns=['Optimal Value'])
|
|
428
|
+
duration = duration.reset_index(names=['Technology'])
|
|
429
|
+
duration['Run'] = 1
|
|
430
|
+
duration['Unit'] = 'h'
|
|
431
|
+
duration['Metric'] = 'Duration'
|
|
432
|
+
summary_results = pd.concat([summary_results, duration], ignore_index=True)
|
|
433
|
+
## Equivalent number of cycles
|
|
434
|
+
cyc = {}
|
|
435
|
+
cyc['LiIon'] = safe_pyomo_value(gen['LiIon']/(model.Ecap['Li-Ion'] + 1e-15))
|
|
436
|
+
cyc['CAES'] = safe_pyomo_value(gen['CAES']/(model.Ecap['CAES'] + 1e-15))
|
|
437
|
+
cyc['PHS'] = safe_pyomo_value(gen['PHS']/(model.Ecap['PHS'] + 1e-15))
|
|
438
|
+
cyc['H2'] = safe_pyomo_value(gen['H2']/(model.Ecap['H2']+ 1e-15))
|
|
439
|
+
cycles = pd.DataFrame.from_dict(cyc, orient='index', columns=['Optimal Value'])
|
|
440
|
+
cycles = cycles.reset_index(names=['Technology'])
|
|
441
|
+
cycles['Run'] = 1
|
|
442
|
+
cycles['Unit'] = '-'
|
|
443
|
+
cycles['Metric'] = 'Equivalent number of cycles'
|
|
444
|
+
summary_results = pd.concat([summary_results, cycles], ignore_index=True)
|
|
445
|
+
|
|
446
|
+
logging.info("Exporting csv files containing SDOM results...")
|
|
447
|
+
# Save generation results to CSV
|
|
448
|
+
logging.debug("--Saving generation results to CSV...")
|
|
449
|
+
if gen_results['Hour']:
|
|
450
|
+
with open(output_dir + f'OutputGeneration_{case}.csv', mode='w', newline='') as file:
|
|
451
|
+
writer = csv.DictWriter(file, fieldnames=gen_results.keys())
|
|
452
|
+
writer.writeheader()
|
|
453
|
+
writer.writerows([dict(zip(gen_results, t))
|
|
454
|
+
for t in zip(*gen_results.values())])
|
|
455
|
+
|
|
456
|
+
# Save storage results to CSV
|
|
457
|
+
logging.debug("--Saving storage results to CSV...")
|
|
458
|
+
if storage_results['Hour']:
|
|
459
|
+
with open(output_dir + f'OutputStorage_{case}.csv', mode='w', newline='') as file:
|
|
460
|
+
writer = csv.DictWriter(file, fieldnames=storage_results.keys())
|
|
461
|
+
writer.writeheader()
|
|
462
|
+
writer.writerows([dict(zip(storage_results, t))
|
|
463
|
+
for t in zip(*storage_results.values())])
|
|
464
|
+
|
|
465
|
+
# Save summary results to CSV
|
|
466
|
+
logging.debug("--Saving summary results to CSV...")
|
|
467
|
+
if len(summary_results)>0:
|
|
468
|
+
summary_results.to_csv(output_dir + f'OutputSummary_{case}.csv', index=False)
|