diff --git a/src/muse/agents/agent.py b/src/muse/agents/agent.py index f116ce944..bdab1a7eb 100644 --- a/src/muse/agents/agent.py +++ b/src/muse/agents/agent.py @@ -86,7 +86,6 @@ def next( technologies: xr.Dataset, market: xr.Dataset, demand: xr.DataArray, - time_period: int, ) -> None: """Increments agent to the next time point (e.g. performing investments).""" @@ -110,9 +109,8 @@ def __init__( search_rules: Optional[Callable] = None, objectives: Optional[Callable] = None, decision: Optional[Callable] = None, - year: int = 2010, + years: list[int] = [2010], maturity_threshold: float = 0, - forecast: int = 5, housekeeping: Optional[Callable] = None, merge_transform: Optional[Callable] = None, demand_threshold: Optional[float] = None, @@ -134,10 +132,9 @@ def __init__( search_rules: method used to filter the search space objectives: One or more objectives by which to decide next investments. decision: single decision objective from one or more objectives. - year: year the agent is created / current year + years: time framework over which the agent will iterate maturity_threshold: threshold when filtering replacement technologies with respect to market share - forecast: Number of years the agent will forecast housekeeping: transform applied to the assets at the start of iteration. Defaults to doing nothing. merge_transform: transform merging current and newly invested assets @@ -168,13 +165,15 @@ def __init__( timeslice_level=timeslice_level, ) - self.year = year - """ Current year. Incremented by one every time next is called.""" - self.forecast = forecast - """Number of years to look into the future for forecating purposed.""" - if search_rules is None: - search_rules = filter_factory() - self.search_rules: Callable = search_rules + """Years to iterate over.""" + self.years = iter(years) + + """Current year. Incremented every time `next` is called.""" + self.current_year: int | None = None + + """Investment year""" + self.investment_year: int = next(self.years) + """Search rule(s) determining potential replacement technologies. This is a string referring to a filter, or a sequence of strings @@ -182,25 +181,29 @@ def __init__( function registered via `muse.filters.register_filter` can be used to filter the search space. """ - self.maturity_threshold = maturity_threshold - """ Market share threshold. + if search_rules is None: + search_rules = filter_factory() + self.search_rules: Callable = search_rules + + """Market share threshold. Threshold when and if filtering replacement technologies with respect to market share. """ + self.maturity_threshold = maturity_threshold + self.spend_limit = spend_limit + """One or more objectives by which to decide next investments.""" if objectives is None: objectives = objectives_factory() self.objectives = objectives - """One or more objectives by which to decide next investments.""" + + """Creates single decision objective from one or more objectives.""" if decision is None: decision = decision_factory() self.decision = decision - """Creates single decision objective from one or more objectives.""" - if housekeeping is None: - housekeeping = housekeeping_factory() - self._housekeeping = housekeeping + """Transforms applied on the assets at the start of each iteration. It could mean keeping the assets as are, or removing assets with no @@ -208,28 +211,29 @@ def __init__( It can be any function registered with :py:func:`~muse.hooks.register_initial_asset_transform`. """ - if merge_transform is None: - merge_transform = asset_merge_factory() - self.merge_transform = merge_transform + if housekeeping is None: + housekeeping = housekeeping_factory() + self._housekeeping = housekeeping + """Transforms applied on the old and new assets. It could mean using only the new assets, or merging old and new, etc... It can be any function registered with :py:func:`~muse.hooks.register_final_asset_transform`. """ - self.demand_threshold = demand_threshold + if merge_transform is None: + merge_transform = asset_merge_factory() + self.merge_transform = merge_transform + """Threshold below which the demand share is zero. This criteria avoids fulfilling demand for very small values. If None, then the criteria is not applied. """ - self.asset_threshold = asset_threshold - """Threshold below which assets are not added.""" + self.demand_threshold = demand_threshold - @property - def forecast_year(self): - """Year to consider when forecasting.""" - return self.year + self.forecast + """Threshold below which assets are not added.""" + self.asset_threshold = asset_threshold def asset_housekeeping(self): """Reduces memory footprint of assets. @@ -250,9 +254,9 @@ def next( technologies: xr.Dataset, market: xr.Dataset, demand: xr.DataArray, - time_period: int, ) -> None: - self.year += time_period + self.current_year = self.investment_year + self.investment_year = next(self.years) class InvestingAgent(Agent): @@ -288,7 +292,6 @@ def next( technologies: xr.Dataset, market: xr.Dataset, demand: xr.DataArray, - time_period: int, ) -> None: """Iterates agent one turn. @@ -303,11 +306,12 @@ def next( from muse.utilities import reduce_assets - current_year = self.year + # Increment the year + self.current_year = self.investment_year + self.investment_year = next(self.years) # Skip forward if demand is zero if demand.size == 0 or demand.sum() < 1e-12: - self.year += time_period return None # Calculate the search space @@ -318,7 +322,6 @@ def next( # Skip forward if the search space is empty if any(u == 0 for u in search_space.shape): getLogger(__name__).critical("Search space is empty") - self.year += time_period return None # Calculate the decision metric @@ -337,12 +340,12 @@ def next( search = search.sel(asset=condtechs) # Get technology parameters for the investment year - techs = self.filter_input(technologies, year=current_year + time_period) + techs = self.filter_input(technologies, year=self.investment_year) - # Calculate capacity in current and forecast year + # Calculate capacity in current and investment year capacity = reduce_assets( self.assets.capacity, coords=("technology", "region") - ).interp(year=[current_year, current_year + self.forecast], method="linear") + ).interp(year=[self.current_year, self.investment_year], method="linear") # Calculate constraints constraints = self.constraints( @@ -358,21 +361,19 @@ def next( search[["search_space", "decision"]], technologies, constraints, - year=current_year, + current_year=self.current_year, timeslice_level=self.timeslice_level, ) # Add investments + time_period = self.investment_year - self.current_year self.add_investments( technologies, investments, - current_year=current_year, + current_year=self.current_year, time_period=time_period, ) - # Increment the year - self.year += time_period - def compute_decision( self, technologies: xr.Dataset, @@ -380,11 +381,11 @@ def compute_decision( demand: xr.DataArray, search_space: xr.DataArray, ) -> xr.DataArray: - # Filter technologies according to the search space, forecast year and region + # Filter technologies according to the search space, investment year and region techs = self.filter_input( technologies, technology=search_space.replacement, - year=self.forecast_year, + year=self.investment_year, ).drop_vars("technology") # Reduce dimensions of the demand array diff --git a/src/muse/agents/factories.py b/src/muse/agents/factories.py index 66d0b79c5..46996a17e 100644 --- a/src/muse/agents/factories.py +++ b/src/muse/agents/factories.py @@ -1,8 +1,10 @@ """Holds all building agents.""" +from __future__ import annotations + from collections.abc import Mapping, Sequence from pathlib import Path -from typing import Any, Callable, Optional, Union +from typing import Any, Callable import xarray as xr @@ -13,9 +15,9 @@ def create_standard_agent( technologies: xr.Dataset, capacity: xr.DataArray, - year: int, + years: list[int], region: str, - share: Optional[str] = None, + share: str | None = None, interpolation: str = "linear", **kwargs, ): @@ -24,10 +26,10 @@ def create_standard_agent( if share is not None: capacity = _shared_capacity( - technologies, capacity, region, share, year, interpolation=interpolation + technologies, capacity, region, share, years[0], interpolation=interpolation ) else: - existing = capacity.interp(year=year, method=interpolation) > 0 + existing = capacity.interp(year=years[0], method=interpolation) > 0 existing = existing.any([u for u in existing.dims if u != "asset"]) years = [capacity.year.min().values, capacity.year.max().values] capacity = xr.zeros_like(capacity.sel(asset=existing.values, year=years)) @@ -38,7 +40,7 @@ def create_standard_agent( assets=assets, region=region, search_rules=filter_factory(kwargs.pop("search_rules", None)), - year=year, + years=years, **kwargs, ) @@ -47,10 +49,10 @@ def create_retrofit_agent( technologies: xr.Dataset, capacity: xr.DataArray, share: str, - year: int, + years: list[int], region: str, interpolation: str = "linear", - decision: Union[Callable, str, Mapping] = "mean", + decision: Callable | str | Mapping = "mean", **kwargs, ): """Creates retrofit agent from muse primitives.""" @@ -69,7 +71,7 @@ def create_retrofit_agent( getLogger(__name__).warning(msg) assets = _shared_capacity( - technologies, capacity, region, share, year, interpolation=interpolation + technologies, capacity, region, share, years[0], interpolation=interpolation ) kwargs = _standardize_investing_inputs(decision=decision, **kwargs) @@ -83,21 +85,21 @@ def create_retrofit_agent( assets=xr.Dataset(dict(capacity=assets)), region=region, search_rules=filter_factory(search_rules), - year=year, + year=years[0], **kwargs, ) def create_newcapa_agent( capacity: xr.DataArray, - year: int, + years: list[int], region: str, share: str, - search_rules: Union[str, Sequence[str]] = "all", + search_rules: str | Sequence[str] = "all", interpolation: str = "linear", - merge_transform: Union[str, Mapping, Callable] = "new", + merge_transform: str | Mapping | Callable = "new", quantity: float = 0.3, - housekeeping: Union[str, Mapping, Callable] = "noop", + housekeeping: str | Mapping | Callable = "noop", retrofit_present: bool = True, **kwargs, ): @@ -112,19 +114,18 @@ def create_newcapa_agent( if "region" in capacity.dims: capacity = capacity.sel(region=region) - existing = capacity.interp(year=year, method=interpolation) > 0 + existing = capacity.interp(year=years[0], method=interpolation) > 0 assert set(existing.dims) == {"asset"} - years = [capacity.year.min().values, capacity.year.max().values] assets = xr.Dataset() if retrofit_present: assets["capacity"] = xr.zeros_like( - capacity.sel(asset=existing.values, year=years) + capacity.sel(asset=existing.values, year=years[0]) ) else: technologies = kwargs["technologies"] assets["capacity"] = _shared_capacity( - technologies, capacity, region, share, year, interpolation=interpolation + technologies, capacity, region, share, years[0], interpolation=interpolation ) merge_transform = "merge" @@ -153,7 +154,7 @@ def create_newcapa_agent( assets=assets, region=region, search_rules=filter_factory(search_rules), - year=year, + years=years, **kwargs, ) result.quantity = quantity # type: ignore @@ -173,11 +174,11 @@ def create_agent(agent_type: str, **kwargs) -> Agent: def agents_factory( - params_or_path: Union[str, Path, list], - capacity: Union[xr.DataArray, str, Path], + params_or_path: str | Path | list, + capacity: xr.DataArray | str | Path, technologies: xr.Dataset, - regions: Optional[Sequence[str]] = None, - year: Optional[int] = None, + regions: Sequence[str] | None = None, + years: list[int] | None = None, **kwargs, ) -> list[Agent]: """Creates a list of agents for the chosen sector.""" @@ -193,8 +194,6 @@ def agents_factory( if isinstance(capacity, (str, Path)): capacity = read_initial_assets(capacity) assert isinstance(capacity, xr.DataArray) - if year is None: - year = int(capacity.year.min()) if regions and "region" in capacity.dims: capacity = capacity.sel(region=regions) @@ -217,7 +216,7 @@ def agents_factory( # We deepcopy the capacity as it changes every iteration and needs to be # a separate object param["capacity"] = deepcopy(capacity.sel(region=param["region"])) - param["year"] = year + param["years"] = years param.update(kwargs) result.append(create_agent(**param, retrofit_present=retrofit_present)) @@ -273,12 +272,10 @@ def _shared_capacity( def _standardize_inputs( - housekeeping: Union[str, Mapping, Callable] = "clean", - merge_transform: Union[str, Mapping, Callable] = "merge", - objectives: Union[ - Callable, str, Mapping, Sequence[Union[str, Mapping]] - ] = "fixed_costs", - decision: Union[Callable, str, Mapping] = "mean", + housekeeping: str | Mapping | Callable = "clean", + merge_transform: str | Mapping | Callable = "merge", + objectives: Callable | str | Mapping | Sequence[str | Mapping] = "fixed_costs", + decision: Callable | str | Mapping = "mean", **kwargs, ): from muse.decisions import factory as decision_factory @@ -302,11 +299,9 @@ def _standardize_inputs( def _standardize_investing_inputs( - search_rules: Optional[Union[str, Sequence[str]]] = None, - investment: Union[Callable, str, Mapping] = "scipy", - constraints: Optional[ - Union[Callable, str, Mapping, Sequence[Union[str, Mapping]]] - ] = None, + search_rules: str | Sequence[str] | None = None, + investment: Callable | str | Mapping = "scipy", + constraints: Callable | str | Mapping | Sequence[str | Mapping] | None = None, **kwargs, ) -> dict[str, Any]: from muse.constraints import factory as constraints_factory diff --git a/src/muse/filters.py b/src/muse/filters.py index 03db5b7d0..365e93ee3 100644 --- a/src/muse/filters.py +++ b/src/muse/filters.py @@ -246,7 +246,7 @@ def same_enduse( tech_enduses = agent.filter_input( technologies.fixed_outputs, - year=agent.year, + year=agent.current_year, commodity=is_enduse(technologies.comm_usage), ) tech_enduses = (tech_enduses > 0).astype(int).rename(technology="replacement") @@ -295,7 +295,7 @@ def currently_existing_tech( capacity in the current year. See `currently_referenced_tech` for a similar filter that does not check the capacity. """ - capacity = agent.filter_input(market.capacity, year=agent.year).rename( + capacity = agent.filter_input(market.capacity, year=agent.current_year).rename( technology="replacement" ) result = search_space & search_space.replacement.isin(capacity.replacement) @@ -319,7 +319,7 @@ def currently_referenced_tech( currently sits at zero capacity (unlike `currently_existing_tech` which requires non-zero capacity in the current year). """ - capacity = agent.filter_input(market.capacity, year=agent.year).rename( + capacity = agent.filter_input(market.capacity, year=agent.current_year).rename( technology="replacement" ) return search_space & search_space.replacement.isin(capacity.replacement) @@ -338,7 +338,7 @@ def maturity( Specifically, the market share refers to the capacity for each end- use. """ - capacity = agent.filter_input(market.capacity, year=agent.year) + capacity = agent.filter_input(market.capacity, year=agent.current_year) total_capacity = capacity.sum("technology") enduse_market_share = agent.maturity_threshold * total_capacity condition = enduse_market_share <= capacity @@ -368,7 +368,7 @@ def spend_limit( ) -> xr.DataArray: """Only allows technologies with a unit capital cost lower than the spend limit.""" limit = agent.spend_limit - unit_capex = agent.filter_input(technologies.cap_par, year=agent.year) + unit_capex = agent.filter_input(technologies.cap_par, year=agent.current_year) condition = (unit_capex <= limit).rename("spend_limit") techs = ( condition.technology.where(condition, drop=True).drop_vars("technology").values diff --git a/src/muse/hooks.py b/src/muse/hooks.py index df21bb388..6de2cd0c6 100644 --- a/src/muse/hooks.py +++ b/src/muse/hooks.py @@ -130,7 +130,7 @@ def clean(agent: Agent, assets: Dataset) -> Dataset: """Removes empty assets.""" from muse.utilities import clean_assets - years = [agent.year, agent.forecast_year] + years = [agent.current_year, agent.investment_year] return clean_assets(assets, years) diff --git a/src/muse/investments.py b/src/muse/investments.py index af9275dcf..558916e03 100644 --- a/src/muse/investments.py +++ b/src/muse/investments.py @@ -20,7 +20,7 @@ def investment( search_space: xr.DataArray, technologies: xr.Dataset, constraints: List[Constraint], - year: int, + current_year: int, **kwargs ) -> xr.DataArray: pass @@ -34,7 +34,7 @@ def investment( technologies: a dataset containing all constant data characterizing the technologies. constraints: a list of constraints as defined in :py:mod:`~muse.constraints`. - year: the current year. + current_year: the current year. Returns: A data array with dimensions `asset` and `technology` specifying the amount @@ -224,7 +224,7 @@ def adhoc_match_demand( search_space: xr.DataArray, technologies: xr.Dataset, constraints: list[Constraint], - year: int, + current_year: int, timeslice_level: Optional[str] = None, ) -> xr.DataArray: from muse.demand_matching import demand_matching @@ -236,7 +236,7 @@ def adhoc_match_demand( max_prod = maximum_production( technologies, max_capacity, - year=year, + year=current_year, technology=costs.replacement, commodity=demand.commodity, timeslice_level=timeslice_level, @@ -258,7 +258,7 @@ def adhoc_match_demand( capacity = capacity_in_use( production, technologies, - year=year, + year=current_year, technology=production.replacement, timeslice_level=timeslice_level, ).drop_vars("technology") @@ -275,7 +275,7 @@ def scipy_match_demand( search_space: xr.DataArray, technologies: xr.Dataset, constraints: list[Constraint], - year: Optional[int] = None, + current_year: Optional[int] = None, timeslice_level: Optional[str] = None, **options, ) -> xr.DataArray: @@ -289,10 +289,10 @@ def scipy_match_demand( costs = timeslice_max(costs) # Select technodata for the current year - if "year" in technologies.dims and year is None: + if "year" in technologies.dims and current_year is None: raise ValueError("Missing year argument") elif "year" in technologies.dims: - techs = technologies.sel(year=year).drop_vars("year") + techs = technologies.sel(year=current_year).drop_vars("year") else: techs = technologies diff --git a/src/muse/mca.py b/src/muse/mca.py index e2560c4f4..db0e5d82c 100644 --- a/src/muse/mca.py +++ b/src/muse/mca.py @@ -522,7 +522,7 @@ def check_equilibrium( int_market: Dataset, tolerance: float, equilibrium_variable: str, - year: int | None = None, + investment_year: int, ) -> bool: """Checks if equilibrium has been reached. @@ -535,24 +535,23 @@ def check_equilibrium( int_market: The market values in the previous iteration. tolerance: Tolerance for reaching equilibrium. equilibrium_variable: Variable to use to calculate the equilibrium condition. - year: year for which to check changes. Default to minimum year in market. + investment_year: year for which to check changes. Returns: True if converged, False otherwise. """ from numpy import abs - if year is None: - year = market.year.min() - if equilibrium_variable == "demand": delta = ( - market.consumption.sel(year=year) - - market.supply.sel(year=year) - - int_market.consumption.sel(year=year) - + int_market.supply.sel(year=year) + market.consumption.sel(year=investment_year) + - market.supply.sel(year=investment_year) + - int_market.consumption.sel(year=investment_year) + + int_market.supply.sel(year=investment_year) ) else: - delta = market.prices.sel(year=year) - int_market.prices.sel(year=year) + delta = market.prices.sel(year=investment_year) - int_market.prices.sel( + year=investment_year + ) return bool((abs(delta) < tolerance).all()) diff --git a/src/muse/outputs/cache.py b/src/muse/outputs/cache.py index 71712cd95..d6169a079 100644 --- a/src/muse/outputs/cache.py +++ b/src/muse/outputs/cache.py @@ -354,8 +354,8 @@ def extract_agents_internal( info[aid]["category"] = agent.category info[aid]["sector"] = sector_name info[aid]["dst_region"] = agent.region - info[aid]["year"] = agent.forecast_year - info[aid]["installed"] = agent.year + # info[aid]["year"] = agent.investment_year + info[aid]["installed"] = agent.current_year return info diff --git a/src/muse/outputs/mca.py b/src/muse/outputs/mca.py index cc17fac7a..b37124d96 100644 --- a/src/muse/outputs/mca.py +++ b/src/muse/outputs/mca.py @@ -256,15 +256,14 @@ def sector_fuel_costs( agent_market = market.copy() if len(technologies) > 0: for a in agents: - output_year = a.year - a.forecast agent_market["consumption"] = (market.consumption * a.quantity).sel( - year=output_year + year=a.current_year ) commodity = is_fuel(technologies.comm_usage) capacity = a.filter_input( a.assets.capacity, - year=output_year, + year=a.current_year, ).fillna(0.0) production = supply( @@ -273,7 +272,7 @@ def sector_fuel_costs( technologies, ) - prices = a.filter_input(market.prices, year=output_year) + prices = a.filter_input(market.prices, year=a.current_year) fcons = consumption( technologies=technologies, production=production, prices=prices ) @@ -282,7 +281,7 @@ def sector_fuel_costs( data_agent["agent"] = a.name data_agent["category"] = a.category data_agent["sector"] = getattr(sector, "name", "unnamed") - data_agent["year"] = output_year + data_agent["year"] = a.current_year data_agent = multiindex_to_coords(data_agent, "timeslice").to_dataframe( "fuel_consumption_costs" ) @@ -314,18 +313,19 @@ def sector_capital_costs( if len(technologies) > 0: for a in agents: - output_year = a.year - a.forecast - capacity = a.filter_input(a.assets.capacity, year=output_year).fillna(0.0) + capacity = a.filter_input(a.assets.capacity, year=a.current_year).fillna( + 0.0 + ) data = a.filter_input( technologies[["cap_par", "cap_exp"]], - year=output_year, + year=a.current_year, technology=capacity.technology, ) data_agent = distribute_timeslice(data.cap_par * (capacity**data.cap_exp)) data_agent["agent"] = a.name data_agent["category"] = a.category data_agent["sector"] = getattr(sector, "name", "unnamed") - data_agent["year"] = output_year + data_agent["year"] = a.current_year data_agent = multiindex_to_coords(data_agent, "timeslice").to_dataframe( "capital_costs" ) @@ -361,23 +361,26 @@ def sector_emission_costs( agent_market = market.copy() if len(technologies) > 0: for a in agents: - output_year = a.year - a.forecast agent_market["consumption"] = (market.consumption * a.quantity).sel( - year=output_year + year=a.current_year ) - capacity = a.filter_input(a.assets.capacity, year=output_year).fillna(0.0) + capacity = a.filter_input(a.assets.capacity, year=a.current_year).fillna( + 0.0 + ) allemissions = a.filter_input( technologies.fixed_outputs, commodity=is_pollutant(technologies.comm_usage), technology=capacity.technology, - year=output_year, + year=a.current_year, ) envs = is_pollutant(technologies.comm_usage) enduses = is_enduse(technologies.comm_usage) i = (np.where(envs))[0][0] red_envs = envs[i].commodity.values - prices = a.filter_input(market.prices, year=output_year, commodity=red_envs) + prices = a.filter_input( + market.prices, year=a.current_year, commodity=red_envs + ) production = supply( agent_market, capacity, @@ -389,7 +392,7 @@ def sector_emission_costs( data_agent["agent"] = a.name data_agent["category"] = a.category data_agent["sector"] = getattr(sector, "name", "unnamed") - data_agent["year"] = output_year + data_agent["year"] = a.current_year data_agent = multiindex_to_coords(data_agent, "timeslice").to_dataframe( "emission_costs" ) @@ -426,8 +429,7 @@ def sector_lcoe(sector: AbstractSector, market: xr.Dataset, **kwargs) -> pd.Data agents = retro if len(retro) > 0 else new if len(technologies) > 0: for agent in agents: - output_year = agent.year - agent.forecast - agent_market = market.sel(year=output_year).copy() + agent_market = market.sel(year=agent.current_year).copy() agent_market["consumption"] = agent_market.consumption * agent.quantity included = [ i @@ -438,15 +440,15 @@ def sector_lcoe(sector: AbstractSector, market: xr.Dataset, **kwargs) -> pd.Data i for i in agent_market["commodity"].values if i not in included ] agent_market.loc[dict(commodity=excluded)] = 0 - years = [output_year, agent.year] + years = [agent.current_year, agent.investment_year] agent_market["prices"] = agent.filter_input(market["prices"], year=years) techs = agent.filter_input( technologies, - year=agent.year, + year=agent.current_year, ) prices = agent_market["prices"].sel( - commodity=techs.commodity, year=agent.year + commodity=techs.commodity, year=agent.current_year ) demand = agent_market.consumption.sel(commodity=included) capacity = agent.filter_input(capacity_to_service_demand(demand, techs)) @@ -472,7 +474,7 @@ def sector_lcoe(sector: AbstractSector, market: xr.Dataset, **kwargs) -> pd.Data data_agent["agent"] = agent.name data_agent["category"] = agent.category data_agent["sector"] = getattr(sector, "name", "unnamed") - data_agent["year"] = output_year + data_agent["year"] = agent.current_year data_agent = data_agent.fillna(0) data_agent = multiindex_to_coords(data_agent, "timeslice").to_dataframe( "LCOE" @@ -509,8 +511,7 @@ def sector_eac(sector: AbstractSector, market: xr.Dataset, **kwargs) -> pd.DataF agents = retro if len(retro) > 0 else new if len(technologies) > 0: for agent in agents: - output_year = agent.year - agent.forecast - agent_market = market.sel(year=output_year).copy() + agent_market = market.sel(year=agent.current_year).copy() agent_market["consumption"] = agent_market.consumption * agent.quantity included = [ i @@ -521,15 +522,15 @@ def sector_eac(sector: AbstractSector, market: xr.Dataset, **kwargs) -> pd.DataF i for i in agent_market["commodity"].values if i not in included ] agent_market.loc[dict(commodity=excluded)] = 0 - years = [output_year, agent.year] + years = [agent.current_year, agent.investment_year] agent_market["prices"] = agent.filter_input(market["prices"], year=years) techs = agent.filter_input( technologies, - year=agent.year, + year=agent.current_year, ) prices = agent_market["prices"].sel( - commodity=techs.commodity, year=agent.year + commodity=techs.commodity, year=agent.current_year ) demand = agent_market.consumption.sel(commodity=included) capacity = agent.filter_input(capacity_to_service_demand(demand, techs)) @@ -554,7 +555,7 @@ def sector_eac(sector: AbstractSector, market: xr.Dataset, **kwargs) -> pd.DataF data_agent["agent"] = agent.name data_agent["category"] = agent.category data_agent["sector"] = getattr(sector, "name", "unnamed") - data_agent["year"] = output_year + data_agent["year"] = agent.current_year data_agent = multiindex_to_coords(data_agent, "timeslice").to_dataframe( "capital_costs" ) diff --git a/src/muse/sectors/sector.py b/src/muse/sectors/sector.py index 79f15b1ad..5e0607c3f 100644 --- a/src/muse/sectors/sector.py +++ b/src/muse/sectors/sector.py @@ -47,7 +47,7 @@ def factory(cls, name: str, settings: Any) -> Sector: subsec_settings, technologies, regions=settings.regions, - current_year=int(min(settings.time_framework)), + years=settings.time_framework.tolist(), name=subsec_name, timeslice_level=sector_settings.get("timeslice_level", None), ) @@ -198,7 +198,6 @@ def next( def group_assets(x: xr.DataArray) -> xr.DataArray: return xr.Dataset(dict(x=x)).groupby("region").sum("asset").x - time_period = int(mca_market.year.max() - mca_market.year.min()) current_year = int(mca_market.year.min()) getLogger(__name__).info(f"Running {self.name} for year {current_year}") @@ -218,7 +217,6 @@ def group_assets(x: xr.DataArray) -> xr.DataArray: subsector.invest( self.technologies, market, - time_period=time_period, current_year=current_year, ) diff --git a/src/muse/sectors/subsector.py b/src/muse/sectors/subsector.py index 925119715..712176549 100644 --- a/src/muse/sectors/subsector.py +++ b/src/muse/sectors/subsector.py @@ -52,7 +52,6 @@ def invest( self, technologies: xr.Dataset, market: xr.Dataset, - time_period: int, current_year: int, ) -> None: # Expand prices to include destination region (for trade models) @@ -67,13 +66,12 @@ def invest( agent.asset_housekeeping() # Perform the investments - self.aggregate_lp(technologies, market, time_period, current_year=current_year) + self.aggregate_lp(technologies, market, current_year=current_year) def aggregate_lp( self, technologies: xr.Dataset, market: xr.Dataset, - time_period, current_year, ) -> None: # Split demand across agents @@ -100,7 +98,8 @@ def aggregate_lp( share = demands.sel(asset=demands.agent == agent.uuid) else: share = demands - agent.next(technologies, market, share, time_period=time_period) + agent.next(technologies, market, share) + assert agent.current_year == current_year @classmethod def factory( @@ -108,7 +107,7 @@ def factory( settings: Any, technologies: xr.Dataset, regions: Sequence[str] | None = None, - current_year: int | None = None, + years: list[int] | None = None, name: str = "subsector", timeslice_level: str | None = None, ) -> Subsector: @@ -138,7 +137,7 @@ def factory( settings.existing_capacity, technologies=technologies, regions=regions, - year=current_year or int(technologies.year.min()), + years=years, asset_threshold=getattr(settings, "asset_threshold", 1e-12), # only used by self-investing agents investment=getattr(settings, "lpsolver", "scipy"), diff --git a/tests/conftest.py b/tests/conftest.py index bef2e1442..c4a445dcc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -340,7 +340,7 @@ def create_agent(agent_args, technologies, stock, agent_type="retrofit") -> Agen agent_type=agent_type, technologies=technologies, capacity=stock, - year=2010, + years=[2010, 2030], **agent_args, ) diff --git a/tests/test_agents.py b/tests/test_agents.py index ea351d9b0..373039df6 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -38,7 +38,7 @@ def test_create_retrofit(agent_args, technologies, stock): agent_type="Retrofit", technologies=technologies, capacity=stock.capacity, - year=2010, + years=[2010, 2030], **agent_args, ) assert isinstance(agent, Agent) @@ -53,7 +53,7 @@ def test_create_retrofit(agent_args, technologies, stock): agent_type="Retrofit", technologies=technologies, capacity=stock.capacity, - year=2010, + years=[2010, 2030], **agent_args, ) assert isinstance(agent, Agent) @@ -73,7 +73,7 @@ def test_create_newcapa(agent_args, technologies, stock): agent_type="Newcapa", technologies=technologies, capacity=stock.capacity, - year=2010, + years=[2010, 2030], **agent_args, ) assert isinstance(agent, Agent) @@ -89,7 +89,7 @@ def test_create_newcapa(agent_args, technologies, stock): agent_type="Newcapa", technologies=technologies, capacity=stock.capacity, - year=2010, + years=[2010, 2030], **agent_args, ) assert isinstance(agent, Agent) @@ -105,7 +105,7 @@ def test_create_newcapa(agent_args, technologies, stock): agent_type="Newcapa", technologies=technologies, capacity=stock.capacity, - year=2010, + years=[2010, 2030], **agent_args, ) @@ -126,7 +126,7 @@ def test_issue_835_and_842(agent_args, technologies, stock): technologies=technologies, capacity=stock.capacity, search_rules="from_techs->compress", - year=2010, + years=[2010, 2030], **agent_args, ) assert isinstance(agent, Agent) @@ -143,7 +143,7 @@ def test_run_retro_agent(retro_agent, technologies, agent_market, demand_share): technologies.max_capacity_addition[:] = retro_agent.assets.capacity.sum() * 100 technologies.max_capacity_growth[:] = retro_agent.assets.capacity.sum() * 100 - retro_agent.next(technologies, agent_market, demand_share, time_period=5) + retro_agent.next(technologies, agent_market, demand_share) def test_merge_assets(assets): diff --git a/tests/test_filters.py b/tests/test_filters.py index 43c5a01ca..21f48a288 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -69,7 +69,7 @@ def test_same_enduse(retro_agent, technologies, search_space): result = same_enduse(retro_agent, search_space, technologies) enduses = is_enduse(technologies.comm_usage) finputs = technologies.sel( - region=retro_agent.region, year=retro_agent.year, commodity=enduses + region=retro_agent.region, year=retro_agent.current_year, commodity=enduses ) finputs = finputs.fixed_outputs > 0 @@ -146,7 +146,7 @@ def test_currently_existing(retro_agent, search_space, technologies, agent_marke assert sorted(actual.dims) == sorted(search_space.dims) assert not actual.sel(replacement=~in_market).any() current_cap = agent_market.capacity.sel( - year=retro_agent.year, region=retro_agent.region + year=retro_agent.current_year, region=retro_agent.region ).rename(technology="replacement") expected = (current_cap > retro_agent.tolerance).rename("expected") assert (actual.sel(replacement=in_market) == expected).all() diff --git a/tests/test_outputs.py b/tests/test_outputs.py index db011e150..daf75bffb 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -554,8 +554,8 @@ def test_consolidate_quantity(newcapa_agent, retro_agent): (*agents[retro_agent.uuid].keys(), "installed", "year", "technology", quantity) ) assert set(actual.columns) == cols - assert all(actual.year == newcapa_agent.forecast_year) - assert all(actual.installed == newcapa_agent.year) + assert all(actual.year == newcapa_agent.investment_year) + assert all(actual.installed == newcapa_agent.current_year) assert all( name in (newcapa_agent.name, retro_agent.name) for name in actual.agent.unique() ) diff --git a/tests/test_subsector.py b/tests/test_subsector.py index 3994ece31..fa400f857 100644 --- a/tests/test_subsector.py +++ b/tests/test_subsector.py @@ -46,9 +46,11 @@ def test_subsector_investing_aggregation(): ).interp(year=[2020, 2025]) subsector = Subsector(agents, commodities) initial_agents = deepcopy(agents) - assert {agent.year for agent in agents} == {int(market.year.min())} - subsector.aggregate_lp(technologies, market, time_period=5, current_year=5) - assert {agent.year for agent in agents} == {int(market.year.min() + 5)} + assert {agent.current_year for agent in agents} == {int(market.year.min())} + subsector.aggregate_lp(technologies, market, current_year=2025) + assert {agent.current_year for agent in agents} == { + int(market.year.min() + 5) + } for initial, final in zip(initial_agents, agents): assert initial.assets.sum() != final.assets.sum() @@ -103,8 +105,8 @@ def test_subsector_noninvesting_aggregation(market, model, technologies, tmp_pat market = market.sel( commodity=technologies.commodity, region=technologies.region ).interp(year=[2020, 2025]) - assert all(agent.year == 2020 for agent in agents) - subsector.aggregate_lp(technologies, market, time_period=5, current_year=2020) + assert all(agent.current_year == 2020 for agent in agents) + subsector.aggregate_lp(technologies, market, current_year=2020) def test_factory_smoke_test(model, technologies, tmp_path):