Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 42 additions & 47 deletions src/muse/demand_share.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@register_demand_share
def demand_share(
agents: Sequence[AbstractAgent],
market: xr.Dataset,
demand: xr.Dataarray,
technologies: xr.Dataset,
**kwargs
) -> xr.DataArray:
Expand All @@ -24,7 +24,7 @@ def demand_share(
be queried for parameters specific to the demand share procedure. For instance,
:py:func`new_and_retro` will query the agents for the assets they own, the
region they are contained with, their category (new or retrofit), etc...
market: Market variables, including prices, consumption and supply.
demand: DataArray of commodity demands for the current year and investment year.
technologies: a dataset containing all constant data characterizing the
technologies.
kwargs: Any number of keyword arguments that can parametrize how the demand is
Expand All @@ -34,7 +34,7 @@ def demand_share(
The unmet consumption. Unless indicated, all agents will compete for a the full
demand. However, if there exists a coordinate "agent" of dimension "asset" giving
the :py:attr:`~muse.agents.agent.AbstractAgent.uuid` of the agent, then agents will
only service that par of the demand.
only service that part of the demand.
"""

from __future__ import annotations
Expand Down Expand Up @@ -67,7 +67,7 @@ def demand_share(
from muse.utilities import check_dimensions

DEMAND_SHARE_SIGNATURE = Callable[
[Sequence[AbstractAgent], xr.Dataset, xr.Dataset, KwArg(Any)], xr.DataArray
[Sequence[AbstractAgent], xr.DataArray, xr.Dataset, KwArg(Any)], xr.DataArray
]
"""Demand share signature."""

Expand Down Expand Up @@ -96,7 +96,7 @@ def factory(

def demand_share(
agents: Sequence[AbstractAgent],
market: xr.Dataset,
demand: xr.DataArray,
technologies: xr.Dataset,
**kwargs,
) -> xr.DataArray:
Expand All @@ -107,19 +107,19 @@ def demand_share(

# Check inputs
check_dimensions(
market,
demand,
["commodity", "year", "timeslice", "region"],
optional=["dst_region"],
)
assert len(market.year) == 2
assert len(demand.year) == 2
check_dimensions(
technologies,
["technology", "region"],
optional=["timeslice", "commodity", "dst_region"],
)

# Calculate demand share
result = function(agents, market, technologies, **keyword_args)
result = function(agents, demand, technologies, **keyword_args)

# Check result
check_dimensions(
Expand All @@ -133,7 +133,7 @@ def demand_share(
@register_demand_share(name="new_and_retro")
def new_and_retro(
agents: Sequence[AbstractAgent],
market: xr.Dataset,
demand: xr.DataArray,
technologies: xr.Dataset,
timeslice_level: str | None = None,
) -> xr.DataArray:
Expand All @@ -147,10 +147,7 @@ def new_and_retro(
agents: a list of all agents. This list should mainly be used to determine the
type of an agent and the assets it owns. The agents will not be modified in
any way.
market: the market for which to satisfy the demand. It should contain at-least
``consumption`` and ``supply``. It may contain ``prices`` if that is of use
to the production method. The ``consumption`` reflects the demand for the
commodities produced by the current sector.
demand: commodity demands for the current year and investment year.
technologies: quantities describing the technologies.
timeslice_level: the timeslice level of the sector (e.g. "hour", "day")

Expand Down Expand Up @@ -253,7 +250,7 @@ def new_and_retro(
reduce_assets,
)

current_year, investment_year = map(int, market.year.values)
current_year, investment_year = map(int, demand.year.values)

def decommissioning(capacity, technologies):
return decommissioning_demand(
Expand All @@ -274,7 +271,7 @@ def decommissioning(capacity, technologies):

demands = new_and_retro_demands(
capacity,
market,
demand,
technodata,
timeslice_level=timeslice_level,
)
Expand Down Expand Up @@ -362,7 +359,7 @@ def decommissioning(capacity, technologies):
@register_demand_share(name="default")
def standard_demand(
agents: Sequence[AbstractAgent],
market: xr.Dataset,
demand: xr.DataArray,
technologies: xr.Dataset,
timeslice_level: str | None = None,
) -> xr.DataArray:
Expand All @@ -376,10 +373,7 @@ def standard_demand(
agents: a list of all agents. This list should mainly be used to determine the
type of an agent and the assets it owns. The agents will not be modified in
any way.
market: the market for which to satisfy the demand. It should contain at-least
``consumption`` and ``supply``. It may contain ``prices`` if that is of use
to the production method. The ``consumption`` reflects the demand for the
commodities produced by the current sector.
demand: commodity demands for the current year and investment year.
technologies: quantities describing the technologies.
timeslice_level: the timeslice level of the sector (e.g. "hour", "day")

Expand All @@ -395,7 +389,7 @@ def standard_demand(
reduce_assets,
)

current_year, investment_year = map(int, market.year.values)
current_year, investment_year = map(int, demand.year.values)

def decommissioning(capacity, technologies):
return decommissioning_demand(
Expand Down Expand Up @@ -423,7 +417,7 @@ def decommissioning(capacity, technologies):
# Calculate new and retrofit demands
demands = new_and_retro_demands(
capacity=capacity,
market=market,
demand=demand,
technologies=technodata,
timeslice_level=timeslice_level,
)
Expand Down Expand Up @@ -487,7 +481,7 @@ def decommissioning(capacity, technologies):
@register_demand_share(name="unmet_demand")
def unmet_forecasted_demand(
agents: Sequence[AbstractAgent],
market: xr.Dataset,
demand: xr.DataArray,
technologies: xr.Dataset,
timeslice_level: str | None = None,
) -> xr.DataArray:
Expand All @@ -499,10 +493,11 @@ def unmet_forecasted_demand(
reduce_assets,
)

current_year, investment_year = map(int, market.year.values)
current_year, investment_year = map(int, demand.year.values)

comm_usage = technologies.comm_usage.sel(commodity=market.commodity)
smarket: xr.Dataset = market.where(is_enduse(comm_usage), 0)
demand = demand.where(
is_enduse(technologies.comm_usage.sel(commodity=demand.commodity)), 0
)

# Calculate existing capacity
capacity = interpolate_capacity(
Expand All @@ -511,15 +506,15 @@ def unmet_forecasted_demand(
)

# Select data for future years
future_market = smarket.sel(year=investment_year, drop=True)
future_demand = demand.sel(year=investment_year, drop=True)
future_capacity = capacity.sel(year=investment_year)

# Select technology data for assets
techs = broadcast_over_assets(technologies, capacity, installed_as_year=True)

# Calculate unmet demand
result = unmet_demand(
market=future_market,
demand=future_demand,
capacity=future_capacity,
technologies=techs,
timeslice_level=timeslice_level,
Expand Down Expand Up @@ -582,7 +577,7 @@ def _inner_split(


def unmet_demand(
market: xr.Dataset,
demand: xr.DataArray,
capacity: xr.DataArray,
technologies: xr.Dataset,
timeslice_level: str | None = None,
Expand All @@ -604,7 +599,7 @@ def unmet_demand(
# Check inputs
assert "year" not in technologies.dims
assert "year" not in capacity.dims
assert "year" not in market.dims
assert "year" not in demand.dims

# Calculate maximum production by existing assets
produced = maximum_production(
Expand All @@ -622,14 +617,14 @@ def unmet_demand(
produced = produced.sum("asset")

# Unmet demand is the difference between the consumption and the production
_unmet_demand = (market.consumption - produced).clip(min=0)
_unmet_demand = (demand - produced).clip(min=0)
assert "year" not in _unmet_demand.dims
return _unmet_demand


def new_consumption(
capacity: xr.DataArray,
market: xr.Dataset,
demand: xr.DataArray,
technologies: xr.Dataset,
timeslice_level: str | None = None,
) -> xr.DataArray:
Expand All @@ -651,21 +646,21 @@ def new_consumption(
from numpy import minimum

# Check inputs
assert len(market.year) == 2
assert len(demand.year) == 2
assert len(capacity.year) == 2
assert (market.year.values == capacity.year.values).all()
assert (demand.year.values == capacity.year.values).all()
assert "year" not in technologies.dims
current_year, investment_year = map(int, market.year.values)
current_year, investment_year = map(int, demand.year.values)

# Select data for current/future years
current_market = market.sel(year=current_year, drop=True)
future_market = market.sel(year=investment_year, drop=True)
current_demand = demand.sel(year=current_year, drop=True)
future_demand = demand.sel(year=investment_year, drop=True)
future_capacity = capacity.sel(year=investment_year)

# Calculate the increase in consumption over the investment period
delta = (future_market.consumption - current_market.consumption).clip(min=0)
delta = (future_demand - current_demand).clip(min=0)
missing = unmet_demand(
market=future_market,
demand=future_demand,
capacity=future_capacity,
technologies=technologies,
timeslice_level=timeslice_level,
Expand All @@ -677,13 +672,13 @@ def new_consumption(

def new_and_retro_demands(
capacity: xr.DataArray,
market: xr.Dataset,
demand: xr.DataArray,
technologies: xr.Dataset,
timeslice_level: str | None = None,
) -> xr.Dataset:
"""Splits demand into *new* and *retrofit* demand.

The demand (.i.e. `market.consumption`) in the investment year is split three ways:
The demand in the investment year is split three ways:

#. the demand that can be serviced by the assets that will still be operational that
year.
Expand All @@ -696,11 +691,11 @@ def new_and_retro_demands(
from muse.quantities import maximum_production

# Check inputs
assert len(market.year) == 2
assert len(demand.year) == 2
assert len(capacity.year) == 2
assert (market.year.values == capacity.year.values).all()
assert (demand.year.values == capacity.year.values).all()
assert "year" not in technologies.dims
investment_year = int(market.year[1])
investment_year = int(demand.year[1])

if hasattr(capacity, "region") and capacity.region.dims == ():
capacity["region"] = (
Expand All @@ -711,7 +706,7 @@ def new_and_retro_demands(
# Calculate demand to allocate to "new" agents
new_demand = new_consumption(
capacity=capacity,
market=market,
demand=demand,
technologies=technologies,
timeslice_level=timeslice_level,
)
Expand All @@ -728,11 +723,11 @@ def new_and_retro_demands(
)

# Existing asset should not execute beyond demand
service = minimum(service, market.consumption.sel(year=investment_year, drop=True))
service = minimum(service, demand.sel(year=investment_year, drop=True))

# Leftover demand that cannot be serviced by existing assets or "new" agents
retro_demand = (
market.consumption.sel(year=investment_year, drop=True) - new_demand - service
demand.sel(year=investment_year, drop=True) - new_demand - service
).clip(min=0)
assert "year" not in retro_demand.dims

Expand Down
2 changes: 1 addition & 1 deletion src/muse/sectors/subsector.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def aggregate_lp(
# Split demand across agents
demands = self.demand_share(
agents=self.agents,
market=market,
demand=market.consumption,
technologies=technologies,
timeslice_level=self.timeslice_level,
)
Expand Down
Loading