Source code for gamspy.formulations.sddp.risk
from __future__ import annotations
from dataclasses import dataclass
from gamspy.exceptions import ValidationError
# CVaR is currently the only risk measure. If others are added later (e.g. a
# worst-case measure), factor out a shared base or Protocol at that point.
[docs]
@dataclass(frozen=True)
class CVaR:
"""Conditional Value-at-Risk: a convex blend of expectation and CVaR.
At every stage the cost-to-go is aggregated as
``(1 - weight) * E[cost + future] + weight * CVaR_tail[cost + future]``
that is, a blend of the expectation and the mean cost over the worst
``tail`` fraction of outcomes. With ``weight = 0`` this is the
risk-neutral expectation; with ``weight = 1`` it is pure CVaR over the
tail.
Parameters
----------
tail : float
Tail probability in ``(0, 1]``; the fraction of worst-case
outcomes that CVaR averages over.
weight : float
Risk weight in ``[0, 1]``; how much mass to place on the CVaR term
relative to the expectation.
"""
tail: float
weight: float
def __post_init__(self) -> None:
self.validate()
[docs]
def validate(self) -> None:
for name, val in (("tail", self.tail), ("weight", self.weight)):
if isinstance(val, bool) or not isinstance(val, (int, float)):
raise ValidationError(
f"CVaR {name} must be a number, got {type(val).__name__}"
)
if not 0.0 < self.tail <= 1.0:
raise ValidationError(f"CVaR tail must be in (0, 1], got {self.tail}")
if not 0.0 <= self.weight <= 1.0:
raise ValidationError(f"CVaR weight must be in [0, 1], got {self.weight}")