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`'
        )
Beispiel #3
0
    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')
Beispiel #5
0
 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.")
Beispiel #8
0
    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"
        )
Beispiel #10
0
    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')
Beispiel #11
0
    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')
Beispiel #13
0
    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')
Beispiel #14
0
 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"
     )
Beispiel #15
0
 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")
Beispiel #17
0
    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."
        )
Beispiel #18
0
 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'
     )
Beispiel #19
0
 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"
     )
Beispiel #20
0
 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"
     )
Beispiel #21
0
    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'")
Beispiel #22
0
    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.')
Beispiel #24
0
    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")
Beispiel #27
0
    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")
Beispiel #30
0
 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`'
         )
Beispiel #31
0
    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)
Beispiel #32
0
    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'")
Beispiel #33
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_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.')
Beispiel #43
0
    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')