diff --git a/src/muse/agents/__init__.py b/src/muse/agents/__init__.py index 069f1c2ef..392b04e73 100644 --- a/src/muse/agents/__init__.py +++ b/src/muse/agents/__init__.py @@ -2,10 +2,9 @@ "AbstractAgent", "Agent", "InvestingAgent", - "factory", "agents_factory", "create_agent", ] from muse.agents.agent import AbstractAgent, Agent, InvestingAgent -from muse.agents.factories import agents_factory, create_agent, factory +from muse.agents.factories import agents_factory, create_agent diff --git a/src/muse/agents/agent.py b/src/muse/agents/agent.py index fb34dffb4..8d7b503ae 100644 --- a/src/muse/agents/agent.py +++ b/src/muse/agents/agent.py @@ -80,14 +80,9 @@ def next( technologies: xr.Dataset, market: xr.Dataset, demand: xr.DataArray, - time_period: int = 1, - ): - """Iterates agent one turn. - - The goal is to figure out from market variables which technologies to invest in - and by how much. - """ - pass + time_period: int, + ) -> None: + """Increments agent to the next time point (e.g. performing investments).""" def __repr__(self): return ( @@ -98,10 +93,7 @@ def __repr__(self): class Agent(AbstractAgent): - """Agent that is capable of computing a search-space and a cost metric. - - This agent will not perform any investment itself. - """ + """Standard agent that does not perform investments.""" def __init__( self, @@ -124,7 +116,7 @@ def __init__( spend_limit: int = 0, **kwargs, ): - """Creates a standard buildings agent. + """Creates a standard agent. Arguments: name: Name of the agent, used for cross-refencing external tables @@ -166,10 +158,7 @@ def __init__( ) self.year = year - """ Current year. - - The year is incremented by one every time next is called. - """ + """ 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: @@ -250,8 +239,46 @@ def next( technologies: xr.Dataset, market: xr.Dataset, demand: xr.DataArray, - time_period: int = 1, - ) -> Optional[xr.Dataset]: + time_period: int, + ) -> None: + self.year += time_period + + +class InvestingAgent(Agent): + """Agent that performs investment for itself.""" + + def __init__( + self, + *args, + constraints: Optional[Callable] = None, + investment: Optional[Callable] = None, + **kwargs, + ): + """Creates an investing agent. + + Arguments: + *args: See :py:class:`~muse.agents.agent.Agent` + constraints: Set of constraints limiting investment + investment: A function to perform investments + **kwargs: See :py:class:`~muse.agents.agent.Agent` + """ + from muse.constraints import factory as csfactory + from muse.investments import factory as ifactory + + super().__init__(*args, **kwargs) + + self.invest = investment or ifactory() + """Method to use when fulfilling demand from rated set of techs.""" + self.constraints = constraints or csfactory() + """Creates a set of constraints limiting investment.""" + + def next( + self, + technologies: xr.Dataset, + market: xr.Dataset, + demand: xr.DataArray, + time_period: int, + ) -> None: """Iterates agent one turn. The goal is to figure out from market variables which technologies to @@ -263,22 +290,75 @@ def next( """ from logging import getLogger - # dataset with intermediate computational results from search - # makes it easier to pass intermediate results to functions, as well as - # filter them when inside a function + current_year = self.year + + # 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 search_space = ( self.search_rules(self, demand, technologies, market).fillna(0).astype(int) ) + # 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 + decision = self.compute_decision(technologies, market, demand, search_space) + search = xr.Dataset(dict(search_space=search_space, decision=decision)) + if "timeslice" in search.dims: + search["demand"] = drop_timeslice(demand) + else: + search["demand"] = demand + + # Filter assets with demand + not_assets = [u for u in search.demand.dims if u != "asset"] + condtechs = ( + search.demand.sum(not_assets) > getattr(self, "tolerance", 1e-8) + ).values + search = search.sel(asset=condtechs) + + # Calculate constraints + constraints = self.constraints( + search.demand, + self.assets, + search.search_space, + market, + technologies, + year=current_year, + ) + + # Calculate investments + investments = self.invest( + search[["search_space", "decision"]], + technologies, + constraints, + year=current_year, + ) + + # Add investments + self.add_investments( + technologies, + investments, + current_year=current_year, + time_period=time_period, + ) + + # Increment the year + self.year += time_period + + def compute_decision( + self, + technologies: xr.Dataset, + market: xr.Dataset, + demand: xr.DataArray, + search_space: xr.DataArray, + ) -> xr.DataArray: # Filter technologies according to the search space, forecast year and region techs = self.filter_input( technologies, @@ -297,23 +377,12 @@ def next( # Filter prices according to the region prices = self.filter_input(market.prices) - # Compute the objective - decision = self._compute_objective( + # Compute the objectives + objectives = self.objectives( technologies=techs, demand=reduced_demand, prices=prices ) - self.year += time_period - return xr.Dataset(dict(search_space=search_space, decision=decision)) - - def _compute_objective( - self, - technologies: xr.Dataset, - demand: xr.DataArray, - prices: xr.DataArray, - ) -> xr.DataArray: - objectives = self.objectives( - technologies=technologies, demand=demand, prices=prices - ) + # Compute the decision metric decision = self.decision(objectives) return decision @@ -323,12 +392,12 @@ def add_investments( investments: xr.DataArray, current_year: int, time_period: int, - ): + ) -> None: """Add new assets to the agent.""" + # Calculate retirement profile of new assets new_capacity = self.retirement_profile( technologies, investments, current_year, time_period ) - if new_capacity is None: return new_capacity = new_capacity.drop_vars( @@ -336,6 +405,7 @@ def add_investments( ) new_assets = xr.Dataset(dict(capacity=new_capacity)) + # Merge new assets with existing assets self.assets = self.merge_transform(self.assets, new_assets) def retirement_profile( @@ -347,10 +417,13 @@ def retirement_profile( ) -> Optional[xr.DataArray]: from muse.investments import cliff_retirement_profile + # Sum investments if "asset" in investments.dims: investments = investments.sum("asset") if "agent" in investments.dims: investments = investments.squeeze("agent", drop=True) + + # Filter out investments below the threshold investments = investments.sel( replacement=(investments > self.asset_threshold).any( [d for d in investments.dims if d != "replacement"] @@ -359,22 +432,22 @@ def retirement_profile( if investments.size == 0: return None - # figures out the retirement profile for the new investments + # Calculate the retirement profile for new investments + # Note: technical life must be at least the length of the time period lifetime = self.filter_input( technologies.technical_life, year=current_year, technology=investments.replacement, - ) + ).clip(min=time_period) profile = cliff_retirement_profile( - lifetime.clip(min=time_period), - current_year=current_year + time_period, - protected=max(self.forecast - time_period - 1, 0), + lifetime, + investment_year=current_year + time_period, ) if "dst_region" in investments.coords: investments = investments.reindex_like(profile, method="ffill") + # Apply the retirement profile to the investments new_assets = (investments * profile).rename(replacement="asset") - new_assets["installed"] = "asset", [current_year] * len(new_assets.asset) # The new assets have picked up quite a few coordinates along the way. @@ -383,89 +456,3 @@ def retirement_profile( new, old = new_assets.dims, self.assets.dims raise RuntimeError(f"Asset dimensions do not match: {new} vs {old}") return new_assets - - -class InvestingAgent(Agent): - """Agent that performs investment for itself.""" - - def __init__( - self, - *args, - constraints: Optional[Callable] = None, - investment: Optional[Callable] = None, - **kwargs, - ): - """Creates a standard buildings agent. - - Arguments: - *args: See :py:class:`~muse.agents.agent.Agent` - constraints: Set of constraints limiting investment - investment: A function to perform investments - **kwargs: See :py:class:`~muse.agents.agent.Agent` - """ - from muse.constraints import factory as csfactory - from muse.investments import factory as ifactory - - super().__init__(*args, **kwargs) - - if investment is None: - investment = ifactory() - self.invest = investment - """Method to use when fulfilling demand from rated set of techs.""" - if not callable(constraints): - constraints = csfactory() - self.constraints = constraints - """Creates a set of constraints limiting investment.""" - - def next( - self, - technologies: xr.Dataset, - market: xr.Dataset, - demand: xr.DataArray, - time_period: int = 1, - ): - """Iterates agent one turn. - - The goal is to figure out from market variables which technologies to - invest in and by how much. - - This function will modify `self.assets` and increment `self.year`. - Other attributes are left unchanged. Arguments to the function are - never modified. - """ - current_year = self.year - search = super().next(technologies, market, demand, time_period=time_period) - if search is None: - return None - - if "timeslice" in search.dims: - search["demand"] = drop_timeslice(demand) - else: - search["demand"] = demand - not_assets = [u for u in search.demand.dims if u != "asset"] - condtechs = ( - search.demand.sum(not_assets) > getattr(self, "tolerance", 1e-8) - ).values - search = search.sel(asset=condtechs) - constraints = self.constraints( - search.demand, - self.assets, - search.search_space, - market, - technologies, - year=current_year, - ) - - investments = self.invest( - search[["search_space", "decision"]], - technologies, - constraints, - year=current_year, - ) - - self.add_investments( - technologies, - investments, - current_year=self.year - time_period, - time_period=time_period, - ) diff --git a/src/muse/agents/factories.py b/src/muse/agents/factories.py index efd7056fd..95ab91fd8 100644 --- a/src/muse/agents/factories.py +++ b/src/muse/agents/factories.py @@ -7,7 +7,6 @@ import xarray as xr from muse.agents.agent import Agent, InvestingAgent -from muse.defaults import DEFAULT_SECTORS_DIRECTORY from muse.errors import RetrofitAgentNotDefined, TechnologyNotDefined @@ -20,7 +19,7 @@ def create_standard_agent( interpolation: str = "linear", **kwargs, ): - """Creates retrofit agent from muse primitives.""" + """Creates standard (noninvesting) agent from muse primitives.""" from muse.filters import factory as filter_factory if share is not None: @@ -114,7 +113,7 @@ def create_newcapa_agent( capacity = capacity.sel(region=region) existing = capacity.interp(year=year, method=interpolation) > 0 - assert set(existing.dims) == {"asset"} + # assert set(existing.dims) == {"asset"} years = [capacity.year.min().values, capacity.year.max().values] assets = xr.Dataset() @@ -173,96 +172,6 @@ def create_agent(agent_type: str, **kwargs) -> Agent: return method(**kwargs) # type: ignore -def factory( - existing_capacity_path: Optional[Union[Path, str]] = None, - agent_parameters_path: Optional[Union[Path, str]] = None, - technodata_path: Optional[Union[Path, str]] = None, - technodata_timeslices_path: Optional[Union[str, Path]] = None, - sector: Optional[str] = None, - sectors_directory: Union[str, Path] = DEFAULT_SECTORS_DIRECTORY, - baseyear: int = 2010, -) -> list[Agent]: - """Reads list of agents from standard MUSE input files.""" - from copy import deepcopy - from logging import getLogger - from textwrap import dedent - - from muse.readers import ( - read_csv_agent_parameters, - read_initial_assets, - read_technodata_timeslices, - read_technodictionary, - ) - from muse.readers.csv import find_sectors_file - - if sector is None: - assert existing_capacity_path is not None - assert agent_parameters_path is not None - assert technodata_path is not None - - if existing_capacity_path is None: - existing_capacity_path = find_sectors_file( - f"Existing{sector}.csv", sector, sectors_directory - ) - if agent_parameters_path is None: - agent_parameters_path = find_sectors_file( - f"BuildingAgent{sector}.csv", sector, sectors_directory - ) - if technodata_path is None: - technodata_path = find_sectors_file( - f"technodata{sector}.csv", sector, sectors_directory - ) - - params = read_csv_agent_parameters(agent_parameters_path) - techno = read_technodictionary(technodata_path) - capa = read_initial_assets(existing_capacity_path) - if technodata_timeslices_path and isinstance( - technodata_timeslices_path, (str, Path) - ): - technodata_timeslices = read_technodata_timeslices(technodata_timeslices_path) - else: - technodata_timeslices = None - result = [] - for param in params: - if param["agent_type"] == "retrofit": - param["technologies"] = techno.sel(region=param["region"]) - if technodata_timeslices is not None: - param.drop_vars("utilization_factor") - param = param.merge(technodata_timeslices.sel(region=param["region"])) - param["category"] = param["agent_type"] - param["capacity"] = deepcopy(capa.sel(region=param["region"])) - param["year"] = baseyear - result.append(create_agent(**param)) - - nregs = len({u.region for u in result}) - types = [u.name for u in result] - msg = dedent( - """\ - Read agents for sector {name} from: - - agent parameter file {para} - - technologies data file {tech} - - initial capacity file {ini} - - Found {n} agents across {nregs} regions{end} - """.format( - n=len(result), - name=sector, - para=agent_parameters_path, - tech=technodata_path, - ini=existing_capacity_path, - nregs=nregs, - end="." if len(result) == 0 else ", with:\n", - ) - ) - for t in set(types): - n = types.count(t) - msg += " - {n} {t} agent{plural}\n".format( - n=n, t=t, plural="" if n == 1 else "s" - ) - getLogger(__name__).info(msg) - return result - - def agents_factory( params_or_path: Union[str, Path, list], capacity: Union[xr.DataArray, str, Path], diff --git a/src/muse/data/example/trade/input/Projections.csv b/src/muse/data/example/trade/input/Projections.csv index 72a3c8165..1ca5d8c9d 100644 --- a/src/muse/data/example/trade/input/Projections.csv +++ b/src/muse/data/example/trade/input/Projections.csv @@ -1,40 +1,40 @@ RegionName,Attribute,Time,electricity,gas,heat,CO2f,wind -Unit,-,Year,MUS$2010/PJ,MUS$2010/PJ,MUS$2010/PJ,MUS$2010/kt,MUS$2010/PJ -R1,CommodityPrice,2010,12.61111111,3.8972,0,0,0 -R1,CommodityPrice,2015,13.14814806,4.307,0,0,0 -R1,CommodityPrice,2020,13.98148139,4.7168,0,0,0 -R1,CommodityPrice,2025,16.27777778,5.1266,0,0,0 -R1,CommodityPrice,2030,17.1574075,5.5364,0,0,0 -R1,CommodityPrice,2035,17.50925917,5.9462,0,0,0 -R1,CommodityPrice,2040,18.05555556,6.3559,0,0,0 -R1,CommodityPrice,2045,20.37962972,6.7657,0,0,0 -R1,CommodityPrice,2050,21.5,7.1755,0,0,0 -R1,CommodityPrice,2055,22.12037028,7.1755,0,0,0 -R1,CommodityPrice,2060,22.00925917,7.1755,0,0,0 -R1,CommodityPrice,2065,22.14814806,7.1755,0,0,0 -R1,CommodityPrice,2070,22,7.1755,0,0,0 -R1,CommodityPrice,2075,22.11111111,7.1755,0,0,0 -R1,CommodityPrice,2080,21.92592583,7.1755,0,0,0 -R1,CommodityPrice,2085,21.51851861,7.1755,0,0,0 -R1,CommodityPrice,2090,21.31481472,7.1755,0,0,0 -R1,CommodityPrice,2095,21.03703694,7.1755,0,0,0 -R1,CommodityPrice,2100,20.46296306,7.1755,0,0,0 -R2,CommodityPrice,2010,17.33333333,3.8972,0,0,0 -R2,CommodityPrice,2015,15.66666667,4.307,0,0,0 -R2,CommodityPrice,2020,19.13888889,4.7168,0,0,0 -R2,CommodityPrice,2025,22.86111111,5.1266,0,0,0 -R2,CommodityPrice,2030,24.08333333,5.5364,0,0,0 -R2,CommodityPrice,2035,20.63888889,5.9462,0,0,0 -R2,CommodityPrice,2040,21.32407417,6.3559,0,0,0 -R2,CommodityPrice,2045,20.38888889,6.7657,0,0,0 -R2,CommodityPrice,2050,19.37037028,7.1755,0,0,0 -R2,CommodityPrice,2055,19.13888889,7.1755,0,0,0 -R2,CommodityPrice,2060,21.0925925,7.1755,0,0,0 -R2,CommodityPrice,2065,22.89814806,7.1755,0,0,0 -R2,CommodityPrice,2070,22.94444444,7.1755,0,0,0 -R2,CommodityPrice,2075,21.60185194,7.1755,0,0,0 -R2,CommodityPrice,2080,21.93518528,7.1755,0,0,0 -R2,CommodityPrice,2085,21.87962972,7.1755,0,0,0 -R2,CommodityPrice,2090,22.06481472,7.1755,0,0,0 -R2,CommodityPrice,2095,23.08333333,7.1755,0,0,0 -R2,CommodityPrice,2100,23.82407417,7.1755,0,0,0 +Unit,-,Year,MUS$2010/PJ,MUS$2010/PJ,MUS$2010/PJ,MUS$2010/kt,MUS$2010/kt +R1,CommodityPrice,2010,14.81481472,6.6759,100,0,0 +R1,CommodityPrice,2015,17.89814806,6.914325,100,0.052913851,0 +R1,CommodityPrice,2020,19.5,7.15275,100,0.08314119,0 +R1,CommodityPrice,2025,21.93518528,8.10645,100,0.120069795,0 +R1,CommodityPrice,2030,26.50925917,9.06015,100,0.156998399,0 +R1,CommodityPrice,2035,26.51851861,9.2191,100,0.214877567,0 +R1,CommodityPrice,2040,23.85185194,9.37805,100,0.272756734,0 +R1,CommodityPrice,2045,23.97222222,9.193829337,100,0.35394801,0 +R1,CommodityPrice,2050,24.06481472,9.009608674,100,0.435139285,0 +R1,CommodityPrice,2055,25.3425925,8.832625604,100,0.542365578,0 +R1,CommodityPrice,2060,25.53703694,8.655642534,100,0.649591871,0 +R1,CommodityPrice,2065,25.32407417,8.485612708,100,0.780892624,0 +R1,CommodityPrice,2070,23.36111111,8.315582883,100,0.912193378,0 +R1,CommodityPrice,2075,22.27777778,8.152233126,100,1.078321687,0 +R1,CommodityPrice,2080,22.25925917,7.988883368,100,1.244449995,0 +R1,CommodityPrice,2085,22.17592583,7.831951236,100,1.4253503,0 +R1,CommodityPrice,2090,22.03703694,7.675019103,100,1.606250604,0 +R1,CommodityPrice,2095,21.94444444,7.524252461,100,1.73877515,0 +R1,CommodityPrice,2100,21.39814806,7.373485819,100,1.871299697,0 +R2,CommodityPrice,2010,14.81481472,6.6759,100,0,0 +R2,CommodityPrice,2015,17.89814806,6.914325,100,0.052913851,0 +R2,CommodityPrice,2020,19.5,7.15275,100,0.08314119,0 +R2,CommodityPrice,2025,21.93518528,8.10645,100,0.120069795,0 +R2,CommodityPrice,2030,26.50925917,9.06015,100,0.156998399,0 +R2,CommodityPrice,2035,26.51851861,9.2191,100,0.214877567,0 +R2,CommodityPrice,2040,23.85185194,9.37805,100,0.272756734,0 +R2,CommodityPrice,2045,23.97222222,9.193829337,100,0.35394801,0 +R2,CommodityPrice,2050,24.06481472,9.009608674,100,0.435139285,0 +R2,CommodityPrice,2055,25.3425925,8.832625604,100,0.542365578,0 +R2,CommodityPrice,2060,25.53703694,8.655642534,100,0.649591871,0 +R2,CommodityPrice,2065,25.32407417,8.485612708,100,0.780892624,0 +R2,CommodityPrice,2070,23.36111111,8.315582883,100,0.912193378,0 +R2,CommodityPrice,2075,22.27777778,8.152233126,100,1.078321687,0 +R2,CommodityPrice,2080,22.25925917,7.988883368,100,1.244449995,0 +R2,CommodityPrice,2085,22.17592583,7.831951236,100,1.4253503,0 +R2,CommodityPrice,2090,22.03703694,7.675019103,100,1.606250604,0 +R2,CommodityPrice,2095,21.94444444,7.524252461,100,1.73877515,0 +R2,CommodityPrice,2100,21.39814806,7.373485819,100,1.871299697,0 diff --git a/src/muse/data/example/trade/settings.toml b/src/muse/data/example/trade/settings.toml index a851e6a96..3b829604a 100644 --- a/src/muse/data/example/trade/settings.toml +++ b/src/muse/data/example/trade/settings.toml @@ -1,6 +1,6 @@ # Global settings - most REQUIRED -time_framework = [2020, 2025, 2030, 2035] +time_framework = [2020, 2025, 2030, 2035, 2040, 2045, 2050] foresight = 5 # Has to be a multiple of the minimum separation between the years in time framework regions = ["R1", "R2"] interest_rate = 0.1 @@ -30,25 +30,18 @@ budget = [] projections = '{path}/input/Projections.csv' global_commodities = '{path}/input/GlobalCommodities.csv' -[sectors.presets] -type = 'presets' -priority = 0 -consumption_path = "{path}/technodata/preset/*Consumption.csv" - [sectors.residential] type = 'default' priority = 1 dispatch_production = 'share' - -[sectors.residential.technodata] technodata = '{path}/technodata/residential/Technodata.csv' commodities_in = '{path}/technodata/residential/CommIn.csv' commodities_out = '{path}/technodata/residential/CommOut.csv' -[sectors.residential.subsectors.retro_and_new] -agents = '{path}/technodata/residential/Agents.csv' +[sectors.residential.subsectors.all] +agents = '{path}/technodata/Agents.csv' existing_capacity = '{path}/technodata/residential/ExistingCapacity.csv' -lpsolver = "scipy" +lpsolver = "scipy" # Optional, defaults to "adhoc" constraints = [ # Optional, defaults to the constraints below "max_production", @@ -58,41 +51,22 @@ constraints = [ "minimum_service", "demand_limiting_capacity" ] -demand_share = "new_and_retro" -forecast = 5 -asset_threshold = 1e-4 - -[[sectors.residential.interactions]] -net = 'new_to_retro' -interaction = 'transfer' +demand_share = "standard_demand" # Optional, default to standard_demand +forecast = 5 # Optional, defaults to 5 [sectors.power] type = 'default' priority = 2 dispatch_production = 'share' - -[sectors.power.technodata] technodata = '{path}/technodata/power/Technodata.csv' -trade = '{path}/technodata/power/TradeTechnodata.csv' commodities_in = '{path}/technodata/power/CommIn.csv' commodities_out = '{path}/technodata/power/CommOut.csv' -[sectors.power.subsectors.trade] +[sectors.power.subsectors.all] agents = '{path}/technodata/Agents.csv' -existing_capacity = '{path}/technodata/power/ExistingTrade.csv' +existing_capacity = '{path}/technodata/power/ExistingCapacity.csv' lpsolver = "scipy" -constraints = [ - # Optional, defaults to the constraints below - "max_production", - "max_capacity_expansion", - "demand", - "search_space", - "minimum_service", - "demand_limiting_capacity" -] -demand_share = "unmet_forecasted_demand" -forecast = 5 -asset_threshold = 1e-4 +demand_share = "standard_demand" [sectors.gas] type = 'default' @@ -106,27 +80,23 @@ commodities_in = '{path}/technodata/gas/CommIn.csv' commodities_out = '{path}/technodata/gas/CommOut.csv' [sectors.gas.subsectors.trade] -agents = '{path}/technodata/Agents.csv' +agents = '{path}/technodata/gas/Agents.csv' existing_capacity = '{path}/technodata/gas/ExistingTrade.csv' lpsolver = "scipy" -constraints = [ - # Optional, defaults to the constraints below - "max_production", - "max_capacity_expansion", - "demand", - "search_space", - "minimum_service", - "demand_limiting_capacity" -] demand_share = "unmet_forecasted_demand" -forecast = 5 -asset_threshold = 1e-4 + +[sectors.residential_presets] +type = 'presets' +priority = 0 +consumption_path = "{path}/technodata/preset/*Consumption.csv" [timeslices] level_names = ["month", "day", "hour"] -all-year.all-week.night = 1460 -all-year.all-week.morning = 1460 -all-year.all-week.afternoon = 1460 -all-year.all-week.early-peak = 1460 -all-year.all-week.late-peak = 1460 -all-year.all-week.evening = 1460 + +[timeslices.all-year.all-week] +night = 1460 +morning = 1460 +afternoon = 1460 +early-peak = 1460 +late-peak = 1460 +evening = 1460 diff --git a/src/muse/data/example/trade/technodata/Agents.csv b/src/muse/data/example/trade/technodata/Agents.csv index 42fd21d17..c5f87c36e 100644 --- a/src/muse/data/example/trade/technodata/Agents.csv +++ b/src/muse/data/example/trade/technodata/Agents.csv @@ -1,3 +1,3 @@ -AgentShare,Name,RegionName,Objective,ObjData,Objsort,SearchRule,DecisionMethod,MaturityThreshold,SpendLimit,Type -agent_share,A1,R1,ALCOE,1,TRUE,from_assets->compress->reduce_assets,singleObj,-1,inf,default -agent_share,A1,R2,ALCOE,1,TRUE,from_assets->compress->reduce_assets,singleObj,-1,inf,default +AgentShare,Name,RegionName,Objective1,Objective2,Objective3,ObjData1,ObjData2,ObjData3,Objsort1,Objsort2,Objsort3,SearchRule,DecisionMethod,Quantity,MaturityThreshold,SpendLimit,Type +Agent1,A1,R1,LCOE,,,1,,,True,,,all,singleObj,1,-1,inf,New +Agent1,A1,R2,LCOE,,,1,,,True,,,all,singleObj,1,-1,inf,New diff --git a/src/muse/data/example/trade/technodata/gas/Agents.csv b/src/muse/data/example/trade/technodata/gas/Agents.csv new file mode 100644 index 000000000..504c3c4e3 --- /dev/null +++ b/src/muse/data/example/trade/technodata/gas/Agents.csv @@ -0,0 +1,3 @@ +AgentShare,Name,RegionName,Objective1,Objective2,Objective3,ObjData1,ObjData2,ObjData3,Objsort1,Objsort2,Objsort3,SearchRule,DecisionMethod,MaturityThreshold,SpendLimit,Type +Agent1,A1,R1,ALCOE,,,1,,,True,,,from_assets->compress->reduce_assets,singleObj,-1,inf,New +Agent1,A1,R2,ALCOE,,,1,,,True,,,from_assets->compress->reduce_assets,singleObj,-1,inf,New diff --git a/src/muse/data/example/trade/technodata/gas/CommIn.csv b/src/muse/data/example/trade/technodata/gas/CommIn.csv index 6550aed24..0e85ade1f 100644 --- a/src/muse/data/example/trade/technodata/gas/CommIn.csv +++ b/src/muse/data/example/trade/technodata/gas/CommIn.csv @@ -1,5 +1,4 @@ ProcessName,RegionName,Time,Level,electricity,gas,heat,CO2f,wind Unit,-,Year,-,PJ/PJ,PJ/PJ,PJ/PJ,kt/PJ,PJ/PJ -gassupply1,R1,2010,fixed,0,1,0,0,0 -gassupply1,R2,2010,fixed,0,1,0,0,0 -gassupply1,R3,2010,fixed,0,1,0,0,0 +gassupply1,R1,2020,fixed,0,0,0,0,0 +gassupply1,R2,2020,fixed,0,0,0,0,0 diff --git a/src/muse/data/example/trade/technodata/gas/CommOut.csv b/src/muse/data/example/trade/technodata/gas/CommOut.csv index 2458e775d..f0eb38ea4 100644 --- a/src/muse/data/example/trade/technodata/gas/CommOut.csv +++ b/src/muse/data/example/trade/technodata/gas/CommOut.csv @@ -1,4 +1,4 @@ ProcessName,RegionName,Time,electricity,gas,heat,CO2f,wind Unit,-,Year,PJ/PJ,PJ/PJ,PJ/PJ,kt/PJ,PJ/PJ -gassupply1,R1,2010,0,1,0,0,0 -gassupply1,R2,2010,0,1,0,0,0 +gassupply1,R1,2020,0,1,0,0,0 +gassupply1,R2,2020,0,1,0,0,0 diff --git a/src/muse/data/example/trade/technodata/gas/ExistingTrade.csv b/src/muse/data/example/trade/technodata/gas/ExistingTrade.csv index fb3ffb9e7..a3f61b3c4 100644 --- a/src/muse/data/example/trade/technodata/gas/ExistingTrade.csv +++ b/src/muse/data/example/trade/technodata/gas/ExistingTrade.csv @@ -1,12 +1,15 @@ ProcessName,RegionName,Time,R1,R2 Unit,-,Year,PJ/y,PJ/y -gassupply1,R1,2010,3000,0 -gassupply1,R1,2020,3000,0 -gassupply1,R1,2030,2100,0 -gassupply1,R1,2040,1470,0 -gassupply1,R1,2050,1029,0 -gassupply1,R2,2010,0,1200 -gassupply1,R2,2020,0,1200 -gassupply1,R2,2030,0,700 -gassupply1,R2,2040,0,490 -gassupply1,R2,2050,0,343 +gassupply1,R1,2020,15,0 +gassupply1,R1,2025,15,0 +gassupply1,R1,2030,7.5,0 +gassupply1,R1,2035,0,0 +gassupply1,R1,2040,0,0 +gassupply1,R1,2045,0,0 +gassupply1,R1,2050,0,0 +gassupply1,R2,2020,0,15 +gassupply1,R2,2025,0,15 +gassupply1,R2,2035,0,7.5 +gassupply1,R2,2040,0,0 +gassupply1,R2,2045,0,0 +gassupply1,R2,2050,0,0 diff --git a/src/muse/data/example/trade/technodata/gas/Technodata.csv b/src/muse/data/example/trade/technodata/gas/Technodata.csv index b418fd53e..91d976b70 100644 --- a/src/muse/data/example/trade/technodata/gas/Technodata.csv +++ b/src/muse/data/example/trade/technodata/gas/Technodata.csv @@ -1,4 +1,4 @@ -ProcessName,RegionName,Time,var_par,var_exp,TechnicalLife,UtilizationFactor,ScalingSize,efficiency,InterestRate,Type,Fuel,EndUse,AgentShare -Unit,-,Year,MUS$2010/PJ,-,Years,-,PJ,%,-,-,-,-,- -gassupply1,R1,2010,3,1,10,0.9,0.00000189,86,0.1,energy,gas,gas,1 -gassupply1,R2,2010,3,1,10,0.9,0.00000189,86,0.1,energy,gas,gas,1 +ProcessName,RegionName,Time,cap_par,cap_exp,fix_par,fix_exp,var_exp,MaxCapacityAddition,MaxCapacityGrowth,TotalCapacityLimit,TechnicalLife,UtilizationFactor,ScalingSize,efficiency,InterestRate,Type,Fuel,EndUse,Agent1 +Unit,-,Year,MUS$2010/PJ_a,-,MUS$2010/PJ,-,-,PJ,%,PJ,Years,-,PJ,%,-,-,-,-,New +gassupply1,R1,2020,0,1,0,1,1,10,1,100,35,0.9,0.00000189,86,0.1,energy,gas,gas,1 +gassupply1,R2,2020,0,1,0,1,1,10,1,100,35,0.9,0.00000189,86,0.1,energy,gas,gas,1 diff --git a/src/muse/data/example/trade/technodata/gas/TradeTechnodata.csv b/src/muse/data/example/trade/technodata/gas/TradeTechnodata.csv index 9a940ae5c..300dc555d 100644 --- a/src/muse/data/example/trade/technodata/gas/TradeTechnodata.csv +++ b/src/muse/data/example/trade/technodata/gas/TradeTechnodata.csv @@ -1,15 +1,3 @@ -ProcessName,RegionName,Parameter,Unit,R1,R2,R3 -gassupply1,R1,cap_par,MUSD/PJ,3,98.013,25 -gassupply1,R2,cap_par,MUSD/PJ,7.8406,5,7.8406 -gassupply1,R1,cap_exp,MUSD/PJ,1,1,1 -gassupply1,R2,cap_exp,MUSD/PJ,1,1,1 -gassupply1,R1,fix_par,MUSD/PJ,0.3,0.98013,0.90612 -gassupply1,R2,fix_par,MUSD/PJ,0.78406,0.5,0.17434 -gassupply1,R1,fix_exp,MUSD/PJ,1,1,1 -gassupply1,R2,fix_exp,MUSD/PJ,1,1,1 -gassupply1,R1,MaxCapacityAddition,PJ/y,200,0,0.1 -gassupply1,R2,MaxCapacityAddition,PJ/y,0,200,0 -gassupply1,R1,MaxCapacityGrowth,PJ/y,1,0,0.002 -gassupply1,R2,MaxCapacityGrowth,PJ/y,0,1,0 -gassupply1,R1,TotalCapacityLimit,PJ/y,3937.219,0,1 -gassupply1,R2,TotalCapacityLimit,PJ/y,0,3937.219,0 +ProcessName,RegionName,Parameter,Unit,R1,R2 +gassupply1,R1,var_par,MUSD/PJ,2.55,2.55 +gassupply1,R2,var_par,MUSD/PJ,2.55,2.55 diff --git a/src/muse/data/example/trade/technodata/power/CommIn.csv b/src/muse/data/example/trade/technodata/power/CommIn.csv index e4095d85b..f910382d9 100644 --- a/src/muse/data/example/trade/technodata/power/CommIn.csv +++ b/src/muse/data/example/trade/technodata/power/CommIn.csv @@ -1,6 +1,6 @@ ProcessName,RegionName,Time,Level,electricity,gas,heat,CO2f,wind Unit,-,Year,-,PJ/PJ,PJ/PJ,PJ/PJ,kt/PJ,PJ/PJ -gasCCGT,R1,2010,fixed,0,1.67,0,0,0 -windturbine,R1,2010,fixed,0,0,0,0,1 -gasCCGT,R2,2010,fixed,0,2,0,0,0 -windturbine,R2,2010,fixed,0,0,0,0,1 +gasCCGT,R1,2020,fixed,0,1.67,0,0,0 +windturbine,R1,2020,fixed,0,0,0,0,1 +gasCCGT,R2,2020,fixed,0,1.67,0,0,0 +windturbine,R2,2020,fixed,0,0,0,0,1 diff --git a/src/muse/data/example/trade/technodata/power/CommOut.csv b/src/muse/data/example/trade/technodata/power/CommOut.csv index cfebcd832..a9d0ed4cc 100644 --- a/src/muse/data/example/trade/technodata/power/CommOut.csv +++ b/src/muse/data/example/trade/technodata/power/CommOut.csv @@ -1,6 +1,6 @@ ProcessName,RegionName,Time,electricity,gas,heat,CO2f,wind Unit,-,Year,PJ/PJ,PJ/PJ,PJ/PJ,kt/PJ,PJ/PJ -gasCCGT,R1,2010,1,0,0,91.66666667,0 -windturbine,R1,2010,1,0,0,0,0 -gasCCGT,R2,2010,1,0,0,91.66666667,0 -windturbine,R2,2010,1,0,0,0,0 +gasCCGT,R1,2020,1,0,0,91.67,0 +windturbine,R1,2020,1,0,0,0,0 +gasCCGT,R2,2020,1,0,0,91.67,0 +windturbine,R2,2020,1,0,0,0,0 diff --git a/src/muse/data/example/trade/technodata/power/ExistingCapacity.csv b/src/muse/data/example/trade/technodata/power/ExistingCapacity.csv new file mode 100644 index 000000000..847f4e949 --- /dev/null +++ b/src/muse/data/example/trade/technodata/power/ExistingCapacity.csv @@ -0,0 +1,5 @@ +ProcessName,RegionName,Unit,2020,2025,2030,2035,2040,2045,2050 +gasCCGT,R1,PJ/y,1,1,0,0,0,0,0 +windturbine,R1,PJ/y,0,0,0,0,0,0,0 +gasCCGT,R2,PJ/y,1,1,0,0,0,0,0 +windturbine,R2,PJ/y,0,0,0,0,0,0,0 diff --git a/src/muse/data/example/trade/technodata/power/ExistingTrade.csv b/src/muse/data/example/trade/technodata/power/ExistingTrade.csv deleted file mode 100644 index c699d5928..000000000 --- a/src/muse/data/example/trade/technodata/power/ExistingTrade.csv +++ /dev/null @@ -1,32 +0,0 @@ -ProcessName,RegionName,Time,R1,R2 -Unit,-,Year,PJ/y,PJ/y -gasCCGT,R1,2010,300,0 -gasCCGT,R1,2020,240,0 -gasCCGT,R2,2010,0,200 -gasCCGT,R2,2020,0,200 -gasCCGT,R1,2025,192,0 -gasCCGT,R2,2025,0,140 -gasCCGT,R1,2030,153.6,0 -gasCCGT,R2,2030,0,98 -gasCCGT,R1,2035,122.88,0 -gasCCGT,R2,2035,0,68.6 -gasCCGT,R1,2040,98.304,0 -gasCCGT,R2,2040,0,48.02 -gasCCGT,R1,2045,78.6432,0 -gasCCGT,R2,2045,0,33.614 -gasCCGT,R1,2050,62.91456,0 -gasCCGT,R2,2050,0,23.5298 -windturbine,R1,2020,0,0 -windturbine,R2,2020,0,0 -windturbine,R1,2025,0,0 -windturbine,R2,2025,0,0 -windturbine,R1,2030,0,0 -windturbine,R2,2030,0,0 -windturbine,R1,2035,0,0 -windturbine,R2,2035,0,0 -windturbine,R1,2040,0,0 -windturbine,R2,2040,0,0 -windturbine,R1,2045,0,0 -windturbine,R2,2045,0,0 -windturbine,R1,2050,0,0 -windturbine,R2,2050,0,0 diff --git a/src/muse/data/example/trade/technodata/power/Technodata.csv b/src/muse/data/example/trade/technodata/power/Technodata.csv index 61e971df9..d7bbf2e9a 100644 --- a/src/muse/data/example/trade/technodata/power/Technodata.csv +++ b/src/muse/data/example/trade/technodata/power/Technodata.csv @@ -1,6 +1,6 @@ -ProcessName,RegionName,Time,cap_exp,fix_exp,var_par,var_exp,TechnicalLife,UtilizationFactor,ScalingSize,efficiency,InterestRate,Type,Fuel,EndUse,AgentShare -Unit,-,Year,-,-,MUS$2010/PJ,-,Years,-,PJ,%,-,-,-,-, -gasCCGT,R1,2010,1,1,0,1,35,0.9,0.00000189,86,0.1,energy,gas,electricity,1 -windturbine,R1,2010,1,1,0,1,25,0.4,0.00000189,86,0.1,energy,wind,electricity,1 -gasCCGT,R2,2010,1,1,0,1,35,0.9,0.00000189,86,0.1,energy,gas,electricity,1 -windturbine,R2,2010,1,1,0,1,25,0.4,0.00000189,86,0.1,energy,wind,electricity,1 +ProcessName,RegionName,Time,cap_par,cap_exp,fix_par,fix_exp,var_par,var_exp,MaxCapacityAddition,MaxCapacityGrowth,TotalCapacityLimit,TechnicalLife,UtilizationFactor,ScalingSize,efficiency,InterestRate,Type,Fuel,EndUse,Agent1 +Unit,-,Year,MUS$2010/PJ_a,-,MUS$2010/PJ,-,MUS$2010/PJ,-,PJ,%,PJ,Years,-,PJ,%,-,-,-,-,New +gasCCGT,R1,2020,23.78234399,1,0,1,0,1,10,1,100,35,0.9,0.00000189,86,0.1,energy,gas,electricity,1 +windturbine,R1,2020,36.30771182,1,0,1,0,1,10,1,100,25,0.4,0.00000189,86,0.1,energy,wind,electricity,1 +gasCCGT,R2,2020,23.78234399,1,0,1,0,1,10,1,100,35,0.9,0.00000189,86,0.1,energy,gas,electricity,1 +windturbine,R2,2020,36.30771182,1,0,1,0,1,10,1,100,25,0.4,0.00000189,86,0.1,energy,wind,electricity,1 diff --git a/src/muse/data/example/trade/technodata/power/TradeTechnodata.csv b/src/muse/data/example/trade/technodata/power/TradeTechnodata.csv deleted file mode 100644 index f5af71ccd..000000000 --- a/src/muse/data/example/trade/technodata/power/TradeTechnodata.csv +++ /dev/null @@ -1,21 +0,0 @@ -ProcessName,RegionName,Parameter,Unit,R1,R2 -gasCCGT,R1,cap_par,MUSD/PJ,28.29,56.58 -windturbine,R1,cap_par,MUSD/PJ,43.2,0 -gasCCGT,R2,cap_par,MUSD/PJ,57.08,28.54 -windturbine,R2,cap_par,MUSD/PJ,0,43.57 -gasCCGT,R1,fix_par,MUSD/PJ,2.829,5.658 -windturbine,R1,fix_par,MUSD/PJ,0,0 -gasCCGT,R2,fix_par,MUSD/PJ,5.708,2.854 -windturbine,R2,fix_par,MUSD/PJ,0,0 -gasCCGT,R1,MaxCapacityAddition,PJ/y,393.72188,0 -windturbine,R1,MaxCapacityAddition,PJ/y,393.72188,0 -gasCCGT,R2,MaxCapacityAddition,PJ/y,0,393.72188 -windturbine,R2,MaxCapacityAddition,PJ/y,0,393.72188 -gasCCGT,R1,MaxCapacityGrowth,PJ/y,0.05,0.05 -windturbine,R1,MaxCapacityGrowth,PJ/y,0.05,0 -gasCCGT,R2,MaxCapacityGrowth,PJ/y,0.05,0.05 -windturbine,R2,MaxCapacityGrowth,PJ/y,0,0.05 -gasCCGT,R1,TotalCapacityLimit,PJ/y,3937.2188,0 -windturbine,R1,TotalCapacityLimit,PJ/y,3937.2188,0 -gasCCGT,R2,TotalCapacityLimit,PJ/y,0,3937.2188 -windturbine,R2,TotalCapacityLimit,PJ/y,0,3937.2188 diff --git a/src/muse/data/example/trade/technodata/preset/Residential2020Consumption.csv b/src/muse/data/example/trade/technodata/preset/Residential2020Consumption.csv index cab86f138..0b3394c3b 100644 --- a/src/muse/data/example/trade/technodata/preset/Residential2020Consumption.csv +++ b/src/muse/data/example/trade/technodata/preset/Residential2020Consumption.csv @@ -1,13 +1,13 @@ -RegionName,Timeslice,electricity,gas,CO2f,wind,heat -R1,1,0,0,0,0,73 -R1,2,0,0,0,0,103.2 -R1,3,0,0,0,0,77.4 -R1,4,0,0,0,0,77.4 -R1,5,0,0,0,0,111.8 -R1,6,0,0,0,0,77.2 -R2,1,0,0,0,0,2 -R2,2,0,0,0,0,2.2 -R2,3,0,0,0,0,1.8 -R2,4,0,0,0,0,1.8 -R2,5,0,0,0,0,2.3 -R2,6,0,0,0,0,1.8 +RegionName,Timeslice,electricity,gas,heat,CO2f,wind +R1,1,0,0,1.0,0,0 +R1,2,0,0,1.5,0,0 +R1,3,0,0,1.0,0,0 +R1,4,0,0,1.5,0,0 +R1,5,0,0,3.0,0,0 +R1,6,0,0,2.0,0,0 +R2,1,0,0,1.0,0,0 +R2,2,0,0,1.5,0,0 +R2,3,0,0,1.0,0,0 +R2,4,0,0,1.5,0,0 +R2,5,0,0,3.0,0,0 +R2,6,0,0,2.0,0,0 diff --git a/src/muse/data/example/trade/technodata/preset/Residential2030Consumption.csv b/src/muse/data/example/trade/technodata/preset/Residential2030Consumption.csv deleted file mode 100644 index b77f0f46b..000000000 --- a/src/muse/data/example/trade/technodata/preset/Residential2030Consumption.csv +++ /dev/null @@ -1,13 +0,0 @@ -RegionName,Timeslice,electricity,gas,CO2f,wind,heat -R1,1,0,0,0,0,87.6 -R1,2,0,0,0,0,123.84 -R1,3,0,0,0,0,92.88 -R1,4,0,0,0,0,92.88 -R1,5,0,0,0,0,134.16 -R1,6,0,0,0,0,92.64 -R2,1,0,0,0,0,2.4 -R2,2,0,0,0,0,2.64 -R2,3,0,0,0,0,2.16 -R2,4,0,0,0,0,2.16 -R2,5,0,0,0,0,2.76 -R2,6,0,0,0,0,2.16 diff --git a/src/muse/data/example/trade/technodata/preset/Residential2050Consumption.csv b/src/muse/data/example/trade/technodata/preset/Residential2050Consumption.csv index 9341b4292..b25b175a5 100644 --- a/src/muse/data/example/trade/technodata/preset/Residential2050Consumption.csv +++ b/src/muse/data/example/trade/technodata/preset/Residential2050Consumption.csv @@ -1,13 +1,13 @@ -RegionName,Timeslice,electricity,gas,CO2f,wind,heat -R1,1,0,0,0,0,94.9 -R1,2,0,0,0,0,134.16 -R1,3,0,0,0,0,100.62 -R1,4,0,0,0,0,100.62 -R1,5,0,0,0,0,145.34 -R1,6,0,0,0,0,100.36 -R2,1,0,0,0,0,2.6 -R2,2,0,0,0,0,2.86 -R2,3,0,0,0,0,2.34 -R2,4,0,0,0,0,2.34 -R2,5,0,0,0,0,2.99 -R2,6,0,0,0,0,2.34 +RegionName,Timeslice,electricity,gas,heat,CO2f,wind +R1,1,0,0,3.0,0,0 +R1,2,0,0,4.5,0,0 +R1,3,0,0,3.0,0,0 +R1,4,0,0,4.5,0,0 +R1,5,0,0,9.0,0,0 +R1,6,0,0,6.0,0,0 +R2,1,0,0,3.0,0,0 +R2,2,0,0,4.5,0,0 +R2,3,0,0,3.0,0,0 +R2,4,0,0,4.5,0,0 +R2,5,0,0,9.0,0,0 +R2,6,0,0,6.0,0,0 diff --git a/src/muse/data/example/trade/technodata/residential/Agents.csv b/src/muse/data/example/trade/technodata/residential/Agents.csv deleted file mode 100644 index 520b667ae..000000000 --- a/src/muse/data/example/trade/technodata/residential/Agents.csv +++ /dev/null @@ -1,5 +0,0 @@ -AgentShare,Name,RegionName,Objective1,Objective2,Objective3,ObjData1,ObjData2,ObjData3,Objsort1,Objsort2,Objsort3,SearchRule,DecisionMethod,Quantity,MaturityThreshold,Budget,Type -agent_share_1,A1,R1,LCOE,,,1,,,TRUE,,,all,singleObj,1,-1,inf,New -agent_share_2,A1,R1,LCOE,,,1,,,TRUE,,,all,singleObj,1,-1,inf,Retrofit -agent_share_1,A1,R2,LCOE,,,1,,,TRUE,,,all,singleObj,1,-1,inf,New -agent_share_2,A1,R2,LCOE,,,1,,,TRUE,,,all,singleObj,1,-1,inf,Retrofit diff --git a/src/muse/data/example/trade/technodata/residential/CommIn.csv b/src/muse/data/example/trade/technodata/residential/CommIn.csv index 67f0e4fde..d55fd41fa 100644 --- a/src/muse/data/example/trade/technodata/residential/CommIn.csv +++ b/src/muse/data/example/trade/technodata/residential/CommIn.csv @@ -1,6 +1,6 @@ ProcessName,RegionName,Time,Level,electricity,gas,heat,CO2f,wind Unit,-,Year,-,PJ/PJ,PJ/PJ,PJ/PJ,kt/PJ,PJ/PJ -gasboiler,R1,2010,fixed,0,1.162790698,0,0,0 -heatpump,R1,2010,fixed,0.4,0,0,0,0 -gasboiler,R2,2010,fixed,0,1.395348838,0,0,0 -heatpump,R2,2010,fixed,0.48,0,0,0,0 +gasboiler,R1,2020,fixed,0,1.16,0,0,0 +heatpump,R1,2020,fixed,0.4,0,0,0,0 +gasboiler,R2,2020,fixed,0,1.16,0,0,0 +heatpump,R2,2020,fixed,0.4,0,0,0,0 diff --git a/src/muse/data/example/trade/technodata/residential/CommOut.csv b/src/muse/data/example/trade/technodata/residential/CommOut.csv index 93c208776..3d3f71984 100644 --- a/src/muse/data/example/trade/technodata/residential/CommOut.csv +++ b/src/muse/data/example/trade/technodata/residential/CommOut.csv @@ -1,6 +1,6 @@ ProcessName,RegionName,Time,electricity,gas,heat,CO2f,wind Unit,-,Year,PJ/PJ,PJ/PJ,PJ/PJ,kt/PJ,PJ/PJ -gasboiler,R1,2010,0,0,1,64.70588235,0 -heatpump,R1,2010,0,0,1,0,0 -gasboiler,R2,2010,0,0,1,77.64705882,0 -heatpump,R2,2010,0,0,1,0,0 +gasboiler,R1,2020,0,0,1,64.71,0 +heatpump,R1,2020,0,0,1,0,0 +gasboiler,R2,2020,0,0,1,64.71,0 +heatpump,R2,2020,0,0,1,0,0 diff --git a/src/muse/data/example/trade/technodata/residential/ExistingCapacity.csv b/src/muse/data/example/trade/technodata/residential/ExistingCapacity.csv index 8fc854a9c..90cd566a4 100644 --- a/src/muse/data/example/trade/technodata/residential/ExistingCapacity.csv +++ b/src/muse/data/example/trade/technodata/residential/ExistingCapacity.csv @@ -1,5 +1,5 @@ -ProcessName,RegionName,Unit,2010,2020,2030,2040,2050 -gasboiler,R1,PJ/y,946.8,662.76,463.932,324.7524,227.32668 -heatpump,R1,PJ/y,0,0,0,0,0 -gasboiler,R2,PJ/y,50,35,24.5,17.15,12.005 -heatpump,R2,PJ/y,0,0,0,0,0 +ProcessName,RegionName,Unit,2020,2025,2030,2035,2040,2045,2050 +gasboiler,R1,PJ/y,10,5,0,0,0,0,0 +heatpump,R1,PJ/y,0,0,0,0,0,0,0 +gasboiler,R2,PJ/y,10,5,0,0,0,0,0 +heatpump,R2,PJ/y,0,0,0,0,0,0,0 diff --git a/src/muse/data/example/trade/technodata/residential/Technodata.csv b/src/muse/data/example/trade/technodata/residential/Technodata.csv index 9dc35cdc7..e447a93ed 100644 --- a/src/muse/data/example/trade/technodata/residential/Technodata.csv +++ b/src/muse/data/example/trade/technodata/residential/Technodata.csv @@ -1,6 +1,6 @@ -ProcessName,RegionName,Time,cap_par,cap_exp,fix_par,fix_exp,var_par,var_exp,MaxCapacityAddition,MaxCapacityGrowth,TotalCapacityLimit,TechnicalLife,UtilizationFactor,ScalingSize,efficiency,InterestRate,Type,Fuel,EndUse,Agent2 -Unit,-,Year,MUS$2010/PJ_a,-,MUS$2010/PJ,-,MUS$2010/PJ,-,PJ,-,PJ,Years,-,PJ,%,-,-,-,-,Retrofit -gasboiler,R1,2010,4.52,1,0,1,0,1,600,0.2,2000,10,0.9,0.00000189,86,0.1,energy,gas,heat,1 -heatpump,R1,2010,10.55,1,0,1,0,1,600,0.2,2000,10,0.9,0.00000189,86,0.1,energy,electricity,heat,1 -gasboiler,R2,2010,4.94,1,0,1,0,1,600,0.2,2000,10,0.9,0.00000189,86,0.1,energy,gas,heat,1 -heatpump,R2,2010,11.53,1,0,1,0,1,600,0.2,2000,10,0.9,0.00000189,86,0.1,energy,electricity,heat,1 +ProcessName,RegionName,Time,cap_par,cap_exp,fix_par,fix_exp,var_par,var_exp,MaxCapacityAddition,MaxCapacityGrowth,TotalCapacityLimit,TechnicalLife,UtilizationFactor,ScalingSize,efficiency,InterestRate,Type,Fuel,EndUse,Agent1 +Unit,-,Year,MUS$2010/PJ_a,-,MUS$2010/PJ,-,MUS$2010/PJ,-,PJ,%,PJ,Years,-,PJ,%,-,-,-,-,New +gasboiler,R1,2020,3.8,1,0,1,0,1,10,1,100,10,1,0.00000189,86,0.1,energy,gas,heat,1 +heatpump,R1,2020,8.866667,1,0,1,0,1,10,1,100,10,1,0.00000189,86,0.1,energy,electricity,heat,1 +gasboiler,R2,2020,3.8,1,0,1,0,1,10,1,100,10,1,0.00000189,86,0.1,energy,gas,heat,1 +heatpump,R2,2020,8.866667,1,0,1,0,1,10,1,20,10,1,0.00000189,86,0.1,energy,electricity,heat,1 diff --git a/src/muse/demand_share.py b/src/muse/demand_share.py index b6a29b591..36a3c6488 100644 --- a/src/muse/demand_share.py +++ b/src/muse/demand_share.py @@ -112,9 +112,9 @@ def new_and_retro( agents: Sequence[AbstractAgent], market: xr.Dataset, technologies: xr.Dataset, + current_year: int, + forecast: int, production: Union[str, Mapping, Callable] = "maximum_production", - current_year: Optional[int] = None, - forecast: int = 5, ) -> xr.DataArray: r"""Splits demand across new and retro agents. @@ -239,9 +239,6 @@ def decommissioning(capacity): timeslices=market.timeslice, ).squeeze("year") - if current_year is None: - current_year = market.year.min() - capacity = reduce_assets([u.assets.capacity for u in agents]) demands = new_and_retro_demands( @@ -331,9 +328,9 @@ def standard_demand( agents: Sequence[AbstractAgent], market: xr.Dataset, technologies: xr.Dataset, + current_year: int, + forecast: int, production: Union[str, Mapping, Callable] = "maximum_production", - current_year: Optional[int] = None, - forecast: int = 5, ) -> xr.DataArray: r"""Splits demand across new agents. @@ -371,11 +368,15 @@ def decommissioning(capacity): timeslices=market.timeslice, ).squeeze("year") - if current_year is None: - current_year = market.year.min() + # Make sure there are no retrofit agents + for agent in agents: + if agent.category == "retrofit": + raise RetrofitAgentInStandardDemandShare() + # Calculate existing capacity capacity = reduce_assets([agent.assets.capacity for agent in agents]) + # Calculate new and retrofit demands demands = new_and_retro_demands( capacity, market, @@ -385,34 +386,32 @@ def decommissioning(capacity): forecast=forecast, ) + # Only consider end-use commodities demands = demands.where( is_enduse(technologies.comm_usage.sel(commodity=demands.commodity)), 0 ) - for agent in agents: - if agent.category == "retrofit": - raise RetrofitAgentInStandardDemandShare() - id_to_share: MutableMapping[Hashable, xr.DataArray] = {} for region in demands.region.values: + # Calculate current capacity current_capacity: MutableMapping[Hashable, xr.DataArray] = { agent.uuid: agent.assets.capacity for agent in agents if agent.region == region } + + # Split demands between agents id_to_quantity = { agent.uuid: (agent.name, agent.region, agent.quantity) for agent in agents if agent.region == region } - retro_demands: MutableMapping[Hashable, xr.DataArray] = _inner_split( current_capacity, demands.retrofit.sel(region=region), decommissioning, id_to_quantity, ) - new_demands = _inner_split( current_capacity, demands.new.sel(region=region), @@ -425,6 +424,7 @@ def decommissioning(capacity): id_to_quantity, ) + # Sum new and retrofit demands total_demands = { k: new_demands[k] + retro_demands[k] for k in new_demands.keys() } @@ -439,17 +439,14 @@ def unmet_forecasted_demand( agents: Sequence[AbstractAgent], market: xr.Dataset, technologies: xr.Dataset, - current_year: Optional[int] = None, + current_year: int, + forecast: int, production: Union[str, Mapping, Callable] = "maximum_production", - forecast: int = 5, ) -> xr.DataArray: """Forecast demand that cannot be serviced by non-decommissioned current assets.""" from muse.commodities import is_enduse from muse.utilities import reduce_assets - if current_year is None: - current_year = market.year.min() - year = current_year + forecast comm_usage = technologies.comm_usage.sel(commodity=market.commodity) smarket: xr.Dataset = market.where(is_enduse(comm_usage), 0).interp(year=year) @@ -474,6 +471,7 @@ def _inner_split( """ from numpy import logical_and + # Find decrease in capacity production by each asset over time shares: Mapping[Hashable, xr.DataArray] = { key: method(capacity=capacity) .groupby("technology") @@ -481,13 +479,12 @@ def _inner_split( .rename(technology="asset") for key, capacity in assets.items() } + + # Total decrease in production across assets try: summed_shares: xr.DataArray = xr.concat(shares.values(), dim="concat_dim").sum( "concat_dim" ) - - # Calculates the total demand assigned in the previous step with the "method" - # function across agents and assets. total: xr.DataArray = summed_shares.sum("asset") except AttributeError: raise AgentWithNoAssetsInDemandShare() @@ -495,7 +492,9 @@ def _inner_split( # Calculates the demand divided by the number of assets times the number of agents # if the demand is bigger than zero and the total demand assigned with the "method" # function is zero. - unassigned = (demand / (len(shares) * len(summed_shares))).where( + n_agents = len(quantity) + n_assets = summed_shares.sizes["asset"] + unassigned = (demand / (n_agents * n_assets)).where( logical_and(demand > 1e-12, total <= 1e-12), 0 ) @@ -534,22 +533,29 @@ def unmet_demand( prod_method = production if callable(production) else prod_factory(production) assert callable(prod_method) + + # Calculate production by existing assets produced = prod_method(market=market, capacity=capacity, technologies=technologies) + + # Total commodity production by summing over assets if "dst_region" in produced.dims: produced = produced.sum("asset").rename(dst_region="region") elif "region" in produced.coords and produced.region.dims: produced = produced.groupby("region").sum("asset") else: produced = produced.sum("asset") - return (market.consumption - produced).clip(min=0) + + # Unmet demand is the difference between the consumption and the production + unmet_demand = (market.consumption - produced).clip(min=0) + return unmet_demand def new_consumption( capacity: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, - current_year: Optional[int] = None, - forecast: int = 5, + current_year: int, + forecast: int, ) -> xr.DataArray: r"""Computes share of the demand attributed to new agents. @@ -567,15 +573,16 @@ def new_consumption( """ from numpy import minimum - if current_year is None: - current_year = market.year.min() - + # Interpolate capacity to forecast year capa = capacity.interp(year=current_year + forecast) assert isinstance(capa, xr.DataArray) + + # Interpolate market to forecast year market = market.interp(year=[current_year, current_year + forecast]) current = market.sel(year=current_year, drop=True) forecasted = market.sel(year=current_year + forecast, drop=True) + # Calculate the increase in consumption over the forecast period delta = (forecasted.consumption - current.consumption).clip(min=0) missing = unmet_demand(current, capa, technologies) consumption = minimum(delta, missing) @@ -586,9 +593,9 @@ def new_and_retro_demands( capacity: xr.DataArray, market: xr.Dataset, technologies: xr.Dataset, + current_year: int, + forecast: int, production: Union[str, Mapping, Callable] = "maximum_production", - current_year: Optional[int] = None, - forecast: int = 5, ) -> xr.Dataset: """Splits demand into *new* and *retrofit* demand. @@ -606,21 +613,22 @@ def new_and_retro_demands( production_method = production if callable(production) else prod_factory(production) assert callable(production_method) - if current_year is None: - current_year = market.year.min() + # Interpolate market to forecast year smarket: xr.Dataset = market.interp(year=[current_year, current_year + forecast]) capa = capacity.interp(year=[current_year, current_year + forecast]) assert isinstance(capa, xr.DataArray) if hasattr(capa, "region") and capa.region.dims == (): capa["region"] = "asset", [str(capa.region.values)] * len(capa.asset) + # Calculate demand to allocate to "new" agents new_demand = new_consumption( capa, smarket, technologies, current_year=current_year, forecast=forecast ) if "year" in new_demand.dims: new_demand = new_demand.squeeze("year") + # Total production in the forecast year by existing assets service = ( production_method( smarket.sel(year=current_year + forecast), @@ -630,37 +638,19 @@ def new_and_retro_demands( .groupby("region") .sum("asset") ) - # existing asset should not execute beyond demand + + # Existing asset should not execute beyond demand service = minimum( service, smarket.consumption.sel(year=current_year + forecast, drop=True) ) + + # Leftover demand that cannot be serviced by existing assets or "new" agents retro_demand = ( smarket.consumption.sel(year=current_year + forecast, drop=True) - new_demand - service ).clip(min=0) - if "year" in retro_demand.dims: retro_demand = retro_demand.squeeze("year") return xr.Dataset({"new": new_demand, "retrofit": retro_demand}) - - -def new_demand( - capacity: xr.DataArray, - market: xr.Dataset, - technologies: xr.Dataset, - production: Union[str, Mapping, Callable] = "maximum_production", - current_year: Optional[int] = None, - forecast: int = 5, -) -> xr.DataArray: - """Calculates the new demand that needs to be covered. - - It groups the demand related to an increase in consumption as well as the existing - demand associated with decommissoned assets. Internally, it just calls - `new_and_retro` demands and adds together both components. - """ - demand = new_and_retro_demands( - capacity, market, technologies, production, current_year, forecast - ) - return (demand["new"] + demand["retrofit"]).rename("demand") diff --git a/src/muse/investments.py b/src/muse/investments.py index bc5baed61..0e2abb831 100644 --- a/src/muse/investments.py +++ b/src/muse/investments.py @@ -154,6 +154,7 @@ def compute_investment( """ from numpy import zeros + # Skip the investment step if no assets or replacements are available if any(u == 0 for u in search.decision.shape): return xr.DataArray( zeros((len(search.asset), len(search.replacement))), @@ -161,6 +162,7 @@ def compute_investment( dims=("asset", "replacement"), ) + # Otherwise, compute the investment return investment( search.decision, search.search_space, @@ -175,8 +177,7 @@ def compute_investment( def cliff_retirement_profile( technical_life: xr.DataArray, - current_year: int = 0, - protected: int = 0, + investment_year: int, interpolation: str = "linear", **kwargs, ) -> xr.DataArray: @@ -186,19 +187,13 @@ def cliff_retirement_profile( Assets with a technical life smaller than the input time-period should automatically be renewed. - Hence, if ``technical_life <= protected``, then effectively, the technical life is - rewritten as ``technical_life * n`` with ``n = int(protected // technical_life) + - 1``. - We could just return an array where each year is represented. Instead, to save memory, we return a compact view of the same where years where no change happens are removed. Arguments: technical_life: lifetimes for each technology - current_year: current year - protected: The technologies are assumed to be renewed between years - `current_year` and `current_year + protected` + investment_year: The year in which the investment is made interpolation: Interpolation type **kwargs: arguments by which to filter technical_life, if any. @@ -211,26 +206,26 @@ def cliff_retirement_profile( if kwargs: technical_life = technical_life.sel(**kwargs) if "year" in technical_life.dims: - technical_life = technical_life.interp(year=current_year, method=interpolation) - technical_life = (1 + protected // technical_life) * technical_life # type:ignore + technical_life = technical_life.interp( + year=investment_year, method=interpolation + ) + # Create profile across all years if len(technical_life) > 0: - max_year = int(current_year + technical_life.max()) + max_year = int(investment_year + technical_life.max()) else: - max_year = int(current_year + protected) + max_year = investment_year allyears = xr.DataArray( - range(current_year, max_year + 1), + range(investment_year, max_year + 1), dims="year", - coords={"year": range(current_year, max_year + 1)}, + coords={"year": range(investment_year, max_year + 1)}, ) + profile = allyears < (investment_year + technical_life) # type: ignore - profile = allyears < (current_year + technical_life) # type: ignore - - # now we minimize the number of years needed to represent the profile fully - # this is done by removing the central year of any three repeating year, ensuring - # the removed year can be recovered by a linear interpolation. + # Minimize the number of years needed to represent the profile fully + # This is done by removing the central year of any three repeating years, ensuring + # the removed year can be recovered by linear interpolation. goodyears = avoid_repetitions(profile.astype(int)) - return profile.sel(year=goodyears).astype(bool) @@ -310,18 +305,24 @@ def scipy_match_demand( if "timeslice" in costs.dims and timeslice_op is not None: costs = timeslice_op(costs) + + timeslice = next(cs.timeslice for cs in constraints if "timeslice" in cs.dims) + + # Select technodata for the current year if "year" in technologies.dims and year is None: raise ValueError("Missing year argument") elif "year" in technologies.dims: techs = technologies.sel(year=year).drop_vars("year") else: techs = technologies - timeslice = next(cs.timeslice for cs in constraints if "timeslice" in cs.dims) + # Run scipy optimization with highs solver adapter = ScipyAdapter.factory( techs, cast(np.ndarray, costs), timeslice, *constraints ) res = linprog(**adapter.kwargs, method="highs") + + # Backup: try with highs-ipm if not res.success and (res.status != 0): res = linprog( **adapter.kwargs, @@ -343,7 +344,9 @@ def scipy_match_demand( getLogger(__name__).critical(msg) raise GrowthOfCapacityTooConstrained - return cast(Callable[[np.ndarray], xr.Dataset], adapter.to_muse)(res.x) + # Convert results to a MUSE friendly format + result = cast(Callable[[np.ndarray], xr.Dataset], adapter.to_muse)(res.x) + return result @register_investment(name=["cvxopt"]) diff --git a/src/muse/mca.py b/src/muse/mca.py index 55014c705..7fa2cd481 100644 --- a/src/muse/mca.py +++ b/src/muse/mca.py @@ -310,6 +310,7 @@ def run(self) -> None: ) self.carbon_price = future_propagation(self.carbon_price, future_price) + # Solve the market _, new_market, self.sectors = self.find_equilibrium(new_market) # Save sector outputs @@ -324,17 +325,19 @@ def run(self) -> None: new_market, year_idx ) + # Update the market dims = {i: new_market[i] for i in new_market.dims} self.market.supply.loc[dims] = new_market.supply self.market.consumption.loc[dims] = new_market.consumption - dims = {i: new_market[i] for i in new_market.prices.dims if i != "year"} self.market.prices.loc[dims] = future_propagation( self.market.prices.sel(dims), new_market.prices.sel(year=years[1]) ) + # Global outputs self.outputs(self.market, self.sectors, year=self.time_framework[year_idx]) # type: ignore self.outputs_cache.consolidate_cache(year=self.time_framework[year_idx]) + getLogger(__name__).info( f"Finish simulation year {years[0]} ({year_idx+1}/{nyear})!" ) @@ -429,29 +432,26 @@ def single_year_iteration( if "updated_prices" not in market.data_vars: market["updated_prices"] = drop_timeslice(market.prices.copy()) - # eventually, the first market should be one that creates the initial demand for sector in sectors: + # Solve the sector sector_market = sector.next( market[["supply", "consumption", "prices"]] # type:ignore ) - sector_market = sector_market.sel(year=market.year) + # Calculate net consumption dims = {i: sector_market[i] for i in sector_market.consumption.dims} - sector_market.consumption.loc[dims] = ( sector_market.consumption.loc[dims] - sector_market.supply.loc[dims] ).clip(min=0.0, max=None) + # Update market supply and consumption market.consumption.loc[dims] += sector_market.consumption - dims = {i: sector_market[i] for i in sector_market.supply.dims} market.supply.loc[dims] += sector_market.supply + # Update market prices costs = sector_market.costs.sel(commodity=is_enduse(sector_market.comm_usage)) - - # do not write costs lower than 1e-4 - # should correspond to rounding value if len(costs.commodity) > 0: costs = costs.where(costs > 1e-4, 0) dims = {i: costs[i] for i in costs.dims} diff --git a/src/muse/quantities.py b/src/muse/quantities.py index 781be683c..4d9f6c54a 100644 --- a/src/muse/quantities.py +++ b/src/muse/quantities.py @@ -259,9 +259,13 @@ def decommissioning_demand( baseyear = min(year) dyears = [u for u in year if u != baseyear] + # Calculate the decrease in capacity from the current year to future years + capacity_decrease = capacity.sel(year=baseyear) - capacity.sel(year=dyears) + + # Calculate production associated with this capacity return maximum_production( technologies, - capacity.sel(year=baseyear) - capacity.sel(year=dyears), + capacity_decrease, timeslices=timeslices, ).clip(min=0) diff --git a/src/muse/sectors/sector.py b/src/muse/sectors/sector.py index b25a7455e..8d0f082ff 100644 --- a/src/muse/sectors/sector.py +++ b/src/muse/sectors/sector.py @@ -30,20 +30,24 @@ def factory(cls, name: str, settings: Any) -> Sector: from muse.readers.toml import read_technodata from muse.utilities import nametuple_to_dict + # Read sector settings sector_settings = getattr(settings.sectors, name)._asdict() for attribute in ("name", "type", "priority", "path"): sector_settings.pop(attribute, None) + if "subsectors" not in sector_settings: + raise RuntimeError(f"Missing 'subsectors' section in sector {name}") + if len(sector_settings["subsectors"]._asdict()) == 0: + raise RuntimeError(f"Empty 'subsectors' section in sector {name}") + # Timeslices timeslices = read_timeslices( sector_settings.pop("timeslice_levels", None) ).get_index("timeslice") + # Read technologies technologies = read_technodata(settings, name, settings.time_framework) - if "subsectors" not in sector_settings: - raise RuntimeError(f"Missing 'subsectors' section in sector {name}") - if len(sector_settings["subsectors"]._asdict()) == 0: - raise RuntimeError(f"Empty 'subsectors' section in sector {name}") + # Create subsectors subsectors = [ Subsector.factory( subsec_settings, @@ -56,12 +60,15 @@ def factory(cls, name: str, settings: Any) -> Sector: ._asdict() .items() ] + + # Check that subsector commodities are disjoint are_disjoint_commodities = sum(len(s.commodities) for s in subsectors) == len( set().union(*(set(s.commodities) for s in subsectors)) # type: ignore ) if not are_disjoint_commodities: raise RuntimeError("Subsector commodities are not disjoint") + # Create outputs outputs = ofactory(*sector_settings.pop("outputs", []), sector_name=name) supply_args = sector_settings.pop( @@ -73,9 +80,16 @@ def factory(cls, name: str, settings: Any) -> Sector: supply_args = nametuple_to_dict(supply_args) supply = pfactory(**supply_args) + # Create interactions interactions = interaction_factory(sector_settings.pop("interactions", None)) - for attr in ("technodata", "commodities_out", "commodities_in"): + # Create sector + for attr in ( + "technodata", + "commodities_out", + "commodities_in", + "technodata_timeslices", + ): sector_settings.pop(attr, None) return cls( name, @@ -94,7 +108,6 @@ def __init__( technologies: xr.Dataset, subsectors: Sequence[Subsector] = [], timeslices: pd.MultiIndex | None = None, - technodata_timeslices: xr.Dataset = None, interactions: Callable[[Sequence[AbstractAgent]], None] | None = None, interpolation: str = "linear", outputs: Callable | None = None, @@ -112,7 +125,6 @@ def __init__( """Parameters describing the sector's technologies.""" self.timeslices: pd.MultiIndex | None = timeslices """Timeslice at which this sector operates. - If None, it will operate using the timeslice of the input market. """ self.interpolation: Mapping[str, Any] = { @@ -158,33 +170,20 @@ def __init__( def forecast(self): """Maximum forecast horizon across agents. - If no agents with a "forecast" attribute are found, defaults to 5. It cannot be - lower than 1 year. + It cannot be lower than 1 year. """ - forecasts = [ - getattr(agent, "forecast") - for agent in self.agents - if hasattr(agent, "forecast") - ] - if len(forecasts) == 0: - return 5 + forecasts = [getattr(agent, "forecast") for agent in self.agents] return max(1, max(forecasts)) def next( self, mca_market: xr.Dataset, - time_period: int | None = None, - current_year: int | None = None, ) -> xr.Dataset: """Advance sector by one time period. Args: mca_market: Market with ``demand``, ``supply``, and ``prices``. - time_period: - Length of the time period in the framework. Defaults to the range of - ``mca_market.year``. - current_year: Current year of the simulation Returns: A market containing the ``supply`` offered by the sector, it's attendant @@ -195,47 +194,29 @@ def next( def group_assets(x: xr.DataArray) -> xr.DataArray: return xr.Dataset(dict(x=x)).groupby("region").sum("asset").x - if time_period is None: - time_period = int(mca_market.year.max() - mca_market.year.min()) - if current_year is None: - current_year = int(mca_market.year.min()) + 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}") - # > to sector timeslice - market = self.convert_market_timeslice( - mca_market.sel( - commodity=self.technologies.commodity, region=self.technologies.region - ).interp( - year=sorted( - { - current_year, - current_year + time_period, - current_year + self.forecast, - } - ), - **self.interpolation, - ), - self.timeslices, - ) - # > agent interactions + # Agent interactions self.interactions(list(self.agents)) - # > investment - years = sorted( - set( - market.year.data.tolist() - + self.capacity.installed.data.tolist() - + self.technologies.year.data.tolist() - ) + + # Select appropriate data from the market + market = mca_market.sel( + commodity=self.technologies.commodity, region=self.technologies.region ) - technologies = self.technologies.interp(year=years, **self.interpolation) + # Investments for subsector in self.subsectors: subsector.invest( - technologies, market, time_period=time_period, current_year=current_year + self.technologies, + market, + time_period=time_period, + current_year=current_year, ) # Full output data - supply, consume, costs = self.market_variables(market, technologies) + supply, consume, costs = self.market_variables(market, self.technologies) self.output_data = xr.Dataset( dict( supply=supply, @@ -287,7 +268,9 @@ def group_assets(x: xr.DataArray) -> xr.DataArray: dict(supply=supply, consumption=consumption, costs=costs) ) result = self.convert_market_timeslice(result, mca_market.timeslice) - result["comm_usage"] = technologies.comm_usage.sel(commodity=result.commodity) + result["comm_usage"] = self.technologies.comm_usage.sel( + commodity=result.commodity + ) result.set_coords("comm_usage") return result @@ -306,15 +289,17 @@ def market_variables(self, market: xr.Dataset, technologies: xr.Dataset) -> Any: years = market.year.values capacity = self.capacity.interp(year=years, **self.interpolation) + # Calculate supply supply = self.supply_prod( market=market, capacity=capacity, technologies=technologies ) - if "timeslice" in market.prices.dims and "timeslice" not in supply.dims: supply = convert_timeslice(supply, market.timeslice, QuantityType.EXTENSIVE) + # Calculate consumption consume = consumption(technologies, supply, market.prices) + # Calculate commodity prices technodata = cast(xr.Dataset, broadcast_techs(technologies, supply)) costs = supply_cost( supply.where(~is_pollutant(supply.comm_usage), 0), @@ -346,6 +331,8 @@ def capacity(self) -> xr.DataArray: for u in self.agents if "dst_region" not in u.assets.capacity.dims ] + + # Only nontraded assets if not traded: full_list = [ list(nontraded[i].year.values) @@ -360,7 +347,9 @@ def capacity(self) -> xr.DataArray: if "dst_region" not in u.assets.capacity.dims ] return reduce_assets(nontraded) - if not nontraded: + + # Only traded assets + elif not nontraded: full_list = [ list(traded[i].year.values) for i in range(len(traded)) @@ -374,15 +363,18 @@ def capacity(self) -> xr.DataArray: if "dst_region" in u.assets.capacity.dims ] return reduce_assets(traded) - traded_results = reduce_assets(traded) - nontraded_results = reduce_assets(nontraded) - return reduce_assets( - [ - traded_results, - nontraded_results - * (nontraded_results.region == traded_results.dst_region), - ] - ) + + # Both traded and nontraded assets + else: + traded_results = reduce_assets(traded) + nontraded_results = reduce_assets(nontraded) + return reduce_assets( + [ + traded_results, + nontraded_results + * (nontraded_results.region == traded_results.dst_region), + ] + ) @property def agents(self) -> Iterator[AbstractAgent]: diff --git a/src/muse/sectors/subsector.py b/src/muse/sectors/subsector.py index 26ba1cb27..03798af0f 100644 --- a/src/muse/sectors/subsector.py +++ b/src/muse/sectors/subsector.py @@ -1,10 +1,9 @@ from __future__ import annotations -from collections.abc import Hashable, MutableMapping, Sequence +from collections.abc import Sequence from typing import ( Any, Callable, - cast, ) import numpy as np @@ -51,62 +50,33 @@ def invest( self, technologies: xr.Dataset, market: xr.Dataset, - time_period: int = 5, - current_year: int | None = None, + time_period: int, + current_year: int, ) -> None: - if current_year is None: - current_year = market.year.min() + # Expand prices to include destination region (for trade models) if self.expand_market_prices: market = market.copy() market["prices"] = drop_timeslice( np.maximum(market.prices, market.prices.rename(region="dst_region")) ) + # Agent housekeeping for agent in self.agents: agent.asset_housekeeping() - lp_problem = self.aggregate_lp( - technologies, market, time_period, current_year=current_year - ) - if lp_problem is None: - return - - years = technologies.year - techs = technologies.interp(year=years) - techs = techs.sel(year=current_year + time_period) - - solution = self.investment( - search=lp_problem[0], technologies=techs, constraints=lp_problem[1] - ) - - self.assign_back_to_agents(technologies, solution, current_year, time_period) - - def assign_back_to_agents( - self, - technologies: xr.Dataset, - solution: xr.DataArray, - current_year: int, - time_period: int, - ): - agents = {u.uuid: u for u in self.agents} - - for uuid, assets in solution.groupby("agent"): - agents[uuid].add_investments( - technologies, assets, current_year, time_period - ) + # Perform the investments + self.aggregate_lp(technologies, market, time_period, current_year=current_year) def aggregate_lp( self, technologies: xr.Dataset, market: xr.Dataset, - time_period: int = 5, - current_year: int | None = None, - ) -> tuple[xr.Dataset, Sequence[xr.Dataset]] | None: + time_period, + current_year, + ) -> None: from muse.utilities import agent_concatenation, reduce_assets - if current_year is None: - current_year = market.year.min() - + # Split demand across agents demands = self.demand_share( self.agents, market, @@ -122,42 +92,27 @@ def aggregate_lp( dimension. """ raise ValueError(msg) - agent_market = market.copy() + + # Concatenate assets assets = agent_concatenation( {agent.uuid: agent.assets for agent in self.agents} ) + + # Calculate existing capacity + agent_market = market.copy() agent_market["capacity"] = ( reduce_assets(assets.capacity, coords=("region", "technology")) .interp(year=market.year, method="linear", kwargs={"fill_value": 0.0}) .swap_dims(dict(asset="technology")) ) - agent_lps: MutableMapping[Hashable, xr.Dataset] = {} + # Increment each agent (perform investments) for agent in self.agents: if "agent" in demands.coords: share = demands.sel(asset=demands.agent == agent.uuid) else: share = demands - result = agent.next( - technologies, agent_market, share, time_period=time_period - ) - if result is not None: - agent_lps[agent.uuid] = result - - if len(agent_lps) == 0: - return None - - lps = cast(xr.Dataset, agent_concatenation(agent_lps, dim="agent")) - coords = {"agent", "technology", "region"}.intersection(assets.asset.coords) - constraints = self.constraints( - demand=demands, - assets=reduce_assets(assets, coords=coords).set_coords(coords), - search_space=lps.search_space, - market=market, - technologies=technologies, - year=current_year, - ) - return lps, constraints + agent.next(technologies, agent_market, share, time_period=time_period) @classmethod def factory( diff --git a/src/muse/utilities.py b/src/muse/utilities.py index 7923687c8..78ef36b7d 100644 --- a/src/muse/utilities.py +++ b/src/muse/utilities.py @@ -139,7 +139,9 @@ def reduce_assets( installed (asset) int32 12B 1990 1991 1990 Dimensions without coordinates: asset """ - from copy import copy + from copy import deepcopy + + assets = deepcopy(assets) if operation is None: @@ -148,23 +150,31 @@ def operation(x): assert operation is not None + # Concatenate assets if a sequence is given if not isinstance(assets, (xr.Dataset, xr.DataArray)): assets = xr.concat(assets, dim=dim) assert isinstance(assets, (xr.Dataset, xr.DataArray)) + + # If there are no assets, nothing needs to be done if assets[dim].size == 0: return assets + + # Coordinates to reduce over (e.g. technology, installed) if coords is None: coords = [cast(str, k) for k, v in assets.coords.items() if v.dims == (dim,)] elif isinstance(coords, str): coords = (coords,) coords = [k for k in coords if k in assets.coords and assets[k].dims == (dim,)] - assets = copy(assets) + + # Create a new dimension to group by dtypes = [(d, assets[d].dtype) for d in coords] grouper = np.array( list(zip(*(cast(Iterator, assets[d].values) for d in coords))), dtype=dtypes ) assert "grouper" not in assets.coords assets["grouper"] = "asset", grouper + + # Perform the operation result = operation(assets.groupby("grouper")).rename(grouper=dim) for i, d in enumerate(coords): result[d] = dim, [u[i] for u in result[dim].values] @@ -385,32 +395,22 @@ def merge_assets( dimension: str = "asset", ) -> xr.DataArray: """Merge two capacity arrays.""" + # Interpolate capacity arrays to a common time framework years = sorted(set(capa_a.year.values).union(capa_b.year.values)) - if len(capa_a.year) == 1: - result = xr.concat( - ( - capa_a, - capa_b.interp(year=years, method=interpolation).fillna(0), - ), - dim=dimension, - ).fillna(0) + capa_a_interp = capa_a + capa_b_interp = capa_b.interp(year=years, method=interpolation).fillna(0) elif len(capa_b.year) == 1: - result = xr.concat( - ( - capa_a.interp(year=years, method=interpolation).fillna(0), - capa_b, - ), - dim=dimension, - ).fillna(0) + capa_a_interp = capa_a.interp(year=years, method=interpolation).fillna(0) + capa_b_interp = capa_b else: - result = xr.concat( - ( - capa_a.interp(year=years, method=interpolation).fillna(0), - capa_b.interp(year=years, method=interpolation).fillna(0), - ), - dim=dimension, - ) + capa_a_interp = capa_a.interp(year=years, method=interpolation).fillna(0) + capa_b_interp = capa_b.interp(year=years, method=interpolation).fillna(0) + + # Concatenate the two capacity arrays + result = xr.concat((capa_a_interp, capa_b_interp), dim=dimension) + + # forgroup = result.pipe(coords_to_multiindex, dimension=dimension) if isinstance(forgroup, xr.DataArray): forgroup = forgroup.to_dataset() diff --git a/tests/test_agents.py b/tests/test_agents.py index c767c2c98..3287e0e3c 100644 --- a/tests/test_agents.py +++ b/tests/test_agents.py @@ -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) + retro_agent.next(technologies, agent_market, demand_share, time_period=5) def test_merge_assets(assets): diff --git a/tests/test_demand_share.py b/tests/test_demand_share.py index 439d416e9..cc5ba52f0 100644 --- a/tests/test_demand_share.py +++ b/tests/test_demand_share.py @@ -343,6 +343,7 @@ def test_unmet_forecast_demand(technologies, coords, timeslice, stock_factory): asia_market = _matching_market(technologies, asia_stock, timeslice) usa_market = _matching_market(technologies, usa_stock, timeslice) market = xr.concat((asia_market, usa_market), dim="region") + current_year = market.year[0] # spoof some agents @dataclass @@ -355,7 +356,9 @@ class Agent: Agent(0.7 * usa_stock.squeeze("region")), Agent(asia_stock.squeeze("region")), ] - result = unmet_forecasted_demand(agents, market, technologies) + result = unmet_forecasted_demand( + agents, market, technologies, current_year=current_year, forecast=5 + ) assert set(result.dims) == set(market.consumption.dims) - {"year"} assert result.values == approx(0) @@ -365,7 +368,9 @@ class Agent: Agent(0.8 * usa_stock.squeeze("region")), Agent(1.1 * asia_stock.squeeze("region")), ] - result = unmet_forecasted_demand(agents, market, technologies) + result = unmet_forecasted_demand( + agents, market, technologies, current_year=current_year, forecast=5 + ) assert set(result.dims) == set(market.consumption.dims) - {"year"} assert result.values == approx(0) @@ -374,7 +379,9 @@ class Agent: Agent(0.5 * usa_stock.squeeze("region")), Agent(0.5 * asia_stock.squeeze("region")), ] - result = unmet_forecasted_demand(agents, market, technologies) + result = unmet_forecasted_demand( + agents, market, technologies, current_year=current_year, forecast=5 + ) comm_usage = technologies.comm_usage.sel(commodity=market.commodity) enduse = is_enduse(comm_usage) assert (result.commodity == comm_usage.commodity).all() diff --git a/tests/test_investments.py b/tests/test_investments.py index 92b9abbbc..38f49a261 100644 --- a/tests/test_investments.py +++ b/tests/test_investments.py @@ -44,7 +44,7 @@ def test_cliff_retirement_known_profile(): name="technical_life", ) - profile = cliff_retirement_profile(lifetime) + profile = cliff_retirement_profile(technical_life=lifetime, investment_year=2020) expected = array( [ [True, False, False, False], @@ -73,12 +73,12 @@ def test_cliff_retirement_random_profile(protected): ) effective_lifetime = (protected // lifetime + 1) * lifetime - current = 5 + investment_year = 2020 profile = cliff_retirement_profile( - lifetime, current_year=current, protected=protected + technical_life=lifetime.clip(min=protected), investment_year=investment_year ) - assert profile.year.min() == current - assert profile.year.max() <= current + effective_lifetime.max() + 1 - assert profile.astype(int).interp(year=current).all() - assert profile.astype(int).interp(year=current + protected).all() + assert profile.year.min() == investment_year + assert profile.year.max() <= investment_year + effective_lifetime.max() + 1 + assert profile.astype(int).interp(year=investment_year).all() + assert profile.astype(int).interp(year=investment_year + protected - 1).all() assert not profile.astype(int).interp(year=profile.year.max()).any() diff --git a/tests/test_readers.py b/tests/test_readers.py index a23fc1650..851c9eaef 100644 --- a/tests/test_readers.py +++ b/tests/test_readers.py @@ -425,7 +425,7 @@ def test_read_trade_technodata(tmp_path): assert all(val == np.float64 for val in data.dtypes.values()) assert list(data.coords["dst_region"].values) == ["R1", "R2"] assert list(data.coords["technology"].values) == ["gassupply1"] - assert list(data.coords["region"].values) == ["R1", "R2", "R3"] + assert list(data.coords["region"].values) == ["R1", "R2"] assert all(var.coords.equals(data.coords) for var in data.data_vars.values()) diff --git a/tests/test_subsector.py b/tests/test_subsector.py index 9c326f1f4..b94d94fae 100644 --- a/tests/test_subsector.py +++ b/tests/test_subsector.py @@ -1,4 +1,3 @@ -from collections.abc import Sequence from unittest.mock import MagicMock, patch import xarray as xr @@ -48,7 +47,7 @@ def test_subsector_investing_aggregation(): subsector = Subsector(agents, commodities) initial_agents = deepcopy(agents) assert {agent.year for agent in agents} == {int(market.year.min())} - assert subsector.aggregate_lp(technologies, market) is None + subsector.aggregate_lp(technologies, market, time_period=5, current_year=5) assert {agent.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() @@ -105,20 +104,7 @@ def test_subsector_noninvesting_aggregation(market, model, technologies, tmp_pat commodity=technologies.commodity, region=technologies.region ).interp(year=[2020, 2025]) assert all(agent.year == 2020 for agent in agents) - result = subsector.aggregate_lp(technologies, market) - - assert result is not None - assert len(result) == 2 - - lpcosts, lpconstraints = result - assert isinstance(lpcosts, xr.Dataset) - assert {"search_space", "decision"} == set(lpcosts.data_vars) - assert "agent" in lpcosts.coords - assert isinstance(lpconstraints, Sequence) - assert len(lpconstraints) == 1 - assert all(isinstance(u, xr.Dataset) for u in lpconstraints) - # makes sure agent investment got called - assert all(agent.year == 2025 for agent in agents) + subsector.aggregate_lp(technologies, market, time_period=5, current_year=2020) def test_factory_smoke_test(model, technologies, tmp_path): diff --git a/tests/test_utilities.py b/tests/test_utilities.py index f5d3ab63a..87c8586b8 100644 --- a/tests/test_utilities.py +++ b/tests/test_utilities.py @@ -37,7 +37,7 @@ def test_reduce_assets_with_zero_size(capacity: xr.DataArray): x = capacity.sel(asset=[]) actual = reduce_assets(x) - assert actual is x + assert (actual == x).all() def test_broadcast_tech(technologies, capacity):