Skip to content
Closed
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
199 changes: 122 additions & 77 deletions src/muse/demand_share.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def demand_share(
)
check_dimensions(
technologies,
["technology", "year", "region"],
["technology", "region"],
optional=["timeslice", "commodity", "dst_region"],
)

Expand Down Expand Up @@ -251,23 +251,27 @@ def new_and_retro(
from muse.utilities import agent_concatenation, reduce_assets

def decommissioning(capacity):
from muse.quantities import decommissioning_demand

current_capacity = capacity.interp(year=current_year) # TODO
future_capacity = capacity.interp(year=current_year + forecast) # TODO
return decommissioning_demand(
technologies,
capacity,
year=[current_year, current_year + forecast],
current_capacity,
future_capacity,
timeslice_level=timeslice_level,
).squeeze("year")
)

# Select market and capacity data
current_market = market.isel(year=0, drop=True)
future_market = market.isel(year=1, drop=True)
capacity = reduce_assets([u.assets.capacity for u in agents])
# current_capacity = capacity.interp(year=market.year[0]) # TODO: change to sel
future_capacity = capacity.interp(year=market.year[1]) # TODO: change to sel

demands = new_and_retro_demands(
capacity,
market,
technologies,
current_year=current_year,
forecast=forecast,
future_capacity=future_capacity,
current_market=current_market,
future_market=future_market,
technologies=technologies,
timeslice_level=timeslice_level,
)

Expand Down Expand Up @@ -380,30 +384,33 @@ def standard_demand(
from muse.utilities import agent_concatenation, reduce_assets

def decommissioning(capacity):
from muse.quantities import decommissioning_demand

current_capacity = capacity.interp(year=current_year) # TODO
future_capacity = capacity.interp(year=current_year + forecast) # TODO
return decommissioning_demand(
technologies,
capacity,
year=[current_year, current_year + forecast],
current_capacity,
future_capacity,
timeslice_level=timeslice_level,
).squeeze("year")
)

# Make sure there are no retrofit agents
for agent in agents:
if agent.category == "retrofit":
raise RetrofitAgentInStandardDemandShare()

# Calculate existing capacity
# Select market and capacity data
current_market = market.isel(year=0, drop=True)
future_market = market.isel(year=1, drop=True)
capacity = reduce_assets([agent.assets.capacity for agent in agents])
current_capacity = capacity.interp(year=market.year[0]) # TODO: change to sel
future_capacity = capacity.interp(year=market.year[1]) # TODO: change to sel

# Calculate new and retrofit demands
demands = new_and_retro_demands(
capacity,
market,
technologies,
current_year=current_year,
forecast=forecast,
future_capacity=future_capacity,
current_market=current_market,
future_market=future_market,
technologies=technologies,
timeslice_level=timeslice_level,
)

Expand Down Expand Up @@ -474,10 +481,11 @@ def unmet_forecasted_demand(
capacity = reduce_assets([u.assets.capacity.interp(year=year) for u in agents])
capacity = cast(xr.DataArray, capacity)
result = unmet_demand(
smarket, capacity, technologies, timeslice_level=timeslice_level
market=smarket,
capacity=capacity,
technologies=technologies,
timeslice_level=timeslice_level,
)
if "year" in result.dims:
result = result.squeeze("year")
return result


Expand Down Expand Up @@ -553,9 +561,14 @@ def unmet_demand(
"""
from muse.quantities import maximum_production

assert "year" not in market.dims
assert "year" not in capacity.dims

# Calculate maximum production by existing assets
produced = maximum_production(
capacity=capacity, technologies=technologies, timeslice_level=timeslice_level
capacity=capacity,
technologies=technologies,
timeslice_level=timeslice_level,
)

# Total commodity production by summing over assets
Expand All @@ -567,16 +580,16 @@ 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)
return unmet_demand
_unmet_demand = (market.consumption - produced).clip(min=0)
assert "year" not in _unmet_demand.dims
return _unmet_demand


def new_consumption(
capacity: xr.DataArray,
market: xr.Dataset,
future_capacity: xr.DataArray,
current_market: xr.Dataset,
future_market: xr.Dataset,
technologies: xr.Dataset,
current_year: int,
forecast: int,
timeslice_level: Optional[str] = None,
) -> xr.DataArray:
r"""Computes share of the demand attributed to new agents.
Expand All @@ -596,28 +609,24 @@ def new_consumption(
"""
from numpy import minimum

# 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, timeslice_level=timeslice_level)
# Calculate the increase in consumption over the time period
delta = (future_market.consumption - current_market.consumption).clip(min=0)
missing = unmet_demand(
market=future_market,
capacity=future_capacity,
technologies=technologies,
timeslice_level=timeslice_level,
)
consumption = minimum(delta, missing)
assert "year" not in consumption.dims
return consumption


def new_and_retro_demands(
capacity: xr.DataArray,
market: xr.Dataset,
future_capacity: xr.DataArray,
current_market: xr.Dataset,
future_market: xr.Dataset,
technologies: xr.Dataset,
current_year: int,
forecast: int,
timeslice_level: Optional[str] = None,
) -> xr.Dataset:
"""Splits demand into *new* and *retrofit* demand.
Expand All @@ -634,51 +643,87 @@ def new_and_retro_demands(

from muse.quantities import maximum_production

# Interpolate market to forecast year
smarket: xr.Dataset = market.interp(year=[current_year, current_year + forecast])

# Interpolate capacity to forecast year
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)
# TODO
if hasattr(future_capacity, "region") and future_capacity.region.dims == ():
future_capacity["region"] = (
"asset",
[str(future_capacity.region.values)] * len(future_capacity.asset),
)

# Calculate demand to allocate to "new" agents
new_demand = new_consumption(
capa,
smarket,
technologies,
current_year=current_year,
forecast=forecast,
future_capacity=future_capacity,
current_market=current_market,
future_market=future_market,
technologies=technologies,
timeslice_level=timeslice_level,
)
if "year" in new_demand.dims:
new_demand = new_demand.squeeze("year")

# Maximum production in the forecast year by existing assets
service = (
maximum_production(
technologies,
capa.sel(year=current_year + forecast),
technologies=technologies,
capacity=future_capacity,
timeslice_level=timeslice_level,
)
.groupby("region")
.sum("asset")
)

# Existing asset should not execute beyond demand
service = minimum(
service, smarket.consumption.sel(year=current_year + forecast, drop=True)
)
service = minimum(service, future_market.consumption)

# 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")
retro_demand = (future_market.consumption - new_demand - service).clip(min=0)

# Return new and retrofit demands
result = xr.Dataset({"new": new_demand, "retrofit": retro_demand})
assert "year" not in result.new.dims
return result


def decommissioning_demand(
technologies: xr.Dataset,
current_capacity: xr.DataArray,
future_capacity: xr.DataArray,
timeslice_level: Optional[str] = None,
) -> xr.DataArray:
r"""Computes demand from process decommissioning.

If `year` is not given, it defaults to all years in capacity. If there are more than
two years, then decommissioning is with respect to first (or minimum) year.

Let :math:`M_t^r(y)` be the retrofit demand, :math:`^{(s)}\mathcal{D}_t^r(y)` be the
decommissioning demand at the level of the sector, and :math:`A^r_{t, \iota}(y)` be
the assets owned by the agent. Then, the decommissioning demand for agent :math:`i`
is :

.. math::

return xr.Dataset({"new": new_demand, "retrofit": retro_demand})
\mathcal{D}^{r, i}_{t, c}(y) =
\sum_\iota \alpha_{t, \iota}^r \beta_{t, \iota, c}^r
\left(A^{i, r}_{t, \iota}(y) - A^{i, r}_{t, \iota, c}(y + 1) \right)

given the utilization factor :math:`\alpha_{t, \iota}` and the fixed output factor
:math:`\beta_{t, \iota, c}`.

Furthermore, decommissioning demand is non-zero only for end-use commodities.

ncsearch-nohlsearch).. SeeAlso:
:ref:`indices`, :ref:`quantities`,
:py:func:`~muse.quantities.maximum_production`
:py:func:`~muse.commodities.is_enduse`
"""
from muse.quantities import maximum_production

# Calculate the decrease in capacity from the current year to future year
capacity_decrease = current_capacity - future_capacity

# Calculate production associated with this capacity
result = maximum_production(
technologies,
capacity_decrease,
timeslice_level=timeslice_level,
).clip(min=0)
assert "year" not in result.dims
return result
51 changes: 0 additions & 51 deletions src/muse/quantities.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
Functions for calculating costs (e.g. LCOE, EAC) are in the `costs` module.
"""

from collections.abc import Sequence
from typing import Optional, Union, cast

import numpy as np
Expand Down Expand Up @@ -234,56 +233,6 @@ def gross_margin(
return result


def decommissioning_demand(
technologies: xr.Dataset,
capacity: xr.DataArray,
year: Optional[Sequence[int]] = None,
timeslice_level: Optional[str] = None,
) -> xr.DataArray:
r"""Computes demand from process decommissioning.

If `year` is not given, it defaults to all years in capacity. If there are more than
two years, then decommissioning is with respect to first (or minimum) year.

Let :math:`M_t^r(y)` be the retrofit demand, :math:`^{(s)}\mathcal{D}_t^r(y)` be the
decommissioning demand at the level of the sector, and :math:`A^r_{t, \iota}(y)` be
the assets owned by the agent. Then, the decommissioning demand for agent :math:`i`
is :

.. math::

\mathcal{D}^{r, i}_{t, c}(y) =
\sum_\iota \alpha_{t, \iota}^r \beta_{t, \iota, c}^r
\left(A^{i, r}_{t, \iota}(y) - A^{i, r}_{t, \iota, c}(y + 1) \right)

given the utilization factor :math:`\alpha_{t, \iota}` and the fixed output factor
:math:`\beta_{t, \iota, c}`.

Furthermore, decommissioning demand is non-zero only for end-use commodities.

ncsearch-nohlsearch).. SeeAlso:
:ref:`indices`, :ref:`quantities`,
:py:func:`~muse.quantities.maximum_production`
:py:func:`~muse.commodities.is_enduse`
"""
if year is None:
year = capacity.year.values
year = sorted(year)
capacity = capacity.interp(year=year, kwargs={"fill_value": 0.0})
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_decrease,
timeslice_level=timeslice_level,
).clip(min=0)


def consumption(
technologies: xr.Dataset,
production: xr.DataArray,
Expand Down
5 changes: 4 additions & 1 deletion src/muse/sectors/sector.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,13 @@ def group_assets(x: xr.DataArray) -> xr.DataArray:
commodity=self.technologies.commodity, region=self.technologies.region
)

# Select technology data for the current year
techs = self.technologies.sel(year=current_year)

# Investments
for subsector in self.subsectors:
subsector.invest(
self.technologies,
techs,
market,
time_period=time_period,
current_year=current_year,
Expand Down
Loading
Loading