timeseries — multi-scenario load flow, no dynamics
This is feature 2.1 from the migration plan: new relative to the
MATLAB tool, which had no equivalent. Each Snapshot is a set of P/Q
overrides (dispatch and/or load) applied to a base Network;
run_time_series() solves each one with the existing static power-flow
adapter in a plain loop — no dynamics, no coupling between snapshots.
sequenceDiagram
participant U as caller
participant S as scale_loads() / dispatch_sweep()
participant A as apply_snapshot()
participant PF as run_power_flow()
participant R as TimeSeriesResult
U->>S: base Network, {label: factor, ...}
S-->>U: list[Snapshot]
loop each Snapshot
U->>A: Network, Snapshot
A-->>U: new Network (der_units/loads overridden,<br/>everything else unchanged)
U->>PF: run_power_flow(overridden Network)
PF-->>R: bus_table() or empty if not converged
end
R-->>U: bus_tables[label], converged[label]
This is a deliberate simplicity choice over pandapower’s native
timeseries/controller batch API: it reuses the already-tested
run_power_flow() path exactly, at the cost of not getting pandapower’s
vectorized C-level speedup. Worth revisiting if sweep size ever makes that
difference matter — swap the inner loop for pandapower’s batch
update_data/OutputWriter machinery without changing the
Snapshot/TimeSeriesResult interface.
Network is a frozen pydantic model, so apply_snapshot() never mutates
the base network — model_copy(update=...) produces an independent copy
per snapshot, which is what makes running snapshots in any order (or in
parallel) safe.
Reference
Time-series load flow: multiple dispatch/load snapshots, no dynamics.
This is feature 2.1 from the migration plan — new relative to the MATLAB
tool. Each Snapshot is a set of P/Q overrides (dispatch and/or
load) applied to a base Network, and
run_time_series() solves each one with the existing static
power-flow adapter (g2elin_core.powerflow) in a plain loop.
That’s a deliberate simplicity choice over pandapower’s native
timeseries/controller batch API: it reuses the already-tested
run_power_flow() path exactly, at the cost of
not getting pandapower’s vectorized C-level speedup. Worth revisiting if
sweep size ever makes that speed difference matter (see the migration
plan’s “pandapower solve speed at scale” risk note) — swap
run_time_series()’s inner loop for pandapower’s batch
update_data/OutputWriter machinery without changing the
Snapshot/TimeSeriesResult interface.
- class g2elin_core.timeseries.scenario.Snapshot(label, der_p_mw=<factory>, der_q_mvar=<factory>, load_p_mw=<factory>, load_q_mvar=<factory>)[source]
Bases:
objectP/Q overrides for one time step. Unlisted DER units/loads keep the base network’s values. DER units are keyed by
DerUnit.id, loads by their index inNetwork.loads(loads have no id field).- Parameters:
- g2elin_core.timeseries.scenario.apply_snapshot(network, snapshot)[source]
Returns a new
Networkwith the snapshot’s overrides applied.
- g2elin_core.timeseries.scenario.scale_loads(network, factors)[source]
One snapshot per
(label, factor)pair, scaling every load’s P and Q byfactorrelative to the base network — e.g. for a simple daily load curve:scale_loads(net, {"00:00": 0.6, "12:00": 1.0, "18:00": 1.15}).
- g2elin_core.timeseries.scenario.dispatch_sweep(network, der_id, p_mw_values)[source]
One snapshot per
(label, P)pair, overriding a single DER unit’s dispatch — e.g. a generator ramp/PV-curve sweep.
- class g2elin_core.timeseries.scenario.TimeSeriesResult(network: 'Network', snapshots: 'list[Snapshot]', bus_tables: 'dict[str, pd.DataFrame]', converged: 'dict[str, bool]', results: 'dict[str, PowerFlowResult]'=<factory>)[source]
Bases:
object- Parameters:
- results: dict[str, PowerFlowResult]