From 088465437f4498ef1925201fc68760f5cddbdc0e Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Tue, 6 May 2025 13:39:26 +0100 Subject: [PATCH 1/6] Fix error defining subsector commoties when zero existing capacity --- src/muse/agents/factories.py | 6 ++---- src/muse/sectors/subsector.py | 14 ++++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/muse/agents/factories.py b/src/muse/agents/factories.py index 7fcde0a9..5f86a071 100644 --- a/src/muse/agents/factories.py +++ b/src/muse/agents/factories.py @@ -169,7 +169,7 @@ def create_agent(agent_type: str, **kwargs) -> Agent: def agents_factory( params_or_path: str | Path | list, - capacity: xr.DataArray | str | Path, + capacity: xr.DataArray, technologies: xr.Dataset, regions: Sequence[str] | None = None, year: int | None = None, @@ -179,14 +179,12 @@ def agents_factory( from copy import deepcopy from logging import getLogger - from muse.readers import read_csv_agent_parameters, read_initial_assets + from muse.readers import read_csv_agent_parameters if isinstance(params_or_path, (str, Path)): params = read_csv_agent_parameters(params_or_path) else: params = params_or_path - if isinstance(capacity, (str, Path)): - capacity = read_initial_assets(capacity) assert isinstance(capacity, xr.DataArray) if year is None: year = int(capacity.year.min()) diff --git a/src/muse/sectors/subsector.py b/src/muse/sectors/subsector.py index 3b2ddee5..c79374d6 100644 --- a/src/muse/sectors/subsector.py +++ b/src/muse/sectors/subsector.py @@ -120,6 +120,7 @@ def factory( from muse import investments as iv from muse.agents import InvestingAgent, agents_factory from muse.commodities import is_enduse + from muse.readers import read_initial_assets from muse.readers.toml import undo_damage # Raise error for renamed asset_threshhold parameter (PR #447) @@ -142,9 +143,13 @@ def factory( ) getLogger(__name__).warning(msg) + # Read existing capacity file + existing_capacity = read_initial_assets(settings.existing_capacity) + + # Create agents agents = agents_factory( settings.agents, - settings.existing_capacity, + capacity=existing_capacity, technologies=technologies, regions=regions, year=current_year or int(technologies.year.min()), @@ -172,12 +177,13 @@ def factory( if np.sum(outputs) == 0.0: raise RuntimeError(msg) + # Get list of commodities for the subsector if hasattr(settings, "commodities"): commodities = settings.commodities else: - commodities = aggregate_enduses( - [agent.assets for agent in agents], technologies - ) + # If commodities aren't explicitly specified, we infer the commodities from + # the existing capacity file + commodities = aggregate_enduses(existing_capacity, technologies) # len(commodities) == 0 may happen only if # we run only one region or all regions have no outputs From 4608d2c66fce006aec12a8ee0f0d5a12fedd9897 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 7 May 2025 15:42:58 +0100 Subject: [PATCH 2/6] Small change to DLC --- src/muse/constraints.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/muse/constraints.py b/src/muse/constraints.py index be0dcbc3..cf975a73 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -548,8 +548,9 @@ def demand_limiting_capacity( # meet the demand which would be a combination of a high demand and a low # utilization factor. if "timeslice" in b.dims or "timeslice" in capacity.dims: - ratio = b / capacity - ts_index = ratio.min("replacement").argmax("timeslice") + ratio = b / capacity.max("replacement") + ratio = ratio.where(b != 0, 0.0) + ts_index = ratio.argmax("timeslice") b = b.isel(timeslice=ts_index) capacity = capacity.isel(timeslice=ts_index) From 2154d74d0dc84acb8e53d28acb6f9be368432db2 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 7 May 2025 15:50:29 +0100 Subject: [PATCH 3/6] Only keep commodities that have demand --- src/muse/constraints.py | 5 ++--- src/muse/sectors/subsector.py | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/muse/constraints.py b/src/muse/constraints.py index cf975a73..be0dcbc3 100644 --- a/src/muse/constraints.py +++ b/src/muse/constraints.py @@ -548,9 +548,8 @@ def demand_limiting_capacity( # meet the demand which would be a combination of a high demand and a low # utilization factor. if "timeslice" in b.dims or "timeslice" in capacity.dims: - ratio = b / capacity.max("replacement") - ratio = ratio.where(b != 0, 0.0) - ts_index = ratio.argmax("timeslice") + ratio = b / capacity + ts_index = ratio.min("replacement").argmax("timeslice") b = b.isel(timeslice=ts_index) capacity = capacity.isel(timeslice=ts_index) diff --git a/src/muse/sectors/subsector.py b/src/muse/sectors/subsector.py index c79374d6..29c9c1d2 100644 --- a/src/muse/sectors/subsector.py +++ b/src/muse/sectors/subsector.py @@ -79,6 +79,11 @@ def aggregate_lp( # Select commodity demands for the subsector demands = market.consumption.sel(commodity=self.commodities) + # Remove commodities that have no demand + demands = demands.sel( + commodity=demands.sum([u for u in demands.dims if u != "commodity"]) > 0.0 + ) + # Split demand across agents demands = self.demand_share( agents=self.agents, From d354e72fcc268dee21cd2071afbc2dd88c50a07d Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 7 May 2025 16:03:30 +0100 Subject: [PATCH 4/6] Better method --- src/muse/sectors/subsector.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/muse/sectors/subsector.py b/src/muse/sectors/subsector.py index 29c9c1d2..883fe1cf 100644 --- a/src/muse/sectors/subsector.py +++ b/src/muse/sectors/subsector.py @@ -79,10 +79,9 @@ def aggregate_lp( # Select commodity demands for the subsector demands = market.consumption.sel(commodity=self.commodities) - # Remove commodities that have no demand - demands = demands.sel( - commodity=demands.sum([u for u in demands.dims if u != "commodity"]) > 0.0 - ) + # Remove commodities that have no demand in the investment year + mask = (demands.isel(year=1, drop=True) > 0).any(dim=["timeslice", "region"]) + demands = demands.sel(commodity=mask) # Split demand across agents demands = self.demand_share( From 298570f02cb8660d0bfc310db1e4749423cd630f Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 7 May 2025 17:15:00 +0100 Subject: [PATCH 5/6] Simplify aggregate_enduses --- src/muse/sectors/subsector.py | 23 ++++++----------------- tests/test_subsector.py | 6 ++---- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/muse/sectors/subsector.py b/src/muse/sectors/subsector.py index 883fe1cf..203076b1 100644 --- a/src/muse/sectors/subsector.py +++ b/src/muse/sectors/subsector.py @@ -187,7 +187,9 @@ def factory( else: # If commodities aren't explicitly specified, we infer the commodities from # the existing capacity file - commodities = aggregate_enduses(existing_capacity, technologies) + commodities = aggregate_enduses( + technologies.sel(technology=existing_capacity.technology.values) + ) # len(commodities) == 0 may happen only if # we run only one region or all regions have no outputs @@ -218,21 +220,8 @@ def factory( ) -def aggregate_enduses( - assets: Sequence[xr.Dataset | xr.DataArray], technologies: xr.Dataset -) -> Sequence[str]: - """Aggregate enduse commodities for input assets. - - This function is meant as a helper to figure out the commodities attached to a group - of agents. - """ +def aggregate_enduses(technologies: xr.Dataset) -> list[str]: + """Aggregate enduse commodities for a set of technologies.""" from muse.commodities import is_enduse - techs = set.union(*(set(data.technology.values) for data in assets)) - outputs = technologies.fixed_outputs.sel( - commodity=is_enduse(technologies.comm_usage), technology=list(techs) - ) - - return outputs.commodity.sel( - commodity=outputs.any([u for u in outputs.dims if u != "commodity"]) - ).values.tolist() + return technologies.commodity.values[is_enduse(technologies.comm_usage)].tolist() diff --git a/tests/test_subsector.py b/tests/test_subsector.py index 23406139..efcbc1fb 100644 --- a/tests/test_subsector.py +++ b/tests/test_subsector.py @@ -36,9 +36,7 @@ def test_subsector_investing_aggregation(): agents = list(examples.sector(sname, model).agents) sector = next(sector for sector in mca.sectors if sector.name == sname) technologies = sector.technologies - commodities = aggregate_enduses( - (agent.assets for agent in agents), technologies - ) + commodities = aggregate_enduses(technologies) market = mca.market.sel( commodity=technologies.commodity, region=technologies.region ).interp(year=[2020, 2025]) @@ -89,7 +87,7 @@ def test_subsector_noninvesting_aggregation(market, model, technologies, tmp_pat param["decision"]["parameters"] = ("ALCOE", False, 1) param.pop("quantity") agents = [create_agent(technologies=technologies, **param) for param in params] - commodities = aggregate_enduses((agent.assets for agent in agents), technologies) + commodities = aggregate_enduses(technologies) subsector = Subsector( agents, From 2b056ce8ef35fedf12aea91166213319fbba3fcb Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Thu, 8 May 2025 10:27:02 +0100 Subject: [PATCH 6/6] Update docstring --- src/muse/sectors/subsector.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/muse/sectors/subsector.py b/src/muse/sectors/subsector.py index 203076b1..7852d689 100644 --- a/src/muse/sectors/subsector.py +++ b/src/muse/sectors/subsector.py @@ -221,7 +221,13 @@ def factory( def aggregate_enduses(technologies: xr.Dataset) -> list[str]: - """Aggregate enduse commodities for a set of technologies.""" + """Aggregate enduse commodities for a set of technologies. + + Returns a list of all enduse commodities associated with the technologies in the + input dataset. Enduse commodities are determined using based on the `comm_usage` + attribute of the technologies, using the `is_enduse` function from the + `muse.commodities` module. + """ from muse.commodities import is_enduse return technologies.commodity.values[is_enduse(technologies.comm_usage)].tolist()