Skip to content

odys.optimization.objective_terms.cvar

Internal implementation detail. The CVaR term: the value at risk, the scenarios' shortfalls below it and the shortfall constraint, weighted by the term's weight. See Conditional value at risk and CV-1.

The Conditional Value at Risk (CVaR) objective term.

CVaRTermFormulation

Bases: VariableOwner[CVaRVariables], ObjectiveTermFormulation

CVaR of the profit: the value at risk less the expected shortfall below it, scaled to the tail.

CVaR = value_at_risk - 1 / (1 - confidence_level) * sum_s probability_s * shortfall_s, with shortfall_s >= value_at_risk - profit_s and shortfall_s >= 0.

Source code in src/odys/optimization/objective_terms/cvar.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class CVaRTermFormulation(VariableOwner[CVaRVariables], ObjectiveTermFormulation):
    """CVaR of the profit: the value at risk less the expected shortfall below it, scaled to the tail.

    CVaR = value_at_risk - 1 / (1 - confidence_level) * sum_s probability_s * shortfall_s,
    with shortfall_s >= value_at_risk - profit_s and shortfall_s >= 0.
    """

    value_at_risk_name: ClassVar[str] = "cvar_value_at_risk"
    shortfall_name: ClassVar[str] = "cvar_shortfall"

    def __init__(self, term: CVaRTerm, inputs: ObjectiveTermInputs) -> None:
        """Initialize the term.

        Args:
            term: The CVaR term of the objective.
            inputs: The objective, the shared indexing, and the entity formulations.
        """
        super().__init__(inputs)
        self.term = term

    @classmethod
    def build(cls, inputs: ObjectiveTermInputs) -> Self | None:
        """Return the CVaR term's formulation, or None if the objective has no CVaR term."""
        term = inputs.objective.term_of(CVaRTerm)
        return None if term is None else cls(term, inputs)

    def _create_variables(self, model: linopy.Model) -> CVaRVariables:
        return CVaRVariables(
            value_at_risk=model.add_variables(name=self.value_at_risk_name, coords={}, lower=-np.inf),
            shortfall=model.add_variables(
                name=self.shortfall_name,
                coords=self.context.scenarios.dimension_coordinates_map,
                lower=0.0,
            ),
        )

    @constraint
    def _get_shortfall_constraint(self) -> ModelConstraint:
        """Each scenario's shortfall is at least the value at risk less that scenario's profit."""
        profit = per_scenario_profit(self.formulations)
        return ModelConstraint(
            constraint=self.variables.shortfall >= self.variables.value_at_risk - profit,
            name="cvar_shortfall_constraint",
        )

    def expression(self) -> linopy.LinearExpression:
        """Return the weight times the CVaR: value at risk less the tail-scaled expected shortfall."""
        expected_shortfall = (self.context.probabilities * self.variables.shortfall).sum(ModelDimension.Scenarios)
        tail_scale = 1 / (1 - self.term.confidence_level)
        weighted: linopy.LinearExpression = self.term.weight * (
            self.variables.value_at_risk - tail_scale * expected_shortfall
        )
        return weighted

__init__(term, inputs)

Initialize the term.

Parameters:

Name Type Description Default
term CVaRTerm

The CVaR term of the objective.

required
inputs ObjectiveTermInputs

The objective, the shared indexing, and the entity formulations.

required
Source code in src/odys/optimization/objective_terms/cvar.py
37
38
39
40
41
42
43
44
45
def __init__(self, term: CVaRTerm, inputs: ObjectiveTermInputs) -> None:
    """Initialize the term.

    Args:
        term: The CVaR term of the objective.
        inputs: The objective, the shared indexing, and the entity formulations.
    """
    super().__init__(inputs)
    self.term = term

build(inputs) classmethod

Return the CVaR term's formulation, or None if the objective has no CVaR term.

Source code in src/odys/optimization/objective_terms/cvar.py
47
48
49
50
51
@classmethod
def build(cls, inputs: ObjectiveTermInputs) -> Self | None:
    """Return the CVaR term's formulation, or None if the objective has no CVaR term."""
    term = inputs.objective.term_of(CVaRTerm)
    return None if term is None else cls(term, inputs)

expression()

Return the weight times the CVaR: value at risk less the tail-scaled expected shortfall.

Source code in src/odys/optimization/objective_terms/cvar.py
72
73
74
75
76
77
78
79
def expression(self) -> linopy.LinearExpression:
    """Return the weight times the CVaR: value at risk less the tail-scaled expected shortfall."""
    expected_shortfall = (self.context.probabilities * self.variables.shortfall).sum(ModelDimension.Scenarios)
    tail_scale = 1 / (1 - self.term.confidence_level)
    weighted: linopy.LinearExpression = self.term.weight * (
        self.variables.value_at_risk - tail_scale * expected_shortfall
    )
    return weighted

CVaRVariables

Bases: BaseModel

Decision variables of the CVaR term: the value at risk and each scenario's shortfall below it.

Source code in src/odys/optimization/objective_terms/cvar.py
18
19
20
21
22
23
24
class CVaRVariables(BaseModel):
    """Decision variables of the CVaR term: the value at risk and each scenario's shortfall below it."""

    model_config = ConfigDict(frozen=True, extra="forbid", arbitrary_types_allowed=True)

    value_at_risk: linopy.Variable
    shortfall: linopy.Variable