diff --git a/data_files/monte_carlo/run_settings_1.csv b/data_files/monte_carlo/run_settings_1.csv
new file mode 100644
index 00000000..869ee12d
--- /dev/null
+++ b/data_files/monte_carlo/run_settings_1.csv
@@ -0,0 +1,5 @@
+run,param,index,mod,value,notes
+1,MaxCapacity,utopia|2010|TXD,a,-1.0,reduce the max capacity of TXD in region Utopia in period 2010 by 1.0 units (absolute)
+2,Demand,utopia|*|RH,r,0.5,make Res Heat costlier by 50% in all 3 periods
+2,CostVariable,*|1990/2000|IMPOIL1|*,s,20.0,substitute cost of 20.0 for var cost of IMPOIL in periods 1990/2000 in all regions (just utopia exists) for all vintages
+3,CostVariable,china|1990|IMPOIL1|*,s,1000,bad input: unknown region (china) should fail and be logged
\ No newline at end of file
diff --git a/data_files/my_configs/config_sample.toml b/data_files/my_configs/config_sample.toml
index 59017324..1763880a 100644
--- a/data_files/my_configs/config_sample.toml
+++ b/data_files/my_configs/config_sample.toml
@@ -15,7 +15,7 @@ scenario = "zulu"
# Scenaio Mode (Mandatory)
# See documentation for explanations. A standard single run is "perfect_foresight"
# mode must be one of (case-insensitive):
-# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check]
+# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check, monte_carlo]
scenario_mode = "perfect_foresight"
# Input database (Mandatory)
@@ -108,3 +108,7 @@ emission_labels = ['co2', 'nox']
capacity_labels = ['TXD', 'TXG']
activity_labels = []
+[monte_carlo]
+# a path from the PROJECT ROOT to the settings file that contains the run data.
+run_settings = 'data_files/monte_carlo/run_settings_1.csv'
+
diff --git a/data_files/my_configs/mga_utopia.toml b/data_files/my_configs/mga_utopia.toml
index a0581fc8..250f0015 100644
--- a/data_files/my_configs/mga_utopia.toml
+++ b/data_files/my_configs/mga_utopia.toml
@@ -18,13 +18,13 @@ scenario = "sierra"
scenario_mode = "mga"
# Input database (Mandatory)
-input_database = "data_files/utopia.sqlite"
+input_database = "data_files/example_dbs/utopia.sqlite"
# Output file (Mandatory)
# The output file must be an existing .sqlite file
# For Pefrect Foresight, the user may target the same input file or a separate /
# copied sqlite file in a different location. Myopic requires that input_database = output_database
-output_database = "data_files/utopia.sqlite"
+output_database = "data_files/example_dbs/utopia.sqlite"
# ------------------------------------
# DATA / MODEL CHECKS
@@ -79,7 +79,7 @@ save_lp_file = false
[MGA]
# see notes on these in the extensions/modeling_to_generate_alternatives folder readme.txt
cost_epsilon = 0.03 # propotional relaxation on optimal cost (ex: 0.05 = bound at 105% of original optimal cost)
-iteration_limit = 55 # max iterations to perform
+iteration_limit = 20 # max iterations to perform
time_limit_hrs = 1 # max time
axis = "tech_category_activity" # use the tech activity Manager to control exploration based on categories in Tech
weighting = "hull_expansion" # use a convex hull expansion algorithm to weight exploration
diff --git a/data_files/my_configs/monte_carlo_utopia.toml b/data_files/my_configs/monte_carlo_utopia.toml
new file mode 100644
index 00000000..263afb91
--- /dev/null
+++ b/data_files/my_configs/monte_carlo_utopia.toml
@@ -0,0 +1,114 @@
+# ----------------------------------------------------------
+# Configuration file for a Temoa Run
+# Allows specification of run type and associated parameters
+# ----------------------------------------------------------
+#
+# For toml format info see: https://toml.io/en/
+# - comments may be added with hash
+# - do NOT comment out table names in brackets like: [
]
+
+# Scenario Name (Mandatory)
+# This scenario name is used to label results within the output .sqlite file
+# (cannot contain "-" dash)
+scenario = "Orange Squirrel"
+
+# Scenaio Mode (Mandatory)
+# See documentation for explanations. A standard single run is "perfect_foresight"
+# mode must be one of (case-insensitive):
+# [perfect_foresight, MGA, myopic, method_of_morris, build_only, check, monte_carlo]
+scenario_mode = "monte_carlo"
+
+# Input database (Mandatory)
+input_database = "data_files/example_dbs/utopia.sqlite"
+
+# Output file (Mandatory)
+# The output file must be an existing .sqlite file
+# For Pefrect Foresight, the user may target the same input file or a separate /
+# copied sqlite file in a different location. Myopic, MGA require that input_database = output_database
+output_database = "data_files/example_dbs/utopia.sqlite"
+
+# ------------------------------------
+# DATA / MODEL CHECKS
+# To check data / cost integrity
+# ------------------------------------
+
+# See the documentation section on Data Quality for notes on the features below
+
+# Check the pricing structure for common errors, which are reported in the log file
+# Strongly recommended
+price_check = true
+
+# Check the network connectivity for processes in the model. Strongly
+# recommended to ensure proper performance. Results are reported in log file
+# This requires that source commodities be marked with 's' in Commodity table
+# This is required for Myopic runs
+source_trace = true
+
+# Produce HTML files for Commodity Networks. Requires source_trace above
+plot_commodity_network = true
+
+# ------------------------------------
+# SOLVER
+# Solver Selection
+# ------------------------------------
+
+# use the NEOS server to solve. (Currently NOT supported)
+neos = false
+
+# solver (Mandatory)
+# Depending on what client machine has installed.
+# [cbc, appsi_highs, gurobi, cplex, ...]
+solver_name = "cbc"
+
+# ------------------------------------
+# OUTPUTS
+# select desired output products/files
+# ------------------------------------
+
+# generate an Excel file in the output_files folder
+save_excel = true
+
+# save the duals in the output Database (may slow execution slightly?)
+save_duals = true
+
+# save a copy of the pyomo-generated lp file(s) to the outputs folder (maybe a large file(s)!)
+save_lp_file = false
+
+# ---------------------------------------------------
+# MODE OPTIONS
+# options below are mode-specific and will be ignored
+# if the run is not executed in that mode.
+# ---------------------------------------------------
+[MGA]
+# see notes on these in the extensions/modeling_to_generate_alternatives folder readme.txt
+cost_epsilon = 0.03 # propotional relaxation on optimal cost (ex: 0.05 = bound at 105% of original optimal cost)
+iteration_limit = 55 # max iterations to perform
+time_limit_hrs = 1 # max time
+axis = "tech_category_activity" # use the tech activity Manager to control exploration based on categories in Tech
+weighting = "hull_expansion" # use a convex hull expansion algorithm to weight exploration
+
+[myopic]
+view_depth = 2 # number of periods seen/analyzed per iteration
+step_size = 1 # number of periods to step by (must be <= view depth)
+
+[morris]
+perturbation = 0.10 # amount to perturb marked parameters (ex: 0.10 -> +/- 10%)
+levels = 8 # number of levels in param grid (must be even number)
+trajectories = 10 # number of Morris trajectories to generate/explore
+seed = false # random seed for use in generation/analysis for repeatable results. false=system derived
+cores = 0 # number of CPU cores to use. 0 (default) = cpu count
+# Note: Problem size (in general) is (Groups + 1) * trajectories see the SALib Dox
+# Groups = number of unique labels used in MM analysis columns in DB
+
+[SVMGA]
+cost_epsilon = 0.05
+# labels from appropriate tables in database. It is recommended to only use one of the lists below and leave
+# the others blank
+emission_labels = ['co2', 'nox']
+capacity_labels = ['TXD', 'TXG']
+activity_labels = []
+
+[monte_carlo]
+# a path from the PROJECT ROOT to the settings file that contains the run data.
+run_settings = 'data_files/monte_carlo/run_settings_1.csv'
+
diff --git a/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py b/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py
index 10536354..d7f1d4c9 100644
--- a/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py
+++ b/temoa/extensions/modeling_to_generate_alternatives/mga_sequencer.py
@@ -75,13 +75,13 @@ def __init__(self, config: TemoaConfig):
'Recommend selecting source trace in config file.'
)
if config.save_lp_file:
- logger.info('Saving LP file is disabled during MGA runs.')
+ logger.warning('Saving LP file is disabled during MGA runs.')
config.save_lp_file = False
if config.save_duals:
- logger.info('Saving duals is disabled during MGA runs.')
+ logger.warning('Saving duals is disabled during MGA runs.')
config.save_duals = False
if config.save_excel:
- logger.info('Saving excel is disabled during MGA runs.')
+ logger.warning('Saving excel is disabled during MGA runs.')
config.save_excel = False
self.config = config
@@ -210,8 +210,11 @@ def start(self):
)
# 5. Set up the Workers
+ num_workers = self.num_workers
work_queue = Queue(1) # restrict the queue to hold just 1 models in it max
- result_queue = Queue(2)
+ result_queue = Queue(
+ num_workers + 1
+ ) # must be able to hold a shutdown signal from all workers at once!
log_queue = Queue(50)
# make workers
workers = []
@@ -219,7 +222,6 @@ def start(self):
'solver_name': self.config.solver_name,
'solver_options': self.worker_solver_options,
}
- num_workers = self.num_workers
# construct path for the solver logs
s_path = Path(get_OUTPUT_PATH(), 'solver_logs')
if not s_path.exists():
@@ -285,6 +287,8 @@ def start(self):
if self.verbose:
print('shutting it down')
for _ in workers:
+ if self.verbose:
+ print('shutdown sent')
work_queue.put('ZEBRA') # shutdown signal
# 7b. Keep pulling results from the queue to empty it out
diff --git a/temoa/extensions/monte_carlo/MC Flow.pdf b/temoa/extensions/monte_carlo/MC Flow.pdf
new file mode 100644
index 00000000..21df6e75
Binary files /dev/null and b/temoa/extensions/monte_carlo/MC Flow.pdf differ
diff --git a/temoa/extensions/monte_carlo/MC_solver_options.toml b/temoa/extensions/monte_carlo/MC_solver_options.toml
new file mode 100644
index 00000000..869a5338
--- /dev/null
+++ b/temoa/extensions/monte_carlo/MC_solver_options.toml
@@ -0,0 +1,26 @@
+# A container for solver options
+# the top level solver name in brackets should align with the solver name in the config.toml
+
+num_workers = 6
+
+[gurobi]
+
+Method= 2 # Barrier ONLY
+Threads= 20 # per solver instance
+BarConvTol = 0.01 # Relative Barrier Tolerance primal-dual
+FeasibilityTol= 1e-2 # pretty loose
+Crossover= 0 # Disabled
+TimeLimit= 18000 # 5 hrs
+
+# regarding BarConvTol: https://www.gurobi.com/documentation/current/refman/barrier_logging.html
+# note that ref above seems to imply that FeasibilyTol is NOT used when using barrier only...?
+
+# for records ...
+# 'LogFile': './my_gurobi_log.log',
+# 'LPWarmStart': 2, # pass basis
+
+[cbc]
+# tbd
+
+[appsi_highs]
+# tbd
diff --git a/temoa/extensions/monte_carlo/Monte-Carlo.py b/temoa/extensions/monte_carlo/Monte-Carlo.py
deleted file mode 100644
index 362aae1c..00000000
--- a/temoa/extensions/monte_carlo/Monte-Carlo.py
+++ /dev/null
@@ -1,677 +0,0 @@
-# from __future__ import division
-import sys
-import time
-
-start_time = time.time()
-from joblib import Parallel, delayed
-import multiprocessing
-from shutil import copyfile
-import sqlite3
-import os
-from numpy import array
-
-import csv
-from pyDOE import *
-
-from SALib.util import read_param_file
-import numpy as np
-
-
-def evaluate(param_values, k):
- param_names = { # the first element is the name of the table, followed by fisrt, second ... filters. The name of the column which is to change comes at the end
- 0: ['CostInvest', 'E_SOLPVCEN_N', 2020, 'cost_invest'],
- 1: ['CostInvest', 'E_SOLPVCEN_N', 2025, 'cost_invest'],
- 2: ['CostInvest', 'E_SOLPVCEN_N', 2030, 'cost_invest'],
- 3: ['CostInvest', 'E_SOLPVCEN_N', 2035, 'cost_invest'],
- 4: ['CostInvest', 'E_SOLPVCEN_N', 2040, 'cost_invest'],
- 5: ['CostInvest', 'E_SOLPVENDUSE_N', 2020, 'cost_invest'],
- 6: ['CostInvest', 'E_SOLPVENDUSE_N', 2025, 'cost_invest'],
- 7: ['CostInvest', 'E_SOLPVENDUSE_N', 2030, 'cost_invest'],
- 8: ['CostInvest', 'E_SOLPVENDUSE_N', 2035, 'cost_invest'],
- 9: ['CostInvest', 'E_SOLPVENDUSE_N', 2040, 'cost_invest'],
- 10: ['CostVariable', 2015, 'IMPELCNGA', 2015, 'cost_variable'],
- 11: ['CostVariable', 2020, 'IMPELCNGA', 2015, 'cost_variable'],
- 12: ['CostVariable', 2025, 'IMPELCNGA', 2015, 'cost_variable'],
- 13: ['CostVariable', 2030, 'IMPELCNGA', 2015, 'cost_variable'],
- 14: ['CostVariable', 2035, 'IMPELCNGA', 2015, 'cost_variable'],
- 15: ['CostVariable', 2040, 'IMPELCNGA', 2015, 'cost_variable'],
- 16: ['CostVariable', 2015, 'IMPTRNCNG', 2015, 'cost_variable'],
- 17: ['CostVariable', 2020, 'IMPTRNCNG', 2015, 'cost_variable'],
- 18: ['CostVariable', 2025, 'IMPTRNCNG', 2015, 'cost_variable'],
- 19: ['CostVariable', 2030, 'IMPTRNCNG', 2015, 'cost_variable'],
- 20: ['CostVariable', 2035, 'IMPTRNCNG', 2015, 'cost_variable'],
- 21: ['CostVariable', 2040, 'IMPTRNCNG', 2015, 'cost_variable'],
- 22: ['CostVariable', 2015, 'IMPRESNGA', 2015, 'cost_variable'],
- 23: ['CostVariable', 2020, 'IMPRESNGA', 2015, 'cost_variable'],
- 24: ['CostVariable', 2025, 'IMPRESNGA', 2015, 'cost_variable'],
- 25: ['CostVariable', 2030, 'IMPRESNGA', 2015, 'cost_variable'],
- 26: ['CostVariable', 2035, 'IMPRESNGA', 2015, 'cost_variable'],
- 27: ['CostVariable', 2040, 'IMPRESNGA', 2015, 'cost_variable'],
- 28: ['CostVariable', 2015, 'IMPCOMNGA', 2015, 'cost_variable'],
- 29: ['CostVariable', 2020, 'IMPCOMNGA', 2015, 'cost_variable'],
- 30: ['CostVariable', 2025, 'IMPCOMNGA', 2015, 'cost_variable'],
- 31: ['CostVariable', 2030, 'IMPCOMNGA', 2015, 'cost_variable'],
- 32: ['CostVariable', 2035, 'IMPCOMNGA', 2015, 'cost_variable'],
- 33: ['CostVariable', 2040, 'IMPCOMNGA', 2015, 'cost_variable'],
- 34: ['CostInvest', 'E_WNDCL4_N', 2020, 'cost_invest'],
- 35: ['CostInvest', 'E_WNDCL4_N', 2025, 'cost_invest'],
- 36: ['CostInvest', 'E_WNDCL4_N', 2030, 'cost_invest'],
- 37: ['CostInvest', 'E_WNDCL4_N', 2035, 'cost_invest'],
- 38: ['CostInvest', 'E_WNDCL4_N', 2040, 'cost_invest'],
- 39: ['CostInvest', 'E_WNDCL5_N', 2020, 'cost_invest'],
- 40: ['CostInvest', 'E_WNDCL5_N', 2025, 'cost_invest'],
- 41: ['CostInvest', 'E_WNDCL5_N', 2030, 'cost_invest'],
- 42: ['CostInvest', 'E_WNDCL5_N', 2035, 'cost_invest'],
- 43: ['CostInvest', 'E_WNDCL5_N', 2040, 'cost_invest'],
- 44: ['CostInvest', 'E_WNDCL6_N', 2020, 'cost_invest'],
- 45: ['CostInvest', 'E_WNDCL6_N', 2025, 'cost_invest'],
- 46: ['CostInvest', 'E_WNDCL6_N', 2030, 'cost_invest'],
- 47: ['CostInvest', 'E_WNDCL6_N', 2035, 'cost_invest'],
- 48: ['CostInvest', 'E_WNDCL6_N', 2040, 'cost_invest'],
- 49: ['CostVariable', 2015, 'IMPELCCOAB', 2015, 'cost_variable'],
- 50: ['CostVariable', 2020, 'IMPELCCOAB', 2015, 'cost_variable'],
- 51: ['CostVariable', 2025, 'IMPELCCOAB', 2015, 'cost_variable'],
- 52: ['CostVariable', 2030, 'IMPELCCOAB', 2015, 'cost_variable'],
- 53: ['CostVariable', 2035, 'IMPELCCOAB', 2015, 'cost_variable'],
- 54: ['CostVariable', 2040, 'IMPELCCOAB', 2015, 'cost_variable'],
- 55: ['CostVariable', 2015, 'IMPELCCOAS', 2015, 'cost_variable'],
- 56: ['CostVariable', 2020, 'IMPELCCOAS', 2015, 'cost_variable'],
- 57: ['CostVariable', 2025, 'IMPELCCOAS', 2015, 'cost_variable'],
- 58: ['CostVariable', 2030, 'IMPELCCOAS', 2015, 'cost_variable'],
- 59: ['CostVariable', 2035, 'IMPELCCOAS', 2015, 'cost_variable'],
- 60: ['CostVariable', 2040, 'IMPELCCOAS', 2015, 'cost_variable'],
- 61: ['CostVariable', 2015, 'IMPELCCOAL', 2015, 'cost_variable'],
- 62: ['CostVariable', 2020, 'IMPELCCOAL', 2015, 'cost_variable'],
- 63: ['CostVariable', 2025, 'IMPELCCOAL', 2015, 'cost_variable'],
- 64: ['CostVariable', 2030, 'IMPELCCOAL', 2015, 'cost_variable'],
- 65: ['CostVariable', 2035, 'IMPELCCOAL', 2015, 'cost_variable'],
- 66: ['CostVariable', 2040, 'IMPELCCOAL', 2015, 'cost_variable'],
- 67: ['CostInvest', 'E_NGACC_N', 2020, 'cost_invest'],
- 68: ['CostInvest', 'E_NGACC_N', 2025, 'cost_invest'],
- 69: ['CostInvest', 'E_NGACC_N', 2030, 'cost_invest'],
- 70: ['CostInvest', 'E_NGACC_N', 2035, 'cost_invest'],
- 71: ['CostInvest', 'E_NGACC_N', 2040, 'cost_invest'],
- 72: ['CostInvest', 'E_NGAACC_N', 2020, 'cost_invest'],
- 73: ['CostInvest', 'E_NGAACC_N', 2025, 'cost_invest'],
- 74: ['CostInvest', 'E_NGAACC_N', 2030, 'cost_invest'],
- 75: ['CostInvest', 'E_NGAACC_N', 2035, 'cost_invest'],
- 76: ['CostInvest', 'E_NGAACC_N', 2040, 'cost_invest'],
- 77: ['CostInvest', 'T_LDV_CE85X_N', 2020, 'cost_invest'],
- 78: ['CostInvest', 'T_LDV_CE85X_N', 2025, 'cost_invest'],
- 79: ['CostInvest', 'T_LDV_CE85X_N', 2030, 'cost_invest'],
- 80: ['CostInvest', 'T_LDV_CE85X_N', 2035, 'cost_invest'],
- 81: ['CostInvest', 'T_LDV_CE85X_N', 2040, 'cost_invest'],
- 82: ['CostInvest', 'T_LDV_FE85X_N', 2020, 'cost_invest'],
- 83: ['CostInvest', 'T_LDV_FE85X_N', 2025, 'cost_invest'],
- 84: ['CostInvest', 'T_LDV_FE85X_N', 2030, 'cost_invest'],
- 85: ['CostInvest', 'T_LDV_FE85X_N', 2035, 'cost_invest'],
- 86: ['CostInvest', 'T_LDV_FE85X_N', 2040, 'cost_invest'],
- 87: ['CostInvest', 'T_LDV_SSE85X_N', 2020, 'cost_invest'],
- 88: ['CostInvest', 'T_LDV_SSE85X_N', 2025, 'cost_invest'],
- 89: ['CostInvest', 'T_LDV_SSE85X_N', 2030, 'cost_invest'],
- 90: ['CostInvest', 'T_LDV_SSE85X_N', 2035, 'cost_invest'],
- 91: ['CostInvest', 'T_LDV_SSE85X_N', 2040, 'cost_invest'],
- 92: ['CostInvest', 'T_LDV_LSE85X_N', 2020, 'cost_invest'],
- 93: ['CostInvest', 'T_LDV_LSE85X_N', 2025, 'cost_invest'],
- 94: ['CostInvest', 'T_LDV_LSE85X_N', 2030, 'cost_invest'],
- 95: ['CostInvest', 'T_LDV_LSE85X_N', 2035, 'cost_invest'],
- 96: ['CostInvest', 'T_LDV_LSE85X_N', 2040, 'cost_invest'],
- 97: ['CostInvest', 'T_LDV_MVE85X_N', 2020, 'cost_invest'],
- 98: ['CostInvest', 'T_LDV_MVE85X_N', 2025, 'cost_invest'],
- 99: ['CostInvest', 'T_LDV_MVE85X_N', 2030, 'cost_invest'],
- 100: ['CostInvest', 'T_LDV_MVE85X_N', 2035, 'cost_invest'],
- 101: ['CostInvest', 'T_LDV_MVE85X_N', 2040, 'cost_invest'],
- 102: ['CostInvest', 'T_LDV_PE85X_N', 2020, 'cost_invest'],
- 103: ['CostInvest', 'T_LDV_PE85X_N', 2025, 'cost_invest'],
- 104: ['CostInvest', 'T_LDV_PE85X_N', 2030, 'cost_invest'],
- 105: ['CostInvest', 'T_LDV_PE85X_N', 2035, 'cost_invest'],
- 106: ['CostInvest', 'T_LDV_PE85X_N', 2040, 'cost_invest'],
- 107: ['CostInvest', 'T_LDV_MCE10_N', 2015, 'cost_invest'],
- 108: ['CostInvest', 'T_LDV_MCE10_N', 2020, 'cost_invest'],
- 109: ['CostInvest', 'T_LDV_MCE10_N', 2025, 'cost_invest'],
- 110: ['CostInvest', 'T_LDV_MCE10_N', 2030, 'cost_invest'],
- 111: ['CostInvest', 'T_LDV_MCE10_N', 2035, 'cost_invest'],
- 112: ['CostInvest', 'T_LDV_MCE10_N', 2040, 'cost_invest'],
- 113: ['CostInvest', 'T_LDV_CE10_N', 2015, 'cost_invest'],
- 114: ['CostInvest', 'T_LDV_CE10_N', 2020, 'cost_invest'],
- 115: ['CostInvest', 'T_LDV_CE10_N', 2025, 'cost_invest'],
- 116: ['CostInvest', 'T_LDV_CE10_N', 2030, 'cost_invest'],
- 117: ['CostInvest', 'T_LDV_CE10_N', 2035, 'cost_invest'],
- 118: ['CostInvest', 'T_LDV_CE10_N', 2040, 'cost_invest'],
- 119: ['CostInvest', 'T_LDV_FE10_N', 2015, 'cost_invest'],
- 120: ['CostInvest', 'T_LDV_FE10_N', 2020, 'cost_invest'],
- 121: ['CostInvest', 'T_LDV_FE10_N', 2025, 'cost_invest'],
- 122: ['CostInvest', 'T_LDV_FE10_N', 2030, 'cost_invest'],
- 123: ['CostInvest', 'T_LDV_FE10_N', 2035, 'cost_invest'],
- 124: ['CostInvest', 'T_LDV_FE10_N', 2040, 'cost_invest'],
- 125: ['CostInvest', 'T_LDV_SSE10_N', 2015, 'cost_invest'],
- 126: ['CostInvest', 'T_LDV_SSE10_N', 2020, 'cost_invest'],
- 127: ['CostInvest', 'T_LDV_SSE10_N', 2025, 'cost_invest'],
- 128: ['CostInvest', 'T_LDV_SSE10_N', 2030, 'cost_invest'],
- 129: ['CostInvest', 'T_LDV_SSE10_N', 2035, 'cost_invest'],
- 130: ['CostInvest', 'T_LDV_SSE10_N', 2040, 'cost_invest'],
- 131: ['CostInvest', 'T_LDV_LSE10_N', 2015, 'cost_invest'],
- 132: ['CostInvest', 'T_LDV_LSE10_N', 2020, 'cost_invest'],
- 133: ['CostInvest', 'T_LDV_LSE10_N', 2025, 'cost_invest'],
- 134: ['CostInvest', 'T_LDV_LSE10_N', 2030, 'cost_invest'],
- 135: ['CostInvest', 'T_LDV_LSE10_N', 2035, 'cost_invest'],
- 136: ['CostInvest', 'T_LDV_LSE10_N', 2040, 'cost_invest'],
- 137: ['CostInvest', 'T_LDV_MVE10_N', 2020, 'cost_invest'],
- 138: ['CostInvest', 'T_LDV_MVE10_N', 2025, 'cost_invest'],
- 139: ['CostInvest', 'T_LDV_MVE10_N', 2030, 'cost_invest'],
- 140: ['CostInvest', 'T_LDV_MVE10_N', 2035, 'cost_invest'],
- 141: ['CostInvest', 'T_LDV_MVE10_N', 2040, 'cost_invest'],
- 142: ['CostInvest', 'T_LDV_PE10_N', 2020, 'cost_invest'],
- 143: ['CostInvest', 'T_LDV_PE10_N', 2025, 'cost_invest'],
- 144: ['CostInvest', 'T_LDV_PE10_N', 2030, 'cost_invest'],
- 145: ['CostInvest', 'T_LDV_PE10_N', 2035, 'cost_invest'],
- 146: ['CostInvest', 'T_LDV_PE10_N', 2040, 'cost_invest'],
- 147: ['CostInvest', 'T_HDV_BE10_N', 2020, 'cost_invest'],
- 148: ['CostInvest', 'T_HDV_BE10_N', 2025, 'cost_invest'],
- 149: ['CostInvest', 'T_HDV_BE10_N', 2030, 'cost_invest'],
- 150: ['CostInvest', 'T_HDV_BE10_N', 2035, 'cost_invest'],
- 151: ['CostInvest', 'T_HDV_BE10_N', 2040, 'cost_invest'],
- 152: ['CostInvest', 'T_HDV_BE10_10_N', 2020, 'cost_invest'],
- 153: ['CostInvest', 'T_HDV_BE10_10_N', 2025, 'cost_invest'],
- 154: ['CostInvest', 'T_HDV_BE10_10_N', 2030, 'cost_invest'],
- 155: ['CostInvest', 'T_HDV_BE10_10_N', 2035, 'cost_invest'],
- 156: ['CostInvest', 'T_HDV_BE10_10_N', 2040, 'cost_invest'],
- 157: ['CostInvest', 'T_HDV_TCE10_N', 2020, 'cost_invest'],
- 158: ['CostInvest', 'T_HDV_TCE10_N', 2025, 'cost_invest'],
- 159: ['CostInvest', 'T_HDV_TCE10_N', 2030, 'cost_invest'],
- 160: ['CostInvest', 'T_HDV_TCE10_N', 2035, 'cost_invest'],
- 161: ['CostInvest', 'T_HDV_TCE10_N', 2040, 'cost_invest'],
- 162: ['CostInvest', 'T_HDV_TCE1020_N', 2020, 'cost_invest'],
- 163: ['CostInvest', 'T_HDV_TCE1020_N', 2025, 'cost_invest'],
- 164: ['CostInvest', 'T_HDV_TCE1020_N', 2030, 'cost_invest'],
- 165: ['CostInvest', 'T_HDV_TCE1020_N', 2035, 'cost_invest'],
- 166: ['CostInvest', 'T_HDV_TCE1020_N', 2040, 'cost_invest'],
- 167: ['CostInvest', 'T_HDV_TCE1030_N', 2030, 'cost_invest'],
- 168: ['CostInvest', 'T_HDV_TCE1030_N', 2035, 'cost_invest'],
- 169: ['CostInvest', 'T_HDV_TCE1030_N', 2040, 'cost_invest'],
- 170: ['CostInvest', 'T_HDV_TCE10_20_N', 2020, 'cost_invest'],
- 171: ['CostInvest', 'T_HDV_TCE10_20_N', 2025, 'cost_invest'],
- 172: ['CostInvest', 'T_HDV_TCE10_20_N', 2030, 'cost_invest'],
- 173: ['CostInvest', 'T_HDV_TCE10_20_N', 2035, 'cost_invest'],
- 174: ['CostInvest', 'T_HDV_TCE10_20_N', 2040, 'cost_invest'],
- 175: ['CostInvest', 'T_HDV_TCE1030_20_N', 2030, 'cost_invest'],
- 176: ['CostInvest', 'T_HDV_TCE1030_20_N', 2035, 'cost_invest'],
- 177: ['CostInvest', 'T_HDV_TCE1030_20_N', 2040, 'cost_invest'],
- 178: ['CostInvest', 'T_HDV_TCE85X_N', 2015, 'cost_invest'],
- 179: ['CostInvest', 'T_HDV_TCE85X_N', 2020, 'cost_invest'],
- 180: ['CostInvest', 'T_HDV_TCE85X_N', 2025, 'cost_invest'],
- 181: ['CostInvest', 'T_HDV_TCE85X_N', 2030, 'cost_invest'],
- 182: ['CostInvest', 'T_HDV_TCE85X_N', 2035, 'cost_invest'],
- 183: ['CostInvest', 'T_HDV_TCE85X_N', 2040, 'cost_invest'],
- 184: ['CostInvest', 'T_HDV_TCLPG_N', 2015, 'cost_invest'],
- 185: ['CostInvest', 'T_HDV_TCLPG_N', 2020, 'cost_invest'],
- 186: ['CostInvest', 'T_HDV_TCLPG_N', 2025, 'cost_invest'],
- 187: ['CostInvest', 'T_HDV_TCLPG_N', 2030, 'cost_invest'],
- 188: ['CostInvest', 'T_HDV_TCLPG_N', 2035, 'cost_invest'],
- 189: ['CostInvest', 'T_HDV_TCLPG_N', 2040, 'cost_invest'],
- 190: ['CostInvest', 'T_HDV_THE10_N', 2020, 'cost_invest'],
- 191: ['CostInvest', 'T_HDV_THE10_N', 2025, 'cost_invest'],
- 192: ['CostInvest', 'T_HDV_THE10_N', 2030, 'cost_invest'],
- 193: ['CostInvest', 'T_HDV_THE10_N', 2035, 'cost_invest'],
- 194: ['CostInvest', 'T_HDV_THE10_N', 2040, 'cost_invest'],
- 195: ['CostInvest', 'T_HDV_THE1020_N', 2020, 'cost_invest'],
- 196: ['CostInvest', 'T_HDV_THE1020_N', 2025, 'cost_invest'],
- 197: ['CostInvest', 'T_HDV_THE1020_N', 2030, 'cost_invest'],
- 198: ['CostInvest', 'T_HDV_THE1020_N', 2035, 'cost_invest'],
- 199: ['CostInvest', 'T_HDV_THE1020_N', 2040, 'cost_invest'],
- 200: ['CostInvest', 'T_HDV_THLPG_N', 2020, 'cost_invest'],
- 201: ['CostInvest', 'T_HDV_THLPG_N', 2025, 'cost_invest'],
- 202: ['CostInvest', 'T_HDV_THLPG_N', 2030, 'cost_invest'],
- 203: ['CostInvest', 'T_HDV_THLPG_N', 2035, 'cost_invest'],
- 204: ['CostInvest', 'T_HDV_THLPG_N', 2040, 'cost_invest'],
- 205: ['CostVariable', 2015, 'IMPELCDSL', 2015, 'cost_variable'],
- 206: ['CostVariable', 2020, 'IMPELCDSL', 2015, 'cost_variable'],
- 207: ['CostVariable', 2025, 'IMPELCDSL', 2015, 'cost_variable'],
- 208: ['CostVariable', 2030, 'IMPELCDSL', 2015, 'cost_variable'],
- 209: ['CostVariable', 2035, 'IMPELCDSL', 2015, 'cost_variable'],
- 210: ['CostVariable', 2040, 'IMPELCDSL', 2015, 'cost_variable'],
- 211: ['CostVariable', 2015, 'IMPELCRFL', 2015, 'cost_variable'],
- 212: ['CostVariable', 2020, 'IMPELCRFL', 2015, 'cost_variable'],
- 213: ['CostVariable', 2025, 'IMPELCRFL', 2015, 'cost_variable'],
- 214: ['CostVariable', 2030, 'IMPELCRFL', 2015, 'cost_variable'],
- 215: ['CostVariable', 2035, 'IMPELCRFL', 2015, 'cost_variable'],
- 216: ['CostVariable', 2040, 'IMPELCRFL', 2015, 'cost_variable'],
- 217: ['CostVariable', 2015, 'IMPTRNDSL', 2015, 'cost_variable'],
- 218: ['CostVariable', 2020, 'IMPTRNDSL', 2015, 'cost_variable'],
- 219: ['CostVariable', 2025, 'IMPTRNDSL', 2015, 'cost_variable'],
- 220: ['CostVariable', 2030, 'IMPTRNDSL', 2015, 'cost_variable'],
- 221: ['CostVariable', 2035, 'IMPTRNDSL', 2015, 'cost_variable'],
- 222: ['CostVariable', 2040, 'IMPTRNDSL', 2015, 'cost_variable'],
- 223: ['CostVariable', 2015, 'IMPTRNE85', 2015, 'cost_variable'],
- 224: ['CostVariable', 2020, 'IMPTRNE85', 2015, 'cost_variable'],
- 225: ['CostVariable', 2025, 'IMPTRNE85', 2015, 'cost_variable'],
- 226: ['CostVariable', 2030, 'IMPTRNE85', 2015, 'cost_variable'],
- 227: ['CostVariable', 2035, 'IMPTRNE85', 2015, 'cost_variable'],
- 228: ['CostVariable', 2040, 'IMPTRNE85', 2015, 'cost_variable'],
- 229: ['CostVariable', 2015, 'IMPTRNE10', 2015, 'cost_variable'],
- 230: ['CostVariable', 2020, 'IMPTRNE10', 2015, 'cost_variable'],
- 231: ['CostVariable', 2025, 'IMPTRNE10', 2015, 'cost_variable'],
- 232: ['CostVariable', 2030, 'IMPTRNE10', 2015, 'cost_variable'],
- 233: ['CostVariable', 2035, 'IMPTRNE10', 2015, 'cost_variable'],
- 234: ['CostVariable', 2040, 'IMPTRNE10', 2015, 'cost_variable'],
- 235: ['CostVariable', 2015, 'IMPTRNLPG', 2015, 'cost_variable'],
- 236: ['CostVariable', 2020, 'IMPTRNLPG', 2015, 'cost_variable'],
- 237: ['CostVariable', 2025, 'IMPTRNLPG', 2015, 'cost_variable'],
- 238: ['CostVariable', 2030, 'IMPTRNLPG', 2015, 'cost_variable'],
- 239: ['CostVariable', 2035, 'IMPTRNLPG', 2015, 'cost_variable'],
- 240: ['CostVariable', 2040, 'IMPTRNLPG', 2015, 'cost_variable'],
- 241: ['CostVariable', 2015, 'IMPTRNBIODSL', 2015, 'cost_variable'],
- 242: ['CostVariable', 2020, 'IMPTRNBIODSL', 2015, 'cost_variable'],
- 243: ['CostVariable', 2025, 'IMPTRNBIODSL', 2015, 'cost_variable'],
- 244: ['CostVariable', 2030, 'IMPTRNBIODSL', 2015, 'cost_variable'],
- 245: ['CostVariable', 2035, 'IMPTRNBIODSL', 2015, 'cost_variable'],
- 246: ['CostVariable', 2040, 'IMPTRNBIODSL', 2015, 'cost_variable'],
- 247: ['CostVariable', 2015, 'IMPTRNJTF', 2015, 'cost_variable'],
- 248: ['CostVariable', 2020, 'IMPTRNJTF', 2015, 'cost_variable'],
- 249: ['CostVariable', 2025, 'IMPTRNJTF', 2015, 'cost_variable'],
- 250: ['CostVariable', 2030, 'IMPTRNJTF', 2015, 'cost_variable'],
- 251: ['CostVariable', 2035, 'IMPTRNJTF', 2015, 'cost_variable'],
- 252: ['CostVariable', 2040, 'IMPTRNJTF', 2015, 'cost_variable'],
- 253: ['CostVariable', 2015, 'IMPTRNRFO', 2015, 'cost_variable'],
- 254: ['CostVariable', 2020, 'IMPTRNRFO', 2015, 'cost_variable'],
- 255: ['CostVariable', 2025, 'IMPTRNRFO', 2015, 'cost_variable'],
- 256: ['CostVariable', 2030, 'IMPTRNRFO', 2015, 'cost_variable'],
- 257: ['CostVariable', 2035, 'IMPTRNRFO', 2015, 'cost_variable'],
- 258: ['CostVariable', 2040, 'IMPTRNRFO', 2015, 'cost_variable'],
- 259: ['CostVariable', 2015, 'IMPRESLPG', 2015, 'cost_variable'],
- 260: ['CostVariable', 2020, 'IMPRESLPG', 2015, 'cost_variable'],
- 261: ['CostVariable', 2025, 'IMPRESLPG', 2015, 'cost_variable'],
- 262: ['CostVariable', 2030, 'IMPRESLPG', 2015, 'cost_variable'],
- 263: ['CostVariable', 2035, 'IMPRESLPG', 2015, 'cost_variable'],
- 264: ['CostVariable', 2040, 'IMPRESLPG', 2015, 'cost_variable'],
- 265: ['CostVariable', 2015, 'IMPCOMLPG', 2015, 'cost_variable'],
- 266: ['CostVariable', 2020, 'IMPCOMLPG', 2015, 'cost_variable'],
- 267: ['CostVariable', 2025, 'IMPCOMLPG', 2015, 'cost_variable'],
- 268: ['CostVariable', 2030, 'IMPCOMLPG', 2015, 'cost_variable'],
- 269: ['CostVariable', 2035, 'IMPCOMLPG', 2015, 'cost_variable'],
- 270: ['CostVariable', 2040, 'IMPCOMLPG', 2015, 'cost_variable'],
- 271: ['CostVariable', 2015, 'IMPCOMDISTOIL', 2015, 'cost_variable'],
- 272: ['CostVariable', 2020, 'IMPCOMDISTOIL', 2015, 'cost_variable'],
- 273: ['CostVariable', 2025, 'IMPCOMDISTOIL', 2015, 'cost_variable'],
- 274: ['CostVariable', 2030, 'IMPCOMDISTOIL', 2015, 'cost_variable'],
- 275: ['CostVariable', 2035, 'IMPCOMDISTOIL', 2015, 'cost_variable'],
- 276: ['CostVariable', 2040, 'IMPCOMDISTOIL', 2015, 'cost_variable'],
- 277: ['CostVariable', 2015, 'IMPCOMRFO', 2015, 'cost_variable'],
- 278: ['CostVariable', 2020, 'IMPCOMRFO', 2015, 'cost_variable'],
- 279: ['CostVariable', 2025, 'IMPCOMRFO', 2015, 'cost_variable'],
- 280: ['CostVariable', 2030, 'IMPCOMRFO', 2015, 'cost_variable'],
- 281: ['CostVariable', 2035, 'IMPCOMRFO', 2015, 'cost_variable'],
- 282: ['CostVariable', 2040, 'IMPCOMRFO', 2015, 'cost_variable'],
- 283: ['CostVariable', 2015, 'IMPRESDISTOIL', 2015, 'cost_variable'],
- 284: ['CostVariable', 2020, 'IMPRESDISTOIL', 2015, 'cost_variable'],
- 285: ['CostVariable', 2025, 'IMPRESDISTOIL', 2015, 'cost_variable'],
- 286: ['CostVariable', 2030, 'IMPRESDISTOIL', 2015, 'cost_variable'],
- 287: ['CostVariable', 2035, 'IMPRESDISTOIL', 2015, 'cost_variable'],
- 288: ['CostVariable', 2040, 'IMPRESDISTOIL', 2015, 'cost_variable'],
- 289: ['CostVariable', 2015, 'IMPRESKER', 2015, 'cost_variable'],
- 290: ['CostVariable', 2020, 'IMPRESKER', 2015, 'cost_variable'],
- 291: ['CostVariable', 2025, 'IMPRESKER', 2015, 'cost_variable'],
- 292: ['CostVariable', 2030, 'IMPRESKER', 2015, 'cost_variable'],
- 293: ['CostVariable', 2035, 'IMPRESKER', 2015, 'cost_variable'],
- 294: ['CostVariable', 2040, 'IMPRESKER', 2015, 'cost_variable'],
- 295: ['CostInvest', 'T_LDV_CDSL_N', 2020, 'cost_invest'],
- 296: ['CostInvest', 'T_LDV_CDSL_N', 2025, 'cost_invest'],
- 297: ['CostInvest', 'T_LDV_CDSL_N', 2030, 'cost_invest'],
- 298: ['CostInvest', 'T_LDV_CDSL_N', 2035, 'cost_invest'],
- 299: ['CostInvest', 'T_LDV_CDSL_N', 2040, 'cost_invest'],
- 300: ['CostInvest', 'T_LDV_FDSL_N', 2020, 'cost_invest'],
- 301: ['CostInvest', 'T_LDV_FDSL_N', 2025, 'cost_invest'],
- 302: ['CostInvest', 'T_LDV_FDSL_N', 2030, 'cost_invest'],
- 303: ['CostInvest', 'T_LDV_FDSL_N', 2035, 'cost_invest'],
- 304: ['CostInvest', 'T_LDV_FDSL_N', 2040, 'cost_invest'],
- 305: ['CostInvest', 'T_LDV_SSDSL_N', 2020, 'cost_invest'],
- 306: ['CostInvest', 'T_LDV_SSDSL_N', 2025, 'cost_invest'],
- 307: ['CostInvest', 'T_LDV_SSDSL_N', 2030, 'cost_invest'],
- 308: ['CostInvest', 'T_LDV_SSDSL_N', 2035, 'cost_invest'],
- 309: ['CostInvest', 'T_LDV_SSDSL_N', 2040, 'cost_invest'],
- 310: ['CostInvest', 'T_LDV_LSDSL_N', 2020, 'cost_invest'],
- 311: ['CostInvest', 'T_LDV_LSDSL_N', 2025, 'cost_invest'],
- 312: ['CostInvest', 'T_LDV_LSDSL_N', 2030, 'cost_invest'],
- 313: ['CostInvest', 'T_LDV_LSDSL_N', 2035, 'cost_invest'],
- 314: ['CostInvest', 'T_LDV_LSDSL_N', 2040, 'cost_invest'],
- 315: ['CostInvest', 'T_LDV_MVDSL_N', 2020, 'cost_invest'],
- 316: ['CostInvest', 'T_LDV_MVDSL_N', 2025, 'cost_invest'],
- 317: ['CostInvest', 'T_LDV_MVDSL_N', 2030, 'cost_invest'],
- 318: ['CostInvest', 'T_LDV_MVDSL_N', 2035, 'cost_invest'],
- 319: ['CostInvest', 'T_LDV_MVDSL_N', 2040, 'cost_invest'],
- 320: ['CostInvest', 'T_LDV_PDSL_N', 2020, 'cost_invest'],
- 321: ['CostInvest', 'T_LDV_PDSL_N', 2025, 'cost_invest'],
- 322: ['CostInvest', 'T_LDV_PDSL_N', 2030, 'cost_invest'],
- 323: ['CostInvest', 'T_LDV_PDSL_N', 2035, 'cost_invest'],
- 324: ['CostInvest', 'T_LDV_PDSL_N', 2040, 'cost_invest'],
- 325: ['CostInvest', 'T_HDV_BDSL_N', 2020, 'cost_invest'],
- 326: ['CostInvest', 'T_HDV_BDSL_N', 2025, 'cost_invest'],
- 327: ['CostInvest', 'T_HDV_BDSL_N', 2030, 'cost_invest'],
- 328: ['CostInvest', 'T_HDV_BDSL_N', 2035, 'cost_invest'],
- 329: ['CostInvest', 'T_HDV_BDSL_N', 2040, 'cost_invest'],
- 330: ['CostInvest', 'T_HDV_BDSL_10_N', 2020, 'cost_invest'],
- 331: ['CostInvest', 'T_HDV_BDSL_10_N', 2025, 'cost_invest'],
- 332: ['CostInvest', 'T_HDV_BDSL_10_N', 2030, 'cost_invest'],
- 333: ['CostInvest', 'T_HDV_BDSL_10_N', 2035, 'cost_invest'],
- 334: ['CostInvest', 'T_HDV_BDSL_10_N', 2040, 'cost_invest'],
- 335: ['CostInvest', 'T_HDV_TCDSL_N', 2020, 'cost_invest'],
- 336: ['CostInvest', 'T_HDV_TCDSL_N', 2025, 'cost_invest'],
- 337: ['CostInvest', 'T_HDV_TCDSL_N', 2030, 'cost_invest'],
- 338: ['CostInvest', 'T_HDV_TCDSL_N', 2035, 'cost_invest'],
- 339: ['CostInvest', 'T_HDV_TCDSL_N', 2040, 'cost_invest'],
- 340: ['CostInvest', 'T_HDV_TCDSL20_N', 2020, 'cost_invest'],
- 341: ['CostInvest', 'T_HDV_TCDSL20_N', 2025, 'cost_invest'],
- 342: ['CostInvest', 'T_HDV_TCDSL20_N', 2030, 'cost_invest'],
- 343: ['CostInvest', 'T_HDV_TCDSL20_N', 2035, 'cost_invest'],
- 344: ['CostInvest', 'T_HDV_TCDSL20_N', 2040, 'cost_invest'],
- 345: ['CostInvest', 'T_HDV_TCBIODSL_N', 2015, 'cost_invest'],
- 346: ['CostInvest', 'T_HDV_TCBIODSL_N', 2020, 'cost_invest'],
- 347: ['CostInvest', 'T_HDV_TCBIODSL_N', 2025, 'cost_invest'],
- 348: ['CostInvest', 'T_HDV_TCBIODSL_N', 2030, 'cost_invest'],
- 349: ['CostInvest', 'T_HDV_TCBIODSL_N', 2035, 'cost_invest'],
- 350: ['CostInvest', 'T_HDV_TCBIODSL_N', 2040, 'cost_invest'],
- 351: ['CostInvest', 'T_HDV_THDSL_N', 2020, 'cost_invest'],
- 352: ['CostInvest', 'T_HDV_THDSL_N', 2025, 'cost_invest'],
- 353: ['CostInvest', 'T_HDV_THDSL_N', 2030, 'cost_invest'],
- 354: ['CostInvest', 'T_HDV_THDSL_N', 2035, 'cost_invest'],
- 355: ['CostInvest', 'T_HDV_THDSL_N', 2040, 'cost_invest'],
- 356: ['CostInvest', 'T_HDV_THDSL_10_N', 2020, 'cost_invest'],
- 357: ['CostInvest', 'T_HDV_THDSL_10_N', 2025, 'cost_invest'],
- 358: ['CostInvest', 'T_HDV_THDSL_10_N', 2030, 'cost_invest'],
- 359: ['CostInvest', 'T_HDV_THDSL_10_N', 2035, 'cost_invest'],
- 360: ['CostInvest', 'T_HDV_THDSL_10_N', 2040, 'cost_invest'],
- 361: ['CostInvest', 'T_HDV_THDSL20_10_N', 2020, 'cost_invest'],
- 362: ['CostInvest', 'T_HDV_THDSL20_10_N', 2025, 'cost_invest'],
- 363: ['CostInvest', 'T_HDV_THDSL20_10_N', 2030, 'cost_invest'],
- 364: ['CostInvest', 'T_HDV_THDSL20_10_N', 2035, 'cost_invest'],
- 365: ['CostInvest', 'T_HDV_THDSL20_10_N', 2040, 'cost_invest'],
- 366: ['CostInvest', 'T_HDV_THDSL20_N', 2020, 'cost_invest'],
- 367: ['CostInvest', 'T_HDV_THDSL20_N', 2025, 'cost_invest'],
- 368: ['CostInvest', 'T_HDV_THDSL20_N', 2030, 'cost_invest'],
- 369: ['CostInvest', 'T_HDV_THDSL20_N', 2035, 'cost_invest'],
- 370: ['CostInvest', 'T_HDV_THDSL20_N', 2040, 'cost_invest'],
- 371: ['CostInvest', 'T_HDV_THDSL_20_N', 2020, 'cost_invest'],
- 372: ['CostInvest', 'T_HDV_THDSL_20_N', 2025, 'cost_invest'],
- 373: ['CostInvest', 'T_HDV_THDSL_20_N', 2030, 'cost_invest'],
- 374: ['CostInvest', 'T_HDV_THDSL_20_N', 2035, 'cost_invest'],
- 375: ['CostInvest', 'T_HDV_THDSL_20_N', 2040, 'cost_invest'],
- 376: ['CostInvest', 'T_HDV_THDSL20_20_N', 2020, 'cost_invest'],
- 377: ['CostInvest', 'T_HDV_THDSL20_20_N', 2025, 'cost_invest'],
- 378: ['CostInvest', 'T_HDV_THDSL20_20_N', 2030, 'cost_invest'],
- 379: ['CostInvest', 'T_HDV_THDSL20_20_N', 2035, 'cost_invest'],
- 380: ['CostInvest', 'T_HDV_THDSL20_20_N', 2040, 'cost_invest'],
- 381: ['CostInvest', 'T_HDV_THDSL20_40_N', 2020, 'cost_invest'],
- 382: ['CostInvest', 'T_HDV_THDSL20_40_N', 2025, 'cost_invest'],
- 383: ['CostInvest', 'T_HDV_THDSL20_40_N', 2030, 'cost_invest'],
- 384: ['CostInvest', 'T_HDV_THDSL20_40_N', 2035, 'cost_invest'],
- 385: ['CostInvest', 'T_HDV_THDSL20_40_N', 2040, 'cost_invest'],
- 386: ['CostInvest', 'R_SH_HPELC_VER1_N', 2020, 'cost_invest'],
- 387: ['CostInvest', 'R_SH_HPELC_VER1_N', 2025, 'cost_invest'],
- 388: ['CostInvest', 'R_SH_HPELC_VER1_N', 2030, 'cost_invest'],
- 389: ['CostInvest', 'R_SH_HPELC_VER1_N', 2035, 'cost_invest'],
- 390: ['CostInvest', 'R_SH_HPELC_VER1_N', 2040, 'cost_invest'],
- 391: ['CostInvest', 'R_SH_HPELC_VER2_N', 2020, 'cost_invest'],
- 392: ['CostInvest', 'R_SH_HPELC_VER2_N', 2025, 'cost_invest'],
- 393: ['CostInvest', 'R_SH_HPELC_VER2_N', 2030, 'cost_invest'],
- 394: ['CostInvest', 'R_SH_HPELC_VER2_N', 2035, 'cost_invest'],
- 395: ['CostInvest', 'R_SH_HPELC_VER2_N', 2040, 'cost_invest'],
- 396: ['CostInvest', 'R_SH_HPELC_VER3_N', 2020, 'cost_invest'],
- 397: ['CostInvest', 'R_SH_HPELC_VER3_N', 2025, 'cost_invest'],
- 398: ['CostInvest', 'R_SH_HPELC_VER3_N', 2030, 'cost_invest'],
- 399: ['CostInvest', 'R_SH_HPELC_VER3_N', 2035, 'cost_invest'],
- 400: ['CostInvest', 'R_SH_HPELC_VER3_N', 2040, 'cost_invest'],
- 401: ['CostInvest', 'R_SH_HPELC_VER4_N', 2020, 'cost_invest'],
- 402: ['CostInvest', 'R_SH_HPELC_VER4_N', 2025, 'cost_invest'],
- 403: ['CostInvest', 'R_SH_HPELC_VER4_N', 2030, 'cost_invest'],
- 404: ['CostInvest', 'R_SH_HPELC_VER4_N', 2035, 'cost_invest'],
- 405: ['CostInvest', 'R_SH_HPELC_VER4_N', 2040, 'cost_invest'],
- 406: ['CostInvest', 'R_SH_HPGEO_N', 2020, 'cost_invest'],
- 407: ['CostInvest', 'R_SH_HPGEO_N', 2025, 'cost_invest'],
- 408: ['CostInvest', 'R_SH_HPGEO_N', 2030, 'cost_invest'],
- 409: ['CostInvest', 'R_SH_HPGEO_N', 2035, 'cost_invest'],
- 410: ['CostInvest', 'R_SH_HPGEO_N', 2040, 'cost_invest'],
- 411: ['CostInvest', 'R_SH_HPNGA_N', 2020, 'cost_invest'],
- 412: ['CostInvest', 'R_SH_HPNGA_N', 2025, 'cost_invest'],
- 413: ['CostInvest', 'R_SH_HPNGA_N', 2030, 'cost_invest'],
- 414: ['CostInvest', 'R_SH_HPNGA_N', 2035, 'cost_invest'],
- 415: ['CostInvest', 'R_SH_HPNGA_N', 2040, 'cost_invest'],
- 416: ['CostInvest', 'C_SH_AHPST_ELC_N', 2020, 'cost_invest'],
- 417: ['CostInvest', 'C_SH_AHPST_ELC_N', 2025, 'cost_invest'],
- 418: ['CostInvest', 'C_SH_AHPST_ELC_N', 2030, 'cost_invest'],
- 419: ['CostInvest', 'C_SH_AHPST_ELC_N', 2035, 'cost_invest'],
- 420: ['CostInvest', 'C_SH_AHPST_ELC_N', 2040, 'cost_invest'],
- 421: ['CostInvest', 'C_SH_AHPHE_ELC_N', 2020, 'cost_invest'],
- 422: ['CostInvest', 'C_SH_AHPHE_ELC_N', 2025, 'cost_invest'],
- 423: ['CostInvest', 'C_SH_AHPHE_ELC_N', 2030, 'cost_invest'],
- 424: ['CostInvest', 'C_SH_AHPHE_ELC_N', 2035, 'cost_invest'],
- 425: ['CostInvest', 'C_SH_AHPHE_ELC_N', 2040, 'cost_invest'],
- 426: ['CostInvest', 'C_SH_GHPST_ELC_N', 2020, 'cost_invest'],
- 427: ['CostInvest', 'C_SH_GHPST_ELC_N', 2025, 'cost_invest'],
- 428: ['CostInvest', 'C_SH_GHPST_ELC_N', 2030, 'cost_invest'],
- 429: ['CostInvest', 'C_SH_GHPST_ELC_N', 2035, 'cost_invest'],
- 430: ['CostInvest', 'C_SH_GHPST_ELC_N', 2040, 'cost_invest'],
- 431: ['CostInvest', 'C_SH_GHPHE_ELC_N', 2020, 'cost_invest'],
- 432: ['CostInvest', 'C_SH_GHPHE_ELC_N', 2025, 'cost_invest'],
- 433: ['CostInvest', 'C_SH_GHPHE_ELC_N', 2030, 'cost_invest'],
- 434: ['CostInvest', 'C_SH_GHPHE_ELC_N', 2035, 'cost_invest'],
- 435: ['CostInvest', 'C_SH_GHPHE_ELC_N', 2040, 'cost_invest'],
- 436: ['CostInvest', 'C_SH_HPST_NGA_N', 2020, 'cost_invest'],
- 437: ['CostInvest', 'C_SH_HPST_NGA_N', 2025, 'cost_invest'],
- 438: ['CostInvest', 'C_SH_HPST_NGA_N', 2030, 'cost_invest'],
- 439: ['CostInvest', 'C_SH_HPST_NGA_N', 2035, 'cost_invest'],
- 440: ['CostInvest', 'C_SH_HPST_NGA_N', 2040, 'cost_invest'],
- 441: ['CostInvest', 'T_LDV_MCELC_N', 2025, 'cost_invest'],
- 442: ['CostInvest', 'T_LDV_MCELC_N', 2030, 'cost_invest'],
- 443: ['CostInvest', 'T_LDV_MCELC_N', 2035, 'cost_invest'],
- 444: ['CostInvest', 'T_LDV_MCELC_N', 2040, 'cost_invest'],
- 445: ['CostInvest', 'T_LDV_CELC_N', 2020, 'cost_invest'],
- 446: ['CostInvest', 'T_LDV_CELC_N', 2025, 'cost_invest'],
- 447: ['CostInvest', 'T_LDV_CELC_N', 2030, 'cost_invest'],
- 448: ['CostInvest', 'T_LDV_CELC_N', 2035, 'cost_invest'],
- 449: ['CostInvest', 'T_LDV_CELC_N', 2040, 'cost_invest'],
- 450: ['CostInvest', 'T_LDV_FELC_N', 2020, 'cost_invest'],
- 451: ['CostInvest', 'T_LDV_FELC_N', 2025, 'cost_invest'],
- 452: ['CostInvest', 'T_LDV_FELC_N', 2030, 'cost_invest'],
- 453: ['CostInvest', 'T_LDV_FELC_N', 2035, 'cost_invest'],
- 454: ['CostInvest', 'T_LDV_FELC_N', 2040, 'cost_invest'],
- 455: ['CostInvest', 'T_LDV_SSELC_N', 2020, 'cost_invest'],
- 456: ['CostInvest', 'T_LDV_SSELC_N', 2025, 'cost_invest'],
- 457: ['CostInvest', 'T_LDV_SSELC_N', 2030, 'cost_invest'],
- 458: ['CostInvest', 'T_LDV_SSELC_N', 2035, 'cost_invest'],
- 459: ['CostInvest', 'T_LDV_SSELC_N', 2040, 'cost_invest'],
- }
-
- m = len(param_values)
- for j in range(0, m):
- Newdbpath = sys.path[0] + '/data_files/1st/Method_of_Morris' + str(k) + '.db'
- con = sqlite3.connect(Newdbpath)
- cur = con.cursor()
- filter1 = param_names[j][1]
- filter2 = param_names[j][2]
- table = param_names[j][0]
- cursor = con.execute('SELECT * FROM ' + "'" + table + "'")
- col_names = list(map(lambda x: x[0], cursor.description))
- if len(param_names[j]) == 4:
- update_var = param_names[j][3]
- text = (
- 'UPDATE '
- + "'"
- + table
- + "' SET "
- + "'"
- + update_var
- + "'=? WHERE "
- + "'"
- + col_names[0]
- + "'=? and "
- + "'"
- + col_names[1]
- + "'=?"
- )
- text = text.replace("'", '')
- con.execute(text, (param_values[j], filter1, filter2))
- con.commit()
- elif len(param_names[j]) == 5:
- filter3 = param_names[j][3]
- update_var = param_names[j][4]
- text = (
- 'UPDATE '
- + "'"
- + table
- + "' SET "
- + "'"
- + update_var
- + "'=? WHERE "
- + "'"
- + col_names[0]
- + "'=? and "
- + "'"
- + col_names[1]
- + "'=? and "
- + "'"
- + col_names[2]
- + "'=?"
- )
- text = text.replace("'", '')
- con.execute(text, (param_values[j], filter1, filter2, filter3))
- con.commit()
- else:
- filter3 = param_names[j][3]
- filter4 = param_names[j][4]
- update_var = param_names[j][5]
- text = (
- 'UPDATE '
- + "'"
- + table
- + "' SET "
- + "'"
- + update_var
- + "'=? WHERE "
- + "'"
- + col_names[0]
- + "'=? and "
- + "'"
- + col_names[1]
- + "'=? and "
- + "'"
- + col_names[2]
- + "'=? and "
- + "'"
- + col_names[3]
- + "'=?"
- )
- text = text.replace("'", '')
- con.execute(text, (param_values[j], filter1, filter2, filter3, filter4))
- con.commit()
- con.close()
- NewConfigfilePath = sys.path[0] + '/temoa_model/config_sample' + str(k)
- copyfile(sys.path[0] + '/temoa_model/config_sample', NewConfigfilePath)
- with open(sys.path[0] + '/temoa_model/config_sample', 'r') as file:
- data = file.readlines()
- data[13] = '--input=data_files/1st/Method_of_Morris' + str(k) + '.db'
- data[20] = '--output=data_files/1st/Method_of_Morris' + str(k) + '.db'
- with open(NewConfigfilePath, 'w') as file:
- file.writelines(data)
- os.system('python temoa_model/ --config=temoa_model/config_sample' + str(k))
- print(k)
-
- MonteCarlo_Objectives = []
-
- Newdbpath = sys.path[0] + '/data_files/1st/Method_of_Morris' + str(k) + '.db'
- con = sqlite3.connect(Newdbpath)
- cur = con.cursor()
- cur.execute('SELECT * FROM OutputObjective')
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute("SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2'")
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute(
- "SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2' and period=2015 and (sector<>'supply' OR tech='IMPTRNE85' OR tech='IMPTRNBIODSL')"
- )
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute(
- "SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2' and period=2020 and (sector<>'supply' OR tech='IMPTRNE85' OR tech='IMPTRNBIODSL')"
- )
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute(
- "SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2' and period=2025 and (sector<>'supply' OR tech='IMPTRNE85' OR tech='IMPTRNBIODSL')"
- )
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute(
- "SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2' and period=2030 and (sector<>'supply' OR tech='IMPTRNE85' OR tech='IMPTRNBIODSL')"
- )
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute(
- "SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2' and period=2035 and (sector<>'supply' OR tech='IMPTRNE85' OR tech='IMPTRNBIODSL')"
- )
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute(
- "SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2' and period=2040 and (sector<>'supply' OR tech='IMPTRNE85' OR tech='IMPTRNBIODSL')"
- )
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute(
- "SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2' and tech='IMPELCNGA'"
- )
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- cur.execute(
- "SELECT emis_comm, SUM(emission) FROM OutputEmission WHERE emis_comm='co2' and tech LIKE 'IMPELCCOA%'"
- )
- output_query = cur.fetchall()
- MonteCarlo_Objectives.append(output_query[-1][-1])
-
- con.close()
- return MonteCarlo_Objectives
-
-
-problem = read_param_file(sys.path[0] + '/Monte-Carlo-1st.txt')
-
-Number_of_Simulations = 1000
-param_values = np.zeros((Number_of_Simulations, problem['num_vars']))
-
-lhd = lhs(len(set(problem['groups'])), samples=Number_of_Simulations)
-for k in range(0, Number_of_Simulations):
- c = 0 # c points to each group of inputs
- param_values[k][0] = problem['bounds'][0][0] + lhd[k][c] * (
- problem['bounds'][0][1] - problem['bounds'][0][0]
- )
- for i in range(1, problem['num_vars']):
- if problem['groups'][i] == problem['groups'][i - 1]:
- param_values[k, i] = problem['bounds'][i][0] + lhd[k][c] * (
- problem['bounds'][i][1] - problem['bounds'][i][0]
- )
- else:
- c = c + 1
- param_values[k, i] = problem['bounds'][i][0] + lhd[k][c] * (
- problem['bounds'][i][1] - problem['bounds'][i][0]
- )
- Newdbpath = sys.path[0] + '/data_files/1st/Method_of_Morris' + str(k) + '.db'
- copyfile(sys.path[0] + '/data_files/1st/Method_of_Morris.db', Newdbpath)
-num_cores = multiprocessing.cpu_count()
-MonteCarlo_Objectives = Parallel(n_jobs=num_cores)(
- delayed(evaluate)(param_values[ii, :], ii) for ii in range(0, Number_of_Simulations)
-)
-MonteCarlo_Objectives = array(MonteCarlo_Objectives)
-
-with open('MonteCarlo1st.csv', 'w') as f:
- writer = csv.writer(f, delimiter=',')
- for i in range(0, Number_of_Simulations):
- writer.writerow(np.append(lhd[i], MonteCarlo_Objectives[i]))
-f.close()
-print('--- %s seconds ---' % (time.time() - start_time))
diff --git a/temoa/extensions/monte_carlo/Monte-Carlo.txt b/temoa/extensions/monte_carlo/Monte-Carlo.txt
deleted file mode 100644
index 09f1f099..00000000
--- a/temoa/extensions/monte_carlo/Monte-Carlo.txt
+++ /dev/null
@@ -1 +0,0 @@
-x0 1212.12 1818.18 SolarPV
x1 1127.945 1691.9175 SolarPV
x2 1043.77 1565.655 SolarPV
x3 959.595 1439.3925 SolarPV
x4 875.42 1313.13 SolarPV
x5 2344.16 3516.24 SolarPV
x6 1910.192 2865.288 SolarPV
x7 1476.384 2214.576 SolarPV
x8 1353.392 2030.088 SolarPV
x9 1230.32 1845.48 SolarPV
x10 2.264 3.396 NG_Price
x11 3.12 4.68 NG_Price
x12 3.296 4.944 NG_Price
x13 3.496 5.244 NG_Price
x14 3.576 5.364 NG_Price
x15 3.68 5.52 NG_Price
x16 11.48681873 17.23022809 NG_Price
x17 11.43782813 17.15674219 NG_Price
x18 11.2094045 16.81410674 NG_Price
x19 10.47136569 15.70704853 NG_Price
x20 10.59199839 15.88799759 NG_Price
x21 10.86698228 16.30047342 NG_Price
x22 6.951870734 10.4278061 NG_Price
x23 7.360032386 11.04004858 NG_Price
x24 7.93519517 11.90279275 NG_Price
x25 8.0004426 12.0006639 NG_Price
x26 8.249891976 12.37483796 NG_Price
x27 8.390864512 12.58629677 NG_Price
x28 5.294350673 7.941526009 NG_Price
x29 6.3621376 9.5432064 NG_Price
x30 6.863169971 10.29475496 NG_Price
x31 6.867264713 10.30089707 NG_Price
x32 7.028128561 10.54219284 NG_Price
x33 7.05853895 10.58780842 NG_Price
x34 1401.110071 2101.665107 Wind_Turbine
x35 1397.598518 2096.397776 Wind_Turbine
x36 1394.086963 2091.130445 Wind_Turbine
x37 1390.575409 2085.863113 Wind_Turbine
x38 1390.575409 2085.863113 Wind_Turbine
x39 1374.162814 2061.244222 Wind_Turbine
x40 1370.718797 2056.078195 Wind_Turbine
x41 1367.27478 2050.91217 Wind_Turbine
x42 1363.830763 2045.746145 Wind_Turbine
x43 1363.830763 2045.746145 Wind_Turbine
x44 1481.731689 2222.597533 Wind_Turbine
x45 1478.018075 2217.027113 Wind_Turbine
x46 1474.304462 2211.456692 Wind_Turbine
x47 1470.590849 2205.886273 Wind_Turbine
x48 1470.590849 2205.886273 Wind_Turbine
x49 1.510398506 2.26559776 Coal_Price
x50 1.560988641 2.341482961 Coal_Price
x51 1.609583158 2.414374738 Coal_Price
x52 1.665994533 2.498991799 Coal_Price
x53 1.732880137 2.599320205 Coal_Price
x54 1.795149323 2.692723985 Coal_Price
x55 1.510398506 2.26559776 Coal_Price
x56 1.560988641 2.341482961 Coal_Price
x57 1.609583158 2.414374738 Coal_Price
x58 1.665994533 2.498991799 Coal_Price
x59 1.732880137 2.599320205 Coal_Price
x60 1.795149323 2.692723985 Coal_Price
x61 1.510398506 2.26559776 Coal_Price
x62 1.560988641 2.341482961 Coal_Price
x63 1.609583158 2.414374738 Coal_Price
x64 1.665994533 2.498991799 Coal_Price
x65 1.732880137 2.599320205 Coal_Price
x66 1.795149323 2.692723985 Coal_Price
x67 714.4 1071.6 Combined_Cycle
x68 714.4 1071.6 Combined_Cycle
x69 714.4 1071.6 Combined_Cycle
x70 714.4 1071.6 Combined_Cycle
x71 714.4 1071.6 Combined_Cycle
x72 806.64 1209.96 Combined_Cycle
x73 806.64 1209.96 Combined_Cycle
x74 806.64 1209.96 Combined_Cycle
x75 806.64 1209.96 Combined_Cycle
x76 806.64 1209.96 Combined_Cycle
x77 1578.4 2367.6 E85_E10
x78 1664 2496 E85_E10
x79 1664 2496 E85_E10
x80 1664 2496 E85_E10
x81 1664 2496 E85_E10
x82 1877.6 2816.4 E85_E10
x83 1973.6 2960.4 E85_E10
x84 1973.6 2960.4 E85_E10
x85 1973.6 2960.4 E85_E10
x86 1973.6 2960.4 E85_E10
x87 1696.8 2545.2 E85_E10
x88 1810.4 2715.6 E85_E10
x89 1816.8 2725.2 E85_E10
x90 1816.8 2725.2 E85_E10
x91 1816.8 2725.2 E85_E10
x92 2518.4 3777.6 E85_E10
x93 2612 3918 E85_E10
x94 2624.8 3937.2 E85_E10
x95 2624.8 3937.2 E85_E10
x96 2624.8 3937.2 E85_E10
x97 1788.8 2683.2 E85_E10
x98 1900 2850 E85_E10
x99 1922.4 2883.6 E85_E10
x100 1922.4 2883.6 E85_E10
x101 1922.4 2883.6 E85_E10
x102 1570.4 2355.6 E85_E10
x103 1652.8 2479.2 E85_E10
x104 1664 2496 E85_E10
x105 1664 2496 E85_E10
x106 1664 2496 E85_E10
x107 2852 4278 E85_E10
x108 2953.6 4430.4 E85_E10
x109 3033.6 4550.4 E85_E10
x110 3031.2 4546.8 E85_E10
x111 3031.2 4546.8 E85_E10
x112 3031.2 4546.8 E85_E10
x113 1484 2226 E85_E10
x114 1572 2358 E85_E10
x115 1659.2 2488.8 E85_E10
x116 1659.2 2488.8 E85_E10
x117 1659.2 2488.8 E85_E10
x118 1659.2 2488.8 E85_E10
x119 1792.8 2689.2 E85_E10
x120 1870.4 2805.6 E85_E10
x121 1967.2 2950.8 E85_E10
x122 1967.2 2950.8 E85_E10
x123 1967.2 2950.8 E85_E10
x124 1967.2 2950.8 E85_E10
x125 1616.8 2425.2 E85_E10
x126 1690.4 2535.6 E85_E10
x127 1803.2 2704.8 E85_E10
x128 1810.4 2715.6 E85_E10
x129 1810.4 2715.6 E85_E10
x130 1810.4 2715.6 E85_E10
x131 2438.4 3657.6 E85_E10
x132 2512 3768 E85_E10
x133 2604.8 3907.2 E85_E10
x134 2618.4 3927.6 E85_E10
x135 2618.4 3927.6 E85_E10
x136 2618.4 3927.6 E85_E10
x137 1781.6 2672.4 E85_E10
x138 1893.6 2840.4 E85_E10
x139 1916 2874 E85_E10
x140 1916 2874 E85_E10
x141 1916 2874 E85_E10
x142 1568.8 2353.2 E85_E10
x143 1645.6 2468.4 E85_E10
x144 1652.8 2479.2 E85_E10
x145 1652.8 2479.2 E85_E10
x146 1652.8 2479.2 E85_E10
x147 4117.88 6176.82 E85_E10
x148 4117.88 6176.82 E85_E10
x149 4117.88 6176.82 E85_E10
x150 4117.88 6176.82 E85_E10
x151 4117.88 6176.82 E85_E10
x152 4941.6 7412.4 E85_E10
x153 4941.6 7412.4 E85_E10
x154 4941.6 7412.4 E85_E10
x155 4941.6 7412.4 E85_E10
x156 4941.6 7412.4 E85_E10
x157 2646.96 3970.44 E85_E10
x158 2646.96 3970.44 E85_E10
x159 2646.96 3970.44 E85_E10
x160 2646.96 3970.44 E85_E10
x161 2646.96 3970.44 E85_E10
x162 2726 4089 E85_E10
x163 2726 4089 E85_E10
x164 2726 4089 E85_E10
x165 2726 4089 E85_E10
x166 2726 4089 E85_E10
x167 2745.2 4117.8 E85_E10
x168 2745.2 4117.8 E85_E10
x169 2745.2 4117.8 E85_E10
x170 2682.64 4023.96 E85_E10
x171 2682.64 4023.96 E85_E10
x172 2682.64 4023.96 E85_E10
x173 2682.64 4023.96 E85_E10
x174 2682.64 4023.96 E85_E10
x175 2792.32 4188.48 E85_E10
x176 2792.32 4188.48 E85_E10
x177 2792.32 4188.48 E85_E10
x178 2905.36 4358.04 E85_E10
x179 2905.36 4358.04 E85_E10
x180 2905.36 4358.04 E85_E10
x181 2905.36 4358.04 E85_E10
x182 2905.36 4358.04 E85_E10
x183 2905.36 4358.04 E85_E10
x184 3009.6 4514.4 E85_E10
x185 3009.6 4514.4 E85_E10
x186 3009.6 4514.4 E85_E10
x187 3009.6 4514.4 E85_E10
x188 3009.6 4514.4 E85_E10
x189 3009.6 4514.4 E85_E10
x190 2448.44 3672.66 E85_E10
x191 2448.44 3672.66 E85_E10
x192 2448.44 3672.66 E85_E10
x193 2448.44 3672.66 E85_E10
x194 2448.44 3672.66 E85_E10
x195 2488.8 3733.2 E85_E10
x196 2488.8 3733.2 E85_E10
x197 2488.8 3733.2 E85_E10
x198 2488.8 3733.2 E85_E10
x199 2488.8 3733.2 E85_E10
x200 2530.8 3796.2 E85_E10
x201 2530.8 3796.2 E85_E10
x202 2530.8 3796.2 E85_E10
x203 2530.8 3796.2 E85_E10
x204 2530.8 3796.2 E85_E10
x205 17.264 25.896 PP_Price
x206 18.336 27.504 PP_Price
x207 19.224 28.836 PP_Price
x208 20.04 30.06 PP_Price
x209 21.08 31.62 PP_Price
x210 22.16 33.24 PP_Price
x211 17.44 26.16 PP_Price
x212 18.488 27.732 PP_Price
x213 19.264 28.896 PP_Price
x214 19.376 29.064 PP_Price
x215 19.504 29.256 PP_Price
x216 20.056 30.084 PP_Price
x217 13.64162421 20.46243631 PP_Price
x218 15.94929381 23.92394071 PP_Price
x219 17.80175502 26.70263252 PP_Price
x220 19.31753796 28.97630694 PP_Price
x221 21.35147612 32.02721418 PP_Price
x222 23.44616742 35.16925114 PP_Price
x223 14.42749296 21.64123944 PP_Price
x224 15.67166428 23.50749642 PP_Price
x225 17.03180082 25.54770124 PP_Price
x226 18.31029925 27.46544887 PP_Price
x227 19.95652875 29.93479313 PP_Price
x228 21.93533557 32.90300335 PP_Price
x229 14.42749296 21.64123944 PP_Price
x230 15.67166428 23.50749642 PP_Price
x231 17.03180082 25.54770124 PP_Price
x232 18.31029925 27.46544887 PP_Price
x233 19.95652875 29.93479313 PP_Price
x234 21.93533557 32.90300335 PP_Price
x235 24.216 36.324 PP_Price
x236 24.424 36.636 PP_Price
x237 25.312 37.968 PP_Price
x238 26.064 39.096 PP_Price
x239 27.104 40.656 PP_Price
x240 27.88 41.82 PP_Price
x241 13.976 20.964 PP_Price
x242 13.976 20.964 PP_Price
x243 13.976 20.964 PP_Price
x244 13.976 20.964 PP_Price
x245 13.976 20.964 PP_Price
x246 13.976 20.964 PP_Price
x247 18 27 PP_Price
x248 19.152 28.728 PP_Price
x249 20.056 30.084 PP_Price
x250 20.912 31.368 PP_Price
x251 22.088 33.132 PP_Price
x252 23.248 34.872 PP_Price
x253 5.593157066 8.389735598 PP_Price
x254 8.044008056 12.06601208 PP_Price
x255 9.25598176 13.88397264 PP_Price
x256 10.35154724 15.52732086 PP_Price
x257 11.71768513 17.57652769 PP_Price
x258 13.22982117 19.84473175 PP_Price
x259 12.31642849 18.47464273 PP_Price
x260 14.67624558 22.01436838 PP_Price
x261 15.56906042 23.35359062 PP_Price
x262 16.27910939 24.41866409 PP_Price
x263 17.43675461 26.15513191 PP_Price
x264 18.61121881 27.91682821 PP_Price
x265 23.28 34.92 PP_Price
x266 23.56 35.34 PP_Price
x267 24.48 36.72 PP_Price
x268 25.24 37.86 PP_Price
x269 26.264 39.396 PP_Price
x270 27.072 40.608 PP_Price
x271 19.072 28.608 PP_Price
x272 20.24 30.36 PP_Price
x273 21.28 31.92 PP_Price
x274 22.2 33.3 PP_Price
x275 23.12 34.68 PP_Price
x276 24.24 36.36 PP_Price
x277 13.888 20.832 PP_Price
x278 14.848 22.272 PP_Price
x279 15.64 23.46 PP_Price
x280 15.744 23.616 PP_Price
x281 15.888 23.832 PP_Price
x282 16.432 24.648 PP_Price
x283 14.08594606 21.12891908 PP_Price
x284 16.30053225 24.45079837 PP_Price
x285 18.57876166 27.86814248 PP_Price
x286 20.2139261 30.32088914 PP_Price
x287 22.3972476 33.5958714 PP_Price
x288 24.63459544 36.95189316 PP_Price
x289 17.336 26.004 PP_Price
x290 17.336 26.004 PP_Price
x291 17.336 26.004 PP_Price
x292 17.336 26.004 PP_Price
x293 17.336 26.004 PP_Price
x294 17.336 26.004 PP_Price
x295 1711.2 2566.8 Diesel_Engines
x296 1772.8 2659.2 Diesel_Engines
x297 1777.6 2666.4 Diesel_Engines
x298 1777.6 2666.4 Diesel_Engines
x299 1777.6 2666.4 Diesel_Engines
x300 1976 2964 Diesel_Engines
x301 2044.8 3067.2 Diesel_Engines
x302 2044.8 3067.2 Diesel_Engines
x303 2044.8 3067.2 Diesel_Engines
x304 2044.8 3067.2 Diesel_Engines
x305 1904 2856 Diesel_Engines
x306 2010.4 3015.6 Diesel_Engines
x307 2030.4 3045.6 Diesel_Engines
x308 2030.4 3045.6 Diesel_Engines
x309 2030.4 3045.6 Diesel_Engines
x310 2758.4 4137.6 Diesel_Engines
x311 2839.2 4258.8 Diesel_Engines
x312 2865.6 4298.4 Diesel_Engines
x313 2865.6 4298.4 Diesel_Engines
x314 2865.6 4298.4 Diesel_Engines
x315 2109.6 3164.4 Diesel_Engines
x316 2211.2 3316.8 Diesel_Engines
x317 2227.2 3340.8 Diesel_Engines
x318 2227.2 3340.8 Diesel_Engines
x319 2227.2 3340.8 Diesel_Engines
x320 1889.6 2834.4 Diesel_Engines
x321 1958.4 2937.6 Diesel_Engines
x322 1958.4 2937.6 Diesel_Engines
x323 1958.4 2937.6 Diesel_Engines
x324 1958.4 2937.6 Diesel_Engines
x325 4159.2 6238.8 Diesel_Engines
x326 4159.2 6238.8 Diesel_Engines
x327 4159.2 6238.8 Diesel_Engines
x328 4159.2 6238.8 Diesel_Engines
x329 4159.2 6238.8 Diesel_Engines
x330 4991.2 7486.8 Diesel_Engines
x331 4991.2 7486.8 Diesel_Engines
x332 4991.2 7486.8 Diesel_Engines
x333 4991.2 7486.8 Diesel_Engines
x334 4991.2 7486.8 Diesel_Engines
x335 2769.36 4154.04 Diesel_Engines
x336 2769.36 4154.04 Diesel_Engines
x337 2769.36 4154.04 Diesel_Engines
x338 2769.36 4154.04 Diesel_Engines
x339 2769.36 4154.04 Diesel_Engines
x340 3022.32 4533.48 Diesel_Engines
x341 3022.32 4533.48 Diesel_Engines
x342 3022.32 4533.48 Diesel_Engines
x343 3022.32 4533.48 Diesel_Engines
x344 3022.32 4533.48 Diesel_Engines
x345 2797.04 4195.56 Diesel_Engines
x346 2797.04 4195.56 Diesel_Engines
x347 2797.04 4195.56 Diesel_Engines
x348 2797.04 4195.56 Diesel_Engines
x349 2797.04 4195.56 Diesel_Engines
x350 2797.04 4195.56 Diesel_Engines
x351 2448.44 3672.66 Diesel_Engines
x352 2448.44 3672.66 Diesel_Engines
x353 2448.44 3672.66 Diesel_Engines
x354 2448.44 3672.66 Diesel_Engines
x355 2448.44 3672.66 Diesel_Engines
x356 2466.744 3700.116 Diesel_Engines
x357 2466.744 3700.116 Diesel_Engines
x358 2466.744 3700.116 Diesel_Engines
x359 2466.744 3700.116 Diesel_Engines
x360 2466.744 3700.116 Diesel_Engines
x361 2508.8 3763.2 Diesel_Engines
x362 2508.8 3763.2 Diesel_Engines
x363 2508.8 3763.2 Diesel_Engines
x364 2508.8 3763.2 Diesel_Engines
x365 2508.8 3763.2 Diesel_Engines
x366 2466.744 3700.116 Diesel_Engines
x367 2466.744 3700.116 Diesel_Engines
x368 2466.744 3700.116 Diesel_Engines
x369 2466.744 3700.116 Diesel_Engines
x370 2466.744 3700.116 Diesel_Engines
x371 2554.56 3831.84 Diesel_Engines
x372 2554.56 3831.84 Diesel_Engines
x373 2554.56 3831.84 Diesel_Engines
x374 2554.56 3831.84 Diesel_Engines
x375 2554.56 3831.84 Diesel_Engines
x376 2609.44 3914.16 Diesel_Engines
x377 2609.44 3914.16 Diesel_Engines
x378 2609.44 3914.16 Diesel_Engines
x379 2609.44 3914.16 Diesel_Engines
x380 2609.44 3914.16 Diesel_Engines
x381 2733.92 4100.88 Diesel_Engines
x382 2733.92 4100.88 Diesel_Engines
x383 2733.92 4100.88 Diesel_Engines
x384 2733.92 4100.88 Diesel_Engines
x385 2733.92 4100.88 Diesel_Engines
x386 5.408 8.112 HP_SH
x387 5.408 8.112 HP_SH
x388 5.408 8.112 HP_SH
x389 5.408 8.112 HP_SH
x390 5.408 8.112 HP_SH
x391 7.488 11.232 HP_SH
x392 7.488 11.232 HP_SH
x393 7.488 11.232 HP_SH
x394 7.488 11.232 HP_SH
x395 7.488 11.232 HP_SH
x396 8.192 12.288 HP_SH
x397 8.192 12.288 HP_SH
x398 8.192 12.288 HP_SH
x399 8.192 12.288 HP_SH
x400 8.192 12.288 HP_SH
x401 8.96 13.44 HP_SH
x402 8.96 13.44 HP_SH
x403 9.016 13.524 HP_SH
x404 9.016 13.524 HP_SH
x405 9.016 13.524 HP_SH
x406 14.208 21.312 HP_SH
x407 14.208 21.312 HP_SH
x408 14.208 21.312 HP_SH
x409 14.208 21.312 HP_SH
x410 14.208 21.312 HP_SH
x411 9.84 14.76 HP_SH
x412 9.84 14.76 HP_SH
x413 9.84 14.76 HP_SH
x414 9.84 14.76 HP_SH
x415 9.84 14.76 HP_SH
x416 6.032 9.048 HP_SH
x417 6.032 9.048 HP_SH
x418 6.032 9.048 HP_SH
x419 6.032 9.048 HP_SH
x420 6.032 9.048 HP_SH
x421 7.608 11.412 HP_SH
x422 7.608 11.412 HP_SH
x423 7.608 11.412 HP_SH
x424 7.608 11.412 HP_SH
x425 7.608 11.412 HP_SH
x426 11.016 16.524 HP_SH
x427 11.016 16.524 HP_SH
x428 11.016 16.524 HP_SH
x429 11.016 16.524 HP_SH
x430 11.016 16.524 HP_SH
x431 13.376 20.064 HP_SH
x432 13.376 20.064 HP_SH
x433 13.376 20.064 HP_SH
x434 13.376 20.064 HP_SH
x435 13.376 20.064 HP_SH
x436 16.72 25.08 HP_SH
x437 16.72 25.08 HP_SH
x438 10.16 15.24 HP_SH
x439 10.16 15.24 HP_SH
x440 10.16 15.24 HP_SH
x441 3401.6 5102.4 Electric_Vehicles
x442 3202.4 4803.6 Electric_Vehicles
x443 3172.8 4759.2 Electric_Vehicles
x444 3172.8 4759.2 Electric_Vehicles
x445 1983.2 2974.8 Electric_Vehicles
x446 1829.6 2744.4 Electric_Vehicles
x447 1763.2 2644.8 Electric_Vehicles
x448 1736 2604 Electric_Vehicles
x449 1736 2604 Electric_Vehicles
x450 2259.2 3388.8 Electric_Vehicles
x451 2205.6 3308.4 Electric_Vehicles
x452 2129.6 3194.4 Electric_Vehicles
x453 2096 3144 Electric_Vehicles
x454 2096 3144 Electric_Vehicles
x455 2219.2 3328.8 Electric_Vehicles
x456 2019.2 3028.8 Electric_Vehicles
x457 1932 2898 Electric_Vehicles
x458 1898.4 2847.6 Electric_Vehicles
x459 1898.4 2847.6 Electric_Vehicles
\ No newline at end of file
diff --git a/temoa/extensions/monte_carlo/Monte_Carlo_README.txt b/temoa/extensions/monte_carlo/Monte_Carlo_README.txt
deleted file mode 100644
index 9d793c6a..00000000
--- a/temoa/extensions/monte_carlo/Monte_Carlo_README.txt
+++ /dev/null
@@ -1,23 +0,0 @@
------------------------
-Monte Carlo README
------------------------
-
-The current version of this Monte Carlo script was developed to run the cases
-associated with Eshraghi et al. [1]. The script contains some hard-wired
-elements that need to be fixed to make the code generalizable to other studies.
-
-Running the Monte Carlo script requires the installation of a python library
-called joblib [2]. To minimize the computational time, this library creates
-an embarrassingly parallel implementation of the framework.
-
-To run the script, run the following from the command line:
-
-$ python Monte-Carlo.py
-
-The Monte-Carlo.txt file defines the parameter ranges used in each Monte Carlo simulation.
-
-
-
-References:
-[1] Eshraghi, h.; De Queiroz, A, R,; DeCarolis, J, F,; US Energy-Related Greenhouse Gas Emissions in the Absence of Federal Climate Policy, Environmental Science and Technology, 2018
-[2] joblib 0.11 : Python Package Index, https://pypi.python.org/pypi/joblib
\ No newline at end of file
diff --git a/temoa/extensions/monte_carlo/__init__.py b/temoa/extensions/monte_carlo/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py b/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py
new file mode 100644
index 00000000..e1d51d3e
--- /dev/null
+++ b/temoa/extensions/monte_carlo/example_builds/scenario_analyzer.py
@@ -0,0 +1,48 @@
+"""
+Tools for Energy Model Optimization and Analysis (Temoa):
+An open source framework for energy systems optimization modeling
+
+Copyright (C) 2015, NC State University
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+A complete copy of the GNU General Public License v2 (GPLv2) is available
+in LICENSE.txt. Users uncompressing this from an archive may not have
+received this license file. If not, see .
+
+
+Written by: J. F. Hyink
+jeff@westernspark.us
+https://westernspark.us
+Created on: 11/11/24
+
+Simple analyzer--example only
+
+"""
+from math import sqrt
+from pathlib import Path
+from sqlite3 import Connection
+
+from matplotlib import pyplot as plt
+
+from definitions import PROJECT_ROOT
+
+scenario_name = 'Purple Onion' # must match config file
+db_path = Path(PROJECT_ROOT, 'data_files/example_dbs/utopia.sqlite')
+with Connection(db_path) as conn:
+ cur = conn.cursor()
+ obj_values = cur.execute(
+ f"SELECT total_system_cost FROM OutputObjective WHERE scenario LIKE '{scenario_name}-%'"
+ ).fetchall()
+ obj_values = tuple(t[0] for t in obj_values)
+
+plt.hist(obj_values, bins=int(sqrt(len(obj_values))))
+plt.show()
diff --git a/temoa/extensions/monte_carlo/example_builds/scenario_maker.py b/temoa/extensions/monte_carlo/example_builds/scenario_maker.py
new file mode 100644
index 00000000..c93d53e1
--- /dev/null
+++ b/temoa/extensions/monte_carlo/example_builds/scenario_maker.py
@@ -0,0 +1,83 @@
+"""
+Tools for Energy Model Optimization and Analysis (Temoa):
+An open source framework for energy systems optimization modeling
+
+Copyright (C) 2015, NC State University
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+A complete copy of the GNU General Public License v2 (GPLv2) is available
+in LICENSE.txt. Users uncompressing this from an archive may not have
+received this license file. If not, see .
+
+
+Written by: J. F. Hyink
+jeff@westernspark.us
+https://westernspark.us
+Created on: 11/11/24
+
+This file is intended to be a simple EXAMPLE for testing mainly of how one might make a set of
+runs for a Monte Carlo simulation.
+
+This scenario is based on Utopia with the following random variables:
+
+Cost of Imported Oil will have a normal distribution of *relative changes* applied in all periods
+Residential Heating (RH) will have a similar distribution, with some negative correlation (seems logical that
+there is some hidden price sensitivity, even though RH could be satisfied from electricity as well.)
+
+Additionally, we will assume there is an independent 20% chance that the govt will subsidize new nuclear
+power by:
+ (a) subsidizing the cost of any Investment Cost by 40%, in the out-years of 2000, 2010 (but not 1990)
+ (b) paying all fixed costs in the same years.
+
+Let's make a set of 500 runs and explore output
+
+"""
+from pathlib import Path
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+from definitions import PROJECT_ROOT
+
+# distro for the related cost vars
+
+# multivariate norm generator:
+# https://numpy.org/doc/stable/reference/random/generated/numpy.random.multivariate_normal.html
+
+num_runs = 1000
+cov = np.array([[0.4, -0.1], [-0.1, 0.1]])
+price_devs = np.random.multivariate_normal([0, 0], cov, size=num_runs)
+print(f'correlation check: {np.corrcoef(price_devs.T)[0, 1]}')
+
+# verify with a peek...
+plt.plot(price_devs[:, 0], price_devs[:, 1], '.', alpha=0.5)
+plt.axis('equal')
+plt.grid()
+plt.show()
+
+# generate nuke discounts
+nuc_dev = np.random.binomial(n=1, p=0.20, size=num_runs) * -0.4
+
+# put it together...
+file_loc = Path(PROJECT_ROOT) / 'data_files/monte_carlo/run_settings_2.csv'
+with open(file_loc, 'w') as f:
+ f.write('run,param,index,mod,value,notes\n')
+ for run_idx in range(num_runs):
+ f.write(
+ f'{run_idx+1},CostVariable,*|*|IMPOIL1|*,r,{price_devs[run_idx, 0]},oil relative change\n'
+ )
+ f.write(f'{run_idx+1},Demand,*|*|RH,r,{price_devs[run_idx, 1]},res heat relative change\n')
+ f.write(
+ f'{run_idx+1},CostInvest,*|E21|2000/2010,r,{nuc_dev[run_idx]},nuclear invest relative discount\n'
+ )
+ if nuc_dev[run_idx] < 0:
+ f.write(f'{run_idx+1},CostFixed,*|*|E21|2000/2010,s,0.0,nuclear op cost covered\n')
diff --git a/temoa/extensions/monte_carlo/make_deltas_table.sql b/temoa/extensions/monte_carlo/make_deltas_table.sql
new file mode 100644
index 00000000..cb2fc304
--- /dev/null
+++ b/temoa/extensions/monte_carlo/make_deltas_table.sql
@@ -0,0 +1,14 @@
+BEGIN;
+
+CREATE TABLE IF NOT EXISTS OutputMCDelta
+(
+ scenario TEXT NOT NULL,
+ run INT NOT NULL,
+ param TEXT NOT NULL,
+ param_index TEXT NOT NULL,
+ old_val REAL NOT NULL,
+ new_val REAL NOT NULL
+
+);
+
+COMMIT;
\ No newline at end of file
diff --git a/temoa/extensions/monte_carlo/mc_run.py b/temoa/extensions/monte_carlo/mc_run.py
new file mode 100644
index 00000000..de030a22
--- /dev/null
+++ b/temoa/extensions/monte_carlo/mc_run.py
@@ -0,0 +1,370 @@
+"""
+Tools for Energy Model Optimization and Analysis (Temoa):
+An open source framework for energy systems optimization modeling
+
+Copyright (C) 2015, NC State University
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+A complete copy of the GNU General Public License v2 (GPLv2) is available
+in LICENSE.txt. Users uncompressing this from an archive may not have
+received this license file. If not, see .
+
+
+Written by: J. F. Hyink
+jeff@westernspark.us
+https://westernspark.us
+Created on: 11/9/24
+
+"""
+from collections import namedtuple, defaultdict
+from collections.abc import Generator
+from itertools import product
+from logging import getLogger
+from pathlib import Path
+
+from definitions import PROJECT_ROOT
+from temoa.temoa_model.hybrid_loader import HybridLoader
+from temoa.temoa_model.temoa_config import TemoaConfig
+from temoa.temoa_model.temoa_model import TemoaModel
+
+logger = getLogger(__name__)
+
+RowData = namedtuple('RowData', ['run', 'param_name', 'indices', 'adjustment', 'value', 'notes'])
+"""cleaned and converted tuple from data in a row of the csv file"""
+ChangeRecord = namedtuple('ChangeRecord', ['param_name', 'param_index', 'old_value', 'new_value'])
+"""a record of a data element change, for an element acted on by a Tweak"""
+
+
+class Tweak:
+ """
+ objects of this class represent individual tweaks to single (or wildcard)
+ data elements for a Monte Carlo run
+ """
+
+ def __init__(self, param_name: str, indices: tuple, adjustment: str, value: float):
+ if not isinstance(indices, tuple):
+ raise TypeError('indices must be a tuple')
+ if adjustment not in {'r', 'a', 's'}:
+ raise ValueError('adjustment must be either r/a/s')
+ if not isinstance(value, float | int):
+ raise TypeError('value must be a float or int')
+
+ self.param_name = param_name
+ self.indices = indices
+ self.adjustment = adjustment
+ self.value = value
+
+ def __repr__(self):
+ return f''
+
+
+class TweakFactory:
+ """
+ factor (likely a singleton) to manufacture Tweaks from input data
+ """
+
+ def __init__(self, data_store: dict):
+ """
+ make a new factor and use data_store as a validation tool
+ :param data_store: the data dictionary holding the base values for the model
+ """
+ if not isinstance(data_store, dict):
+ raise TypeError('data_store must be a dict')
+ self.val_data = data_store
+ tweak_dict: dict[int, list[Tweak]] = defaultdict(list)
+
+ def make_tweaks(self, row_number: int, row: str) -> tuple[int, list[Tweak]]:
+ """
+ make a tuple of tweaks from the row input. Rows with multiple identifiers (separated by /)
+ will produce 1 tweak per identifier per group
+ :param row: run, param, index, adjustment, value
+ :return: tuple of Tweaks generated from the row
+ """
+ rd = self.row_parser(row_number=row_number, row=row)
+ # pry the index
+ p_index = rd.indices.replace('(', '').replace(')', '') # remove any optional parens
+ tokens = p_index.split('|')
+ tokens = [t.strip() for t in tokens]
+ tweaks = []
+ # locate all 'multi' indices...
+ index_vals: dict[int, list] = defaultdict(list)
+ for pos, token in enumerate(tokens):
+ if '/' in token: # it is a multi-token
+ sub_tokens = token.split('/')
+ sub_tokens = [t.strip() for t in sub_tokens]
+ for sub_token in sub_tokens:
+ try: # integer conversion
+ sub_token = int(sub_token)
+ index_vals[pos].append(sub_token)
+ except ValueError:
+ index_vals[pos].append(sub_token)
+ else: # singleton
+ try: # integer conversion
+ token = int(token)
+ index_vals[pos].append(token)
+ except ValueError:
+ index_vals[pos].append(token)
+
+ # iterate through the positions and make all sets of indices...
+ index_groups = [index_vals[pos] for pos in sorted(index_vals.keys())]
+ all_inedexes = product(*index_groups)
+ res = [
+ Tweak(param_name=rd.param_name, indices=index, adjustment=rd.adjustment, value=rd.value)
+ for index in all_inedexes
+ ]
+ logger.debug('Made %d Tweaks for data labeled with run: %d', len(res), rd.run)
+ return rd.run, res
+
+ def row_parser(self, row_number: int, row: str) -> RowData:
+ """
+ Parse an individual row of the input .csv file
+ :param row_number: the row number from the reader (used to ID errors)
+ :param row: the raw row in string format
+ :return: a RowData tuple element
+ """
+ tokens = row.strip().split(',')
+ tokens = [t.strip() for t in tokens]
+ # check length
+ if len(tokens) != 6:
+ raise ValueError(
+ f'Incorrect number of tokens for row {row_number}. Did you omit notes / trailing comma for no notes or have a comma in your note?'
+ )
+ # convert the run number
+ try:
+ tokens[0] = int(tokens[0])
+ except ValueError:
+ raise ValueError(f'run number at row {row_number} must be an integer')
+ # convert the value
+ try:
+ tokens[-2] = float(tokens[-2])
+ except ValueError:
+ raise ValueError('value at row {idx} must be numeric')
+ rd = RowData(*tokens)
+
+ # make other checks...
+ if rd.param_name not in self.val_data:
+ # the param name should be a key value in the data dictionary
+ raise ValueError(
+ f'param_name at index: {row_number} is either invalid or not represented in the input dataset'
+ )
+ if rd.adjustment not in {'r', 'a', 's'}:
+ raise ValueError(f'adjustment at index {row_number} must be either r/a/s')
+ # check for no "empty" indices in the index
+ if '||' in rd.indices:
+ raise ValueError(
+ f'indices at index {row_number} cannot contain empty marker: ||. Did you mean to put in wildcard "*"?'
+ )
+ return rd
+
+
+class MCRun:
+ """
+ The data (and more?) to support a model build + run
+ """
+
+ def __init__(
+ self,
+ scenario_name: str,
+ run_index: int,
+ data_store: dict,
+ included_tweaks: dict[Tweak, list[ChangeRecord]],
+ ):
+ self.scenario_name = scenario_name
+ self.run_index = run_index
+ self.data_store = data_store
+ self.included_tweaks = included_tweaks
+
+ @property
+ def change_records(self) -> list[ChangeRecord]:
+ res = []
+ for k in self.included_tweaks:
+ res.extend(self.included_tweaks[k])
+ return res
+
+ @property
+ def model(self) -> TemoaModel:
+ dp = HybridLoader.data_portal_from_data(self.data_store)
+ model = TemoaModel()
+ instance = model.create_instance(data=dp)
+ # update the name to indexed...
+ instance.name = f'{self.scenario_name}-{self.run_index}'
+ logger.info('Created model instance for run %d', self.run_index)
+ return instance
+
+
+class MCRunFactory:
+ """
+ objects of this class represent individual run settings for Monte Carlo.
+
+ They will hold the "data tweaks" gathered from input file for application to the base data
+ """
+
+ def __init__(self, config: TemoaConfig, data_store: dict):
+ self.config = config
+ self.data_store = data_store
+ self.tweak_factory = TweakFactory(data_store)
+ self.settings_file = PROJECT_ROOT / Path(self.config.monte_carlo_inputs['run_settings'])
+
+ def prescreen_input_file(self):
+ """
+ read the input csv file and screen common errors
+ :return: True if file passes, false otherwise with log entries
+ """
+ with open(self.settings_file, 'r') as f:
+ header = f.readline().strip()
+ assert (
+ header == 'run,param,index,mod,value,notes'
+ ), 'header should be: run,param,index,mod,value,notes'
+ current_run = -1
+ for idx, row in enumerate(f.readlines(), start=2):
+ rd = self.tweak_factory.row_parser(idx, row)
+ # check that run indexing is monotonically increasing
+ if idx == 2:
+ current_run = rd.run
+ elif current_run > rd.run:
+ raise ValueError(f'Run sequence violation at row {idx}')
+ elif current_run < rd.run:
+ current_run = rd.run
+ logger.info(f'Pre-screen of data file: {self.settings_file} successful.')
+ return True
+
+ def _next_row_generator(self) -> Generator[tuple[int, str], None, None]:
+ """
+ A generator to read lines from thr run settings file
+ :return:
+ """
+ with open(self.settings_file, 'r') as f:
+ # burn header
+ f.readline()
+ idx = 2
+ for line in f:
+ yield idx, line
+ idx += 1
+
+ def tweak_set_generator(self) -> tuple[int, list[Tweak]]:
+ """
+ generator for lists of tweaks per run
+ :return:
+ """
+ rows = self._next_row_generator()
+ empty = False
+ run_tweaks = []
+ row_number, next_row = next(rows)
+ run_number, tweaks = self.tweak_factory.make_tweaks(row_number=row_number, row=next_row)
+ current_run = run_number
+ while not empty:
+ while run_number == current_run and not empty:
+ run_tweaks.extend(tweaks)
+ try:
+ row_number, next_row = next(rows)
+ run_number, tweaks = self.tweak_factory.make_tweaks(
+ row_number=row_number, row=next_row
+ )
+ except StopIteration:
+ empty = True
+ yield current_run, run_tweaks
+ # prep the next
+ run_tweaks = []
+ current_run = run_number
+
+ @staticmethod
+ def element_locator(data_store: dict, param: str, target_index: tuple) -> list[tuple]:
+ """
+ find the associated indices that match the index, which may
+ contain wildcards
+ :param data_store: the data dictionary to search
+ :param target_index: the search criteria
+ :return: list of matching indices
+ """
+ # locate non-wildcards
+ non_wildcard_locs = []
+ for idx, item in enumerate(target_index):
+ if item != '*':
+ non_wildcard_locs.append(idx)
+ # grab all the indices for the given parameter
+ param_data = data_store.get(param)
+ if not param_data:
+ return []
+ # check for correct index length (would be odd, but...)
+ first_index = tuple(param_data.keys())[0]
+ if len(target_index) != len(first_index):
+ raise ValueError(
+ f'length of search index {target_index} for parameter {param} does not match data ex: {first_index}'
+ )
+ raw_indices = param_data.keys()
+ matches = [
+ k
+ for k in raw_indices
+ if all((k[idx] == target_index[idx] for idx in non_wildcard_locs))
+ ]
+ return matches
+
+ @staticmethod
+ def _adjust_value(old_value: float, adjust_type: str, factor: float) -> float:
+ match adjust_type:
+ case 'r': # relative (ratio) based change
+ res = old_value * (1 + factor)
+ case 's': # pure substitution
+ res = factor
+ case 'a': # absolute change
+ res = old_value + factor
+ case _:
+ raise ValueError(f'Unsupported adjustment type {adjust_type}')
+ return res
+
+ def run_generator(self) -> Generator[MCRun, None, None]:
+ """
+ make a new MC Run, log problems with tweaks and write successful
+ tweaks to the DB Output
+ :return:
+ """
+ ts_gen = self.tweak_set_generator()
+ for run, tweaks in ts_gen:
+ logger.info('Making run %d from %d tweaks: %s', run, len(tweaks), tweaks)
+
+ # need to make a DEEP copy of the orig, which holds other dictionaries...
+ data_store = {k: v.copy() for k, v in self.data_store.items()}
+ failed_tweaks = []
+ good_tweaks: dict[Tweak, list[ChangeRecord]] = defaultdict(list)
+ for tweak in tweaks:
+ # locate the element
+ matching_indices = self.element_locator(data_store, tweak.param_name, tweak.indices)
+ if not matching_indices: # catalog as failure
+ failed_tweaks.append(tweak)
+ else:
+ for index in matching_indices:
+ old_value = data_store.get(tweak.param_name)[index]
+ new_value = self._adjust_value(old_value, tweak.adjustment, tweak.value)
+ data_store[tweak.param_name][index] = new_value
+ good_tweaks[tweak].append(
+ ChangeRecord(tweak.param_name, index, old_value, new_value)
+ )
+
+ # do the logging
+ for tweak in good_tweaks:
+ logger.debug('Successful tweak: %s', tweak)
+ for adjustment in good_tweaks[tweak]:
+ logger.debug(' made delta: %s', adjustment)
+
+ for tweak in failed_tweaks:
+ logger.warning('Failed tweak: %s', tweak)
+ # skip the creation of the run if no tweaks were successful (it would just be the baseline run...)
+ if not good_tweaks:
+ logger.warning(f'Aborting run: {run}. No good tweaks found')
+ continue
+ mc_run = MCRun(
+ scenario_name=self.config.scenario,
+ run_index=run,
+ data_store=data_store,
+ included_tweaks=good_tweaks,
+ )
+ yield mc_run
diff --git a/temoa/extensions/monte_carlo/mc_sequencer.py b/temoa/extensions/monte_carlo/mc_sequencer.py
new file mode 100644
index 00000000..7d52f759
--- /dev/null
+++ b/temoa/extensions/monte_carlo/mc_sequencer.py
@@ -0,0 +1,253 @@
+"""
+Tools for Energy Model Optimization and Analysis (Temoa):
+An open source framework for energy systems optimization modeling
+
+Copyright (C) 2015, NC State University
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+A complete copy of the GNU General Public License v2 (GPLv2) is available
+in LICENSE.txt. Users uncompressing this from an archive may not have
+received this license file. If not, see .
+
+
+Written by: J. F. Hyink
+jeff@westernspark.us
+https://westernspark.us
+Created on: 11/9/24
+
+A sequencer for Monte Carlo Runs
+.
+"""
+import logging
+import queue
+import sqlite3
+import time
+import tomllib
+from datetime import datetime
+from logging import getLogger
+from multiprocessing import Queue
+from pathlib import Path
+
+from definitions import PROJECT_ROOT, get_OUTPUT_PATH
+from temoa.extensions.modeling_to_generate_alternatives.worker import Worker
+from temoa.extensions.monte_carlo.mc_run import MCRunFactory
+from temoa.temoa_model.hybrid_loader import HybridLoader
+from temoa.temoa_model.table_writer import TableWriter
+from temoa.temoa_model.temoa_config import TemoaConfig
+from temoa.temoa_model.temoa_model import TemoaModel
+
+logger = getLogger(__name__)
+
+solver_options_path = Path(PROJECT_ROOT, 'temoa/extensions/monte_carlo/MC_solver_options.toml')
+
+
+class MCSequencer:
+ """
+ A sequencer to control the steps in Monte Carlo run sequence
+ """
+
+ def __init__(self, config: TemoaConfig):
+ self.config = config
+
+ # read in the options
+ try:
+ with open(solver_options_path, 'rb') as f:
+ all_options = tomllib.load(f)
+ s_options = all_options.get(self.config.solver_name, {})
+ logger.info('Using solver options: %s', s_options)
+
+ except FileNotFoundError:
+ logger.warning('Unable to find solver options toml file. Using default options.')
+ s_options = {}
+ all_options = {}
+
+ # worker options pulled from file
+ self.num_workers = all_options.get('num_workers', 1)
+ self.worker_solver_options = s_options
+
+ # internal records
+ self.solve_count = 0
+ self.seen_instance_indices = set()
+ self.orig_label = self.config.scenario
+
+ self.writer = TableWriter(self.config)
+ self.verbose = False # for troubleshooting
+
+ def start(self):
+ """Run the sequencer"""
+ # ==== basic sequence ====
+ # 1. Load the model data, which may involve filtering it down if source tracing
+ # 2. run a quick screen on the inputs using the data above as part of the screen
+ # before starting the long run
+ # 3. make a queue for runs
+ # 4. copy & modify the base data to make per-dataset runs
+ # 5. farm out the runs to workers
+
+ start_time = datetime.now()
+
+ # 0. Set up database for scenario
+ self.writer.clear_scenario()
+ self.writer.make_mc_tweaks_table() # add the output table for tweaks, if not exists
+ self.writer.make_summary_flow_table() # add the summary flow table, if not exists
+
+ # 1. Load data
+ with sqlite3.connect(self.config.input_database) as con:
+ hybrid_loader = HybridLoader(db_connection=con, config=self.config)
+ data_store = hybrid_loader.create_data_dict(myopic_index=None)
+ mc_run = MCRunFactory(config=self.config, data_store=data_store)
+
+ # 2. Screen the input file
+ mc_run.prescreen_input_file()
+
+ # 3. set up the run generator
+ run_gen = mc_run.run_generator()
+
+ # 4. Set up the workers
+ num_workers = self.num_workers
+ work_queue = Queue(1) # restrict the queue to hold just 1 models in it max
+ result_queue = Queue(
+ num_workers + 1
+ ) # must be able to hold a shutdown signal from all workers at once!
+ log_queue = Queue(50)
+ # make workers
+ workers = []
+ kwargs = {
+ 'solver_name': self.config.solver_name,
+ 'solver_options': self.worker_solver_options,
+ }
+ # construct path for the solver logs
+ s_path = Path(get_OUTPUT_PATH(), 'solver_logs')
+ if not s_path.exists():
+ s_path.mkdir()
+ for i in range(num_workers):
+ w = Worker(
+ model_queue=work_queue,
+ results_queue=result_queue,
+ log_root_name=__name__,
+ log_queue=log_queue,
+ log_level=logging.INFO,
+ solver_log_path=s_path,
+ **kwargs,
+ )
+ w.start()
+ workers.append(w)
+ # workers now running and waiting for jobs...
+
+ # 6. Start the iterative solve process and let the manager run the show
+ more_runs = True
+ # pull the first instance
+ mc_run = next(run_gen)
+ # capture the "tweaks"
+ self.writer.write_tweaks(iteration=mc_run.run_index, change_records=mc_run.change_records)
+ instance = mc_run.model
+ while more_runs:
+ try:
+ work_queue.put(instance, block=False) # put a log on the fire, if room
+ logger.info('Putting an instance in the work queue')
+ try:
+ mc_run = next(run_gen)
+ # capture the "tweaks"
+ self.writer.write_tweaks(
+ iteration=mc_run.run_index, change_records=mc_run.change_records
+ )
+ instance = mc_run.model
+ except StopIteration:
+ logger.debug('Pulled last run from run generator')
+ more_runs = False
+ except queue.Full:
+ # print('work queue is full')
+ pass
+ # see if there is a result ready to pick up, if not, pass
+ try:
+ next_result = result_queue.get_nowait()
+ except queue.Empty:
+ next_result = None
+ # print('no result')
+ if next_result is not None:
+ self.process_solve_results(next_result)
+ logger.info('Solve count: %d', self.solve_count)
+ self.solve_count += 1
+ if self.verbose or not self.config.silent:
+ print(f'MC Solve count: {self.solve_count}')
+ # pull anything from the logging queue and log it...
+ while True:
+ try:
+ record = log_queue.get_nowait()
+ process_logger = getLogger(record.name)
+ process_logger.handle(record)
+ except queue.Empty:
+ break
+ time.sleep(0.1) # prevent hyperactivity...
+
+ # 7. Shut down the workers and then the logging queue
+ if self.verbose:
+ print('shutting it down')
+ for _ in workers:
+ if self.verbose:
+ print('shutdown sent')
+ work_queue.put('ZEBRA') # shutdown signal
+
+ # 7b. Keep pulling results from the queue to empty it out
+ empty = 0
+ while True:
+ try:
+ next_result = result_queue.get_nowait()
+ if next_result == 'COYOTE': # shutdown signal
+ empty += 1
+ except queue.Empty:
+ next_result = None
+ if next_result is not None and next_result != 'COYOTE':
+ logger.debug('bagged a result post-shutdown')
+ self.process_solve_results(next_result)
+ logger.info('Solve count: %d', self.solve_count)
+ self.solve_count += 1
+ if self.verbose or not self.config.silent:
+ print(f'MC Solve count: {self.solve_count}')
+ while True:
+ try:
+ record = log_queue.get_nowait()
+ process_logger = getLogger(record.name)
+ process_logger.handle(record)
+ except queue.Empty:
+ break
+ if empty == num_workers:
+ break
+
+ for w in workers:
+ w.join()
+ logger.debug('worker wrapped up...')
+
+ log_queue.close()
+ log_queue.join_thread()
+ if self.verbose:
+ print('log queue closed')
+ work_queue.close()
+ work_queue.join_thread()
+ if self.verbose:
+ print('work queue joined')
+ result_queue.close()
+ result_queue.join_thread()
+ if self.verbose:
+ print('result queue joined')
+
+ def process_solve_results(self, instance: TemoaModel):
+ """write the results as required"""
+ # get the instance number from the model name, if provided
+ if '-' not in instance.name:
+ raise ValueError(
+ 'Instance name does not appear to contain a -idx value. The manager should be tagging/updating this'
+ )
+ idx = int(instance.name.split('-')[-1])
+ if idx in self.seen_instance_indices:
+ raise ValueError(f'Instance index {idx} already seen. Likely coding error')
+ self.seen_instance_indices.add(idx)
+ self.writer.write_mc_results(M=instance, iteration=idx)
diff --git a/temoa/extensions/monte_carlo/monte carlo design.md b/temoa/extensions/monte_carlo/monte carlo design.md
new file mode 100644
index 00000000..86ef6517
--- /dev/null
+++ b/temoa/extensions/monte_carlo/monte carlo design.md
@@ -0,0 +1,46 @@
+## Overall Framework
+### Objectives
+- Provide a flexible framework in which user can execute an arbitrary number of structured runs
+- Provide a clean interface which encapsulates the settings (or changes/deviations) for each run
+- Each run may have a single or multitude of deviations from the base data
+- Framework should do the "heavy lifting" to interpret the run settings within the DB context and
+make adjustments as necessary
+- Framework should record adjustments made to the DB for reporting/verification purposes
+### Use Cases
+- Primarily for Monte Carlo simulation where the settings for the individual runs represent a
+probabilistic draw from a multivariate (or multiple independent) settings with the intent of
+characterizing the related distribution of outcomes
+- Alternatively, a space-filling design may be employed to do sensitivity analysis
+### General Approach
+1. Construct an inputs `csv` file that represents the deviations for each run. The structure and
+options for this file are described _______.
+2. Make a `config.toml` file that employs the `monte_carlo` options and `MONTE_CARLO` mode to
+point to this file and the related database
+3. Run the model
+4. Inspect the log file for warnings
+
+### Design Decisions
+- Allow individual lines in the settings `csv` to use either
+ - explicit index matching
+ - multiple explicit option combinations
+ - wildcard characters
+- Individual lines can then spawn an arbitrary number of elemental corrections
+- Allow adjustments of 3 types for versatility:
+ - relative change
+ - absolute change
+ - direct substitution of value
+- Multiple lines can be used to describe an individual run
+- Employ worker functionality similar to MGA. Within the temoa extensions, there are 2 types of
+parallel processing employed. First, Method of Morris employs `joblib`'s parallel functionality.
+MGA on the other hand, employs a fully-developed multiprocessing environment with custom workers.
+- The advantages of `joblib` are:
+ - relatively straightforward to implement and catch results
+ - can pass in a thread-safe queue to capture log entries as is done in MM.
+- The disadvantages of `joblib` (relative to some of the Temoa goals) are:
+ - within the called function, we need to repeatedly re-spin-up a solver and db connection,
+ if either are desired (no persistence in the process).
+ - it is near impossible to catch "large results" like a solved model. In MM, only small data
+ is returned, in MC, we are probably going to return a full model and inspect/write to the DB
+ from it. We *might* be able to do that within the called function, but that would imply
+ opening/closing a new DB connection for each run with `num_cores` possible simultaneous
+ connections.
\ No newline at end of file
diff --git a/temoa/extensions/stochastics/EVPI.py b/temoa/extensions/stochastics/EVPI.py
index 708c44b5..4732a337 100755
--- a/temoa/extensions/stochastics/EVPI.py
+++ b/temoa/extensions/stochastics/EVPI.py
@@ -63,7 +63,8 @@ def return_obj(instance):
# obj_name, obj_value = objs[0], value(objs[1]())
# return obj_value
- import sys, os
+ import sys
+ import os
from collections import deque, defaultdict
from pyomo.pysp.util.scenariomodels import scenario_tree_model
diff --git a/temoa/extensions/stochastics/VSS.py b/temoa/extensions/stochastics/VSS.py
index 3145bc2d..f8e88577 100755
--- a/temoa/extensions/stochastics/VSS.py
+++ b/temoa/extensions/stochastics/VSS.py
@@ -109,7 +109,8 @@ def my_ef_writer(scenario_tree):
def solve_ef(ef_options):
# This function solves a stochastic optimization problem via extensive form
# This function was imported from the EVPI script
- import os, sys
+ import os
+ import sys
sif = ScenarioTreeInstanceFactory(
ef_options.model_directory, ef_options.instance_directory, ef_options.verbose
@@ -134,7 +135,8 @@ def solve_ef_fix(ef_options, avg_instance):
# where first stage decision variables are fixed at the optimal values from
# the deterministic model called here avg_instance
- import os, sys
+ import os
+ import sys
sif = ScenarioTreeInstanceFactory(
ef_options.model_directory, ef_options.instance_directory, ef_options.verbose
@@ -195,7 +197,8 @@ def return_obj(instance):
# Assuming there is only one objective function
return obj_values[0]
- import sys, os
+ import sys
+ import os
(head, tail) = os.path.split(p_model)
sys.path.insert(0, head)
diff --git a/temoa/temoa_model/hybrid_loader.py b/temoa/temoa_model/hybrid_loader.py
index ac301f43..5963433a 100644
--- a/temoa/temoa_model/hybrid_loader.py
+++ b/temoa/temoa_model/hybrid_loader.py
@@ -208,7 +208,36 @@ def table_exists(self, table_name: str) -> bool:
logger.info('Did not find existing table for (optional) table: %s', table_name)
return False
+ pass
+
def load_data_portal(self, myopic_index: MyopicIndex | None = None) -> DataPortal:
+ # time the creation of the data portal
+ tic = time.time()
+ data_dict = self.create_data_dict(myopic_index=myopic_index)
+
+ # pyomo namespace format has data[namespace][idx]=value
+ # the default namespace is None, thus...
+ namespace = {None: data_dict}
+ if self.debugging:
+ for item in namespace[None].items():
+ print(item[0], item[1])
+ dp = DataPortal(data_dict=namespace)
+ toc = time.time()
+ logger.debug('Data Portal Load time: %0.5f seconds', (toc - tic))
+ return dp
+
+ @staticmethod
+ def data_portal_from_data(data_source: dict) -> DataPortal:
+ """
+ Create a DataPortal object from a data dictionary. Useful when the data has been modified
+ :param data_source: the dataset to use
+ :return: a new DataPortal object
+ """
+ namespace = {None: data_source}
+ dp = DataPortal(data_dict=namespace)
+ return dp
+
+ def create_data_dict(self, myopic_index: MyopicIndex | None = None) -> dict:
"""
Create and Load a Data Portal. If source tracing is enabled in the config, the source trace will
be executed and filtered data will be used. Without source-trace, raw (unfiltered) data will be loaded.
@@ -222,7 +251,7 @@ def load_data_portal(self, myopic_index: MyopicIndex | None = None) -> DataPorta
# 3. use SQL query to get the full table
# 4. (OPTIONALLY) filter it, as needed for myopic
# 5. load it into the data dictionary
- logger.info('Loading data portal')
+ logger.info('Loading data dictionary')
# some logic checking...
if myopic_index is not None:
@@ -230,7 +259,7 @@ def load_data_portal(self, myopic_index: MyopicIndex | None = None) -> DataPorta
raise ValueError(f'received an illegal entry for the myopic index: {myopic_index}')
if self.config.scenario_mode != TemoaMode.MYOPIC:
raise RuntimeError(
- 'Myopic Index passed to data portal build, but mode is not Myopic.... '
+ 'Myopic Index passed to data dictionary build, but mode is not Myopic.... '
'Likely code error.'
)
elif myopic_index is None and self.config.scenario_mode == TemoaMode.MYOPIC:
@@ -250,8 +279,6 @@ def load_data_portal(self, myopic_index: MyopicIndex | None = None) -> DataPorta
mi = myopic_index # convenience
- # time the creation of the data portal
- tic = time.time()
# housekeeping
data: dict[str, list | dict] = dict()
@@ -1131,16 +1158,8 @@ def load_indexed_set(indexed_set: Set, index_value, element, element_validator):
set_data = self.load_param_idx_sets(data=data)
data.update(set_data)
self.data = data
- # pyomo namespace format has data[namespace][idx]=value
- # the default namespace is None, thus...
- namespace = {None: data}
- if self.debugging:
- for item in namespace[None].items():
- print(item[0], item[1])
- dp = DataPortal(data_dict=namespace)
- toc = time.time()
- logger.debug('Data Portal Load time: %0.5f seconds', (toc - tic))
- return dp
+
+ return data
def load_param_idx_sets(self, data: dict) -> dict:
"""
@@ -1149,10 +1168,13 @@ def load_param_idx_sets(self, data: dict) -> dict:
:return: a dictionary of the set name: values
The purpose of this function is to use the data we have already captured for the parameters
- to make indexing sets in the model. This replaces all of the "lambda" functions to reverse
- engineer the built parameters.
+ to make indexing sets in the model. This replaces all of the "lambda" functions which were
+ previously used to reverse engineer the built parameters.
+
+ Having these sets allows quicker constraint builds because they are the basis of many constraints
- Having these sets allows quicker constraint builds becuase they are the basis of many constraints
+ It also enables the model to be serialized by python's pickle by removing functions from the model
+ definitions
"""
M: TemoaModel = TemoaModel() # for typing
diff --git a/temoa/temoa_model/model_checking/validators.py b/temoa/temoa_model/model_checking/validators.py
index 216404ff..16799edd 100644
--- a/temoa/temoa_model/model_checking/validators.py
+++ b/temoa/temoa_model/model_checking/validators.py
@@ -91,6 +91,22 @@ def validate_linked_tech(M: 'TemoaModel') -> bool:
return True
+def no_slash_or_pipe(M: 'TemoaModel', element) -> bool:
+ """
+ No slash character in element
+ :param M:
+ :param element:
+ :return:
+ """
+ if isinstance(element, int | float):
+ return True
+ good = '/' not in str(element) and '|' not in str(element)
+ if not good:
+ logger.error('no slash "/" or pipe "|" character is allowed in: %s', str(element))
+ return False
+ return True
+
+
def region_check(M: 'TemoaModel', region) -> bool:
"""
Validate the region name (letters + numbers only + underscore)
diff --git a/temoa/temoa_model/table_writer.py b/temoa/temoa_model/table_writer.py
index 9e03cb38..86533183 100644
--- a/temoa/temoa_model/table_writer.py
+++ b/temoa/temoa_model/table_writer.py
@@ -4,6 +4,7 @@
import sqlite3
import sys
from collections import defaultdict, namedtuple
+from collections.abc import Iterable
from enum import Enum, unique
from logging import getLogger
from pathlib import Path
@@ -13,6 +14,7 @@
from pyomo.opt import SolverResults
from definitions import PROJECT_ROOT
+from temoa.extensions.monte_carlo.mc_run import ChangeRecord
from temoa.temoa_model import temoa_rules
from temoa.temoa_model.exchange_tech_cost_ledger import CostType, ExchangeTechCostLedger
from temoa.temoa_model.temoa_config import TemoaConfig
@@ -67,13 +69,12 @@
'OutputObjective',
'OutputRetiredCapacity',
]
-optional_output_tables = [
- 'OutputFlowOutSummary',
-]
+optional_output_tables = ['OutputFlowOutSummary', 'OutputMCDelta']
flow_summary_file_loc = Path(
PROJECT_ROOT, 'temoa/extensions/modeling_to_generate_alternatives/make_flow_summary_table.sql'
)
+mc_tweaks_file_loc = Path(PROJECT_ROOT, 'temoa/extensions/monte_carlo/make_deltas_table.sql')
def _marks(num: int) -> str:
@@ -144,7 +145,7 @@ def write_results(
if not append:
self.clear_scenario()
if not self.tech_sectors:
- self._get_tech_sectors()
+ self._set_tech_sectors()
self.write_objective(M, iteration=iteration)
self.write_capacity_tables(M, iteration=iteration)
# analyze the emissions to get the costs and flows
@@ -172,7 +173,7 @@ def write_mm_results(self, M: TemoaModel, iteration: int):
:return:
"""
if not self.tech_sectors:
- self._get_tech_sectors()
+ self._set_tech_sectors()
self.write_objective(M, iteration=iteration)
# analyze the emissions to get the costs and flows
e_costs, e_flows = self._gather_emission_costs_and_flows(M)
@@ -181,7 +182,26 @@ def write_mm_results(self, M: TemoaModel, iteration: int):
self.con.commit()
self.con.execute('VACUUM')
- def _get_tech_sectors(self):
+ def write_mc_results(self, M: TemoaModel, iteration: int):
+ """
+ tailored write function to capture appropriate monte carlo results
+ :param M: solve model
+ :param iteration: iteration number
+ :return:
+ """
+ if not self.tech_sectors:
+ self._set_tech_sectors()
+ # analyze the emissions to get the costs and flows
+ e_costs, e_flows = self._gather_emission_costs_and_flows(M)
+ self.emission_register = e_flows
+ self.write_emissions(iteration=iteration)
+ self.write_capacity_tables(M, iteration=iteration)
+ self.write_summary_flow(M, iteration=iteration)
+ self.write_objective(M, iteration=iteration)
+ self.con.commit()
+ self.con.execute('VACUUM')
+
+ def _set_tech_sectors(self):
"""pull the sector info and fill the mapping"""
qry = 'SELECT tech, sector FROM Technology'
data = self.con.execute(qry).fetchall()
@@ -191,8 +211,6 @@ def clear_scenario(self):
cur = self.con.cursor()
for table in basic_output_tables:
cur.execute(f'DELETE FROM {table} WHERE scenario = ?', (self.config.scenario,))
- for table in optional_output_tables:
- cur.execute(f'DROP TABLE IF EXISTS {table}')
self.con.commit()
self.clear_iterative_runs()
@@ -207,6 +225,12 @@ def clear_iterative_runs(self):
for table in basic_output_tables:
cur.execute(f'DELETE FROM {table} WHERE scenario like ?', (target,))
self.con.commit()
+ for table in optional_output_tables:
+ try:
+ cur.execute(f'DELETE FROM {table} WHERE scenario like ?', (target,))
+ except sqlite3.OperationalError:
+ pass
+ self.con.commit()
def write_objective(self, M: TemoaModel, iteration=None) -> None:
"""Write the value of all ACTIVE objectives to the DB"""
@@ -830,6 +854,25 @@ def write_dual_variables(self, results: SolverResults, iteration=None):
self.con.executemany(qry, dual_data)
self.con.commit()
+ # MONTE CARLO stuff
+
+ def write_tweaks(self, iteration: int, change_records: Iterable[ChangeRecord]):
+ scenario = f'{self.config.scenario}-{iteration}'
+ records = []
+ for change_record in change_records:
+ element = (
+ scenario,
+ iteration,
+ change_record.param_name,
+ str(change_record.param_index).replace("'", ''),
+ change_record.old_value,
+ change_record.new_value,
+ )
+ records.append(element)
+ qry = 'INSERT INTO OutputMCDelta VALUES (?, ?, ?, ?, ?, ?)'
+ self.con.executemany(qry, records)
+ self.con.commit()
+
def __del__(self):
if self.con:
self.con.close()
@@ -838,6 +881,10 @@ def make_summary_flow_table(self):
# make the additional output table, if needed...
self.execute_script(flow_summary_file_loc)
+ def make_mc_tweaks_table(self):
+ # make the table for monte carlo tweaks, if needed...
+ self.execute_script(mc_tweaks_file_loc)
+
def execute_script(self, script_file: str | Path):
"""
A utility to execute a sql script on the current db connection
diff --git a/temoa/temoa_model/temoa_config.py b/temoa/temoa_model/temoa_config.py
index d94e78f6..9a3583c0 100644
--- a/temoa/temoa_model/temoa_config.py
+++ b/temoa/temoa_model/temoa_config.py
@@ -50,6 +50,7 @@ def __init__(
SVMGA: dict | None = None,
myopic: dict | None = None,
morris: dict | None = None,
+ monte_carlo: dict | None = None,
config_file: Path | None = None,
silent: bool = False,
stream_output: bool = False,
@@ -122,6 +123,7 @@ def __init__(
self.svmga_inputs = SVMGA
self.myopic_inputs = myopic
self.morris_inputs = morris
+ self.monte_carlo_inputs = monte_carlo
self.silent = silent
self.stream_output = stream_output
self.price_check = price_check
diff --git a/temoa/temoa_model/temoa_mode.py b/temoa/temoa_model/temoa_mode.py
index efe29245..dcd510cf 100644
--- a/temoa/temoa_model/temoa_mode.py
+++ b/temoa/temoa_model/temoa_mode.py
@@ -41,3 +41,4 @@ class TemoaMode(Enum):
BUILD_ONLY = 5 # Just build the model, no solve
CHECK = 6 # build and run price check, source trace it
SVMGA = 7 # single-vector MGA
+ MONTE_CARLO = 8 # MC optimization
diff --git a/temoa/temoa_model/temoa_model.py b/temoa/temoa_model/temoa_model.py
index de1a593b..a602e3ab 100755
--- a/temoa/temoa_model/temoa_model.py
+++ b/temoa/temoa_model/temoa_model.py
@@ -40,6 +40,7 @@
region_group_check,
validate_Efficiency,
check_flex_curtail,
+ no_slash_or_pipe,
)
from temoa.temoa_model.temoa_initialize import *
from temoa.temoa_model.temoa_initialize import get_loan_life
@@ -130,8 +131,8 @@ def __init__(M, *args, **kwargs):
M.validate_time = BuildAction(rule=validate_time)
# Define the model time slices
- M.time_season = Set(ordered=True)
- M.time_of_day = Set(ordered=True)
+ M.time_season = Set(ordered=True, validate=no_slash_or_pipe)
+ M.time_of_day = Set(ordered=True, validate=no_slash_or_pipe)
# Define regions
M.regions = Set(validate=region_check)
@@ -143,7 +144,7 @@ def __init__(M, *args, **kwargs):
# Define technology-related sets
M.tech_resource = Set()
M.tech_production = Set()
- M.tech_all = Set(initialize=M.tech_resource | M.tech_production)
+ M.tech_all = Set(initialize=M.tech_resource | M.tech_production, validate=no_slash_or_pipe)
M.tech_baseload = Set(within=M.tech_all)
M.tech_annual = Set(within=M.tech_all)
# annual storage not supported in Storage constraint or TableWriter, so exclude from domain
@@ -181,7 +182,9 @@ def __init__(M, *args, **kwargs):
M.commodity_physical = Set()
M.commodity_source = Set(within=M.commodity_physical)
M.commodity_carrier = Set(initialize=M.commodity_physical | M.commodity_demand)
- M.commodity_all = Set(initialize=M.commodity_carrier | M.commodity_emissions)
+ M.commodity_all = Set(
+ initialize=M.commodity_carrier | M.commodity_emissions, validate=no_slash_or_pipe
+ )
# Define sets for MGA weighting
M.tech_mga = Set(within=M.tech_all)
diff --git a/temoa/temoa_model/temoa_sequencer.py b/temoa/temoa_model/temoa_sequencer.py
index c2666593..0a85197b 100644
--- a/temoa/temoa_model/temoa_sequencer.py
+++ b/temoa/temoa_model/temoa_sequencer.py
@@ -38,6 +38,7 @@
from temoa.extensions.method_of_morris.morris_sequencer import MorrisSequencer
from temoa.extensions.modeling_to_generate_alternatives.mga_sequencer import MgaSequencer
+from temoa.extensions.monte_carlo.mc_sequencer import MCSequencer
from temoa.extensions.myopic.myopic_sequencer import MyopicSequencer
from temoa.extensions.single_vector_mga.sv_mga_sequencer import SvMgaSequencer
from temoa.temoa_model.hybrid_loader import HybridLoader
@@ -163,12 +164,12 @@ def start(self) -> TemoaModel | None:
# override the "extras"
if self.config.source_trace:
self.config.source_trace = False
- logger.info('Source trace disabled for BUILD_ONLY')
+ logger.warning('Source trace disabled for BUILD_ONLY')
if self.config.plot_commodity_network:
self.config.plot_commodity_network = False
- logger.info('Plot commodity network disabled for BUILD_ONLY')
+ logger.warning('Plot commodity network disabled for BUILD_ONLY')
if self.config.price_check:
- logger.info('Price check disabled for BUILD_ONLY')
+ logger.warning('Price check disabled for BUILD_ONLY')
con = sqlite3.connect(self.config.input_database)
hybrid_loader = HybridLoader(db_connection=con, config=self.config)
data_portal = hybrid_loader.load_data_portal(myopic_index=None)
@@ -179,7 +180,7 @@ def start(self) -> TemoaModel | None:
case TemoaMode.CHECK:
con = sqlite3.connect(self.config.input_database)
if self.config.source_trace is False:
- logger.info('Source trace automatic for CHECK')
+ logger.warning('Source trace automatic for CHECK')
self.config.source_trace = True
hybrid_loader = HybridLoader(db_connection=con, config=self.config)
data_portal = hybrid_loader.load_data_portal(myopic_index=None)
@@ -191,7 +192,7 @@ def start(self) -> TemoaModel | None:
)
# disregard what the config says about price_check and source_trace and just do it...
if self.config.price_check is False:
- logger.info('Price check of model is automatic with CHECK')
+ logger.warning('Price check of model is automatic with CHECK')
good_prices = price_checker(instance)
if not good_prices and not self.config.silent:
print('\nWarning: Cost anomalies discovered. Check log file for details.')
@@ -234,7 +235,6 @@ def start(self) -> TemoaModel | None:
)
sys.exit(-1)
handle_results(self.pf_solved_instance, self.pf_results, self.config)
-
con.close()
case TemoaMode.MYOPIC:
@@ -243,6 +243,11 @@ def start(self) -> TemoaModel | None:
myopic_sequencer.start()
case TemoaMode.MGA:
+ if self.config.solver_name == 'appsi_highs':
+ raise ValueError(
+ 'Multiprocessing currently not working with HiGHS solver. '
+ 'Unknown fix...appears to be pyomo issue. Gurobi, CBC, Ipopt all work.'
+ )
mga_sequencer = MgaSequencer(config=self.config)
mga_sequencer.start()
@@ -254,5 +259,29 @@ def start(self) -> TemoaModel | None:
mm_sequencer = MorrisSequencer(config=self.config)
mm_sequencer.start()
+ case TemoaMode.MONTE_CARLO:
+ if self.config.solver_name == 'appsi_highs':
+ raise ValueError(
+ 'Multiprocessing currently not working with HiGHS solver. '
+ 'Unknown fix...appears to be pyomo issue. Gurobi, CBC, Ipopt all work.'
+ )
+ if self.config.plot_commodity_network:
+ self.config.plot_commodity_network = False
+ logger.warning('Plot commodity network disabled for MONTE_CARLO')
+ if self.config.price_check:
+ self.config.price_check = False
+ logger.warning('Price check disabled for MONTE_CARLO')
+ if self.config.save_excel:
+ self.config.save_excel = False
+ logger.warning('Save excel disabled for MONTE_CARLO')
+ if self.config.save_lp_file:
+ self.config.save_lp_file = False
+ logger.warning('Save lp file disabled for MONTE_CARLO')
+ if self.config.save_duals:
+ self.config.save_duals = False
+ logger.warning('Save duals disabled for MONTE_CARLO')
+ mc_sequencer = MCSequencer(config=self.config)
+ mc_sequencer.start()
+
case _:
raise NotImplementedError('not yet built')
diff --git a/temoa/utilities/db_migration_to_v3.py b/temoa/utilities/db_migration_to_v3.py
index 20a3a9c8..cac299ab 100644
--- a/temoa/utilities/db_migration_to_v3.py
+++ b/temoa/utilities/db_migration_to_v3.py
@@ -219,7 +219,7 @@
try:
entries = con_old.execute('SELECT * FROM tech_rps').fetchall()
except sqlite3.OperationalError:
- print(f'source does not appear to include RPS techs...skipping')
+ print('source does not appear to include RPS techs...skipping')
skip_rps = True
if not skip_rps:
for region, tech, notes in entries:
@@ -289,7 +289,7 @@
)
cur.execute('INSERT OR REPLACE INTO TechGroupMember VALUES (?, ?)', (new_name, tech))
except sqlite3.OperationalError:
- print(f'souce does not appear to employ tech_groups...skipping.')
+ print('souce does not appear to employ tech_groups...skipping.')
skip_tech_groups = True
if not skip_tech_groups:
# ------- FIX TABLES THAT USED TO USE tech_groups -----------
diff --git a/tests/test_mc_run.py b/tests/test_mc_run.py
new file mode 100644
index 00000000..69da7e44
--- /dev/null
+++ b/tests/test_mc_run.py
@@ -0,0 +1,73 @@
+"""
+Tools for Energy Model Optimization and Analysis (Temoa):
+An open source framework for energy systems optimization modeling
+
+Copyright (C) 2015, NC State University
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+A complete copy of the GNU General Public License v2 (GPLv2) is available
+in LICENSE.txt. Users uncompressing this from an archive may not have
+received this license file. If not, see .
+
+
+Written by: J. F. Hyink
+jeff@westernspark.us
+https://westernspark.us
+Created on: 11/9/24
+
+"""
+import pytest
+
+from temoa.extensions.monte_carlo.mc_run import TweakFactory, RowData
+
+
+@pytest.fixture(scope='module')
+def tweak_factory():
+ tweak_factory = TweakFactory(
+ data_store={'dog': {(1, 2): 3.0, (5, 6): 4.0}, 'cat': {('a', 'b'): 7.0, ('c', 'd'): 8.0}}
+ )
+ return tweak_factory
+
+
+good_params = [
+ ('1,dog,1|2,a,1.0,some good notes', RowData(1, 'dog', '1|2', 'a', 1.0, 'some good notes'), 1),
+ (
+ '1 , dog, 1|2 , a , 1.0,',
+ RowData(1, 'dog', '1|2', 'a', 1.0, ''),
+ 1,
+ ), # we should be able to strip lead/trail spaces
+ ('22,cat,c|d/e/f|9/10,r,2,', RowData(22, 'cat', 'c|d/e/f|9/10', 'r', 2.0, ''), 6),
+]
+fail_examples = [
+ ('z,dog,1|2,a,1.0,'), # has 'z' for run, non integer
+ ('1,dog,1||2,a,1.0,'), # has empty index location
+ ('2,dog,5|6,x,2.0,'), # has 'x' not in r/s/a
+ ('3,pig,4|5|7,r,2.0,'), # no pig in data source
+]
+ids = ['non-int run label', 'empty index', 'non r/s/a', 'no-match param']
+
+
+@pytest.mark.parametrize('row, expected,_', good_params, ids=range(len(good_params)))
+def test__row_parser(row, expected, _, tweak_factory):
+ assert tweak_factory.row_parser(0, row=row) == expected
+
+
+@pytest.mark.parametrize('row', fail_examples, ids=ids)
+def test__row_parser_fail(row, tweak_factory):
+ with pytest.raises(ValueError):
+ tweak_factory.row_parser(0, row=row)
+
+
+@pytest.mark.parametrize('row, _, num_tweaks', good_params, ids=range(len(good_params)))
+def test_make_tweaks(row, _, num_tweaks, tweak_factory):
+ _, tweaks = tweak_factory.make_tweaks(0, row=row)
+ assert len(tweaks) == num_tweaks
diff --git a/tests/test_region_indexer.py b/tests/test_validators.py
similarity index 90%
rename from tests/test_region_indexer.py
rename to tests/test_validators.py
index 20f96998..5c9b3a6e 100644
--- a/tests/test_region_indexer.py
+++ b/tests/test_validators.py
@@ -28,11 +28,13 @@
"""
import pyomo.environ as pyo
+import pytest
from temoa.temoa_model.model_checking.validators import (
linked_region_check,
region_check,
region_group_check,
+ no_slash_or_pipe,
)
@@ -93,3 +95,17 @@ def test_region_group_check():
assert region_group_check(m, name), f'This name should have been good: {name}'
for name in bad_names:
assert not region_group_check(m, name), f'This name should have failed: {name}'
+
+
+params = [
+ ('dogfood', True),
+ ('cat/dog', False),
+ ('cat|dog', False),
+ ('123/45', False),
+ (678, True),
+]
+
+
+@pytest.mark.parametrize('value, expected', params)
+def test_no_slash(value, expected):
+ assert no_slash_or_pipe(M=None, element=value) == expected