diff --git a/cemo/cluster.py b/cemo/cluster.py index df91ca7..f8dbe50 100644 --- a/cemo/cluster.py +++ b/cemo/cluster.py @@ -42,12 +42,12 @@ def __init__(self, firstdow=4, lastdow=3, max_d=12, - regions=[1, 2, 3, 4, 5], + regions=None, maxsynth=False): self.firstdow = firstdow # Day of week starting period self.lastdow = lastdow # Day of week ending period` self.max_d = max_d # Maximum number of clusters - self.regions = regions # NEM region tuple + self.regions = range(1, 6) if regions is None else regions # NEM region tuple self.maxsynth = maxsynth # make week pattern into a list @@ -113,13 +113,13 @@ def _get_region_data(self, region): return X1 - def _calc_Xsynth(self, max=False): + def _calc_Xsynth(self, max_p=False): # empty array of synthetic individual in each cluster self.Xsynth = np.empty((self.max_d, self.nplen)) for k in range(self.max_d): # calculate synthetic individual per clusters - if max: + if max_p: self.Xsynth[k] = self.Xclus[k].max(axis=0)[:self.nplen] else: self.Xsynth[k] = self.Xclus[k].mean(axis=0)[:self.nplen] @@ -140,7 +140,7 @@ def clusterset(self, max_d, method='average', metric='cityblock'): self.Xclus[self.cluster[j] - 1] \ = np.row_stack((self.Xclus[self.cluster[j] - 1], X2[j, :])) # genereate Xsynth (by default is the max of all features in cluster) - self._calc_Xsynth(max=self.maxsynth) + self._calc_Xsynth(max_p=self.maxsynth) # Obtain the date index for the observation in each cluster # closest to their respective cluster mean diff --git a/cemo/const.py b/cemo/const.py index e5aa578..016122c 100644 --- a/cemo/const.py +++ b/cemo/const.py @@ -186,7 +186,7 @@ 11: 20975.96, 12: 10487.98, 16: 52439.9, - 19: 52439.9 # FIXME Sould this be 10k like for gas? + 19: 52439.9 # FIXME Should this be 10k like for gas? } DEFAULT_TECH_LIFETIME = { @@ -225,8 +225,7 @@ 17: 0, }, 5: { - 11: 4000 + 3000 + 1000 + - 29.9, # Brkn hill + 1/2 of Murray R + 1/2 of Riverland + existing + 11: 4000 + 3000 + 1000 + 29.9, # Brkn hill + 1/2 of Murray R + 1/2 of Riverland + existing 12: 1870 + 1620 + 232.5 + 0, 17: 620 + 527.5 + 77.5 + 199, # }, diff --git a/cemo/initialisers.py b/cemo/initialisers.py index 7422808..3d6386b 100644 --- a/cemo/initialisers.py +++ b/cemo/initialisers.py @@ -13,6 +13,7 @@ def init_year_correction_factor(model): + #pylint: disable=unused-argument '''Calculate factor to adjust dispatch periods different to 8760 hours''' ystr = model.t.last() year = int(ystr[:4]) @@ -23,6 +24,7 @@ def init_year_correction_factor(model): def init_zones_in_regions(model): + #pylint: disable=unused-argument '''Return zones in region tuples for declared regions''' for i in cemo.const.ZONES_IN_REGIONS: if i[0] in model.regions and i[1] in model.zones: @@ -30,6 +32,7 @@ def init_zones_in_regions(model): def init_region_intercons(model): + #pylint: disable=unused-argument '''Return regional interconnectors for declared regions''' for i in cemo.const.REGION_INTERCONS: if i[0] in model.regions and i[1] in model.regions: @@ -37,67 +40,93 @@ def init_region_intercons(model): def init_stor_rt_eff(model, tech): + #pylint: disable=unused-argument '''Default return efficiency for storage techs''' return cemo.const.DEFAULT_STOR_PROPS["rt_eff"].get(tech, 0) def init_stor_charge_hours(model, tech): + #pylint: disable=unused-argument '''Default charge hours for storage tech''' return cemo.const.DEFAULT_STOR_PROPS["charge_hours"].get(tech, 0) def init_hyb_col_mult(model, tech): + #pylint: disable=unused-argument '''Default collector multiple for hybrid tech''' return cemo.const.DEFAULT_HYB_PROPS["col_mult"].get(tech, 0) def init_hyb_charge_hours(model, tech): + #pylint: disable=unused-argument '''Default charge hours for hybrid tech''' return cemo.const.DEFAULT_HYB_PROPS["charge_hours"].get(tech, 0) -def init_intercon_prop_factor(m, source, dest): +def init_intercon_prop_factor(model, source, dest): + #pylint: disable=unused-argument '''Initialise interconnector proportioning factors''' return cemo.const.INTERCON_PROP_FACTOR.get(source).get(dest, 0) -def init_intercon_trans_limit(m, source, dest): +def init_intercon_trans_limit(model, source, dest): + #pylint: disable=unused-argument + '''Initialise interconecto transmission limits''' return cemo.const.INTERCON_TRANS_LIMIT.get(source).get(dest) def init_default_fuel_price(model, zone, tech): + #pylint: disable=unused-argument + '''Assign default price across zone and technologies''' return cemo.const.DEFAULT_FUEL_PRICE.get(tech, 100.0) def init_default_heat_rate(model, zone, tech): + #pylint: disable=unused-argument + '''Initialise default heat rate for fuel based generators in each zone''' return cemo.const.DEFAULT_HEAT_RATE.get(tech, 15.0) def init_default_fuel_emit_rate(model, tech): + #pylint: disable=unused-argument + '''Default fuel emission rate for fuel based generators''' return cemo.const.DEFAULT_FUEL_EMIT_RATE.get(tech, 800) def init_cost_retire(model, tech): + #pylint: disable=unused-argument + '''Default retirement/rehabilitation cost in $/MW per technology''' return cemo.const.DEFAULT_RETIREMENT_COST.get(tech, 60000.0) def init_default_lifetime(model, tech): + #pylint: disable=unused-argument + '''Default lifetime for technologies''' return cemo.const.DEFAULT_TECH_LIFETIME.get(tech, 30.0) def init_gen_build_limit(model, zone, tech): + #pylint: disable=unused-argument + ''' Default build limits per technology and per zone''' return cemo.const.DEFAULT_BUILD_LIMIT.get(zone).get(tech, 100000) def init_fcr(model, tech): + #pylint: disable=unused-argument + '''Calculate fixed charge rate for each technology''' return model.all_tech_discount_rate / ( (model.all_tech_discount_rate + 1)**model.all_tech_lifetime[tech] - 1) + model.all_tech_discount_rate def init_cap_factor(model, zone, tech, time): + #pylint: disable=unused-argument + '''Default capacity factor per hour per technology and per zone. + Note:Default to zero means technology does not generate''' return cemo.const.GEN_CAP_FACTOR.get(tech, 0) def init_max_hydro(model, zone): + #pylint: disable=unused-argument + '''Default maximum hydro generation per year in each zone''' return cemo.const.DEFAULT_HYDRO_MWH_MAX.get(zone, 0) diff --git a/cemo/jsonify.py b/cemo/jsonify.py index be070fc..4eebc3c 100755 --- a/cemo/jsonify.py +++ b/cemo/jsonify.py @@ -138,10 +138,10 @@ def jsonify(inst): def jsoninit(inst): '''Produce JSON output sufficient to initialise a cemo model''' - input = jsonify(inst) + inp = jsonify(inst) out = {} - out.update(input['sets']) - out.update(input['params']) + out.update(inp['sets']) + out.update(inp['params']) del out['zones_per_region'] del out['gen_tech_per_zone'] diff --git a/cemo/multi.py b/cemo/multi.py index 0dc04cb..3712f3a 100644 --- a/cemo/multi.py +++ b/cemo/multi.py @@ -49,7 +49,7 @@ def roundup(cap): Catching small negative numbers due to solver numerical tolerance. Let big negative numners pass to raise exception. ''' - if cap > -1e-6 and cap < 0: + if -1e-6 < cap < 0: return 0 return round(cap, 2) @@ -119,12 +119,12 @@ def __init__(self, cfgfile, solver='cbc', log=False, tmpdir=tempfile.mkdtemp() + self.nem_re_disp_ratio = json.loads(Scenario['nem_re_disp_ratio']) # Keep track of policy options to configure model instances down the line self.model_options = { - 'nem_ret_ratio': (True if self.nem_ret_ratio is not None else False), - 'nem_ret_gwh': (True if self.nem_ret_gwh is not None else False), - 'region_ret_ratio': (True if self.region_ret_ratio is not None else False), - 'emitlimit': (True if self.emitlimit is not None else False), - 'nem_disp_ratio': (True if self.nem_disp_ratio is not None else False), - 'nem_re_disp_ratio': (True if self.nem_re_disp_ratio is not None else False), + 'nem_ret_ratio': self.nem_ret_ratio is not None, + 'nem_ret_gwh': self.nem_ret_gwh is not None, + 'region_ret_ratio': self.region_ret_ratio is not None, + 'emitlimit': self.emitlimit is not None, + 'nem_disp_ratio': self.nem_disp_ratio is not None, + 'nem_re_disp_ratio': self.nem_re_disp_ratio is not None } self.discountrate = Scenario['discountrate'] @@ -339,7 +339,7 @@ def exogenous_capacity(self, a): def tracetechs(self): # TODO refactor this and how tech sets populate template self.fueltech = {} - self.committech ={} + self.committech = {} self.regentech = {} self.dispgentech = {} self.redispgentech = {} @@ -496,7 +496,12 @@ def produce_exogenous_capacity(self, year): exogenous_capacity += '#Exogenous capacity entry ' + key + '\n' exogenous_capacity += 'param ' + key + ':=\n' exogenous_capacity += cap[['zone', 'tech', 'value'] - ].to_string(header=False, index=False) + ].to_string(header=False, index=False, + formatters={ + 'zone': lambda x: '%i' % x, + 'tech': lambda x: '%i' % x, + 'value': lambda x: '%10.2f' % x, + }) exogenous_capacity += '\n;\n' return exogenous_capacity diff --git a/cemo/rules.py b/cemo/rules.py index 6e87b0f..7ca646e 100644 --- a/cemo/rules.py +++ b/cemo/rules.py @@ -338,16 +338,15 @@ def con_opcap(model, z, n): # z and n come both from TechinZones return model.gen_cap_op[z, n] == model.gen_cap_initial[z, n] \ + (model.gen_cap_exo[z, n] - model.gen_cap_exo_neg[z, n]) - else: - if n in model.retire_gen_tech: - return model.gen_cap_op[z, n] == model.gen_cap_initial[z, n] \ - + (model.gen_cap_exo[z, n] - model.gen_cap_exo_neg[z, n])\ - + model.gen_cap_new[z, n]\ - - model.gen_cap_ret[z, n] - \ - (model.ret_gen_cap_exo[z, n] - model.gen_cap_ret_neg[z, n]) - return model.gen_cap_op[z, n] == model.gen_cap_initial[z, n]\ + if n in model.retire_gen_tech: + return model.gen_cap_op[z, n] == model.gen_cap_initial[z, n] \ + (model.gen_cap_exo[z, n] - model.gen_cap_exo_neg[z, n])\ - + model.gen_cap_new[z, n] + + model.gen_cap_new[z, n]\ + - model.gen_cap_ret[z, n] - \ + (model.ret_gen_cap_exo[z, n] - model.gen_cap_ret_neg[z, n]) + return model.gen_cap_op[z, n] == model.gen_cap_initial[z, n]\ + + (model.gen_cap_exo[z, n] - model.gen_cap_exo_neg[z, n])\ + + model.gen_cap_new[z, n] def con_stcap(model, z, s): # z and n come both from TechinZones diff --git a/cemo/utils.py b/cemo/utils.py index 2421afe..f7c825f 100644 --- a/cemo/utils.py +++ b/cemo/utils.py @@ -48,7 +48,7 @@ def _techsinregion(instance, region): # pragma: no cover techsinregion = techsinregion | instance.gen_tech_per_zone[z]() techsinregion = techsinregion | instance.hyb_tech_per_zone[z]() techsinregion = techsinregion | instance.stor_tech_per_zone[z]() - return sorted(techsinregion, key=lambda x: cemo.const.DISPLAY_ORDER.index(x)) + return sorted(techsinregion, key=cemo.const.DISPLAY_ORDER.index) def palette(instance, techsinregion): # pragma: no cover @@ -211,7 +211,7 @@ def _printemissionrate(instance): emrate = sum(value(cemo.rules.emissions(instance, r)) for r in instance.regions) /\ (sum(value(cemo.rules.dispatch(instance, r)) for r in instance.regions)+1.0e-12) - print("Total Emission rate: %s kg/MWh" % str(emrate)) + print("Total Emission rate: %6.3f kg/MWh" % emrate) def _printunserved(instance): @@ -281,11 +281,11 @@ def printstats(instance): print("End of results for %s" % instance.name, flush=True) -def plotcluster(cluster, row=3, col=4, ylim=[5500, 16000]): # pragma: no cover +def plotcluster(cluster, row=3, col=4, ylim=None): # pragma: no cover # Plot cluster result from full set of weeks, cluster weeks and weights t = range(1, cluster.nplen + 1) # Make row * col subplots - f, axarr = plt.subplots(row, col, sharex=True) + axarr = plt.subplots(row, col, sharex=True)[1] # Plot each observation in their respective cluster plot for i in range(cluster.periods): axarr.flat[cluster.cluster[i] - @@ -302,6 +302,10 @@ def plotcluster(cluster, row=3, col=4, ylim=[5500, 16000]): # pragma: no cover marker='+') # closest observation # make yrange the same in all plots for ax in axarr.flat: - ax.set_ylim(ylim[0], ylim[1]) + if ylim is None: + # default + ax.set_ylim(5500, 16000) + else: + ax.set_ylim(ylim[0], ylim[1]) # Show results plt.show(figsize=(14, 9)) diff --git a/msolve.py b/msolve.py index d4c1327..014adb5 100755 --- a/msolve.py +++ b/msolve.py @@ -18,7 +18,7 @@ def valid_file(param): - base, ext = os.path.splitext(param) + ext = os.path.splitext(param)[1] if ext not in ('.cfg'): raise argparse.ArgumentTypeError('File must have a cfg extension') return param @@ -35,7 +35,7 @@ def valid_file(param): "config", help="Specify a configuration file for simulation" + " Note: Python configuration files named CONFIG.cfg", - type=lambda f: valid_file(f), + type=valid_file, metavar='CONFIG') # Obtain a solver name from command line, default cbc parser.add_argument( @@ -55,8 +55,7 @@ def valid_file(param): parser.add_argument( "-k", "--keepfiles", - help= - "Save generated files onto a folder with the same name as the configuration file", + help="Save generated files onto a folder with the same name as the configuration file", action='store_true') # parse arguments into args structure diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fc3ba12 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,23 @@ +appdirs==1.4.3 +atomicwrites==1.3.0 +attrs==19.1.0 +cycler==0.10.0 +kiwisolver==1.0.1 +matplotlib==3.0.3 +more-itertools==7.0.0 +nose==1.3.7 +numpy==1.16.2 +pandas==0.24.2 +pluggy==0.9.0 +ply==3.11 +py==1.8.0 +PyMySQL==0.9.3 +Pyomo==5.6.1 +pyparsing==2.3.1 +pytest==4.4.0 +python-dateutil==2.8.0 +pytz==2018.9 +PyUtilib==5.6.5 +scipy==1.2.1 +si-prefix==1.2.2 +six==1.12.0 diff --git a/ssolve.py b/ssolve.py index b3246db..b81e268 100755 --- a/ssolve.py +++ b/ssolve.py @@ -95,11 +95,7 @@ def check_arg(config_file, parameter): region_ret_ratio=check_arg(MODEL_NAME, 'region_ret_ratio'), nem_re_disp_ratio=check_arg(MODEL_NAME, 'nem_re_disp_ratio')) # create a specific instance using file modelName.dat -try: - INSTANCE = MODEL.create_instance(MODEL_NAME + '.dat') -except Exception as ex: - print("openCEM solve.py: ", ex) - sys.exit(1) # exit gracefully if file does not exist +INSTANCE = MODEL.create_instance(MODEL_NAME + '.dat') # Produce only a debugging printout of model and then exit if ARGS.printonly: diff --git a/tests/Sim2020.dat b/tests/Sim2020.dat index d7410ae..fc7e661 100644 --- a/tests/Sim2020.dat +++ b/tests/Sim2020.dat @@ -690,7 +690,7 @@ order BY regions,t;": [regions,t] region_net_demand; #Custom cost entry for cost_stor_build param cost_stor_build:= 13 14 999999.00 -5 21 2000000.00 + 5 21 2000000.00 ; #Custom cost entry for cost_gen_fom param cost_gen_fom:= diff --git a/tests/Sim2025.dat b/tests/Sim2025.dat index c2705ae..880a491 100644 --- a/tests/Sim2025.dat +++ b/tests/Sim2025.dat @@ -572,7 +572,7 @@ FROM opex WHERE (source_id, technology_type_id) in FROM opex where technology_type_id in (13) GROUP BY technology_type_id);": [all_tech] cost_hyb_fom cost_hyb_vom; #Starting capacity (either cfrom capacity table or carry forward from previous) -load '/tmp/tmpy2njl9hu/gen_cap_op2020.json' : [zones,all_tech] gen_cap_initial stor_cap_initial hyb_cap_initial; +load 'gen_cap_op2020.json' : [zones,all_tech] gen_cap_initial stor_cap_initial hyb_cap_initial; # Exogenously commissioned capacity load "opencem.ckvu5hxg6w5z.ap-southeast-1.rds.amazonaws.com" database=opencem_input @@ -665,7 +665,7 @@ param cost_gen_build:= #Custom cost entry for cost_stor_build param cost_stor_build:= 13 14 888888.00 -5 21 2000000.00 + 5 21 2000000.00 ; #Custom cost entry for cost_gen_fom param cost_gen_fom:= @@ -688,15 +688,15 @@ param cost_stor_vom:= #Exogenous capacity entry gen_cap_exo param gen_cap_exo:= -8 12 500 +8 12 500.00 ; #Exogenous capacity entry stor_cap_exo param stor_cap_exo:= -5 21 2000 +5 21 2000.00 ; #Exogenous capacity entry ret_gen_cap_exo param ret_gen_cap_exo:= -8 4 600 +8 4 600.00 ; #Discount rate for project @@ -704,7 +704,7 @@ param all_tech_discount_rate := 0.06; #Cost of emissions $/Mhw param cost_emit:= 0.023; #Carry forward annualised capital costs -load '/tmp/tmpy2njl9hu/gen_cap_op2020.json' : cost_cap_carry_forward; +load 'gen_cap_op2020.json' : cost_cap_carry_forward; # NEM wide RET param nem_ret_ratio :=0.2; diff --git a/tests/stats.txt b/tests/stats.txt index e7d8847..ae90e8e 100644 --- a/tests/stats.txt +++ b/tests/stats.txt @@ -19,5 +19,5 @@ Unserved cost: $0.00 Emission cost: $0.00 Retirmt cost: $0.00 Unserved %:[0. 0. 0. 0. 0.] -Total Emission rate: 516.5884733858522 kg/MWh +Total Emission rate: 516.588 kg/MWh End of results for CTV_trans diff --git a/tests/test_base.py b/tests/test_base.py index 1f30bf9..aa4f59b 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,12 +1,14 @@ -# openCEM tests for basic instantiation +"""openCEM tests for basic instantiation""" def test_model_creation(model, benchmark): + """Assert model is constructed matches benchmark""" assert model.name == benchmark.name, "Test model name does not match saved data" - assert model.is_constructed() is False + assert model.is_constructed() is False, "Model is constructed" def test_model_instantiation(instance, benchmark): + """Assert that instance is constructed from model and matches benchmark""" assert instance.name == benchmark.name, "Instance name does not match" assert instance.is_constructed() is True assert instance.zones.data() == benchmark.zones.data(), "Mismatch in test regions" diff --git a/tests/test_base_solution.py b/tests/test_base_solution.py index a0d9654..7c57e5a 100644 --- a/tests/test_base_solution.py +++ b/tests/test_base_solution.py @@ -48,7 +48,7 @@ def test_storage(solution, benchmark, zone, time, tech): @pytest.mark.parametrize("tech", [13]) def test_hybrid(solution, benchmark, zone, time, tech): assert value(solution.hyb_disp[zone, tech, time]) \ - == pytest.approx(value(benchmark.hyb_disp[zone, tech, time]),abs=1e-11) + == pytest.approx(value(benchmark.hyb_disp[zone, tech, time]), abs=1e-11) @pytest.mark.parametrize("tfrom,tto,time", [ diff --git a/tests/test_jsonify.py b/tests/test_jsonify.py index 25b923b..b4f436d 100644 --- a/tests/test_jsonify.py +++ b/tests/test_jsonify.py @@ -1,12 +1,29 @@ +'''Test suite for jsonify module''' + import json from cemo.jsonify import jsoninit +def sort_func(entry): + '''Sort list of dictionaries by index''' + if not isinstance(entry, int): + if isinstance(entry, dict): + return entry['index'] + return entry + + +def dumped_data(entry): + '''create sorted dumps of dictionary''' + if not isinstance(entry, (int, float, str)): + return json.dumps(sorted(entry, key=sort_func)) + return json.dumps(entry) + + def test_json_init(solution): + '''Assert that generated jsoninit of solution matches known output''' data = jsoninit(solution) - # with open('tests/jsoninit_test.json', 'w') as f: - # json.dump(data, f, indent=2) - with open('tests/jsoninit_test.json', 'r') as f1: - data2 = json.load(f1) - assert json.dumps(data) == json.dumps(data2) + with open('tests/jsoninit_test.json', 'r') as known: + data2 = json.load(known) + for key in data: + assert dumped_data(data[key]) == dumped_data(data2[key]) diff --git a/tests/test_multi.py b/tests/test_multi.py index 1e1a001..71dc635 100644 --- a/tests/test_multi.py +++ b/tests/test_multi.py @@ -1,4 +1,4 @@ -# Multi year simulation unit tests +'''Unit test suite for multi.py module (multi year simulations)''' import filecmp import json import tempfile @@ -15,30 +15,30 @@ def test_multi_conf_file_not_found(): SolveTemplate(cfgfile='Nofile.cfg') -@pytest.mark.parametrize("option,value", [ - ('nem_ret_ratio', 'nem_ret_ratio = [0.1, 1.2, 0.3, 0.4, 0.5, 0.6]'), - ('nem_ret_gwh', 'nem_ret_gwh = [45222, 150000, -12, a, 14]'), - ('region_ret_ratio', 'region_ret_ratio = [[1, [0.1, 1.2, 0.3, 0.4, 0.5, 0.6]]]'), - ('region_ret_ratio', 'region_ret_ratio = [[2, [ 0.2, 0.3, 0.4, 0.5, 0.6]]]'), - ('emitlimit', 'emitlimit = [-100, 100, 100, 100, 100, 100]'), - ('Years', 'Years=[2020,2022,2071]'), - ('discountrate', 'discountrate=1.1'), - ('cost_emit', 'cost_emit = [-11,2,3,4,5,6,8]'), - ('nem_disp_ratio', 'nem_disp_ratio=[0,0,0,2,0,0,0]'), - ('nem_re_disp_ratio', 'nem_re_disp_ratio=[0,0,0,0,0,0]') -] -) +@pytest.mark.parametrize( + "option,value", + [('nem_ret_ratio', 'nem_ret_ratio = [0.1, 1.2, 0.3, 0.4, 0.5, 0.6]'), + ('nem_ret_gwh', 'nem_ret_gwh = [45222, 150000, -12, a, 14]'), + ('region_ret_ratio', + 'region_ret_ratio = [[1, [0.1, 1.2, 0.3, 0.4, 0.5, 0.6]]]'), + ('region_ret_ratio', + 'region_ret_ratio = [[2, [ 0.2, 0.3, 0.4, 0.5, 0.6]]]'), + ('emitlimit', 'emitlimit = [-100, 100, 100, 100, 100, 100]'), + ('Years', 'Years=[2020,2022,2071]'), ('discountrate', 'discountrate=1.1'), + ('cost_emit', 'cost_emit = [-11,2,3,4,5,6,8]'), + ('nem_disp_ratio', 'nem_disp_ratio=[0,0,0,2,0,0,0]'), + ('nem_re_disp_ratio', 'nem_re_disp_ratio=[0,0,0,0,0,0]')]) def test_multi_bad_cfg(option, value): - ''' Assert validate bad config option''' - fp = tempfile.NamedTemporaryFile() - with open('tests/Sample.cfg') as fin: - with open(fp.name, 'w') as fo: - for line in fin: + ''' Assert validate bad config option by replacing known bad options in sample file''' + temp_file = tempfile.NamedTemporaryFile() + with open('tests/Sample.cfg') as sample: + with open(temp_file.name, 'w') as temp_sample: + for line in sample: if option in line: line = value + '\n' - fo.write(line) + temp_sample.write(line) with pytest.raises(ValueError): - SolveTemplate(cfgfile=fp.name) + SolveTemplate(cfgfile=temp_file.name) @pytest.mark.parametrize("option,value", [ @@ -47,42 +47,42 @@ def test_multi_bad_cfg(option, value): ('Template', 'Template=Badfile.dat'), ]) def test_multi_bad_file(option, value): - ''' Assert validate bad config option''' - fp = tempfile.NamedTemporaryFile() - with open('tests/Sample.cfg') as fin: - with open(fp.name, 'w') as fo: - for line in fin: + ''' Assert that multi detects missing files in config''' + temp_file = tempfile.NamedTemporaryFile() + with open('tests/Sample.cfg') as sample: + with open(temp_file.name, 'w') as temp_sample: + for line in sample: if option in line: line = value + '\n' - fo.write(line) + temp_sample.write(line) with pytest.raises(OSError): - SolveTemplate(cfgfile=fp.name) + SolveTemplate(cfgfile=temp_file.name) def test_multi_template_first(): '''Tests generate first year template by comparing to known good result''' - X = SolveTemplate(cfgfile='tests/Sample.cfg') - X.generateyeartemplate(X.Years[0], test=True) - assert filecmp.cmp(X.tmpdir + 'Sim2020.dat', 'tests/Sim2020.dat') + multi_sim = SolveTemplate(cfgfile='tests/Sample.cfg') + multi_sim.generateyeartemplate(multi_sim.Years[0], test=True) + assert filecmp.cmp(multi_sim.tmpdir + 'Sim2020.dat', 'tests/Sim2020.dat') def test_multi_template_second(): '''Tests generate second (and later) year template by comparing to known good result''' - X = SolveTemplate(cfgfile='tests/Sample.cfg') - X.generateyeartemplate(X.Years[1], test=True) - with open(X.tmpdir + 'Sim2025.dat') as ff: - fromfile = ff.readlines() - with open('tests/Sim2025.dat') as tf: - tofile = tf.readlines() + multi_sim = SolveTemplate(cfgfile='tests/Sample.cfg') + multi_sim.generateyeartemplate(multi_sim.Years[1], test=True) + with open(multi_sim.tmpdir + 'Sim2025.dat') as generated_template: + genfile = generated_template.readlines() + with open('tests/Sim2025.dat') as test_template: + testfile = test_template.readlines() # Check that they are mostly the same, but for tempdir entries - d = SequenceMatcher(None, fromfile, tofile) - assert d.ratio() >= 0.99657534 + sequence = SequenceMatcher(None, genfile, testfile) + assert sequence.ratio() >= 0.99657534 def test_multi_metadata(): '''Tests generate year template by comparing to known good result''' - X = SolveTemplate(cfgfile='tests/Sample.cfg') - meta = X.generate_metadata() - with open('tests/metadata.json', 'r') as f: - metad = json.load(f) + multi_sim = SolveTemplate(cfgfile='tests/Sample.cfg') + meta = multi_sim.generate_metadata() + with open('tests/metadata.json', 'r') as test_meta: + metad = json.load(test_meta) assert json.dumps(meta, indent=2) == json.dumps(metad, indent=2) diff --git a/tests/test_rules.py b/tests/test_rules.py index 8f5cad5..65c3345 100644 --- a/tests/test_rules.py +++ b/tests/test_rules.py @@ -1,3 +1,5 @@ +'''Test suite to check model rules''' + import pytest from pyomo.environ import value @@ -11,6 +13,7 @@ (16, 12), ]) def test_con_maxcap(solution, zone, tech): + '''Assert solution obeys maximum capacity constraints''' assert pytest.approx(value(con_maxcap(solution, zone, tech))) @@ -25,6 +28,7 @@ def test_con_maxcap(solution, zone, tech): '2020-01-01 21:00:00', ]) def test_con_caplim(solution, zone, tech, time): + '''Assert solution obeys capacity factor limitation constraint''' assert pytest.approx(value(con_caplim(solution, zone, tech, time))) @@ -36,9 +40,11 @@ def test_con_caplim(solution, zone, tech, time): (16, 12) ]) def test_con_opcap(solution, zone, tech): + '''Assert solution computes same operating capacity as benchmark''' assert pytest.approx(value(con_opcap(solution, zone, tech))) @pytest.mark.parametrize("region", [4, 5]) def test_dispatch(solution, region): + '''Assert solution dispatch matches benchmark''' assert pytest.approx(value(dispatch(solution, region))) diff --git a/tests/test_utils.py b/tests/test_utils.py index 6007a76..ef3f17d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -4,8 +4,6 @@ def test_printstats(solution, capfd): printstats(solution) captured = capfd.readouterr() - with open('tests/stats.txt', 'r') as ins: - array = '' - for line in ins: - array += line + with open('tests/stats.txt', 'r') as f: + array = f.read() assert captured.out == array