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 key-word 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 1e-8.

  • 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=1e-9,
                    solver_type=lightsim2grid.solver.SolverType.KLUSolverSingleSlack,
                    # etc.
                    )
                  )

Detailed documentation

Classes:

LightSimBackend([...])

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 = 1e-08, 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.

assert_grid_correct_after_powerflow()

This method is called by the environment.

close()

INTERNAL

copy()

INTERNAL

generators_info()

INTERNAL

get_line_flow()

INTERNAL

get_line_status()

INTERNAL

get_solver_types()

Return the types of solver that are used in the form a tuple with 2 elements.

get_theta()

returns:
  • line_or_theta (numpy.ndarray) -- For each orgin side of powerline, gives the voltage angle

get_topo_vect()

INTERNAL

lines_ex_info()

INTERNAL

lines_or_info()

INTERNAL

load_grid(path[, filename])

INTERNAL

loads_info()

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.

shunt_info()

INTERNAL

storages_info()

INTERNAL

Attributes:

supported_grid_format

New in version 0.8.0.

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

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 the grid2op.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 and grid2op.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 or grid2op.Observation.BaseObservation.a_ex for example

Return 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 instead

Note

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 by Backend.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 angle

  • line_ex_theta (numpy.ndarray) – For each extremity side of powerline, gives the voltage angle

  • load_theta (numpy.ndarray) – Gives the voltage angle to the bus at which each load is connected

  • gen_theta (numpy.ndarray) – Gives the voltage angle to the bus at which each generator is connected

  • storage_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 instead

Note

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 instead

Note

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 the grid2op.Space.GridObjects is properly set up. See the description of grid2op.Space.GridObjects to know which attributes should be set here and which should not.

Parameters:
  • path (string) – the path to find the powergrid

  • filename (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 and grid2op.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 nor LightSimBackend.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 1e-8.

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 method Backend.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