Skip to content

odys.energy_system

Main entry point for configuring and optimizing an energy system.

Read this page if you want to understand the orchestration object that ties assets, scenarios, and solver execution together. For most users, this is the core API page after odys.

Energy system configuration and optimization.

This module provides the EnergySystem class, the main entry point for configuring and optimizing energy systems. It performs validation on initialization and provides an optimize() method for solving.

EnergySystem

Bases: BaseModel

Energy system configuration and optimization orchestrator.

This class provides a high-level interface for configuring and optimizing energy systems. It performs comprehensive validation on initialization to ensure the system configuration is feasible, then provides an optimize() method for solving the optimization problem.

Validation includes: - Validating that the timestep is positive and there is at least one step - Validating that scenario probabilities sum to 1 and names are unique - Checking that each initial and final condition references an asset of the system, at most one of each type per asset - Checking that every asset whose results depend on its starting state has an initial condition - Checking that durations (minimum up and down times, time in state) are whole timesteps - Checking that each generator's final power fits its available capacity at the last timestep - Checking that each profile references an asset or market of the system - Checking that every load and market has a profile in every scenario - Checking that every profile has one value per timestep - Checking that electric vehicle trips fit the horizon - Ensuring maximum supply can meet minimum demand at every timestep

Raises:

Type Description
OdysValidationError

If the system configuration is invalid or infeasible.

Example

gen = Generator(name="gen1", nominal_power=100, variable_cost=20) load = FixedLoad(name="load1") portfolio = AssetPortfolio(assets=[gen, load]) system = EnergySystem( ... portfolio=portfolio, ... timestep=timedelta(hours=1), ... number_of_steps=3, ... scenarios=Scenario(profiles=(LoadProfile(load=load, values=[50, 80, 60]),)), ... ) results = system.optimize()

Source code in src/odys/energy_system.py
 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
class EnergySystem(BaseModel):
    """Energy system configuration and optimization orchestrator.

    This class provides a high-level interface for configuring and optimizing
    energy systems. It performs comprehensive validation on initialization to ensure
    the system configuration is feasible, then provides an optimize() method
    for solving the optimization problem.

    Validation includes:
    - Validating that the timestep is positive and there is at least one step
    - Validating that scenario probabilities sum to 1 and names are unique
    - Checking that each initial and final condition references an asset of the system,
      at most one of each type per asset
    - Checking that every asset whose results depend on its starting state has an initial condition
    - Checking that durations (minimum up and down times, time in state) are whole timesteps
    - Checking that each generator's final power fits its available capacity at the last timestep
    - Checking that each profile references an asset or market of the system
    - Checking that every load and market has a profile in every scenario
    - Checking that every profile has one value per timestep
    - Checking that electric vehicle trips fit the horizon
    - Ensuring maximum supply can meet minimum demand at every timestep

    Raises:
        OdysValidationError: If the system configuration is invalid or infeasible.

    Example:
        >>> gen = Generator(name="gen1", nominal_power=100, variable_cost=20)
        >>> load = FixedLoad(name="load1")
        >>> portfolio = AssetPortfolio(assets=[gen, load])
        >>> system = EnergySystem(
        ...     portfolio=portfolio,
        ...     timestep=timedelta(hours=1),
        ...     number_of_steps=3,
        ...     scenarios=Scenario(profiles=(LoadProfile(load=load, values=[50, 80, 60]),)),
        ... )
        >>> results = system.optimize()
    """

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

    portfolio: AssetPortfolio
    timestep: timedelta
    number_of_steps: int
    objective: Objective = Field(default_factory=Objective)
    markets: EnergyMarket | Sequence[EnergyMarket] | None = Field(default=None, init_var=True)
    scenarios: Scenario | Sequence[Scenario] = Field(init_var=True)
    initial_conditions: Sequence[AnyInitialCondition] = Field(default=(), init_var=True)
    final_conditions: Sequence[AnyFinalCondition] = Field(default=(), init_var=True)

    @model_validator(mode="after")
    def _validate_inputs(self) -> Self:
        validate_entities_supported(self._entities)
        validate_energy_system_inputs(
            portfolio=self.portfolio,
            scenario_set=self.scenario_set,
            markets=self.collection_of_markets,
            horizon=self.horizon,
            condition_set=self.condition_set,
        )
        return self

    @property
    def horizon(self) -> Horizon:
        """Return the time grid of the optimization."""
        return Horizon(timestep=self.timestep, number_of_steps=self.number_of_steps)

    @cached_property
    def scenario_set(self) -> ScenarioSet:
        """Return the scenarios as a `ScenarioSet`; a single `Scenario` becomes a one-element set."""
        if isinstance(self.scenarios, Scenario):
            return ScenarioSet(scenarios=(self.scenarios,))
        return ScenarioSet(scenarios=tuple(self.scenarios))

    @cached_property
    def condition_set(self) -> ConditionSet:
        """Return the initial and final conditions as a `ConditionSet`."""
        return ConditionSet(initial=tuple(self.initial_conditions), final=tuple(self.final_conditions))

    @cached_property
    def collection_of_markets(self) -> tuple[EnergyMarket, ...]:
        """Return markets as a normalized tuple."""
        if self.markets is None:
            return ()
        if isinstance(self.markets, EnergyMarket):
            return (self.markets,)
        return tuple(self.markets)

    @property
    def _entities(self) -> tuple[EnergyEntity, ...]:
        """Return every asset of the portfolio, then every market."""
        return (*self.portfolio.assets.values(), *self.collection_of_markets)

    def build_problem(self) -> OptimizationProblem:
        """Build the optimization problem of this energy system: one formulation per entity type and objective term."""
        return OptimizationProblem.assemble(
            self._entities,
            self.horizon,
            self.scenario_set,
            self.condition_set,
            self.objective,
        )

    def optimize(self, solver_config: SolverConfig | None = None) -> OptimalDispatchResults:
        """Optimize the energy system.

        This method builds and solves the optimization model using the configured solver.

        Args:
            solver_config: Solver configuration. Uses HiGHS defaults if not provided.

        Returns:
            OptimizationResults containing the solution and metadata.

        """
        problem = self.build_problem()
        outcome = solve(build_model(problem), solver_config or SolverConfig())
        return OptimalDispatchResults(outcome, problem.dispatches)

collection_of_markets cached property

Return markets as a normalized tuple.

condition_set cached property

Return the initial and final conditions as a ConditionSet.

horizon property

Return the time grid of the optimization.

scenario_set cached property

Return the scenarios as a ScenarioSet; a single Scenario becomes a one-element set.

build_problem()

Build the optimization problem of this energy system: one formulation per entity type and objective term.

Source code in src/odys/energy_system.py
123
124
125
126
127
128
129
130
131
def build_problem(self) -> OptimizationProblem:
    """Build the optimization problem of this energy system: one formulation per entity type and objective term."""
    return OptimizationProblem.assemble(
        self._entities,
        self.horizon,
        self.scenario_set,
        self.condition_set,
        self.objective,
    )

optimize(solver_config=None)

Optimize the energy system.

This method builds and solves the optimization model using the configured solver.

Parameters:

Name Type Description Default
solver_config SolverConfig | None

Solver configuration. Uses HiGHS defaults if not provided.

None

Returns:

Type Description
OptimalDispatchResults

OptimizationResults containing the solution and metadata.

Source code in src/odys/energy_system.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
def optimize(self, solver_config: SolverConfig | None = None) -> OptimalDispatchResults:
    """Optimize the energy system.

    This method builds and solves the optimization model using the configured solver.

    Args:
        solver_config: Solver configuration. Uses HiGHS defaults if not provided.

    Returns:
        OptimizationResults containing the solution and metadata.

    """
    problem = self.build_problem()
    outcome = solve(build_model(problem), solver_config or SolverConfig())
    return OptimalDispatchResults(outcome, problem.dispatches)