h2integrate.control.control_strategies.storage.plm_openloop_storage_controller#
Classes
Peak-load management storage controller implementing an open-loop control strategy. |
|
|
Configuration class for the PeakLoadManagementHeuristicOpenLoopStorageController. |
- class h2integrate.control.control_strategies.storage.plm_openloop_storage_controller.PeakLoadManagementHeuristicOpenLoopStorageControllerConfig(*, commodity, commodity_rate_units, demand_profile, commodity_amount_units=None, max_capacity=None, max_soc_fraction=None, min_soc_fraction=None, init_soc_fraction=None, max_charge_rate=None, charge_equals_discharge=True, max_discharge_rate=None, charge_efficiency=None, discharge_efficiency=None, round_trip_efficiency=None, demand_profile_upstream, dispatch_priority_demand_profile, n_override_events=None, override_events_period=None, peak_range, advance_discharge_period, delay_charge_period, allow_charge_in_peak_range=True, min_peak_proximity)#
Configuration class for the PeakLoadManagementHeuristicOpenLoopStorageController.
Defines peak-selection and dispatch-priority rules used to pre-compute an open-loop discharge and recharge schedule.
- Variables:
demand_profile_upstream (int | float | list | None, optional) -- Demand values for additional connected system for each timestep, in the same units as commodity_rate_units. May be a scalar for constant demand or a list/array for time-varying demand.
dispatch_priority_demand_profile (str | None, optional) -- which demand profile takes precedence for dispatch decisions. One of ["demand_profile", "demand_profile_upstream"].
n_override_events (int | None) -- (int | None, optional): The maximum number of discharge events allowed for the priority profile in the period specified in override_events_period, or across all time steps if override_events_period is None.
override_events_period (int | str | None) -- (int | None, optional): Duration, in time steps, of the period in which the n_override_events must occur or a str indicating the time period (e.g. W for week, M for month). Defaults to the length of the simulation.
peak_range (dict) -- Daily time window restricting which timesteps are considered as peak candidates in the primary demand profile. Keys
startandendmust beHH:MM:SSstrings (e.g.{'start': '12:00:00', 'end': '17:00:00'}). Only the highest-demand timestep within this window is marked as a candidate peak for each day.advance_discharge_period (dict) -- Lead time before a detected peak at which discharge mode activates. Dict with keys
units(pandas timedelta unit string, e.g.'h') andval(numeric). For example{'units': 'h', 'val': 2}begins discharge two hours before the identified peak.delay_charge_period (dict) -- Minimum time to wait after the battery reaches minimum SOC before recharging is permitted. Dict with keys
unitsandval, using the same format asadvance_discharge_period.allow_charge_in_peak_range (bool, optional) -- If
True, charging is never suppressed. IfFalse, charging is blocked for timesteps that fall insidepeak_rangeto prevent charging whilst peak demand is expected. Defaults toTrue.min_peak_proximity (dict) -- Minimum required time separation between consecutive retained peak events. A
ValueErroris raised during setup if selected peaks violate this constraint. Dict with keysunitsandval, using the same format asadvance_discharge_period.
- Parameters:
commodity (str)
commodity_rate_units (str)
demand_profile (int | float | list | dict)
commodity_amount_units (str)
max_capacity (float | None)
max_soc_fraction (float | None)
min_soc_fraction (float | None)
init_soc_fraction (float | None)
max_charge_rate (float | None)
charge_equals_discharge (bool)
max_discharge_rate (float | None)
charge_efficiency (float | None)
discharge_efficiency (float | None)
round_trip_efficiency (float | None)
demand_profile_upstream (int | float | list | None)
dispatch_priority_demand_profile (str)
n_override_events (int | None)
override_events_period (int | str | None)
peak_range (dict)
advance_discharge_period (dict)
delay_charge_period (dict)
allow_charge_in_peak_range (bool)
min_peak_proximity (dict)
- require_storage_parameters: ClassVar[bool] = True#
- demand_profile_upstream: int | float | list | None#
- dispatch_priority_demand_profile: str#
- n_override_events: int | None#
- override_events_period: int | str | None#
- peak_range: dict#
- advance_discharge_period: dict#
- delay_charge_period: dict#
- allow_charge_in_peak_range: bool#
- min_peak_proximity: dict#
- class h2integrate.control.control_strategies.storage.plm_openloop_storage_controller.PeakLoadManagementHeuristicOpenLoopStorageController(**kwargs)#
Peak-load management storage controller implementing an open-loop control strategy.
This controller manages commodity (e.g., hydrogen) storage to reduce detected demand peaks. It detects peaks in the demand profile using configurable time windows and event limits, then uses multi-stage state machine control to:
Discharge storage in advance of peaks (configurable lead time)
Charge storage during expected low-demand periods (using provided charging window bounds)
Enforce SOC, rate, and efficiency limits throughout
The controller uses an open-loop architecture where peak discharge/charge decisions are pre-planned during setup() rather than dynamically optimized during compute().
- setup()#
Initialize controller configuration, storage inputs, and compute peak schedules.
During setup: 1. Loads and validates configuration from tech_config and plant_config options 2. Registers OpenMDAO inputs for storage parameters (capacity, charge rates, etc.) 3. Detects peaks in the demand profile (demand_profile and demand_profile_upstream) 4. Merges peaks with demand_profile_upstream prioritization if configured 5. Computes time-to-next-peak for each timestep 6. Identifies allowed charging windows based on peak_range configuration
- Raises:
ValueError -- If configuration is invalid or required keys are missing
- compute(inputs, outputs)#
Compute storage state of charge (SOC), delivered output, curtailment, and unmet demand over the simulation horizon.
This method applies an open-loop storage control strategy to balance the commodity demand and input flow. When input exceeds demand, excess commodity is used to charge storage (subject to rate, efficiency, and SOC limits). When demand exceeds input, storage is discharged to meet the deficit (also subject to constraints). SOC is updated at each time step, ensuring it remains within allowable bounds.
Dispatch strategy outline:
Discharge:
Starting when time_to_peak <= advance_discharge_period
Discharge at max rate (or less to reach targets)
Stop discharging only when SOC reaches min_soc
Charge:
When not discharging, SOC < max, and allow_charge window is active
Start charging only after delay_charge_period since last discharge
Charge at max rate (or less to reach target)
Stop charging when SOC reaches max_soc
Expected input keys:
<commodity>_in: Timeseries of commodity available at each time step.<commodity>_set_point: Timeseries set-point profile.max_charge_rate: Maximum charge rate permitted.max_capacity: Maximum total storage capacity.
Outputs populated:
<commodity>_command_value: Dispatch command to storage,negative when charging, positive when discharging.
- Raises:
UserWarning -- If the set-point profile is entirely zero.
UserWarning -- If
max_charge_rateormax_capacityis negative.
- Returns:
None
- static _build_demand_profile_dict(demand_profile, time_series)#
Convert scalar/list demand input into a timestamped demand dictionary.
- static _parse_peak_range(peak_range)#
Validate and parse peak_range values from HH:MM:SS strings.
Returns a dict with datetime.time objects.
- static get_peaks(demand_profile, n_override_events=None, override_events_period=None, min_proximity=None, peak_range={'end': '23:59:59', 'start': '00:00:00'})#
Detect demand peaks using configurable time windows and event limits.
Identifies peak demand periods from a demand profile, with control over: - Daily time windows (e.g., peak detection only 12:00-17:00 each day) - Event frequency (e.g., max 1 peak per week) - Temporal spacing (e.g., minimum 24 hours between peaks)
- Parameters:
demand_profile (dict) -- Timeseries data with keys: - 'date_time': timestamps (list or DatetimeIndex convertible) - 'demand': demand values (list or array)
n_override_events (int | None) -- Maximum number of peaks to keep globally or per period. If None, returns all daily peaks. Defaults to None.
override_events_period (int | str | None) -- Grouping period for n_override_events limit. - None: apply n_override_events limit globally (keep top-N peaks overall) - int: group by timestep intervals (e.g., 288 for 24-hour periods) - str: pandas period frequency (e.g., 'W' for week, 'M' for month) Defaults to None.
min_proximity (dict | None) -- Minimum time gap between sequential peaks. Dict with keys {'units': <pandas timedelta unit str>, 'val': <numeric>}. Example: {'units': 'D', 'val': 1} enforces 1-day minimum gap. Raises ValueError if violated. Defaults to None (no constraint).
peak_range (dict, optional) -- Daily time window for peak detection. Dict with keys: - 'start': HH:MM:SS string (inclusive) - 'end': HH:MM:SS string (exclusive) Defaults to full day.
- Returns:
pd.DataFrame --
- Input demand_profile with added 'is_peak' boolean column.
Each row is True if that timestep is a peak, False otherwise.
- Raises:
ValueError -- If configuration is invalid (bad period frequency, type mismatches, etc.)
- static merge_peaks(peaks_1, peaks_2)#
Merge peaks_1 and peak_2 schedules with peak_1 precedence.
Combines two peak schedules (primary and fallback) using day-level precedence:
For each day, if the peaks_1 profile has any peaks on that day, use all peaks_1 peaks for that day
Otherwise, use the peaks_2 peaks for that day
This allows overriding peaks (peaks_1) to take scheduling precedence while falling back to peaks_2 peaks for days with no overriding peaks.
- Parameters:
peaks_1 (pd.DataFrame | None) -- primary peak schedule with columns ['date_time', 'is_peak', 'demand', ...].
peaks_2 (pd.DataFrame) -- fallback peak schedule with same columns.
- Returns:
pd.DataFrame --
- Merged peak schedule. If peaks_2 is None, returns peaks_1 unchanged.
Otherwise, returns peaks_2 with 'is_peak' flags overridden on peak_1 peak days.
- get_time_to_peak()#
Compute time delta from each timestep to the next detected peak.
For each row in peaks_df, determines how long until the next peak (marked as is_peak=True) will occur. This enables the discharge trigger: when time_to_peak <= advance_discharge_period, discharge mode activates.
Timesteps after the final peak receive time.max as their time_to_peak value. This default prevents charging at simulation end (since advance_discharge_period will never be reached). TODO: Consider configurable end-of-horizon behavior.
Side effect: Modifies self.peaks_df by adding/updating 'time_to_peak' column with pd.Timedelta values or time.max.
- get_allowed_charge()#
Compute allowed charging time windows based on peak range configuration.
Determines for each timestep whether charging is permitted. If allow_charge_in_peak_range=True, charging is allowed at all times. Otherwise, charging is suppressed during the configured peak_range window (e.g., 12:00-17:00 each day) to prioritize meeting peak demand from storage.
Side effect: Modifies self.peaks_df by adding/updating 'allow_charge' column with boolean values (True=charging allowed, False=charging suppressed).