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_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_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_predefined_clusters_fail(self): override = { 'model.subset_time': ['2005-01-01', '2005-01-04'], 'model.time': { 'function': 'apply_clustering', 'function_options': { 'clustering_func': 'file=clusters.csv:0', 'how': 'mean' } } } # should fail - no CSV data column defined override1 = {**override, **{ 'model.time.function_options.clustering_func': 'file=clusters.csv' }} with pytest.raises(exceptions.ModelError) as error: build_test_model(override1, scenario='simple_supply') assert check_error_or_warning(error, 'No time clustering column given') # should fail - unknown CSV data column defined override2 = {**override, **{ 'model.time.function_options.clustering_func': 'file=clusters.csv:1' }} with pytest.raises(KeyError) as error: build_test_model(override2, scenario='simple_supply') assert check_error_or_warning(error, 'time clustering column 1 not found') # should fail - more than one cluster given to any one day override3 = {**override, **{ 'model.time.function_options.clustering_func': 'file=clusters.csv:b' }} with pytest.raises(exceptions.ModelError) as error: build_test_model(override3, scenario='simple_supply') assert check_error_or_warning( error, 'More than one cluster value assigned to a day in `clusters.csv:b`' ) # should fail - not enough data in clusters.csv to cover subset_time override4 = {**override, **{ 'model.subset_time': ['2005-01-01', '2005-01-06'], 'model.time.function_options.clustering_func': 'file=cluster_days.csv:1' }} with pytest.raises(exceptions.ModelError) as error: build_test_model(override4, scenario='simple_supply') assert check_error_or_warning(error, 'Missing cluster days')
def test_check_data(self, model_data, subdict): model_data._extract_node_tech_data() setattr(model_data, f"{subdict}_dict", {"foo": 1}) with pytest.raises(exceptions.ModelError) as errmsg: model_data._check_data() assert check_error_or_warning( errmsg, "Some data not extracted from inputs into model dataset")
def test_override_coordinates(self): """ Check that warning is raised if we are completely overhauling the coordinate system with an override """ override = { 'locations': { 'X1.coordinates': {'lat': 51.4596158, 'lon': -0.1613446}, 'X2.coordinates': {'lat': 51.4652373, 'lon': -0.1141548}, 'X3.coordinates': {'lat': 51.4287016, 'lon': -0.1310635}, 'N1.coordinates': {'lat': 51.4450766, 'lon': -0.1247183} }, 'links': { 'X1,X2.techs.power_lines.distance': 10, 'X1,X3.techs.power_lines.istance': 5, 'X1,N1.techs.heat_pipes.distance': 3, 'N1,X2.techs.heat_pipes.distance': 3, 'N1,X3.techs.heat_pipes.distance': 4 } } with pytest.warns(exceptions.ModelWarning) as excinfo: calliope.examples.urban_scale(override_dict=override) assert check_error_or_warning( excinfo, "Updated from coordinate system" )
def test_import_must_be_list(self): yaml_string = """ import: 'somefile.yaml' """ with pytest.raises(ValueError) as excinfo: AttrDict.from_yaml_string(yaml_string, resolve_imports=True) assert check_error_or_warning(excinfo, "`import` must be a list.")
def test_invalid_csv_columns(self): override = { 'locations': { '2.techs': { 'test_supply_elec': None, 'test_demand_elec': None }, '3.techs': { 'test_supply_elec': None, 'test_demand_elec': None } }, 'links': { '0,1': { 'exists': False }, '2,3.techs': { 'test_transmission_elec': None } } } with pytest.raises(exceptions.ModelError) as excinfo: build_test_model(override_dict=override, scenario='one_day') assert check_error_or_warning(excinfo, [ 'column `2` not found in file `demand_elec.csv`, but was requested by loc::tech `2::test_demand_elec`.', 'column `3` not found in file `demand_elec.csv`, but was requested by loc::tech `3::test_demand_elec`.' ])
def test_override_coordinates(self): """ Check that warning is raised if we are completely overhauling the coordinate system with an override """ override = { 'locations': { 'X1.coordinates': {'lat': 51.4596158, 'lon': -0.1613446}, 'X2.coordinates': {'lat': 51.4652373, 'lon': -0.1141548}, 'X3.coordinates': {'lat': 51.4287016, 'lon': -0.1310635}, 'N1.coordinates': {'lat': 51.4450766, 'lon': -0.1247183} }, 'links': { 'X1,X2.techs.power_lines.distance': 10, 'X1,X3.techs.power_lines.istance': 5, 'X1,N1.techs.heat_pipes.distance': 3, 'N1,X2.techs.heat_pipes.distance': 3, 'N1,X3.techs.heat_pipes.distance': 4 } } with pytest.warns(exceptions.ModelWarning) as excinfo: calliope.examples.urban_scale(override_dict=override) assert check_error_or_warning( excinfo, "Updated from coordinate system" )
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_invalid_csv_columns(self): override = { "locations": { "2.techs": { "test_supply_elec": None, "test_demand_elec": None }, "3.techs": { "test_supply_elec": None, "test_demand_elec": None }, }, "links": { "0,1": { "exists": False }, "2,3.techs": { "test_transmission_elec": None }, }, } with pytest.raises(exceptions.ModelError) as excinfo: build_test_model(override_dict=override, scenario="one_day") assert check_error_or_warning( excinfo, [ "column `2` not found in file `demand_elec.csv`, but was requested by loc::tech `2::test_demand_elec`.", "column `3` not found in file `demand_elec.csv`, but was requested by loc::tech `3::test_demand_elec`.", ], )
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_urban_scale_plotting(self): override = { 'model.subset_time': ['2005-07-01 00:00:00', '2005-07-01 12:00:00'] } model = calliope.examples.urban_scale(override_dict=override) model.run() # Just try plotting each model.plot.summary() model.plot.capacity(html_only=True) model.plot.timeseries(html_only=True) model.plot.transmission(html_only=True) model.plot.flows(html_only=True, timestep_index_subset=[ 0, 12 ]) # cover the use of timestep_index_subset # Testing that the model catches an expected error on the model not # defining coordinates model._model_data = model._model_data.drop('loc_coordinates') with pytest.raises(ValueError) as error: model.plot.flows() model.plot.transmission() assert check_error_or_warning( error, 'Model does not define location coordinates')
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_split_loc_tech_too_many_loc_tech_dims(self, example_dataarray): _array = example_dataarray.rename({"costs": "loc_techs_2"}) with pytest.raises(exceptions.ModelError) as excinfo: dataset.split_loc_techs(_array) assert check_error_or_warning( excinfo, "Cannot split loc_techs or loc_tech_carriers dimension" )
def test_rerun_fail_on_operate(self, model): # should fail if the run mode is not 'plan' model.run_config["mode"] = "operate" with pytest.raises(exceptions.ModelError) as excinfo: model.backend.rerun() assert check_error_or_warning( excinfo, "Cannot rerun the backend in operate run mode")
def test_not_a_param(self, model): """ Raise error when trying to update a non-Param Pyomo object """ with pytest.raises(exceptions.ModelError) as excinfo: model.backend.update_param("energy_cap", {("b", "test_supply_elec"): 20}) assert check_error_or_warning( excinfo, "`energy_cap` not a Parameter in the Pyomo Backend." ) with pytest.raises(exceptions.ModelError) as excinfo: model.backend.update_param("loc_techs", {("b", "test_supply_elec"): 20}) assert check_error_or_warning( excinfo, "Parameter `loc_techs` not in the Pyomo Backend." )
def test_fail_reorganise_dimensions(self): with pytest.raises(TypeError) as excinfo: dataset.reorganise_xarray_dimensions( ['timesteps', 'loc_techs_bar', 'costs']) assert check_error_or_warning( excinfo, 'Must provide either xarray Dataset or DataArray to be reorganised' )
def test_fail_reorganise_dimensions(self): with pytest.raises(TypeError) as excinfo: dataset.reorganise_xarray_dimensions( ["timesteps", "nodes", "techs", "costs"]) assert check_error_or_warning( excinfo, "Must provide either xarray Dataset or DataArray to be reorganised" )
def test_regeneration_needed_warning(self, model_persistent): with pytest.warns(exceptions.ModelWarning) as excinfo: model_persistent.backend.update_param( "objective_cost_class", {"monetary": 0.5} ) assert check_error_or_warning( excinfo, "Updating the Pyomo parameter won't affect the optimisation" )
def test_get_formatted_array_unknown_format(self, national_scale_example): with pytest.raises(ValueError) as excinfo: national_scale_example.get_formatted_array('resource', index_format='foo') assert check_error_or_warning( excinfo, "Argument 'index_format' must be one of 'index' or 'multiindex'")
def test_not_a_param(self, model): """ Raise error when trying to update a non-Param Pyomo object """ with pytest.raises(exceptions.ModelError) as excinfo: model.backend.update_param('energy_cap', {'1::test_supply_elec': 20}) assert check_error_or_warning( excinfo, '`energy_cap` not a Parameter in the Pyomo Backend.') with pytest.raises(exceptions.ModelError) as excinfo: model.backend.update_param('loc_techs', {'1::test_supply_elec': 20}) assert check_error_or_warning( excinfo, '`loc_techs` not a Parameter in the Pyomo Backend.')
def test_future_warning(self): """ Test and warning to be removed in v0.6.3-dev """ with pytest.warns(FutureWarning) as warning: build_model({}, override_groups='simple_storage') assert check_error_or_warning( warning, 'From v0.6.3, cyclic storage will default to True.')
def test_incorrect_location_coordinates(self): """ Either all or no locations must have `coordinates` defined and, if all defined, they must be in the same coordinate system (lat/lon or x/y) """ def _override(param0, param1): override = {} if param0 is not None: override.update({'locations.0.coordinates': param0}) if param1 is not None: override.update({'locations.1.coordinates': param1}) return override cartesian0 = {'x': 0, 'y': 1} cartesian1 = {'x': 1, 'y': 1} geographic0 = {'lat': 0, 'lon': 1} geographic1 = {'lat': 1, 'lon': 1} fictional0 = {'a': 0, 'b': 1} fictional1 = {'a': 1, 'b': 1} # should fail: cannot have locations in one place and not in another with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=_override(cartesian0, None), scenario='simple_storage,one_day') check_error_or_warning( error, "Either all or no locations must have `coordinates` defined") # should fail: cannot have cartesian coordinates in one place and geographic in another with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=_override(cartesian0, geographic1), scenario='simple_storage,one_day') check_error_or_warning( error, "All locations must use the same coordinate format") # should fail: cannot use a non-cartesian or non-geographic coordinate system with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=_override(fictional0, fictional1), scenario='simple_storage,one_day') check_error_or_warning(error, "Unidentified coordinate system") # should fail: coordinates must be given as key:value pairs with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=_override([0, 1], [1, 1]), scenario='simple_storage,one_day') check_error_or_warning(error, "Coordinates must be given in the format") # should pass: cartesian coordinates in both places build_model(override_dict=_override(cartesian0, cartesian1), scenario='simple_storage,one_day') # should pass: geographic coordinates in both places build_model(override_dict=_override(geographic0, geographic1), scenario='simple_storage,one_day')
def test_storage_inter_cluster_no_storage(self): with pytest.warns(calliope.exceptions.ModelWarning) as excinfo: self.model_runner(storage_inter_cluster=True, storage=False) expected_warnings = [ 'Tech battery was removed by setting ``exists: False``', 'Tech csp was removed by setting ``exists: False``' ] assert check_error_or_warning(excinfo, expected_warnings)
def test_fail_with_spores_as_input_dim(self, base_model_data): spores_model = calliope.Model(config=None, model_data=base_model_data.loc[{ "spores": [0, 1] }]) with pytest.raises(exceptions.ModelError) as excinfo: spores_model.run(force_rerun=True) assert check_error_or_warning( excinfo, "Cannot run SPORES with a SPORES dimension in any input")
def test_netcdf_to_yaml_raises_if_attrs_missing(self, model): with tempfile.TemporaryDirectory() as tempdir: out_path_yaml = os.path.join(tempdir, 'modelrun.yaml') model._model_run = {} with pytest.raises(KeyError) as error: model.save_commented_model_yaml(out_path_yaml) assert check_error_or_warning( error, 'This model does not have the fully built model attached')
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_combine_imask(self, operator, result): curr = False new = True if isinstance(result, bool): assert _combine_imasks(curr, new, operator) is result elif result == "error": with pytest.raises(ValueError) as excinfo: _combine_imasks(curr, new, operator) assert check_error_or_warning(excinfo, "Operator `foo` not recognised")
def test_split_loc_tech_unknown_output(self, example_dataarray, example_one_dim_dataarray): for array in [example_dataarray, example_one_dim_dataarray]: with pytest.raises(ValueError) as excinfo: dataset.split_loc_techs(array, return_as='foo') assert check_error_or_warning( excinfo, '`return_as` must be `DataArray`, `Series`, or `MultiIndex DataArray`' )
def test_storage_inter_cluster_no_storage(self): with pytest.warns(calliope.exceptions.ModelWarning) as excinfo: self.model_runner(storage_inter_cluster=True, storage=False) expected_warnings = [ 'Tech battery was removed by setting ``exists: False``', 'Tech csp was removed by setting ``exists: False``' ] assert check_error_or_warning(excinfo, expected_warnings)
def test_get_subkey_from_nested_non_attrdict(self, attr_dict): # Directly assigning a dict means it is not modified # but it breaks get_key with nested keys attr_dict['c']['z']['newkey'] = {'foo': 1, 'doo': 2} with pytest.raises(AttributeError) as excinfo: attr_dict.get_key('c.z.newkey.foo') assert check_error_or_warning( excinfo, "'dict' object has no attribute 'get_key'")
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_get_subkey_from_nested_non_attrdict(self, attr_dict): # Directly assigning a dict means it is not modified # but it breaks get_key with nested keys attr_dict["c"]["z"]["newkey"] = {"foo": 1, "doo": 2} with pytest.raises(AttributeError) as excinfo: attr_dict.get_key("c.z.newkey.foo") assert check_error_or_warning( excinfo, "'dict' object has no attribute 'get_key'")
def test_fail_on_parameter_activate(self, model): """ test that the function activate_constraint fails if trying to activate a non-constraint Pyomo object. """ with pytest.raises(exceptions.ModelError) as excinfo: model.backend.activate_constraint("resource", active=False) assert check_error_or_warning( excinfo, "`resource` not a constraint in the Pyomo Backend.")
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_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_hartigans_rule(self, model_national): data = model_national._model_data with pytest.warns(exceptions.ModelWarning) as excinfo: funcs.apply_clustering( data, timesteps=None, clustering_func='kmeans', how='mean', normalize=True ) assert check_error_or_warning(excinfo, 'a good number of clusters is 5')
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_hierarchical_no_hartigans_rule(self, model_national): data = model_national._model_data with pytest.raises(exceptions.ModelError) as excinfo: funcs.apply_clustering( data, timesteps=None, clustering_func='hierarchical', how='mean', normalize=True ) assert check_error_or_warning(excinfo, 'Cannot undertake hierarchical clustering')
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_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_subset_plotting(self, national_scale_example): model = national_scale_example model.plot.capacity( html_only=True, subset={'timesteps': ['2015-01-01 01:00']} ) # should raise, subsetting with a tech that does not exist with pytest.raises(ValueError) as excinfo: model.plot.capacity( html_only=True, subset={'techs': ['foobar']} ) assert check_error_or_warning(excinfo, 'No data to plot')
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_operate_example_results(self): model = calliope.examples.operate( override_dict={'model.subset_time': ['2005-07-01', '2005-07-04']} ) with pytest.warns(calliope.exceptions.ModelWarning) as excinfo: model.run() expected_warnings = [ 'Energy capacity constraint removed', 'Resource capacity constraint defined and set to infinity for all supply_plus techs' ] assert check_error_or_warning(excinfo, expected_warnings) assert all(model.results.timesteps == pd.date_range('2005-07', '2005-07-04 23:00:00', freq='H'))
def test_incorrect_location_coordinates(self): """ Either all or no locations must have `coordinates` defined and, if all defined, they must be in the same coordinate system (lat/lon or x/y) """ def _override(param0, param1): override = {} if param0 is not None: override.update({'locations.0.coordinates': param0}) if param1 is not None: override.update({'locations.1.coordinates': param1}) return override cartesian0 = {'x': 0, 'y': 1} cartesian1 = {'x': 1, 'y': 1} geographic0 = {'lat': 0, 'lon': 1} geographic1 = {'lat': 1, 'lon': 1} fictional0 = {'a': 0, 'b': 1} fictional1 = {'a': 1, 'b': 1} # should fail: cannot have locations in one place and not in another with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=_override(cartesian0, None), scenario='simple_storage,one_day') check_error_or_warning(error, "Either all or no locations must have `coordinates` defined") # should fail: cannot have cartesian coordinates in one place and geographic in another with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=_override(cartesian0, geographic1), scenario='simple_storage,one_day') check_error_or_warning(error, "All locations must use the same coordinate format") # should fail: cannot use a non-cartesian or non-geographic coordinate system with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=_override(fictional0, fictional1), scenario='simple_storage,one_day') check_error_or_warning(error, "Unidentified coordinate system") # should fail: coordinates must be given as key:value pairs with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=_override([0, 1], [1, 1]), scenario='simple_storage,one_day') check_error_or_warning(error, "Coordinates must be given in the format") # should pass: cartesian coordinates in both places build_model(override_dict=_override(cartesian0, cartesian1), scenario='simple_storage,one_day') # should pass: geographic coordinates in both places build_model(override_dict=_override(geographic0, geographic1), scenario='simple_storage,one_day')
def example_tester(self): with pytest.warns(calliope.exceptions.ModelWarning) as excinfo: model = calliope.examples.national_scale( override_dict={'model.subset_time': ['2005-01-01', '2005-01-03']}, scenario='operate') model.run() expected_warnings = [ 'Energy capacity constraint removed from region1::demand_power as force_resource is applied', 'Energy capacity constraint removed from region2::demand_power as force_resource is applied', 'Resource capacity constraint defined and set to infinity for all supply_plus techs' ] assert check_error_or_warning(excinfo, expected_warnings) assert all(model.results.timesteps == pd.date_range('2005-01', '2005-01-03 23:00:00', freq='H'))
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 example_tester(self): with pytest.warns(calliope.exceptions.ModelWarning) as excinfo: model = calliope.examples.national_scale( scenario='check_feasibility', override_dict={'run.cyclic_storage': False} ) expected_warnings = [ 'Objective function argument `cost_class` given but not used by objective function `check_feasibility`', 'Objective function argument `sense` given but not used by objective function `check_feasibility`' ] assert check_error_or_warning(excinfo, expected_warnings) model.run() assert model.results.attrs['termination_condition'] == 'other' assert 'systemwide_levelised_cost' not in model.results.data_vars assert 'systemwide_capacity_factor' not in model.results.data_vars
def test_incorrect_resource_unit(self): """ Only `energy`, `energy_per_cap`, or `energy_per_area` is allowed under `resource unit`. """ def _override(resource_unit): return { 'techs.test_supply_elec.constraints.resource_unit': resource_unit } with pytest.raises(exceptions.ModelError) as error: build_model(_override('power'), scenario='simple_supply') build_model(_override('energy'), scenario='simple_supply') build_model(_override('energy_per_cap'), scenario='simple_supply') build_model(_override('energy_per_area'), scenario='simple_supply') assert check_error_or_warning( error, '`power` is an unknown resource unit for `test_supply_elec`' )
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')
def test_calculate_depreciation(self): """ Technologies which define investment costs *must* define lifetime and interest rate, so that a depreciation rate can be calculated. If lifetime == inf and interested > 0, depreciation rate will be inf, so we want to avoid that too. """ override1 = { 'techs.test_supply_elec.costs.monetary.energy_cap': 10 } with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override1, scenario='simple_supply,one_day') assert check_error_or_warning( error, 'Must specify constraints.lifetime and costs.monetary.interest_rate' ) override2 = { 'techs.test_supply_elec.constraints.lifetime': 10, 'techs.test_supply_elec.costs.monetary.energy_cap': 10 } with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override2, scenario='simple_supply,one_day') assert check_error_or_warning( error, 'Must specify constraints.lifetime and costs.monetary.interest_rate' ) override3 = { 'techs.test_supply_elec.costs.monetary.interest_rate': 0.1, 'techs.test_supply_elec.costs.monetary.energy_cap': 10 } with pytest.raises(exceptions.ModelError) as error: build_model(override_dict=override3, scenario='simple_supply,one_day') assert check_error_or_warning( error, 'Must specify constraints.lifetime and costs.monetary.interest_rate' ) override4 = { 'techs.test_supply_elec.constraints.lifetime': 10, 'techs.test_supply_elec.costs.monetary.interest_rate': 0, 'techs.test_supply_elec.costs.monetary.energy_cap': 10 } with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override4, scenario='simple_supply,one_day') assert check_error_or_warning(excinfo, '`monetary` interest rate of zero') override5 = { 'techs.test_supply_elec.constraints.lifetime': np.inf, 'techs.test_supply_elec.costs.monetary.interest_rate': 0, 'techs.test_supply_elec.costs.monetary.energy_cap': 10 } with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override5, scenario='simple_supply,one_day') assert check_error_or_warning( excinfo, 'No investment monetary cost will be incurred for `test_supply_elec`' ) override6 = { 'techs.test_supply_elec.constraints.lifetime': np.inf, 'techs.test_supply_elec.costs.monetary.interest_rate': 0.1, 'techs.test_supply_elec.costs.monetary.energy_cap': 10 } with pytest.warns(exceptions.ModelWarning) as excinfo: build_model(override_dict=override6, scenario='simple_supply,one_day') assert check_error_or_warning( excinfo, 'No investment monetary cost will be incurred for `test_supply_elec`' ) override7 = { 'techs.test_supply_elec.constraints.lifetime': 10, 'techs.test_supply_elec.costs.monetary.interest_rate': 0.1, 'techs.test_supply_elec.costs.monetary.energy_cap': 10 } build_model(override_dict=override7, scenario='simple_supply,one_day')