Skip to content

odys.results.dispatch

Typed dispatch result containers: GeneratorDispatch, BatteryStorageDispatch, ElectricVehicleDispatch, ChargerDispatch, MarketDispatch, and FlexibleLoadDispatch. Each is a Dispatch (storage and electric vehicles through BatteryDispatch): select one entity with dispatch["name"], iterate over the entities, read each series as a pandas.Series property, or get everything with .to_dataframe() and .to_dataset().

See Reading results in the User Guide.

Typed dispatch results for assets and markets.

BatteryDispatch

Bases: Dispatch

Dispatch results of battery-bearing entities: net power, state of charge and charge mode.

Source code in src/odys/results/dispatch.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
class BatteryDispatch(Dispatch):
    """Dispatch results of battery-bearing entities: net power, state of charge and charge mode."""

    __slots__ = ()

    @property
    def net_power(self) -> pd.Series:
        """Net power (discharging - charging)."""
        return self._series("net_power")

    @property
    def soc(self) -> pd.Series:
        """State of charge (MWh)."""
        return self._series("soc")

    @property
    def charge_mode(self) -> pd.Series:
        """Binary charge mode (1=charging, 0=discharging)."""
        return self._series("charge_mode")

charge_mode property

Binary charge mode (1=charging, 0=discharging).

net_power property

Net power (discharging - charging).

soc property

State of charge (MWh).

BatteryStorageDispatch

Bases: BatteryDispatch

Dispatch results for battery storages in the portfolio.

Source code in src/odys/results/dispatch.py
130
131
132
133
134
class BatteryStorageDispatch(BatteryDispatch):
    """Dispatch results for battery storages in the portfolio."""

    __slots__ = ()
    label = "battery storage"

ChargerDispatch

Bases: Dispatch

Dispatch results for chargers in the portfolio.

Source code in src/odys/results/dispatch.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
class ChargerDispatch(Dispatch):
    """Dispatch results for chargers in the portfolio."""

    __slots__ = ()
    label = "charger"

    @property
    def assignment(self) -> pd.Series:
        """Binary assignment of each electric vehicle (1=connected)."""
        return self._series("assignment")

    @property
    def power(self) -> pd.Series:
        """Power delivered by each charger (MW)."""
        return self._series("power")

assignment property

Binary assignment of each electric vehicle (1=connected).

power property

Power delivered by each charger (MW).

Dispatch

Dispatch results of every entity of one type, indexed along that type's dimension.

Wraps one dataset holding the type's series. dispatch[name] selects one entity, iterating yields one dispatch per entity, and subclasses expose each series as a typed property.

Source code in src/odys/results/dispatch.py
10
11
12
13
14
15
16
17
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
class Dispatch:
    """Dispatch results of every entity of one type, indexed along that type's dimension.

    Wraps one dataset holding the type's series. `dispatch[name]` selects one entity,
    iterating yields one dispatch per entity, and subclasses expose each series as a
    typed property.
    """

    __slots__ = ("_data", "_dimension")

    label: ClassVar[str]

    def __init__(self, data: xr.Dataset, dimension: str) -> None:
        """Initialize from the series of the entities and the dimension they are indexed along.

        Args:
            data: One data variable per series, indexed along `dimension`.
            dimension: The entity axis, such as "generator".
        """
        self._data = data
        self._dimension = dimension

    @property
    def dimension(self) -> str:
        """Return the dimension the entities are indexed along."""
        return self._dimension

    @property
    def _names(self) -> xr.DataArray:
        return self._data.coords[self._dimension]

    def __getitem__(self, key: str) -> Self:
        """Return the dispatch of one entity."""
        return type(self)(self._data.sel({self._dimension: key}), self._dimension)

    def __iter__(self) -> Iterator[Self]:
        """Iterate over the dispatch of each entity."""
        for name in self._names.to_numpy():
            yield self[str(name)]

    def __len__(self) -> int:
        """Return the number of entities."""
        return len(self._names)

    def __contains__(self, key: str) -> bool:
        """Check whether an entity exists by name."""
        return key in self._names

    def _series(self, name: str) -> pd.Series:
        return self._data[name].to_series()

    def to_dataset(self) -> xr.Dataset:
        """Return the dispatch results as an xarray Dataset (a copy, so editing it leaves the results unchanged)."""
        data: xr.Dataset = self._data.copy(deep=True)
        return data

    def to_dataframe(self) -> pd.DataFrame:
        """Return the dispatch results as a pandas DataFrame, one row per label of the dimensions every series has.

        A series with more dimensions, such as a charger's assignment to each vehicle, gets one
        column per label of the extra dimensions (`assignment[ev_1]`), so no value is repeated.
        """
        variables = list(self._data.data_vars.values())
        shared_dims = [dim for dim in self._data.dims if all(dim in variable.dims for variable in variables)]
        columns = xr.merge([_split_extra_dim(variable, shared_dims) for variable in variables])
        return columns.to_dataframe(dim_order=shared_dims)

    def __repr__(self) -> str:
        """Return the type and the entity names."""
        return f"{type(self).__name__}(names={self._names!r})"

dimension property

Return the dimension the entities are indexed along.

__contains__(key)

Check whether an entity exists by name.

Source code in src/odys/results/dispatch.py
54
55
56
def __contains__(self, key: str) -> bool:
    """Check whether an entity exists by name."""
    return key in self._names

__getitem__(key)

Return the dispatch of one entity.

Source code in src/odys/results/dispatch.py
41
42
43
def __getitem__(self, key: str) -> Self:
    """Return the dispatch of one entity."""
    return type(self)(self._data.sel({self._dimension: key}), self._dimension)

__init__(data, dimension)

Initialize from the series of the entities and the dimension they are indexed along.

Parameters:

Name Type Description Default
data Dataset

One data variable per series, indexed along dimension.

required
dimension str

The entity axis, such as "generator".

required
Source code in src/odys/results/dispatch.py
22
23
24
25
26
27
28
29
30
def __init__(self, data: xr.Dataset, dimension: str) -> None:
    """Initialize from the series of the entities and the dimension they are indexed along.

    Args:
        data: One data variable per series, indexed along `dimension`.
        dimension: The entity axis, such as "generator".
    """
    self._data = data
    self._dimension = dimension

__iter__()

Iterate over the dispatch of each entity.

Source code in src/odys/results/dispatch.py
45
46
47
48
def __iter__(self) -> Iterator[Self]:
    """Iterate over the dispatch of each entity."""
    for name in self._names.to_numpy():
        yield self[str(name)]

__len__()

Return the number of entities.

Source code in src/odys/results/dispatch.py
50
51
52
def __len__(self) -> int:
    """Return the number of entities."""
    return len(self._names)

__repr__()

Return the type and the entity names.

Source code in src/odys/results/dispatch.py
77
78
79
def __repr__(self) -> str:
    """Return the type and the entity names."""
    return f"{type(self).__name__}(names={self._names!r})"

to_dataframe()

Return the dispatch results as a pandas DataFrame, one row per label of the dimensions every series has.

A series with more dimensions, such as a charger's assignment to each vehicle, gets one column per label of the extra dimensions (assignment[ev_1]), so no value is repeated.

Source code in src/odys/results/dispatch.py
66
67
68
69
70
71
72
73
74
75
def to_dataframe(self) -> pd.DataFrame:
    """Return the dispatch results as a pandas DataFrame, one row per label of the dimensions every series has.

    A series with more dimensions, such as a charger's assignment to each vehicle, gets one
    column per label of the extra dimensions (`assignment[ev_1]`), so no value is repeated.
    """
    variables = list(self._data.data_vars.values())
    shared_dims = [dim for dim in self._data.dims if all(dim in variable.dims for variable in variables)]
    columns = xr.merge([_split_extra_dim(variable, shared_dims) for variable in variables])
    return columns.to_dataframe(dim_order=shared_dims)

to_dataset()

Return the dispatch results as an xarray Dataset (a copy, so editing it leaves the results unchanged).

Source code in src/odys/results/dispatch.py
61
62
63
64
def to_dataset(self) -> xr.Dataset:
    """Return the dispatch results as an xarray Dataset (a copy, so editing it leaves the results unchanged)."""
    data: xr.Dataset = self._data.copy(deep=True)
    return data

ElectricVehicleDispatch

Bases: BatteryDispatch

Dispatch results for electric vehicles in the portfolio.

Source code in src/odys/results/dispatch.py
137
138
139
140
141
class ElectricVehicleDispatch(BatteryDispatch):
    """Dispatch results for electric vehicles in the portfolio."""

    __slots__ = ()
    label = "electric vehicle"

FlexibleLoadDispatch

Bases: Dispatch

Dispatch results for flexible loads in the portfolio.

Source code in src/odys/results/dispatch.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
class FlexibleLoadDispatch(Dispatch):
    """Dispatch results for flexible loads in the portfolio."""

    __slots__ = ()
    label = "flexible load"

    @property
    def consumption(self) -> pd.Series:
        """Power the load consumes (MW)."""
        return self._series("consumption")

    @property
    def status(self) -> pd.Series:
        """On/off status (1 while running, always 1 for a load without a commitment)."""
        return self._series("status")

    @property
    def startup(self) -> pd.Series:
        """Startup indicator (1 at the step the load starts)."""
        return self._series("startup")

    @property
    def shutdown(self) -> pd.Series:
        """Shutdown indicator (1 at the step the load stops)."""
        return self._series("shutdown")

consumption property

Power the load consumes (MW).

shutdown property

Shutdown indicator (1 at the step the load stops).

startup property

Startup indicator (1 at the step the load starts).

status property

On/off status (1 while running, always 1 for a load without a commitment).

GeneratorDispatch

Bases: Dispatch

Dispatch results for generators in the portfolio.

Source code in src/odys/results/dispatch.py
 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
class GeneratorDispatch(Dispatch):
    """Dispatch results for generators in the portfolio."""

    __slots__ = ()
    label = "generator"

    @property
    def power(self) -> pd.Series:
        """Power output (MW)."""
        return self._series("power")

    @property
    def status(self) -> pd.Series:
        """Binary on/off status."""
        return self._series("status")

    @property
    def startup(self) -> pd.Series:
        """Binary startup event."""
        return self._series("startup")

    @property
    def shutdown(self) -> pd.Series:
        """Binary shutdown event."""
        return self._series("shutdown")

power property

Power output (MW).

shutdown property

Binary shutdown event.

startup property

Binary startup event.

status property

Binary on/off status.

MarketDispatch

Bases: Dispatch

Dispatch results for markets in the portfolio.

Source code in src/odys/results/dispatch.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class MarketDispatch(Dispatch):
    """Dispatch results for markets in the portfolio."""

    __slots__ = ()
    label = "market"

    @property
    def sell_volume(self) -> pd.Series:
        """Sell volume (MW)."""
        return self._series("sell_volume")

    @property
    def buy_volume(self) -> pd.Series:
        """Buy volume (MW)."""
        return self._series("buy_volume")

    @property
    def net_volume(self) -> pd.Series:
        """Net volume (sell - buy)."""
        return self._series("net_volume")

buy_volume property

Buy volume (MW).

net_volume property

Net volume (sell - buy).

sell_volume property

Sell volume (MW).