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