From e59cc5b165496c18b78923ad0f99866d0faac79e Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 5 Jun 2024 10:52:47 +0100 Subject: [PATCH 1/5] Spelling --- src/muse/constraints.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/muse/constraints.py b/src/muse/constraints.py index 6ac3c7255..01bc717d3 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -1,6 +1,6 @@ r"""Investment constraints. -Constraints on investements ensure that investements match some given criteria. For +Constraints on investments ensure that investments match some given criteria. For instance, the constraints could ensure that only so much of a new asset can be built every year. @@ -8,7 +8,7 @@ :py:meth:`~muse.constraints.register_constraints`. This registration step makes it possible for constraints to be declared in the TOML file. -Generally, LP solvers accept linear constraint defined as: +Generally, LP solvers accept linear constraints defined as: .. math:: @@ -16,8 +16,8 @@ with :math:`A` a matrix, :math:`x` the decision variables, and :math:`b` a vector. However, these quantities are dimensionless. They do no have timeslices, assets, or -replacement technologies, or any other dimensions that users have set-up in their model. -The crux is to translates from MUSE's data-structures to a consistent dimensionless +replacement technologies, or any other dimensions that users have set up in their model. +The crux is to translate from MUSE's data-structures to a consistent dimensionless format. In MUSE, users can register constraints functions that return fully dimensional @@ -44,8 +44,8 @@ - Any dimension in :math:`A_c .* x_c` (:math:`A_p .* x_p`) that is also in :math:`b` defines diagonal entries into the left (right) submatrix of :math:`A`. - Any dimension in :math:`A_c .* x_c` (:math:`A_p .* x_b`) and missing from - :math:`b` is reduce by summation over a row in the left (right) submatrix of - :math:`A`. In other words, those dimension do become part of a standard tensor + :math:`b` is reduced by summation over a row in the left (right) submatrix of + :math:`A`. In other words, those dimensions become part of a standard tensor reduction or matrix multiplication. There are two additional rules. However, they are likely to be the result of an @@ -288,7 +288,7 @@ def max_capacity_expansion( :math:`y=y_1` is the year marking the end of the investment period. Let :math:`\mathcal{A}^{i, r}_{t, \iota}(y)` be the current assets, before - invesment, and let :math:`\Delta\mathcal{A}^{i,r}_t` be the future investements. + investment, and let :math:`\Delta\mathcal{A}^{i,r}_t` be the future investments. The the constraint on agent :math:`i` are given as: .. math:: From c7a0101fb494d337020e90e02170f94462fd73cf Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 5 Jun 2024 11:40:18 +0100 Subject: [PATCH 2/5] Raise error if minimum service factor is <0 or >1 Fixes #320. --- src/muse/constraints.py | 7 +++++++ tests/test_minimum_service.py | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/muse/constraints.py b/src/muse/constraints.py index 01bc717d3..e4beb1e14 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -519,6 +519,13 @@ def minimum_service( return None if np.all(technologies["minimum_service_factor"] == 0): return None + + min_service_factor = technologies["minimum_service_factor"] + if not np.all((0 <= min_service_factor) & (min_service_factor <= 1)): + raise ValueError( + "Minimum service factor values must all be between 0 and 1 inclusive" + ) + if year is None: year = int(market.year.min()) commodities = technologies.commodity.sel( diff --git a/tests/test_minimum_service.py b/tests/test_minimum_service.py index ded2b50d1..0f33599e7 100644 --- a/tests/test_minimum_service.py +++ b/tests/test_minimum_service.py @@ -1,3 +1,4 @@ +import numpy as np from pytest import mark @@ -23,7 +24,8 @@ def modify_minimum_service_factors( @mark.parametrize("process_name", [("gasCCGT", "windturbine")]) @mark.parametrize( - "minimum_service_factor", [([1, 2, 3, 4, 5, 6], [0] * 6), ([0], [1, 2, 3, 4, 5, 6])] + "minimum_service_factor", + [(np.linspace(0, 1, 6), [0] * 6), ([0], np.linspace(0, 1, 6))], ) def test_minimum_service_factor(tmpdir, minimum_service_factor, process_name): import pandas as pd From 7a1751b7bafd51858d4b30b5bca675dc0b266d52 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 5 Jun 2024 14:18:54 +0100 Subject: [PATCH 3/5] Remove pointless return value The data argument is just passed through unchanged. --- src/muse/readers/csv.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/muse/readers/csv.py b/src/muse/readers/csv.py index a7bc5ba71..581cafec0 100644 --- a/src/muse/readers/csv.py +++ b/src/muse/readers/csv.py @@ -129,7 +129,7 @@ def read_technodata_timeslices(filename: Union[Text, Path]) -> xr.Dataset: data = csv[csv.technology != "Unit"] data = data.apply(lambda x: pd.to_numeric(x, errors="ignore")) - data = check_utilization_not_all_zero(data, filename) + check_utilization_not_all_zero(data, filename) ts = pd.MultiIndex.from_frame( data.drop( @@ -921,4 +921,3 @@ def check_utilization_not_all_zero(data, filename): """A technology can not have a utilization factor of 0 for every timeslice. Please check file {}.""".format(filename) ) - return data From fe1fc9690012fd5b8ecea54d9416c784f6b97a8b Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 5 Jun 2024 14:44:53 +0100 Subject: [PATCH 4/5] test_minimum_service.py: Refactor for readability --- tests/test_minimum_service.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/test_minimum_service.py b/tests/test_minimum_service.py index 0f33599e7..2064cf869 100644 --- a/tests/test_minimum_service.py +++ b/tests/test_minimum_service.py @@ -1,9 +1,11 @@ +from itertools import permutations + import numpy as np from pytest import mark def modify_minimum_service_factors( - model_path, sector, process_name, minimum_service_factor + model_path, sector, processes, minimum_service_factors ): import pandas as pd @@ -11,28 +13,25 @@ def modify_minimum_service_factors( model_path / "technodata" / sector / "TechnodataTimeslices.csv" ) - technodata_timeslices.loc[ - technodata_timeslices["ProcessName"] == process_name[0], "MinimumServiceFactor" - ] = minimum_service_factor[0] - - technodata_timeslices.loc[ - technodata_timeslices["ProcessName"] == process_name[1], "MinimumServiceFactor" - ] = minimum_service_factor[1] + for process, minimum in zip(processes, minimum_service_factors): + technodata_timeslices.loc[ + technodata_timeslices["ProcessName"] == process, "MinimumServiceFactor" + ] = minimum return technodata_timeslices -@mark.parametrize("process_name", [("gasCCGT", "windturbine")]) @mark.parametrize( - "minimum_service_factor", - [(np.linspace(0, 1, 6), [0] * 6), ([0], np.linspace(0, 1, 6))], + "minimum_service_factors", + permutations((np.linspace(0, 1, 6), [0] * 6)), ) -def test_minimum_service_factor(tmpdir, minimum_service_factor, process_name): +def test_minimum_service_factor(tmpdir, minimum_service_factors): import pandas as pd from muse import examples from muse.mca import MCA sector = "power" + processes = ("gasCCGT", "windturbine") # Copy the model inputs to tmpdir model_path = examples.copy_model( @@ -42,8 +41,8 @@ def test_minimum_service_factor(tmpdir, minimum_service_factor, process_name): technodata_timeslices = modify_minimum_service_factors( model_path=model_path, sector=sector, - process_name=process_name, - minimum_service_factor=minimum_service_factor, + processes=processes, + minimum_service_factors=minimum_service_factors, ) technodata_timeslices.to_csv( @@ -55,7 +54,7 @@ def test_minimum_service_factor(tmpdir, minimum_service_factor, process_name): supply_timeslice = pd.read_csv(tmpdir / "Results/MCAMetric_Supply.csv") - for process, service_factor in zip(process_name, minimum_service_factor): + for process, service_factor in zip(processes, minimum_service_factors): for i, factor in enumerate(service_factor): assert ( supply_timeslice[ From 945eb54bfefeb3f4333d7a2a8a3ab1ce48517de9 Mon Sep 17 00:00:00 2001 From: Alex Dewar Date: Wed, 5 Jun 2024 15:33:35 +0100 Subject: [PATCH 5/5] Move check that minimum service factors are in range to csv.py --- src/muse/constraints.py | 7 ------- src/muse/readers/csv.py | 12 ++++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/muse/constraints.py b/src/muse/constraints.py index e4beb1e14..01bc717d3 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -519,13 +519,6 @@ def minimum_service( return None if np.all(technologies["minimum_service_factor"] == 0): return None - - min_service_factor = technologies["minimum_service_factor"] - if not np.all((0 <= min_service_factor) & (min_service_factor <= 1)): - raise ValueError( - "Minimum service factor values must all be between 0 and 1 inclusive" - ) - if year is None: year = int(market.year.min()) commodities = technologies.commodity.sel( diff --git a/src/muse/readers/csv.py b/src/muse/readers/csv.py index 581cafec0..2f8608c4c 100644 --- a/src/muse/readers/csv.py +++ b/src/muse/readers/csv.py @@ -114,6 +114,9 @@ def to_agent_share(name): if "year" in result.dims and len(result.year) == 1: result = result.isel(year=0, drop=True) + + check_minimum_service_factors_in_range(data, filename) + return result @@ -921,3 +924,12 @@ def check_utilization_not_all_zero(data, filename): """A technology can not have a utilization factor of 0 for every timeslice. Please check file {}.""".format(filename) ) + + +def check_minimum_service_factors_in_range(data, filename): + min_service_factor = data["minimum_service_factor"] + if not np.all((0 <= min_service_factor) & (min_service_factor <= 1)): + raise ValueError( + f"""Minimum service factor values must all be between 0 and 1 inclusive. + Please check file {filename}.""" + )