Skip to content

odys.results.optimization_results

Top-level results container returned by EnergySystem.optimize(). Provides access to generator, storage, and market dispatch results, solver status, and objective value.

See Results in the User Guide for a full walkthrough.

Dispatch results of one solve.

OptimalDispatchResults

Dispatch results of one solve: the solver status, the objective value and one dispatch per entity type.

Source code in src/odys/results/optimization_results.py
 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
class OptimalDispatchResults:
    """Dispatch results of one solve: the solver status, the objective value and one dispatch per entity type."""

    __slots__ = (
        "_dispatches",
        "_has_solution",
        "_objective_value",
        "_solution",
        "_solver_status",
        "_termination_condition",
    )

    def __init__(
        self,
        outcome: SolveOutcome,
        build_dispatches: Callable[[xr.Dataset], Sequence[Dispatch]],
    ) -> None:
        """Initialize from the solve outcome and the function that reads the dispatches from its solution.

        Args:
            outcome: What the solver returned.
            build_dispatches: Returns one dispatch per entity type from the solution
                (`OptimizationProblem.dispatches`); called only when the outcome has a solution.
                It gets the solution with its scenario dimension, and each dispatch is squeezed
                afterwards: derived series such as the flexible-load actual load add scenario-indexed
                parameters, which would broadcast the dimension back if the solution were squeezed first.
        """
        self._solver_status = outcome.status
        self._termination_condition = outcome.termination_condition
        self._has_solution = outcome.has_solution
        self._objective_value = outcome.objective_value
        self._solution = _without_single_scenario(outcome.solution)
        dispatches = build_dispatches(outcome.solution) if outcome.has_solution else ()
        self._dispatches: dict[type[Dispatch], Dispatch] = {
            type(dispatch): type(dispatch)(_without_single_scenario(dispatch.to_dataset()), dispatch.dimension)
            for dispatch in dispatches
        }

    @property
    def solver_status(self) -> SolveStatus:
        """Get the solver status."""
        return self._solver_status

    @property
    def termination_condition(self) -> str:
        """Get the termination condition."""
        return self._termination_condition

    def to_dataset(self) -> xr.Dataset:
        """Get the raw solution dataset."""
        self._validate_terminated_successfully()
        return self._solution

    def _validate_terminated_successfully(self) -> None:
        if not self._has_solution:
            msg = f"No solution available. Optimization Termination Condition: {self._termination_condition}."
            raise OdysSolverError(msg)

    def _dispatch_of(self, kind: type[DispatchT]) -> DispatchT:
        self._validate_terminated_successfully()
        dispatch = self._dispatches.get(kind)
        if not isinstance(dispatch, kind):
            msg = f"This model does not contain {kind.label} results"
            raise OdysNoResultsError(msg)
        return dispatch

    @property
    def generators(self) -> GeneratorDispatch:
        """Get generator dispatch results."""
        return self._dispatch_of(GeneratorDispatch)

    @property
    def battery_storages(self) -> BatteryStorageDispatch:
        """Get battery storage dispatch results."""
        return self._dispatch_of(BatteryStorageDispatch)

    @property
    def electric_vehicles(self) -> ElectricVehicleDispatch:
        """Get electric vehicle dispatch results."""
        return self._dispatch_of(ElectricVehicleDispatch)

    @property
    def chargers(self) -> ChargerDispatch:
        """Get charger dispatch results."""
        return self._dispatch_of(ChargerDispatch)

    @property
    def markets(self) -> MarketDispatch:
        """Get market dispatch results."""
        return self._dispatch_of(MarketDispatch)

    @property
    def flexible_loads(self) -> FlexibleLoadDispatch:
        """Get flexible load dispatch results."""
        return self._dispatch_of(FlexibleLoadDispatch)

    @property
    def objective_value(self) -> float | None:
        """Objective value from optimization."""
        self._validate_terminated_successfully()
        return self._objective_value

battery_storages property

Get battery storage dispatch results.

chargers property

Get charger dispatch results.

electric_vehicles property

Get electric vehicle dispatch results.

flexible_loads property

Get flexible load dispatch results.

generators property

Get generator dispatch results.

markets property

Get market dispatch results.

objective_value property

Objective value from optimization.

solver_status property

Get the solver status.

termination_condition property

Get the termination condition.

__init__(outcome, build_dispatches)

Initialize from the solve outcome and the function that reads the dispatches from its solution.

Parameters:

Name Type Description Default
outcome SolveOutcome

What the solver returned.

required
build_dispatches Callable[[Dataset], Sequence[Dispatch]]

Returns one dispatch per entity type from the solution (OptimizationProblem.dispatches); called only when the outcome has a solution. It gets the solution with its scenario dimension, and each dispatch is squeezed afterwards: derived series such as the flexible-load actual load add scenario-indexed parameters, which would broadcast the dimension back if the solution were squeezed first.

required
Source code in src/odys/results/optimization_results.py
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
def __init__(
    self,
    outcome: SolveOutcome,
    build_dispatches: Callable[[xr.Dataset], Sequence[Dispatch]],
) -> None:
    """Initialize from the solve outcome and the function that reads the dispatches from its solution.

    Args:
        outcome: What the solver returned.
        build_dispatches: Returns one dispatch per entity type from the solution
            (`OptimizationProblem.dispatches`); called only when the outcome has a solution.
            It gets the solution with its scenario dimension, and each dispatch is squeezed
            afterwards: derived series such as the flexible-load actual load add scenario-indexed
            parameters, which would broadcast the dimension back if the solution were squeezed first.
    """
    self._solver_status = outcome.status
    self._termination_condition = outcome.termination_condition
    self._has_solution = outcome.has_solution
    self._objective_value = outcome.objective_value
    self._solution = _without_single_scenario(outcome.solution)
    dispatches = build_dispatches(outcome.solution) if outcome.has_solution else ()
    self._dispatches: dict[type[Dispatch], Dispatch] = {
        type(dispatch): type(dispatch)(_without_single_scenario(dispatch.to_dataset()), dispatch.dimension)
        for dispatch in dispatches
    }

to_dataset()

Get the raw solution dataset.

Source code in src/odys/results/optimization_results.py
72
73
74
75
def to_dataset(self) -> xr.Dataset:
    """Get the raw solution dataset."""
    self._validate_terminated_successfully()
    return self._solution