Skip to content

odys.optimization.formulations.battery_storage

The battery storage formulation: the shared battery model (see storage), injecting discharge minus charge into the power balance, with its degradation cost in the profit.

The variables, constraints and objective terms are written out on the Batteries formulation page.

See also BatteryStorage for the domain model and entity_arrays for the parameter arrays.

Model formulation of battery storage.

BatteryStorageFormulation

Bases: VariableFormulation[StorageVariables]

Battery storage: the shared battery model, injecting its net discharge into the bus.

Source code in src/odys/optimization/formulations/battery_storage.py
 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
class BatteryStorageFormulation(VariableFormulation[StorageVariables]):
    """Battery storage: the shared battery model, injecting its net discharge into the bus."""

    dimension = BATTERY_STORAGE
    entity_type = BatteryStorage
    net_power_name: ClassVar[str] = f"{BATTERY_STORAGE}_net_power"
    soc_name: ClassVar[str] = f"{BATTERY_STORAGE}_soc"
    charge_mode_name: ClassVar[str] = f"{BATTERY_STORAGE}_charge_mode"

    def __init__(self, storages: Sequence[BatteryStorage], context: ModelContext) -> None:
        """Initialize with the battery storages of the system.

        Args:
            storages: The battery storages, at least one.
            context: The shared indexing of the problem.
        """
        super().__init__(storages, context)
        battery = vectorize(BatteryArrays, [storage.battery for storage in storages], self.coordinates)
        states = battery_storage_state_arrays(storages, self.coordinates, context)
        self.storage = StorageFormulation(self.coordinates, battery, states, context)

    @classmethod
    def build(cls, inputs: FormulationInputs) -> Self | None:
        """Return the formulation of the battery storages, or None if there are none."""
        storages = inputs.of_type(BatteryStorage)
        return cls(storages, inputs.context) if storages else None

    def _create_variables(self, model: linopy.Model) -> StorageVariables:
        """Add the battery variables of every battery storage."""
        return self.storage.create_variables(model)

    @constraint
    def _get_battery_storage_max_charge_constraint(self) -> ModelConstraint:
        """Charging power is limited by `max_charge_power`, and only in charging mode."""
        return self.storage.max_charge_constraint(self.variables)

    @constraint
    def _get_battery_storage_max_discharge_constraint(self) -> ModelConstraint:
        """Discharging power is limited by `max_discharge_power`, and only outside charging mode."""
        return self.storage.max_discharge_constraint(self.variables)

    @constraint
    def _get_battery_storage_soc_dynamics_constraint(self) -> ModelConstraint:
        """SOC follows self-discharge, charging and discharging from step to step."""
        return self.storage.soc_dynamics_constraint(self.variables)

    @constraint
    def _get_battery_storage_soc_start_constraint(self) -> ModelConstraint:
        """The SOC after the first step starts from the storage's initial state of charge."""
        return self.storage.soc_start_constraint(self.variables)

    @constraint
    def _get_battery_storage_soc_end_constraint(self) -> list[ModelConstraint]:
        """The SOC at the last step equals the storage's final state of charge, where set."""
        return self.storage.soc_end_constraint(self.variables)

    @constraint
    def _get_battery_storage_soc_min_constraint(self) -> ModelConstraint:
        """The SOC never falls below `soc_min`."""
        return self.storage.soc_min_constraint(self.variables)

    @constraint
    def _get_battery_storage_soc_max_constraint(self) -> ModelConstraint:
        """The SOC never exceeds `soc_max`."""
        return self.storage.soc_max_constraint(self.variables)

    @constraint
    def _get_battery_storage_capacity_constraint(self) -> ModelConstraint:
        """The SOC never exceeds a full battery."""
        return self.storage.capacity_constraint(self.variables)

    @constraint
    def _get_battery_storage_net_power_constraint(self) -> ModelConstraint:
        """Net power is charging minus discharging power."""
        return self.storage.net_power_constraint(self.variables)

    def power_injection(self) -> linopy.LinearExpression:
        """Return discharging minus charging power, summed over the storages."""
        return self.storage.power_injection(self.variables)

    def profit(self) -> linopy.LinearExpression:
        """Return minus the degradation cost per scenario."""
        profit: linopy.LinearExpression = -self.storage.degradation_cost(self.variables)
        return profit

    def dispatch(self, solution: xr.Dataset) -> BatteryStorageDispatch:
        """Return the net power, state of charge and charge mode of the battery storages."""
        return BatteryStorageDispatch(self.storage.dispatch_data(solution), BATTERY_STORAGE)

__init__(storages, context)

Initialize with the battery storages of the system.

Parameters:

Name Type Description Default
storages Sequence[BatteryStorage]

The battery storages, at least one.

required
context ModelContext

The shared indexing of the problem.

required
Source code in src/odys/optimization/formulations/battery_storage.py
31
32
33
34
35
36
37
38
39
40
41
def __init__(self, storages: Sequence[BatteryStorage], context: ModelContext) -> None:
    """Initialize with the battery storages of the system.

    Args:
        storages: The battery storages, at least one.
        context: The shared indexing of the problem.
    """
    super().__init__(storages, context)
    battery = vectorize(BatteryArrays, [storage.battery for storage in storages], self.coordinates)
    states = battery_storage_state_arrays(storages, self.coordinates, context)
    self.storage = StorageFormulation(self.coordinates, battery, states, context)

build(inputs) classmethod

Return the formulation of the battery storages, or None if there are none.

Source code in src/odys/optimization/formulations/battery_storage.py
43
44
45
46
47
@classmethod
def build(cls, inputs: FormulationInputs) -> Self | None:
    """Return the formulation of the battery storages, or None if there are none."""
    storages = inputs.of_type(BatteryStorage)
    return cls(storages, inputs.context) if storages else None

dispatch(solution)

Return the net power, state of charge and charge mode of the battery storages.

Source code in src/odys/optimization/formulations/battery_storage.py
107
108
109
def dispatch(self, solution: xr.Dataset) -> BatteryStorageDispatch:
    """Return the net power, state of charge and charge mode of the battery storages."""
    return BatteryStorageDispatch(self.storage.dispatch_data(solution), BATTERY_STORAGE)

power_injection()

Return discharging minus charging power, summed over the storages.

Source code in src/odys/optimization/formulations/battery_storage.py
 98
 99
100
def power_injection(self) -> linopy.LinearExpression:
    """Return discharging minus charging power, summed over the storages."""
    return self.storage.power_injection(self.variables)

profit()

Return minus the degradation cost per scenario.

Source code in src/odys/optimization/formulations/battery_storage.py
102
103
104
105
def profit(self) -> linopy.LinearExpression:
    """Return minus the degradation cost per scenario."""
    profit: linopy.LinearExpression = -self.storage.degradation_cost(self.variables)
    return profit