LightSimBackend
This is an implementation of a grid2op Backend that uses lightsim2grid simulator coded in c++.
The integration with grid2op is rather easy. You simply need to provide the keyword argument backend=LightSimBackend() when building your environment using the grid2op.make function and you can use it transparently.
Example
See the section Use with grid2op for more information and more examples.
For standard grid2op environment, you can use it like:
import grid2op
from lightsim2grid.LightSimBackend import LightSimBackend
from grid2op.Agent import RandomAgent
# create an environment
env_name = "rte_case14_realistic" # for example, other environments might be usable
env = grid2op.make(env_name,
backend=LightSimBackend() # this is the only change you have to make!
)
# create an agent
my_agent = RandomAgent(env.action_space)
# proceed as you would any open ai gym loop
nb_episode = 10
for _ in range(nb_episde):
# you perform in this case 10 different episodes
obs = env.reset()
reward = env.reward_range[0]
done = False
while not done:
# here you loop on the time steps: at each step your agent receive an observation
# takes an action
# and the environment computes the next observation that will be used at the next step.
act = agent.act(obs, reward, done)
obs, reward, done, info = env.step(act)
# the `LightSimBackend` will be used to carry out the powerflow computation instead
# of the default grid2op `PandaPowerBackend`
Customization of the backend
Warning
Use grid2op > 1.7.1 for this feature to work properly. Otherwise some bugs (hard to detect) will occur.
You can customize the way the backend behaves in different ways:
max_iter: maximum number of iterations you allow the solver to perform. If a valid solution to the Kirchhoff Current Laws (KCL) is not found after this number of iterations, then the backend will “diverge”. Default is 10 which is a good value for medium size powergrid if you use a Newton Raphson based method (default)
tol: During its internal iterations, the underlying solver will say the Kirchhoff Current Laws (KCL) are matched if the maximum value of the difference is lower than this. Default is 1e8.
solver_type: which type of “solver” you want to use. See Available “solvers” (doc in progress) for more information. By default it uses what it considers the fastest solver available which is likely to be
lightsim2grid.solver.SolverType.KLUSolverSingleSlack
turned_off_pv : by default (set to turned_off_pv=True) all generators partipate in the voltage regulation, which is not completely realistic. When you initialize a backend with turned_off_pv=False then the generators that do not produce power (eg “p=0.”) or that are turned off are excluded from the voltage regulation.
dist_slack_non_renew: by default in most grid2op environment, the slack bus is “centralize” / “single slack”. This parameters allows to bypass this restriction and use all non renewable generators (and turned on and with > 0.) in a distributed slack bus setting. It might change the default solver_type used.
* detailed_infos_for_cascading_failures: for exhaustivity, do not modify.
* can_be_copied: for exhaustivity, do not modify.
The easiest way to customize your backend is when you create the grid2op environment, like this:
import grid2op
import lightsim2grid
from lightsim2grid import LightSimBackend
env_name = ...
env = grid2op.make(env_name,
backend=LightSimBackend(
max_iter=15,
tol=1e9,
solver_type=lightsim2grid.solver.SolverType.KLUSolverSingleSlack,
# etc.
)
)
Detailed documentation
Classes:

This is a specialization of the grid2op Backend class to use the lightsim2grid solver, coded in c++, aiming at speeding up the computations. 
 class lightsim2grid.lightSimBackend.LightSimBackend(detailed_infos_for_cascading_failures: bool = False, can_be_copied: bool = True, max_iter: int = 10, tol: float = 1e08, solver_type: SolverType  None = None, turned_off_pv: bool = True, dist_slack_non_renew: bool = False, use_static_gen: bool = False, loader_method: Literal['pandapower', 'pypowsybl'] = 'pandapower', loader_kwargs: dict  None = None, stop_if_load_disco: bool  None = True, stop_if_gen_disco: bool  None = True)[source]
This is a specialization of the grid2op Backend class to use the lightsim2grid solver, coded in c++, aiming at speeding up the computations.
Methods:
apply_action
(backendAction)Specific implementation of the method to apply an action modifying a powergrid in the pandapower format.
This method is called by the environment.
close
()INTERNAL
copy
()INTERNAL
INTERNAL
INTERNAL
INTERNAL
Return the types of solver that are used in the form a tuple with 2 elements.
 returns:
line_or_theta (
numpy.ndarray
)  For each orgin side of powerline, gives the voltage angle
INTERNAL
INTERNAL
INTERNAL
load_grid
(path[, filename])INTERNAL
INTERNAL
reset
(path[, grid_filename])INTERNAL
runpf
([is_dc])INTERNAL
set_solver_max_iter
(max_iter)Set the maximum number of iteration the solver is allowed to perform.
set_solver_type
(solver_type)Change the type of solver you want to use.
set_tol
(new_tol)Set the tolerance of the powerflow.
INTERNAL
INTERNAL
Attributes:
New in version 0.8.0.
computation time of the powerflow it takes into account everything in the gridmodel, including the mapping to the solver, building of Ybus and Sbus AND the time to solve the powerflow
 apply_action(backendAction: _BackendAction  None) None [source]
Specific implementation of the method to apply an action modifying a powergrid in the pandapower format.
 assert_grid_correct_after_powerflow() None [source]
This method is called by the environment. It ensure that the backend remains consistent even after a powerflow has be run with
Backend.runpf()
method. Returns:
None
 Raise:
grid2op.Exceptions.EnvError
and possibly all of its derived class.
 close() None [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
This is called by env.close() do not attempt to use it otherwise.
This function is called when the environment is over. After calling this function, the backend might not behave properly, and in any case should not be used before another call to
Backend.load_grid()
is performed
 copy() Self [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Note
As of grid2op 1.7.1 you it is not mandatory to implement this function when creating a backend.
If it is not available, then grid2op will automatically deactivate the forecast capability and will not use the “backend.copy()” function.
When this function is not implement, you will not be able to use (for example)
grid2op.Observation.BaseObservation.simulate()
nor thegrid2op.simulator.Simulator
for example.Performs a deep copy of the backend.
In the default implementation we explicitly called the deepcopy operator on self._grid to make the error message more explicit in case there is a problem with this part.
The implementation is equivalent to:
def copy(self): return copy.deepcopy(self)
 Returns:
An instance of Backend equal to
self
, but deep copied. Return type:
Backend
 generators_info() Tuple[ndarray, ndarray, ndarray] [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Prefer using
grid2op.Observation.BaseObservation.gen_p
,grid2op.Observation.BaseObservation.gen_q
andgrid2op.Observation.BaseObservation.gen_v
instead.Note
It is called after the solver has been ran, only in case of success (convergence).
This method is used to retrieve information about the generators (active, reactive production and voltage magnitude of the bus to which it is connected).
Note
The values returned here are the values AFTER the powerflow has been computed and not the target values.
 Returns:
prod_p
numpy.ndarray
– The active power production for each generator (in MW)prod_q
numpy.ndarray
– The reactive power production for each generator (in MVAr)prod_v
numpy.ndarray
– The voltage magnitude of the bus to which each generators is connected (in kV)
 get_line_flow() ndarray [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Prefer using
grid2op.Observation.BaseObservation.a_or
orgrid2op.Observation.BaseObservation.a_ex
for exampleReturn the current flow in each lines of the powergrid. Only one value per powerline is returned.
Note
It is called after the solver has been ran, only in case of success (convergence).
If the AC mod is used, this shall return the current flow on the end of the powerline where there is a protection. For example, if there is a protection on “origin side” of powerline “l2” then this method shall return the current flow of at the “origin side” of powerline l2.
Note that in general, there is no loss of generality in supposing all protections are set on the “origin side” of the powerline. So this method will return all origin line flows. It is also possible, for a specific application, to return the maximum current flow between both ends of a power _grid for more complex scenario.
For assumption about the order of the powerline flows return in this vector, see the help of the
Backend.get_line_status()
method. Returns:
an array with the line flows of each powerline
 Return type:
np.array, dtype:float
 get_line_status() ndarray [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Prefer using
grid2op.Observation.BaseObservation.line_status
insteadNote
It is called after the solver has been ran, only in case of success (convergence).
Return the status of each lines (connected : True / disconnected: False )
It is assume that the order of the powerline is fixed: if the status of powerline “l1” is put at the 42nd element of the return vector, then it should always be set at the 42nd element.
It is also assumed that all the other methods of the backend that allows to retrieve informations on the powerlines also respect the same convention, and consistent with one another. For example, if powerline “l1” is the 42nd second of the vector returned by
Backend.get_line_status()
then information about it’s flow will be at position 42 of the vector returned byBackend.get_line_flow()
for example. Returns:
an array with the line status of each powerline
 Return type:
np.array, dtype:bool
 get_solver_types() SolverType [source]
Return the types of solver that are used in the form a tuple with 2 elements.
The first one is the solver used for AC computation, the second one for DC computation (and also for initialization of AC computations)
You can use it to check what is used:
import grid2op import lightsim2grid from ligthsim2grid import LightSimBackend env_name = ... env = grid2op.make(env_name, backend=LightSimBackend()) print(env.backend.get_solver_types()) # >>> (<SolverType.KLUSingleSlack: 7>, <SolverType.KLUDC: 9>) [can depend on your installation of lightsim2grid] env2 = grid2op.make(env_name, backend=LightSimBackend(solver_type=lightsim2grid.solver.SolverType.SparseLU)) print(env2.backend.get_solver_types()) # >>> (<SolverType.SparseLU: 0>, <SolverType.KLUDC: 9>) [can depend on your installation of lightsim2grid]
 get_theta() Tuple[ndarray, ndarray, ndarray, ndarray] [source]
 Returns:
line_or_theta (
numpy.ndarray
) – For each orgin side of powerline, gives the voltage angleline_ex_theta (
numpy.ndarray
) – For each extremity side of powerline, gives the voltage angleload_theta (
numpy.ndarray
) – Gives the voltage angle to the bus at which each load is connectedgen_theta (
numpy.ndarray
) – Gives the voltage angle to the bus at which each generator is connectedstorage_theta (
numpy.ndarray
) – Gives the voltage angle to the bus at which each storage unit is connected
 get_topo_vect() ndarray [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Prefer using
grid2op.Observation.BaseObservation.topo_vect
Get the topology vector from the
Backend._grid
.Note
It is called after the solver has been ran, only in case of success (convergence).
The topology vector defines, for each object, on which bus it is connected. It returns 1 if the object is not connected.
It is a vector with as much elements (productions, loads and lines extremity, storage) as there are in the powergrid.
For each elements, it gives on which bus it is connected in its substation (after the solver has ran)
For example, if the first element of this vector is the load of id 1, then if res[0] = 2 it means that the load of id 1 is connected to the second bus of its substation.
You can check which object of the powerlines is represented by each component of this vector by looking at the *_pos_topo_vect (eg.
grid2op.Space.GridObjects.load_pos_topo_vect
) vectors. For each elements it gives its position in this vector.As any function of the backend, it is not advised to use it directly. You can get this information in the
grid2op.Observation.Observation.topo_vect
instead. Returns:
res – An array saying to which bus the object is connected.
 Return type:
numpy.ndarray
dtype:int
 lines_ex_info() Tuple[ndarray, ndarray, ndarray, ndarray] [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Prefer using
grid2op.Observation.BaseObservation.p_ex
,grid2op.Observation.BaseObservation.q_ex
,grid2op.Observation.BaseObservation.a_ex
and,grid2op.Observation.BaseObservation.v_ex
insteadNote
It is called after the solver has been ran, only in case of success (convergence).
It returns the information extracted from the _grid at the extremity side of each powerline.
For assumption about the order of the powerline flows return in this vector, see the help of the
Backend.get_line_status()
method. Returns:
p_ex
numpy.ndarray
– the extremity active power flowing on the lines (in MW)q_ex
numpy.ndarray
– the extremity reactive power flowing on the lines (in MVAr)v_ex
numpy.ndarray
– the voltage magnitude at the extremity of each powerlines (in kV)a_ex
numpy.ndarray
– the current flow at the extremity of each powerlines (in A)
 lines_or_info() Tuple[ndarray, ndarray, ndarray, ndarray] [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Prefer using
grid2op.Observation.BaseObservation.p_or
,grid2op.Observation.BaseObservation.q_or
,grid2op.Observation.BaseObservation.a_or
and,grid2op.Observation.BaseObservation.v_or
insteadNote
It is called after the solver has been ran, only in case of success (convergence).
It returns the information extracted from the _grid at the origin side of each powerline.
For assumption about the order of the powerline flows return in this vector, see the help of the
Backend.get_line_status()
method. Returns:
p_or
numpy.ndarray
– the origin active power flowing on the lines (in MW)q_or
numpy.ndarray
– the origin reactive power flowing on the lines (in MVAr)v_or
numpy.ndarray
– the voltage magnitude at the origin of each powerlines (in kV)a_or
numpy.ndarray
– the current flow at the origin of each powerlines (in A)
 load_grid(path: PathLike  str, filename: PathLike  str  None = None) None [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
This is called once at the loading of the powergrid.
Load the powergrid. It should first define self._grid.
And then fill all the helpers used by the backend eg. all the attributes of
Space.GridObjects
.After a the call to
Backend.load_grid()
has been performed, the backend should be in such a state where thegrid2op.Space.GridObjects
is properly set up. See the description ofgrid2op.Space.GridObjects
to know which attributes should be set here and which should not. Parameters:
path (
string
) – the path to find the powergridfilename (
string
, optional) – the filename of the powergrid
 Returns:
None
 loads_info() Tuple[ndarray, ndarray, ndarray] [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Prefer using
grid2op.Observation.BaseObservation.load_p
,grid2op.Observation.BaseObservation.load_q
andgrid2op.Observation.BaseObservation.load_v
instead.Note
It is called after the solver has been ran, only in case of success (convergence).
This method is used to retrieve information about the loads (active, reactive consumption and voltage magnitude of the bus to which it is connected).
Note
The values returned here are the values AFTER the powerflow has been computed and not the target values.
 Returns:
load_p
numpy.ndarray
– The active power consumption for each load (in MW)load_q
numpy.ndarray
– The reactive power consumption for each load (in MVAr)load_v
numpy.ndarray
– The voltage magnitude of the bus to which each load is connected (in kV)
 reset(path: PathLike  str, grid_filename: PathLike  str  None = None) None [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
This is done in the env.reset() method and should be performed otherwise.
Reload the power grid. For backwards compatibility this method calls Backend.load_grid. But it is encouraged to overload it in the subclasses.
 runpf(is_dc: bool = False) Tuple[bool, Exception  None] [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
This is called by
Backend.next_grid_state()
(that computes some kind of cascading failures).This is one of the core function if you want to code a backend. It will carry out a powerflow.
Run a power flow on the underlying _grid. Powerflow can be AC (is_dc = False) or DC (is_dc = True)
 Parameters:
is_dc (
bool
) – is the powerflow run in DC or in AC Returns:
True
if it has converged, or false otherwise. In case of non convergence, no flows can be inspected on the _grid. Return type:
bool
 Returns:
an exception in case of divergence (or none if no particular info are available)
 Return type:
Exception
 set_solver_max_iter(max_iter: int) None [source]
Set the maximum number of iteration the solver is allowed to perform.
We do not recommend to modify the default value (10), unless you are using the GaussSeidel powerflow. This powerflow being slower, we do not recommend to use it.
Recommendation, for medium sized grid (eg based on the ieee 118):
for SolverType.SparseLU: 10
for SolverType.GaussSeidel: 10000
for SolverType.SparseKLU: 10
 Parameters:
max_iter (
int
) – Maximum number of iteration the powerflow can run. It should be number >= 1
Notes
This has to be set for every backend that you want to use. For example, you have to set it in the backend of the _obs_env of the observation and if you are using “grid2op.MultMixEnv` you have to set it in all mixes!
 set_solver_type(solver_type: SolverType) None [source]
Change the type of solver you want to use.
Note that a powergrid should have been loaded for this function to work.
This function does not modify
LightSimBackend.max_iter
norLightSimBackend.tol
. You might want to modify these values depending on the solver you are using.Notes
By default, the fastest AC solver is used for your platform. This means that if KLU is available, then it is used otherwise it’s SparseLU.
This has to be set for every backend that you want to use. For example, you have to set it in the backend of the _obs_env of the observation and if you are using “grid2op.MultMixEnv` you have to set it in all mixes!
 Parameters:
solver_type (lightsim2grid.SolverType) – The new type of solver you want to use. See backend.available_solvers for a list of available solver on your machine.
 set_tol(new_tol: float) None [source]
Set the tolerance of the powerflow. This means that the powerflow will stop when the Kirchhoff’s Circuit Laws are met up to a tolerance of “new_tol”.
Decrease the tolerance might speed up the computation of the powerflow but will decrease the accuracy. We do not recommend to modify the default value of 1e8.
 Parameters:
new_tol (
float
) – The new tolerance to use (should be a float > 0)
Notes
This has to be set for every backend that you want to use. For example, you have to set it in the backend of the _obs_env of the observation and if you are using “grid2op.MultMixEnv` you have to set it in all mixes!
 shunt_info() Tuple[ndarray, ndarray, ndarray, ndarray] [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Note
It is called after the solver has been ran, only in case of success (convergence).
This method is optional. If implemented, it should return the proper information about the shunt in the powergrid.
If not implemented it returns empty list.
Note that if there are shunt on the powergrid, it is recommended that this method should be implemented before calling
Backend.check_kirchoff()
.If this method is implemented AND
Backend.check_kirchoff()
is called, the methodBackend.sub_from_bus_id()
should also be implemented preferably. Returns:
shunt_p (
numpy.ndarray
) – For each shunt, the active power it withdraw at the bus to which it is connected.shunt_q (
numpy.ndarray
) – For each shunt, the reactive power it withdraw at the bus to which it is connected.shunt_v (
numpy.ndarray
) – For each shunt, the voltage magnitude of the bus to which it is connected.shunt_bus (
numpy.ndarray
) – For each shunt, the bus id to which it is connected.
 storages_info() Tuple[ndarray, ndarray, ndarray] [source]
INTERNAL
Warning
/!\ Internal, do not use unless you know what you are doing /!\
Prefer using
grid2op.Observation.BaseObservation.storage_power
instead.This method is used to retrieve information about the storage units (active, reactive consumption and voltage magnitude of the bus to which it is connected).
 Returns:
storage_p
numpy.ndarray
– The active power consumption for each load (in MW)storage_q
numpy.ndarray
– The reactive power consumption for each load (in MVAr)storage_v
numpy.ndarray
– The voltage magnitude of the bus to which each load is connected (in kV)
 supported_grid_format
New in version 0.8.0.
Which type of grid format can be read by your backend. It is “json” if loaded from pandapower or “xiidm” if loaded from pypowsybl.
 timer_gridmodel_xx_pf
computation time of the powerflow it takes into account everything in the gridmodel, including the mapping to the solver, building of Ybus and Sbus AND the time to solve the powerflow