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:

ContingencyResult(element_ids, ...)

Limit violations for a single simulated contingency.

LimitViolation

A single limit violation, as detected by ContingencyAnalysisCPP.

LimitViolationType

The kind of limit that was violated: LOW_VOLTAGE / HIGH_VOLTAGE (a bus voltage

PreContingencyResult(converged, limit_violations)

Limit violations for the pre-contingency ("n", no disconnection) case.

ScenarioSweep(grid2op_env)

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 of set_contingency_lines / set_contingency_trafos.

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.

SecurityAnalysisResult(...)

Result of ContingencyAnalysis.run / run_ac / run_dc, modeled after pypowsybl's security analysis result.

ViolationElementType

The kind of element on which a limit was violated: BUS (a voltage limit), LINE /

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 converged is False, limit_violations contains exactly one LimitViolation with element_type == ViolationElementType.GRID and violation_type either LimitViolationType.NOT_SIMULATED (a pre-check skipped this contingency, eg it splits the grid, without ever invoking the solver) or LimitViolationType.DIVERGENCE (the solver ran but did not converge).

Attributes:

contingency_name

user-supplied name, see add_single_contingency

element_ids

branch ids (lines then trafos) disconnected by this contingency

element_names

names (env.name_line) of the elements disconnected by this contingency

contingency_name: str | None

user-supplied name, see add_single_contingency

element_ids: List[int]

branch ids (lines then trafos) disconnected by this contingency

element_names: List[str]

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:

element_id

Which element this violation is about -- the grid-model bus id for BUS; the local (0-based, own-type) line / transformer id for LINE / TRAFO; unused (-1) for GRID.

element_type

The kind of element this violation is about, as a ViolationElementType.

limit

The limit that was violated; unused (NaN) for NOT_SIMULATED / DIVERGENCE.

name

The violating element's name -- for LINE / TRAFO, as set by lightsim2grid.network.LSGrid.set_line_names() / lightsim2grid.network.LSGrid.set_trafo_names(); for BUS, the name of the substation the violating bus belongs to (see lightsim2grid.network.LSGrid.set_substation_names() -- there is no per-bus name in LSGrid, only per-substation ones).

side

1 or 2 for a LINE / TRAFO violation (which side's current limit was violated); unused (0) for BUS / GRID.

value

The value actually reached (the voltage magnitude or the current, matching violation_type); unused (NaN) for NOT_SIMULATED / DIVERGENCE.

violation_type

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 for LINE / TRAFO; unused (-1) for GRID.

property element_type

The kind of element this violation is about, as a ViolationElementType.

property limit

The limit that was violated; unused (NaN) for NOT_SIMULATED / DIVERGENCE.

property name

The violating element’s name – for LINE / TRAFO, as set by lightsim2grid.network.LSGrid.set_line_names() / lightsim2grid.network.LSGrid.set_trafo_names(); for BUS, the name of the substation the violating bus belongs to (see lightsim2grid.network.LSGrid.set_substation_names() – there is no per-bus name in LSGrid, only per-substation ones). Empty string if names were never set on the grid for the relevant kind, or for GRID.

property side

1 or 2 for a LINE / TRAFO violation (which side’s current limit was violated); unused (0) for BUS / GRID.

property value

The value actually reached (the voltage magnitude or the current, matching violation_type); unused (NaN) for NOT_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) or CURRENT (a line / transformer thermal limit) for an ordinary, element-level violation; NOT_SIMULATED or DIVERGENCE for a contingency-level one (see ViolationElementType’s GRID):

  • 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:

name

property name
class lightsim2grid.scenarioSweep.PreContingencyResult(converged: bool, limit_violations: List[LimitViolation])[source]

Limit violations for the pre-contingency (“n”, no disconnection) case.

Note

converged is always True here: ContingencyAnalysisCPP.compute raises a RuntimeError if 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 of set_contingency_lines / set_contingency_trafos.

Unlike lightsim2grid.timeSerie.TimeSerie / lightsim2grid.injectionSweep.InjectionSweep (a single bundled compute_V_from_inj call), this class uses a setter-based API: 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(). 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. Note modify_gen_v is different from the other modify_* 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’s add_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.

compute_A()

Current flows (in Amps, A) at the origin (for powerline) / high voltage (for transformer) side, per simulation.

compute_P()

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.

get_violations()

Per row (same order as every modify_* / set_contingency_* input): list of LimitViolation.

get_violations_n()

List of LimitViolation for the pre-batch ("n") case (no injection change, no contingency) shared by every row.

get_voltages()

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 as lightsim2grid.contingencyAnalysis.ContingencyAnalysis.run(), reusing the very same PreContingencyResult / ContingencyResult / SecurityAnalysisResult dataclasses 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:

compute_limit_violations

Whether limit violations are computed inline, per row, during compute() / run() (see get_violations() / get_violations_n() / run() -- there is no converged / converged_n here, unlike lightsim2grid.contingencyAnalysis.ContingencyAnalysis: a non-converged row's violations already carry a ViolationElementType.GRID-typed sentinel that fully encodes that row's status by itself).

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.

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.

nb_thread

1).

violation_threshold

Threshold (a float in ]0., 1.], default 1.0) applied to every limit-violation check performed when compute_limit_violations is True.

clear()[source]

Clear everything, as if nothing had ever been set / computed.

close()[source]

permanently close the object

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 / tol default 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() (see get_violations() / get_violations_n() / run() – there is no converged / converged_n here, unlike lightsim2grid.contingencyAnalysis.ContingencyAnalysis: a non-converged row’s violations already carry a ViolationElementType.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 need compute_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 of LimitViolation. Requires compute_limit_violations to be True (raises otherwise). Prefer run() for a structured result – this is the raw passthrough. See lightsim2grid.contingencyAnalysis.ContingencyAnalysis.compute_limit_violations’s note on the sentinel entry a non-converged row carries (there is no separate converged here, by design – see compute_limit_violations).

get_violations_n()[source]

List of LimitViolation for the pre-batch (“n”) case (no injection change, no contingency) shared by every row. Requires compute_limit_violations to be True (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.). When True, 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 as lightsim2grid.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 before compute() actually runs.

modify_gen_p(gen_p)[source]

Per-step active generator setpoints, shape (n_simul, n_gen).

modify_gen_v(gen_v)[source]

Per-step generator target voltage magnitude, shape (n_simul, n_gen), in pu (vm_pu), NOT kV. Unlike modify_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.

modify_load_p(load_p)[source]

Per-step active load setpoints, shape (n_simul, n_load).

modify_load_q(load_q)[source]

Per-step reactive load setpoints, shape (n_simul, n_load).

modify_sgen_p(sgen_p)[source]

Per-step active static generator setpoints, shape (n_simul, n_sgen).

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 < 1 are clamped to 1.

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 as lightsim2grid.contingencyAnalysis.ContingencyAnalysis.run(), reusing the very same PreContingencyResult / ContingencyResult / SecurityAnalysisResult dataclasses so callers can handle either result the same way.

Requires compute_limit_violations to be True (set via this_instance.compute_limit_violations = True – there is no constructor argument for it, unlike lightsim2grid.contingencyAnalysis.ContingencyAnalysis), else a RuntimeError is raised.

Unlike lightsim2grid.contingencyAnalysis.ContingencyAnalysis.run(), rows are already in caller-set order here (no dedup / reordering concept, since ScenarioSweep rows are independent scenarios, not a set of contingencies) – and every ContingencyResult.contingency_name is None (no such concept on ScenarioSweep); element_ids / element_names are instead derived from that row’s own set_contingency_lines / set_contingency_trafos mask.

set_contingency_lines(mask)[source]

Per-step powerline contingency mask, shape (n_simul, n_line), dtype bool. True means “deactivate this powerline for this simulation”.

set_contingency_trafos(mask)[source]

Per-step trafo contingency mask, shape (n_simul, n_trafo), dtype bool. See set_contingency_lines().

property violation_threshold

Threshold (a float in ]0., 1.], default 1.0) applied to every limit-violation check performed when compute_limit_violations is True. Same meaning as lightsim2grid.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

available_algorithm_names(self)

Returns the names of all registered algorithms, including any loaded plugins, as a list of string.

available_default_algorithms(self)

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:

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.

handle_disconnected_grid

Whether to simulate a row whose contingency splits the grid into multiple connected components.

init_from_n_powerflow

false, meaning each simulation is initialized with the given input vector.

nb_thread

Number of OS threads used to compute the steps (default 1).

violation_threshold

Threshold (a float in ]0., 1.], default 1.0) applied to every limit check performed when compute_limit_violations is True.

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.

  1. 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.AlgorithmType for 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)
    
  2. 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 by lightsim2grid.algorithm.AlgorithmType instead.

change_solver(*args, **kwargs)

Overloaded function.

  1. change_solver(self: lightsim2grid.lightsim2grid_cpp.ScenarioSweepCPP, arg0: lightsim2grid.lightsim2grid_cpp.AlgorithmType) -> None

DEPRECATED: use ‘change_algorithm’ instead

  1. 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 that lightsim2grid.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 that lightsim2grid.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 == 1 the behaviour is the legacy sequential one. With nb_thread > 1 the 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 < 1 are clamped to 1.

Warning

This is only available on lightsim2grid.injectionSweep.InjectionSweepCPP (and on lightsim2grid.contingencyAnalysis.ContingencyAnalysisCPP), whose computations are independent of one another.

On lightsim2grid.timeSerie.TimeSeriesCPP each 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 a RuntimeError. Use InjectionSweepCPP if 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. when nb_thread is 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 float in ]0., 1.], default 1.0) applied to every limit check performed when compute_limit_violations is True. 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_a

LOW_VOLTAGE

vn_kv

vmin_kv

v <= threshold * vmin + (1 - threshold) * vn

HIGH_VOLTAGE

vn_kv

vmax_kv

v >= threshold * vmax + (1 - threshold) * vn

A line’s usable range really is [0, limit_a], so its anchor is 0 and 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 exactly threshold times 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 exactly vn_kv.

Anchoring both voltage checks on vn_kv – rather than on each other – is what keeps them independent: the LOW_VOLTAGE verdict is a function of vmin_kv, vn_kv and the threshold alone, so configuring vmax_kv (or leaving it at NaN) can never change it, and vice versa. It also means the two effective bounds each converge towards vn_kv from 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 a RuntimeError rather 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.0 reproduces 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 as lightsim2grid.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), or GRID – the whole grid / contingency rather than a specific element, used when the contingency itself could not be simulated at all (see LimitViolationType’s NOT_SIMULATED / DIVERGENCE).

Members:

BUS

LINE

TRAFO

GRID : The whole grid / contingency, not a specific element (see LimitViolationType.NOT_SIMULATED / LimitViolationType.DIVERGENCE).

Attributes:

name

property name