Scenario Sweep
Goal
This class varies both the injection (generators / static generators / loads) and a contingency (a line / transformer disconnection) per simulation, independently, row-aligned: row i of every injection input is solved together with row i of the contingency mask.
It is the 4th instantiation of the same underlying C++ template as
lightsim2grid.timeSerie.TimeSerie / lightsim2grid.injectionSweep.InjectionSweep
(which vary only the injection) and lightsim2grid.contingencyAnalysis.ContingencyAnalysis
(which varies only the topology, over one shared base injection) – see Time Series
and Contingency Analysis.
It can be used as:
import numpy as np
import grid2op
from lightsim2grid import ScenarioSweep
from lightsim2grid import LightSimBackend
env_name = ...
env = grid2op.make(env_name, backend=LightSimBackend())
obs = env.reset()
n_simul = 10
load_p = np.tile(obs.load_p, (n_simul, 1))
load_p *= 1 + 0.01 * np.arange(n_simul).reshape(-1, 1) # vary the load a bit per row
# lightsim2grid's own line / trafo counts -- NOT grid2op's flat `env.n_line`
# (grid2op merges lines and trafos into one "powerline" list; lightsim2grid keeps
# them as two separate containers, and the contingency masks below follow that split)
n_line = len(env.backend._grid.get_lines())
n_trafo = len(env.backend._grid.get_trafos())
line_mask = np.zeros((n_simul, n_line), dtype=bool)
line_mask[5:, 3] = True # disconnect line 3 for the last 5 simulations
sweep = ScenarioSweep(env)
sweep.modify_load_p(load_p)
sweep.set_contingency_lines(line_mask)
# modify_gen_p / modify_sgen_p / modify_load_q / set_contingency_trafos were never
# called: those axes default to the grid's own current state, broadcast across
# every row (see "Unset axes" below).
sweep.compute()
res_v = sweep.get_voltages()
res_a = sweep.compute_A()
res_p = sweep.compute_P()
# res_p[row_id] / res_a[row_id] / res_v[row_id] are the flows / voltages for
# simulation `row_id`, combining that row's injections and that row's contingency.
The setter-based API
Unlike TimeSerie / InjectionSweep’s single bundled compute_V_from_inj call, ScenarioSweep is built up incrementally: call as many of modify_gen_p / modify_sgen_p / modify_load_p / modify_load_q / modify_gen_v / set_contingency_lines / set_contingency_trafos as are relevant, then compute().
Row-count locking. The first setter you call fixes the number of simulations for the whole object; every later setter is checked against it immediately – a shape mistake raises right at the call that made it, not later inside compute().
Unset axes. Any injection axis you never call (eg modify_gen_p) defaults to the grid’s own current target value, broadcast across every row – not zero. Any contingency mask you never call (set_contingency_lines / set_contingency_trafos) defaults to all-False – nothing disconnected. compute() raises if you never call any setter (a degenerate call – use ac_pf() / dc_pf() directly for a single powerflow).
`modify_gen_v` is different from the other four `modify_*` setters. It does not feed the injection (Sbus) at all – a PV bus’s voltage magnitude is never part of what Newton-Raphson solves for, only of what it starts from. modify_gen_v instead re-seeds |V| at each voltage-regulating generator’s regulated bus immediately before that row’s solve, shape (n_simul, n_gen), in pu (vm_pu), NOT kV. Left unset, every row keeps using the grid’s own target_vm_pu, exactly as if it had never been called.
TimeSerie / InjectionSweep also gained this same setter API (modify_* + compute()) alongside their legacy compute_V_from_inj call, which remains available (deprecated, not removed).
Two contingency APIs, on purpose
ScenarioSweep.set_contingency_lines / set_contingency_trafos and ContingencyAnalysis.add_n1 / add_nk are deliberately two different APIs, not a naming inconsistency:
ContingencyAnalysis applies an arbitrary set of distinct contingencies to one shared base injection – add_n1(3) registers “disconnect line 3”, independent of any row ordering or count.
ScenarioSweep pairs each contingency with that same row’s own injection, so a dense, row-aligned boolean mask (shape (n_simul, n_line) / (n_simul, n_trafo), True = “deactivate this branch for this simulation”) is the natural fit instead.
ScenarioSweep does not have add_n1 / add_nk; ContingencyAnalysis does not have set_contingency_lines / set_contingency_trafos.
Note
Dense contingency masks cost real memory at scale (eg 10k simulations x 3k branches ~= 30 MB) – fine for typical use, a lighter-weight representation may be added in a future release if that becomes a bottleneck.
Handling disconnected grids and limit violations
ScenarioSweep has the same handle_disconnected_grid mode and inline limit-violation checking (compute_limit_violations / violation_threshold / get_violations / get_violations_n) as ContingencyAnalysis – see Contingency Analysis for the full description of what each does. Same names, same semantics, and both classes’ get_violations / get_violations_n return the same LimitViolation objects.
sweep = ScenarioSweep(env)
sweep.compute_limit_violations = True # see warning below: set this FIRST
sweep.handle_disconnected_grid = True
sweep.modify_load_p(load_p)
sweep.set_contingency_lines(line_mask)
sweep.compute()
row_violations = sweep.get_violations() # one list of LimitViolation per row
n_violations = sweep.get_violations_n() # violations of the shared pre-batch base case
sa_res = sweep.run() # PreContingencyResult / ContingencyResult / SecurityAnalysisResult,
# reusing lightsim2grid.contingencyAnalysis's dataclasses --
# contingency_name is always None here (no such concept on ScenarioSweep)
Warning
Just like on ContingencyAnalysis, setting compute_limit_violations through the property clears the whole object – registered injections, contingency masks, handle_disconnected_grid, everything, the same reset clear() performs. Set it first, before calling any modify_* / set_contingency_* setter or handle_disconnected_grid, or those calls will be silently wiped.
Unlike ContingencyAnalysis, ScenarioSweep has no (grid, compute_limit_violations) constructor shortcut to dodge this ordering – the property is the only way to set it.
Note
There is deliberately no converged / converged_n on ScenarioSweep (unlike ContingencyAnalysis). A non-converged row’s get_violations() entry already carries a single LimitViolation with element_type == ViolationElementType.GRID and violation_type either NOT_SIMULATED (skipped before the solver ran, eg it splits the grid and handle_disconnected_grid is off) or DIVERGENCE (the solver ran but did not converge) – a separate convergence flag would be redundant. If the shared pre-batch “n” powerflow itself diverges, that same sentinel is stamped into get_violations_n() and into every row of get_violations(), rather than leaving them as empty lists indistinguishable from “converged, nothing found”.
A future release may add a “combine mode” axis (this row-aligned / zipped behavior vs. a cartesian “every contingency x every injection profile”) – not yet available.
Detailed usage
Classes:
|
Limit violations for a single simulated contingency. |
A single limit violation, as detected by |
|
The kind of limit that was violated: |
|
|
Limit violations for the pre-contingency ("n", no disconnection) case. |
|
Batch powerflow that varies both the injection and a contingency (line / trafo disconnection) per simulation, independently, row-aligned: row i of every |
Batch powerflow varying both the injection AND a contingency per simulation, row-aligned: row i of every modify_* input is solved together with row i of set_contingency_lines / set_contingency_trafos. |
|
Result of ContingencyAnalysis.run / run_ac / run_dc, modeled after pypowsybl's security analysis result. |
|
The kind of element on which a limit was violated: |
- class lightsim2grid.scenarioSweep.ContingencyResult(element_ids: List[int], element_names: List[str], contingency_name: str | None, converged: bool, limit_violations: List[LimitViolation])[source]
Limit violations for a single simulated contingency.
Note
If
convergedis False,limit_violationscontains exactly oneLimitViolationwithelement_type == ViolationElementType.GRIDandviolation_typeeitherLimitViolationType.NOT_SIMULATED(a pre-check skipped this contingency, eg it splits the grid, without ever invoking the solver) orLimitViolationType.DIVERGENCE(the solver ran but did not converge).Attributes:
user-supplied name, see add_single_contingency
branch ids (lines then trafos) disconnected by this contingency
names (env.name_line) of the elements disconnected by this contingency
- class lightsim2grid.scenarioSweep.LimitViolation
A single limit violation, as detected by
ContingencyAnalysisCPP.See also
get_violations()/get_violations_n(), which return a list of these per contingency.Attributes:
Which element this violation is about -- the grid-model bus id for
BUS; the local (0-based, own-type) line / transformer id forLINE/TRAFO; unused (-1) forGRID.The kind of element this violation is about, as a
ViolationElementType.The limit that was violated; unused (
NaN) forNOT_SIMULATED/DIVERGENCE.The violating element's name -- for
LINE/TRAFO, as set bylightsim2grid.network.LSGrid.set_line_names()/lightsim2grid.network.LSGrid.set_trafo_names(); forBUS, the name of the substation the violating bus belongs to (seelightsim2grid.network.LSGrid.set_substation_names()-- there is no per-bus name inLSGrid, only per-substation ones).1or2for aLINE/TRAFOviolation (which side's current limit was violated); unused (0) forBUS/GRID.The value actually reached (the voltage magnitude or the current, matching
violation_type); unused (NaN) forNOT_SIMULATED/DIVERGENCE.The kind of limit that was violated, as a
LimitViolationType.- property element_id
Which element this violation is about – the grid-model bus id for
BUS; the local (0-based, own-type) line / transformer id forLINE/TRAFO; unused (-1) forGRID.
- property element_type
The kind of element this violation is about, as a
ViolationElementType.
- property limit
The limit that was violated; unused (
NaN) forNOT_SIMULATED/DIVERGENCE.
- property name
The violating element’s name – for
LINE/TRAFO, as set bylightsim2grid.network.LSGrid.set_line_names()/lightsim2grid.network.LSGrid.set_trafo_names(); forBUS, the name of the substation the violating bus belongs to (seelightsim2grid.network.LSGrid.set_substation_names()– there is no per-bus name inLSGrid, only per-substation ones). Empty string if names were never set on the grid for the relevant kind, or forGRID.
- property side
1or2for aLINE/TRAFOviolation (which side’s current limit was violated); unused (0) forBUS/GRID.
- property value
The value actually reached (the voltage magnitude or the current, matching
violation_type); unused (NaN) forNOT_SIMULATED/DIVERGENCE.
- property violation_type
The kind of limit that was violated, as a
LimitViolationType.
- class lightsim2grid.scenarioSweep.LimitViolationType
The kind of limit that was violated:
LOW_VOLTAGE/HIGH_VOLTAGE(a bus voltage magnitude limit) orCURRENT(a line / transformer thermal limit) for an ordinary, element-level violation;NOT_SIMULATEDorDIVERGENCEfor a contingency-level one (seeViolationElementType’sGRID):NOT_SIMULATED: a pre-check (eg graph connectivity) skipped this contingency – the solver was never invoked for it.DIVERGENCE: the solver was invoked for this contingency but did not converge.
Members:
LOW_VOLTAGE
HIGH_VOLTAGE
CURRENT
NOT_SIMULATED : A pre-check (graph connectivity) skipped this contingency: the solver was never invoked (element_type is ViolationElementType.GRID).
DIVERGENCE : The solver was invoked for this contingency but did not converge (element_type is ViolationElementType.GRID).
Attributes:
- property name
- class lightsim2grid.scenarioSweep.PreContingencyResult(converged: bool, limit_violations: List[LimitViolation])[source]
Limit violations for the pre-contingency (“n”, no disconnection) case.
Note
convergedis always True here: ContingencyAnalysisCPP.compute raises aRuntimeErrorif the pre-contingency powerflow itself does not converge (every contingency is solved relative to this base case, so a diverging base case makes the whole analysis meaningless).
- class lightsim2grid.scenarioSweep.ScenarioSweep(grid2op_env)[source]
Batch powerflow that varies both the injection and a contingency (line / trafo disconnection) per simulation, independently, row-aligned: row i of every
modify_*input is solved together with row i ofset_contingency_lines/set_contingency_trafos.Unlike
lightsim2grid.timeSerie.TimeSerie/lightsim2grid.injectionSweep.InjectionSweep(a single bundledcompute_V_from_injcall), this class uses a setter-based API: call as many ofmodify_gen_p/modify_sgen_p/modify_load_p/modify_load_q/modify_gen_v/set_contingency_lines/set_contingency_trafosas are relevant, thencompute(). Any axis you never set defaults to the grid’s own state for every row (its own target injection / voltage setpoint, or “nothing disconnected”). The first setter you call fixes the number of simulations; every later setter is checked against it immediately. Notemodify_gen_vis different from the othermodify_*setters: it does not feed the injection (Sbus), it only re-seeds the voltage magnitude (in pu, NOT kV) at each voltage-regulating generator’s regulated bus before that row’s solve.This is deliberately a different API from
lightsim2grid.contingencyAnalysis.ContingencyAnalysis’sadd_n1/add_nk(a set of distinct scenarios applied to one shared base case): here each row pairs its own injection with its own contingency, so a dense, row-aligned mask is the natural shape – the two APIs are not interchangeable.Examples
import numpy as np import grid2op from lightsim2grid import ScenarioSweep from lightsim2grid import LightSimBackend env_name = ... env = grid2op.make(env_name, backend=LightSimBackend()) sweep = ScenarioSweep(env) sweep.modify_load_p(load_p) # shape (n_simul, n_load) line_mask = np.zeros((load_p.shape[0], env.n_line), dtype=bool) line_mask[:, 3] = True # disconnect line 3 for every simulation sweep.set_contingency_lines(line_mask) sweep.compute() Vs = sweep.get_voltages() Ps, amps = sweep.compute_P(), sweep.compute_A()
Methods:
clear()Clear everything, as if nothing had ever been set / computed.
close()permanently close the object
compute([v_init, max_iter, tol, ignore_errors])Run the batch: one powerflow per simulation, using whatever was set by the
modify_*/set_contingency_*setters.Current flows (in Amps, A) at the origin (for powerline) / high voltage (for transformer) side, per simulation.
Active power flows (in MW) at the origin (for powerline) / high voltage (for transformer) side, per simulation.
get_flows([v_init, max_iter, tol, ignore_errors])Run
compute()(using whatever was set by the setters) then retrieve the active power / current / voltage results for every simulation, in one call.Per row (same order as every
modify_*/set_contingency_*input): list ofLimitViolation.List of
LimitViolationfor the pre-batch ("n") case (no injection change, no contingency) shared by every row.Complex voltage, at each bus, for each simulation.
modify_gen_p(gen_p)Per-step active generator setpoints, shape
(n_simul, n_gen).modify_gen_v(gen_v)Per-step generator target voltage magnitude, shape
(n_simul, n_gen), in pu (vm_pu), NOT kV.modify_load_p(load_p)Per-step active load setpoints, shape
(n_simul, n_load).modify_load_q(load_q)Per-step reactive load setpoints, shape
(n_simul, n_load).modify_sgen_p(sgen_p)Per-step active static generator setpoints, shape
(n_simul, n_sgen).run()Run this batch (calling
compute()if not already done) and report, for the pre-batch ("n") case and for each row, the list of limit violations -- same return type aslightsim2grid.contingencyAnalysis.ContingencyAnalysis.run(), reusing the very samePreContingencyResult/ContingencyResult/SecurityAnalysisResultdataclasses so callers can handle either result the same way.set_contingency_lines(mask)Per-step powerline contingency mask, shape
(n_simul, n_line), dtype bool.set_contingency_trafos(mask)Per-step trafo contingency mask, shape
(n_simul, n_trafo), dtype bool.Attributes:
Whether limit violations are computed inline, per row, during
compute()/run()(seeget_violations()/get_violations_n()/run()-- there is noconverged/converged_nhere, unlikelightsim2grid.contingencyAnalysis.ContingencyAnalysis: a non-converged row's violations already carry aViolationElementType.GRID-typed sentinel that fully encodes that row's status by itself).Whether a row whose contingency splits the grid into several connected components is simulated on its largest component instead of being skipped.
Whether to initialize the complex voltages of each simulation with the results of a "n" powerflow (a powerflow with no injection change and no contingency) instead of the vector given to
compute.1).Threshold (a
floatin]0., 1.], default1.0) applied to every limit-violation check performed whencompute_limit_violationsisTrue.- compute(v_init=None, max_iter=None, tol=None, ignore_errors=False)[source]
Run the batch: one powerflow per simulation, using whatever was set by the
modify_*/set_contingency_*setters. Raises if nothing was ever set.max_iter/toldefault to the backend’s own values (self.grid2op_env.backend.max_it/.tol) when not given.
- compute_A()[source]
Current flows (in Amps, A) at the origin (for powerline) / high voltage (for transformer) side, per simulation. Does not recompute the voltages; you must call
compute()first.
- compute_P()[source]
Active power flows (in MW) at the origin (for powerline) / high voltage (for transformer) side, per simulation. Does not recompute the voltages; you must call
compute()first.
- property compute_limit_violations
Whether limit violations are computed inline, per row, during
compute()/run()(seeget_violations()/get_violations_n()/run()– there is noconverged/converged_nhere, unlikelightsim2grid.contingencyAnalysis.ContingencyAnalysis: a non-converged row’s violations already carry aViolationElementType.GRID-typed sentinel that fully encodes that row’s status by itself). Default:False. Computing violations means an extra per-element current / voltage check in every row’s solve, so leave this off if you only needcompute_flows()/get_flows(). Changing this flag clears any previously-computed results.
- get_flows(v_init=None, max_iter=None, tol=None, ignore_errors=False)[source]
Run
compute()(using whatever was set by the setters) then retrieve the active power / current / voltage results for every simulation, in one call.Each row of the resulting flow matrix corresponds to a simulation.
- get_violations()[source]
Per row (same order as every
modify_*/set_contingency_*input): list ofLimitViolation. Requirescompute_limit_violationsto beTrue(raises otherwise). Preferrun()for a structured result – this is the raw passthrough. Seelightsim2grid.contingencyAnalysis.ContingencyAnalysis.compute_limit_violations’s note on the sentinel entry a non-converged row carries (there is no separateconvergedhere, by design – seecompute_limit_violations).
- get_violations_n()[source]
List of
LimitViolationfor the pre-batch (“n”) case (no injection change, no contingency) shared by every row. Requirescompute_limit_violationsto beTrue(raises otherwise).
- get_voltages()[source]
Complex voltage, at each bus, for each simulation. Must be called after
compute().
- property handle_disconnected_grid
Whether a row whose contingency splits the grid into several connected components is simulated on its largest component instead of being skipped. Default:
False, meaning such a row is not simulated at all (its voltages are left at 0.). WhenTrue, the buses of the other component(s) are “masked” (their voltage reported as 0.) and the largest component is solved normally, without triggering any extra matrix re-factorization. Supported by the Newton-Raphson family (AC) and by the DC solver; a non Newton-Raphson AC algorithm (eg Gauss-Seidel or Fast-Decoupled) is rejected. Same name/semantics aslightsim2grid.contingencyAnalysis.ContingencyAnalysis.handle_disconnected_grid.
- property init_from_n_powerflow
Whether to initialize the complex voltages of each simulation with the results of a “n” powerflow (a powerflow with no injection change and no contingency) instead of the vector given to
compute. Default:False. Must be set beforecompute()actually runs.
- modify_gen_v(gen_v)[source]
Per-step generator target voltage magnitude, shape
(n_simul, n_gen), in pu (vm_pu), NOT kV. Unlikemodify_gen_p/modify_load_p/modify_load_q, this does NOT feed the injection (Sbus) – it only re-seeds|V|at each voltage-regulating generator’s regulated bus before that step’s solve.
- property nb_thread
1).The steps are split into contiguous ranges, each solved by its own thread with its own solver (and its own admittance-matrix copy, since a contingency edits it), writing to disjoint rows of the result matrix: the results do not depend on the number of threads. Values
< 1are clamped to1.Must be set before
compute()actually runs; it has no effect on a batch that has already been computed.- Type:
Number of OS threads used to compute the steps (default
- run() SecurityAnalysisResult[source]
Run this batch (calling
compute()if not already done) and report, for the pre-batch (“n”) case and for each row, the list of limit violations – same return type aslightsim2grid.contingencyAnalysis.ContingencyAnalysis.run(), reusing the very samePreContingencyResult/ContingencyResult/SecurityAnalysisResultdataclasses so callers can handle either result the same way.Requires
compute_limit_violationsto beTrue(set viathis_instance.compute_limit_violations = True– there is no constructor argument for it, unlikelightsim2grid.contingencyAnalysis.ContingencyAnalysis), else aRuntimeErroris raised.Unlike
lightsim2grid.contingencyAnalysis.ContingencyAnalysis.run(), rows are already in caller-set order here (no dedup / reordering concept, sinceScenarioSweeprows are independent scenarios, not a set of contingencies) – and everyContingencyResult.contingency_nameisNone(no such concept onScenarioSweep);element_ids/element_namesare instead derived from that row’s ownset_contingency_lines/set_contingency_trafosmask.
- set_contingency_lines(mask)[source]
Per-step powerline contingency mask, shape
(n_simul, n_line), dtype bool.Truemeans “deactivate this powerline for this simulation”.
- set_contingency_trafos(mask)[source]
Per-step trafo contingency mask, shape
(n_simul, n_trafo), dtype bool. Seeset_contingency_lines().
- property violation_threshold
Threshold (a
floatin]0., 1.], default1.0) applied to every limit-violation check performed whencompute_limit_violationsisTrue. Same meaning aslightsim2grid.contingencyAnalysis.ContingencyAnalysis.violation_threshold– see that docstring for the full formulas. Lowering it makes every check stricter (more violations reported, never fewer) and invalidates any already-computed results; raising it back up does not.
- class lightsim2grid.scenarioSweep.ScenarioSweepCPP
Batch powerflow varying both the injection AND a contingency per simulation, row-aligned: row i of every modify_* input is solved together with row i of set_contingency_lines / set_contingency_trafos. Build up the batch with modify_gen_p / modify_sgen_p / modify_load_p / modify_load_q and set_contingency_lines / set_contingency_trafos (any axis never set defaults to the grid’s own state for every row), then call compute(). Unlike ContingencyAnalysisCPP’s add_n1/add_nk (a set of distinct scenarios applied to one shared base case), set_contingency_lines/trafos are dense boolean masks of shape (n_simul, n_lines) / (n_simul, n_trafos) – True means ‘deactivate this branch for this simulation’; the two APIs are deliberately not unified, they serve different usages.
Methods:
amps_computation_time(self)Time spent in computing the flows (in amps) after the voltages have been computed at each nodes
Returns the names of all registered algorithms, including any loaded plugins, as a list of string.
Return the list of the names of the algorithm available on the current lightsim2grid installation.
change_algorithm(*args, **kwargs)Overloaded function.
change_solver(*args, **kwargs)Overloaded function.
clear(self)Clear the solver and to as if the class never performed any powerflow.
close(self)Clear the solver and to as if the class never performed any powerflow.
compute(self, Vinit, max_iter, tol)Run the batch: one powerflow per simulation, using whatever was set by modify_* (and, on ScenarioSweep, set_contingency_lines / set_contingency_trafos).
compute_flows(self)Retrieve the flows (in amps, at the origin of each powerlines / high voltage size of each transformers.
compute_power_flows(self)Retrieve the active flows (in MW, at the origin of each powerlines / high voltage size of each transformers.
converged_mask(self)Per-row convergence, as a list[bool] of length nb_steps() (row order matches the row order of whatever was solved: my_defaults() for ContingencyAnalysis, the injection / contingency matrices otherwise).
get_algo_config(self)Config (eg ScalingPolicyType / damping parameters) of the internal solver used for every step.
get_algo_name(self)Registry name of the currently selected algorithm.
get_algo_type(self)Return the type of the solver currently used.
get_flows(self)Get the current flows (in kA) at the origin side / high voltage side of each transformers / powerlines.
get_power_flows(self)Get the active flows (in MW) at the origin side / high voltage side of each transformers / powerlines.
get_status(self)Status of the solvers (1: success, 0: failure).
get_violations(self)Per row (same order as every modify_* / set_contingency_* input): list of LimitViolation.
get_violations_n(self)List of LimitViolation for the pre-batch ("n") case (no injection change, no contingency) shared by every row.
get_voltages(self)Get the complex voltage angles at each bus of the powergrid.
modify_gen_p(self, gen_p)Per-step active generator setpoints, shape (n_simul, n_gen).
modify_gen_v(self, gen_v)Per-step generator target voltage magnitude, shape (n_simul, n_gen), in pu (vm_pu), NOT kV.
modify_load_p(self, load_p)Per-step active load setpoints, shape (n_simul, n_load).
modify_load_q(self, load_q)Per-step reactive load setpoints, shape (n_simul, n_load).
modify_sgen_p(self, sgen_p)Per-step active static generator setpoints, shape (n_simul, n_sgen).
nb_converged(self)Number of powerflows, among those nb_solved() attempted, that actually converged.
nb_solved(self)Total number of powerflows solved.
preprocessing_time(self)Time spent in pre processing the data (this involves, but is not limited to the computation of the Sbus)
set_algo_config(self, config)See get_algo_config().
set_contingency_lines(self, mask)Per-step powerline contingency mask, shape (n_simul, n_line), dtype bool.
set_contingency_trafos(self, mask)Per-step trafo contingency mask, shape (n_simul, n_trafo), dtype bool.
solver_time(self)Total time spent only in solving the powerflows (excluding pre processing the data, post processing them, initializing everything etc.)
thread_init_time(self)Time (in seconds,
float) spent building the per thread solvers.total_time(self)Total time spent in solving the powerflows, pre processing the data, post processing them, initializing everything etc.
Attributes:
a non-converged row's get_violations() entry already carries a GRID-type NOT_SIMULATED / DIVERGENCE LimitViolation, which fully encodes that row's status by itself.
Whether to simulate a row whose contingency splits the grid into multiple connected components.
false, meaning each simulation is initialized with the given input vector.
Number of OS threads used to compute the steps (default
1).Threshold (a
floatin]0., 1.], default1.0) applied to every limit check performed when compute_limit_violations isTrue.- amps_computation_time(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) float
Time spent in computing the flows (in amps) after the voltages have been computed at each nodes
It is given in seconds (
float).
- available_algorithm_names(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) list[str]
Returns the names of all registered algorithms, including any loaded plugins, as a list of string.
- available_default_algorithms(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) list[lightsim2grid.lightsim2grid_cpp.AlgorithmType]
Return the list of the names of the algorithm available on the current lightsim2grid installation.
This is a list of
lightsim2grid.algorithm.AlgorithmType.
- change_algorithm(*args, **kwargs)
Overloaded function.
change_algorithm(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, arg0: lightsim2grid.lightsim2grid_cpp.AlgorithmType) -> None
This function allows to control which solver is used during the powerflow. See the section Available powerflow algorithms for more information about them.
See also
lightsim2grid.algorithm.AlgorithmTypefor a list of the available algorithms (NB: some algorithms might not be available on all platform)Note
If the algorithm type entered is a DC algorithm (eg from
lightsim2grid.algorithm.AlgorithmType, DC_SparseLU, DC_KLU or DC_NICSLU), it will change the _dc_solver otherwise the regular _solver is modified.Examples
from lightsim2grid.algorithm import AlgorithmType # init the grid model from lightsim2grid.network import init_from_pandapower pp_net = ... # any pandapower grid lightsim_grid_model = init_from_pandapower(pp_net) # some warnings might be issued as well as some warnings # change the algorithm used for the powerflow # to use internally a Newton Raphson algorithm with the Eigen sparse LU linear solver lightsim_grid_model.change_algorithm(AlgorithmType.NR_SparseLU)
change_algorithm(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, arg0: str) -> None
Change the AC (or DC) algorithm by registry name. Accepts built-in names and plugin names registered via
load_solver_plugin().See also
change_algorithm()to change it bylightsim2grid.algorithm.AlgorithmTypeinstead.
- change_solver(*args, **kwargs)
Overloaded function.
change_solver(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, arg0: lightsim2grid.lightsim2grid_cpp.AlgorithmType) -> None
DEPRECATED: use ‘change_algorithm’ instead
change_solver(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, arg0: str) -> None
DEPRECATED: use ‘change_algorithm’ instead
- clear(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) None
Clear the solver and to as if the class never performed any powerflow.
- close(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) None
Clear the solver and to as if the class never performed any powerflow.
- compute(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, Vinit: Annotated[numpy.typing.NDArray[numpy.complex128], '[m, 1]'], max_iter: SupportsInt | SupportsIndex, tol: SupportsFloat | SupportsIndex) None
Run the batch: one powerflow per simulation, using whatever was set by modify_* (and, on ScenarioSweep, set_contingency_lines / set_contingency_trafos). Raises if nothing was ever set.
- compute_flows(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]', 'flags.writeable', 'flags.c_contiguous']
Retrieve the flows (in amps, at the origin of each powerlines / high voltage size of each transformers.
Warning
This function must be called after
lightsim2grid.timeSerie.TimeSeriesCPP.compute_Vs()has been called.Note
This function must be called before
lightsim2grid.timeSerie.TimeSeriesCPP.get_flows()Note
During this computation, the GIL is released, allowing easier parrallel computation
- property compute_limit_violations
a non-converged row’s get_violations() entry already carries a GRID-type NOT_SIMULATED / DIVERGENCE LimitViolation, which fully encodes that row’s status by itself.
- Type:
Whether limit violations are computed inline, per row, during compute() (see get_violations() / get_violations_n()). Defaults to
False. Computing violations means an extra per-element current / voltage check in every row’s solve, so users who only need compute_flows() / get_flows() should leave this off. Changing this flag clears any previously-computed results. Unlike ContingencyAnalysisCPP, there is no converged() / converged_n() here
- compute_power_flows(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]', 'flags.writeable', 'flags.c_contiguous']
Retrieve the active flows (in MW, at the origin of each powerlines / high voltage size of each transformers.
Warning
This function must be called after
lightsim2grid.timeSerie.TimeSeriesCPP.compute_Vs()has been called.Note
This function must be called before
lightsim2grid.timeSerie.TimeSeriesCPP.get_flows()Note
During this computation, the GIL is released, allowing easier parrallel computation
- converged_mask(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) list[bool]
Per-row convergence, as a list[bool] of length nb_steps() (row order matches the row order of whatever was solved: my_defaults() for ContingencyAnalysis, the injection / contingency matrices otherwise). Unlike converged() (ContingencyAnalysis-only, and only usable if compute_limit_violations=True was set), this is available on every batch class – TimeSeries, InjectionSweep, ContingencyAnalysis and ScenarioSweep alike – unconditionally, no setup required.
Row i is True iff that row was both invertible and reported convergence by the solver. A row that was never attempted at all (eg every row after the first failure in a TimeSeries, whose steps are chained and so stop there) is False too – indistinguishable from an outright divergence, the same “never attempted” == “did not converge” convention nb_solved() / nb_converged() already use.
- get_algo_config(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) lightsim2grid.lightsim2grid_cpp.AlgoConfig
Config (eg ScalingPolicyType / damping parameters) of the internal solver used for every step. Copied once from the grid model’s own get_ac_algo_config() at construction time, then independent of it; re-apply with set_algo_config() if you change the grid model’s config afterwards, or after change_algorithm().
- get_algo_name(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) str
Registry name of the currently selected algorithm. Unlike get_algo_type(), stays meaningful for plugin solvers (and built-ins with no dedicated AlgorithmType member).
- get_algo_type(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) lightsim2grid.lightsim2grid_cpp.AlgorithmType
Return the type of the solver currently used.
This is equivalent to the get_type of the
lightsim2grid.algorithm.AlgorithmSelector.get_type()of the solver used.
- get_flows(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]']
Get the current flows (in kA) at the origin side / high voltage side of each transformers / powerlines.
Each rows correspond to a time step, each column to a powerline / transformer
Warning
This function must be called after
lightsim2grid.timeSerie.TimeSeriesCPP.compute_flows()has been called. (compute_flows also requires thatlightsim2grid.timeSerie.TimeSeriesCPP.compute_Vs()has been caleed)- Returns:
As – The flows (in kA) at the origin side / high voltage side of each transformers / powerlines.
- Return type:
numpy.ndarry(matrix)
- get_power_flows(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]']
Get the active flows (in MW) at the origin side / high voltage side of each transformers / powerlines.
Each rows correspond to a time step, each column to a powerline / transformer
Warning
This function must be called after
lightsim2grid.timeSerie.TimeSeriesCPP.compute_power_flows()has been called. (compute_power_flows also requires thatlightsim2grid.timeSerie.TimeSeriesCPP.compute_Vs()has been caleed)- Returns:
Ps – The active flows (in MW) at the origin side / high voltage side of each transformers / powerlines.
- Return type:
numpy.ndarry(matrix)
- get_status(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) int
Status of the solvers (1: success, 0: failure).
Note
Even if the solver failed at some point, some results might still be available (but not totally).
- get_violations(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) list[list[lightsim2grid.lightsim2grid_cpp.LimitViolation]]
Per row (same order as every modify_* / set_contingency_* input): list of LimitViolation. A row that did not converge has exactly one LimitViolation here, with element_type ViolationElementType.GRID and violation_type either LimitViolationType.NOT_SIMULATED (a pre-check skipped it, eg it splits the grid with handle_disconnected_grid off) or LimitViolationType.DIVERGENCE (the solver ran but did not converge, including a diverging pre-batch “n” powerflow, which stamps every row this way). Requires compute_limit_violations=True.
- get_violations_n(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) list[lightsim2grid.lightsim2grid_cpp.LimitViolation]
List of LimitViolation for the pre-batch (“n”) case (no injection change, no contingency) shared by every row. Requires compute_limit_violations=True.
- get_voltages(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) Annotated[numpy.typing.NDArray[numpy.complex128], '[m, n]']
Get the complex voltage angles at each bus of the powergrid.
Each rows correspond to a time step, each column to a bus.
Warning
This function must be called after
lightsim2grid.timeSerie.TimeSeriesCPP.compute_Vs().- Returns:
Vs – The complex voltage angles at each bus of the powergrid.
- Return type:
numpy.ndarry(matrix)
- property handle_disconnected_grid
Whether to simulate a row whose contingency splits the grid into multiple connected components. When False (default) such a row is skipped (its voltages are left at 0), reproducing the legacy behaviour. When True, the largest connected component is solved while the buses of the other component(s) are masked (their voltage is reported as 0). Supported by the Newton-Raphson family (AC) and the DC solver; a non Newton-Raphson AC algorithm is rejected.
- property init_from_n_powerflow
false, meaning each simulation is initialized with the given input vector.
- Type:
Whether to initialize the complex voltages of each simulation with the results of a n-powerflow (ie a powerflow with no injection change and no contingency) or not. Default
- modify_gen_p(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, gen_p: Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]', 'flags.c_contiguous']) None
Per-step active generator setpoints, shape (n_simul, n_gen). See the class docstring: locks / checks the number of simulations against any other modify_* / set_contingency_* call already made on this object.
- modify_gen_v(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, gen_v: Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]', 'flags.c_contiguous']) None
Per-step generator target voltage magnitude, shape (n_simul, n_gen), in pu (vm_pu), NOT kV. Unlike modify_gen_p/modify_sgen_p/modify_load_p/modify_load_q, this does NOT feed the injection (Sbus) – it only re-seeds |V| at each voltage-regulating generator’s regulated bus before that step’s solve. See modify_gen_p() for the shared row-count-lock behavior.
- modify_load_p(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, load_p: Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]', 'flags.c_contiguous']) None
Per-step active load setpoints, shape (n_simul, n_load). See modify_gen_p().
- modify_load_q(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, load_q: Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]', 'flags.c_contiguous']) None
Per-step reactive load setpoints, shape (n_simul, n_load). See modify_gen_p().
- modify_sgen_p(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, sgen_p: Annotated[numpy.typing.NDArray[numpy.float64], '[m, n]', 'flags.c_contiguous']) None
Per-step active static generator setpoints, shape (n_simul, n_sgen). See modify_gen_p().
- nb_converged(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) int
Number of powerflows, among those nb_solved() attempted, that actually converged. Always <= nb_solved(): a row skipped outright (eg a non-invertible / islanding admittance matrix, on ContingencyAnalysis / ScenarioSweep) never reaches the solver at all, so it counts towards neither.
- nb_solved(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) int
Total number of powerflows solved.
- property nb_thread
Number of OS threads used to compute the steps (default
1).With
nb_thread == 1the behaviour is the legacy sequential one. Withnb_thread > 1the steps are split into contiguous ranges, each solved by its own thread (each with its own solver), writing to disjoint rows of the result matrix: the results do NOT depend on the number of threads. Values< 1are clamped to1.Warning
This is only available on
lightsim2grid.injectionSweep.InjectionSweepCPP(and onlightsim2grid.contingencyAnalysis.ContingencyAnalysisCPP), whose computations are independent of one another.On
lightsim2grid.timeSerie.TimeSeriesCPPeach step is initialized with the result of the previous one, so the steps cannot be split over threads without making the results depend on how they were split: setting it to anything but 1 raises aRuntimeError. UseInjectionSweepCPPif you want the very same injections computed in parallel.
- preprocessing_time(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) float
Time spent in pre processing the data (this involves, but is not limited to the computation of the Sbus)
It is given in seconds (
float).
- set_algo_config(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, config: lightsim2grid.lightsim2grid_cpp.AlgoConfig) None
See get_algo_config().
- set_contingency_lines(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, mask: Annotated[numpy.typing.NDArray[numpy.bool], '[m, n]', 'flags.c_contiguous']) None
Per-step powerline contingency mask, shape (n_simul, n_line), dtype bool. True means ‘deactivate this powerline for this simulation’. See the class docstring: locks / checks the number of simulations, and is a different API from ContingencyAnalysisCPP’s add_n1/add_nk on purpose.
- set_contingency_trafos(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, mask: Annotated[numpy.typing.NDArray[numpy.bool], '[m, n]', 'flags.c_contiguous']) None
Per-step trafo contingency mask, shape (n_simul, n_trafo), dtype bool. See set_contingency_lines().
- solver_time(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) float
Total time spent only in solving the powerflows (excluding pre processing the data, post processing them, initializing everything etc.)
It is given in seconds (
float).
- thread_init_time(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) float
Time (in seconds,
float) spent building the per thread solvers. It is 0. whennb_threadis 1, in which case the (already initialized) internal solver is reused.
- total_time(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP) float
Total time spent in solving the powerflows, pre processing the data, post processing them, initializing everything etc.
It is given in seconds (
float).
- property violation_threshold
Threshold (a
floatin]0., 1.], default1.0) applied to every limit check performed when compute_limit_violations isTrue. It is the fraction of the usable range that is still considered acceptable, so lowering it makes every check stricter (more violations are reported, never fewer).Each of the three checks owns one interval, running from a “healthy” anchor to the limit that can be violated, and the threshold simply moves that limit towards its anchor – a linear interpolation, identical for all three:
effective_limit = threshold * limit + (1 - threshold) * anchor
check
anchor
limit
violates when
CURRENT
0
limit_a
value >= threshold * limit_aLOW_VOLTAGE
vn_kv
vmin_kv
v <= threshold * vmin + (1 - threshold) * vnHIGH_VOLTAGE
vn_kv
vmax_kv
v >= threshold * vmax + (1 - threshold) * vnA line’s usable range really is
[0, limit_a], so its anchor is0and the rule reduces to scaling the limit. A voltage bound has no such natural “zero” end, so the bus NOMINAL voltage is used instead – it is what operating limits are conventionally expressed around (+/- x% of vn). Either way each acceptable interval keeps a width of exactlythresholdtimes its original one.The voltage anchor is the nominal voltage clamped into
[vmin_kv, vmax_kv]. A band is not guaranteed to bracket it: real data does violate this (a 380 kV level declared with an operating range of[390, 450]kV is ordinary on the European 400 kV network), and an anchor outside the band would push a bound the wrong way. Clamping keeps each bound moving inwards only. Where the band does bracket the nominal voltage – the overwhelmingly common case – the clamp does nothing and the anchor is exactlyvn_kv.Anchoring both voltage checks on
vn_kv– rather than on each other – is what keeps them independent: theLOW_VOLTAGEverdict is a function ofvmin_kv,vn_kvand the threshold alone, so configuringvmax_kv(or leaving it atNaN) can never change it, and vice versa. It also means the two effective bounds each converge towardsvn_kvfrom their own side and can never cross, so no bus is ever reported as both too low and too high, whatever the threshold.A bus whose limits are inconsistent (
vmin_kv > vmax_kv– the only way the two effective bounds can still end up crossed) raises aRuntimeErrorrather than being reported as an arbitrary one of the two violation types.The reported value and limit of each violation are unaffected by the threshold: they remain the value actually reached and the limit exactly as configured. Only the test deciding whether to report at all is shifted.
The default
1.0reproduces the previous, threshold-less behaviour exactly (modulo the strict>/<comparisons becoming>=/<=, which only differ when a value lands exactly on its limit – negligible in floating point).Like nb_thread / handle_disconnected_grid, this is a plain runtime knob: it only affects the next
lightsim2grid.contingencyAnalysis.ContingencyAnalysisCPP.compute(). Lowering it invalidates any already-computed results (get_violations, get_violations_n, converged, converged_n), exactly aslightsim2grid.contingencyAnalysis.ContingencyAnalysisCPP.clear_results_only()would – the registered contingencies are kept, so it is enough to call compute again. Raising it back up does not clear anything.
- class lightsim2grid.scenarioSweep.SecurityAnalysisResult(pre_contingency_result: PreContingencyResult, post_contingency_results: List[ContingencyResult])[source]
Result of ContingencyAnalysis.run / run_ac / run_dc, modeled after pypowsybl’s security analysis result.
- class lightsim2grid.scenarioSweep.ViolationElementType
The kind of element on which a limit was violated:
BUS(a voltage limit),LINE/TRAFO(a current limit), orGRID– the whole grid / contingency rather than a specific element, used when the contingency itself could not be simulated at all (seeLimitViolationType’sNOT_SIMULATED/DIVERGENCE).Members:
BUS
LINE
TRAFO
GRID : The whole grid / contingency, not a specific element (see LimitViolationType.NOT_SIMULATED / LimitViolationType.DIVERGENCE).
Attributes:
- property name