What can be improved?
We have to currently input vintage availability manually. Ideally we would input it programatically as part of the math.
I have put together two possible ways we can do it, with different math in the YAML vs in the helper functions. @irm-codebase which do you think is more readable?
Both have this new math to get the year as an integer from the vintagesteps/investsteps (necessary as long as this is still an issue: calliope-project/calliope#567).
YAML math
global_expressions:
investment_year:
foreach: [investsteps]
equations:
- expression: year(investsteps)
vintage_year:
foreach: [vintagesteps]
equations:
- expression: year(vintagesteps)
investstep_resolution:
foreach: [investsteps]
equations:
- where: investsteps=get_val_at_index(investsteps, 0)
expression: get_val_at_index(investment_year, 1) - initial_year
- where: NOT investsteps=get_val_at_index(investsteps, 0)
expression: investment_year - roll(investment_year, investsteps=1)
Helper functions
class Year(ParsingHelperFunction):
#:
NAME = "year"
#:
ALLOWED_IN = ["where", "expression"]
def as_math_string(self, array: str) -> str:
return f"year({array})"
def as_array(self, array: xr.DataArray) -> xr.DataArray:
return array.dt.year
version 1:
YAML math
global_expressions:
available_vintages:
foreach: [vintagesteps, investsteps, techs]
equations:
- expression: get_available_vintages(weibull)
Helper functions
class GetVintageAvailability(ParsingHelperFunction):
#:
NAME = "get_vintage_availability"
#:
ALLOWED_IN = ["expression"]
def _weibull_func(self, year_diff: xr.DataArray) -> xr.DataArray:
shape = self._input_data.get("shape", 1)
gamma = scipy.special.gamma(1 + 1 / shape)
availability = np.exp(
-((year_diff / self._input_data.lifetime.fillna(np.inf)) ** shape)
* (gamma**shape)
)
return availability.fillna(0)
def _linear_func(self, year_diff: xr.DataArray) -> xr.DataArray:
availability = 1 - (year_diff / self._input_data.lifetime)
return availability.clip(min=0)
def _step_func(self, year_diff: xr.DataArray) -> xr.DataArray:
life_diff = self._input_data.lifetime - year_diff
availability = (life_diff).clip(min=0) / life_diff
return availability
def as_math_string(self, method: Literal["weibull", "linear", "step"]) -> str:
# TODO: implement for each func type
pass
def as_array(self, method: Literal["weibull", "linear", "step"]) -> xr.DataArray:
"""For each investment step in pathway optimisation, get the historical capacity additions that now must be decommissioned.
Args:
method (str): The method with which to assume technology survival rates.
Returns:
xr.DataArray:
"""
year_diff = (
self._input_data.investsteps.dt.year - self._input_data.vintagesteps.dt.year
)
year_diff_no_negative = year_diff.where(year_diff >= 0)
if method == "weibull":
availability = self._weibull_func(year_diff_no_negative)
elif method == "linear":
availability = self._linear_func(year_diff_no_negative)
elif method == "step":
availability = self._step_func(year_diff_no_negative)
else:
raise ValueError(f"Cannot get vintage availability with `method`: {method}")
return availability.where(year_diff_no_negative.notnull())
version 2:
YAML math
global_expressions:
available_vintages:
foreach: [vintagesteps, investsteps, techs]
equations:
- where: config.vintage_survival=weibull
expression: >-
exponential(
-(($year_diff / default_if_empty(lifetime, inf)) ** shape)
* (gamma(1 + 1 / shape) ** shape)
)
- where: config.vintage_survival=linear
expression: >-
clip(1 - ($year_diff / lifetime), lower=0)
- where: config.vintage_survival=step
expression: >-
clip(lifetime - $year_diff, lower=0) / (lifetime - $year_diff)
sub_expressions:
year_diff:
- expression: investment_year - vintage_year
Helper functions
class Exponential(ParsingHelperFunction):
#:
NAME = "exponential"
#:
ALLOWED_IN = ["expression"]
def as_math_string(self, array: str) -> str:
return rf"\exp^{{{array}}}"
def as_array(self, array: xr.DataArray) -> xr.DataArray:
return np.exp(array)
class Gamma(ParsingHelperFunction):
#:
NAME = "gamma"
#:
ALLOWED_IN = ["expression"]
def as_math_string(self, array: str) -> str:
return rf"\Gamma({array})"
def as_array(self, array: xr.DataArray) -> xr.DataArray:
return scipy.special.gamma(array)
class Clip(ParsingHelperFunction):
#:
NAME = "clip"
#:
ALLOWED_IN = ["expression"]
def as_math_string(self, array: str, lower: Optional[str] = None, upper: Optional[str] = None) -> str:
base = rf"\text{{clip}}({array}"
if lower is not None:
base += rf", \text{{lower}}={lower}"
if upper is not None:
base += rf", \text{{upper}}={upper}"
return base + ")"
def as_array(self, array: xr.DataArray, lower: Optional[str] = None, upper: Optional[str] = None) -> xr.DataArray:
return array.clip(min=lower, max=upper)
Version
v0.1.0
What can be improved?
We have to currently input vintage availability manually. Ideally we would input it programatically as part of the math.
I have put together two possible ways we can do it, with different math in the YAML vs in the helper functions. @irm-codebase which do you think is more readable?
Both have this new math to get the year as an integer from the vintagesteps/investsteps (necessary as long as this is still an issue: calliope-project/calliope#567).
YAML math
Helper functions
version 1:
YAML math
Helper functions
version 2:
YAML math
Helper functions
Version
v0.1.0