Flexibility configuration

FlexMeasures describes an optimization problem with two complementary parts: a system-wide flex-context and one or more device-specific flex-models. Together they describe what flexibility is available, what limits it, and what outcome the scheduler should optimize.

This page defines these concepts independently of how they are configured. They can be persisted on assets, edited in the UI, or supplied when triggering a schedule through the API, client, or CLI.

Describing flexibility

To compute a schedule, FlexMeasures first needs to assess the flexibility state of the system. This is described by:

  • The flex-context ― information about the system as a whole, in order to assess the value of activating flexibility.

  • Flex-models ― information about the state and possible actions of the flexible device. We will discuss these per scheduled device type.

This information goes beyond the usual time series recorded by an asset’s sensors. It can be sent to FlexMeasures through the API when triggering schedule computation. Also, this information can be persisted on the FlexMeasures data model (in the db), and is editable through the UI (actually, that is design work in progress, currently possible with the flex context).

Note

You can also specify the scheduling resolution to control how often setpoints can change in the schedule. See Scheduling resolution for details on when and how to use custom resolutions.

Let’s dive into the details ― what can you tell FlexMeasures about your optimization problem?

Variable quantities

Many API fields deal with variable quantities, for example, flex-model and flex-context fields. Unless stated otherwise, values of such fields can take one of the following forms:

  • A fixed quantity, to describe steady constraints such as a physical power capacity. For example:

    {
        "power-capacity": "15 kW"
    }
    
  • A variable quantity defined at specific moments in time, to describe dynamic constraints/preferences such as target states of charge.

    {
        "soc-targets": [
            {"datetime": "2024-02-05T08:00:00+01:00", "value": "8.2 kWh"},
            ...
            {"datetime": "2024-02-05T13:00:00+01:00", "value": "2.2 kWh"}
        ]
    }
    
  • A variable quantity defined for specific time ranges, to describe dynamic constraints/preferences such as minimum state-of-charge requirements.

    {
        "soc-minima": [
            {"start": "2024-02-05T08:00:00+01:00", "duration": "PT2H", "value": "10.1 kWh"},
            ...
            {"start": "2024-02-05T13:00:00+01:00", "end": "2024-02-05T13:15:00+01:00", "value": "10.3 kWh"}
        ]
    }
    

    Note the two distinct ways of specifying a time period ("end" in combination with "duration" also works).

    Note

    In case a field defines partially overlapping time periods, FlexMeasures automatically resolves this. By default, time periods that are defined earlier in the list take precedence. Fields that deviate from this policy will note so explicitly. (For example, for fields dealing with capacities, the minimum is selected instead.)

  • A reference to a sensor that records a variable quantity, which allows cross-referencing to dynamic contexts that are already recorded as sensor data in FlexMeasures. For instance, a site’s contracted consumption capacity that changes over time.

    {
        "site-consumption-capacity": {"sensor": 55}
    }
    

    The unit of the data is specified on the sensor.

    A sensor reference can optionally include a source filter, so it keeps pointing at the right data even when multiple sources (e.g. a forecast and a schedule) record beliefs on the same sensor:

    {
        "site-consumption-capacity": {"sensor": 55, "source-types": ["forecaster"]}
    }
    

    The supported filter keys are:

    • source-types / exclude-source-types: include or exclude sources by type (e.g. "forecaster", "scheduler", "user"). Recommended over a specific source or sources ID, because forecasters and schedulers are versioned — a version bump gives new data a new source ID, but the source-type stays the same, so filters based on it don’t need updating.

    • source-account: a list of account IDs, to filter by the account(s) linked to data sources. Useful in multi-tenant setups where several accounts run their own forecasters or schedulers.

    • sources: a list of specific data source IDs.

    • source: a single specific data source ID.

    This is the same source filtering mechanism described under Sources, just scoped to sensor references inside flex-model/flex-context fields rather than GET data endpoints.

A few fields don’t hold a single variable quantity, but a list of them, whose values add up. The soc-gain and soc-usage fields of the flex-model work this way, so that separate components (say, two loads draining the same buffer) can be described independently. Each component takes any of the forms listed above, so a component defined for specific time ranges sits one level deeper than in those examples:

{
    "soc-usage": [
        "100 W",
        {"sensor": 23},
        [
            {"start": "2024-02-05T08:00:00+01:00", "duration": "PT2H", "value": "10.1 kW"},
            {"start": "2024-02-05T13:00:00+01:00", "duration": "PT2H", "value": "10.3 kW"}
        ]
    ]
}

The flex-context

The flex-context is independent of the type of flexible device that is optimized, or which scheduler is used. With the flexibility context, we aim to describe the system in which the flexible assets operate, such as its physical and contractual limitations. For multi-commodity scheduling problems, the flex-context can be defined separately per commodity (e.g. electricity and gas). See A flex-modeling tutorial for storage: Multiple commodities (gas & electricity) for a hands-on example.

A non-electricity commodity that defines no energy prices and no capacity (grid-connection) fields in the flex-context (e.g. a heat or steam network without a grid connection) is treated as an internal node: its devices must balance each other at every time step, so everything produced into the node is consumed from it within the same time step. Electricity is the exception: it is always assumed to be grid-connected, so electricity without a price raises an error rather than becoming an internal node. Devices that convert between commodities (such as a CHP unit, gas boiler or electric heater) are described in the flex-model, one entry per commodity port, tied together by a coupling group. See Converters between commodities for a worked example flex-model.

Fields can have fixed values, but some fields can also point to sensors, so they will always represent the dynamics of the asset’s environment (as long as that sensor has current data). The full list of flex-context fields follows below. For more details on the possible formats for field values, see Variable quantities.

Where should you set these fields? Within requests to the API or by editing the relevant asset in the UI. If they are not sent in via the API (one of the endpoints triggering schedule computation), the scheduler will look them up on the flex-context field of the asset. And if the asset belongs to a larger system (a hierarchy of assets), the scheduler will also search if parent assets have them set.

Field

Example value

Description

commodity

electricity or gas

Commodity to which this part of the flex-context applies. Defaults to "electricity".

inflexible-consumption

[{'sensor': 3}, {'sensor': 4}]

Power (or energy) sensors representing loads that are relevant, but not flexible in the timing of their demand. For example, a sensor recording the power of a base load that is connected behind the main meter, and whose consumption falls under the same contract as the flexible device(s) being scheduled. Their power demand cannot be adjusted but still matters for finding the best schedule for other devices.

The sign convention is determined by the key name: positive values denote consumption. Sensors that explicitly record consumption as negative values (consumption_is_positive attribute set to false) are rejected here; list them under inflexible-production instead.

Each entry is a sensor reference, optionally with source filters. In the flex-context this is a list of such references (site-level base load); in a flex-model entry it is a single reference, so that an inflexible device modelled as its own asset can join a group like any other member.

inflexible-production

[{'sensor': 3}, {'sensor': 4}]

Power (or energy) sensors representing generators that are relevant, but not flexible in the timing of their supply. For example, a sensor recording rooftop solar power that is connected behind the main meter, and whose production falls under the same contract as the flexible device(s) being scheduled. Their power supply cannot be adjusted but still matters for finding the best schedule for other devices.

The sign convention is determined by the key name: positive values denote production (the FlexMeasures default). Sensors that explicitly record production as negative values (consumption_is_positive attribute set to true) are rejected here; list them under inflexible-consumption instead.

Each entry is a sensor reference, optionally with source filters. In the flex-context this is a list of such references (site-level base generation); in a flex-model entry it is a single reference, so that an inflexible device modelled as its own asset can join a group like any other member.

inflexible-device-sensors

[3, 4]

[Deprecated field] Power sensors representing devices that are relevant, but not flexible in the timing of their demand/supply. To avoid using the field, use inflexible-consumption and/or inflexible-production instead, which make clear the sign convention. For this field, each sensor’s sign convention is determined by its consumption_is_positive attribute (default: false, i.e. production-positive). Must be a list of integers.

aggregate-consumption

{'sensor': 10}

Sensor used to record the aggregate consumption schedule of all flexible and inflexible devices involved when scheduling this asset.

The sign convention is determined by the key name, and is stored on the sensor itself using the consumption_is_positive attribute.

Depending on which output sensors are defined:

  • Only aggregate-consumption defined: the full aggregate power schedule is stored on this sensor using the consumption-positive sign convention (consumption positive, production negative).

  • Only aggregate-production defined: the full aggregate power schedule is stored on the aggregate-production sensor with the production-positive convention (production positive, consumption negative).

  • Both defined: only the non-negative part of the aggregate schedule is stored on this sensor (zero for time steps with net production), and only the non-positive part (sign-flipped) is stored on the aggregate-production sensor.

aggregate-production

{'sensor': 11}

Sensor used to record the aggregate production schedule of all flexible and inflexible devices involved when scheduling this asset.

The sign convention is determined by the key name, and is stored on the sensor itself using the consumption_is_positive attribute.

See the aggregate-consumption field for the full description of the split logic when both sensors are defined.

aggregate-power

{'sensor': 9}

[Deprecated field] Sensor used to record the aggregate power schedule of all flexible and inflexible devices involved when scheduling this asset. To avoid using the field, use aggregate-consumption or aggregate-production instead, which make clear the sign convention.

consumption-price

{'sensor': 5} or 0.29 EUR/kWh

The commodity price (e.g. electricity price) applied to the site’s aggregate consumption. Can be (a sensor recording) market prices, but also CO₂ intensity—whatever fits your optimization problem. [1]

production-price

0.12 EUR/kWh

The commodity price (e.g. electricity price) applied to the site’s aggregate production. Can be (a sensor recording) market prices, but also CO₂ intensity—whatever fits your optimization problem, as long as the unit matches the consumption-price unit. [2]

site-power-capacity

45kVA

Maximum achievable power at the site’s grid connection point, in either direction. Becomes a hard constraint in the optimization problem, which is especially suitable for physical limitations. [3] [4]

site-consumption-capacity

45kW

Maximum consumption power at the site’s grid connection point. If site-power-capacity is defined, the minimum between the site-power-capacity and site-consumption-capacity will be used. [5] If a site-consumption-breach-price is defined, the site-consumption-capacity becomes a soft constraint in the optimization problem. Otherwise, it becomes a hard constraint. [4]

site-production-capacity

0kW

Maximum production power at the site’s grid connection point. If site-power-capacity is defined, the minimum between the site-power-capacity and site-production-capacity will be used. [7] If a site-production-breach-price is defined, the site-production-capacity becomes a soft constraint in the optimization problem. Otherwise, it becomes a hard constraint. [4]

site-peak-consumption

{'sensor': 7}

The site’s previously achieved achieved peak consumption. This value forms the baseline for new peak charges, since any peaks up to this level represent sunk costs. Defaults to 0 kW.

relax-constraints

True

If True (the default), several constraints are relaxed by setting default breach prices within the optimization problem, leading to the default priority:

  1. Avoid breaching the site consumption/production capacity.

  2. Avoid not meeting SoC minima/maxima.

The specific relax-soc-constraints and relax-site-capacity-constraints flags follow this flag unless they are set explicitly, in which case they take precedence. The device consumption-capacity and production-capacity are deliberately not covered; see relax-capacity-constraints.

For tighter control over prices and priorities, the breach prices can also be set explicitly (the relevant fields have breach-price in their name).

site-consumption-breach-price

1000 EUR/kW

This penalty value is used to discourage the violation of the site-consumption-capacity constraint in the flex-context. It effectively treats the capacity as a soft constraint, allowing the scheduler to exceed it when necessary but with a high cost. The scheduler will attempt to minimize this cost. It must use the same currency as the other price settings and cannot be negative. The field may define (a sensor recording) contractual penalties, or a theoretical penalty influencing how badly breaches should be avoided. [6] [8]

site-production-breach-price

1000 EUR/kW

This penalty value is used to discourage the violation of the site-production-capacity constraint in the flex-context. It effectively treats the capacity as a soft constraint, allowing the scheduler to exceed it when necessary but with a high cost. The scheduler will attempt to minimize this cost. It must use the same currency as the other price settings and cannot be negative. The field may define (a sensor recording) contractual penalties, or a theoretical penalty influencing how badly breaches should be avoided. [6] [8]

site-peak-consumption-price

260 EUR/MW

Per-kW price applied to any consumption that exceeds the site’s previously achieved peak consumption. This price reflects the cost of increasing the site’s peak further and is used by the scheduler to motivate peak shaving. It must use the same currency as the other price settings and cannot be negative. For large connections, this price is usually stated explicitly on the tariff sheets of their network operator. [6]

site-peak-production

{'sensor': 8}

The site’s previously achieved achieved peak production. This value forms the baseline for new peak charges, since any peaks up to this level represent sunk costs. Defaults to 0 kW.

site-peak-production-price

260 EUR/MW

Per-kW price applied to any production that exceeds the site’s previously achieved peak production. This price reflects the cost of increasing the site’s peak further and is used by the scheduler to motivate peak shaving. It must use the same currency as the other price settings and cannot be negative. For large connections, this price is usually stated explicitly on the tariff sheets of their network operator. [6]

soc-minima-breach-price

120 EUR/kWh

This penalty value is used to discourage the violation of soc-minima constraints in the flex-model, which the scheduler will attempt to minimize. Together with soc-maxima-breach-price, it also prices soc-targets: falling short of a target is priced like a soc-minima breach. It must use the same currency as the other price settings and cannot be negative. While it’s an internal nudge to steer the scheduler—and doesn’t represent a real-life cost—it should still be chosen in proportion to the actual energy prices at your site. If it’s too high, it will overly dominate other constraints; if it’s too low, it will have no effect. Without this value, the soc-minima become hard constraints, which means that any infeasible state-of-charge minima would prevent a complete schedule from being computed. The same goes for the soc-targets, which need both breach prices to become soft. [6] [8]

soc-maxima-breach-price

120 EUR/kWh

This penalty value is used to discourage the violation of soc-maxima constraints in the flex-model, which the scheduler will attempt to minimize. Together with soc-minima-breach-price, it also prices soc-targets: overshooting a target is priced like a soc-maxima breach. It must use the same currency as the other price settings and cannot be negative. While it’s an internal nudge to steer the scheduler—and doesn’t represent a real-life cost—it should still be chosen in proportion to the actual energy prices at your site. If it’s too high, it will overly dominate other constraints; if it’s too low, it will have no effect. Without this value, the soc-maxima become hard constraints, which means that any infeasible state-of-charge maxima would prevent a complete schedule from being computed. The same goes for the soc-targets, which need both breach prices to become soft. [6] [8]

consumption-breach-price

10 EUR/kW

This penalty value is used to discourage the violation of the consumption-capacity constraint in the flex-model. It effectively treats the capacity as a soft constraint, allowing the scheduler to exceed it when necessary but with a high cost. The scheduler will attempt to minimize this cost. It must use the same currency as the other price settings and cannot be negative. [6] [8]

production-breach-price

10 EUR/kW

This penalty value is used to discourage the violation of the production-capacity constraint in the flex-model. It effectively treats the capacity as a soft constraint, allowing the scheduler to exceed it when necessary but with a high cost. The scheduler will attempt to minimize this cost. It must use the same currency as the other price settings and cannot be negative. [6] [8]

commitments

[{'name': 'capacity contract', 'baseline': '100 kW', 'up-price': {'sensor': 5}}]

Prior commitments. Each commitment needs a name and a baseline, plus at least one deviation price (up-price and/or down-price); its commodity defaults to electricity. You can find more information in Commitments.

Note

If no (symmetric, consumption and production) site capacity is defined (also not as defaults), the scheduler will not enforce any bound on the site power. The flexible device can still have its own power limit defined in its flex-model.

Smart defaults for commodity-context grid connections

For multi-commodity scheduling problems, each entry of the top-level commodities list is itself a flex-context (a “commodity context”) describing the grid connection for that commodity. A commodity context that leaves out some or all of its grid-connection fields (consumption-price, production-price, site-consumption-capacity, site-production-capacity and site-power-capacity) gets sensible defaults for the missing fields, rather than failing or silently leaving the grid unconstrained.

As a rule of thumb, a price given for a direction (consumption or production) implies a grid connection in that direction, with an unlimited capacity unless a capacity is also given; a capacity given for a direction (without a price) implies a zero consumption-price or production-price (respectively) in that direction; and anything not implied by a given field defaults to “no connection” (a zero capacity, as a soft constraint). The exception is site-power-capacity given on its own, which sets a hard (symmetric) capacity limit instead.

This leads to the following defaults, depending on which fields are explicitly given:

  • Nothing given (e.g. just {"commodity": "gas"}): both site-consumption-capacity and site-production-capacity default to zero, as soft constraints (a breach is possible, but penalized). site-power-capacity stays unlimited.

  • Only consumption-price: Then, site-power-capacity and site-consumption-capacity stay unlimited; site-production-capacity defaults to zero (soft).

  • Only production-price: the mirror image, for production.

  • Only site-consumption-capacity: Then, site-power-capacity stays unlimited; consumption-price defaults to zero; site-production-capacity (and, transitively, production-price) default to zero.

  • Only site-production-capacity: the mirror image, for production.

  • Only site-power-capacity: Then, a hard constraint applies at that capacity, with site-consumption-capacity and site-production-capacity both set equal to it, and consumption-price/production-price defaulting to zero.

When several fields are given, each rule only fills in the fields not already determined by a given field, per direction (consumption/production) independently. Giving all capacity fields is perfectly valid, too: the directional capacities then act as soft constraints, within the hard site-power-capacity limit. As a safety net, consumption-price still defaults to zero if it remains unset after applying the rules above, since the scheduler requires a resolvable consumption price.

Note

Setting relax-constraints to False on a commodity context that ends up with a smart-defaulted 0 hard capacity can make the schedule infeasible; FlexMeasures logs a warning in that case.

The flex-models & corresponding schedulers

FlexMeasures comes with a storage scheduler and a process scheduler, which work with flex models for storages and loads, respectively.

The storage scheduler is suitable for batteries and EV chargers, and is automatically selected when scheduling an asset with one of the following asset types: "battery", "one-way_evse" and "two-way_evse".

The process scheduler is suitable for shiftable, breakable and inflexible loads, and is automatically selected for asset types "process" and "load".

We describe the respective flex models below.

These fields can be configured in the UI editor on the asset properties page or sent through the API (one of the endpoints to trigger schedule computation, or using the FlexMeasures client) or through the CLI (the command to add schedules).

Storage

For storage devices, the FlexMeasures scheduler deals with the state of charge (SoC) for an optimal outcome. You can do a lot with this ― examples for storage devices are:

  • batteries

  • EV batteries connected to charge points

  • hot water storage (“heat batteries”, where the SoC relates to the water temperature)

  • pumped hydro storage (SoC is the water level)

  • water basins (here, SoC is supposed to be low, as water is being pumped out)

  • buffers of energy-intensive chemicals that are needed in other industry processes

The flex-model for storage devices describes to the scheduler what the flexible asset’s state is, and what constraints or preferences should be taken into account.

The full list of flex-model fields for the storage scheduler follows below. For more details on the possible formats for field values, see Variable quantities.

Field

Example value

Description

commodity

electricity or gas

Commodity on which this device acts. Defaults to "electricity".

coupling

chp

Name of the coupling group this device belongs to. Devices sharing the same coupling name are constrained to have proportionally related power flows, via a hard equality constraint. Use this to model a device that converts one commodity into another, by describing each of its commodity ports as a separate device. For example, a combined heat and power (CHP) unit is described as a gas input device, a heat output device and an electricity output device, all sharing one coupling name. Use together with coupling-coefficient to set the flow ratios.

coupling-coefficient

0.5

Positive coupling magnitude for this device within its coupling group. The scheduler couples the power flows of all devices in the group: each device’s power is its coupling coefficient times the group’s common flow level. The flow direction of each device is inferred from which directional capacity is set: a device given only a production-capacity is an output (producing) device, and a device given only a consumption-capacity is an input (consuming) device. The unspecified direction is assumed to be zero (mirroring how a missing directional site capacity defaults to zero), so there is no need to set the opposite direction to a fixed 0 (though setting it explicitly still works). For example, a CHP unit with 50% thermal and 30% electrical efficiency uses a gas input device (coefficient 1), a heat output device (coefficient 0.5) and an electricity output device (coefficient 0.3). Defaults to 1.

consumption

{'sensor': 14}

Sensor used to record the scheduled power as seen from a consumption perspective.

The sign convention is determined by the key name, and is stored on the sensor itself using the consumption_is_positive attribute.

Depending on which output sensors are defined:

  • Only consumption defined: the full power schedule is stored on this sensor using the consumption-positive sign convention (consumption positive, production negative).

  • Only production defined: the full power schedule is stored on the production sensor with the production-positive convention (production positive, consumption negative).

  • Both defined: only the non-negative part of the schedule is stored on this sensor (zero for time steps with net production), and only the non-positive part (sign-flipped) is stored on the production sensor.

production

{'sensor': 15}

Sensor used to record the scheduled power as seen from a production perspective.

The sign convention is determined by the key name, and is stored on the sensor itself using the consumption_is_positive attribute.

See the consumption field for the full description of the split logic when both sensors are defined.

state-of-charge

{'sensor': 12}

Sensor used to record the scheduled state of charge. If soc-at-start is omitted, FlexMeasures will also use this field to infer the starting state of charge. For this use case, the field may also contain a time series specification instead. When a sensor is used, its unit may be an energy unit (e.g. MWh or kWh) or a percentage (%). For sensors with a % unit, the soc-max flex-model field must be set to a non-zero value to allow converting between the energy-based schedule and a percentage. Also, the state-of-charge sensor’s resolution should be instantaneous (i.e. PT0M).

soc-at-start

3.1 kWh

The (estimated) state of charge at the beginning of the schedule (for storage devices, this defaults to 0). Usually added to each scheduling request. [9]

soc-unit

kWh

[Deprecated field] The unit used to interpret any SoC related flex-model value that does not mention a unit itself (only applies to numeric values, so not to string values). To avoid using this field, mention the unit in each field explicitly (for instance, "3.1 kWh" rather than 3.1). Only kWh and MWh are allowed.

soc-min

2.5 kWh

A constant and non-negotiable lower boundary for all SoC values in the schedule. If omitted, no lower boundary is applied. If used, this is regarded as an unsurpassable physical limitation. To set softer boundaries, use the soc-minima flex-model field instead together with the soc-minima-breach-price field in the flex-context. [9]

soc-max

7 kWh

A constant and non-negotiable upper boundary for all values in the schedule (for storage devices, this defaults to max soc-target, if that is provided). If omitted, no upper boundary is applied. If used, this is regarded as an unsurpassable physical limitation. To set softer boundaries, use the soc-maxima flex-model field instead together with the soc-maxima-breach-price field in the flex-context. [9]

soc-minima

[{'datetime': '2024-02-05T08:00:00+01:00', 'value': '8.2 kWh'}, {'value': '51 kWh', 'start': '2024-02-05T12:00:00+01:00', 'end': '2024-02-05T13:30:00+01:00'}]

Set points that form lower boundaries, e.g. to target a full car battery in the morning. Soft constraints by default (see relax-soc-constraints). [10] [11] Both single points in time and ranges are possible, see example. [13]

soc-maxima

[{'value': '51 kWh', 'start': '2024-02-05T12:00:00+01:00', 'end': '2024-02-05T13:30:00+01:00'}]

Set points that form upper boundaries at certain times, e.g. to target an empty heat buffer before a maintenance window. Soft constraints by default (see relax-soc-constraints). [10] [12] [13]

soc-targets

[{'datetime': '2024-02-05T08:00:00+01:00', 'value': '3.2 kWh'}]

Exact set point(s) of the storage’s state of charge that the scheduler needs to realize. A target is two-sided, so if both a soc-minima-breach-price and a soc-maxima-breach-price are defined, the soc-targets become soft constraints in the optimization problem. Falling short of a target is then priced like a soc-minima breach, and overshooting it like a soc-maxima breach. Otherwise, they become hard constraints, which means that any infeasible state-of-charge targets would prevent a complete schedule from being computed. [13]

soc-gain

['100 Wh/h', {'sensor': 34}]

SoC gain per time step, e.g. from a secondary energy source. Useful if energy is inserted by an external process (in-flow). This field allows setting multiple components, either fixed or dynamic, which add up to an aggregated gain. This field represents an energy flow (for instance, in kW) rather than saying something about an (allowed) energy state (for instance, in kWh). The SoC gain is unaffected by the charging efficiency.

soc-usage

['100 Wh/h', {'sensor': 23}]

SoC drain per time step, e.g. from a load or heat sink. Useful if energy is extracted by an external process or there are dissipating losses (out-flow). This field allows setting multiple components, either fixed or dynamic, which add up to an aggregated usage. This field represents an energy flow (for instance, in kW) rather than saying something about an (allowed) energy state (for instance, in kWh). The SoC drain is unaffected by the discharging efficiency.

roundtrip-efficiency

90%

Below 100%, this represents roundtrip losses (of charging & discharging), usually used for batteries. Can be a percentage or a ratio in the range [0,1]. Defaults to 100% (no roundtrip loss). [9]

charging-efficiency

.9

One-way conversion efficiency from the commodity (e.g. electricity) to the storage’s state of charge. Can be a percentage, a ratio in the range [0,1], or a coefficient of performance (>1). Defaults to 100% (no conversion loss).

discharging-efficiency

90%

One-way conversion efficiency from the storage’s state of charge to the commodity (e.g. electricity). Defaults to 100% (no conversion loss).

storage-efficiency

99.9%

The efficiency of keeping the storage’s state of charge at its present level, used to encode losses over time. As a result, each time step the energy is held longer leads to higher losses. This setting is crucial to some sorts of energy storage, e.g. thermal buffers. To give an example, when this setting is at 95% (or 0.95), this means a loss of 5% per time step. Defaults to 100% (no storage loss over time). Note that the storage efficiency used by the scheduler is applied over each time step equal to the scheduling resolution. For example, a storage efficiency of 95 percent per (absolute) day, for scheduling a 1-hour resolution sensor, should be passed as a storage efficiency of \(0.95^{1/24} = 0.997865\). Alternatively, to let FlexMeasures handle the conversion for you, record the storage-efficiency on a dedicated sensor (in this example, with a 24-hour event resolution). Then reference that sensor in the storage-efficiency field.

prefer-charging-sooner

True

Tie-breaking policy to apply if conditions are stable, which signals a preference to charge sooner rather than later (defaults to True). It also signals a preference to discharge later. Boolean option only.

prefer-curtailing-later

True

Tie-breaking policy to apply if conditions are stable, which signals a preference to curtail both consumption and production later, whichever is applicable (defaults to True). Boolean option only.

power-capacity

50 kVA

Symmetric device-level power constraint. How much power can be applied to this asset in either direction. If omitted, the scheduler infers this limit from the greatest of consumption-capacity and production-capacity when either is configured, before falling back to site-power-capacity. When exactly one of consumption-capacity or production-capacity is configured to non-zero capacity, the missing opposite capacity defaults to zero. [12]

consumption-capacity

{'sensor': 56}

Device-level power constraint on consumption. How much power can be drawn by this asset. [12] [14]

production-capacity

0 kW (only consumption)

Device-level power constraint on production. How much power can be supplied by this asset. For PV curtailment, set this to reference your sensor containing PV power forecasts. [12] [14]

operation-modes

[{'consumption-range': ['0 W', '0 W']}, {'consumption-range': ['883.7 W', '883.7 W']}]

Confine the device’s power to one of several power ranges at every time step. Each operation mode declares a consumption-range (non-negative, positive is consumption) and/or a production-range (non-negative, positive is production); a mode may use either or both, and combining both (each starting at 0) forms a single band through zero. This is useful for devices that cannot modulate their power freely, such as a device that is either off or running at some minimum power (or at one fixed power). Terminology and semantics follow the operation modes of the S2 standard. S2 fixes one sign convention for power (positive is consumption), whereas FM leaves it to the user; an S2 signed power-range therefore maps onto these fields by sign: its non-negative part corresponds to the FM consumption-range, negative S2 power values (production) correspond to the FM production-range (with their sign flipped to non-negative), and an S2 range spanning zero maps to a combination of both. Declaring operation modes introduces binary decision variables into the optimization problem (making it a mixed-integer linear program), which may increase solve times.

group

{'asset': 7}

Reference to a group of devices whose aggregate power is constrained. The recommended form identifies the group by the asset that represents the shared equipment ({"asset": <id>}, e.g. a sub-EMS asset in the tree); a power sensor ({"sensor": <id>}) is also accepted - give exactly one of the two. The referenced asset or sensor should itself get its own flex-model entry defining the group’s power-capacity (hard constraint) and/or consumption-capacity/production-capacity (soft constraints with default breach prices). When the group is referenced by asset, the group entry defines no power sensor of its own; the group’s aggregate power is saved via that entry’s own consumption and/or production output sensors, following the usual output-sensor conventions. When the group is referenced by sensor, the group’s scheduled aggregate power is saved directly to that group sensor.

inflexible-consumption

{"sensor": 3}

Power (or energy) sensors representing loads that are relevant, but not flexible in the timing of their demand. For example, a sensor recording the power of a base load that is connected behind the main meter, and whose consumption falls under the same contract as the flexible device(s) being scheduled. Their power demand cannot be adjusted but still matters for finding the best schedule for other devices.

The sign convention is determined by the key name: positive values denote consumption. Sensors that explicitly record consumption as negative values (consumption_is_positive attribute set to false) are rejected here; list them under inflexible-production instead.

Each entry is a sensor reference, optionally with source filters. In the flex-context this is a list of such references (site-level base load); in a flex-model entry it is a single reference, so that an inflexible device modelled as its own asset can join a group like any other member.

inflexible-production

{"sensor": 3}

Power (or energy) sensors representing generators that are relevant, but not flexible in the timing of their supply. For example, a sensor recording rooftop solar power that is connected behind the main meter, and whose production falls under the same contract as the flexible device(s) being scheduled. Their power supply cannot be adjusted but still matters for finding the best schedule for other devices.

The sign convention is determined by the key name: positive values denote production (the FlexMeasures default). Sensors that explicitly record production as negative values (consumption_is_positive attribute set to true) are rejected here; list them under inflexible-consumption instead.

Each entry is a sensor reference, optionally with source filters. In the flex-context this is a list of such references (site-level base generation); in a flex-model entry it is a single reference, so that an inflexible device modelled as its own asset can join a group like any other member.

For more details on the possible formats for field values, see Variable quantities.

Intermediate power constraints

In a multi-device flex-model list, a device entry may declare a group field referencing a group of devices, for example a hybrid inverter shared by a battery and PV installation, or a feeder shared by several devices. This lets you model an intermediate power constraint that sits between the individual devices and the site as a whole.

The recommended way to identify a group is by the asset that represents the shared equipment — a node in your asset tree, such as the inverter. This is the form that composes with flex-models stored on the asset tree and with multi-level hierarchies (see below), and it is what stored configurations naturally produce:

  • {"asset": <asset id>}: the group is identified by the flex-model entry on that asset (typically a sub-EMS/asset in the asset tree, such as the inverter in the example below). Such a group entry defines no power sensor of its own; instead, like any other asset-only entry, it may define consumption and/or production output sensor references (see below) on which the group’s aggregate power gets saved.

Alternatively, a group can be identified by a power sensor. This is handy for compact, one-shot flex-models passed via the API, or when you already have an aggregate power sensor (e.g. a metered inverter feed) on which to record the group’s schedule:

  • {"sensor": <power sensor id>}: the group is identified by a power sensor, which itself gets its own flex-model entry (typically passed alongside the device entries).

Either way, the group reference’s target (asset or sensor) gets its own flex-model entry, defining constraints on the group’s aggregate (summed) power:

  • power-capacity on the group is a hard constraint (applied in both directions).

  • consumption-capacity and production-capacity on the group are soft constraints, enforced with the same default breach prices used at the site level (10000 currency/kW); users cannot configure custom breach prices for groups.

The group’s scheduled aggregate power is saved as a schedule output, following the same conventions used for any device’s schedule output:

  • For an asset-referenced group (an asset-only entry), the aggregate power is saved via its consumption and/or production output sensor references: with only consumption set, the full profile is saved consumption-positive; with only production set, the full profile is saved production-positive (i.e. sign-flipped before saving); with both set, the profile is split into its non-negative part (saved to consumption) and its non-positive part (saved, as a positive magnitude, to production).

  • For a sensor-referenced group (whose flex-model entry has a sensor field), the aggregate power is saved directly to that sensor.

Groups can be nested (a group entry may itself reference a parent group), but cyclic references are rejected. Groups require a multi-device flex-model; they are rejected when scheduling a single sensor.

The recommended, tree-based way to configure a group is to define the whole flex-model on the asset tree in the DB, with no flex-model needed in the scheduling trigger at all: each device asset carries its own (partial) flex-model, including a group field pointing at the parent asset that represents the shared equipment, and that parent asset’s own flex-model defines the group’s constraints and output sensor(s). Triggering a schedule for the top-level site asset with an empty (or omitted) flex-model then collects the full configuration from the tree. For a hands-on walkthrough (including how to store flex-models on assets, and where the resulting schedules end up), see Toy example VI: Intermediate power constraints (groups).

The sensor-referenced form is convenient when you pass the whole flex-model in one go via the API. For example, a 2.5 kW hybrid inverter (sensor 5) shared by a battery (sensor 1) and PV installation (sensor 2), taken from issue #2092:

[
    {"sensor": 1, "power-capacity": "2 kW", "group": {"sensor": 5}},
    {"sensor": 2, "production-capacity": "2 kW", "consumption-capacity": "0 kW", "group": {"sensor": 5}},
    {"sensor": 5, "power-capacity": "2.5 kW"}
]

Here, the battery and PV installation may each individually schedule up to 2 kW, but their combined power flowing through the shared inverter is hard-limited to 2.5 kW.

Inflexible devices in the flex-model

Inflexible (measured) devices can be modelled in the flex-model too — for example, an unschedulable base load. To do so, model the inflexible device as its own asset and give its flex-model entry a single inflexible-consumption or inflexible-production reference to the sensor recording its power (the field name sets the sign convention, and source filters may be added). Such an entry carries no schedulable-device fields; it simply declares a fixed device whose power is accounted for. Like any device entry, it may set a commodity (defaulting to electricity), and its fixed power is then netted into that commodity’s grid connection.

There are two places to declare an inflexible device, and the choice is about where it belongs rather than what it does. Listing its sensor in the flex-context’s inflexible-consumption/inflexible-production fields describes plain site base load, which is a property of the connection. Giving it its own flex-model entry describes a device that sits somewhere specific in the asset tree — under a particular inverter, feeder or commodity — which is a property of the device. Both net the same fixed power into the grid connection.

The group field is optional on such an entry. Without it, the device is simply accounted for under the grid connection (just like listing its sensor in the flex-context’s inflexible-consumption/inflexible-production fields, only declared on the asset instead). With it, the device also joins that group through the ordinary group field, exactly like a flexible member (the group’s own flex-model entry, defining its capacities, must still be present), so that its fixed load or supply additionally counts towards the group’s intermediate power constraint — for example, a base load sitting behind the same inverter or feeder as a battery.

Usually, not the whole flexibility model is needed. FlexMeasures can infer missing values in the flex model, and even get them (as default) from the sensor’s attributes.

You can add new storage schedules with the CLI command flexmeasures add schedule.

If you model devices that buffer energy (e.g. thermal energy storage systems connected to heat pumps), we can use the same flexibility parameters described above for storage devices. However, here are some tips to model a buffer correctly:

  • Describe the thermal energy content in kWh or MWh.

  • Set soc-minima to the accumulative usage forecast.

  • Set charging-efficiency to the sensor describing the COP values.

  • Set storage-efficiency to a value below 100% to model (heat) loss.

For a hands-on example of a heat buffer fed by multiple devices, see A flex-modeling tutorial for storage: Multiple feeds into shared storage.

If the flex model describes an infeasible problem for the storage scheduler, the failure should remain visible. By default, soc-minima and soc-maxima are relaxed into soft constraints, so the scheduler can still return a useful schedule when these boundaries cannot be fully met. Setting either relax-soc-constraints or relax-constraints to false in the flex-context keeps them as hard constraints. Exact soc-targets, physical soc-min / soc-max bounds, and power-capacity (in the flex-model) and site-power-capacity (in the flex-context) remain hard constraints. If those hard constraints make the problem infeasible, the scheduling job fails instead of producing a fallback schedule.

It is important to take note of these failures. Often, misconfigured flex models are the reason.

For a hands-on tutorial on using some of the storage flex-model fields, head over to A flex-modeling tutorial for storage: Vehicle-to-grid use case and the API documentation for triggering schedules. For further hands-on examples, see A flex-modeling tutorial for storage: Multiple feeds into shared storage (multiple devices feeding one shared storage) and A flex-modeling tutorial for storage: Multiple commodities (gas & electricity) (devices on different commodities scheduled together).

Finally, are you interested in the linear programming details behind the storage scheduler? Then head over to Device scheduler: mixed-integer linear model! You can also review the current flex-model for storage in the code, at flexmeasures.data.schemas.scheduling.storage.StorageFlexModelSchema.

Shiftable loads (processes)

For processes that can be shifted or interrupted, but have to happen at a constant rate (of consumption), FlexMeasures provides the ProcessScheduler. Some examples from practice (usually industry) could be:

  • A centrifuge’s daily work of combing through sludge water. Depends on amount of sludge present.

  • Production processes with a target amount of output until the end of the current shift. The target usually comes out of production planning.

  • Application of coating under hot temperature, with fixed number of times it needs to happen before some deadline.

Field

Example value

Description

power

"15kW"

Nominal power of the load.

duration

"PT4H"

Time that the load needs to lasts.

optimization_direction

"MAX"

Objective of the scheduler, to maximize ("MAX") or minimize ("MIN").

time_restrictions

[{"start": "2015-01-02T08:00:00+01:00", "duration": "PT2H"}]

Time periods in which the load cannot be scheduled to run.

process_type

"INFLEXIBLE", "SHIFTABLE" or "BREAKABLE"

Is the load inflexible and should it run as soon as possible? Or can the process’s start time be shifted? Or can it even be broken up into smaller segments?

You can review the current flex-model for processes in the code, at flexmeasures.data.schemas.scheduling.process.ProcessSchedulerFlexModelSchema.

You can add new shiftable-process schedules with the CLI command flexmeasures add schedule. Make sure to use the --scheduler ProcessScheduler option to use the in-built process scheduler.

Note

Currently, the ProcessScheduler uses only the consumption-price field of the flex-context, so it ignores any site capacities and inflexible devices.