Skip to content

odys.parameters.context

Internal implementation detail. Most users do not need this page directly.

ModelContext is the shared indexing of one problem: the time and scenario coordinates, the step length, the scenario probabilities, and scenario profiles as arrays. Entity coordinates belong to the formulations. It is the only producer of time labels, which are strings.

The shared indexing of one optimization problem.

ModelContext

Bases: BaseModel

Shared indexing of one optimization problem.

Holds the time and scenario coordinates, the step length and the scenario probabilities, and turns scenario profiles and the run's initial and final conditions into arrays. Entity coordinates belong to each entity type's formulation. It is the only producer of time labels, which are strings ("0", "1", ...): arrays with integer time coordinates would align with nothing and silently mask every constraint using them.

Source code in src/odys/parameters/context.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 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
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
class ModelContext(BaseModel):
    """Shared indexing of one optimization problem.

    Holds the time and scenario coordinates, the step length and the scenario
    probabilities, and turns scenario profiles and the run's initial and final
    conditions into arrays. Entity coordinates
    belong to each entity type's formulation. It is the only producer of time labels,
    which are strings ("0", "1", ...): arrays with integer time coordinates
    would align with nothing and silently mask every constraint using them.
    """

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

    horizon: Horizon
    scenario_set: ScenarioSet
    condition_set: ConditionSet = Field(default_factory=ConditionSet)

    @cached_property
    def time(self) -> Coordinates:
        """Return the time coordinates, one string label per step."""
        return Coordinates(
            dimension=ModelDimension.Time,
            labels=tuple(str(step) for step in range(self.horizon.number_of_steps)),
        )

    @cached_property
    def scenarios(self) -> Coordinates:
        """Return the scenario coordinates, labelled by scenario name."""
        return Coordinates(dimension=ModelDimension.Scenarios, labels=self.scenario_set.names)

    @property
    def timestep_hours(self) -> float:
        """Return the length of one step in hours."""
        return self.horizon.hours_per_step

    @cached_property
    def probabilities(self) -> xr.DataArray:
        """Return the scenario probabilities along the scenario dimension."""
        return xr.DataArray(
            data=[scenario.probability for scenario in self.scenario_set.scenarios],
            coords=self.scenarios.dimension_coordinates_map,
        )

    def variable_coords(self, *entity_coordinates: Coordinates) -> dict[str, list[str]]:
        """Return the coordinates of a decision variable over scenario, time and the given entity dimensions.

        Args:
            entity_coordinates: The entity dimensions of the variable, in order.

        Returns:
            The xarray coordinate mapping, scenario first, then time, then the entity dimensions.

        """
        coords = self.scenarios.dimension_coordinates_map | self.time.dimension_coordinates_map
        for coordinates in entity_coordinates:
            coords |= coordinates.dimension_coordinates_map
        return coords

    def profiles(
        self,
        kind: type[ProfileT],
        entities: Sequence[EnergyEntity],
        coordinates: Coordinates,
        value: Callable[[ProfileT], Sequence[float]],
        *,
        default: float | None = None,
    ) -> xr.DataArray:
        """Return one series of the given kind of profile for the given entities, over scenario, entity and time.

        Args:
            kind: The profile type to read, such as `PriceProfile`.
            entities: The entities, in the order of their coordinates.
            coordinates: The entities' coordinates, which name their dimension.
            value: Reads the series from a profile, such as its values or its lower bound.
            default: The value at every step for an entity without a profile in a scenario.

        Returns:
            An array with dimensions (scenario, the entity dimension, time).

        Raises:
            OdysError: If an entity has no profile in a scenario and there is no default.

        """
        data = [
            [self._profile_values(scenario, kind, entity, value, default) for entity in entities]
            for scenario in self.scenario_set.scenarios
        ]
        return xr.DataArray(
            data=data,
            coords=(
                self.scenarios.dimension_coordinates_map
                | coordinates.dimension_coordinates_map
                | self.time.dimension_coordinates_map
            ),
        )

    def conditions(
        self,
        kind: type[ConditionT],
        entities: Sequence[Asset],
        coordinates: Coordinates,
        value: Callable[[ConditionT], float],
        *,
        default: float | None = None,
    ) -> xr.DataArray:
        """Return one value of each entity's condition of the given kind, along the entity dimension.

        Args:
            kind: The condition type to read, such as `GeneratorInitialState`.
            entities: The entities, in the order of their coordinates.
            coordinates: The entities' coordinates, which name their dimension.
            value: Reads the value from a condition, such as its power.
            default: The value for an entity without a condition of this kind. None makes the condition required;
                pass NaN to mark a missing optional condition.

        Returns:
            An array along the entity dimension.

        Raises:
            OdysError: If an entity has no condition of this kind and there is no default.

        """
        data = [self._condition_value(kind, entity, value, default) for entity in entities]
        return xr.DataArray(data, dims=coordinates.dimension, coords=coordinates.dimension_coordinates_map)

    def _condition_value(
        self,
        kind: type[ConditionT],
        entity: Asset,
        value: Callable[[ConditionT], float],
        default: float | None,
    ) -> float:
        condition = self.condition_set.get(kind, entity)
        if condition is not None:
            return value(condition)
        if default is None:
            msg = f"There is no {kind.__name__} for '{entity.name}'."
            raise OdysError(msg)
        return default

    def _profile_values(
        self,
        scenario: Scenario,
        kind: type[ProfileT],
        entity: EnergyEntity,
        value: Callable[[ProfileT], Sequence[float]],
        default: float | None,
    ) -> tuple[float, ...]:
        profile = next((profile for profile in scenario.profiles_of(kind) if profile.entity == entity), None)
        if profile is not None:
            return tuple(value(profile))
        if default is None:
            msg = f"Scenario '{scenario.name}' has no {kind.__name__} for '{entity.name}'."
            raise OdysError(msg)
        return (default,) * self.horizon.number_of_steps

probabilities cached property

Return the scenario probabilities along the scenario dimension.

scenarios cached property

Return the scenario coordinates, labelled by scenario name.

time cached property

Return the time coordinates, one string label per step.

timestep_hours property

Return the length of one step in hours.

conditions(kind, entities, coordinates, value, *, default=None)

Return one value of each entity's condition of the given kind, along the entity dimension.

Parameters:

Name Type Description Default
kind type[ConditionT]

The condition type to read, such as GeneratorInitialState.

required
entities Sequence[Asset]

The entities, in the order of their coordinates.

required
coordinates Coordinates

The entities' coordinates, which name their dimension.

required
value Callable[[ConditionT], float]

Reads the value from a condition, such as its power.

required
default float | None

The value for an entity without a condition of this kind. None makes the condition required; pass NaN to mark a missing optional condition.

None

Returns:

Type Description
DataArray

An array along the entity dimension.

Raises:

Type Description
OdysError

If an entity has no condition of this kind and there is no default.

Source code in src/odys/parameters/context.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def conditions(
    self,
    kind: type[ConditionT],
    entities: Sequence[Asset],
    coordinates: Coordinates,
    value: Callable[[ConditionT], float],
    *,
    default: float | None = None,
) -> xr.DataArray:
    """Return one value of each entity's condition of the given kind, along the entity dimension.

    Args:
        kind: The condition type to read, such as `GeneratorInitialState`.
        entities: The entities, in the order of their coordinates.
        coordinates: The entities' coordinates, which name their dimension.
        value: Reads the value from a condition, such as its power.
        default: The value for an entity without a condition of this kind. None makes the condition required;
            pass NaN to mark a missing optional condition.

    Returns:
        An array along the entity dimension.

    Raises:
        OdysError: If an entity has no condition of this kind and there is no default.

    """
    data = [self._condition_value(kind, entity, value, default) for entity in entities]
    return xr.DataArray(data, dims=coordinates.dimension, coords=coordinates.dimension_coordinates_map)

profiles(kind, entities, coordinates, value, *, default=None)

Return one series of the given kind of profile for the given entities, over scenario, entity and time.

Parameters:

Name Type Description Default
kind type[ProfileT]

The profile type to read, such as PriceProfile.

required
entities Sequence[EnergyEntity]

The entities, in the order of their coordinates.

required
coordinates Coordinates

The entities' coordinates, which name their dimension.

required
value Callable[[ProfileT], Sequence[float]]

Reads the series from a profile, such as its values or its lower bound.

required
default float | None

The value at every step for an entity without a profile in a scenario.

None

Returns:

Type Description
DataArray

An array with dimensions (scenario, the entity dimension, time).

Raises:

Type Description
OdysError

If an entity has no profile in a scenario and there is no default.

Source code in src/odys/parameters/context.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def profiles(
    self,
    kind: type[ProfileT],
    entities: Sequence[EnergyEntity],
    coordinates: Coordinates,
    value: Callable[[ProfileT], Sequence[float]],
    *,
    default: float | None = None,
) -> xr.DataArray:
    """Return one series of the given kind of profile for the given entities, over scenario, entity and time.

    Args:
        kind: The profile type to read, such as `PriceProfile`.
        entities: The entities, in the order of their coordinates.
        coordinates: The entities' coordinates, which name their dimension.
        value: Reads the series from a profile, such as its values or its lower bound.
        default: The value at every step for an entity without a profile in a scenario.

    Returns:
        An array with dimensions (scenario, the entity dimension, time).

    Raises:
        OdysError: If an entity has no profile in a scenario and there is no default.

    """
    data = [
        [self._profile_values(scenario, kind, entity, value, default) for entity in entities]
        for scenario in self.scenario_set.scenarios
    ]
    return xr.DataArray(
        data=data,
        coords=(
            self.scenarios.dimension_coordinates_map
            | coordinates.dimension_coordinates_map
            | self.time.dimension_coordinates_map
        ),
    )

variable_coords(*entity_coordinates)

Return the coordinates of a decision variable over scenario, time and the given entity dimensions.

Parameters:

Name Type Description Default
entity_coordinates Coordinates

The entity dimensions of the variable, in order.

()

Returns:

Type Description
dict[str, list[str]]

The xarray coordinate mapping, scenario first, then time, then the entity dimensions.

Source code in src/odys/parameters/context.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def variable_coords(self, *entity_coordinates: Coordinates) -> dict[str, list[str]]:
    """Return the coordinates of a decision variable over scenario, time and the given entity dimensions.

    Args:
        entity_coordinates: The entity dimensions of the variable, in order.

    Returns:
        The xarray coordinate mapping, scenario first, then time, then the entity dimensions.

    """
    coords = self.scenarios.dimension_coordinates_map | self.time.dimension_coordinates_map
    for coordinates in entity_coordinates:
        coords |= coordinates.dimension_coordinates_map
    return coords