flexmeasures.data.models.planning

Modules

flexmeasures.data.models.planning.devices

Typed device tracking for schedulers.

flexmeasures.data.models.planning.exceptions

flexmeasures.data.models.planning.highspy_optimization

Direct HiGHS (highspy) implementation of the device scheduler.

flexmeasures.data.models.planning.linear_optimization

flexmeasures.data.models.planning.process

flexmeasures.data.models.planning.scheduling_problem

Solver-agnostic preparation of the device scheduler's inputs.

flexmeasures.data.models.planning.soc_projection

Projection of off-tick point-like SoC constraints onto scheduling ticks.

flexmeasures.data.models.planning.storage

flexmeasures.data.models.planning.utils

Functions

flexmeasures.data.models.planning.extract_devices(device)

Return a flat list of unique device identifiers from: - scalar device - Series of scalars - Series of iterables (e.g. [0, 1])

flexmeasures.data.models.planning.map_device_to_group(device_series, device_group_map)

Map device identifiers to device_group.

  • scalar device → group label

  • iterable of devices → group label (must be identical)

Classes

class flexmeasures.data.models.planning.Commitment(name: str, device: Series = None, device_group: Series = None, index: DatetimeIndex = None, _type: str = 'each', quantity: Series = 0, upwards_deviation_price: Series = 0, downwards_deviation_price: Series = 0, commodity: str | Series | None = None, stock: int | None = None, provenance: str = 'scheduler')

Contractual commitment specifying prices for deviating from a given position.

Attributes:

name: Name of the commitment. device: Device to which the commitment pertains. If None, the commitment pertains to the EMS. index: Pandas DatetimeIndex defining the time slots to which the commitment applies.

The index is shared by the group, quantity, upwards_deviation_price and downwards_deviation_price Pandas Series.

_type: ‘any’ or ‘each’. Any deviation is penalized via 1 group, whereas each deviation is penalized via n groups. group: Each time slot is assigned to a group. Deviations are determined for each group.

The deviation of a group is determined by the time slot with the maximum deviation within that group.

quantity: The deviation for each group is determined with respect to this quantity.

Can be initialized with a constant value, but always returns a Pandas Series (see also the index parameter).

upwards_deviation_price:

The deviation in the upwards direction is priced against this price. Use a positive price to set a penalty. Can be initialized with a constant value, but always returns a Pandas Series (see also the index parameter).

downwards_deviation_price:

The deviation in the downwards direction is priced against this price. Use a negative price to set a penalty. Can be initialized with a constant value, but always returns a Pandas Series (see also the index parameter).

__init__(name: str, device: Series = None, device_group: Series = None, index: DatetimeIndex = None, _type: str = 'each', quantity: Series = 0, upwards_deviation_price: Series = 0, downwards_deviation_price: Series = 0, commodity: str | Series | None = None, stock: int | None = None, provenance: str = 'scheduler') None
pretty_print()

Pretty-print a list of Commitment objects as tabulated pandas DataFrames.

For each Commitment, a DataFrame indexed by time is created containing the commitment name, device values, group index, quantity, and any available upward or downward deviation prices. Each commitment is printed separately in a readable table format, making this function suitable for debugging, logging, and interactive inspection.

provenance: str = 'scheduler'

Who defined the commitment: “scheduler” for commitments the scheduler sets up internally, “custom” for user-given commitments (from the flex-context). Used to disambiguate cost reporting when names collide.

stock: int | None = None

Stock key of the stock the commitment pertains to (StockCommitments only). When set, the solver couples the commitment to the stock group as a whole, rather than to the device index named by device.

to_frame() DataFrame

Contains all info, including the name (so the solver can identify the commitment in error messages).

class flexmeasures.data.models.planning.FlowCommitment(name: str, device: Series = None, device_group: Series = None, index: DatetimeIndex = None, _type: str = 'each', quantity: Series = 0, upwards_deviation_price: Series = 0, downwards_deviation_price: Series = 0, commodity: str | Series | None = None, stock: int | None = None, provenance: str = 'scheduler')

NB index contains event start, while quantity applies to average flow between event start and end.

class flexmeasures.data.models.planning.Scheduler(sensor: Sensor | None = None, start: datetime | None = None, end: datetime | None = None, resolution: timedelta | None = None, belief_time: datetime | None = None, asset_or_sensor: GenericAsset | Sensor | None = None, round_to_decimals: int | None = 6, flex_model: list[dict] | dict | None = None, flex_context: dict | None = None, return_multiple: bool = False)

Superclass for all FlexMeasures Schedulers.

A scheduler currently computes the schedule for one flexible asset. TODO: extend to multiple flexible assets.

The scheduler knows the power sensor of the flexible asset. It also knows the basic timing parameter of the schedule (start, end, resolution), including the point in time when knowledge can be assumed to be available (belief_time).

Furthermore, the scheduler needs to have knowledge about the asset’s flexibility model, which says under what constraints the schedule may be optimized, and about the system’s flexibility context, which says which other sensors are relevant, e.g. prices. These two flexibility configurations are usually fed in from outside, so the scheduler should check them. The deserialize_flex_config function can be used for that.

__init__(sensor: Sensor | None = None, start: datetime | None = None, end: datetime | None = None, resolution: timedelta | None = None, belief_time: datetime | None = None, asset_or_sensor: GenericAsset | Sensor | None = None, round_to_decimals: int | None = 6, flex_model: list[dict] | dict | None = None, flex_context: dict | None = None, return_multiple: bool = False)

Initialize a new Scheduler.

TODO: We might adapt the class design, so that a Scheduler object is initialized with configuration parameters,
and can then be used multiple times (via compute()) to compute schedules of different kinds, e.g.

If we started later (put in a later start), what would the schedule be? If we could change set points less often (put in a coarser resolution), what would the schedule be? If we knew what was going to happen (put in a later belief_time), what would the schedule have been?

For now, we don’t see the best separation between config and state parameters (esp. within flex models) E.g. start and flex_model[soc_at_start] are intertwined.

static _build_stock_groups(flex_model: list[dict]) dict

Build stock groups where devices sharing the same state-of-charge sensor are grouped together.

Deprecated: use DeviceInventory.stock_groups (see planning.devices), which classifies flex-model entries once and keeps stock-group keys in sync with the stock parameters. Note that this function’s synthetic keys (for devices without a state-of-charge sensor) depend on the length of the passed list, so they only match stock_models keys built from the same list.

static _get_sensor_or_raise(sensor_id: int) Sensor

Look up a sensor by ID; raise ValueError if missing (SensorIdField style).

collect_flex_config()

Merge the flex-config from the db (from the asset and its ancestors) with the initialization flex-config.

Note that self.flex_context overrides db_flex_context (from the asset and its ancestors). Merging twice would be wrong rather than merely wasteful, so this returns early when it already ran.

compute() Series | list[dict[str, Any]] | None

Overwrite with the actual computation of your schedule.

compute_schedule() Series | None

Overwrite with the actual computation of your schedule.

Deprecated method in v0.14. As an alternative, use Scheduler.compute().

property data_source: DataSource

The data source describing this scheduler, its version and its configuration.

Unlike reporters and forecasters, a scheduler names its source after the scheduler’s author, and versions it by the scheduler’s __version__, so this does not defer to DataGenerator.data_source. What it does share is that the configuration is part of the source’s identity: two schedules computed under different flex configs are recorded by different sources.

deserialize_config()

Check all configurations we have, throwing either ValidationErrors or ValueErrors. Other code can decide if/how to handle those.

deserialize_flex_config()

Check if the flex model and flex context are valid. Should be overwritten.

Ideas: - Apply a schema to check validity (see in-built flex model schemas) - Check for inconsistencies between settings (can also happen in Marshmallow) - fill in missing values from the scheduler’s knowledge (e.g. sensor attributes)

Raises ValidationErrors or ValueErrors.

deserialize_timing_config()

Check if the timing of the schedule is valid. Raises ValueErrors.

device_inventory = None

Typed classification of the flex config (see planning.devices.DeviceInventory); derived state, (re)built when the flex config is deserialized.

classmethod get_data_source_info() dict

Create and return the data source info, from which a data source lookup/creation is possible. See for instance get_data_source_for_job().

persist_flex_model()

If useful, (parts of) the flex model can be persisted here, e.g. as asset attributes, sensor attributes or as sensor data (beliefs).

record_config(config: dict) None

Record config on this scheduler’s data source, rather than the config it resolves itself.

A device job of a sequential schedule uses the configuration of the request it belongs to, so that one request records one schedule per sensor, rather than one per device’s own slice of the flex-model.

resolve_flex_config() dict

The serialized flex config this scheduler computes with, which is what its data source records.

By default, that is the flex config as it was passed in. A scheduler which also reads flex config from the asset tree should override this, so that its data source describes the configuration the scheduler actually used.

The config is kept serialized, as the trigger message and the asset tree spell it, because a deserialized flex config holds sensors, quantities and time series, which do not survive a round trip.

class flexmeasures.data.models.planning.StockCommitment(name: str, device: Series = None, device_group: Series = None, index: DatetimeIndex = None, _type: str = 'each', quantity: Series = 0, upwards_deviation_price: Series = 0, downwards_deviation_price: Series = 0, commodity: str | Series | None = None, stock: int | None = None, provenance: str = 'scheduler')

NB index contains event start, while quantity applies to stock at event end.