def test_allowed_time_varying_constraints(self): """ `file=` is only allowed on a hardcoded list of constraints, unless `_time_varying` is appended to the constraint (i.e. user input) """ allowed_constraints_no_file = list( set(defaults_model.tech_groups.storage.allowed_constraints) .difference(defaults.file_allowed) ) allowed_constraints_file = list( set(defaults_model.tech_groups.storage.allowed_constraints) .intersection(defaults.file_allowed) ) override = lambda param: AttrDict.from_yaml_string( "techs.test_storage.constraints.{}: file=binary_one_day.csv".format(param) ) # should fail: Cannot have `file=` on the following constraints for param in allowed_constraints_no_file: with pytest.raises(exceptions.ModelError) as errors: build_model(override_dict=override(param), scenario='simple_storage,one_day') assert check_error_or_warning( errors, 'Cannot load `{}` from file for configuration'.format(param) ) # should pass: can have `file=` on the following constraints for param in allowed_constraints_file: build_model(override_dict=override(param), scenario='simple_storage,one_day')
def test_incorrect_subset_time(self): """ If subset_time is a list, it must have two entries (start_time, end_time) If subset_time is not a list, it should successfully subset on the given string/integer """ override = lambda param: AttrDict.from_yaml_string( "model.subset_time: {}".format(param) ) # should fail: one string in list with pytest.raises(exceptions.ModelError): build_model(override_dict=override(['2005-01']), scenario='simple_supply') # should fail: three strings in list with pytest.raises(exceptions.ModelError): build_model(override_dict=override(['2005-01-01', '2005-01-02', '2005-01-03']), scenario='simple_supply') # should pass: two string in list as slice model = build_model(override_dict=override(['2005-01-01', '2005-01-07']), scenario='simple_supply') assert all(model.inputs.timesteps.to_index() == pd.date_range('2005-01', '2005-01-07 23:00:00', freq='H')) # should pass: one integer/string model = build_model(override_dict=override('2005-01'), scenario='simple_supply') assert all(model.inputs.timesteps.to_index() == pd.date_range('2005-01', '2005-01-31 23:00:00', freq='H')) # should fail: time subset out of range of input data with pytest.raises(KeyError): build_model(override_dict=override('2005-03'), scenario='simple_supply') # should fail: time subset out of range of input data with pytest.raises(exceptions.ModelError): build_model(override_dict=override(['2005-02-01', '2005-02-05']), scenario='simple_supply')
def test_resource_as_carrier(self): """ No carrier in technology or technology group can be called `resource` """ override1 = AttrDict.from_yaml_string( """ techs: test_supply_elec: essentials: name: Supply tech carrier: resource parent: supply """ ) with pytest.raises(exceptions.ModelError): build_model(override_dict=override1, scenario='simple_supply,one_day') override2 = AttrDict.from_yaml_string( """ tech_groups: test_supply_group: essentials: name: Supply tech carrier: resource parent: supply techs.test_supply_elec.essentials.parent: test_supply_group """ ) with pytest.raises(exceptions.ModelError): build_model(override_dict=override2, scenario='simple_supply,one_day')
def test_unrecognised_model_run_keys(self): """ Check that the only keys allowed in 'model' and 'run' are those in the model defaults """ override1 = {'model.nonsensical_key': 'random_string'} with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override1, scenario='simple_supply') assert check_error_or_warning( excinfo, 'Unrecognised setting in model configuration: nonsensical_key' ) override2 = {'run.nonsensical_key': 'random_string'} with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override2, scenario='simple_supply') assert check_error_or_warning( excinfo, 'Unrecognised setting in run configuration: nonsensical_key' ) # A key that should be in run but is given in model override3 = {'model.solver': 'glpk'} with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override3, scenario='simple_supply') assert check_error_or_warning( excinfo, 'Unrecognised setting in model configuration: solver' ) # A key that should be in model but is given in run override4 = {'run.subset_time': None} with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override4, scenario='simple_supply') assert check_error_or_warning( excinfo, 'Unrecognised setting in run configuration: subset_time' ) override5 = { 'run.objective': 'minmax_cost_optimization', 'run.objective_options': { 'cost_class': 'monetary', 'sense': 'minimize', 'unused_option': 'some_value' } } with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override5, scenario='simple_supply') assert check_error_or_warning( excinfo, 'Objective function argument `unused_option` given but not used by objective function `minmax_cost_optimization`' )
def test_model_version_mismatch(self): """ Model config says model.calliope_version = 0.1, which is not what we are running, so we want a warning. """ override = {'model.calliope_version': 0.1} with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override, scenario='simple_supply,one_day') assert check_error_or_warning(excinfo, 'Model configuration specifies calliope_version')
def test_positive_demand(self): """ Resource for demand must be negative """ override = { 'techs.test_demand_elec.constraints.resource': 'file=demand_elec_positive.csv', } with pytest.raises(exceptions.ModelError): build_model(override_dict=override, scenario='simple_supply,one_day')
def test_key_clash_on_set_loc_key(self): """ Raise error on attempted overwrite of information regarding a recently exploded location """ override = { 'locations.0.test_supply_elec.constraints.resource': 10, 'locations.0,1.test_supply_elec.constraints.resource': 15 } with pytest.raises(KeyError): build_model(override_dict=override, scenario='simple_supply,one_day')
def test_unrecognised_config_keys(self): """ Check that the only top level keys can be 'model', 'run', 'locations', 'techs', 'tech_groups' (+ 'config_path', but that is an internal addition) """ override = {'nonsensical_key': 'random_string'} with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override, scenario='simple_supply') assert check_error_or_warning( excinfo, 'Unrecognised top-level configuration item: nonsensical_key' )
def test_exporting_unspecified_carrier(self): """ User can only define an export carrier if it is defined in ['carrier_out', 'carrier_out_2', 'carrier_out_3'] """ override_supply = lambda param: AttrDict.from_yaml_string( "techs.test_supply_elec.constraints.export_carrier: {}".format(param) ) override_converison_plus = lambda param: AttrDict.from_yaml_string( "techs.test_conversion_plus.constraints.export_carrier: {}".format(param) ) # should fail: exporting `heat` not allowed for electricity supply tech with pytest.raises(exceptions.ModelError): build_model(override_dict=override_supply('heat'), scenario='simple_supply,one_day') # should fail: exporting `random` not allowed for conversion_plus tech with pytest.raises(exceptions.ModelError): build_model(override_dict=override_converison_plus('random'), scenario='simple_conversion_plus,one_day') # should pass: exporting electricity for supply tech build_model(override_dict=override_supply('electricity'), scenario='simple_supply,one_day') # should pass: exporting heat for conversion tech build_model(override_dict=override_converison_plus('heat'), scenario='simple_conversion_plus,one_day')
def test_invalid_scenarios_str(self): """ Test that invalid scenario definition raises appropriate error """ override = AttrDict.from_yaml_string( """ scenarios: scenario_1: 'foo1,foo2' """ ) with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override, scenario='scenario_1') assert check_error_or_warning(error, 'Scenario definition must be a list of override names.')
def test_scenario_name_overlaps_overrides(self): """ Test that a scenario name cannot be a combination of override names """ override = AttrDict.from_yaml_string( """ scenarios: 'simple_supply,group_share_energy_cap_min': 'foobar' """ ) with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override, scenario='simple_supply,group_share_energy_cap_min') assert check_error_or_warning(error, 'Manually defined scenario cannot be a combination of override names.')
def test_defining_non_allowed_constraints(self): """ A technology within an abstract base technology can only define a subset of hardcoded constraints, anything else will not be implemented, so are not allowed for that technology. This includes misspellings """ # should fail: storage_cap_max not allowed for supply tech override_supply1 = AttrDict.from_yaml_string( """ techs.test_supply_elec.constraints.storage_cap_max: 10 """ ) with pytest.raises(exceptions.ModelError): build_model(override_dict=override_supply1, override_groups='simple_supply,one_day')
def test_defining_non_allowed_constraints(self): """ A technology within an abstract base technology can only define a subset of hardcoded constraints, anything else will not be implemented, so are not allowed for that technology. This includes misspellings """ # should fail: storage_cap_max not allowed for supply tech override_supply1 = AttrDict.from_yaml_string( """ techs.test_supply_elec.constraints.storage_cap_max: 10 """ ) with pytest.raises(exceptions.ModelError): build_model(override_dict=override_supply1, scenario='simple_supply,one_day')
def test_force_infinite_resource(self): """ Ensure that no loc-tech specifies infinite resource and force_resource=True """ override = { 'techs.test_supply_plus.constraints.resource': 'file=supply_plus_resource_inf.csv', 'techs.test_supply_plus.constraints.force_resource': True, } with pytest.raises(exceptions.ModelError) as error_info: build_model(override_dict=override, scenario='simple_supply_plus,one_day') assert check_error_or_warning(error_info, 'Ensure all entries are numeric')
def test_inconsistent_time_indeces(self): """ Test that, including after any time subsetting, the indeces of all time varying input data are consistent with each other """ # should fail: wrong length of demand_heat csv vs demand_elec override1 = { 'techs.test_demand_heat.constraints.resource': 'file=demand_heat_wrong_length.csv' } # check in output error that it points to: 07/01/2005 10:00:00 with pytest.raises(exceptions.ModelError): build_model(override_dict=override1, override_groups='simple_conversion') # should pass: wrong length of demand_heat csv, but time subsetting removes the difference build_model(override_dict=override1, override_groups='simple_conversion,one_day')
def test_invalid_scenarios(self): """ Test that invalid scenario definition raises appropriate error """ override = AttrDict.from_yaml_string(""" scenarios: scenario_1: techs.foo.bar: 1 """) with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override, scenario='scenario_1') assert check_error_or_warning( error, 'Scenario definition must be string of comma-separated overrides.')
def test_loc_techs_carrier_production_min_conversion_plus_milp_constraint( self): m = build_model({}, 'conversion_plus_milp,two_hours,investment_costs') m.run(build_only=True) assert not hasattr( m._backend_model, 'carrier_production_min_conversion_plus_constraint') m = build_model( {'techs.test_conversion_plus.constraints.energy_cap_min_use': 0.1}, 'conversion_plus_milp,two_hours,investment_costs') m.run(build_only=True) assert not hasattr( m._backend_model, 'carrier_production_min_conversion_plus_constraint')
def test_inconsistent_time_indeces(self): """ Test that, including after any time subsetting, the indeces of all time varying input data are consistent with each other """ # should fail: wrong length of demand_heat csv vs demand_elec override1 = { 'techs.test_demand_heat.constraints.resource': 'file=demand_heat_wrong_length.csv' } # check in output error that it points to: 07/01/2005 10:00:00 with pytest.raises(exceptions.ModelError): build_model(override_dict=override1, scenario='simple_conversion') # should pass: wrong length of demand_heat csv, but time subsetting removes the difference build_model(override_dict=override1, scenario='simple_conversion,one_day')
def test_abstract_base_tech_group_override(self): """ Abstract base technology groups can be overridden """ override = AttrDict.from_yaml_string(""" tech_groups: supply: constraints: lifetime: 25 locations: 1.techs.test_supply_elec: 1.techs.test_demand_elec: """) build_model(override_dict=override, scenario='one_day')
def test_scenario_name_overlaps_overrides(self): """ Test that a scenario name cannot be a combination of override names """ override = AttrDict.from_yaml_string(""" scenarios: 'simple_supply,group_share_energy_cap_min': 'foobar' """) with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override, scenario='simple_supply,group_share_energy_cap_min') assert check_error_or_warning( error, 'Manually defined scenario cannot be a combination of override names.' )
def test_clustering_no_datestep(self): """ On clustering, there are a few new dimensions in the model_data, and a few new lookup arrays. Cyclic storage is set to False as you cannot have cyclic storage without `storage_inter_cluster` being active. """ override = { 'model.subset_time': ['2005-01-01', '2005-01-04'], 'model.time': { 'function': 'apply_clustering', 'function_options': { 'clustering_func': 'file=cluster_days.csv:0', 'how': 'mean', 'storage_inter_cluster': False } }, 'run.cyclic_storage': False } model = build_model(override, scenario='simple_supply') assert 'clusters' in model._model_data.dims assert 'datesteps' not in model._model_data.dims assert 'lookup_cluster_first_timestep' in model._model_data.data_vars assert 'lookup_cluster_last_timestep' in model._model_data.data_vars assert 'lookup_datestep_last_cluster_timestep' not in model._model_data.data_vars assert 'lookup_datestep_cluster' not in model._model_data.data_vars assert 'timestep_cluster' in model._model_data.data_vars
def test_clustering(self): """ On clustering, there are a few new dimensions in the model_data, and a few new lookup arrays """ override = { 'model.subset_time': ['2005-01-01', '2005-01-04'], 'model.time': { 'function': 'apply_clustering', 'function_options': { 'clustering_func': 'file=cluster_days.csv:0', 'how': 'mean' } } } model = build_model(override, scenario='simple_supply') assert 'clusters' in model._model_data.dims assert 'lookup_cluster_first_timestep' in model._model_data.data_vars assert 'lookup_cluster_last_timestep' in model._model_data.data_vars assert 'lookup_datestep_last_cluster_timestep' in model._model_data.data_vars assert 'lookup_datestep_cluster' in model._model_data.data_vars assert 'timestep_cluster' in model._model_data.data_vars datesteps = model.inputs.datesteps.to_index().strftime('%Y-%m-%d') daterange = pd.date_range('2005-01-01', '2005-01-04', freq='1D').strftime('%Y-%m-%d') assert np.array_equal(datesteps, daterange)
def test_clustering_no_datestep(self): """ On clustering, there are a few new dimensions in the model_data, and a few new lookup arrays """ override = { 'model.subset_time': ['2005-01-01', '2005-01-04'], 'model.time': { 'function': 'apply_clustering', 'function_options': { 'clustering_func': 'file=cluster_days.csv:0', 'how': 'mean', 'storage_inter_cluster': False } } } model = build_model(override, override_groups='simple_supply') assert 'clusters' in model._model_data.dims assert 'datesteps' not in model._model_data.dims assert 'lookup_cluster_first_timestep' in model._model_data.data_vars assert 'lookup_cluster_last_timestep' in model._model_data.data_vars assert 'lookup_datestep_last_cluster_timestep' not in model._model_data.data_vars assert 'lookup_datestep_cluster' not in model._model_data.data_vars assert 'timestep_cluster' in model._model_data.data_vars
def test_valid_scenarios(self): """ Test that valid scenario definition raises no error and results in applied scenario. """ override = AttrDict.from_yaml_string(""" scenarios: scenario_1: ['one', 'two'] overrides: one: techs.test_supply_gas.constraints.energy_cap_max: 20 two: techs.test_supply_elec.constraints.energy_cap_max: 20 locations: 0: techs: test_supply_gas: test_supply_elec: test_demand_elec: """) model = build_model(override_dict=override, scenario='scenario_1') assert model._model_run.locations[ '0'].techs.test_supply_gas.constraints.energy_cap_max == 20 assert model._model_run.locations[ '0'].techs.test_supply_elec.constraints.energy_cap_max == 20
def test_loc_techs_carrier_production_max_conversion_plus_milp_constraint( self): m = build_model({}, "conversion_plus_milp,two_hours,investment_costs") m.run(build_only=True) assert not hasattr( m._backend_model, "carrier_production_max_conversion_plus_constraint")
def test_equals(self, model_file): model = build_model(model_file=model_file, scenario="equals") model.run() assert model.results.termination_condition == "optimal" energy_capacity = model.get_formatted_array("energy_cap").loc[{'techs': 'my_storage'}].sum().item() storage_capacity = model.get_formatted_array("storage_cap").loc[{'techs': 'my_storage'}].sum().item() assert storage_capacity == pytest.approx(1 / 10 * energy_capacity)
def test_group_constraint_without_technology(self): model = build_model( model_file='group_constraints.yaml', scenario='group_constraint_without_tech' ) with pytest.raises(calliope.exceptions.ModelError): model.run()
def test_demand_share_per_timestep_decision_inf_with_heat_constrain_heat_and_electricity(self): model = build_model( model_file='demand_share_decision.yaml', scenario='demand_share_per_timestep_decision_inf_with_heat,demand_share_per_timestep_decision_not_one,with_electricity_conversion_tech' ) model.run() demand_heat = -1 * model.get_formatted_array("carrier_con").loc[{'carriers': "heat"}].sum(['locs', 'techs']).to_pandas() supply_heat = model.get_formatted_array("carrier_prod").loc[{'carriers': "heat"}].sum('locs').to_pandas().T shares_heat = supply_heat.div(demand_heat, axis=0) demand_elec = -1 * ( model._model_data.carrier_con.loc[{ 'loc_tech_carriers_con': [ i for i in model._model_data.loc_tech_carriers_demand.values if 'electricity' in i ] }].sum(['loc_tech_carriers_con']).to_pandas() ) supply_elec = model.get_formatted_array("carrier_prod").loc[{'carriers': "electricity"}].sum('locs').to_pandas().T shares_elec = supply_elec.div(demand_elec, axis=0) assert all([i == pytest.approx(0.5) for i in shares_heat['elec_to_heat'].values]) assert all([i == pytest.approx(0.5) for i in shares_heat['heating'].values]) assert all([i == pytest.approx(0.9) for i in shares_elec['normal_elec_supply'].values]) assert all([round(i, 5) >= 0.1 for i in shares_elec['cheap_elec_supply'].values])
def test_operate_mode(self, model_file): model = build_model(model_file=model_file, scenario="operate_mode_min") with pytest.raises(calliope.exceptions.ModelError) as error: model.run() assert check_error_or_warning( error, "Operational mode requires a timestep window and horizon" )
def test_valid_scenarios(self): """ Test that valid scenario definition raises no error and results in applied scenario. """ override = AttrDict.from_yaml_string( """ scenarios: scenario_1: ['one', 'two'] overrides: one: techs.test_supply_gas.constraints.energy_cap_max: 20 two: techs.test_supply_elec.constraints.energy_cap_max: 20 locations: 0: techs: test_supply_gas: test_supply_elec: test_demand_elec: """ ) model = build_model(override_dict=override, scenario='scenario_1') assert model._model_run.locations['0'].techs.test_supply_gas.constraints.energy_cap_max == 20 assert model._model_run.locations['0'].techs.test_supply_elec.constraints.energy_cap_max == 20
def test_weighted_objective_results(self, scenario, cost_class, weight): model = build_model(model_file="weighted_obj_func.yaml", scenario=scenario) model.run() assert sum(model.results.cost.loc[{ "costs": cost_class[i] }].sum().item() * weight[i] for i in range(len(cost_class))) == approx( po.value(model._backend_model.obj))
def test_missing_constraints(self): """ A technology must define at least one constraint. """ override = AttrDict.from_yaml_string(""" techs: supply_missing_constraint: essentials: parent: supply carrier: electricity name: supply missing constraint locations.1.techs.supply_missing_constraint: """) with pytest.raises(exceptions.ModelError): build_model(override_dict=override, scenario='simple_supply,one_day')
def test_model_version_mismatch(self): """ Model config says model.calliope_version = 0.1, which is not what we are running, so we want a warning. """ override = AttrDict.from_yaml_string(""" model.calliope_version: 0.1 """) with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override, override_groups='simple_supply,one_day') all_warnings = ','.join( str(excinfo.list[i]) for i in range(len(excinfo.list))) assert 'Model configuration specifies calliope_version' in all_warnings
def test_no_group_constraint(self): model = build_model(model_file="group_constraints.yaml") model.run() expensive_generation = ( model.get_formatted_array("carrier_prod") .loc[{'techs': 'expensive_supply'}].sum().item() ) assert expensive_generation == 0
def test_no_balance_conversion_plus_primary_constraint(self): """ sets.loc_techs_conversion_plus, """ m = build_model({}, "simple_supply,two_hours,investment_costs") m.run(build_only=True) assert not hasattr(m._backend_model, "balance_conversion_plus_primary_constraint")
def test_abstract_base_tech_group_override(self): """ Abstract base technology groups can be overridden """ override = AttrDict.from_yaml_string( """ tech_groups: supply: constraints: lifetime: 25 locations: 1.techs.test_supply_elec: 1.techs.test_demand_elec: """ ) build_model(override_dict=override, scenario='one_day')
def test_conversion_plus_primary_carriers(self): """ Test that user has input input/output primary carriers for conversion_plus techs """ override1 = {'techs.test_conversion_plus.essentials.carrier_in': ['gas', 'coal']} override2 = {'techs.test_conversion_plus.essentials.primary_carrier_in': 'coal'} override3 = {'techs.test_conversion_plus.essentials.primary_carrier_out': 'coal'} model = build_model({}, scenario='simple_conversion_plus,two_hours') assert model._model_run.techs.test_conversion_plus.essentials.get_key( 'primary_carrier_in', None ) == 'gas' # should fail: multiple carriers in, but no primary_carrier_in assigned with pytest.raises(exceptions.ModelError) as error: build_model(override1, scenario='simple_conversion_plus,two_hours') assert check_error_or_warning(error, 'Primary_carrier_in must be assigned') # should fail: primary_carrier_in not one of the carriers_in with pytest.raises(exceptions.ModelError) as error: build_model(override2, scenario='simple_conversion_plus,two_hours') assert check_error_or_warning(error, 'Primary_carrier_in `coal` not one') # should fail: primary_carrier_out not one of the carriers_out with pytest.raises(exceptions.ModelError) as error: build_model(override3, scenario='simple_conversion_plus,two_hours') assert check_error_or_warning(error, 'Primary_carrier_out `coal` not one')
def test_systemwide_emissions_max_constraint(self): model = build_model( model_file='model_cost_cap.yaml', scenario='emissions_max_systemwide' ) model.run() emissions = (model.get_formatted_array('cost') .loc[{'costs': 'emissions'}]).sum().item() assert round(emissions, 5) <= 400
def test_systemwide_energy_cap_max_constraint(self): model = build_model( model_file='energy_cap.yaml', scenario='energy_cap_max_systemwide' ) model.run() cheap_capacity = (model.get_formatted_array("energy_cap") .loc[{'techs': "cheap_supply"}].sum()).item() assert round(cheap_capacity, 5) <= 14
def test_systemwide_cost_equals_constraint(self): model = build_model( model_file='model_cost_cap.yaml', scenario='cheap_cost_equals_systemwide' ) model.run() cheap_cost = (model.get_formatted_array('cost') .loc[{'costs': 'monetary', 'techs': 'cheap_polluting_supply'}]).sum().item() assert cheap_cost == approx(210)
def test_undefined_carriers(self): """ Test that user has input either carrier or carrier_in/_out for each tech """ override = AttrDict.from_yaml_string(""" techs: test_undefined_carrier: essentials: parent: supply name: test constraints: resource: .inf energy_cap_max: .inf locations.1.techs.test_undefined_carrier: """) with pytest.raises(exceptions.ModelError): build_model(override_dict=override, scenario='simple_supply,one_day')
def test_15min_timesteps(self): override = { 'techs.test_demand_elec.constraints.resource': 'file=demand_elec_15mins.csv', } model = build_model(override, scenario='simple_supply,one_day') assert model.inputs.timestep_resolution.to_pandas().unique() == [0.25]
def test_no_constraint_set(self, model_file): model = build_model(model_file=model_file) model.run() assert model.results.termination_condition == "optimal" energy_capacity = model.get_formatted_array("energy_cap").loc[{'techs': 'my_storage'}].sum().item() storage_capacity = model.get_formatted_array("storage_cap").loc[{'techs': 'my_storage'}].sum().item() assert energy_capacity == pytest.approx(10) assert storage_capacity == pytest.approx(175) assert storage_capacity != pytest.approx(1 / 10 * energy_capacity)
def test_locations_instead_of_nodes(self): with pytest.warns(DeprecationWarning) as warning: model = build_model( scenario="simple_supply_locations,one_day,investment_costs") assert check_error_or_warning(warning, "`locations` has been renamed") model.run() assert model.get_formatted_array("carrier_prod").sum() == 420
def test_force_resource_ignored(self): """ If a technology is defines force_resource but is not in loc_techs_finite_resource it will have no effect """ override = { 'techs.test_supply_elec.constraints.resource': np.inf, 'techs.test_supply_elec.constraints.force_resource': True, } with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override, scenario='simple_supply,one_day') assert check_error_or_warning( excinfo, '`test_supply_elec` at `0` defines force_resource but not a finite resource' )
def test_systemwide_resource_area_max_constraint(self): model = build_model( model_file='resource_area.yaml', scenario='resource_area_max_systemwide' ) model.run() cheap_resource_area = (model.get_formatted_array("resource_area") .loc[{'techs': "cheap_supply"}].sum()).item() assert cheap_resource_area == 20
def test_systemwide_resource_area_min_constraint(self): model = build_model( model_file='resource_area.yaml', scenario='resource_area_min_systemwide' ) model.run() resource_area = model.get_formatted_array("resource_area") assert resource_area.loc[{'techs': "cheap_supply"}].sum().item() == 0 assert resource_area.loc[{'techs': "expensive_supply"}].sum().item() == 20
def test_missing_constraints(self): """ A technology must define at least one constraint. """ override = AttrDict.from_yaml_string( """ techs: supply_missing_constraint: essentials: parent: supply carrier: electricity name: supply missing constraint locations.1.techs.supply_missing_constraint: """ ) with pytest.raises(exceptions.ModelError): build_model(override_dict=override, scenario='simple_supply,one_day')
def test_systemwide_cost_min_constraint(self): model = build_model( model_file='model_cost_cap.yaml', scenario='expensive_cost_min_systemwide' ) model.run() expensive_cost = (model.get_formatted_array('cost') .loc[{'costs': 'monetary', 'techs': 'expensive_clean_supply'}]).sum().item() assert round(expensive_cost, 5) >= 600
def test_empty_dimensions(self): """ Empty dimensions lead Pyomo to blow up (building sets with no data), so check that we have successfully removed them here. """ model = build_model(scenario='simple_conversion_plus,one_day') assert 'distance' not in model._model_data.data_vars assert 'lookup_remotes' not in model._model_data.data_vars
def test_milp_constraints(self): """ If `units` is defined, but not `energy_cap_per_unit`, throw an error """ # should fail: no energy_cap_per_unit override1 = AttrDict.from_yaml_string("techs.test_supply_elec.constraints.units_max: 4") with pytest.raises(exceptions.ModelError): build_model(override_dict=override1, scenario='simple_supply,one_day') # should pass: energy_cap_per_unit given override2 = AttrDict.from_yaml_string(""" techs.test_supply_elec.constraints: units_max: 4 energy_cap_per_unit: 5 """) build_model(override_dict=override2, scenario='simple_supply,one_day')
def test_undefined_carriers(self): """ Test that user has input either carrier or carrier_in/_out for each tech """ override = AttrDict.from_yaml_string( """ techs: test_undefined_carrier: essentials: parent: supply name: test constraints: resource: .inf energy_cap_max: .inf locations.1.techs.test_undefined_carrier: """ ) with pytest.raises(exceptions.ModelError): build_model(override_dict=override, scenario='simple_supply,one_day')
def check_operate_mode_allowed(self): """ On masking times, operate mode will no longer be allowed """ model = build_model(scenario='simple_supply,one_day') assert model.model_data.attrs['allow_operate_mode'] == 1 model1 = calliope.examples.time_masking() assert model1.model_data.attrs['allow_operate_mode'] == 0
def test_tech_as_parent(self): """ All technologies and technology groups must specify a parent """ override1 = AttrDict.from_yaml_string( """ techs.test_supply_tech_parent: essentials: name: Supply tech carrier: gas parent: test_supply_elec constraints: energy_cap_max: 10 resource: .inf locations.1.test_supply_tech_parent: """ ) with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override1, scenario='simple_supply,one_day') check_error_or_warning(error, 'tech `test_supply_tech_parent` has another tech as a parent') override2 = AttrDict.from_yaml_string( """ tech_groups.test_supply_group: essentials: carrier: gas parent: test_supply_elec constraints: energy_cap_max: 10 resource: .inf techs.test_supply_tech_parent.essentials: name: Supply tech parent: test_supply_group locations.1.test_supply_tech_parent: """ ) with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override2, scenario='simple_supply,one_day') check_error_or_warning(error, 'tech_group `test_supply_group` has a tech as a parent')
def test_unspecified_parent(self): """ All technologies and technology groups must specify a parent """ override = AttrDict.from_yaml_string( """ techs.test_supply_no_parent: essentials: name: Supply tech carrier: gas constraints: energy_cap_max: 10 resource: .inf locations.1.test_supply_no_parent: """ ) with pytest.raises(KeyError): build_model(override_dict=override, scenario='simple_supply,one_day')
def test_delete_interest_rate(self): """ If only 'interest_rate' is given in the cost class for a technology, we should be able to handle deleting it without leaving an empty cost key. """ override1 = { 'techs.test_supply_elec.costs.monetary.interest_rate': 0.1 } m = build_model(override_dict=override1, scenario='simple_supply,one_day') assert 'loc_techs_cost' not in m._model_data.dims
def test_clustering_and_cyclic_storage(self): """ Don't allow time clustering with cyclic storage if not also using storage_inter_cluster """ override = { 'model.subset_time': ['2005-01-01', '2005-01-04'], 'model.time': { 'function': 'apply_clustering', 'function_options': { 'clustering_func': 'file=cluster_days.csv:0', 'how': 'mean', 'storage_inter_cluster': False } }, 'run.cyclic_storage': True } with pytest.raises(exceptions.ModelError) as error: build_model(override, scenario='simple_supply') assert check_error_or_warning(error, 'cannot have cyclic storage')