Repository: Grid2op/lightsim2grid (language: python)

Issue:
Adding the distributed slack feature
Merge some development made with the distributed slack feature.


Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 01dbee9..472baeb 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,7 +1,7 @@
 version: 2.1
 
 orbs:
-    win: circleci/windows@2.2.0 # The Windows orb give you everything you need to start using the Windows executor.
+    win: circleci/windows@2.4.1 # The Windows orb give you everything you need to start using the Windows executor.
 
 
 executors:
@@ -94,15 +94,27 @@ jobs:
             - run: python3 -m pip install virtualenv
             - run: python3 -m virtualenv venv_test
             - run:
+                name: "Install grid2op from source"
+                command: |
+                    source venv_test/bin/activate
+                    git clone https://github.com/rte-france/grid2op.git _grid2op
+                    pip install -e _grid2op
+            - run:
+                name: "Install lightsim2grid"
                 command: |
                     source venv_test/bin/activate
-                    pip install -U grid2op
                     pip install -U pybind11
                     git submodule init
                     git submodule update
                     make
                     CC=gcc python setup.py build
-                    python -m pip install -U .
+                    python -m pip install .[test]
+            - run:
+                name: "make tests"
+                command: |
+                    source venv_test/bin/activate
+                    cd lightsim2grid/tests/
+                    python -m unittest discover  -v
     compile_clang10:
         executor: clang10
         resource_class: small
@@ -114,7 +126,6 @@ jobs:
             - run:
                 command: |
                     source venv_test/bin/activate
-                    pip install -U grid2op
                     pip install -U pybind11
                     git submodule init
                     git submodule update
@@ -148,30 +159,59 @@ jobs:
             - run: python3 -m pip install virtualenv
             - run: python3 -m virtualenv venv_test
             - run:
+                name: "Install grid2op from source"
+                command: |
+                    source venv_test/bin/activate
+                    git clone https://github.com/rte-france/grid2op.git _grid2op
+                    pip install -e _grid2op
+            - run:
+                name: "Install lightsim2grid"
                 command: |
                     source venv_test/bin/activate
-                    pip install -U grid2op
                     pip install -U pybind11
                     git submodule init
                     git submodule update
                     make
                     CC=clang python setup.py build
-                    python -m pip install -U .
+                    python -m pip install -e .[test]
+            - run:
+                name: "make tests"
+                command: |
+                    source venv_test/bin/activate
+                    cd lightsim2grid/tests/
+                    python -m unittest discover -v
     compile_windows:
-        executor: win/default  # executor type
+        executor: 
+            name: win/default  # executor type
+            size: medium  # ("medium" "large" "xlarge" "2xlarge")
         steps:
             - checkout
+            - run:
+                name: "Install Python"
+                command: choco install python --version=3.9  # use python 3.9 for windows test
             - run: py -m pip install virtualenv
             - run: py -m virtualenv venv_test
             - run:
+                name: "Install grid2op from source"
+                command: |
+                    .\venv_test\Scripts\activate
+                    git clone https://github.com/rte-france/grid2op.git _grid2op
+                    pip install -e _grid2op
+            - run:
+                name: "Install lightsim2grid"
                 command: |
                     .\venv_test\Scripts\activate
-                    pip install -U grid2op
                     pip install -U pybind11
                     git submodule init
                     git submodule update
                     py setup.py build
-                    py -m pip install -U .
+                    py -m pip install -e .[test]
+            - run:
+                name: "make tests"
+                command: |
+                    .\venv_test\Scripts\activate
+                    cd lightsim2grid\tests
+                    python -m unittest discover -v
 
 workflows:
     version: 2.1
diff --git a/.gitignore b/.gitignore
index 94d0a67..010a0e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -218,3 +218,15 @@ perf.data
 perf.data.old
 test_profile.py.lprof
 benchmarks/case9241pegase.json
+benchmarks/case118.json
+benchmarks/case_illinois200.json
+benchmarks/case300.json
+benchmarks/case1354pegase.json
+benchmarks/case1888rte.json
+benchmarks/GBnetwork.json
+benchmarks/case2848rte.json
+benchmarks/case2869pegase.json
+benchmarks/case3120sp.json
+benchmarks/case6495rte.json
+_pandapower
+benchmarks/slacks.py
\ No newline at end of file
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 3ce825e..97df330 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -7,7 +7,7 @@ submodules:
   recursive: true
 
 python:
-    version: 3.6
+    version: 3.8
     install:
        - method: pip
          path: .
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 3dc73db..3495b02 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -3,15 +3,50 @@ Change Log
 
 [TODO]
 --------
-- switch c++ to float32 instead
+- improve speed by not performing internal checks 
+  (keep check for boundaries and all for python API instead) [see `TODO DEBUG MODE` in c++ code]
 - improve speed
-- improve documentation
-- easier building (get rid of the "make" part)
+- code parrallelism directly in the `Computer` and `SecurityAnalysisCPP` classes
 - code `helm` powerflow method
-- code NR with dense matrices
+- possibility to read CGMES files
+- possibility to read XIIDM files
 - interface with gridpack (to enforce q limits for example)
 - maybe have a look at suitesparse "sliplu" tools ?
-- perform unittest on CI (either github or circleci)
+- easier building (get rid of the "make" part)
+- code NR with dense matrices
+
+[0.6.0] 2021-12-17
+-------------------
+- [BREAKING] change the interface of the `newton_pf` function to reflect pandapower change in their
+  latest version (arguments `ref` has been added). You can still use the old `newton_pf` function, with the
+  old signature by importing `newtonpf_old` instead.
+- [BREAKING] `SecurityAnalysis` now also returns the active flows when calling `security_analysis.get_flows()`
+- [BREAKING] change the file names (python side) to be compliant with pep 8. You can no longer
+  do things like `from lightsim2grid.LightSimBackend import LightSimBackend` change it to
+  `from lightsim2grid import LightSimBackend` (preferred method)
+- [BREAKING] change the file names (python side) to be compliant with pep 8. You can no longer
+  do things like `from lightsim2grid.initGridModel import init` change it to
+  `from lightsim2grid.gridmodel import init` (preferred method) (same for `GridModel` class)
+- [FIXED] a bug that lead to the wrong computation of the dc powerflow in case of `sn_mva != 1.` and phase shifters.
+- [FIXED] bug preventing to use the NICSLU linear solver in the `GridModel`
+- [FIXED] compilation warnings on clang (missing virtual destructor, unused variables, etc.)
+- [FIXED] a bug in the `SecurityAnalysisCPP`: when it diverges for some contingencies, the others were not simulated properly.
+- [FIXED] `LightSimBackend` now contains members for `shunts` and `***_theta` as it does for the other quantities. This improves the consistency, but most importantly
+  fixes some bugs when used in earlier grid2op versions
+- [ADDED] possibility to compute the active flows using the `BaseMultiplePower` 
+- [ADDED] possibility to change linear solver used when performing a DC solver
+- [ADDED] possibility to make powerflow with distributed slack bus (only for newton raphson at the moment)
+- [ADDED] access (read only) to the element of a lightsim2grid grid with the `get_XXX` (*eg* `get_loads()`) methods (see documentation)
+- [ADDED] direct access to the solver used in the grid model python side
+- [ADDED] unittest in circleci.
+- [ADDED] all kind of solvers based on different linear solvers (Eigen sparse LU, KLU or NICSLU) for Newton Raphson and
+  DC approximation (9 solvers in total)
+- [IMPROVED] use of `steady_clock` to retrieve the ellapse time c++ side
+- [IMPROVED] refactoring of the c++ part to use template mecanism instead of inheritance for the
+  Newton Raphson and DC solvers.
+- [IMPROVED] `GridModel` now contains two different solvers, one for AC powerflow and one for DC powerflow.
+- [IMPROVED] error message in the solver are now embedded in an Enum instead of being integers, for better readibility.
+- [IMPROVED] error message when the powerflow diverge (error are read from c++ now)
 
 [0.5.5] 2021-11-10
 -------------------
diff --git a/DISCLAIMER.md b/DISCLAIMER.md
index a14d1f7..8f807d0 100644
--- a/DISCLAIMER.md
+++ b/DISCLAIMER.md
@@ -26,11 +26,8 @@ This simulator is free and uses Eigen and KLU for performances optimization. It
 independant for powersystem focus analysis. Indeed, the code provided in this package was made mainly for
 developping AI focused controlers. Some of its limitations include, but are not limited to:
 
-- it does not support multiple slack buses
-- it can only use Newton-Raphson algorithm
 - it does not enforce reactive power limits on generators
 - it does not model AC/DC converters
-- it does not model phase shifters
 - transformers have fixed tap ratio (though it can be changed at initialization of the solver)
 - shunts have fixed tap during the Newton-Raphson algorithm (though it can be changed at the initialization of the solver)
 - only powerflow ("steady state") can be performed
@@ -39,6 +36,7 @@ Open source options for powerflow analysis
 --------------------------------------------------
 To get free of these limitations and be able to perform state of the art powerflow analysis, 
 while still using open source softwares, we kindly recommend you to have a look at:
+
 - [Matpower](https://matpower.org/) which is a "free, open-source tools for electric power system simulation and 
   optimization "
 - [Pandapower](https://www.pandapower.org/) that is "An easy to use open source tool for power system modeling, 
diff --git a/Makefile b/Makefile
index 1be5558..a8a4603 100644
--- a/Makefile
+++ b/Makefile
@@ -79,4 +79,7 @@ doc_help:
 
 
 html: Makefile
-	@$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
+	@$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+# cmd windows: > sphinx-build -M html docs documentation
+# cmd for pdf > make latexpdf
\ No newline at end of file
diff --git a/README.md b/README.md
index e7ae4c9..b49d75b 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,29 @@ the world of power system.
 See the [Disclaimer](DISCLAIMER.md) to have a more detailed view on what is and what is not this package. For example
 this package should not be used for detailed power system computations or simulations.
 
+
+*   [1 Usage](#Usage)
+    *   [1.1. As a grid2op backend (preferred method)](#1-as-a-grid2op-backend-preferred-method)
+    *   [1.2. replacement of pandapower "newtonpf" method (advanced method)](#2-replacement-of-pandapower-newtonpf-method-advanced-method)
+*   [2 Installation (from pypi official repository, recommended)](#Installation-from-pypi-official-repository-recommended)
+*   [3 Installation (from source, for more advanced user)](#Installation-from-source-for-more-advanced-user)
+    *   [3.0 Important note](#Important-note)
+    *   [3.1. Retrieve the sources](#1-Retrieve-the-sources)
+    *   [(optional, recommended) Compilation of SuiteSparse](#optional-recommended-Compilation-of-SuiteSparse)
+        *   [option A. Compilation of SuiteSparse using "make"](#optional-option-a-Compilation-of-SuiteSparse-using-make)
+        *   [option B. Compilation of SuiteSparse using "cmake"](#optional-option-b-Compilation-of-SuiteSparse-using-cmake)
+    *   [(optional) Include NICSLU linear solver (experimental)](#optional-Include-NICSLU-linear-solver-experimental)
+    *   [(optional) customization of the installation](#optional-customization-of-the-installation)
+    *   [3.2 Installation of the python package](#2-Installation-of-the-python-package)
+*   [4. Benchmarks](#Benchmarks)
+*   [5. Philosophy](#Philosophy)
+*   [6. Miscellaneous](#Miscellaneous)
+    * [6.1 Customization of the compilation](#Customization-of-the-compilation)
+    * [6.2 Profile the code](#Profile-the-code)
+    * [6.3 Local testing](#Local-testing)
+    * [6.4 Tests performed automatically](#Tests-performed-automatically)
+    * [5.5 Known issues](#Known-issues)
+
 ## Usage
 Once installed (don't forget, if you used the optional virtual env
 above you need to load it with `source venv/bin/activate`) you can
@@ -13,8 +36,9 @@ use it as any python package.
 
 ### 1. As a grid2op backend (preferred method)
 This functionality requires you to have grid2op installed, with at least version 0.7.0. You can install it with
+
 ```commandline
-pip install grid2op>=0.7.0
+pip install grid2op>=1.6.4
 ```
 
 Then you can use a LightSimBackend instead of the default PandapowerBackend this way:
@@ -32,19 +56,21 @@ And you are good to go.
 It is also possible to use directly the "solver" part of lightsim2grid.
 
 Suppose you somehow get:
-- `Ybus` the admittance matrix of your powersystem given by pandapower
-- `V0` the (complex) voltage vector at each bus given by pandapower
-- `Sbus` the (complex) power absorb at each bus as given by pandapower
-- `ppci` a ppc internal pandapower test case
+- `Ybus` the admittance matrix of your powersystem, for example given by pandapower
+  (will be converted to a scipy `sparse.csc_matrix` )
+- `V0` the (complex) voltage vector at each bus, for example given by pandapower
+- `Sbus` the (complex) power absorb at each bus, for example as given by pandapower
+- `ref` Ids of the slack buses (added in version 0.5.6 to match recent pandapower changes)
 - `pv` list of PV buses
 - `pq` list of PQ buses
-- `options` list of pandapower "options"
+- `ppci` a ppc internal pandapower test case (or dictionary, is used to retrieve the coefficients associated to each slack bus)
+- `options` list of pandapower "options" (or dictionary with keys `max_iteration` and `tolerance_mva`)
 
 You can define replace the `newtonpf` function of `pandapower.pandapower.newtonpf` function with the following
 piece of code:
 ```python
 from lightsim2grid.newtonpf import newtonpf
-V, converged, iterations, J = newtonpf(Ybus, V, Sbus, pv, pq, ppci, options)
+V, converged, iterations, J = newtonpf(Ybus, V, Sbus, ref, pv, pq, ppci, options)
 ```
 
 This function uses the KLU algorithm and a c++ implementation of a Newton solver for speed.
@@ -108,10 +134,8 @@ Newton-Raphson based method. But to carry out this algorithm, one need to solver
 difference in the two version (with KLU and without) is that the linear equation solver is different. Up to the
 double float precision, both results (with and without KLU) should match.
 
-We only detail the compilation on a system using "make" (so most likely GNU-Linux and MacOS). If you don't feel 
-comfortable with this, either you can ignore it, or you have also the possibility to use the
-provided a docker version. See the next section [Installation Using Docker](#installation-using-docker) 
-for more information.
+There are 2 ways to install this package. Either you use "make" (preferred method on linux / unix -- including MacOS) or you use "cmake", which works on all platforms but takes more time and is less automatic (mainly because SuiteSparse
+cannot be directly built with "cmake" so we need extra steps to make it possible.)
 
 #### (optional) option A. Compilation of SuiteSparse using "make"
 This is the easiest method to compile SuiteSparse on your system but unfortunately it only works on OS where "make" is
@@ -162,8 +186,7 @@ To use it, you need to:
 And this is it. Lightsim will be able to use this linear solver.
 
 Be carefull though, you require a license file in order to use it. As of now, the best way is to copy paste the license
-file at the same location that the one you execute python from (*ie* you need to copy paste it each time). We will 
-try to find another solution.
+file at the same location that the one you execute python from (*ie* you need to copy paste it each time).
 
 ### (optional) customization of the installation
 
@@ -198,110 +221,39 @@ pip install -U .
 And you are done :-)
 
 
-### Benchmark
-In this section we will expose some brief benchmarks about the use of lightsim2grid in the grid2op settings.
-The code to run these benchmarks are given with this package int the [benchmark](./benchmarks) folder.
-
-All of them has been run on a computer with the following configuration:
-Configuration:
-- system: Linux 5.11.0-38-generic
-- OS: ubuntu 20.04
-- processor: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
-- python version: 3.8.10.final.0 (64 bit)
-- numpy version: 1.18.5
-- pandas version: 1.1.4
-- pandapower version: 2.6.0
-- lightsim2grid version: 0.5.5
-- grid2op version: 1.6.4
-
+## Benchmarks
 
-The code to reproduce the benchmark on your machine are given, once `cd` into the `benchmarks` directory:
-
-```commandline
-cd benchmarks  # cd in the lightsim2grid benchmarks directory if not already
-python3 benchmark_solvers.py --name l2rpn_case14_sandbox --no_test --number 1000
-python3 benchmark_solvers.py --name l2rpn_neurips_2020_track2_small --no_test --number 1000
-```
-(results may vary depending on the hard drive, the ram etc. )
-
-(to run these benchmarks, some data will automatically be downloaded, this requires an internet access)
-
-(we remind that these simulations correspond to simulation on one core of the CPU. Of course it is possible to
-make use of all the available cores, which would increase the number of steps that can be performed per second)
-
-We compare 6 different solvers:
-
-- **PP**: PandaPowerBackend (default grid2op backend) which is the reference in our benchmarks (uses the numba
-  acceleration). It is our reference solver.
-- **LS+GS** (LightSimBackend+Gauss Seidel): the grid2op backend based on lightsim2grid that uses the "Gauss Seidel"
-  solver to compute the powerflows It is implemented in
-  [GaussSeidelSolver](./src/GaussSeidelSolver.h).
-- **LS+GS S** (LightSimBackend+Gauss Seidel Synchronous): the grid2op backend based on lightsim2grid that uses a
-  variant of the "Gauss Seidel" method to compute the powerflows. It is implemented in
-  [GaussSeidelSynchSolver](./src/GaussSeidelSynchSolver.h).
-- **LS+SLU** (Newton Raphson+SparseLU): the grid2op backend based on lightsim2grid that uses the 
-  "Newton Raphson" algorithm coupled with the linear solver "SparseLU" from the
-  Eigen c++ library (available on all platform) and is implemented in
-  [SparseLUSolver](./src/SparseLUSolver.h).
-- **LS+KLU** (Newton Raphson+KLU): he grid2op backend based on lightsim2grid that uses the 
-  "Newton Raphson" algorithm coupled with the linear solver 
-  "KLU" from the `SuiteSparse` c package implemented in
-  [KLUSolver](./src/KLUSolver.h).
-- **LS+NICSLU** (Newton Raphson+NICSLU): he grid2op backend based on lightsim2grid that uses the 
-  "Newton Raphson" algorithm coupled with the linear solver 
-  "NICSLU" implemented in
-  [NICSLUSolver](./src/NICSLUSolver.h). [**NB** NICSLU is a free software but not open source, in order to use
-  it with lightsim2grid, you need to check section 
-  [(optional) Include NICSLU linear solver (experimental)](###-\(optional\)-Include-NICSLU-linear-solver-\(experimental\)) 
-  It is required to install lightsim2grid from source for such solver]
+Lightsim2grid is significantly faster than pandapower when used with grid2op for all kind of environment size.
 
 First on an environment based on the IEEE case14 grid:
 
-| case14_sandbox   |   grid2op speed (it/s) |   grid2op 'backend.runpf' time (ms) |   solver powerflow time (ms) |
-|------------------|------------------------|-------------------------------------|------------------------------|
-| PP               |                   68.8 |                              11.3   |                       4.36   |
-| LS+GS            |                  866   |                               0.41  |                       0.308  |
-| LS+GS S          |                  859   |                               0.416 |                       0.314  |
-| LS+SLU           |                 1090   |                               0.168 |                       0.0632 |
-| LS+KLU           |                 1160   |                               0.12  |                       0.0188 |
-| LS+NICSLU        |                 1150   |                               0.121 |                       0.0186 |
-
-
-From a grid2op perspective, lightsim2grid allows to compute up to ~1200 steps each second on the case 14 and
-"only" 69 for the default PandaPower Backend, leading to a speed up of **~17** in this case
-(lightsim2grid is ~17 times faster than Pandapower). For such a small environment, there is no sensible
-difference in using 
-KLU linear solver compared to using the SparseLU solver of Eigen (1160 vs 1090 iterations on the reported
-runs, might slightly vary across runs). KLU and NICSLU achieve almost identical performances.
+| case14_sandbox     |   grid2op speed (it/s) |   grid2op 'backend.runpf' time (ms) |   solver powerflow time (ms) |
+|--------------------|------------------------|-------------------------------------|------------------------------|
+| PP                 |                   70.5 |                              11     |                       4.27   |
+| LS+GS              |                  881   |                               0.447 |                       0.327  |
+| LS+GS S            |                  877   |                               0.446 |                       0.327  |
+| LS+SLU (single)    |                 1110   |                               0.191 |                       0.0655 |
+| LS+SLU             |                 1120   |                               0.195 |                       0.0683 |
+| LS+KLU (single)    |                 1200   |                               0.138 |                       0.0176 |
+| LS+KLU             |                 1180   |                               0.141 |                       0.0188 |
+| LS+NICSLU (single) |                 1200   |                               0.139 |                       0.0179 |
+| LS+NICSLU          |                 1200   |                               0.139 |                       0.0184 |
 
 Then on an environment based on the IEEE case 118:
 
 | neurips_2020_track2   |   grid2op speed (it/s) |   grid2op 'backend.runpf' time (ms) |   solver powerflow time (ms) |
 |-----------------------|------------------------|-------------------------------------|------------------------------|
-| PP                    |                  39.4  |                              13.5   |                        5.65  |
-| LS+GS                 |                   5.12 |                             194     |                      194     |
-| LS+GS S               |                  35.2  |                              27.5   |                       27.3   |
-| LS+SLU                |                 621    |                               0.775 |                        0.591 |
-| LS+KLU                |                 883    |                               0.301 |                        0.12  |
-| LS+NICSLU             |                 881    |                               0.302 |                        0.121 |
-
-For an environment based on the IEEE 118, the speed up in using lightsim + KLU (LS+KLU) is **~22** time faster than
-using the default PandaPower backend. The speed up of lightsim + SparseLU is a bit lower, but it is still **~16**
-times faster than using the default backend [the `LS+KLU` solver is ~4-5 times faster than the `LS+SLU` solver 
-(`0.12` ms per powerflow for `L2+KLU`  compared to `0.59` ms for `LS+SLU`), but it only translates to `LS+KLU` 
-providing ~30-40% more
-iterations per second in the total program (`880` vs `650`) mainly because grid2op itself takes some times to modify the
-grid and performs all the check it does.] For this testcase once again there is no noticeable difference between
-`NICSLU` and `KLU`.
-
-If we look now only at the time to compute one powerflow (and don't take into account the time to load the data, to
-initialize the solver, to modify the grid, read back the results, to perform the other update in the
-grid2op environment etc.) we can notice that it takes on average (over 1000 different states) approximately **0.12ms**
-to compute a powerflow with the LightSimBackend (if using the KLU linear solver) compared to the **5.6 ms** when using
-the PandaPowerBackend (speed up of **~46** times)
-
-**NB** pandapower performances heavily depends on the pandas version used, we used here a version of pandas which
-we found gave the best performances on our machine.
+| PP                    |                   39.6 |                              13.3   |                        5.58  |
+| LS+GS                 |                    5.3 |                             188     |                      188     |
+| LS+GS S               |                   36.5 |                              26.6   |                       26.4   |
+| LS+SLU (single)       |                  642   |                               0.775 |                        0.607 |
+| LS+SLU                |                  588   |                               0.932 |                        0.769 |
+| LS+KLU (single)       |                  945   |                               0.277 |                        0.116 |
+| LS+KLU                |                  918   |                               0.306 |                        0.144 |
+| LS+NICSLU (single)    |                  947   |                               0.274 |                        0.11  |
+| LS+NICSLU             |                  929   |                               0.298 |                        0.134 |
+
+For more information (including the exact way to reproduce these results, as well as the computer used), you can consult the dedicated [Benchmarks](https://lightsim2grid.readthedocs.io/en/latest/benchmarks.html) page on the documentation.
 
 ## Philosophy
 Lightsim2grid aims at providing a somewhat efficient (in terms of computation speed) backend targeting the 
@@ -309,7 +261,7 @@ grid2op platform.
 
 It provides a c++ api, compatible with grid2op that is able to compute flows (and voltages and reactive power) from
 a given grid. This grid can be modified according to grid2op mechanism (see more information in the [official
-grid2Op documentation](https://grid2op.readthedocs.io/en/latest/index.html`) ).
+grid2Op documentation](https://grid2op.readthedocs.io/en/latest/index.html) ).
 
 This code do not aim at providing state of the art solver in term of performances nor in terms of realism in the
 modeling of power system elements (*eg* loads, generators, powerlines, transformers, etc.).
@@ -327,6 +279,11 @@ Lightsim2grid codebase is "organized" in 4 different parts:
 4) computes the active power, reactive power, flow on powerllines etc. from the `V` and `Sbus` complex vectors computed
    at step 3).
 
+Step 1, 2 and 4 are done in the [GridModel](https://lightsim2grid.readthedocs.io/en/latest/gridmodel.html#lightsim2grid.gridmodel.GridModel) class.
+
+Step 3 is performed thanks to a "powerflow solver".
+
+### Using a custom powerflow solver
 For now some basic "solver" (*eg* the program that performs points `3.` above) are available, based on the
 Gauss Seidel or the Newton-Raphson methods to perform "powerflows". 
 
@@ -336,6 +293,8 @@ implement, in c++ a "lightsim2grid solver" which mainly consists in defining a f
 bool compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,  // the admittance matrix
                 CplxVect & V,  // store the results of the powerflow and the Vinit !
                 const CplxVect & Sbus,  // the injection vector
+                const Eigen::VectorXi & ref,  // bus id participating to the distributed slack
+                const RealVect & slack_weights,  // slack weights for each bus
                 const Eigen::VectorXi & pv,  // (might be ignored) index of the components of Sbus should be computed
                 const Eigen::VectorXi & pq,  // (might be ignored) index of the components of |V| should be computed
                 int max_iter,  // maximum number of iteration (might be ignored)
@@ -347,23 +306,42 @@ The types used are:
 
 - `real_type`: double => type representing the real number
 - `cplx_type` :  std::complex<real_type> => type representing the complex number
-- `CplxVect` : Eigen::Matrix<cplx_type, Eigen::Dynamic, 1> => type representing a vector of complex elements
+- `CplxVect` : Eigen::Matrix<cplx_type, Eigen::Dynamic, 1> => type representing a vector of complex numbers
+- `RealVect` : Eigen::Matrix<real_type, Eigen::Dynamic, 1> => type representing a vector of real numbers
 - `Eigen::VectorXi` => represents a vector of integer
 - `Eigen::SparseMatrix<cplx_type>` => represents a sparse matrix
 
-See for example [BaseNRSolver](./src/BaseNRSolver.h) for the implementation of a Newton Raphson solver (and
-its derived classes [KLUSolver](./src/KLUSolver.h) and [SparseLUSolver](./src/SparseLUSolver.h) that uses
-different routine to implement this algorithm) for examples on how to implement a solver.
+See for example [BaseNRSolver](./src/BaseNRSolver.h) for the implementation of a Newton Raphson solver (it requires some "linear solvers", more details about that are given in the section bellow)
 
 Any contribution in this area is more than welcome.
 
+**NB** For now the "solver" only uses these above information to perform the powerflow. If a more
+"in depth" solution needs to be implemented, let us know with a github issue. For example, it could be totally fine that a proposed "solver" uses direct information about the elements (powerline, topology etc.) of the grid in order to perform some powerflow.
+
 **NB** It is not mandatory to "embed" all the code of the solver in lightsim2grid. Thanks to different customization, 
 it is perfectly possible to install a given "lightsim solver" only if certain conditions are met. For example, on
 windows based machine, the SuiteSparse library cannot be easily compiled, and the KLUSolver is then not available.
 
-It would be totally fine if some "lightsim2grid" solvers are available only if some packages are installed on the
+**NB** It would be totally fine if some "lightsim2grid" solvers are available only if some packages are installed on the
 machine for example.
 
+### Using custom linear solvers to solve powerflows
+
+In lightsim2grid (c++ part) it is also possible, thanks to the use of "template meta programming" to
+not recode the Newton Raphson algorithm (or the DC powerflow algorithm) and to leverage the 
+use of a linear solver.
+
+A "linear solver" is anything that can implement 3 basic functions:
+
+- `initialize(const Eigen::SparseMatrix<real_type> & J)` : initialize the solver and prepare it to solve for linear systems `J.x = b` (usually called once per powerflow)
+- `ErrorType solve(const Eigen::SparseMatrix<real_type> & J, RealVect & b, bool has_just_been_inialized)`: effectively solves `J.x = b` (usually called multiple times per powerflow)
+- `ErrorType reset()`: clear the state of the solver (usually performed at the end of a powerflow
+  to reset the state to a "blank" / "as if it was just initialized" state)
+
+Some example are given in the c++ code "KLUSolver.h", "SparLUSolver.h" and "NICSLU.h"
+
+This usage usually takes approximately around 20 / 30 lines of c++ code (not counting the comments, and boiler code for exception handling for example).
+
 ## Citing
 
 If you use this package in one of your work, please cite:
@@ -378,149 +356,6 @@ If you use this package in one of your work, please cite:
 }
 ```
 
-## Installation (using docker)
-In this section we cover the use of docker with grid2op.
-
-### 1. Install docker
-First, you need to install docker. You can consult the 
-[docker on windows](https://hub.docker.com/editions/community/docker-ce-desktop-windows) if you use a windows like
-operating system, if you are using MacOs you can consult 
-[docker on Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac/). The installation of docker on linux
-depends on your linux distribution, we will not list them all here.
-
-### 2. Get the lightsim2grid image
-
-Once done, you can simply "install" the lightsim2grid image with:
-```commandline
-docker pull bdonnot/lightsim2grid:latest
-```
-
-This step should be done only once (unless you delete the image) it will download approximately 4 or 5GB from the
-internet. The lightsim2grid image contains lightsim and grid2op python packages (as well as their
-dependencies), equivalent of what would be installed if you typed:
-```commandline
-pip install -U grid2op[optional] pybind11
-# and do steps detailed in section "Installation (from source)"
-# that we will not repeat
-```
-
-### 3. Run a code on this container
-You can skip this section if you know how to use docker. We will present here "the simplest way" to use. This is NOT
-a tutorial on docker, and you can find better use of this technology on 
-[the docker website](https://www.docker.com/get-started).
-
-For this tutorial, we suppose you have a script named `my_script.py` located in the directory (complete path) 
-`DIR_PATH` (*e.g.* on windows you can have `DIR_PATH` looking like "c:\User\MyName\L2RPNCompeitionCode" or 
-on Linux `DIR_PATH` will look like "/home/MyName/L2RPNCompeitionCode", this path is your choice, you can name it
-the way you like)
-
-#### 3.1) Start a docker container
-You first need to start a docker container and tell docker that the container can access your local files with the 
-following command:
-
-```commandline
-docker run -t -d -p 8888:8888 --name lightsim_container -v DIR_PATH:/L2RPNCompeitionCode -w /L2RPNCompeitionCode bdonnot/lightsim2grid
-```
-More information on this command 
-[in the official docker documentation](https://docs.docker.com/engine/reference/commandline/run/)
-
-After this call you can check everything went smoothly with by invoking:
-```commandline
-docker ps
-```
-And the results should look like:
-```
-CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS              PORTS               NAMES
-89750964ca55        bdonnot/lightsim2grid   "python3"           5 seconds ago       Up 4 seconds        80/tcp              lightsim_container
-```
-
-**NB** I insist, `DIR_PATH` should be replaced by the path on which you are working, see again the introduction of this
-section for more information, in the example above this can look like:
-```commandline
-docker run -t -d -p 8888:8888 --name lightsim_container -v /home/MyName/L2RPNCompeitionCode:/L2RPNCompeitionCode -w /L2RPNCompeitionCode bdonnot/lightsim2grid
-```
-
-#### 3.2) Execute your code on this container
-Once everything is set-up you can execute anything you want on this container. Note that doing so, the execution
-of the code will be totally independant of your system. Only the things located in `DIR_PATH` will be visible 
-by your script, only the python package installed in the container will be usable, only the python interpreter
-of the containter (python 3.6 at time of writing) will be usable etc.
-
-```commandline
-docker exec lightsim_container python my_script.py
-```
-
-Of course, the "my_script.py" should save its output somewhere on the hard drive.
-
-If you rather want to execute a python REPL (read-eval-print loop), corresponding to the "interactive python 
-interpreter", you can run this command:
-```commandline
-docker exec -it lightsim_container python
-```
-
-We also added the possibility to run jupyter notebook from this container. To do so, you can run the command:
-```commandline
-docker exec -it lightsim_container jupyter notebook --port=8888 --no-browser --ip='*' --allow-root
-```
-
-More information is provided in the official documentation of 
-[docker exec](https://docs.docker.com/engine/reference/commandline/exec/).
-
-#### 3.3) Disclaimer
-Usually, docker run as root on your machine, be careful, you can do irreversible things with it. "A great power 
-comes with a great responsibility".
-
-Also, we recall that we presented a really short introduction to docker and its possibility. We have not implied
-that this was enough, nor explain (on purpose, to make this short) any of the commands. 
-We strongly encourage you to have a look for yourself. 
-
-We want to recall the paragraph `7. Limitation of Liability` under which lightsim2grid, and this "tutorial" 
-is distributed:
-
-*Under no circumstances and under no legal 
-theory, whether tort (including negligence), 
-contract, or otherwise, shall any Contributor, or 
-anyone who distributes Covered Software as 
-permitted above, be liable to You for any direct, 
-indirect, special, incidental, or consequential 
-damages of any character including, without 
-limitation, damages for lost profits, loss of 
-goodwill, work stoppage, __**computer failure or**__
-__**malfunction**__, or any and all other commercial 
-damages or losses, even if such party shall have 
-been informed of the possibility of such damages.*
-
-### 4) Clean-up
-Once you are done with your experiments, you can stop the docker container:
-```commandline
-docker container stop lightsim_container
-```
-This will free all the CPU / GPU resources that this container will use. If you want to start it again, for another 
-experiment for example, just use the command:
-```commandline
-docker container start lightsim_container
-```
-This will allow you to run another batch of `dcoker exec` (see `3.2) Execute your code on this container`) 
-without having to re run the container.
-
-
-If you want to go a step further, you can also delete the container with the command:
-```commandline
-docker container rm lightsim_container
-```
-This will remove the container, and all your code executed there, the history of commands etc. If you want to use
-lightsim2grid with docker again you will have to go through section `3. Run a code on this container` all over
-again.
-
-And if you also want to remove the image, you can do:
-```commandline
-docker rmi bdonnot/lightsim2grid 
-```
-**NB** this last command will completely erase the lightsim2grid image from your machine. This means that 
-if you want to use it again, you will have to download it again (see section `2. Get the lightsim2grid image`)
-
-Finally, you can see the official documentation in case you need to uninstall docker completely from your system.
-
 ## Miscellaneous
 
 ### Customization of the compilation
@@ -579,7 +414,7 @@ Some tests are performed automatically on standard platform each time modificati
 
 These tests include, for now, compilation on gcc (version 8, 9, 10 and 11) and clang (version 10, 11 and 12).
 
-**NB** Older version of clang are not tested regularly, but lightsim2grid used to work on these versions.
+**NB** Older versions of clang are not tested regularly, but lightsim2grid used to work on these.
 
 ### Known issues
 
diff --git a/benchmarks/benchmark_solvers.py b/benchmarks/benchmark_solvers.py
index de0980c..9a6bb30 100644
--- a/benchmarks/benchmark_solvers.py
+++ b/benchmarks/benchmark_solvers.py
@@ -14,6 +14,8 @@
 from grid2op.Agent import DoNothingAgent
 from grid2op.Chronics import ChangeNothing
 import re
+
+from lightsim2grid import solver
 try:
     from grid2op.Chronics import GridStateFromFileWithForecastsWithoutMaintenance as GridStateFromFile
 except ImportError:
@@ -22,7 +24,7 @@
 
 from grid2op.Parameters import Parameters
 import lightsim2grid
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 from utils_benchmark import print_res, run_env, str2bool, get_env_name_displayed, print_configuration
 TABULATE_AVAIL = False
 try:
@@ -36,9 +38,36 @@
 
 NICSLU_LICENSE_AVAIL = os.path.exists("./nicslu.lic") and os.path.isfile("./nicslu.lic")
 
+solver_names = {lightsim2grid.SolverType.GaussSeidel: "LS+GS",
+                lightsim2grid.SolverType.GaussSeidelSynch: "LS+GS S",
+                lightsim2grid.SolverType.SparseLU: "LS+SLU ",
+                lightsim2grid.SolverType.KLU: "LS+KLU",
+                lightsim2grid.SolverType.NICSLU: "LS+NICSLU",
+                lightsim2grid.SolverType.SparseLUSingleSlack: "LS+SLU (single)",
+                lightsim2grid.SolverType.KLUSingleSlack: "LS+KLU (single)",
+                lightsim2grid.SolverType.NICSLUSingleSlack: "LS+NICSLU (single)",
+                # lightsim2grid.SolverType.DC: "LS+DC",
+                # lightsim2grid.SolverType.KLUDC: "LS+SLU",
+                # lightsim2grid.SolverType.NICSLUDC: "LS+SLU"
+                }
+solver_gs = {lightsim2grid.SolverType.GaussSeidelSynch, lightsim2grid.SolverType.GaussSeidel}
+res_times = {}
+
+order_solver_print = [
+    lightsim2grid.SolverType.GaussSeidel,
+    lightsim2grid.SolverType.GaussSeidelSynch,
+    lightsim2grid.SolverType.SparseLUSingleSlack,
+    lightsim2grid.SolverType.SparseLU,
+    lightsim2grid.SolverType.KLUSingleSlack,
+    lightsim2grid.SolverType.KLU,
+    lightsim2grid.SolverType.NICSLUSingleSlack,
+    lightsim2grid.SolverType.NICSLU,
+]
+
 
 def main(max_ts, env_name_input, test=True,
-         no_gs=False, no_gs_synch=False,
+         no_gs=False,
+         no_gs_synch=False,
          no_pp=False):
     param = Parameters()
     param.init_from_dict({"NO_OVERFLOW_DISCONNECTION": True})
@@ -72,57 +101,37 @@ def main(max_ts, env_name_input, test=True,
 
     wst = True  # print extra info in the run_env function
     solver_types = env_lightsim.backend.available_solvers
-    if lightsim2grid.SolverType.KLU in solver_types:
-        print("Start using KLU")
-        env_lightsim.backend.set_solver_type(lightsim2grid.SolverType.KLU)
-        env_lightsim.backend.set_solver_max_iter(10)
-        nb_ts_klu, time_klu, aor_klu, gen_p_klu, gen_q_klu = run_env(env_lightsim, max_ts, agent, chron_id=0,
-                                                                     with_type_solver=wst, env_seed=0)
-        klu_comp_time = env_lightsim.backend.comp_time
-        klu_time_pf = env_lightsim._time_powerflow
-
-    if lightsim2grid.SolverType.NICSLU in solver_types and NICSLU_LICENSE_AVAIL:
-        print("Start using NICSLU")
-        env_lightsim.backend.set_solver_type(lightsim2grid.SolverType.NICSLU)
-        env_lightsim.backend.set_solver_max_iter(10)
-        nb_ts_nicslu, time_nicslu, aor_nicslu, gen_p_nicslu, gen_q_nicslu = run_env(env_lightsim,
-                                                                                    max_ts,
-                                                                                    agent, chron_id=0,
-                                                                                    with_type_solver=wst,
-                                                                                    env_seed=0)
-        nicslu_comp_time = env_lightsim.backend.comp_time
-        nicslu_time_pf = env_lightsim._time_powerflow
-
-    if lightsim2grid.SolverType.SparseLU in solver_types:
-        print("Start using SparseLU")
-        env_lightsim.backend.set_solver_type(lightsim2grid.SolverType.SparseLU)
-        env_lightsim.backend.set_solver_max_iter(10)
-        nb_ts_slu, time_slu, aor_slu, gen_p_slu, gen_q_slu = run_env(env_lightsim, max_ts, agent, chron_id=0,
-                                                                     with_type_solver=wst, env_seed=0)
-        slu_comp_time = env_lightsim.backend.comp_time
-        slu_time_pf = env_lightsim._time_powerflow
-
-    if lightsim2grid.SolverType.GaussSeidel in solver_types and no_gs is False:
-        print("Start using GaussSeidel")
-        env_lightsim.backend.set_solver_type(lightsim2grid.SolverType.GaussSeidel)
-        env_lightsim.backend.set_solver_max_iter(10000)
+    
+    for solver_type in solver_types:
+        if solver_type not in solver_names:
+            continue
+        print(f"Start using {solver_type}")
+        env_lightsim.backend.set_solver_type(solver_type)
+        if solver_type in solver_gs:
+            # gauss seidel sovler => more iterations
+            env_lightsim.backend.set_solver_max_iter(10000)
+            if lightsim2grid.SolverType.GaussSeidel == solver_type and no_gs:
+                # I don't study the gauss seidel solver
+                continue
+            elif lightsim2grid.SolverType.GaussSeidelSynch  == solver_type and no_gs_synch:
+                # I don't study the gauss seidel synch solver
+                continue
+        else:
+            # NR based solver => less iterations
+            env_lightsim.backend.set_solver_max_iter(10)
         nb_ts_gs, time_gs, aor_gs, gen_p_gs, gen_q_gs = run_env(env_lightsim, max_ts, agent, chron_id=0,
                                                                 with_type_solver=wst, env_seed=0)
         gs_comp_time = env_lightsim.backend.comp_time
         gs_time_pf = env_lightsim._time_powerflow
-
-    if lightsim2grid.SolverType.GaussSeidelSynch in solver_types and no_gs_synch is False:
-        print("Start using GaussSeidelSynch")
-        env_lightsim.backend.set_solver_type(lightsim2grid.SolverType.GaussSeidelSynch)
-        env_lightsim.backend.set_solver_max_iter(10000)
-        nb_ts_gsa, time_gsa, aor_gsa, gen_p_gsa, gen_q_gsa = run_env(env_lightsim, max_ts, agent, chron_id=0,
-                                                                     with_type_solver=wst, env_seed=0)
-        gsa_comp_time = env_lightsim.backend.comp_time
-        gsa_time_pf = env_lightsim._time_powerflow
+        res_times[solver_type] = (solver_names[solver_type],
+                                  nb_ts_gs, time_gs, aor_gs, gen_p_gs,
+                                  gen_q_gs, gs_comp_time, gs_time_pf)
 
     # NOW PRINT THE RESULTS
     print("Configuration:")
     print_configuration()
+    # order on which the solvers will be 
+    this_order =  [el for el in res_times.keys() if el not in order_solver_print] + order_solver_print
 
     env_name = get_env_name_displayed(env_name_input)
     hds = [f"{env_name}", f"grid2op speed (it/s)", f"grid2op 'backend.runpf' time (ms)", f"solver powerflow time (ms)"]
@@ -131,26 +140,15 @@ def main(max_ts, env_name_input, test=True,
         tab.append(["PP", f"{nb_ts_pp/time_pp:.2e}",
                     f"{1000.*pp_time_pf/nb_ts_pp:.2e}",
                     f"{1000.*pp_comp_time/nb_ts_pp:.2e}"])
-    if lightsim2grid.SolverType.GaussSeidel in solver_types and no_gs is False:
-        tab.append(["LS+GS", f"{nb_ts_gs/time_gs:.2e}",
+
+    for key in this_order:
+        if key not in res_times:
+            continue
+        solver_name, nb_ts_gs, time_gs, aor_gs, gen_p_gs, gen_q_gs, gs_comp_time, gs_time_pf = res_times[key]
+        tab.append([solver_name,
+                    f"{nb_ts_gs/time_gs:.2e}",
                     f"{1000.*gs_time_pf/nb_ts_gs:.2e}",
                     f"{1000.*gs_comp_time/nb_ts_gs:.2e}"])
-    if lightsim2grid.SolverType.GaussSeidelSynch in solver_types and no_gs_synch is False:
-        tab.append(["LS+GS S", f"{nb_ts_gsa/time_gsa:.2e}",
-                    f"{1000.*gsa_time_pf/nb_ts_gsa:.2e}",
-                    f"{1000.*gsa_comp_time/nb_ts_gsa:.2e}"])
-    if lightsim2grid.SolverType.SparseLU in solver_types:
-        tab.append(["LS+SLU", f"{nb_ts_slu/time_slu:.2e}",
-                    f"{1000.*slu_time_pf/nb_ts_slu:.2e}",
-                    f"{1000.*slu_comp_time/nb_ts_slu:.2e}"])
-    if lightsim2grid.SolverType.KLU in solver_types:
-        tab.append(["LS+KLU", f"{nb_ts_klu/time_klu:.2e}",
-                    f"{1000.*klu_time_pf/nb_ts_klu:.2e}",
-                    f"{1000.*klu_comp_time/nb_ts_klu:.2e}"])
-    if lightsim2grid.SolverType.NICSLU in solver_types:
-        tab.append(["LS+NICSLU", f"{nb_ts_nicslu/time_nicslu:.2e}",
-                    f"{1000.*nicslu_time_pf/nb_ts_nicslu:.2e}",
-                    f"{1000.*nicslu_comp_time/nb_ts_nicslu:.2e}"])
 
     if TABULATE_AVAIL:
         res_use_with_grid2op_1 = tabulate(tab, headers=hds,  tablefmt="rst")
@@ -169,37 +167,22 @@ def main(max_ts, env_name_input, test=True,
     if no_pp is False:
         hds = [f"{env_name} ({nb_ts_pp} iter)", f"Δ aor (amps)", f"Δ gen_p (MW)", f"Δ gen_q (MVAr)"]
         tab = [["PP (ref)", "0.00", "0.00", "0.00"]]
-        if lightsim2grid.SolverType.GaussSeidel in solver_types and no_gs is False:
-            tab.append(["LS+GS",
-                        f"{np.max(np.abs(aor_gs - aor_pp)):.2e}",
-                        f"{np.max(np.abs(gen_p_gs - gen_p_pp)):.2e}",
-                        f"{np.max(np.abs(gen_q_gs - gen_q_pp)):.2e}"])
-        if lightsim2grid.SolverType.GaussSeidelSynch in solver_types and no_gs_synch is False:
-            tab.append(["LS+GS S",
-                        f"{np.max(np.abs(aor_gsa - aor_pp)):.2e}",
-                        f"{np.max(np.abs(gen_p_gsa - gen_p_pp)):.2e}",
-                        f"{np.max(np.abs(gen_q_gsa - gen_q_pp)):.2e}"])
-        if lightsim2grid.SolverType.SparseLU in solver_types:
-            tab.append(["LS+SLU",
-                        f"{np.max(np.abs(aor_slu - aor_pp)):.2e}",
-                        f"{np.max(np.abs(gen_p_slu - gen_p_pp)):.2e}",
-                        f"{np.max(np.abs(gen_q_slu - gen_q_pp)):.2e}"])
-        if lightsim2grid.SolverType.KLU in solver_types:
-            tab.append(["LS+KLU",
-                        f"{np.max(np.abs(aor_klu - aor_pp)):.2e}",
-                        f"{np.max(np.abs(gen_p_klu - gen_p_pp)):.2e}",
-                        f"{np.max(np.abs(gen_q_klu - gen_q_pp)):.2e}"])
-        if lightsim2grid.SolverType.NICSLU in solver_types:
-            tab.append(["LS+NICSLU",
-                        f"{np.max(np.abs(aor_nicslu - aor_pp)):.2e}",
-                        f"{np.max(np.abs(gen_p_nicslu - gen_p_pp)):.2e}",
-                        f"{np.max(np.abs(gen_q_nicslu - gen_q_pp)):.2e}"])
-
-        if TABULATE_AVAIL:
-            res_use_with_grid2op_2 = tabulate(tab, headers=hds,  tablefmt="rst")
-            print(res_use_with_grid2op_2)
-        else:
-            print(tab)
+
+    for key in this_order:
+        if key not in res_times:
+            continue
+        solver_name, nb_ts_gs, time_gs, aor_gs, gen_p_gs, gen_q_gs, gs_comp_time, gs_time_pf = res_times[key]
+        tab.append([solver_name,
+                    f"{np.max(np.abs(aor_gs - aor_pp)):.2e}",
+                    f"{np.max(np.abs(gen_p_gs - gen_p_pp)):.2e}",
+                    f"{np.max(np.abs(gen_q_gs - gen_q_pp)):.2e}"])
+
+    if TABULATE_AVAIL:
+        res_use_with_grid2op_2 = tabulate(tab, headers=hds,  tablefmt="rst")
+        print(res_use_with_grid2op_2)
+    else:
+        print(tab)
+
     print()
 
 
diff --git a/benchmarks/benchmark_tolerance.py b/benchmarks/benchmark_tolerance.py
index 200cad8..99e47cc 100644
--- a/benchmarks/benchmark_tolerance.py
+++ b/benchmarks/benchmark_tolerance.py
@@ -19,7 +19,7 @@
 
 from grid2op.Parameters import Parameters
 import lightsim2grid
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 from utils_benchmark import print_res, run_env, str2bool, get_env_name_displayed
 TABULATE_AVAIL = False
 try:
diff --git a/benchmarks/do_nothing.py b/benchmarks/do_nothing.py
index a55903f..967de73 100644
--- a/benchmarks/do_nothing.py
+++ b/benchmarks/do_nothing.py
@@ -13,7 +13,7 @@
 from grid2op.Agent import DoNothingAgent
 from grid2op.Chronics import GridStateFromFile
 from grid2op.Parameters import Parameters
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 from utils_benchmark import print_res, run_env, str2bool
 import pdb
 
diff --git a/benchmarks/greedy_powerline.py b/benchmarks/greedy_powerline.py
index 2ce6a2b..885b971 100644
--- a/benchmarks/greedy_powerline.py
+++ b/benchmarks/greedy_powerline.py
@@ -14,7 +14,7 @@
 from grid2op.Rules import AlwaysLegal
 from grid2op.Chronics import GridStateFromFileWithForecasts
 
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 
 from utils_benchmark import print_res, run_env
 
diff --git a/benchmarks/greedy_topo.py b/benchmarks/greedy_topo.py
index 5b01d06..69f0996 100644
--- a/benchmarks/greedy_topo.py
+++ b/benchmarks/greedy_topo.py
@@ -13,7 +13,7 @@
 from grid2op.Parameters import Parameters
 from grid2op.Rules import AlwaysLegal
 
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 
 from utils_benchmark import print_res, run_env
 
diff --git a/benchmarks/grid_size.py b/benchmarks/grid_size.py
new file mode 100644
index 0000000..0a54a85
--- /dev/null
+++ b/benchmarks/grid_size.py
@@ -0,0 +1,206 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform.
+
+import warnings
+from numpy.core.fromnumeric import size
+import pandapower as pp
+import numpy as np
+from scipy.interpolate import interp1d
+import matplotlib.pyplot as plt
+from grid2op import make, Parameters
+from grid2op.Chronics import ChangeNothing
+from lightsim2grid import LightSimBackend, TimeSerie
+from tqdm import tqdm
+
+VERBOSE = False
+
+case_names = ["case118.json",
+              "case_illinois200.json",
+              "case300.json",
+              "case1354pegase.json",
+              "case1888rte.json",
+              "GBnetwork.json",  # 2224 buses
+              "case2848rte.json",
+              "case2869pegase.json",
+              "case3120sp.json",
+              "case6495rte.json",
+              "case9241pegase.json"]
+
+def make_grid2op_env(pp_case, casse_name):
+    param = Parameters.Parameters()
+    param.init_from_dict({"NO_OVERFLOW_DISCONNECTION": True})
+    with warnings.catch_warnings():
+        warnings.filterwarnings("ignore")
+        env_lightsim = make("blank", param=param, test=True,
+                            backend=LightSimBackend(),
+                            data_feeding_kwargs={"gridvalueClass": ChangeNothing},
+                            grid_path=case_name,
+                            _add_to_name=f"{case_name}")
+    return env_lightsim
+
+def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
+    # scale loads
+
+    # use some French time series data for loads
+    # see https://github.com/BDonnot/data_generation for where to find this file
+    coeffs = {"sources": {
+    "country": "France",
+    "year": "2012",
+    "web": "http://clients.rte-france.com/lang/fr/visiteurs/vie/vie_stats_conso_inst.jsp"
+    },
+    "month": {
+    "jan": 1.21,
+    "feb": 1.40,
+    "mar": 1.05,
+    "apr": 1.01,
+    "may": 0.86,
+    "jun": 0.84,
+    "jul": 0.84,
+    "aug": 0.79,
+    "sep": 0.85,
+    "oct": 0.94,
+    "nov": 1.01,
+    "dec": 1.20
+    },
+    "day": {
+    "mon": 1.01,
+    "tue": 1.05,
+    "wed": 1.05,
+    "thu": 1.05,
+    "fri": 1.03,
+    "sat": 0.93,
+    "sun": 0.88
+    },
+    "hour": {
+    "00:00": 1.00,
+    "01:00": 0.93,
+    "02:00": 0.91,
+    "03:00": 0.86,
+    "04:00": 0.84,
+    "05:00": 0.85,
+    "06:00": 0.90,
+    "07:00": 0.97,
+    "08:00": 1.03,
+    "09:00": 1.06,
+    "10:00": 1.08,
+    "11:00": 1.09,
+    "12:00": 1.09,
+    "13:00": 1.09,
+    "14:00": 1.06,
+    "15:00": 1.03,
+    "16:00": 1.00,
+    "17:00": 1.00,
+    "18:00": 1.04,
+    "19:00": 1.09,
+    "20:00": 1.05,
+    "21:00": 1.01,
+    "22:00": 0.99,
+    "23:00": 1.03
+    }
+    }
+    vals = list(coeffs["hour"].values())
+    x_final = np.arange(12*len(vals))
+
+    # interpolate them at 5 minutes resolution (instead of 1h)
+    vals.append(vals[0])
+    vals = np.array(vals) * coeffs["month"]["mar"] * coeffs["day"]["mon"]
+    x_interp = 12 * np.arange(len(vals))
+    coeffs = interp1d(x=x_interp, y=vals, kind="cubic")
+    all_vals = coeffs(x_final)
+
+    # compute the "smooth" loads matrix
+    load_p_smooth = all_vals.reshape(-1, 1) * load_p_init.reshape(1, -1)
+    load_q_smooth = all_vals.reshape(-1, 1) * load_q_init.reshape(1, -1)
+
+    # add a bit of noise to it to get the "final" loads matrix
+    load_p = load_p_smooth * np.random.lognormal(mean=0., sigma=0.003, size=load_p_smooth.shape)
+    load_q = load_q_smooth * np.random.lognormal(mean=0., sigma=0.003, size=load_q_smooth.shape)
+
+    # scale generators accordingly
+    gen_p = load_p.sum(axis=1).reshape(-1, 1) / load_p_init.sum() * gen_p_init.reshape(1, -1)
+    sgen_p = load_p.sum(axis=1).reshape(-1, 1) / load_p_init.sum() * sgen_p_init.reshape(1, -1)
+
+    return load_p, load_q, gen_p, sgen_p
+
+
+if __name__ == "__main__":
+    np.random.seed(42)
+    times = []
+    speeds = []
+    sizes = []
+    for case_name in tqdm(case_names):
+        # load the case file
+        case = pp.from_json(case_name)
+        # extract reference data
+        load_p_init = 1.0 * case.load["p_mw"].values
+        load_q_init = 1.0 * case.load["q_mvar"].values
+        gen_p_init = 1.0 * case.gen["p_mw"].values
+        sgen_p_init = 1.0 * case.sgen["p_mw"].values
+
+        res_time = 1.
+        res_unit = "s"
+        if len(load_p_init) <= 1000:
+            # report results in ms if there are less than 1000 loads
+            # only affects "verbose" printing
+            res_time = 1e3
+            res_unit = "ms"
+
+        # simulate the data
+        load_p, load_q, gen_p, sgen_p = get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init)
+        
+        # create the grid2op env
+        env_lightsim = make_grid2op_env(case, case_name)
+
+        # compute the right things
+        time_serie = TimeSerie(env_lightsim)
+        computer = time_serie.computer
+        v_init = env_lightsim.backend.V
+        status = computer.compute_Vs(gen_p,
+                                    sgen_p,
+                                    load_p,
+                                    load_q,
+                                    v_init,
+                                    env_lightsim.backend.max_it,
+                                    env_lightsim.backend.tol)
+        time_serie._TimeSerie__computed = True
+        a_or = time_serie.compute_A()
+        assert status, f"some powerflow diverge for {case_name}"
+
+        if VERBOSE:
+            # print detailed results if needed
+            print(f"For environment: {case_name} ({env_lightsim.n_sub} substations) [{computer.nb_solved()} powerflows]")
+            print(f"Total time spent in \"computer\" to solve everything: {res_time*computer.total_time():.2f}{res_unit} "
+                f"({computer.nb_solved() / computer.total_time():.0f} pf / s), "
+                f"{1000.*computer.total_time() / computer.nb_solved():.2f} ms / pf)")
+            print(f"\t - time to pre process the injections: {res_time * computer.preprocessing_time():.2f}{res_unit}")
+            print(f"\t - time to perform powerflows: {res_time * computer.solver_time():.2f} {res_unit} "
+                f"({computer.nb_solved() / computer.solver_time():.0f} pf / s, "
+                f"{1000.*computer.solver_time() / computer.nb_solved():.2f} ms / pf)")
+            print(f"In addition, it took {res_time * computer.amps_computation_time():.2f} {res_unit} to retrieve the current "
+                f"from the complex voltages (in total "
+                f"{computer.nb_solved() / ( computer.total_time() + computer.amps_computation_time()):.1f} "
+                "pf /s, "
+                f"{1000.*( computer.total_time() + computer.amps_computation_time()) / computer.nb_solved():.2f} ms / pf)")
+        
+        times.append(computer.total_time() + computer.amps_computation_time())
+        speeds.append(computer.nb_solved() / (computer.total_time() + computer.amps_computation_time()) )
+        sizes.append(env_lightsim.n_sub)
+        env_lightsim.close()
+
+    # make the plot summarizing all results
+    plt.plot(sizes, times, linestyle='solid', marker='+', markersize=8)
+    plt.xlabel("Size (number of substation)")
+    plt.ylabel("Time taken (s)")
+    plt.title(f"Time to compute {computer.nb_solved()} powerflows")
+    plt.show()
+
+    plt.plot(sizes, speeds, linestyle='solid', marker='+', markersize=8)
+    plt.xlabel("Size (number of substation)")
+    plt.ylabel("Speed (pf / s)")
+    plt.title(f"Computation speed")
+    plt.show()
diff --git a/benchmarks/topo_action.py b/benchmarks/topo_action.py
index 1206d3c..1302a3a 100644
--- a/benchmarks/topo_action.py
+++ b/benchmarks/topo_action.py
@@ -15,7 +15,7 @@
 from grid2op.Rules import AlwaysLegal
 from grid2op.Chronics import GridStateFromFile
 
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 
 from utils_benchmark import print_res, run_env, str2bool
 
diff --git a/benchmarks/utils_benchmark.py b/benchmarks/utils_benchmark.py
index 945c296..b7246df 100644
--- a/benchmarks/utils_benchmark.py
+++ b/benchmarks/utils_benchmark.py
@@ -94,7 +94,7 @@ def run_env(env, max_ts, agent, chron_id=None, keep_forecast=False, with_type_so
     reward = env.reward_range[0]
     nb_ts = 0
     prev_act = None
-    beg_ = time.time()
+    beg_ = time.perf_counter()
     with tqdm(total=nb_rows, desc=pbar_desc) as pbar:
         while not done:
             act = agent.act(obs, reward, done)
@@ -109,7 +109,7 @@ def run_env(env, max_ts, agent, chron_id=None, keep_forecast=False, with_type_so
             # if np.sum(obs.line_status) < obs.n_line - 1 * (nb_ts % 2 == 1):
             #     print("There is a bug following action; {}".format(act))
             prev_act = act
-    end_ = time.time()
+    end_ = time.perf_counter()
     total_time = end_ - beg_
     return nb_ts, total_time, aor, gen_p, gen_q
 
diff --git a/docs/benchmarks.rst b/docs/benchmarks.rst
new file mode 100644
index 0000000..83371c8
--- /dev/null
+++ b/docs/benchmarks.rst
@@ -0,0 +1,227 @@
+
+Benchmarks
+============
+
+In this paragraph we will expose some brief benchmarks about the use of lightsim2grid in the grid2op settings.
+The code to run these benchmarks are given with this package int the [benchmark](./benchmarks) folder.
+
+TODO DOC in progress
+
+If you are interested in other type of benchmark, let us know !
+
+Using a grid2op environment
+----------------------------
+In this section we perform some benchmark of a `do nothing` agent to test the raw performance of lightsim2grid
+compared with pandapower when using grid2op.
+
+All of them has been run on a computer with a the following characteristics:
+
+- system: Linux 5.11.0-40-generic
+- OS: ubuntu 20.04
+- processor: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
+- python version: 3.8.10.final.0 (64 bit)
+- numpy version: 1.18.5
+- pandas version: 1.1.4
+- pandapower version: 2.7.0
+- lightsim2grid version: 0.6.0
+- grid2op version: 1.6.4
+
+
+To run the benchmark `cd` in the [benchmark](./benchmarks) folder and type:
+
+.. code-block:: bash
+
+    python3 benchmark_solvers.py --name l2rpn_case14_sandbox --no_test --number 1000
+    python3 benchmark_solvers.py --name l2rpn_neurips_2020_track2_small --no_test --number 1000
+
+(results may vary depending on the hard drive, the ram etc. and are presented here for illustration only)
+
+(we remind that these simulations correspond to simulation on one core of the CPU. Of course it is possible to
+make use of all the available cores, which would increase the number of steps that can be performed)
+
+We compare up to 9 different solvers:
+
+- **PP**: PandaPowerBackend (default grid2op backend) which is the reference in our benchmarks (uses the numba
+  acceleration). It is our reference solver.
+- **LS+GS** (LightSimBackend+Gauss Seidel): the grid2op backend based on lightsim2grid that uses the "Gauss Seidel"
+  solver to compute the powerflows.
+- **LS+GS S** (LightSimBackend+Gauss Seidel Synchronous): the grid2op backend based on lightsim2grid that uses a
+  variant of the "Gauss Seidel" method to compute the powerflows).
+- **LS+SLU** (Newton Raphson+SparseLU): the grid2op backend based on lightsim2grid that uses the 
+  "Newton Raphson" algorithm coupled with the linear solver "SparseLU" from the
+  Eigen c++ library (available on all platform). This solver supports distributed slack bus.
+- **LS+SLU (single)** (Newton Raphson+SparseLU): same as above but this solver does not support distributed slack bus and
+  can thus be slightly faster.
+- **LS+KLU** (Newton Raphson+KLU): he grid2op backend based on lightsim2grid that uses the 
+  "Newton Raphson" algorithm coupled with the linear solver 
+  "KLU" from the `SuiteSparse` C package. This solver supports distributed slack bus.
+- **LS+KLU (single)** (Newton Raphson+KLU): same as above but this solver does not support distributed slack bus and
+  can thus be slightly faster.
+- **LS+NICSLU** (Newton Raphson+NICSLU): he grid2op backend based on lightsim2grid that uses the 
+  "Newton Raphson" algorithm coupled with the linear solver 
+  "NICSLU". [**NB** NICSLU is a free software but not open source, in order to use
+  it with lightsim2grid, you need to install lightsim2grid from source for such solver]
+- **LS+NICSLU (single)** (Newton Raphson+NICSLU): same as above but this solver does not support distributed slack bus and
+  can thus be slightly faster.
+
+All benchmarks where done with all the customization (for speed, *eg* `-O3` for linux). See the readme for more information.
+
+Computation time
+~~~~~~~~~~~~~~~~~~~
+
+In this first subsection we compare the computation times:
+
+- **grid2op speed** from a grid2op point of view
+  (this include the time to compute the powerflow, plus the time to modify the powergrid plus the
+  time to read back the data once the powerflow has run plus the time to update the environment and
+  the observations etc.). It is reported in "iteration per second" (`it/s`) and represents the number of grid2op "step"
+  that can be computed per second.
+- **grid2op 'backend.runpf' time** corresponds to the time the solver take to perform a powerflow
+  as seen from grid2op (counting the resolution time and some time to check the validity of the results but
+  not the time to update the grid nor the grid2op environment), for lightsim2grid it includes the time to read back the data
+  from c++ to python. It is reported in milli seconds (ms).
+- **solver powerflow time** corresponds only to the time spent in the solver itself. It does not take into
+  account any of the checking, nor the transfer of the data python side etc. It is reported in milli seconds (ms) as well.
+
+There are two major differences between **grid2op 'backend.runpf' time** and **solver powerflow time**. In **grid2op 'backend.runpf' time**
+the time to initialize the solver (usually with the DC approximation) is counted (it is not in **solver powerflow time**). Secondly,
+in **grid2op 'backend.runpf' time** the time to read back the data is also included. This explain why **grid2op 'backend.runpf' time** is
+stricly greater, for all benchmarks, than **solver powerflow time** (the closer it is, the better the implementation of the LightSimBackend)
+
+
+First on an environment based on the IEEE case 14 grid:
+
+==================  ======================  ===================================  ============================
+case14_sandbox        grid2op speed (it/s)    grid2op 'backend.runpf' time (ms)    solver powerflow time (ms)
+==================  ======================  ===================================  ============================
+PP                                    70.5                               11                            4.27
+LS+GS                                881                                  0.447                        0.327
+LS+GS S                              877                                  0.446                        0.327
+LS+SLU (single)                     1110                                  0.191                        0.0655
+LS+SLU                              1120                                  0.195                        0.0683
+LS+KLU (single)                     1200                                  0.138                        0.0176
+LS+KLU                              1180                                  0.141                        0.0188
+LS+NICSLU (single)                  1200                                  0.139                        0.0179
+LS+NICSLU                           1200                                  0.139                        0.0184
+==================  ======================  ===================================  ============================
+
+From a grid2op perspective, lightsim2grid allows to compute up to ~1200 steps each second on the case 14 and
+"only" 70 for the default PandaPower Backend, leading to a speed up of **~17** in this case
+(lightsim2grid is ~17 times faster than `Pandapower`). For such a small environment, there is no sensible
+difference in using `KLU` linear solver compared to using the SparseLU solver of Eigen (1120 vs 1200 iterations on the reported
+runs, might slightly vary across runs). `KLU` and `NICSLU` achieve almost identical performances.
+
+Then on an environment based on the IEEE case 118:
+
+=====================  ======================  ===================================  ============================
+neurips_2020_track2      grid2op speed (it/s)    grid2op 'backend.runpf' time (ms)    solver powerflow time (ms)
+=====================  ======================  ===================================  ============================
+PP                                       39.6                               13.3                           5.58
+LS+GS                                     5.3                              188                           188
+LS+GS S                                  36.5                               26.6                          26.4
+LS+SLU (single)                         642                                  0.775                         0.607
+LS+SLU                                  588                                  0.932                         0.769
+LS+KLU (single)                         945                                  0.277                         0.116
+LS+KLU                                  918                                  0.306                         0.144
+LS+NICSLU (single)                      947                                  0.274                         0.11
+LS+NICSLU                               929                                  0.298                         0.134
+=====================  ======================  ===================================  ============================
+
+For an environment based on the IEEE 118, the speed up in using lightsim + KLU (LS+KLU) is **~24** time faster than
+using the default `PandaPower` backend (~950 it/s vs ~40). 
+
+The speed up of lightsim + SparseLU (`0.11`) is a bit lower, but it is still **~16**
+times faster than using the default backend [the `LS+KLU` solver is ~5-6 times faster than the `LS+SLU` solver 
+(`0.11` ms per powerflow for `L2+KLU`  compared to `0.6` ms for `LS+SLU`), but it only translates to `LS+KLU` 
+providing ~40-50% more
+iterations per second in the total program (`950` vs `640`) mainly because grid2op itself takes some times to modify the
+grid and performs all the check it does.] For this testcase once again there is no noticeable difference between
+`NICSLU` and `KLU`.
+
+If we look now only at the time to compute one powerflow (and don't take into account the time to load the data, to
+initialize the solver, to modify the grid, read back the results, to perform the other update in the
+grid2op environment etc. -- column "solver powerflow time (ms)") we can notice that it takes on average (over 1000 different states) approximately **0.12ms**
+to compute a powerflow with the LightSimBackend (if using the `KLU` linear solver) compared to the **5.6 ms** when using
+the PandaPowerBackend (speed up of **~46** times)
+
+**NB** pandapower performances heavily depends on the pandas version used, we used here a version of pandas which
+we found gave the best performances on our machine.
+
+.. note:: The "solver powerflow time" reported for pandapower is obtained by summing, over the 1000 powerflow performed
+    the `pandapower_backend._grid["_ppc"]["et"]` (the "estimated time" of the pandapower newton raphson computation
+    with the numba accelaration enabled)
+
+    For the lightsim backend, the "solver powerflow time" corresponds to the sum of the results of
+    `gridmodel.get_computation_time()` function that, for each powerflow, returns the time spent in the solver
+    uniquely (time inside the `basesolver.compute_pf()` function. In particular it do not count the time
+    to initialize the vector V with the DC approximation)
+
+Differences
+~~~~~~~~~~~~~~~~~~~
+Using the same command, we report the maximum value of the differences between different quantities:
+
+- `aor` : the current flow (in Amps) at the origin side of each powerline
+- `gen_p` : the generators active production values
+- `gen_q`: the generators reactive production values
+
+Note that only the maximum values (of the absolute differences) across all the steps (1000 for the IEEE case 14 and
+1000 for the IEEE case 118)
+and across all the lines (or generators) is displayed.
+
+We report only the difference compared with the baseline which is pandapower (PP).
+
+Here are the results for the IEEE case 14 (max over 1000 powerflows):
+
+============================  ==============  ==============  ================
+case14_sandbox (1000 iter)      Δ aor (amps)    Δ gen_p (MW)    Δ gen_q (MVAr)
+============================  ==============  ==============  ================
+PP (ref)                            0               0                 0
+LS+GS                               0.000122        7.63e-06          7.63e-06
+LS+GS S                             0.000122        7.63e-06          7.63e-06
+LS+SLU (single)                     0.000122        7.63e-06          7.63e-06
+LS+SLU                              0.000122        7.63e-06          7.63e-06
+LS+KLU (single)                     0.000122        7.63e-06          7.63e-06
+LS+KLU                              0.000122        7.63e-06          7.63e-06
+LS+NICSLU (single)                  0.000122        7.63e-06          7.63e-06
+LS+NICSLU                           0.000122        7.63e-06          7.63e-06
+============================  ==============  ==============  ================
+
+.. note::
+
+    Flows are here measured in amps (and not kA). The maximum difference of flows is approximately 0.1mA
+    or 1e-4 A. This difference is totally neglectible on power transportation side where the current is usually
+    around 1kA (1e3 A).
+
+Here are the results for the IEEE case 118 (max over 1000 powerflows):
+
+=================================  ==============  ==============  ================
+neurips_2020_track2 (1000 iter)      Δ aor (amps)    Δ gen_p (MW)    Δ gen_q (MVAr)
+=================================  ==============  ==============  ================
+PP (ref)                                  0              0                 0
+LS+GS                                     6.1e-05        3.81e-06          1.53e-05
+LS+GS S                                   6.1e-05        3.81e-06          1.53e-05
+LS+SLU (single)                           6.1e-05        0                 9.54e-07
+LS+SLU                                    6.1e-05        0                 9.54e-07
+LS+KLU (single)                           6.1e-05        0                 9.54e-07
+LS+KLU                                    6.1e-05        0                 9.54e-07
+LS+NICSLU (single)                        6.1e-05        0                 9.54e-07
+LS+NICSLU                                 6.1e-05        0                 9.54e-07
+=================================  ==============  ==============  ================
+
+As we can see on all the tables above, the difference when using lightsim and pandapower is rather
+small, even when using a different algorithm to solve the powerflow (LS + GS corresponds to
+using Gauss Seidel as opposed to using Newton Raphson solver)
+
+When using Newton Raphson solvers, the difference in absolute values when using lightsim2grid compared
+with using PandaPowerBackend is neglectible: less than 1e-06 in all cases (and 0.00 when comparing the
+flows on the powerline for both environments).
+
+Other benchmarks
+----------------------------
+
+With lightsim2grid 0.5.5 some new feature has been introduced, which are the "security analysis" and the "comptuation 
+of time series". 
+
+The respective benchmarks are put in their respective section :ref:`sa_benchmarks` and :ref:`ts_benchmarks`. These 
+classes allow to achieve a *15x* and even *100x* speed ups over grid2op (using lightsim2grid), for example 
+allowing to perform 186 powerflow on the IEEE 118 in less than 3 ms.
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 3fa503e..1f0eca3 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -22,8 +22,8 @@
 author = 'Benjamin DONNOT'
 
 # The full version, including alpha/beta/rc tags
-release = '0.5.5'
-version = '0.5'
+release = '0.6.0'
+version = '0.6'
 
 # -- General configuration ---------------------------------------------------
 
diff --git a/docs/disclaimer.rst b/docs/disclaimer.rst
index 1a642d2..f8091f3 100644
--- a/docs/disclaimer.rst
+++ b/docs/disclaimer.rst
@@ -16,11 +16,8 @@ This simulator is free and uses Eigen (optionally KLU) for performances optimiza
 independant for powersystem focus analysis. Indeed, the code provided in this package was made mainly for
 developping AI focused controlers. Some of its limitations include, but are not limited to:
 
-- it does not support multiple slack buses
-- it can only use Newton-Raphson algorithm
 - it does not enforce reactive power limits on generators
 - it does not model AC/DC converters
-- it does not model phase shifters
 - transformers have fixed tap ratio (though it can be changed at initialization of the solver)
 - shunts have fixed tap during the Newton-Raphson algorithm (though it can be changed at the initialization of the solver)
 - only powerflow ("steady state") can be performed
@@ -41,8 +38,6 @@ while still using open source software, we kindly recommend you to have a look a
 - `Dynaωo <https://github.com/dynawo/dynawo>`_ an hybrid C++/Modelica suite of simulation tools for
   power systems.
 
-
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
-
diff --git a/docs/gridmodel.rst b/docs/gridmodel.rst
index 6221c63..e22abfd 100644
--- a/docs/gridmodel.rst
+++ b/docs/gridmodel.rst
@@ -1,13 +1,114 @@
 GridModel module (doc in progress)
 ====================================
 
-TODO:
+The main class of the lightsim2grid python package is the `GridModel` class, that is a python class created
+from the the c++ `GridModel` (thanks fo pybind11).
 
-- PandaPowerConverter
-- GridModel
+This class basically represents a powergrid (what elements it is made for, their electro technical properties etc.)
 
-example on how to import, and documentation of main methods
+To create such class, for now the only way is to get it from a pandapower grid (and it does not model every elements there !)
 
+For example, you can init it like (NOT RECOMMENDED, though sometimes needed):
+
+.. code-block:: python
+
+    from lightsim2grid.gridmodel import init
+    pp_net = ...  # any pandapower grid eg. pp_net = pn.case118()
+
+    lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+A better initialization is through the :class:`lightsim2grid.LightSimBackend.LightSimBackend` class:
+
+.. code-block:: python
+
+    from lightsim2grid.gridmodel import init
+    # create a lightsim2grid "gridmodel"
+    env_name = ... # eg. "l2rpn_case14_test"
+    env = grid2op.make(env_name, backend=LightSimBackend())
+    grid_model = env.backend._grid
+
+.. warning::
+    We do not recommend to manipulate directly the :class:`lightsim2grid.gridmodel.GridModel` directy, but to use
+    it via the backend class. This is much more tested this way.
+
+.. _elements-modeled: 
+
+Elements modeled
+------------------
+
+Generators (standard)
++++++++++++++++++++++
+
+.. autoclass:: lightsim2grid.elements.DataGen
+    :members:
+    :autosummary:
+
+.. autoclass:: lightsim2grid.elements.GenInfo
+    :members:
+    :autosummary:
+
+Static Generators (more exotic)
+++++++++++++++++++++++++++++++++
+
+.. autoclass:: lightsim2grid.elements.DataSGen
+    :members:
+    :autosummary:
+
+.. autoclass:: lightsim2grid.elements.SGenInfo
+    :members:
+    :autosummary:
+
+Loads and Storage Units
+++++++++++++++++++++++++
+
+.. autoclass:: lightsim2grid.elements.DataLoad
+    :members:
+    :autosummary:
+
+.. autoclass:: lightsim2grid.elements.LoadInfo
+    :members:
+    :autosummary:
+
+Shunts
+++++++++++++++++++++++++
+
+.. autoclass:: lightsim2grid.elements.DataShunt
+    :members:
+    :autosummary:
+
+.. autoclass:: lightsim2grid.elements.ShuntInfo
+    :members:
+    :autosummary:
+
+Lines
+++++++
+
+.. autoclass:: lightsim2grid.elements.DataLine
+    :members:
+    :autosummary:
+
+.. autoclass:: lightsim2grid.elements.LineInfo
+    :members:
+    :autosummary:
+
+Transformers
++++++++++++++
+
+.. autoclass:: lightsim2grid.elements.DataTrafo
+    :members:
+    :autosummary:
+
+.. autoclass:: lightsim2grid.elements.TrafoInfo
+    :members:
+    :autosummary:
+
+
+Detailed documentation
+--------------------------
+
+.. automodule:: lightsim2grid.gridmodel
+    :members:
+    :autosummary:
 
 * :ref:`genindex`
 * :ref:`modindex`
diff --git a/docs/index.rst b/docs/index.rst
index d7ecd05..72639bf 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -17,6 +17,10 @@ As from version 0.3.0:
 - `LightSimBackend` is compatible with the "pickle" library, so you can use it with multiprocessing
   on windows, and with frameworks that requires it (for example ray rllib).
 
+As from version 0.5.3:
+
+- `lightsim2grid` is available on pypi, you can install it (on windows, macos and linux) with `pip install lighsim2grid`
+
 .. toctree::
    :maxdepth: 2
    :caption: Quickstart
@@ -24,7 +28,9 @@ As from version 0.3.0:
    quickstart
    disclaimer
    use_with_grid2op
+   benchmarks
    use_solver
+   physical_law_checker
 
 
 Technical Documentation (work in progress)
diff --git a/docs/lightsimbackend.rst b/docs/lightsimbackend.rst
index 776fef2..839bf4a 100644
--- a/docs/lightsimbackend.rst
+++ b/docs/lightsimbackend.rst
@@ -43,8 +43,10 @@ For standard grid2op environment, you can use it like:
             # the `LightSimBackend` will be used to carry out the powerflow computation instead
             # of the default grid2op `PandaPowerBackend`
 
+Detailed documentation
+--------------------------
 
-.. automodule:: lightsim2grid.LightSimBackend
+.. automodule:: lightsim2grid.lightSimBackend
     :members:
     :autosummary:
 
diff --git a/docs/physical_law_checker.rst b/docs/physical_law_checker.rst
new file mode 100644
index 0000000..d911fa1
--- /dev/null
+++ b/docs/physical_law_checker.rst
@@ -0,0 +1,55 @@
+Check if some given (complex) Voltage meets the KCL
+====================================================
+
+This module allows to check if a given vector checks the KCL (Kirchhoff Current Law) or not.
+
+It is a wrapper around the :func:`lightsim2grid.gridmodel.GridModel.check_solution` function.
+
+TODO DOC in progress !
+
+.. warning::
+    The grid2op environment is read from a `grid.json`` file. Make sure to use an environment that can be
+    loaded by lightsim2grid (default environment uses pandapower and are fully compatible)
+
+Examples
+---------
+See the section :ref:`use_with_g2op` for more information and more examples.
+
+For standard grid2op environment, you can use it like:
+
+.. code-block:: python
+
+    import grid2op
+    import numpy as np
+    from lightsim2grid import PhysicalLawChecker
+
+    # create a grid2op environment
+    env_name = "l2rpn_case14_sandbox"
+    env = grid2op.make(env_name, ...)
+
+    # create the checker
+    checker = PhysicalLawChecker(env)
+
+    # get an observation
+    obs = env.reset()
+
+    # retrieve somehow a complex voltage
+    v = np.zeros(2*env.n_sub, dtype=complex)
+    v[...] = ...  # put here the value of the complex voltage you want to get
+
+    # check if it meets the KCL (Kirchhoff's Current Law)
+    mismatch = checker.check_solution(v, obs)
+    # mistmatch has same size as v and contains the (complex) current mismatch at each bus of the grid.
+
+
+Detailed usage
+--------------------------
+
+.. automodule:: lightsim2grid.physical_law_checker
+    :members:
+    :autosummary:
+
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index c379a3a..46b4ba4 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -3,13 +3,82 @@ Getting started
 
 In this chapter we present how to install lightsim2grid.
 
-Installation
-############
+.. versionadded:: 0.5.4
+
+    lightsim2grid can be installed directly from pypi.
+
+Standard recommended installation
+------------------------------------
+
+Simply install lightsim2grid as any python package:
+
+.. code-block:: bash
+    
+    pip install lightsim2grid
+
+.. note::
+    depending on your settings, OS, installation of python, you might want to do `python -m pip XXX`, 
+    `python3 -m pip install XXX` or `py -m pip install XXX` etc. see the documentation of `pip` for your system)
+
+
+Start Using LightSim2grid
+---------------------------
+
+The preferred way to use light2im simulator is with Grid2op. And in this case, you can simply use it
+this way:
+
+.. code-block:: python
+
+    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!
+                       )
+
+    # As of now, you can do whatever you want with this grid2op environment
+    # for example...
+
+    # 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`
+
+
+Installation from source (advanced usage)
+-------------------------------------------
+
+To install this package from source, you need, in summary, to:
+
+- clone this repository and get the code of Eigen (mandatory for compilation) and SparseSuite (optional, but recommended)
+- (optional, but recommended) compile a piece of SparseSuite
+- (optional) [experimental] retrieve and get a proper license for the NICSLU linear solver (see https://github.com/chenxm1986/nicslu)
+- (optional) specify some compilation flags to make the package run faster on your machine
+- install the package
+  
 
 .. _requirements:
 
 Requirements
-*************
+~~~~~~~~~~~~~~~~~~~~~~
+
 The requirements are:
 
 - a working compiler
@@ -18,7 +87,7 @@ The requirements are:
 - the python packages "pybind11"
 
 Install a compiler
-~~~~~~~~~~~~~~~~~~~
++++++++++++++++++++
 It relies on c++ to carry out some computations faster than pure python solvers. To integrate
 c++ into python the excellent `pybind11 <https://github.com/pybind/pybind11>`_ library is used.
 This entails that you need to have a *compiler* that can be used by pybind11 (don't hesitate to
@@ -37,14 +106,14 @@ feel free to send us a `github issue <https://github.com/BDonnot/lightsim2grid/i
 do our best to answer.
 
 Install python and git
-~~~~~~~~~~~~~~~~~~~~~~~~~
++++++++++++++++++++++++
 Once you have a compiler you need to install **python** (again we will not cover how to get python on your
 system) [because this is a python package] and **git** to install this package easily.
 
 .. _install pybind11:
 
 Install "pybind11"
-~~~~~~~~~~~~~~~~~~~~~~~~
+++++++++++++++++++++++
 As most python package you can install it using `pip`.
 
 On MacOs or Linux (Ubuntu, Fedora, etc.) you can install it from a commandline with:
@@ -67,15 +136,13 @@ or
     python3 -m pip install pybind11
 
 
-Installation
-*************
-
-This package is not (yet) available on pypi. Which means you have to install it from sources.
+Installation of the python package
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Do not forget to look at the :ref:`requirements` before proceeding in this step.
+Now that everything is setup, you can proceed with the installation of the package
 
 1. Retrieve the sources
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++++++++++++++++++++++++
 
 First, you can download it with git with:
 
@@ -93,14 +160,14 @@ First, you can download it with git with:
 
 
 Compilation of SuiteSparse (optional)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++++++++++++++++++++++++++++++++++++++++
 
 SuiteSparse comes with the faster KLU linear solver.
 
 Since version 0.3.0 this requirement has been removed. This entails
 that on linux / macos you can still benefit from the faster KLU solver. On windows you will still benefit from the
 speed up of lightsim (versus the default PandaPowerBackend) but this speed up will be less than if you manage
-to compile SuiteSparse (see the subsection [Benchmark](#benchmark) for more information).
+to compile SuiteSparse (see the subsection *Benchmark* for more information).
 
 **NB** in both cases the algorithm to compute the powerflow is exactly the same. It is a
 Newton Raphson based method. But to carry out this algorithm, one need to solver some linear equations. The only
@@ -111,6 +178,14 @@ We only detail the compilation on a system using "make" (so most likely GNU-Linu
 do this step on Windows, you can continue (and let us know!). If you don't feel confortable with this, we
 provided a docker version. See the next section for more information.
 
+(optional) option A. Compilation of SuiteSparse using "make"
+******************************************************************
+
+This is the easiest method to compile SuiteSparse on your system but unfortunately it only works on OS where "make" is
+available (*eg* Linux or MacOS) but this will not work on Windows... The compilation on windows is covered in the next
+paragraph *(optional) option B. Compilation of SuiteSparse using "cmake"* bellow.
+
+Anyway, in this case, it's super easy. Just do:
 
 .. code-block:: bash
 
@@ -119,8 +194,93 @@ provided a docker version. See the next section for more information.
 
 And yes that is it :-)
 
+(optional) option B. Compilation of SuiteSparse using "cmake"
+******************************************************************
+This works on most platform including MacOS, Linux and Windows.
+
+It requires to install the free `cmake` program and to do a bit more works than for other system. This is why we
+only recommend to use it on Windows.
+
+The main steps (for windows, somme commands needs to be adapted on linux / macos) are:
+1) `cd build_cmake`
+2) `py generate_c_files.py`
+3) `mkdir build` and cd there: `cd build`
+4) `cmake -DCMAKE_INSTALL_PREFIX=..\built -DCMAKE_BUILD_TYPE=Release ..`
+5) `cmake --build . --config Release`
+6) `cmake --build . --config Release --target install`
+
+For more information, feel free to read the dedicated [README](build_cmake/README.md).
+
+(optional) Include NICSLU linear solver (experimental)
+******************************************************************
+
+Another linear solver that can be used with lighsim2grid is the "NICSLU" linear solver that might, in some cases, be
+even faster than the KLU linear solver. This can lead to more speed up if using lighsim2grid.
+
+To use it, you need to:
+
+1) retrieve the sources (only available as a freeware) from https://github.com/chenxm1986/nicslu and save
+   it on your machine. Say you clone this github repository in `NICSLU_GIT` 
+   (*eg* NICSLU_GIT="/home/user/Documents/nicslu/"). Also note that you need to check that your usage
+   is compliant with their license !
+2) define the "PATH_NICSLU" environment variable **before** compiling lightsim2grid, on linux you can do
+   `export PATH_NICSLU=NICSLU_GIT/nicsluDATE` 
+   (for example `export PATH_NICSLU=/home/user/Documents/nicslu/nicslu202103` if you cloned the repository 
+   as the example of `step 1)` and use the version of nicslu compiled by the author on March 2021 [version 
+   distributed at time of writing the readme] )
+
+And this is it. Lightsim will be able to use this linear solver.
+
+Be carefull though, you require a license file in order to use it. As of now, the best way is to copy paste the license
+file at the same location that the one you execute python from (*ie* you need to copy paste it each time).
+
+.. _quickstart_install_custom:
+
+(optional) customization of the installation
+******************************************************************
+
+If you bother to compile from source the package, you might also want to benefit from some
+extra speed ups.
+
+This can be achieve by specifying the `__O3_OPTIM` and `__COMPILE_MARCHNATIVE` environment variables. 
+
+The first one will compile the package using the `-O3` compiler flag (`/O2` on windows) which will tell the compiler to optimize the code for speed even more.
+
+The second one will compile the package using the `-march=native` flag (on macos and linux)
+
+And example to do such things on a linux based machine is:
+
+.. code-block:: bash
+
+    export __O3_OPTIM=1
+    export __COMPILE_MARCHNATIVE=1
+
+On windows, the equivalent is either (*eg* in the good old fashion "cmd"):
+
+.. code-block:: none
+
+    setx __O3_OPTIM "1"
+    setx __COMPILE_MARCHNATIVE "1"
+
+or (*eg* in the "new" powershell):
+
+.. code-block:: none
+
+    $Env:__O3_OPTIM="1"
+    $Env:__COMPILE_MARCHNATIVE="1"
+
+If you want to disable them, you simply need to set their respective value to "0" instead of "1".
+
+.. note::
+    By default, on pypi, the packages are built with the `__O3_OPTIM` flag.
+
+.. warning::
+    If you use the `__COMPILE_MARCHNATIVE` flag, the python package you generate might not 
+    work on other installation: you cannot reuse it on other computer, or on docker etc.
+
 2. Installation of the python package
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
++++++++++++++++++++++++++++++++++++++++
+
 Now you simply need to install the lightsim2grid package this way, like any python package:
 
 .. code-block:: bash
@@ -132,44 +292,191 @@ Now you simply need to install the lightsim2grid package this way, like any pyth
 command to invoke python may vary. You may need to replace `python3` with `python`, `py` or `py3` for
 example.
 
+.. note::
+    We remind you that you can customize the installation, see more details in :ref:`quickstart_install_custom`
+
 And you are done :-)
 
-Start Using LightSim2grid
-############################
-The preferred way to use light2im simulator is with Grid2op. And in this case, you can simply use it
-this way:
+Usage with docker
+-------------------------------------------
 
-.. code-block:: python
+In this section we cover the use of docker with grid2op.
 
-    import grid2op
-    from lightsim2grid.LightSimBackend import LightSimBackend
-    from grid2op.Agent import RandomAgent
+1. Install docker
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+First, you need to install docker. You can consult the 
+`docker on windows <https://hub.docker.com/editions/community/docker-ce-desktop-windows>`_ if you use a windows like
+operating system, if you are using MacOs you can consult 
+`docker on Mac <https://hub.docker.com/editions/community/docker-ce-desktop-mac/>`_ . The installation of docker on linux
+depends on your linux distribution, we will not list them all here.
 
-    # 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!
-                       )
+2. Get the lightsim2grid image
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    # create an agent
-    my_agent = RandomAgent(env.action_space)
+Once done, you can simply "install" the lightsim2grid image with:
 
-    # 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`
+.. code-block:: bash
+
+    docker pull bdonnot/lightsim2grid:latest
+
+
+This step should be done only once (unless you delete the image) it will download approximately 4 or 5GB from the
+internet. The lightsim2grid image contains lightsim and grid2op python packages (as well as their
+dependencies), equivalent of what would be installed if you typed:
+.. code-block:: bash
+
+    pip install -U grid2op[optional] pybind11
+    # and do steps detailed in section "Installation (from source)"
+    # that we will not repeat
+
+
+3. Run a code on this container
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can skip this section if you know how to use docker. We will present here "the simplest way" to use. This is NOT
+a tutorial on docker, and you can find better use of this technology on 
+`the docker website <https://www.docker.com/get-started>`_ .
+
+For this tutorial, we suppose you have a script named `my_script.py` located in the directory (complete path) 
+`DIR_PATH` (*e.g.* on windows you can have `DIR_PATH` looking like "c:\\User\\MyName\\L2RPNCompeitionCode" or 
+on Linux `DIR_PATH` will look like "/home/MyName/L2RPNCompeitionCode", this path is your choice, you can name it
+the way you like)
+
+3.1) Start a docker container
++++++++++++++++++++++++++++++++++++++++
+
+You first need to start a docker container and tell docker that the container can access your local files with the 
+following command:
+
+.. code-block:: bash
+
+    docker run -t -d -p 8888:8888 --name lightsim_container -v DIR_PATH:/L2RPNCompeitionCode -w /L2RPNCompeitionCode bdonnot/lightsim2grid
+
+More information on this command 
+`in the official docker documentation <https://docs.docker.com/engine/reference/commandline/run/>`_
+
+After this call you can check everything went smoothly with by invoking:
+
+.. code-block:: bash
+
+    docker ps
+
+And the results should look like::
+
+    CONTAINER ID        IMAGE                   COMMAND             CREATED             STATUS              PORTS               NAMES
+    89750964ca55        bdonnot/lightsim2grid   "python3"           5 seconds ago       Up 4 seconds        80/tcp              lightsim_container
+
+
+`DIR_PATH` should be replaced by the path on which you are working, see again the introduction of this
+section for more information, in the example above this can look like:
+
+.. code-block:: bash
+
+    docker run -t -d -p 8888:8888 --name lightsim_container -v /home/MyName/L2RPNCompeitionCode:/L2RPNCompeitionCode -w /L2RPNCompeitionCode bdonnot/lightsim2grid
+
+
+3.2) Execute your code on this container
++++++++++++++++++++++++++++++++++++++++++
+
+Once everything is set-up you can execute anything you want on this container. Note that doing so, the execution
+of the code will be totally independant of your system. Only the things located in `DIR_PATH` will be visible 
+by your script, only the python package installed in the container will be usable, only the python interpreter
+of the containter (python 3.6 at time of writing) will be usable etc.
+
+.. code-block:: bash
+
+    docker exec lightsim_container python my_script.py
+
+
+Of course, the "my_script.py" should save its output somewhere on the hard drive.
+
+If you rather want to execute a python REPL (read-eval-print loop), corresponding to the "interactive python 
+interpreter", you can run this command:
+
+.. code-block:: bash
+
+    docker exec -it lightsim_container python
+
+
+We also added the possibility to run jupyter notebook from this container. To do so, you can run the command:
+
+.. code-block:: bash
+
+    docker exec -it lightsim_container jupyter notebook --port=8888 --no-browser --ip='*' --allow-root
+
+More information is provided in the official documentation of 
+`docker exec  <https://docs.docker.com/engine/reference/commandline/exec/>`_.
+
+3.3) Disclaimer
++++++++++++++++++++++++++++++++++++++++
+
+Usually, docker run as root on your machine, be careful, you can do irreversible things with it. "A great power 
+comes with a great responsibility".
+
+Also, we recall that we presented a really short introduction to docker and its possibility. We have not implied
+that this was enough, nor explain (on purpose, to make this short) any of the commands. 
+We strongly encourage you to have a look for yourself. 
+
+We want to recall the paragraph `7. Limitation of Liability` under which lightsim2grid, and this "tutorial" 
+is distributed
+
+
+.. note:: 
+    
+    Under no circumstances and under no legal 
+    theory, whether tort (including negligence), 
+    contract, or otherwise, shall any Contributor, or 
+    anyone who distributes Covered Software as 
+    permitted above, be liable to You for any direct, 
+    indirect, special, incidental, or consequential 
+    damages of any character including, without 
+    limitation, damages for lost profits, loss of 
+    goodwill, work stoppage, **computer failure** or
+    **malfunction**, or any and all other commercial 
+    damages or losses, even if such party shall have 
+    been informed of the possibility of such damages.
+
+1. Clean-up
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Once you are done with your experiments, you can stop the docker container:
+
+.. code-block:: bash
+
+    docker container stop lightsim_container
+
+
+This will free all the CPU / GPU resources that this container will use. If you want to start it again, for another 
+experiment for example, just use the command:
+
+.. code-block:: bash
+
+    docker container start lightsim_container
+
+This will allow you to run another batch of `dcoker exec` (see `3.2) Execute your code on this container`) 
+without having to re run the container.
+
+
+If you want to go a step further, you can also delete the container with the command:
+
+.. code-block:: bash
+
+    docker container rm lightsim_container
+
+This will remove the container, and all your code executed there, the history of commands etc. If you want to use
+lightsim2grid with docker again you will have to go through section `3. Run a code on this container` all over
+again.
+
+And if you also want to remove the image, you can do:
+
+.. code-block:: bash
+
+    docker rmi bdonnot/lightsim2grid 
+
+**NB** this last command will completely erase the lightsim2grid image from your machine. This means that 
+if you want to use it again, you will have to download it again (see section `2. Get the lightsim2grid image`)
 
+Finally, you can see the official documentation in case you need to uninstall docker completely from your system.
 
 * :ref:`genindex`
 * :ref:`modindex`
diff --git a/docs/security_analysis.rst b/docs/security_analysis.rst
index af0253b..f86e176 100644
--- a/docs/security_analysis.rst
+++ b/docs/security_analysis.rst
@@ -6,14 +6,19 @@ basic features.
 
 If you are interested in collaborating to improve this section, let us know.
 
+.. warning::
+    This function might give wrong result for lightsim2grid version 0.5.5 were they were a bug : when some contingencies made the grid
+    non connex, it made all the other contingencies diverge. This bug has been fixed in version 0.6.0 and this is why we **do not recommend**
+    to use this feature with lightsim2grid version < 0.6.0 !
+    
 Goal
-##############################
+-----------------
 
 This class aims to make faster (and easier) the computations of a security analysis (which is the results of some 
 powerflow after the disconnection of one or more powerlines)
 
 This function is much (much) faster than its pure grid2op counterpart. For example,
-on the case 118, to simulate all n-1 contingencies you can expect a **~100x** speed ups 
+on the case 118, to simulate all n-1 contingencies you can expect a **~20x** speed ups 
 compared to using the grid2op `obs.simulate(..., time_step=0)` while obtaining the
 exact same results (see section `Benchmarks`)
 
@@ -43,19 +48,21 @@ to / from other data sources.
     A more advanced usage is given in the `examples\\security_analysis.py` 
     file from the lightsim2grid package.
 
+.. _sa_benchmarks:
+
 Benchmarks
-************
+-----------------
 
 Here are some benchmarks made with:
 
-- system: Linux 5.11.0-38-generic
+- system: Linux 5.11.0-40-generic
 - OS: ubuntu 20.04
 - processor: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
 - python version: 3.8.10.final.0 (64 bit)
 - numpy version: 1.18.5
 - pandas version: 1.1.4
-- pandapower version: 2.6.0
-- lightsim2grid version: 0.5.5
+- pandapower version: 2.7.0
+- lightsim2grid version: 0.6.0
 - grid2op version: 1.6.4
 
 Where lightsim2grid has been installed from source with all optimization enabled.
@@ -72,31 +79,32 @@ For this setting the outputs are:
 
 .. code-block:: bash
 
-    For environment: l2rpn_neurips_2020_track2_small (186 n-1 simulated)
-    Total time spent in "computer" to solve everything: 2.8ms (66375 pf / s), 0.02 ms / pf)
+    For environment: l2rpn_neurips_2020_track2_small (177 n-1 simulated)
+    Total time spent in "computer" to solve everything: 18.5ms (9573 pf / s), 0.10 ms / pf)
         - time to compute the coefficients to simulate line disconnection: 0.04ms
-        - time to pre process Ybus: 2.00ms
-        - time to perform powerflows: 0.73ms (256458 pf / s, 0.00 ms / pf)
-    In addition, it took 0.67 ms to retrieve the current from the complex voltages (in total 53578.7 pf /s, 0.02 ms / pf)
+        - time to pre process Ybus: 2.50ms
+        - time to perform powerflows: 15.84ms (11172 pf / s, 0.09 ms / pf)
+    In addition, it took 0.83 ms to retrieve the current from the complex voltages (in total 9160.4 pf /s, 0.11 ms / pf)
+
     Comparison with raw grid2op timings
-    It took grid2op: 0.41s to perform the same computation
-    This is a 116.7 speed up from SecurityAnalysis over raw grid2op (using obs.simulate)
+    It took grid2op (with lightsim2grid, using obs.simulate): 0.42s to perform the same computation
+        This is a 21.6 speed up from SecurityAnalysis over raw grid2op (using obs.simulate and lightsim2grid)
+    It took grid2op (with pandapower, using obs.simulate): 6.39s to perform the same computation
+        This is a 330.9 speed up from SecurityAnalysis over raw grid2op (using obs.simulate and pandapower)
+    All results match !
 
 
-In this case then, the `SecurityAnalysis` module is more than **100** times faster than raw grid2op (
+In this case then, the `SecurityAnalysis` module is more than **22** times faster than raw grid2op (
 with obs.simulate as a way to compute the outcome of a contingency)
 
 
 Detailed usage
-###############
-TODO examples on how to import, and documentation of main methods
-
+--------------------------
 
 .. automodule:: lightsim2grid.securityAnalysis
     :members:
     :autosummary:
 
-
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
\ No newline at end of file
diff --git a/docs/solvers.rst b/docs/solvers.rst
index c94bac5..d3f6701 100644
--- a/docs/solvers.rst
+++ b/docs/solvers.rst
@@ -7,9 +7,9 @@ basic features.
 If you are interested in collaborating to improve this section, let us know.
 
 Type of solvers available
-##########################
+--------------------------
 
-For now, lightsim2grid ships with at most 3 fully working (and tested) solvers:
+For now, lightsim2grid ships with at most TODO DOC fully working (and tested) solvers:
 
 - **LS+GS** (LightSimBackend+Gauss Seidel): the grid2op backend based on lightsim2grid that uses the "Gauss Seidel"
   solver to compute the powerflows.
@@ -17,19 +17,23 @@ For now, lightsim2grid ships with at most 3 fully working (and tested) solvers:
   variant of the "Gauss Seidel" method to compute the powerflows.
 - **LS+SLU** (Newton Raphson+SparseLU): the grid2op backend based on lightsim2grid that uses the 
   "Newton Raphson" algorithm coupled with the linear solver "SparseLU" from the
-  Eigen c++ library (available on all platform).
+  Eigen c++ library (available on all platform). This solver supports distributed slack bus.
+- **LS+SLU (single)** (Newton Raphson+SparseLU): same as above but this solver does not support distributed slack bus and
+  can thus be slightly faster.
 - **LS+KLU** (Newton Raphson+KLU): he grid2op backend based on lightsim2grid that uses the 
   "Newton Raphson" algorithm coupled with the linear solver 
-  "KLU" from the `SuiteSparse` c package implemented.
+  "KLU" from the `SuiteSparse` C package. This solver supports distributed slack bus.
+- **LS+KLU (single)** (Newton Raphson+KLU): same as above but this solver does not support distributed slack bus and
+  can thus be slightly faster.
 - **LS+NICSLU** (Newton Raphson+NICSLU): he grid2op backend based on lightsim2grid that uses the 
   "Newton Raphson" algorithm coupled with the linear solver 
   "NICSLU". [**NB** NICSLU is a free software but not open source, in order to use
-  it with lightsim2grid, you need to check section 
-  [It is required to install lightsim2grid from source for such solver and following the 
-  Readme for instructions on how to compile with such solver]
+  it with lightsim2grid, you need to install lightsim2grid from source for such solver]
+- **LS+NICSLU (single)** (Newton Raphson+NICSLU): same as above but this solver does not support distributed slack bus and
+  can thus be slightly faster.
 
 Usage
-############
+--------------------------
 In this section we briefly explain how to switch from one solver to another. An example of code using this feature
 is given in the
 `"benchmark_solvers.py" <https://github.com/BDonnot/lightsim2grid/blob/master/benchmarks/benchmark_solvers.py>`_
@@ -59,11 +63,15 @@ A concrete example of how to change the solver in this backend is:
     env_lightsim.backend.set_tol(1e-7)  # change the tolerance (smaller tolerance gives a more accurate results but takes longer to compute)
     # see the documentation of LightSimBackend for more information
 
+For the list of availbale solvers, you can consult the "enum" :class:`lightsim2grid.solver.SolverType`.
 
 
 Detailed usage
-###############
-TODO examples on how to import, and documentation of main methods
+--------------------------
+
+.. automodule:: lightsim2grid.solver
+    :members:
+    :autosummary:
 
 
 * :ref:`genindex`
diff --git a/docs/time_series.rst b/docs/time_series.rst
index c9bbe52..00ac354 100644
--- a/docs/time_series.rst
+++ b/docs/time_series.rst
@@ -7,10 +7,10 @@ basic features.
 If you are interested in collaborating to improve this section, let us know.
 
 Goal
-##############################
+--------------------------
 
 This class aims to make faster (and easier) the computations of the current flows (measured in Amps)
-at a certain side of a powerline.
+at a certain side of a powerline / transformer when the topology is not modified.
 
 It can be used as:
 
@@ -18,7 +18,7 @@ It can be used as:
 
     from lightsim2grid import TimeSerie
     import grid2op
-    from lightsim2grid.LightSimBackend import LightSimBackend
+    from lightsim2grid.lightSimBackend import LightSimBackend
 
     env_name = ...
     env = grid2op.make(env_name, backend=LightSimBackend())
@@ -28,10 +28,10 @@ It can be used as:
     As = time_series.compute_A()  # will contain the flows, in amps at each step (rows) for each powerline (column)
 
 For now this relies on grid2op, but we could imagine a version of this class that can read
-to / from other data sources.
+to / from other data sources (for now please use the more basic :class:`lightsim2grid.timeSerie.Computers` for such purpose)
 
-Importantly, this method is around 15 times faster than simulating "do nothing" with grid2op
-(see section `Benchmarks`)
+Importantly, this method is around **13x** faster than simulating "do nothing" (or "one change then nothing") with grid2op
+(see section :ref:`timeserie_benchmark` )
 
 .. note:: 
 
@@ -52,20 +52,23 @@ Importantly, this method is around 15 times faster than simulating "do nothing"
     module to achieve high efficient parrallelism. An example is provided in the
     `examples\\computers_with_grid2op_multithreading.py` file.
 
+.. _timeserie_benchmark: 
+
+.. _ts_benchmarks:
 
 Benchmarks
-************
+-----------------
 
 Here are some benchmarks made with:
 
-- system: Linux 5.11.0-38-generic
+- system: Linux 5.11.0-40-generic
 - OS: ubuntu 20.04
 - processor: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
 - python version: 3.8.10.final.0 (64 bit)
 - numpy version: 1.18.5
 - pandas version: 1.1.4
-- pandapower version: 2.6.0
-- lightsim2grid version: 0.5.5
+- pandapower version: 2.7.0
+- lightsim2grid version: 0.6.0
 - grid2op version: 1.6.4
 
 Where lightsim2grid has been installed from source with all optimization enabled.
@@ -82,22 +85,26 @@ For this setting the outputs are:
 
 .. code-block:: bash
 
-    For environment: l2rpn_neurips_2020_track2_small
-    Total time spent in "computer" to solve everything: 0.53s (15252 pf / s), 0.07 ms / pf)
-        - time to pre process the injections: 0.03s
-        - time to perform powerflows: 0.50s (16269 pf / s, 0.06 ms / pf)
-    In addition, it took 0.06 s to retrieve the current from the complex voltages (in total 13666.3 pf /s, 0.07 ms / pf)
+    For environment: l2rpn_neurips_2020_track2
+    Total time spent in "computer" to solve everything: 0.05s (12277 pf / s), 0.08 ms / pf)
+        - time to pre process the injections: 0.00s
+        - time to perform powerflows: 0.05s (12697 pf / s, 0.08 ms / pf)
+    In addition, it took 0.00 s to retrieve the current from the complex voltages (in total 11681.3 pf /s, 0.09 ms / pf)
+
     Comparison with raw grid2op timings
-    It took grid2op: 9.26s to perform the same computation
-    This is a 15.7 speed up from TimeSerie over raw grid2op
+    It took grid2op (with lightsim2grid): 0.66s to perform the same computation
+        This is a 13.4 speed up from TimeSerie over raw grid2op (lightsim2grid)
+    It took grid2op (with pandapower): 14.94s to perform the same computation
+        This is a 302.9 speed up from TimeSerie over raw grid2op (pandapower)
+    All results match !
+
 
 
 In this case then, the `TimeSerie` module is more than **15** times faster than raw grid2op.
 
 
 Detailed usage
-###############
-TODO examples on how to import, and documentation of main methods
+--------------------------
 
 .. automodule:: lightsim2grid.timeSerie
     :members:
@@ -105,4 +112,4 @@ TODO examples on how to import, and documentation of main methods
 
 * :ref:`genindex`
 * :ref:`modindex`
-* :ref:`search`
\ No newline at end of file
+* :ref:`search`
diff --git a/docs/use_solver.rst b/docs/use_solver.rst
index 30b0a90..a512e10 100644
--- a/docs/use_solver.rst
+++ b/docs/use_solver.rst
@@ -1,16 +1,27 @@
 Use as Pandapower Solver
 =========================
+
+Advanced usage
+-----------------
+
+.. versionchanged:: 0.6.0
+
+    As of version 0.6.0 lightsim2grid implements the new API of "newtonpf" required by pandapower. This means that
+    it is asked specifically to provide `ref` a vector of integer identifying the slack buses (implements a distributed
+    slack)
+
 LightSim2grid can be used as a specific implementation of the pandapower "newtonpf" function.
 
 Suppose you somehow get:
 
 - `Ybus` the admittance matrix of your powersystem, for example given by pandapower
   (will be converted to a scipy `sparse.csc_matrix` )
-- `V0` the (complex) voltage vector at each bus, for example  given by pandapower
-- `Sbus` the (complex) power absorb at each bus, for example  as given by pandapower
-- `ppci` a ppc internal pandapower test case (for compatibility with  pandapower, currently not used)
+- `V0` the (complex) voltage vector at each bus, for example given by pandapower
+- `Sbus` the (complex) power absorb at each bus, for example as given by pandapower
+- `ref` Ids of the slack buses (added in version 0.5.6 to match recent pandapower changes)
 - `pv` list of PV buses
 - `pq` list of PQ buses
+- `ppci` a ppc internal pandapower test case (or dictionary, is used to retrieve the coefficients associated to each slack bus)
 - `options` list of pandapower "options" (or dictionary with keys `max_iteration` and `tolerance_mva`)
 
 You can define replace the `newtonpf` function of `pandapower.pandapower.newtonpf` function with the following
@@ -18,11 +29,190 @@ piece of code:
 
 .. code-block:: python
 
-    from lighsim2grid.newtonpf import newtonpf
-    V, converged, iterations, J = newtonpf(Ybus, V, Sbus, pv, pq, ppci, options)
+    from lightsim2grid.newtonpf import newtonpf
+
+    # when pandapower version <= 2.7.0
+    # V, converged, iterations, J, Vm_it, Va_it = newtonpf(Ybus, Sbus, V0, pv, pq, ppci, options)
+
+    # when pandapower version > 2.7.0
+    V, converged, iterations, J, Vm_it, Va_it = newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppci, options)
+
+This function uses the KLU algorithm (or the solver provided in Eigen if KLU has not been instealld) 
+and a c++ implementation of a Newton solver for speed.
+
+.. note::
+
+  The oldest `newtonpf` function compatible with older version of pandapower (*eg* <=2.6.0) can still be accessed with
+  `from lightsim2grid.newtonpf import newtonpf_old`
+
+.. _available-powerflow-solvers: 
+
+Even more advanced usage
+--------------------------
+You can customize even more the solvers that you want to use.
+
+Lightsim2grid comes with 11 available solvers that can solver either AC or DC powerflows. We can cluster them into 3 categories.
+
+If you want to stay "relatively high level", once you have a grid model, you can change the solver using
+the "enum" of the solvers you want to use as showed bellow:
+
+.. code-block:: python
+
+    from lightsim2grid.solver import SolverType
+    # init the grid model
+    from lightsim2grid.gridmodel import init
+    pp_net = ...  # any pandapower grid
+    lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+    # change the solver used for the powerflow
+    lightsim_grid_model.change_solver(SolverType.KLUSolver)  # change the NR solver that uses KLU
+    # you can replace `SolverType.KLUSolver` by any of the 11 available solvers described bellow, 
+    # for example (and we will not write the 11...) `SolverType.KLUSolverSingleSlack`, `SolverType.SparseLUSolver` 
+    # or even `SolverType.NICSLUSolver`
+        
+All solvers can be accessed with the same API (if you want to use the raw python class, not recommended):
+
+.. code-block:: python
+
+  from lightsim2grid.solver import ASolverAvailable
+  Ybus = ...  # a csc sparse matrix (it's really important that it is a csc and not a csr !)
+  V0 = ...  # a complex vector (initial guess)
+  Sbus = ...  # a complex vector (power injected at each bus)
+  ref = ...  # a vector of integer giving the bus id of the slack buses
+  slack_weight = ...  # a vector of real number giving the weights associated to each slack bus
+  pv = ...  # a vector of integers giving the bus id of the PV bus
+  pq = ...  # a vector of integers giving the bus id of the PQ bus
+  max_it = ...  # a > 0 integer maximum number of iterations the solver is allowed to perform
+  tol = ...  # a > 0. real number giving the maximum KCL violation allowed for a all nodes
+
+  solver = ASolverAvailable()
+  converged = solver.solve(Ybus, V0, Sbus, ref, slack_weights, pv, pq, max_it, tol)
+
+  # to retrieve the voltages related information (in case converged is True)
+  solver.get_Va()  # voltage magnitude
+  solver.get_Vm()  # voltage angle
+  solver.get_V()  # complex voltage
+  # for compatible solvers
+  solver.get_J()  # see documentation of the `newton_pf` function for more information about the shape of J.
+
+  # some other usefull information
+  solver.get_nb_iter()  # return the number of iteration performed
+  solver.get_timers()  # some execution times for some function (TODO DOC)
+  sovler.get_error()  # the id of the error encountered  (TODO DOC)
+  sovler.converged()  # equal to the boolean `converged` above
+
+Be carefull, there are some constraints on the data that are not necessarily checked, and might lead to hugly crash of the
+python virtual machine at execution time. So we encourage you to check that:
+
+- tol > 0.
+- maxt_it > 0
+- Ybus is a squared sparse matrix, in CSC format (see documentation of scipy sparse for more information) **It is 
+  really important that this matrix is in CSC format**
+- `Sbus` and `V0` have the same size which corresponds to the size (number of rows or columns) of `Ybus`
+- for all node id in `ref`, `slack_weight[node id] > 0.`
+- `sum(slack_weight) = 1.` and all elements of `slack_weight` are > 0.
+- all the buses are on `ref` (for slack buses) or on `pv` (for PV buses) or on `pq` (for PQ buses)
+  [informatically, this means that the ensemble `[0, len(V0) - 1]` is included in the union `ref U pv U pq` ]
+- all buses are only in one of `ref`, `pv` and `pq` [informatically an element of `[0, len(V0) - 1]` cannot be at the 
+  same time in `pv` and `pq` or in `ref` and `pv` or in `ref` and `pq`
+- there should be at least one element in `ref` (`len(ref) > 0`)
+  
+.. warning::
+
+    Just to emphasize that if any of the condition above is not met, this can result in crash of the python
+    virtual machine without any exception thrown (segfault). 
+
+    This is why we do not recommend to use these solvers directly !
+
+
+AC solvers using Newton Raphson
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are 6 solvers in this categorie. They can in turn, be split into two main sub categories. The first one allows for a
+distributed slack bus (but can be a bit slower) as the other one does not allow for such (in case of multiple slack bus, only 
+the first one is used as a real slack bus, the other ones are converted silently to PV buses)
+
+The list is:
+
+- `KLUSolver` \*: implementation of the Newton Raphson algorithm supporting the distributed slack bus, where the 
+  fast `KLU` implementation is used to iteratively update the jacobian matrix `J`.
+- `NICSLUSolver` \*: implementation of the Newton Raphson algorithm supporting the distributed slack bus, where the 
+  fast `NICSLU` implementation is used to iteratively update the jacobian matrix `J`.
+- `SparseLUSolver`: implementation of the Newton Raphson algorithm supporting the distributed slack bus, where the 
+  Eigen default implementation is used to iteratively update the jacobian matrix `J` (instead of the faster `KLU` or `NICSLU`)
+- `KLUSolverSingleSlack` \*: implementation of the Newton Raphson algorithm only supporting single slack bus [ignores `slack_weight`, assign 
+  all elements of `ref` into `pv` except the first one], where the 
+  fast `KLU` implementation is used to iteratively update the jacobian matrix `J`
+- `NICSLUSolverSingleSlack` \*: implementation of the Newton Raphson algorithm only supporting single slack bus [ignores `slack_weight`, assign 
+  all elements of `ref` into `pv` except the first one], where the 
+  fast `NICSLU` implementation is used to iteratively update the jacobian matrix `J`.
+- `SparseLUSolverSingleSlack`: implementation of the Newton Raphson algorithm only supporting single slack bus [ignores `slack_weight`, assign 
+  all elements of `ref` into `pv` except the first one], where the 
+  Eigen default implementation is used to iteratively update the jacobian matrix `J` (instead of the faster `KLU` or `NICSLU`)
+
+You can use them as:
+
+.. code-block:: python
+
+  from lightsim2grid.solver import KLUSolver  # or any of the names above
+
+  # retrieve some Ybus, V0, etc. as above
+  solver = KLUSolver()
+  converged = solver.solve(Ybus, V0, Sbus, ref, slack_weights, pv, pq, max_it, tol)
+  # process the results as above
+
+.. note::
+  \* these 4 solvers might not be available on all platforms (`KLU` is available if installed from pypi, but not
+  necessarily when installed from source). The solvers based on `NICSLU` also requires an installation from
+  source.
+
+AC solvers using Gauss Seidel method
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There are 2 solvers in this categorie. Neither of them supports distributed slack bus [they both ignore `slack_weight` and
+assign all elements of `ref` into `pv` except the first one]. If a grid with more
+more than 1 slack bus is provided, only the first one will be used as a slack bus, the others will be considered as "PV" nodes.
+
+These solvers use the Gauss Seidel method to compute powerflows. This method will iteratively update the component
+of a bus based on the mismatch of the KCL. The "Gauss Seidel Synch" method is a custom implementation of this method
+that updates every components at once intead of updating them one by one for each iterations.
+
+The two solvers there are `GaussSeidelSolver` and `GaussSeidelSynchSolver`. Unless for some particular use case, we
+do not recommend to use them as they often are slower than the Newton Raphson based solvers above.
+
+DC solvers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is another type of solvers available in lightsim2grid, they use a DC modeling of the powergrid equation and
+are often really fast compared to full AC powerflow.
+
+The DC equations comes from the linearization of the AC equation, and solving a DC powerflow is basically equivalent to 
+inverting a sparse matrix (or said differently solving for an equation of the sort `Ybus * Theta = Sbus` - strictly speaking 
+it's not exactly this equation as we need a slack bus, for various reasons out of the scope of this documentation). 
+In the current implementation it does not uses `slack_weight` and does not model distributed slack.
+
+There are 3 solvers of this type that are different in the way they solve `Ybus * Theta = Sbus`:
+
+- `DCSolver` uses the default Eigen sparse LU implementation
+- `KLUDCSolver` uses the fast `KLU` solver
+- `NICSLUDCSolver` uses the fast `NICSLU` solver    
+
+.. code-block:: python
+
+  from lightsim2grid.solver import DCSolver  # or any of the names above
+
+  # retrieve some Ybus, V0, etc. as above
+  dc_solver = DCSolver()
+  converged = dc_solver.solve(Ybus, V0, Sbus, ref, slack_weights, pv, pq, max_it, tol)
+  # process the results as above
+
 
-This function uses the KLU algorithm and a c++ implementation of a Newton solver for speed.
+Detailed documentation
+--------------------------
 
+.. automodule:: lightsim2grid.newtonpf
+    :members:
+    :autosummary:
 
 * :ref:`genindex`
 * :ref:`modindex`
diff --git a/docs/use_with_grid2op.rst b/docs/use_with_grid2op.rst
index 161f667..66f45cf 100644
--- a/docs/use_with_grid2op.rst
+++ b/docs/use_with_grid2op.rst
@@ -1,7 +1,7 @@
 .. _use_with_g2op:
 
 Use with grid2op
-==================
+==================================================
 
 The preferred way to use light2im simulator is with Grid2op. And in this case, you can simply use it
 this way.
@@ -14,9 +14,10 @@ can use it transparently.
 is not easy to change it. If this is of any interest for you, please let us know with a
 `feature request <https://github.com/BDonnot/lightsim2grid/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=>`_
 
-*************************
+
 Regular environments
-*************************
+----------------------------------
+
 For standard grid2op environment, you can use it like:
 
 .. code-block:: python
@@ -30,6 +31,7 @@ For standard grid2op environment, you can use it like:
     env = grid2op.make(env_name,
                        backend=LightSimBackend()  # this is the only change you have to make!
                        )
+    # and use this as any grid2op environment, for example
 
     # create an agent
     my_agent = RandomAgent(env.action_space)
@@ -54,11 +56,12 @@ This also works transparently if you use the grid2op `Runner` or the grid2op `Mu
 Some working examples are given bellow (do not hesitate to consult the documentation of grid2Op
 for more information about these features `there <https://grid2op.readthedocs.io/en/latest/>`_
 
-*************************
 Runner
-*************************
-For the grid2op `Runner <https://grid2op.readthedocs.io/en/latest/runner.html>`_ you can use it as
-follow:
+-----------------
+
+For the grid2op `Runner <https://grid2op.readthedocs.io/en/latest/runner.html>`_ using lightsim2grid is
+also perfectly transparent once the environment is loaded.
+
 
 .. code-block:: python
 
@@ -78,9 +81,9 @@ follow:
     runner = Runner(**env.get_params_for_runner(), agentClass=RandomAgent)
     runner.run(nb_episode=NB_EPISODE, nb_process=NB_CORE, path_save=PATH_SAVE)
 
-*************************
 MultiProcessing
-*************************
+-----------------
+
 Finally if you want to use multi processing to get even faster, you can use the grid2op multi processing
 class transparently too (see their documentation
 `here <https://grid2op.readthedocs.io/en/latest/environment.html#grid2op.Environment.MultiEnvMultiProcess>`_)
@@ -111,192 +114,6 @@ class transparently too (see their documentation
     # and now you can use it like any other grid2op environment (almost)
     observations = multi_env.reset()
 
-Benchmarks
-************
-
-In this section we will expose some brief benchmarks about the use of lightsim2grid in the grid2op settings.
-The code to run these benchmarks are given with this package int the [benchmark](./benchmarks) folder.
-
-All of them has been run on a computer with a the following characteristics:
-
-- system: Linux 5.11.0-38-generic
-- OS: ubuntu 20.04
-- processor: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
-- python version: 3.8.10.final.0 (64 bit)
-- numpy version: 1.18.5
-- pandas version: 1.1.4
-- pandapower version: 2.6.0
-- lightsim2grid version: 0.5.5
-- grid2op version: 1.6.4
-
-
-To run the benchmark `cd` in the [benchmark](./benchmarks) folder and type:
-
-.. code-block:: bash
-
-    python3 benchmark_solvers.py --name l2rpn_case14_sandbox --no_test --number 1000
-    python3 benchmark_solvers.py --name l2rpn_neurips_2020_track2_small --no_test --number 1000
-
-(results may vary depending on the hard drive, the ram etc. and are presented here for illustration only)
-
-(we remind that these simulations correspond to simulation on one core of the CPU. Of course it is possible to
-make use of all the available cores, which would increase the number of steps that can be performed)
-
-We compare 5 different backends:
-
-- **PP**: PandaPowerBackend (default grid2op backend) which is the reference in our benchmarks (uses the numba
-  acceleration). It is our reference solver.
-- **LS+GS** (LightSimBackend+Gauss Seidel): the grid2op backend based on lightsim2grid that uses the "Gauss Seidel"
-  solver to compute the powerflows It is implemented in `GaussSeidelSolver.h`.
-- **LS+GS S** (LightSimBackend+Gauss Seidel Synchronous): the grid2op backend based on lightsim2grid that uses a
-  variant of the "Gauss Seidel" method to compute the powerflows. It is implemented in `GaussSeidelSynchSolver.h`.
-- **LS+SLU** (Newton Raphson+SparseLU): the grid2op backend based on lightsim2grid that uses the
-  "Newton Raphson" algorithm coupled with the linear solver "SparseLU" from the
-  Eigen c++ library (available on all platform) and is implemented in `SparseLUSolver.h`.
-- **LS+KLU** (Newton Raphson+KLU): he grid2op backend based on lightsim2grid that uses the
-  "Newton Raphson" algorithm coupled with the linear solver
-  "KLU" from the `SuiteSparse` c package implemented in `KLUSolver.h`.
-- **LS+NICSLU** (Newton Raphson+NICSLU): he grid2op backend based on lightsim2grid that uses the
-  "Newton Raphson" algorithm coupled with the linear solver
-  "NICSLU" implemented in
-  `[NICSLUSolver.h`. [**NB** NICSLU is a free software but not open source, in order to use
-  it with lightsim2grid, you need to check section
-  `(optional) Include NICSLU linear solver (experimental)` of the Readme file.
-  It is required to install lightsim2grid from source for such solver]
-
-All benchmarks where done with all the customization enabled. See the readme for more information.
-
-Computation time
-~~~~~~~~~~~~~~~~~~~
-
-In this first subsection we compare the computation times:
-
-- **grid2op speed** from a grid2op point of view
-  (this include the time to compute the powerflow, plus the time to modify the powergrid plus the
-  time to read back the data once the powerflow has run plus the time to update the environment and
-  the observations etc.). It is reported in "iteration per second" (`it/s`) and represents the number of grid2op "step"
-  that can be computed per second.
-- **grid2op powerflow time** corresponds to the time the solver take to perform a powerflow
-  as seen from grid2op (counting the resolution time and some time to check the validity of the results but
-  not the time to update the grid nor the grid2op environment). It is reported in milli seconds (ms).
-- **solver powerflow time** corresponds to only the time spend in the solver itself. It does not take into
-  account any of the checking, nor the reading back of the data etc. It is reported in milli seconds (ms).
-
-
-First on an environment based on the IEEE case 14 grid:
-
-================  ======================  ===================================  ============================
-case14_sandbox      grid2op speed (it/s)    grid2op 'backend.runpf' time (ms)    solver powerflow time (ms)
-================  ======================  ===================================  ============================
-PP                                  68.8                               11.3                          4.36
-LS+GS                              866                                  0.41                         0.308
-LS+GS S                            859                                  0.416                        0.314
-LS+SLU                            1090                                  0.168                        0.0632
-LS+KLU                            1160                                  0.12                         0.0188
-LS+NICSLU                         1150                                  0.121                        0.0186
-================  ======================  ===================================  ============================
-
-From a grid2op perspective, lightsim2grid allows to compute up to ~1200 steps each second on the case 14 and
-"only" 69 for the default PandaPower Backend, leading to a speed up of **~17** in this case
-(lightsim2grid is ~16 times faster than Pandapower). For such a small environment, there is no sensible
-difference in using
-KLU linear solver compared to using the SparseLU solver of Eigen (1160 vs 1090 iterations on the reported
-runs, might slightly vary across runs). KLU and NICSLU achieve almost identical performances.
-
-Then on an environment based on the IEEE case 118:
-
-=====================  ======================  ===================================  ============================
-neurips_2020_track2      grid2op speed (it/s)    grid2op 'backend.runpf' time (ms)    solver powerflow time (ms)
-=====================  ======================  ===================================  ============================
-PP                                      39.4                                13.5                           5.65
-LS+GS                                    5.12                              194                           194
-LS+GS S                                 35.2                                27.5                          27.3
-LS+SLU                                 621                                   0.775                         0.591
-LS+KLU                                 883                                   0.301                         0.12
-LS+NICSLU                              881                                   0.302                         0.121
-=====================  ======================  ===================================  ============================
-
-For an environment based on the IEEE 118, the speed up in using lightsim + KLU (LS+KLU) is **~22** time faster than
-using the default PandaPower backend. The speed up of lightsim + SparseLU is a bit lower, but it is still **~16**
-times faster than using the default backend [the `LS+KLU` solver is ~4-5 times faster than the `LS+SLU` solver 
-(`0.12` ms per powerflow for `L2+KLU`  compared to `0.59` ms for `LS+SLU`), but it only translates to `LS+KLU` 
-providing ~30-40% more
-iterations per second in the total program (`880` vs `650`) mainly because grid2op itself takes some times to modify the
-grid and performs all the check it does.] For this testcase once again there is no noticeable difference between
-`NICSLU` and `KLU`.
-
-If we look now only at the time to compute one powerflow (and don't take into account the time to load the data, to
-initialize the solver, to modify the grid, read back the results, to perform the other update in the
-grid2op environment etc.) we can notice that it takes on average (over 1000 different states) approximately **0.12ms**
-to compute a powerflow with the LightSimBackend (if using the KLU linear solver) compared to the **5.6 ms** when using
-the PandaPowerBackend (speed up of **~46** times)
-
-**NB** pandapower performances heavily depends on the pandas version used, we used here a version of pandas which
-we found gave the best performances on our machine.
-
-.. note:: The "solver powerflow time" reported for pandapower is obtained by summing, over the 1000 powerflow performed
-    the `pandapower_backend._grid["_ppc"]["et"]` (the "estimated time" of the pandapower newton raphson computation
-    with the numba accelaration enabled)
-
-    For the lightsim backend, the "solver powerflow time" corresponds to the sum of the results of
-    `gridmodel.get_computation_time()` function that, for each powerflow, returns the time spent in the solver
-    uniquely (time inside the `basesolver.compute_pf()` function. In particular it do not count the time
-    to initialize the vector V with the DC approximation)
-
-Differences
-~~~~~~~~~~~~~~~~~~~
-Using the same command, we report the maximum value of the differences between different quantities:
-
-- `aor` : the current flow (in Amps) at the origin side of each powerline
-- `gen_p` : the generators active production values
-- `gen_q`: the generators reactive production values
-
-Note that only the maximum values (of the absolute differences) across all the steps (1000 for the IEEE case 14 and
-1000 for the IEEE case 118)
-and across all the lines (or generators) is displayed.
-
-We report only the difference compared with the baseline which is pandapower (PP).
-
-Here are the results for the IEEE case 14 (max over 1000 powerflows):
-
-============================  ==============  ==============  ================
-case14_sandbox (1000 iter)      Δ aor (amps)    Δ gen_p (MW)    Δ gen_q (MVAr)
-============================  ==============  ==============  ================
-PP (ref)                            0               0                 0
-LS+GS                               0.000122        7.63e-06          7.63e-06
-LS+GS S                             0.000122        7.63e-06          7.63e-06
-LS+SLU                              0.000122        7.63e-06          7.63e-06
-LS+KLU                              0.000122        7.63e-06          7.63e-06
-LS+NICSLU                           0.000122        7.63e-06          7.63e-06
-============================  ==============  ==============  ================
-
-.. note::
-
-    Flows are here measured in amps (and not kA). The maximum difference of flows is approximately 0.1mA
-    or 1e-4 A. This difference is totally neglectible on power transportation side where the current is usually
-    around 1kA (1e3 A).
-
-Here are the results for the IEEE case 118 (max over 1000 powerflows):
-
-=================================  ==============  ==============  ================
-neurips_2020_track2 (1000 iter)      Δ aor (amps)    Δ gen_p (MW)    Δ gen_q (MVAr)
-=================================  ==============  ==============  ================
-PP (ref)                                  0              0                 0
-LS+GS                                     6.1e-05        3.81e-06          1.53e-05
-LS+GS S                                   6.1e-05        3.81e-06          1.53e-05
-LS+SLU                                    6.1e-05        0                 9.54e-07
-LS+KLU                                    6.1e-05        0                 9.54e-07
-LS+NICSLU                                 6.1e-05        0                 9.54e-07
-=================================  ==============  ==============  ================
-
-As we can see on all the tables above, the difference when using lightsim and pandapower is rather
-small, even when using a different algorithm to solve the powerflow (LS + GS corresponds to
-using Gauss Seidel as opposed to using Newton Raphson solver)
-
-When using Newton Raphson solvers, the difference in absolute values when using lightsim2grid compared
-with using PandaPowerBackend is neglectible: less than 1e-06 in all cases (and 0.00 when comparing the
-flows on the powerline for both environments).
-
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
diff --git a/examples/computers_with_grid2op.py b/examples/computers_with_grid2op.py
index f6c3c4b..fbb930a 100644
--- a/examples/computers_with_grid2op.py
+++ b/examples/computers_with_grid2op.py
@@ -16,10 +16,10 @@
 import warnings
 import numpy as np
 from lightsim2grid import LightSimBackend
-from lightsim2grid_cpp import Computers
+from lightsim2grid.timeSerie import Computers
 
-env_name = "l2rpn_neurips_2020_track2_small"
-test = False
+env_name = "l2rpn_neurips_2020_track2"
+test = True
 param = Parameters()
 param.NO_OVERFLOW_DISCONNECTION = True
 with warnings.catch_warnings():
diff --git a/examples/computers_with_grid2op_multithreading.py b/examples/computers_with_grid2op_multithreading.py
index 5fa19cb..ef9d80b 100644
--- a/examples/computers_with_grid2op_multithreading.py
+++ b/examples/computers_with_grid2op_multithreading.py
@@ -22,7 +22,7 @@
 import grid2op
 from grid2op.Parameters import Parameters
 from lightsim2grid import LightSimBackend
-from lightsim2grid_cpp import Computers
+from lightsim2grid.timeSerie import Computers
 
 NB_THREAD = 4
 ENV_NAME = "l2rpn_neurips_2020_track2_small"
diff --git a/examples/security_analysis.py b/examples/security_analysis.py
index 50a62f0..dbdd5e6 100644
--- a/examples/security_analysis.py
+++ b/examples/security_analysis.py
@@ -31,14 +31,21 @@
                                  param=param,
                                  test=test
                                  )
+    multi_mix_env_pp = grid2op.make(env_name,
+                                    # ignore the protection, that are NOT simulated
+                                    # by the TimeSerie module !
+                                    param=param,
+                                    test=test
+                                    )
 
 key_env = max([el for el in multi_mix_env.keys()])
 env = multi_mix_env[key_env]
+env_pp = multi_mix_env_pp[key_env]
 
 # Run the environment on a scenario using the TimeSerie module
 security_analysis = SecurityAnalysis(env)
 security_analysis.add_all_n1_contingencies()
-a_or, voltages = security_analysis.get_flows()
+p_or, a_or, voltages = security_analysis.get_flows()
 # the 3 lines above are the only lines you need to do to perform a security analysis !
 
 computer = security_analysis.computer
@@ -64,11 +71,25 @@
     sim_obs, sim_r, sim_d, sim_info = obs.simulate(env.action_space({"set_line_status": [(cont_id, -1)]}),
                                                    time_step=0)
 end_ = time.perf_counter()
-total_time = end_ - beg_
+total_time_glop_ls = end_ - beg_
+
+obs_pp = env_pp.get_obs()
+beg_ = time.perf_counter()
+for cont_id in range(env.n_line):
+    sim_obs, sim_r, sim_d, sim_info = obs_pp.simulate(env.action_space({"set_line_status": [(cont_id, -1)]}),
+                                                      time_step=0)
+end_ = time.perf_counter()
+total_time_glop_pp = end_ - beg_
+
+full_time_sa = computer.total_time() + computer.amps_computation_time()
+print()
 print("Comparison with raw grid2op timings")
-print(f"It took grid2op: {end_ - beg_:.2f}s to perform the same computation")
-print(f"This is a {(end_ - beg_) / ( computer.total_time() + computer.amps_computation_time()) :.1f} "
-      f"speed up from SecurityAnalysis over raw grid2op (using obs.simulate)")
+print(f"It took grid2op (with lightsim2grid, using obs.simulate): {total_time_glop_ls:.2f}s to perform the same computation")
+print(f"\t This is a {(total_time_glop_ls) / (full_time_sa) :.1f} "
+      f"speed up from SecurityAnalysis over raw grid2op (using obs.simulate and lightsim2grid)")
+print(f"It took grid2op (with pandapower, using obs.simulate): {total_time_glop_pp:.2f}s to perform the same computation")
+print(f"\t This is a {(total_time_glop_pp) / (full_time_sa) :.1f} "
+      f"speed up from SecurityAnalysis over raw grid2op (using obs.simulate and pandapower)")
 
 
 #### Check that the results match
@@ -77,7 +98,15 @@
     sim_obs, sim_r, sim_d, sim_info = obs.simulate(action,
                                                    time_step=0)
     if not sim_d:
+        # simulation converged
+        assert np.all(np.isfinite(a_or[cont_id,:])), f"amps should not be Nan for cont {cont_id} (converged)"
         if np.max(np.abs(sim_obs.a_or - a_or[cont_id,:])) > 1e-4:
             raise RuntimeError(f"wrong amps for contingency {cont_id}")
+        if np.max(np.abs(sim_obs.p_or - p_or[cont_id,:])) > 1e-4:
+            raise RuntimeError(f"wrong active power for contingency {cont_id}")
     else:
-        assert np.all(~np.isfinite(a_or[cont_id,:])), f"amps should be Nan for cont {cont_id}"
+        # simulation diverged
+        assert np.all(~np.isfinite(a_or[cont_id,:])), f"amps should be Nan for cont {cont_id} (diverged)"
+        assert np.all(np.abs(p_or[cont_id,:]) <= 1e-6), f"active power should be 0. for cont {cont_id}"
+
+print("All results match !")
diff --git a/examples/time_serie.py b/examples/time_serie.py
index 4b37cba..8d47ba8 100755
--- a/examples/time_serie.py
+++ b/examples/time_serie.py
@@ -1,4 +1,4 @@
-#!/bin/python3
+# #!/bin/python3
 
 # Copyright (c) 2020, RTE (https://www.rte-france.com)
 # See AUTHORS.txt
@@ -20,8 +20,8 @@
 import warnings
 from lightsim2grid import LightSimBackend, TimeSerie
 
-env_name = "l2rpn_neurips_2020_track2_small"
-test = False
+env_name = "l2rpn_neurips_2020_track2"  # use "l2rpn_neurips_2020_track2_small" if `test=True`
+test = True
 scenario_id = 1
 
 # Create the grid2op environment
@@ -39,13 +39,25 @@
                                  # by the TimeSerie module !
                                  data_feeding_kwargs={"gridvalueClass": GridStateFromFileWithForecastsWithoutMaintenance}
                                  )
+
+    multi_mix_env_pp = grid2op.make(env_name,
+                                    # ignore the protection, that are NOT simulated
+                                    # by the TimeSerie module !
+                                    param=param,
+                                    test=test,
+                                    # ignore the maintenance, that are NOT simulated
+                                    # by the TimeSerie module !
+                                    data_feeding_kwargs={"gridvalueClass": GridStateFromFileWithForecastsWithoutMaintenance}
+                                    )
 key_env = max([el for el in multi_mix_env.keys()])
 env = multi_mix_env[key_env]
+env_pp = multi_mix_env_pp[key_env]
 
 # Run the environment on a scenario using the TimeSerie module
 time_serie = TimeSerie(env)
 time_serie.compute_V(scenario_id=scenario_id)
 a_or = time_serie.compute_A()
+p_or = time_serie.compute_P()
 computer = time_serie.computer
 
 print(f"For environment: {env_name}")
@@ -70,9 +82,24 @@
 while not done:
       *_, done, info = env.step(env.action_space())
 end_ = time.perf_counter()
+total_time_glop_ls = end_ - beg_
+
+env_pp.set_id(scenario_id)
+_ = env_pp.reset()
+beg_ = time.perf_counter()
+done = False
+while not done:
+      *_, done, info = env_pp.step(env.action_space())
+end_ = time.perf_counter()
+total_time_glop_pp = end_ - beg_
+
+full_time_ts =  computer.total_time() + computer.amps_computation_time()
+print()
 print("Comparison with raw grid2op timings")
-print(f"It took grid2op: {end_ - beg_:.2f}s to perform the same computation")
-print(f"This is a {(end_ - beg_) / ( computer.total_time() + computer.amps_computation_time()) :.1f} speed up from TimeSerie over raw grid2op")
+print(f"It took grid2op (with lightsim2grid): {total_time_glop_ls:.2f}s to perform the same computation")
+print(f"\t This is a {(total_time_glop_ls) / (full_time_ts) :.1f} speed up from TimeSerie over raw grid2op (lightsim2grid)")
+print(f"It took grid2op (with pandapower): {total_time_glop_pp:.2f}s to perform the same computation")
+print(f"\t This is a {(total_time_glop_pp) / (full_time_ts) :.1f} speed up from TimeSerie over raw grid2op (pandapower)")
 
 #### Check that the results matches
 env.set_id(scenario_id)
@@ -82,5 +109,9 @@
 done = False
 while not done:
       obs, reward, done, info = env.step(env.action_space())
+      assert np.all(np.isfinite(a_or[ts])), f"non finite value for the amps"
+      assert np.all(np.isfinite(p_or[ts])), f"non finite value for the active power"
       assert np.max(np.abs(obs.a_or - a_or[ts])) <= 1e-4, f"wrong amps at step {ts}"
-      ts += 1
\ No newline at end of file
+      assert np.max(np.abs(obs.p_or - p_or[ts])) <= 1e-4, f"wrong active power at step {ts}"
+      ts += 1
+print("All results match !")
\ No newline at end of file
diff --git a/lightsim2grid/__init__.py b/lightsim2grid/__init__.py
index 110d2be..ce59084 100644
--- a/lightsim2grid/__init__.py
+++ b/lightsim2grid/__init__.py
@@ -1,14 +1,21 @@
-__version__ = "0.5.5"
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
-__all__ = ["newtonpf", "SolverType", "solver",
-          # for documentation
-          "timeSerie", "securityAnalysis"]
+__version__ = "0.6.0"
+
+__all__ = ["newtonpf", "SolverType", "ErrorType", "solver"]
 
 # import directly from c++ module
 from lightsim2grid.solver import SolverType
+from lightsim2grid.solver import ErrorType
 
 try:
-    from lightsim2grid.LightSimBackend import LightSimBackend
+    from lightsim2grid.lightSimBackend import LightSimBackend
     __all__.append("LightSimBackend")
 except ImportError:
     # grid2op is not installed, the Backend will not be available
@@ -24,6 +31,7 @@
 try:
     from lightsim2grid.timeSerie import TimeSerie
     __all__.append("TimeSerie")
+    __all__.append("timeSerie")
 except ImportError as exc_:
     # grid2op is not installed, the TimeSeries module will not be available
     pass
@@ -31,6 +39,7 @@
 try:
     from lightsim2grid.securityAnalysis import SecurityAnalysis
     __all__.append("SecurityAnalysis")
+    __all__.append("securityAnalysis")
 except ImportError as exc_:
     # grid2op is not installed, the SecurtiyAnalysis module will not be available
     pass
diff --git a/lightsim2grid/_aux_add_slack.py b/lightsim2grid/_aux_add_slack.py
deleted file mode 100644
index bd079e9..0000000
--- a/lightsim2grid/_aux_add_slack.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (c) 2020, RTE (https://www.rte-france.com)
-# See AUTHORS.txt
-# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
-# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
-# you can obtain one at http://mozilla.org/MPL/2.0/.
-# SPDX-License-Identifier: MPL-2.0
-# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-import warnings
-import numpy as np
-
-
-def _aux_add_slack(model, pp_net):
-    """
-    add the slack bus(es) to the lightsim2grid "model" based on the information in the pandapower network.
-
-    Notes
-    -----
-    For now only single slack bus are supported in lightsim2grid. A warning will be issue if the pandapower
-    network has a distributed slack bus.
-
-    Parameters
-    ----------
-    model
-    pp_net
-
-    Returns
-    -------
-
-    """
-    # TODO handle that better maybe, and warn only one slack bus is implemented
-    if np.any(pp_net.gen["slack"].values):
-        if np.sum(pp_net.gen["slack"].values) >= 2:
-            warnings.warn("LightSim cannot handle multiple slack bus at the moment. Only the first "
-                          "slack bus of pandapower will be used.")
-        slack_gen_id = np.where(pp_net.gen["slack"].values)[0]
-        model.change_v_gen(slack_gen_id, pp_net.gen["vm_pu"][slack_gen_id])
-    else:
-        # there is no slack bus in the generator of the pp grid
-
-        # first i try to see if a generator is connected to a slack bus
-        slack_bus_id = pp_net.ext_grid["bus"].values[0]
-        if pp_net.ext_grid.shape[0] >= 2:
-            warnings.warn("LightSim cannot handle multiple slack bus at the moment. Only the first "
-                          "slack bus of pandapower will be used.")
-
-        if np.any(pp_net.gen["bus"].values == slack_bus_id):
-            slack_gen_id = np.where(pp_net.gen["bus"].values == slack_bus_id)[0]
-        else:
-            # no gen is connected to a slack bus, so i create one.
-            gen_p = np.concatenate((pp_net.gen["p_mw"].values, [np.sum(pp_net.load["p_mw"]) - np.sum(pp_net.gen["p_mw"])]))
-            gen_v = np.concatenate((pp_net.gen["vm_pu"].values, [pp_net.ext_grid["vm_pu"].values[0]]))
-            gen_bus = np.concatenate((pp_net.gen["bus"].values, [slack_bus_id]))
-            gen_min_q = np.concatenate((pp_net.gen["min_q_mvar"].values, [-999999.]))
-            gen_max_q = np.concatenate((pp_net.gen["max_q_mvar"].values, [+99999.]))
-            model.init_generators(gen_p, gen_v, gen_min_q, gen_max_q, gen_bus)
-            slack_gen_id = pp_net.gen["bus"].shape[0]
-
-    model.add_gen_slackbus(slack_gen_id)
-
diff --git a/lightsim2grid/elements/__init__.py b/lightsim2grid/elements/__init__.py
new file mode 100644
index 0000000..7e795ee
--- /dev/null
+++ b/lightsim2grid/elements/__init__.py
@@ -0,0 +1,34 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+__all__ = ["DataGen",
+           "GenInfo",
+           "DataSGen",
+           "SGenInfo",
+           "DataLoad",
+           "LoadInfo",
+           "DataShunt",
+           "ShuntInfo",
+           "DataTrafo",
+           "TrafoInfo",
+           "DataLine",
+           "LineInfo",
+           ]
+
+from lightsim2grid_cpp import DataGen
+from lightsim2grid_cpp import GenInfo
+from lightsim2grid_cpp import DataSGen
+from lightsim2grid_cpp import SGenInfo
+from lightsim2grid_cpp import DataLoad
+from lightsim2grid_cpp import LoadInfo
+from lightsim2grid_cpp import DataShunt
+from lightsim2grid_cpp import ShuntInfo
+from lightsim2grid_cpp import DataTrafo
+from lightsim2grid_cpp import TrafoInfo
+from lightsim2grid_cpp import DataLine
+from lightsim2grid_cpp import LineInfo
diff --git a/lightsim2grid/gridmodel/__init__.py b/lightsim2grid/gridmodel/__init__.py
new file mode 100644
index 0000000..e6900e8
--- /dev/null
+++ b/lightsim2grid/gridmodel/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+__all__ = ["init", "GridModel"]
+
+from lightsim2grid.gridmodel.initGridModel import init, GridModel
diff --git a/lightsim2grid/_aux_add_gen.py b/lightsim2grid/gridmodel/_aux_add_gen.py
similarity index 98%
rename from lightsim2grid/_aux_add_gen.py
rename to lightsim2grid/gridmodel/_aux_add_gen.py
index 256f155..2590b4a 100644
--- a/lightsim2grid/_aux_add_gen.py
+++ b/lightsim2grid/gridmodel/_aux_add_gen.py
@@ -17,9 +17,6 @@ def _aux_add_gen(model, pp_net):
     model
     pp_net
 
-    Returns
-    -------
-
     """
     if "parallel" in pp_net.gen and np.any(pp_net.gen["parallel"].values != 1):
         raise RuntimeError("Cannot handle 'parallel' gen columns. Please duplicate the rows if that is the case. "
diff --git a/lightsim2grid/_aux_add_line.py b/lightsim2grid/gridmodel/_aux_add_line.py
similarity index 100%
rename from lightsim2grid/_aux_add_line.py
rename to lightsim2grid/gridmodel/_aux_add_line.py
diff --git a/lightsim2grid/_aux_add_load.py b/lightsim2grid/gridmodel/_aux_add_load.py
similarity index 100%
rename from lightsim2grid/_aux_add_load.py
rename to lightsim2grid/gridmodel/_aux_add_load.py
diff --git a/lightsim2grid/_aux_add_sgen.py b/lightsim2grid/gridmodel/_aux_add_sgen.py
similarity index 100%
rename from lightsim2grid/_aux_add_sgen.py
rename to lightsim2grid/gridmodel/_aux_add_sgen.py
diff --git a/lightsim2grid/_aux_add_shunt.py b/lightsim2grid/gridmodel/_aux_add_shunt.py
similarity index 100%
rename from lightsim2grid/_aux_add_shunt.py
rename to lightsim2grid/gridmodel/_aux_add_shunt.py
diff --git a/lightsim2grid/gridmodel/_aux_add_slack.py b/lightsim2grid/gridmodel/_aux_add_slack.py
new file mode 100644
index 0000000..c9fdf89
--- /dev/null
+++ b/lightsim2grid/gridmodel/_aux_add_slack.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+import warnings
+import numpy as np
+
+
+def _aux_add_slack(model, pp_net):
+    """
+    add the slack bus(es) to the lightsim2grid "model" based on the information in the pandapower network.
+
+    Notes
+    -----
+    For now only single slack bus are supported in lightsim2grid. A warning will be issue if the pandapower
+    network has a distributed slack bus.
+
+    Parameters
+    ----------
+    model
+    pp_net
+
+    Returns
+    -------
+
+    """
+    # TODO handle that better maybe, and warn only one slack bus is implemented
+    slack_coeff = None
+    if np.any(pp_net.gen["slack"].values):
+        # most favorable cases
+        if np.sum(pp_net.gen["slack"].values) >= 2:
+            # TODO SLACK remove this warning ! (will be done at the end when more tests will be done)
+            warnings.warn("LightSim cannot handle multiple slack bus at the moment. Only the first "
+                          "slack bus of pandapower will be used.")
+        slack_gen_ids = np.where(pp_net.gen["slack"].values)[0]
+        if "slack_weight" in pp_net.gen:
+            slack_coeff = pp_net.gen["slack_weight"].values[slack_gen_ids]
+        for slack_id in slack_gen_ids:
+            model.change_v_gen(slack_id, pp_net.gen["vm_pu"].iloc[slack_id])
+
+        # in this case the ext grid is not taken into account, i raise a warning if
+        # there is one
+        slack_bus_ids = pp_net.ext_grid["bus"].values
+        if pp_net.ext_grid.shape[0] >= 1:
+            warnings.warn("LightSim will not consider the pandapower \"ext_grid\" as there "
+                          "are already generators tagged as slack bus")
+    else:
+        # there is no slack bus in the generator of the pp grid
+        warnings.warn("LightSim has not found any generators tagged as \"slack bus\" in the pandapower network."
+                      "I will attempt to add some from the ext_grid.")
+        # first i try to see if a generator is connected to a slack bus
+        # TODO SLACK: deactivate warnings (will be done at the end when more tests will be done)
+        slack_bus_ids = pp_net.ext_grid["bus"].values
+        if pp_net.ext_grid.shape[0] >= 2:
+            warnings.warn("LightSim cannot handle multiple slack bus at the moment. Only the first "
+                          "slack bus of pandapower will be used.")
+
+        if np.all(np.isin(slack_bus_ids, pp_net.gen["bus"].values)):
+            # all slack buses have a generator connected to them
+            # so i assume it was just a computation artifact, and assign these generators as slack buses
+            slack_gen_ids = np.isin(pp_net.gen["bus"].values, slack_bus_ids)  # id of generators connected to slack bus
+            slack_gen_ids = np.where(slack_gen_ids)[0]  # keep only the id of the generators
+            if "slack_weight" in pp_net.gen:
+                slack_coeff = pp_net.gen["slack_weight"].values[slack_gen_ids]
+        else:
+            # at least one slack bus has no generator connected to it
+            # so I assume i need to add as many generators as number of slack bus
+            nb_slack = len(slack_bus_ids)
+            if "slack_weight" in pp_net.ext_grid:
+                slack_coeff = 1.0 * pp_net.ext_grid["slack_weight"].values
+            else:
+                slack_coeff = np.ones(nb_slack)
+            slack_coeff_norm = slack_coeff / slack_coeff.sum()
+            slack_gen_ids = np.arange(nb_slack) + pp_net.gen.shape[0]
+            slack_contrib = (np.sum(pp_net.gen["p_mw"]) - np.sum(pp_net.load["p_mw"]) ) * slack_coeff_norm
+            vm_pu = 1.0 * pp_net.ext_grid["vm_pu"].values
+            gen_p = np.concatenate((pp_net.gen["p_mw"].values, slack_contrib))
+            gen_v = np.concatenate((pp_net.gen["vm_pu"].values, vm_pu))
+            gen_bus = np.concatenate((pp_net.gen["bus"].values, slack_bus_ids))
+            gen_min_q = np.concatenate((pp_net.gen["min_q_mvar"].values, [-999999.]))
+            gen_max_q = np.concatenate((pp_net.gen["max_q_mvar"].values, [+99999.]))
+            model.init_generators(gen_p, gen_v, gen_min_q, gen_max_q, gen_bus)
+            slack_gen_ids = [pp_net.gen["bus"].shape[0]]
+
+    # handle the possible distributed slack bus
+    if slack_coeff is None:
+        slack_coeff = np.ones(len(slack_gen_ids))
+
+    if np.sum(slack_coeff) == 0. or np.any(slack_coeff < 0.):
+        warnings.warn("We found either some slack coefficient to be < 0. or they were all 0."
+                      "We set them all to 1.0 to avoid such issues")
+        slack_coeff[:] = 1.
+
+    for gid, slack_gen_id in enumerate(slack_gen_ids):
+        model.add_gen_slackbus(slack_gen_id, slack_coeff[gid])
diff --git a/lightsim2grid/_aux_add_storage.py b/lightsim2grid/gridmodel/_aux_add_storage.py
similarity index 100%
rename from lightsim2grid/_aux_add_storage.py
rename to lightsim2grid/gridmodel/_aux_add_storage.py
diff --git a/lightsim2grid/_aux_add_trafo.py b/lightsim2grid/gridmodel/_aux_add_trafo.py
similarity index 100%
rename from lightsim2grid/_aux_add_trafo.py
rename to lightsim2grid/gridmodel/_aux_add_trafo.py
diff --git a/lightsim2grid/_aux_check_legit.py b/lightsim2grid/gridmodel/_aux_check_legit.py
similarity index 100%
rename from lightsim2grid/_aux_check_legit.py
rename to lightsim2grid/gridmodel/_aux_check_legit.py
diff --git a/lightsim2grid/initGridModel.py b/lightsim2grid/gridmodel/initGridModel.py
similarity index 61%
rename from lightsim2grid/initGridModel.py
rename to lightsim2grid/gridmodel/initGridModel.py
index a75a44a..ed7d860 100644
--- a/lightsim2grid/initGridModel.py
+++ b/lightsim2grid/gridmodel/initGridModel.py
@@ -7,38 +7,42 @@
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
 """
-Use the pandapower converter to properly initialized a GridModel c++ object.
+Use the pandapower converter to properly initialize a GridModel c++ object.
 """
 
+__all__ = ["init", "GridModel"]
+
 import numpy as np
+from numbers import Number
 import warnings
-from lightsim2grid_cpp import GridModel, PandaPowerConverter, SolverType
-from lightsim2grid._aux_add_sgen import _aux_add_sgen
-from lightsim2grid._aux_add_load import _aux_add_load
-from lightsim2grid._aux_add_trafo import _aux_add_trafo
-from lightsim2grid._aux_add_line import _aux_add_line
-from lightsim2grid._aux_add_gen import _aux_add_gen
-from lightsim2grid._aux_add_shunt import _aux_add_shunt
-from lightsim2grid._aux_check_legit import _aux_check_legit
-from lightsim2grid._aux_add_slack import _aux_add_slack
-from lightsim2grid._aux_add_storage import _aux_add_storage
+from lightsim2grid_cpp import GridModel, PandaPowerConverter
+from lightsim2grid.gridmodel._aux_add_sgen import _aux_add_sgen
+from lightsim2grid.gridmodel._aux_add_load import _aux_add_load
+from lightsim2grid.gridmodel._aux_add_trafo import _aux_add_trafo
+from lightsim2grid.gridmodel._aux_add_line import _aux_add_line
+from lightsim2grid.gridmodel._aux_add_gen import _aux_add_gen
+from lightsim2grid.gridmodel._aux_add_shunt import _aux_add_shunt
+from lightsim2grid.gridmodel._aux_check_legit import _aux_check_legit
+from lightsim2grid.gridmodel._aux_add_slack import _aux_add_slack
+from lightsim2grid.gridmodel._aux_add_storage import _aux_add_storage
 
 
 def init(pp_net):
     """
     Convert a pandapower network as input into a GridModel.
 
-    This does not throw any error at the moment when the conversion is not possible.
+    This can fail to convert the grid and still not throw any error, use with care (for example, you can run a powerflow
+    after this conversion, run a powerflow with pandapower, and compare the results to make sure they match !)
 
     Cases for which conversion is not possible include, but are not limited to:
 
     - the pandapower grid has 3 winding transformers
     - the pandapower grid has xwards
+    - the pandapower grid has dcline
+    - the pandapower grid has switch, motor, assymetric loads, etc.
     - the pandapower grid any parrallel "elements" (at least one of the column "parrallel" is not 1)
-    - some `g_us_per_km` for some lines are not zero
+    - some `g_us_per_km` for some lines are not zero ? TODO not sure if that is still the case !
     - some `p_mw` for some shunts are not zero
-    - some `tap_step_degre` are non zero for some trafo
-    - no "ext_grid" is reported on the initial grid
 
     if you really need any of the above, please submit a github issue and we will work on their support.
 
@@ -52,7 +56,7 @@ def init(pp_net):
 
     Returns
     -------
-    model: :class:`GridModel`
+    model: :class:`lightsim2grid.gridmodel.GridModel`
         The initialize gridmodel
 
     """
@@ -68,8 +72,9 @@ def init(pp_net):
     model = GridModel()
     if "_options" in pp_net:
         if "init_vm_pu" in pp_net["_options"]:
-            model.set_init_vm_pu(pp_net["_options"]["init_vm_pu"])
-
+            tmp_ = pp_net["_options"]["init_vm_pu"]
+            if isinstance(tmp_, Number):
+                model.set_init_vm_pu(float(tmp_))
     model.set_sn_mva(pp_net.sn_mva)
 
     tmp_bus_ind = np.argsort(pp_net.bus.index)
@@ -77,6 +82,11 @@ def init(pp_net):
                    pp_net.line.shape[0],
                    pp_net.trafo.shape[0])
 
+    # deactivate in lightsim the deactivated bus in pandapower
+    for bus_id in range(pp_net.bus.shape[0]):
+        if not pp_net.bus["in_service"].values[bus_id]:
+            model.deactivate_bus(bus_id)
+
     # init the powerlines
     _aux_add_line(converter, model, pp_net)
 
diff --git a/lightsim2grid/LightSimBackend.py b/lightsim2grid/lightSimBackend.py
similarity index 84%
rename from lightsim2grid/LightSimBackend.py
rename to lightsim2grid/lightSimBackend.py
index 03dfe4c..092e832 100644
--- a/lightsim2grid/LightSimBackend.py
+++ b/lightsim2grid/lightSimBackend.py
@@ -16,11 +16,17 @@
 from grid2op.Action._BackendAction import _BackendAction
 from grid2op.dtypes import dt_float, dt_int, dt_bool
 
-from lightsim2grid.initGridModel import init, SolverType
+from lightsim2grid.gridmodel import init
+from lightsim2grid.solver import SolverType
+
 # import lightsim2grid.solver.SolverType.DCSolver
 # from lightsim2grid.solver import DCSolver
 
 class LightSimBackend(Backend):
+    """
+    This is a specialization of the grid2op Backend class to use the lightsim2grid solver,
+    coded in c++, aiming at speeding up the computations.
+    """
     def __init__(self, detailed_infos_for_cascading_failures=False):
         Backend.__init__(self,
                          detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures)
@@ -32,7 +38,7 @@ def __init__(self, detailed_infos_for_cascading_failures=False):
         if not self.__has_storage:
             pass
             # warnings.warn("Please upgrade your grid2Op to >= 1.5.0. You are using a backward compatibility "
-            #              "feature that will be removed in further lightsim2grid version.")
+            #               "feature that will be removed in further lightsim2grid version.")
 
         self.nb_bus_total = None
         self.initdc = True  # does not really hurt computation time
@@ -83,6 +89,19 @@ def __init__(self, detailed_infos_for_cascading_failures=False):
         self.storage_q = None
         self.storage_v = None
 
+        # shunts
+        self.sh_p = None
+        self.sh_q = None
+        self.sh_v = None
+        self.sh_bus = None
+
+        # voltage angle
+        self.line_or_theta = None
+        self.line_ex_theta = None
+        self.load_theta = None
+        self.gen_theta = None
+        self.storage_theta = None
+
         self.thermal_limit_a = None
 
         self.dim_topo = -1
@@ -108,6 +127,23 @@ def __init__(self, detailed_infos_for_cascading_failures=False):
         # backend SHOULD not do these kind of stuff
         self._idx_hack_storage = []
 
+    def _fill_theta(self):
+        # line_or_theta = np.empty(self.n_line)
+        self.line_or_theta[:self.__nb_powerline] = self._grid.get_lineor_theta()
+        self.line_or_theta[self.__nb_powerline:] = self._grid.get_trafohv_theta()
+
+        # line_ex_theta = np.empty(self.n_line)
+        self.line_ex_theta[:self.__nb_powerline] = self._grid.get_lineex_theta()
+        self.line_ex_theta[self.__nb_powerline:] = self._grid.get_trafolv_theta()
+
+        # line_or_theta = np.concatenate((self._grid.get_lineor_theta(), self._grid.get_trafohv_theta()))
+        # line_ex_theta = np.concatenate((self._grid.get_lineex_theta(), self._grid.get_trafolv_theta()))
+        self.load_theta[:] = self._grid.get_load_theta()
+        self.gen_theta[:] = self._grid.get_gen_theta()
+
+        if self.__has_storage:
+            self.storage_theta[:] = self._grid.get_storage_theta()
+
     def get_theta(self):
         """
 
@@ -124,20 +160,11 @@ def get_theta(self):
         storage_theta: ``numpy.ndarray``
             Gives the voltage angle to the bus at which each storage unit is connected
         """
-        line_or_theta = np.empty(self.n_line)
-        line_or_theta[:self.__nb_powerline] = self._grid.get_lineor_theta()
-        line_or_theta[self.__nb_powerline:] = self._grid.get_trafohv_theta()
-
-        line_ex_theta = np.empty(self.n_line)
-        line_ex_theta[:self.__nb_powerline] = self._grid.get_lineex_theta()
-        line_ex_theta[self.__nb_powerline:] = self._grid.get_trafolv_theta()
-
-        # line_or_theta = np.concatenate((self._grid.get_lineor_theta(), self._grid.get_trafohv_theta()))
-        # line_ex_theta = np.concatenate((self._grid.get_lineex_theta(), self._grid.get_trafolv_theta()))
-        load_theta = self.cst_1 * self._grid.get_load_theta()
-        gen_theta = self.cst_1 * self._grid.get_gen_theta()
-        storage_theta = self.cst_1 * self._grid.get_storage_theta()
-        return line_or_theta, line_ex_theta, load_theta, gen_theta, storage_theta
+        return self.cst_1 * self.line_or_theta, \
+               self.cst_1 * self.line_ex_theta, \
+               self.cst_1 * self.load_theta, \
+               self.cst_1 * self.gen_theta, \
+               self.cst_1 * self.storage_theta
 
     def set_solver_type(self, solver_type):
         """
@@ -245,6 +272,9 @@ def load_grid(self, path=None, filename=None):
         if SolverType.KLU in self.available_solvers:
             # use the faster KLU if available
             self._grid.change_solver(SolverType.KLU)
+        if SolverType.KLUDC in self.available_solvers:
+            # use the faster KLU if available
+            self._grid.change_solver(SolverType.KLUDC)
         if self.__current_solver_type is None:
             self.__current_solver_type = copy.deepcopy(self._grid.get_solver_type())
 
@@ -390,6 +420,11 @@ def load_grid(self, path=None, filename=None):
         self.topo_vect = np.ones(self.dim_topo, dtype=dt_int)
         if self.shunts_data_available:
             self.shunt_topo_vect = np.ones(self.n_shunt, dtype=dt_int)
+             # shunts
+            self.sh_p = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN)
+            self.sh_q = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN)
+            self.sh_v = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN)
+            self.sh_bus = np.full(self.n_shunt, dtype=dt_int, fill_value=-1)
 
         self.p_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
         self.q_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
@@ -407,12 +442,18 @@ def load_grid(self, path=None, filename=None):
         self.prod_p = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
         self.prod_q = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
         self.prod_v = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
+        
+        self.line_or_theta = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
+        self.line_ex_theta = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
+        self.load_theta = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
+        self.gen_theta = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
 
         # TODO storage check grid2op version and see if storage is available !
         if self.__has_storage:
             self.storage_p = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN)
             self.storage_q = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN)
             self.storage_v = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN)
+            self.storage_theta = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN)
 
         self._count_object_per_bus()
         self._grid.tell_topo_changed()
@@ -557,7 +598,6 @@ def apply_action(self, backendAction):
     def runpf(self, is_dc=False):
         my_exc_ = None
         res = False
-        # self._grid.tell_topo_changed()  # TODO it does not work if we remove that, segfault !
         try:
             if is_dc:
                 # somehow, when asked to do a powerflow in DC, pandapower assign Vm to be
@@ -565,9 +605,8 @@ def runpf(self, is_dc=False):
                 # But not when it initializes in DC mode... (see below)
                 self.V = np.ones(self.nb_bus_total, dtype=np.complex_) #  * self._grid.get_init_vm_pu()
                 V = self._grid.dc_pf(self.V, self.max_it, self.tol)
-                self._grid.change_solver(SolverType.DC)
                 if V.shape[0] == 0:
-                    raise DivergingPowerFlow("Divergence of powerflow (non connected grid)")
+                    raise DivergingPowerFlow(f"Divergence of DC powerflow (non connected grid). Detailed error: {self._grid.get_dc_solver().get_error()}")
             else:
                 if (self.V is None) or (self.V.shape[0] == 0):
                     # create the vector V as it is not created
@@ -583,15 +622,19 @@ def runpf(self, is_dc=False):
                     self._grid.reactivate_result_computation()
                     # self._grid.change_solver(self.__current_solver_type)
                     if Vdc.shape[0] == 0:
-                        raise DivergingPowerFlow("divergence of powerflow (non connected grid)")
+                        raise DivergingPowerFlow(f"Divergence of DC powerflow (non connected grid) at the initialization of AC powerflow. Detailed error: {self._grid.get_dc_solver().get_error()}")
                     V_init = Vdc
                 else:
                     V_init = copy.deepcopy(self.V)
                 V = self._grid.ac_pf(V_init, self.max_it, self.tol)
                 if V.shape[0] == 0:
-                    raise DivergingPowerFlow(f"divergence of powerflow (more than {self.max_it} iterations)")
+                    raise DivergingPowerFlow(f"Divergence of AC powerflow. Detailed error: {self._grid.get_solver().get_error()}")
 
-            self.comp_time += self._grid.get_computation_time()
+            if is_dc:
+                self.comp_time += self._grid.get_dc_computation_time()
+            else:
+                self.comp_time += self._grid.get_computation_time()
+                
             self.V[:] = V
             (self.p_or[:self.__nb_powerline],
              self.q_or[:self.__nb_powerline],
@@ -610,8 +653,8 @@ def runpf(self, is_dc=False):
              self.v_ex[self.__nb_powerline:],
              self.a_ex[self.__nb_powerline:]) = self._grid.get_trafolv_res()
 
-            self.a_or *= 1000.  # amps in lightsim, kA expected in grid2op
-            self.a_ex *= 1000.  # amps in lightsim, kA expected in grid2op
+            self.a_or *= 1000.  # kA in lightsim, A expected in grid2op
+            self.a_ex *= 1000.  # kA in lightsim, A expected in grid2op
 
             self.a_or[~np.isfinite(self.a_or)] = 0.
             self.v_or[~np.isfinite(self.v_or)] = 0.
@@ -625,15 +668,21 @@ def runpf(self, is_dc=False):
             self.next_prod_p[:] = self.prod_p
 
             if np.any(~np.isfinite(self.load_v)) or np.any(self.load_v <= 0.):
-                raise DivergingPowerFlow("One load is disconnected")
+                disco = (~np.isfinite(self.load_v)) | (self.load_v <= 0.)
+                load_disco = np.where(disco)[0]
+                raise DivergingPowerFlow(f"At least one load is disconnected (check loads {load_disco})")
             if np.any(~np.isfinite(self.prod_v)) or np.any(self.prod_v <= 0.):
-                raise DivergingPowerFlow("One generator is disconnected")
+                disco = (~np.isfinite(self.prod_v)) | (self.prod_v <= 0.)
+                gen_disco = np.where(disco)[0]
+                raise DivergingPowerFlow(f"At least one generator is disconnected (check loads {gen_disco})")
             # TODO storage case of divergence !
 
+            if self.shunts_data_available:
+                self._set_shunt_info()
+            
+            self._fill_theta()
+
             res = True
-            if is_dc:
-                # set back the solver to its previous state
-                self._grid.change_solver(self.__current_solver_type)
             self._grid.unset_topo_changed()
         except Exception as exc_:
             # of the powerflow has not converged, results are Nan
@@ -668,11 +717,22 @@ def _fill_nans(self):
         self.next_prod_p[:] = np.NaN
         self.prod_q[:] = np.NaN
         self.prod_v[:] = np.NaN
-        self.topo_vect[:] = -1
+        self.line_or_theta[:] = np.NaN
+        self.line_ex_theta[:] = np.NaN
+        self.load_theta[:] = np.NaN
+        self.gen_theta[:] = np.NaN
+
+        if self.shunts_data_available:
+            self.sh_p[:] = np.NaN
+            self.sh_q[:] = np.NaN
+            self.sh_v[:] = np.NaN
+            self.sh_bus[:] = -1
+
         if self.__has_storage:
             self.storage_p[:] = np.NaN
             self.storage_q[:] = np.NaN
             self.storage_v[:] = np.NaN
+            self.storage_theta[:] = np.NaN
         self.V[:] = self._grid.get_init_vm_pu()  # reset the V to its "original" value (see issue 30)
 
     def __deepcopy__(self, memo):
@@ -709,7 +769,9 @@ def copy(self):
                            "_big_topo_to_obj", "max_it", "tol", "dim_topo",
                            "_idx_hack_storage"]
         for attr_nm in li_regular_attr:
-            setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm)))
+            if hasattr(self, attr_nm):
+                # this test is needed for backward compatibility with other grid2op version
+                setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm)))
 
         # copy the numpy array
         res.__nb_bus_before = copy.deepcopy(self.__nb_bus_before)
@@ -722,11 +784,41 @@ def copy(self):
                        "load_p", "load_q", "load_v",
                        "prod_p", "prod_q", "prod_v",
                        "storage_p", "storage_q", "storage_v",
+                       "sh_p", "sh_q", "sh_v", "sh_bus",
+                       "line_or_theta", "line_ex_theta", "load_theta", "gen_theta", "storage_theta",                   
                        ]
         for attr_nm in li_attr_npy:
-            setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm)))
+            if hasattr(self, attr_nm):
+                # this test is needed for backward compatibility with other grid2op version
+                setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm)))
+
+        # copy class attribute for older grid2op version (did not use the class attribute)
+        cls_attr = ["env_name", 
+                    "n_line", "n_gen", "n_load", "n_sub",
+                    "name_gen", "name_load", "name_line", "name_sub", "name_storage",
+                    "sub_info", "dim_topo", 
+                    "load_to_subid", "gen_to_subid", "line_or_to_subid",
+                    "line_ex_to_subid", "load_to_sub_pos", "line_or_to_sub_pos",
+                    "line_ex_to_sub_pos", "load_pos_topo_vect", "gen_pos_topo_vect",
+                    "line_or_pos_topo_vect", "line_ex_pos_topo_vect", "storage_pos_topo_vect",
+
+                    "shunts_data_available", "shunt_to_subid", "n_shunt", "name_shunt",
+
+                    "n_storage", "storage_to_subid", "storage_to_sub_pos", "storage_type", 
+                    "storage_Emax", "storage_Emin", "storage_max_p_prod", "storage_max_p_absorb",
+                    "storage_marginal_cost", "storage_loss", "storage_charging_efficiency", 
+                    "storage_discharging_efficiency",
+
+                    "alarms_area_names", "alarms_lines_area", "alarms_area_lines"
+                    ] + type(self)._li_attr_disp
+
+        for attr_nm in cls_attr:
+            if hasattr(self, attr_nm):
+                # this test is needed for backward compatibility with other grid2op version
+                setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm)))
         ###############
 
+
         # handle the most complicated
         res._grid = mygrid.copy()
         res.__me_at_init = __me_at_init.copy()  # this is const
@@ -781,12 +873,15 @@ def storages_info(self):
         return self.cst_1 * self.storage_p, self.cst_1 * self.storage_q, self.cst_1 * self.storage_v
 
     def shunt_info(self):
-        tmp = self._grid.get_shunts_res()
+        return self.cst_1 * self.sh_p, self.cst_1 * self.sh_q, self.cst_1 * self.sh_v, self.sh_bus
+
+    def _set_shunt_info(self):
+        self.sh_p[:], self.sh_q[:], self.sh_v[:]  = self._grid.get_shunts_res()
         shunt_bus = np.array([self._grid.get_bus_shunt(i) for i in range(self.n_shunt)], dtype=dt_int)
         res_bus = np.ones(shunt_bus.shape[0], dtype=dt_int)  # by default all shunts are on bus one
         res_bus[shunt_bus >= self.__nb_bus_before] = 2  # except the one that are connected to bus 2
         res_bus[shunt_bus == -1] = -1  # or the one that are disconnected
-        return tmp[0].astype(dt_float), tmp[1].astype(dt_float), tmp[2].astype(dt_float), res_bus
+        self.sh_bus[:] = res_bus
 
     def _disconnect_line(self, id_):
         self.topo_vect[self.line_ex_pos_topo_vect[id_]] = -1
diff --git a/lightsim2grid/newtonpf.py b/lightsim2grid/newtonpf.py
deleted file mode 100644
index 12f74ae..0000000
--- a/lightsim2grid/newtonpf.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# Copyright (c) 2020, RTE (https://www.rte-france.com)
-# See AUTHORS.txt
-# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
-# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
-# you can obtain one at http://mozilla.org/MPL/2.0/.
-# SPDX-License-Identifier: MPL-2.0
-# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-
-"""
-This module provide a function that can serve as a base replacement to the function newtonpf of
-`pandapower/pypower/newtonpf.py`
-
-
-"""
-import numpy as np
-from scipy import sparse
-
-from lightsim2grid_cpp import SparseLUSolver
-KLU_solver_available = False
-try:
-    from lightsim2grid_cpp import KLUSolver
-    KLU_solver_available = True
-except ImportError:
-    pass
-
-
-def newtonpf(Ybus, Sbus, V0, pv, pq, ppci, options):
-    """
-    Perform the Newton scheme to compute the AC powerflow of the system provided as input.
-    It supports only one single slack bus.
-
-    It is main as being integrated into pandapower as a replacement of the pypower implementation of "newtonpf"
-
-    Parameters
-    ----------
-    Ybus: ``numpy.ndarray``, ``numpy.sparmatrix``, dtype:complex
-        The admittance matrix. If not in a sparse CSC format, it will be converted to it.
-
-    Sbus: ``numpy.ndarray``, dtype:complex
-        The power injected at each bus.
-
-    V0: ``numpy.ndarray``, dtype:complex
-        The initial voltage
-
-    pv: ``numpy.ndarray``, dtype:np.int
-        Index of the pv buses (slack bus must NOT be on this list)
-
-    pq: ``numpy.ndarray``, dtype:np.int
-        Index of the pq buses (slack bus must NOT be on this list)
-
-    ppci: ``dict``
-        pandapower internal "ppc", ignored.
-
-    options: ``dict``
-        Dictionnary of various pandapower option. Only "max_iteration" and "tolerance_mva" are used at the moment.
-
-    Returns
-    -------
-    V: ``numpy.ndarray``, dtype:complex
-        The final complex voltage vector
-
-    converged: ``bool``
-        Whether the powerflow converged or not
-
-    iterations: ``int``
-        The number of iterations the solver performed
-
-    J: `numpy.sparmatrix``, dtype:float
-        The csc scipy matrix of the jacobian matrix of the system.
-
-    """
-    max_it = options["max_iteration"]
-    tol = options['tolerance_mva']
-    # initialize the solver
-    # TODO have that in options maybe (can use GaussSeidel, and NR with KLU -faster- or SparseLU)
-    if KLU_solver_available:
-        solver = KLUSolver()
-    else:
-        solver = SparseLUSolver()
-    Ybus = sparse.csc_matrix(Ybus)
-
-    # do the newton raphson algorithm
-    solver.solve(Ybus, V0, Sbus, pv, pq, max_it, tol)
-
-    # extract the results
-    Va = solver.get_Va()
-    Vm = solver.get_Vm()
-    V = Vm * np.exp(1j * Va)
-    J = solver.get_J()
-    converged = solver.converged()
-    iterations = solver.get_nb_iter()
-
-    Vm_it = None
-    Va_it = None
-    return V, converged, iterations, J, Vm_it, Va_it
diff --git a/lightsim2grid/newtonpf/__init__.py b/lightsim2grid/newtonpf/__init__.py
new file mode 100644
index 0000000..a6a1f45
--- /dev/null
+++ b/lightsim2grid/newtonpf/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+from lightsim2grid.newtonpf.newtonpf import newtonpf, newtonpf_new, newtonpf_old
+
+__all__ = ["newtonpf", "newtonpf_new", "newtonpf_old"]
diff --git a/lightsim2grid/newtonpf/newtonpf.py b/lightsim2grid/newtonpf/newtonpf.py
new file mode 100644
index 0000000..6bf7faf
--- /dev/null
+++ b/lightsim2grid/newtonpf/newtonpf.py
@@ -0,0 +1,328 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+import warnings
+import numpy as np
+from scipy import sparse
+
+from lightsim2grid_cpp import SparseLUSolver
+KLU_solver_available = False
+try:
+    from lightsim2grid_cpp import KLUSolver
+    KLU_solver_available = True
+except ImportError:
+    pass
+
+_PP_VERSION_MAX = "2.7.0"
+
+def _isolate_slack_ids(Sbus, pv, pq):
+    # extract the slack bus
+    ref = set(np.arange(Sbus.shape[0])) - set(pv) - set(pq)
+    ref = np.array(list(ref))
+    # build the slack weights
+    slack_weights = np.zeros(Sbus.shape[0])
+    slack_weights[ref] = 1.0 / ref.shape[0]
+    return ref, slack_weights
+
+
+def newtonpf(*args, **kwargs):
+    """
+    Pandapower changed the interface when they introduced the distributed slack functionality.
+    (around pandapower 2.7.0, pandapower 2.7.0 does not support distributed slack)
+
+    As of lightsim2grid version 0.6.0 we tried to reflect this change.
+
+    However we want to keep the full compatibility with different pandapower version.
+
+    This wrapper tries to select the proper version among:
+
+    - :func:`lightsim2grid.newtonpf.newtonpf_old` for older version of pandapower (<= 2.7.0)
+    - :func:`lightsim2grid.newtonpf.newtonpf_new` for newer version of pandapower (> 2.7.0)
+    
+    .. versionchanged:: 0.6.0
+        Before this version, the function was :func:`lightsim2grid.newtonpf.newtonpf_old` , 
+        now it's a wrapper.
+
+    Examples
+    ----------
+
+    .. code-block::
+
+        from lightsim2grid.newtonpf import newtonpf
+
+        # when pandapower version <= 2.7.0
+        V, converged, iterations, J, Vm_it, Va_it = newtonpf(Ybus, Sbus, V0, pv, pq, ppci, options)
+
+        # when pandapower version > 2.7.0
+        V, converged, iterations, J, Vm_it, Va_it = newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppci, options)
+
+    """
+    import pandapower as pp
+    if pp.__version__ <= _PP_VERSION_MAX:
+        try:
+            # should be the old version
+            return newtonpf_old(*args, **kwargs)
+        except TypeError as exc_:
+            # old version does not work, I try the new one
+            try:
+                return newtonpf_new(*args, **kwargs)
+            except TypeError as exc2_:
+                # nothing work, I stop here
+                raise exc2_ from exc_
+    else:
+        try:
+            # should be the new version
+            return newtonpf_new(*args, **kwargs)
+        except TypeError as exc_:
+            # new version does not work, I try the old one
+            try:
+                return newtonpf_old(*args, **kwargs)
+            except TypeError as exc2_:
+                # nothing work, I stop here
+                raise exc2_ from exc_
+
+def newtonpf_old(Ybus, Sbus, V0, pv, pq, ppci, options):
+    """
+    Perform the Newton scheme to compute the AC powerflow of the system provided as input.
+    It supports only one single slack bus.
+
+    It is main as being integrated into pandapower as a replacement of the pypower implementation of "newtonpf"
+
+    .. seealso::
+        :func:`lightsim2grid.newtonpf.newtonpf` for a compatibility wrapper that tries to find
+        the best option among :func:`lightsim2grid.newtonpf.newtonpf_old` and 
+        :func:`lightsim2grid.newtonpf.newtonpf_new` depending on pandapower version
+
+    .. versionadded:: 0.6.0
+
+        Added as a way to retrieve the "old" signature for compatibility with older pandapower version
+
+    .. note::
+        This is a legacy code mainly present for compatibility with older pandapower versions.
+
+    .. warning::
+        It considers that all nodes non pv, non pq are slack nodes and assign the same weights
+        for all of them even though it was not possible to perform such computation in earlier versions.
+
+    Parameters
+    ----------
+    Ybus: ``numpy.ndarray``, ``numpy.sparmatrix``, dtype:complex
+        The admittance matrix. If not in a sparse CSC format, it will be converted to it.
+
+    Sbus: ``numpy.ndarray``, dtype:complex
+        The power injected at each bus.
+
+    V0: ``numpy.ndarray``, dtype:complex
+        The initial voltage
+
+    pv: ``numpy.ndarray``, dtype:np.int
+        Index of the pv buses (slack bus must NOT be on this list)
+
+    pq: ``numpy.ndarray``, dtype:np.int
+        Index of the pq buses (slack bus must NOT be on this list)
+
+    ppci: ``dict``
+        pandapower internal "ppc", ignored.
+
+    options: ``dict``
+        Dictionnary of various pandapower option. Only "max_iteration" and "tolerance_mva" are used at the moment.
+
+    Returns
+    -------
+    V: ``numpy.ndarray``, dtype:complex
+        The final complex voltage vector
+
+    converged: ``bool``
+        Whether the powerflow converged or not
+
+    iterations: ``int``
+        The number of iterations the solver performed
+
+    J: ``scipy.sparsematrix```, dtype:float
+        The csc scipy sparse matrix of the jacobian matrix of the system.
+
+    Notes
+    -----
+
+    J has the shape::
+    
+        | s | slack_bus |               | (pvpq+1,1) |   (1, pvpq)  |  (1, pq)   |
+        | l |  -------  |               |            | ------------------------- |
+        | a | J11 | J12 | = dimensions: |            | (pvpq, pvpq) | (pvpq, pq) |
+        | c | --------- |               |   ------   | ------------------------- |
+        | k | J21 | J22 |               |  (pq, 1)   |  (pq, pvpq)  | (pq, pq)   |
+        
+
+    With:
+    
+    - `J11` = dS_dVa[array([pvpq]).T, pvpq].real (= real part of dS / dVa for all pv and pq buses)
+    - `J12` = dS_dVm[array([pvpq]).T, pq].real
+    - `J21` = dS_dVa[array([pq]).T, pvpq].imag
+    - `J22` = dS_dVm[array([pq]).T, pq].imag (= imaginary part of dS / dVm for all pq buses)
+    - `slack_bus` = is the representation of the equation for the reference slack bus dS_dVa[slack_bus_id, pvpq].real 
+      and dS_dVm[slack_bus_id, pq].real
+    - `slack` is the representation of the equation connecting together the slack buses (represented by slack_weights)
+      the remaining pq components are all 0.
+
+    .. note::
+        By default (and this cannot be changed at the moment), all buses in `ref` will be pv buses except the first one.
+
+    """
+    max_it = options["max_iteration"]
+    tol = options['tolerance_mva']
+
+    # initialize the solver
+    # TODO have that in options maybe (can use GaussSeidel, and NR with KLU -faster- or SparseLU)
+    if KLU_solver_available:
+        solver = KLUSolver()
+    else:
+        solver = SparseLUSolver()
+    Ybus = sparse.csc_matrix(Ybus)
+
+    # extract the slack bus
+    ref, slack_weights = _isolate_slack_ids(Sbus, pv, pq)
+
+    # do the newton raphson algorithm
+    solver.solve(Ybus, V0, Sbus, ref, slack_weights, pv, pq, max_it, tol)
+
+    # extract the results
+    Va = solver.get_Va()
+    Vm = solver.get_Vm()
+    V = Vm * np.exp(1j * Va)
+    J = solver.get_J()
+    converged = solver.converged()
+    iterations = solver.get_nb_iter()
+
+    Vm_it = None
+    Va_it = None
+    return V, converged, iterations, J, Vm_it, Va_it
+
+
+def newtonpf_new(Ybus, Sbus, V0, ref, pv, pq, ppci, options):
+    """
+    Perform the Newton scheme to compute the AC powerflow of the system provided as input.
+    It supports only one single slack bus.
+
+    It is main as being integrated into pandapower as a replacement of the pypower implementation of "newtonpf"
+
+    .. versionadded:: 0.6.0
+
+    .. seealso::
+        :func:`lightsim2grid.newtonpf.newtonpf` for a compatibility wrapper that tries to find
+        the best option among :func:`lightsim2grid.newtonpf.newtonpf_old` and 
+        :func:`lightsim2grid.newtonpf.newtonpf_new` depending on pandapower version
+
+    .. note::
+
+        It has been updated in version 0.6.0 to match pandapower new signature (addition of the `ref` 
+        parameter)
+
+        If you want the old behaviour, please use the `newtonpf_old` function.
+    
+    Parameters
+    ----------
+    Ybus: ``numpy.ndarray``, ``numpy.sparmatrix``, dtype:complex
+        The admittance matrix. If not in a sparse CSC format, it will be converted to it.
+
+    Sbus: ``numpy.ndarray``, dtype:complex
+        The power injected at each bus.
+
+    V0: ``numpy.ndarray``, dtype:complex
+        The initial voltage
+
+    ref: ``numpy.ndarray``, dtype:np.int
+        Ids of the slack buses (added in version 0.5.6 to match pandapower changes)
+
+    pv: ``numpy.ndarray``, dtype:np.int
+        Index of the pv buses (slack bus must NOT be on this list)
+
+    pq: ``numpy.ndarray``, dtype:np.int
+        Index of the pq buses (slack bus must NOT be on this list)
+
+    ppci: ``dict``
+        pandapower internal "ppc", ignored.
+
+    options: ``dict``
+        Dictionnary of various pandapower option. Only "max_iteration" and "tolerance_mva" are used at the moment.
+
+    Returns
+    -------
+    V: ``numpy.ndarray``, dtype:complex
+        The final complex voltage vector
+
+    converged: ``bool``
+        Whether the powerflow converged or not
+
+    iterations: ``int``
+        The number of iterations the solver performed
+
+    J: ``scipy.sparsematrix``, dtype:float
+        The csc scipy sparse matrix of the jacobian matrix of the system.
+
+    Notes
+    -----
+    J has the shape::
+    
+        | s | slack_bus |               | (pvpq+1,1) |   (1, pvpq)  |  (1, pq)   |
+        | l |  -------  |               |            | ------------------------- |
+        | a | J11 | J12 | = dimensions: |            | (pvpq, pvpq) | (pvpq, pq) |
+        | c | --------- |               |   ------   | ------------------------- |
+        | k | J21 | J22 |               |  (pq, 1)   |  (pq, pvpq)  | (pq, pq)   |
+        
+
+    With:
+    
+    - `J11` = dS_dVa[array([pvpq]).T, pvpq].real (= real part of dS / dVa for all pv and pq buses)
+    - `J12` = dS_dVm[array([pvpq]).T, pq].real
+    - `J21` = dS_dVa[array([pq]).T, pvpq].imag
+    - `J22` = dS_dVm[array([pq]).T, pq].imag (= imaginary part of dS / dVm for all pq buses)
+    - `slack_bus` = is the representation of the equation for the reference slack bus dS_dVa[slack_bus_id, pvpq].real 
+      and dS_dVm[slack_bus_id, pq].real
+    - `slack` is the representation of the equation connecting together the slack buses (represented by slack_weights)
+      the remaining pq components are all 0.
+
+    .. note::
+        By default (and this cannot be changed at the moment), all buses in `ref` will be pv buses except the first one.
+
+    """
+    max_it = options["max_iteration"]
+    tol = options['tolerance_mva']
+    bus = ppci['bus']
+
+    try:
+        from pandapower.pypower.idx_bus import SL_FAC  # lazy import for earlier pandapower version (without distributed slack)
+        slack_weights = bus[:, SL_FAC].astype(float)  ## contribution factors for distributed slack
+        slack_weights /= slack_weights.sum()
+    except ImportError:
+        # earlier version of pandapower
+        warnings.warn("You are using a pandapower version that does not support distributed slack. We will attempt to "
+                      "replicate this with lightsim2grid.")
+        ref, slack_weights = _isolate_slack_ids(Sbus, pv, pq)
+
+    # initialize the solver
+    # TODO have that in options maybe (can use GaussSeidel, and NR with KLU -faster- or SparseLU)
+    if KLU_solver_available:
+        solver = KLUSolver()
+    else:
+        solver = SparseLUSolver()
+    Ybus = sparse.csc_matrix(Ybus)
+
+    # do the newton raphson algorithm
+    solver.solve(Ybus, V0, Sbus, ref, slack_weights, pv, pq, max_it, tol)
+
+    # extract the results
+    Va = solver.get_Va()
+    Vm = solver.get_Vm()
+    V = Vm * np.exp(1j * Va)
+    J = solver.get_J()
+    converged = solver.converged()
+    iterations = solver.get_nb_iter()
+
+    Vm_it = None
+    Va_it = None
+    return V, converged, iterations, J, Vm_it, Va_it
diff --git a/lightsim2grid/physical_law_checker.py b/lightsim2grid/physical_law_checker.py
index 5ddfdec..88fff61 100644
--- a/lightsim2grid/physical_law_checker.py
+++ b/lightsim2grid/physical_law_checker.py
@@ -7,13 +7,13 @@
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
 import os
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 
 
 class PhysicalLawChecker:
     """
     Utility tools to check that a given vector meets the KCL (Kirchhoff's Current Law)
-     for a given grid (given as a grid2op observation).
+    for a given grid (given as a grid2op observation).
 
     Notes
     ------
@@ -21,7 +21,7 @@ class PhysicalLawChecker:
     difference in the "mismatch" in the order of 1e-5 / 1e-6. If you want to check the kirchoffs law, then you won't be
     able to check with a tolerance less than 1e-5.
 
-    ..warning::
+    .. warning::
         The grid2op environment is read from a grid.json file. Make sure to use an environment that can be
         loaded by lightsim2grid !
 
@@ -30,7 +30,7 @@ class PhysicalLawChecker:
 
     It can be used as:
 
-    ..code-block:: python
+    .. code-block:: python
 
         import grid2op
         import numpy as np
@@ -88,7 +88,8 @@ def check_solution(self, vcomplex, grid2op_obs, with_qlim=False):
         Parameters
         ----------
         vcomplex:
-            A numpy complex vector representing the complex voltage at each bus
+            A numpy complex vector representing the complex voltage at each bus (should have the same size as the total number of buses in the 
+            grid2op environment)
 
         grid2op_obs:
             A grid2op observation representing the state of the grid
@@ -104,10 +105,10 @@ def check_solution(self, vcomplex, grid2op_obs, with_qlim=False):
 
         Notes
         -----
-        The vector `vcomplex` needs to have as many components as the possible number of buses (typically `2.env.n_sub`)
+        The vector `vcomplex` needs to have as many components as the possible number of buses (typically `2 * env.n_sub`)
         for regular grid2op environments.
 
-        If some buses are "empty" (*ie* no elements are connected to it) then the given components of `vcomplex` is
+        If some buses are "empty" / "deactivated" (*ie* no elements are connected to it) then the given components of `vcomplex` is
         ignored: you can put whatever you want there it will have no impact on the final result.
 
         The order of the buses are expected to be given in the following order: First `n_sub` components of
diff --git a/lightsim2grid/securityAnalysis.py b/lightsim2grid/securityAnalysis.py
index 44326e7..dc5bdb6 100644
--- a/lightsim2grid/securityAnalysis.py
+++ b/lightsim2grid/securityAnalysis.py
@@ -6,15 +6,17 @@
 # SPDX-License-Identifier: MPL-2.0
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
+__all__ = ["SecurityAnalysisCPP", "SecurityAnalysis"]
+
 import os
-import numpy as np
 import warnings
-from collections.abc import Iterable
 import copy
+import numpy as np
+from collections.abc import Iterable
 
-from lightsim2grid.LightSimBackend import LightSimBackend
-from lightsim2grid.initGridModel import SolverType
-from lightsim2grid_cpp import SecurityAnalysis as _SecurityAnalysisCPP
+from lightsim2grid.lightSimBackend import LightSimBackend
+from lightsim2grid.solver import SolverType
+from lightsim2grid_cpp import SecurityAnalysisCPP
 
 
 class SecurityAnalysis(object):
@@ -26,9 +28,18 @@ class SecurityAnalysis(object):
     
     Feel free to post a feature request if you want to extend it.
 
+    This class is used in 4 phases:
+    
+    0) you create it from a grid2op environment (the grid topology will not be modified from this environment)
+    1) you add some contingencies to simulate
+    2) you start the simulation
+    3) you read back the results
+    
+    
     Examples
     --------
-
+    An example is given here
+    
     .. code-block:: python
 
         import grid2op
@@ -37,9 +48,15 @@ class SecurityAnalysis(object):
         env_name = ...
         env = grid2op.make(env_name, backend=LightSimBackend())
 
+        0) you create
         security_analysis = SecurityAnalysis(env)
+        
+        1) you add some contingencies to simulate
         security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...)
-        res_a, res_v = security_analysis.get_flows()
+        
+        2) you start the simulation (done automatically)
+        3) you read back the results
+        res_p, res_a, res_v = security_analysis.get_flows()
 
         # in this results, then
         # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
@@ -54,14 +71,13 @@ class SecurityAnalysis(object):
 
     In grid2op, it would be, in this case, 0. for the flows and 0. for the voltages.
 
-    
     """
     STR_TYPES = (str, np.str, np.str_)
     def __init__(self, grid2op_env):
         if not isinstance(grid2op_env.backend, LightSimBackend):
             raise RuntimeError("This class only works with LightSimBackend")
         self.grid2op_env = grid2op_env.copy()
-        self.computer = _SecurityAnalysisCPP(self.grid2op_env.backend._grid)
+        self.computer = SecurityAnalysisCPP(self.grid2op_env.backend._grid)
         self._contingency_order = {}  # key: contingency (as tuple), value: order in which it is entered
         self._all_contingencies = []
         self.__computed = False
@@ -240,7 +256,7 @@ def get_flows(self, *args):
 
             security_analysis = SecurityAnalysis(env)
             security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...)
-            res_a, res_v = security_analysis.get_flows()
+            res_p, res_a, res_v = security_analysis.get_flows()
 
             # in this results, then
             # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
@@ -270,8 +286,9 @@ def get_flows(self, *args):
         if not self.__computed:
             self.compute_V()
             self.compute_A()
+            self.compute_P()
         
-        return self._ampss[orders_], self._vs[orders_]
+        return self._mws[orders_], self._ampss[orders_], self._vs[orders_]
 
     def compute_V(self):
         """
@@ -305,3 +322,18 @@ def compute_A(self):
             raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
         self._ampss = 1e3 * self.computer.compute_flows()
         return self._ampss
+
+    def compute_P(self):
+        """
+        This function returns the active power flows (in MW) at the origin / high voltage side
+
+        .. warning:: Order of the results
+
+            The order in which the results are returned is NOT necessarily the order in which the contingencies have
+            been entered. Please use `get_flows()` method for easier reading back of the results !
+
+        """
+        if not self.__computed:
+            raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
+        self._mws = 1.0 * self.computer.compute_power_flows()
+        return self._mws
diff --git a/lightsim2grid/solver/__init__.py b/lightsim2grid/solver/__init__.py
index 7cb9282..d33d344 100644
--- a/lightsim2grid/solver/__init__.py
+++ b/lightsim2grid/solver/__init__.py
@@ -6,24 +6,44 @@
 # SPDX-License-Identifier: MPL-2.0
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
-__all__ = ["SolverType", "SparseLUSolver", "GaussSeidelSolver", "GaussSeidelSynchSolver", "DCSolver"]
+# TODO add the other solver now !
+__all__ = ["SolverType",
+           "ErrorType",
+           "AnySolver",  
+           "GaussSeidelSolver",
+           "GaussSeidelSynchSolver",
+           "SparseLUSolver",
+           "SparseLUSolverSingleSlack",
+           "DCSolver"]
+
+from lightsim2grid_cpp import SolverType
+from lightsim2grid_cpp import ErrorType
+from lightsim2grid_cpp import AnySolver
+
+from lightsim2grid_cpp import GaussSeidelSolver
+from lightsim2grid_cpp import GaussSeidelSynchSolver
+from lightsim2grid_cpp import SparseLUSolver
+from lightsim2grid_cpp import SparseLUSolverSingleSlack
+from lightsim2grid_cpp import DCSolver
 
 try:
     from lightsim2grid_cpp import KLUSolver
+    from lightsim2grid_cpp import KLUSolverSingleSlack
+    from lightsim2grid_cpp import KLUDCSolver
     __all__.append("KLUSolver")
+    __all__.append("KLUSolverSingleSlack")
+    __all__.append("KLUDCSolver")
 except Exception as exc_:
     # KLU is not available
     pass
 
 try:
     from lightsim2grid_cpp import NICSLUSolver
+    from lightsim2grid_cpp import NICSLUSolverSingleSlack
+    from lightsim2grid_cpp import NICSLUDCSolver
     __all__.append("NICSLUSolver")
+    __all__.append("NICSLUSolverSingleSlack")
+    __all__.append("NICSLUDCSolver")
 except Exception as exc_:
     # NICSLU is not available
     pass
-
-from lightsim2grid_cpp import SolverType
-from lightsim2grid_cpp import SparseLUSolver
-from lightsim2grid_cpp import GaussSeidelSolver
-from lightsim2grid_cpp import GaussSeidelSynchSolver
-from lightsim2grid_cpp import DCSolver
diff --git a/lightsim2grid/timeSerie.py b/lightsim2grid/timeSerie.py
index 54b23bb..ebad90a 100644
--- a/lightsim2grid/timeSerie.py
+++ b/lightsim2grid/timeSerie.py
@@ -6,17 +6,17 @@
 # SPDX-License-Identifier: MPL-2.0
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
+__all__ = ["Computers", "TimeSerie"]
 import os
 import numpy as np
 import warnings
 
 from grid2op.Chronics import Multifolder, GridStateFromFile
 
-from lightsim2grid.LightSimBackend import LightSimBackend
-from lightsim2grid.initGridModel import SolverType
+from lightsim2grid.lightSimBackend import LightSimBackend
+from lightsim2grid.solver import SolverType
 from lightsim2grid_cpp import Computers
 
-
 class TimeSerie:
     """
     This helper class, that only works with grid2op when using a LightSimBackend allows to compute
@@ -46,9 +46,11 @@ class TimeSerie:
             Vs[i, :env.n_sub] = env.backend.V
             As[i] = obs.a_or
 
-    Compare to the previous code, it avoid all grid2op code (coded in python) and can be roughly 3-5 times 
-    faster.
-    It allows also to use python threading, as the c++ computation can be done in different thread.
+    Compare to the previous code, it avoid all grid2op code and can be more than 15 times 
+    faster (on the case 118).
+    
+    It also allows to use python threading module, as the c++ computation can be done in different python threads (the GIL is not locked
+    during the c++ computation).
 
     Examples
     ----------
@@ -178,6 +180,19 @@ def compute_A(self):
         ampss = self.computer.compute_flows()
         return 1000. * ampss
 
+    def compute_P(self):
+        """
+        This function returns the active power flows (in MW) at the origin (for powerline) / high voltage (for transformer) 
+        side
+        
+        It does not recompute the voltages at each buses, it uses the information get from `compute_V` and
+        This is why you must call `compute_V(...)` first !
+        """
+        if not self.__computed:
+            raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
+        mws = self.computer.compute_power_flows()
+        return mws
+
     def _extract_inj(self):
         data_loader = None
         if isinstance(self.grid2op_env.chronics_handler.real_data, Multifolder):
diff --git a/setup.py b/setup.py
index 0efa145..16eb990 100644
--- a/setup.py
+++ b/setup.py
@@ -6,7 +6,7 @@
 import warnings
 from pybind11.setup_helpers import Pybind11Extension, build_ext
 
-__version__ = "0.5.5"
+__version__ = "0.6.0"
 KLU_SOLVER_AVAILABLE = False
 
 # Try to link against SuiteSparse (if available)
@@ -120,11 +120,6 @@
                                "-D_USE_MATH_DEFINES"]
     IS_WINDOWS = True
 
-
-# for even greater speed, you can add the "-march=native" flag. It does not work on all platform, that is
-# why we deactivated it by default
-# extra_compile_args_tmp += ["-march=native"]
-
 # if you have installed some BLAS or LAPACKE libraries (on ubuntu sudo apt-get install libblas-dev liblapacke-dev)
 # you can also trigger their use when using eigen.
 # extra_compile_args_tmp += ["-DEIGEN_USE_BLAS", "-DEIGEN_USE_LAPACKE"]
@@ -134,6 +129,7 @@
                        f"-DVERSION_MEDIUM={VERSION_MEDIUM}",
                        f"-DVERSION_MINOR={VERSION_MINOR}"]
 src_files = ['src/main.cpp',
+             "src/help_fun_msg.cpp",
              "src/SparseLUSolver.cpp",
              "src/BaseConstants.cpp",
              "src/GridModel.cpp",
@@ -145,12 +141,13 @@
              "src/DataLoad.cpp",
              "src/DataGen.cpp",
              "src/DataSGen.cpp",
-             "src/BaseNRSolver.cpp",
+            #  "src/BaseNRSolver.cpp",  # moved as a template class
+            #  "src/BaseNRSolverSingleSlack.cpp",  # moved as a template class
+            #  "src/DCSolver.cpp",  # moved as a template class
              "src/ChooseSolver.cpp",
              "src/GaussSeidelSolver.cpp",
              "src/GaussSeidelSynchSolver.cpp",
              "src/BaseSolver.cpp",
-             "src/DCSolver.cpp",
              "src/BaseMultiplePowerflow.cpp",
              "src/Computers.cpp",
              "src/SecurityAnalysis.cpp"]
@@ -169,25 +166,36 @@
     include_nicslu = True
     # check for appropriate license
     if not os.path.exists(path_nicslu):
+        print(f"WARNING: nothing for NICSLU at at: {path_nicslu}")
         include_nicslu = False
-    if include_nicslu and not os.path.exists(os.path.join(path_nicslu, "license")):
+    license_path = os.path.join(path_nicslu, "license")
+    if include_nicslu and not os.path.exists(license_path):
         # license not located at the right directory
+        print(f"WARNING: no license path found for NICSLU at: {license_path}")
         include_nicslu = False
-    if include_nicslu and not os.path.exists(os.path.join(path_nicslu, "license", "nicslu.lic")):
+    license_file = os.path.join(license_path, "nicslu.lic")
+    if include_nicslu and not os.path.exists(license_file):
         # no license found
+        print(f"WARNING: no license file is found for NICSLU at: {license_file}")
         include_nicslu = False
     libnicslu_path = None
     if include_nicslu:
         if sys.platform.startswith("linux") or sys.platform.startswith("darwin"):
             libnicslu_path = os.path.join(path_nicslu, "linux/lib_centos6_x64_gcc482_fma/int32/libnicslu.so")
             if not os.path.exists(libnicslu_path):
+                print(f"WARNING: cannot locate the NICSLU shared object that should be at: {libnicslu_path}")
                 include_nicslu = False
                 libnicslu_path = None
         elif sys.platform.startswith("win"):
             libnicslu_path = os.path.join(path_nicslu, "windows/lib_win7_x64_fma/int32/nicslu.lib")
             if not os.path.exists(libnicslu_path):
+                print(f"WARNING: cannot locate the NICSLU shared object that should be at: {libnicslu_path}")
                 include_nicslu = False
                 libnicslu_path = None
+        else:
+            print(f"WARNING: NICSLU can only be added when using linux, darwin (MacOS) or win (Windows) python version, you are using {sys.platform}")
+            include_nicslu = False
+            libnicslu_path = None
 
     if include_nicslu and libnicslu_path is not None:
         LIBS.append(os.path.join(path_nicslu, libnicslu_path))
@@ -200,12 +208,22 @@
     # to add extra info in cout for the computation times, we do not recommend to use it !
     if os.environ["__COUT_TIMES"] == "1":
         extra_compile_args.append("-D__COUT_TIMES")
+        print("WARNING: Using the \"cout times\" compatibility mode, do not use the generated package outside of testing !")
 
 if "__COMPILE_MARCHNATIVE" in os.environ:
     if os.environ["__COMPILE_MARCHNATIVE"] == "1":
         extra_compile_args.append("-march=native")
         print("INFO: Using \"-march=native\" compiler flag")
 
+# $Env:_READ_THE_DOCS = "1" in powershell
+if "_READ_THE_DOCS" in os.environ:
+    # generation is made on readthedocs.org for documentation, everything must be added, even though some packages will
+    # not be available (eg KLU, NICSLU, etc.)
+
+    if os.environ["_READ_THE_DOCS"] == "1":
+        extra_compile_args.append("-D_READ_THE_DOCS")
+        print("WARNING: Using the \"read the docs\" compatibility mode, do not use the package for something else than generating documentation.")
+        
 if False:
     path_iidm = ""
     lib_iidm = [os.path.join(path_iidm, "lib", "libiidm.a")]
@@ -247,21 +265,24 @@
             "sphinx-rtd-theme>=0.4.3",
             "sphinxcontrib-trio>=1.1.0",
             "autodocsumm>=0.1.13",
-            # "m2r"
+            "grid2op>=1.6.4",
             "recommonmark",
         ],
         "benchmark": [
             "tabulate",
-            "grid2op>=1.5.0",
+            "grid2op>=1.6.4",
             "numpy",
             "distro",
             "py-cpuinfo"
         ],
         "recommended": [
-            "grid2op>=1.5.0"
+            "grid2op>=1.6.4",
+            "numba"
         ],
         "test": [
-            "grid2op>=1.5.0"
+            "grid2op>=1.6.4",
+            "numba",
+            "pandapower==2.7.0"  # force this version for test, otherwise problem in test_DataConverter !
         ]
     }
 }
diff --git a/src/BaseMultiplePowerflow.cpp b/src/BaseMultiplePowerflow.cpp
index e094ac9..168ee60 100644
--- a/src/BaseMultiplePowerflow.cpp
+++ b/src/BaseMultiplePowerflow.cpp
@@ -14,13 +14,15 @@
 bool BaseMultiplePowerflow::compute_one_powerflow(const Eigen::SparseMatrix<cplx_type> & Ybus,
                                                   CplxVect & V,
                                                   const CplxVect & Sbus,
+                                                  const Eigen::VectorXi & slack_ids,
+                                                  const RealVect & slack_weights,
                                                   const Eigen::VectorXi & bus_pv,
                                                   const Eigen::VectorXi & bus_pq,
                                                   int max_iter,
                                                   double tol
                                                   )
 {
-    bool conv = _solver.compute_pf(Ybus, V, Sbus, bus_pv, bus_pq, max_iter, tol);
+    bool conv = _solver.compute_pf(Ybus, V, Sbus, slack_ids, slack_weights, bus_pv, bus_pq, max_iter, tol);
     if(conv){
         V = _solver.get_V().array();
     }
@@ -29,7 +31,7 @@ bool BaseMultiplePowerflow::compute_one_powerflow(const Eigen::SparseMatrix<cplx
     return conv;
 }
 
-void BaseMultiplePowerflow::compute_flows_from_Vs()
+void BaseMultiplePowerflow::compute_flows_from_Vs(bool amps)
 {
     // TODO find a way to factorize that with DataTrafo::compute_results
     // TODO and DataLine::compute_results
@@ -37,23 +39,29 @@ void BaseMultiplePowerflow::compute_flows_from_Vs()
     if (_voltages.size() == 0)
     {
         std::ostringstream exc_;
-        exc_ << "BaseMultiplePowerflow::compute_flows_from_Vs: cannot compute the flows as the voltages are not set. Have you called ";
+        exc_ << "BaseMultiplePowerflow::compute_flows_from_Vs: cannot compute the flows as the voltages are not set. Have you called compute(...) ? ";
         throw std::runtime_error(exc_.str());
     }
-    _timer_compute_A = 0.;
+    if (amps) _timer_compute_A = 0.;
+    else _timer_compute_P = 0.;
 
-    auto timer_compute_A = CustTimer();
+    auto timer_compute = CustTimer();
     const auto & sn_mva = _grid_model.get_sn_mva();
     const auto & nb_steps = _voltages.rows();
 
     // reset the results
-    _amps_flows = RealMat::Zero(nb_steps, n_total_);
+    if (amps) _amps_flows = RealMat::Zero(nb_steps, n_total_);
+    else _active_power_flows = RealMat::Zero(nb_steps, n_total_);
     
     // compute the flows for the powerlines
     Eigen::Index lag_id = 0;
-    compute_amps_flows(_grid_model.get_powerlines_as_data(), sn_mva, lag_id);
+    if (amps) compute_amps_flows(_grid_model.get_powerlines_as_data(), sn_mva, lag_id);
+    else compute_active_power_flows(_grid_model.get_powerlines_as_data(), sn_mva, lag_id);
     // compute the flows for the trafos
     lag_id = n_line_;
-    compute_amps_flows(_grid_model.get_trafos_as_data(), sn_mva, lag_id);
-    _timer_compute_A = timer_compute_A.duration();
+    if (amps) compute_amps_flows(_grid_model.get_trafos_as_data(), sn_mva, lag_id);
+    else compute_active_power_flows(_grid_model.get_trafos_as_data(), sn_mva, lag_id);
+
+    if (amps) _timer_compute_A = timer_compute.duration();
+    else _timer_compute_P = timer_compute.duration();
 }
diff --git a/src/BaseMultiplePowerflow.h b/src/BaseMultiplePowerflow.h
index 3532aff..f79f2b0 100644
--- a/src/BaseMultiplePowerflow.h
+++ b/src/BaseMultiplePowerflow.h
@@ -31,6 +31,7 @@ class BaseMultiplePowerflow
             _voltages(),
             _nb_solved(0),
             _timer_compute_A(0.),
+            _timer_compute_P(0.),
             _timer_solver(0.)
             {
                 // make sure that my "grid_model" is ready to be used (for ac and dc)
@@ -61,6 +62,7 @@ class BaseMultiplePowerflow
 
         // results
         Eigen::Ref<const RealMat > get_flows() const {return _amps_flows;}
+        Eigen::Ref<const RealMat > get_power_flows() const {return _active_power_flows;}
         Eigen::Ref<const CplxMat > get_voltages() const {return _voltages;}
         
     protected:
@@ -107,17 +109,60 @@ class BaseMultiplePowerflow
                 _amps_flows.col(el_id + lag_id) = res;
             }
         }
+        template<class T>
+        void compute_active_power_flows(const T & structure_data,
+                                        real_type sn_mva,
+                                        Eigen::Index lag_id) 
+        {
+            const auto & bus_vn_kv = _grid_model.get_bus_vn_kv();
+            const auto & el_status = structure_data.get_status();
+            const auto & bus_from = structure_data.get_bus_from();
+            const auto & bus_to = structure_data.get_bus_to();
+            const auto & v_yac_ff = structure_data.yac_ff();
+            const auto & v_yac_ft = structure_data.yac_ft();
+            Eigen::Index nb_el = structure_data.nb();
+
+            for(Eigen::Index el_id = 0; el_id < nb_el; ++el_id){
+                if(!el_status[el_id]) continue;
+
+                // retrieve which buses are used
+                int bus_from_me = bus_from(el_id);
+                int bus_to_me = bus_to(el_id);
+
+                // retrieve voltages
+                const auto Efrom = _voltages.col(bus_from_me);  // vector (one voltages per step)
+                const auto Eto = _voltages.col(bus_to_me);
+
+                const real_type bus_vn_kv_f = bus_vn_kv(bus_from_me);
+                const RealVect v_f_kv = Efrom.array().abs() * bus_vn_kv_f;
+
+                // retrieve physical parameters
+                const cplx_type yac_ff = v_yac_ff(el_id);  // scalar
+                const cplx_type yac_ft = v_yac_ft(el_id);
+
+                // trafo equations (to get the power at the "from" side)
+                CplxVect I_ft = yac_ff * Efrom + yac_ft * Eto;
+                I_ft = I_ft.array().conjugate();
+                const CplxVect S_ft = Efrom.array() * I_ft.array();
+
+                // now compute the current flow
+                RealVect res = S_ft.array().real() * sn_mva;
+                _active_power_flows.col(el_id + lag_id) = res;
+            }
+        }
 
         bool compute_one_powerflow(const Eigen::SparseMatrix<cplx_type> & Ybus,
                                    CplxVect & V,
                                    const CplxVect & Sbus,
+                                   const Eigen::VectorXi & slack_ids,
+                                   const RealVect & slack_weights,
                                    const Eigen::VectorXi & bus_pv,
                                    const Eigen::VectorXi & bus_pq,
                                    int max_iter,
                                    double tol
                                    );
 
-        void compute_flows_from_Vs();
+        void compute_flows_from_Vs(bool amps=true);
 
         CplxVect extract_Vsolver_from_Vinit(const CplxVect& Vinit,
                                             Eigen::Index nb_buses_solver,
@@ -147,11 +192,13 @@ class BaseMultiplePowerflow
 
         // outputs
         RealMat _amps_flows;
+        RealMat _active_power_flows;
         CplxMat _voltages;
         
         // timers
         int _nb_solved;
         double _timer_compute_A;
+        double _timer_compute_P;
         double _timer_solver;
 
 };
diff --git a/src/BaseNRSolver.h b/src/BaseNRSolver.h
index 4353493..3a392c6 100644
--- a/src/BaseNRSolver.h
+++ b/src/BaseNRSolver.h
@@ -14,17 +14,22 @@
 /**
 Base class for Newton Raphson based solver
 **/
+template<class LinearSolver>
 class BaseNRSolver : public BaseSolver
 {
     public:
         BaseNRSolver():need_factorize_(true), timer_initialize_(0.), timer_dSbus_(0.), timer_fillJ_(0.) {}
 
-        ~BaseNRSolver(){}
-
         virtual
-        Eigen::SparseMatrix<real_type> get_J(){
+        Eigen::Ref<const Eigen::SparseMatrix<real_type> > get_J() const {
             return J_;
         }
+        
+        virtual
+        Eigen::SparseMatrix<real_type> get_J_python() const {
+            Eigen::SparseMatrix<real_type> res = get_J();
+            return res;
+        }
 
         virtual
         std::tuple<double, double, double, double, double, double, double> get_timers_jacobian()
@@ -39,14 +44,15 @@ class BaseNRSolver : public BaseSolver
         bool compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
                         CplxVect & V,
                         const CplxVect & Sbus,
+                        const Eigen::VectorXi & slack_ids,
+                        const RealVect & slack_weights,
                         const Eigen::VectorXi & pv,
                         const Eigen::VectorXi & pq,
                         int max_iter,
                         real_type tol
                         ) ;
 
-        virtual
-        void reset();
+        virtual void reset();
 
     protected:
         virtual void reset_timer(){
@@ -56,62 +62,85 @@ class BaseNRSolver : public BaseSolver
             timer_initialize_ = 0.;
         }
         virtual
-        void initialize()=0;
+        void initialize(){
+            auto timer = CustTimer();
+            n_ = static_cast<int>(J_.cols()); // should be equal to J_.nrows()
+            err_ = ErrorType::NoError; // reset error message
+            const ErrorType init_status = _linear_solver.initialize(J_);
+            if(init_status != ErrorType::NoError){
+                // std::cout << "init_ok " << init_ok << std::endl;
+                err_ = init_status;
+            }
+            need_factorize_ = false;
+            timer_initialize_ += timer.duration();
+        }
 
         virtual
-        void solve(RealVect & b, bool has_just_been_inialized)=0;
+        void solve(RealVect & b, bool has_just_been_inialized){
+            auto timer = CustTimer();
+            const ErrorType solve_status = _linear_solver.solve(J_, b, has_just_been_inialized);
+            if(solve_status != ErrorType::NoError){
+                // std::cout << "solve error: " << solve_status << std::endl;
+                err_ = solve_status;
+            }
+            timer_solve_ += timer.duration();
+        }
 
         void _dSbus_dV(const Eigen::Ref<const Eigen::SparseMatrix<cplx_type> > & Ybus,
                        const Eigen::Ref<const CplxVect > & V);
 
         void _get_values_J(int & nb_obj_this_col,
-                           std::vector<int> & inner_index,
+                           std::vector<Eigen::Index> & inner_index,
                            std::vector<real_type> & values,
                            const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & mat,  // ex. dS_dVa_r
                            const std::vector<int> & index_row_inv, // ex. pvpq_inv
                            const Eigen::VectorXi & index_col, // ex. pvpq
-                           int col_id,
-                           int row_lag  // 0 for J11 for example, n_pvpq for J12
+                           Eigen::Index col_id,
+                           Eigen::Index row_lag,  // 0 for J11 for example, n_pvpq for J12
+                           Eigen::Index col_lag
+                           );
+        void _get_values_J(int & nb_obj_this_col,
+                           std::vector<Eigen::Index> & inner_index,
+                           std::vector<real_type> & values,
+                           const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & mat,  // ex. dS_dVa_r
+                           const std::vector<int> & index_row_inv, // ex. pvpq_inv
+                           Eigen::Index col_id_mat, // ex. pvpq(col_id)
+                           Eigen::Index row_lag,  // 0 for J11 for example, n_pvpq for J12
+                           Eigen::Index col_lag  // to remove the ref slack bus from this
                            );
 
         void fill_jacobian_matrix(const Eigen::SparseMatrix<cplx_type> & Ybus,
                                   const CplxVect & V,
+                                  Eigen::Index slack_bus_id,
+                                  const RealVect & slack_weights,
                                   const Eigen::VectorXi & pq,
                                   const Eigen::VectorXi & pvpq,
                                   const std::vector<int> & pq_inv,
                                   const std::vector<int> & pvpq_inv
                                   );
         void fill_jacobian_matrix_kown_sparsity_pattern(
-                 // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVa_r,
-                 // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVa_i,
-                 // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVm_r,
-                 // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVm_i,
-                 // const Eigen::SparseMatrix<cplx_type> & Ybus,
-                 // const CplxVect & V,
+                 Eigen::Index slack_bus_id,
                  const Eigen::VectorXi & pq,
-                 const Eigen::VectorXi & pvpq,
-                 const std::vector<int> & pq_inv,
-                 const std::vector<int> & pvpq_inv
+                 const Eigen::VectorXi & pvpq
                  );
         void fill_jacobian_matrix_unkown_sparsity_pattern(
-                 // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVa_r,
-                 // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVa_i,
-                 // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVm_r,
-                 // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVm_i,
                  const Eigen::SparseMatrix<cplx_type> & Ybus,
                  const CplxVect & V,
+                 Eigen::Index slack_bus_id,
+                 const RealVect & slack_weights,
                  const Eigen::VectorXi & pq,
                  const Eigen::VectorXi & pvpq,
                  const std::vector<int> & pq_inv,
                  const std::vector<int> & pvpq_inv
                  );
 
-        void fill_value_map(const Eigen::VectorXi & pq,
-                            const Eigen::VectorXi & pvpq,
-                            const std::vector<int> & pq_inv,
-                            const std::vector<int> & pvpq_inv);
+        void fill_value_map(Eigen::Index slack_bus_id,
+                            const Eigen::VectorXi & pq,
+                            const Eigen::VectorXi & pvpq);
 
     protected:
+        // used linear solver
+        LinearSolver _linear_solver;
 
         // solution of the problem
         Eigen::SparseMatrix<real_type> J_;  // the jacobian matrix
@@ -128,10 +157,50 @@ class BaseNRSolver : public BaseSolver
         double timer_dSbus_;
         double timer_fillJ_;
 
+
+    Eigen::SparseMatrix<real_type>
+        create_jacobian_matrix_test(const Eigen::SparseMatrix<cplx_type> & Ybus,
+                                    const CplxVect & V,
+                                    const RealVect & slack_weights,
+                                    const Eigen::VectorXi & pq,
+                                    const Eigen::VectorXi & pvpq
+                                    ){
+            // DO NOT USE, FOR DEBUG ONLY (especially for multiple slacks)
+            const auto & n_pvpq = pvpq.size();
+            const auto & n_pq = pvpq.size();
+            std::vector<int> pvpq_inv(V.size(), -1);
+            for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id;
+            std::vector<int> pq_inv(V.size(), -1);
+            for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id;
+            // TODO if bug when using it, check the "pvpq" below, 
+            // in theory its "pv" !
+            int slack_bus_id = extract_slack_bus_id(pvpq, pq,
+                                                    static_cast<unsigned int>(V.size())
+                                                    );
+            fill_jacobian_matrix(Ybus, V, static_cast<Eigen::Index>(slack_bus_id),
+                                 slack_weights, pq, pvpq, pq_inv, pvpq_inv);
+            return J_;
+        }
+
     private:
         // no copy allowed
-        BaseNRSolver( const BaseNRSolver & ) ;
-        BaseNRSolver & operator=( const BaseNRSolver & ) ;
+        BaseNRSolver( const BaseNRSolver & ) =delete ;
+        BaseNRSolver & operator=( const BaseNRSolver & ) =delete ;
+
+        /** helper function to print the max_col left most columns of the J matrix **/
+        void print_J(int min_col=-1, int max_col=-1) const{
+            auto size_J = J_.cols();
+            if(max_col == -1) max_col = static_cast<int>(size_J);
+            if(min_col == -1) min_col = 0;
+            for (int col_id=min_col; col_id < max_col; ++col_id){
+                for (Eigen::SparseMatrix<real_type>::InnerIterator it(J_, col_id); it; ++it)
+                {
+                    std::cout << it.row() << ", " << it.col() << ": " << it.value() << std::endl;
+                }
+                std::cout << std::endl;
+            }
+        }
+
 
     private:
         Eigen::SparseMatrix<cplx_type>
@@ -152,4 +221,6 @@ class BaseNRSolver : public BaseSolver
         }
 };
 
+#include "BaseNRSolver.tpp"
+
 #endif // BASENRSOLVER_H
diff --git a/src/BaseNRSolver.tpp b/src/BaseNRSolver.tpp
new file mode 100644
index 0000000..48d4fae
--- /dev/null
+++ b/src/BaseNRSolver.tpp
@@ -0,0 +1,649 @@
+// Copyright (c) 2020, RTE (https://www.rte-france.com)
+// See AUTHORS.txt
+// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+// you can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
+// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+// #include "BaseNRSolver.h"  // now a template class, so this file will be included instead !
+
+// TODO get rid of the pvpq, pv, pq etc and put the jacobian "in the right order"
+// to ease and make way faster the filling of the sparse matrix J
+
+template<class LinearSolver>
+bool BaseNRSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
+                              CplxVect & V,
+                              const CplxVect & Sbus,
+                              const Eigen::VectorXi & slack_ids,
+                              const RealVect & slack_weights,
+                              const Eigen::VectorXi & pv,
+                              const Eigen::VectorXi & pq,
+                              int max_iter,
+                              real_type tol
+                              )
+{
+    /**
+    This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus
+    of the system.
+    If the Ybus matrix changed, please uses the appropriate method to recomptue it!
+    **/
+    // TODO DEBUG MODE check what can be checked: no voltage at 0, Ybus is square, Sbus same size than V and
+    // TODO DEBUG MODE Ybus (nrow or ncol), pv and pq have value that are between 0 and nrow etc.
+    // TODO DEBUG MODE: check that all nodes is either pv, pq or slack
+    // TODO DEBUG MODE: check that the slack_weights sum to 1.0
+    if(Sbus.size() != Ybus.rows() || Sbus.size() != Ybus.cols() ){
+        // TODO DEBUG MODE
+        std::ostringstream exc_;
+        exc_ << "BaseNRSolver::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: ";
+        exc_ << "Sbus  (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.rows() << ").";
+        throw std::runtime_error(exc_.str());
+    }
+    if(V.size() != Ybus.rows() || V.size() != Ybus.cols() ){
+        // TODO DEBUG MODE
+        std::ostringstream exc_;
+        exc_ << "BaseNRSolver::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: ";
+        exc_ << "V  (" << V.size() << ") and Ybus (" << Ybus.rows()<< ", " << Ybus.rows() << ").";
+        throw std::runtime_error(exc_.str());
+    }
+    reset_timer();
+    auto timer = CustTimer();
+    if(!is_linear_solver_valid()) return false;
+
+    err_ = ErrorType::NoError;  // reset the error if previous error happened
+
+    Eigen::VectorXi my_pv = retrieve_pv_with_slack(slack_ids, pv);  // retrieve_pv_with_slack (not all), add_slack_to_pv (all)
+    real_type slack_absorbed = std::real(Sbus.sum());  // initial guess for slack_absorbed
+    const auto slack_bus_id = slack_ids(0);
+    
+    // initialize once and for all the "inverse" of these vectors
+    const auto n_pv = my_pv.size();
+    const auto n_pq = pq.size();
+    Eigen::VectorXi pvpq(n_pv + n_pq);
+    pvpq << my_pv, pq; 
+
+    // some clever tricks are used in the making of the Jacobian to handle the slack bus 
+    // (in case there is a distributed slack bus)
+    const auto n_pvpq = pvpq.size();
+    std::vector<int> pvpq_inv(V.size(), -1);
+    for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id;
+    std::vector<int> pq_inv(V.size(), -1);
+    for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id;
+    // const auto last_index = n_pvpq + n_pq;  // unused
+
+    V_ = V;
+    Vm_ = V_.array().abs();  // update Vm and Va again in case
+    Va_ = V_.array().arg();  // we "wrapped around" with a negative Vm
+
+    // first check, if the problem is already solved, i stop there
+    // compute a first time the mismatch to initialize the slack bus
+    RealVect F = _evaluate_Fx(Ybus, V, Sbus, slack_bus_id, slack_absorbed, slack_weights, my_pv, pq);
+
+    bool converged = _check_for_convergence(F, tol);
+    nr_iter_ = 0; //current step
+    bool res = true;  // have i converged or not
+    bool has_just_been_initialized = false;  // to avoid a call to klu_refactor follow a call to klu_factor in the same loop
+    // std::cout << "iter " << nr_iter_ << " dx(0): " << -F(0) << " dx(1): " << -F(1) << std::endl;
+    // std::cout << "slack_absorbed " << slack_absorbed << std::endl;
+    while ((!converged) & (nr_iter_ < max_iter)){
+        nr_iter_++;
+        fill_jacobian_matrix(Ybus, V_, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv);
+
+        if(need_factorize_){
+            initialize();
+            if(err_ != ErrorType::NoError){
+                // I got an error during the initialization of the linear system, i need to stop here
+                res = false;
+                break;
+            }
+            has_just_been_initialized = true;
+        }
+        solve(F, has_just_been_initialized);
+
+        has_just_been_initialized = false;
+        if(err_ != ErrorType::NoError){
+            // I got an error during the solving of the linear system, i need to stop here
+            res = false;
+            break;
+        }
+        // const auto dx = -F;  // removed for speed optimization (-= used below)
+
+        // update voltage (this should be done consistently with "_evaluate_Fx")
+        if (n_pv > 0) Va_(my_pv) -= F.segment(1, n_pv);
+        if (n_pq > 0){
+            Va_(pq) -= F.segment(n_pv + 1, n_pq);
+            Vm_(pq) -= F.segment(n_pv + n_pq + 1, n_pq);
+        }
+        slack_absorbed -= F(0); // by convention in fill_jacobian_matrix the slack bus is the last component
+
+        // std::cout << "iter " << nr_iter_ << " dx(0): " << -F(0) << " dx(1): " << -F(1) << std::endl;
+        // std::cout << "slack_absorbed " << slack_absorbed << std::endl;
+        // TODO change here for not having to cast all the time ... maybe
+        V_ = Vm_.array() * (Va_.array().cos().template cast<cplx_type>() + my_i * Va_.array().sin().template cast<cplx_type>() );
+        Vm_ = V_.array().abs();  // update Vm and Va again in case
+        Va_ = V_.array().arg();  // we wrapped around with a negative Vm TODO more efficient way maybe ?
+
+        F = _evaluate_Fx(Ybus, V_, Sbus, slack_bus_id, slack_absorbed, slack_weights, my_pv, pq);
+        bool tmp = F.allFinite();
+        if(!tmp){
+            err_ = ErrorType::InifiniteValue;
+            break; // divergence due to Nans
+        }
+        converged = _check_for_convergence(F, tol);
+    }
+    if(!converged){
+        if (err_ == ErrorType::NoError) err_ = ErrorType::TooManyIterations;
+        res = false;
+    }
+    timer_total_nr_ += timer.duration();
+    #ifdef __COUT_TIMES
+        std::cout << "Computation time: " << "\n\t timer_initialize_: " << timer_initialize_
+                  << "\n\t timer_dSbus_ (called in _fillJ_): " << timer_dSbus_
+                  << "\n\t timer_fillJ_: " << timer_fillJ_
+                  << "\n\t timer_Fx_: " << timer_Fx_
+                  << "\n\t timer_check_: " << timer_check_
+                  << "\n\t timer_solve_: " << timer_solve_
+                  << "\n\t timer_total_nr_: " << timer_total_nr_
+                  << "\n\n";
+    #endif // __COUT_TIMES
+    return res;
+}
+
+template<class LinearSolver>
+void BaseNRSolver<LinearSolver>::reset(){
+    BaseSolver::reset();
+    // reset specific attributes
+    J_ = Eigen::SparseMatrix<real_type>();  // the jacobian matrix
+    dS_dVm_ = Eigen::SparseMatrix<cplx_type>();
+    dS_dVa_ = Eigen::SparseMatrix<cplx_type>();
+    need_factorize_ = true;
+    n_ = -1;
+    // reset linear solver
+    ErrorType reset_status = _linear_solver.reset();
+    if(reset_status != ErrorType::NoError) err_ = reset_status;
+}
+
+template<class LinearSolver>
+void BaseNRSolver<LinearSolver>::_dSbus_dV(const Eigen::Ref<const Eigen::SparseMatrix<cplx_type> > & Ybus,
+                             const Eigen::Ref<const CplxVect > & V){
+    auto timer = CustTimer();
+    const auto size_dS = V.size();
+    const CplxVect Vnorm = V.array() / V.array().abs();
+    const CplxVect Ibus = Ybus * V;
+    const CplxVect conjIbus_Vnorm = Ibus.array().conjugate() * Vnorm.array();
+
+    if (dS_dVm_.cols() != Ybus.cols())
+    {
+        // initiliaze the matrices once (especially for the sparsity pattern)
+        // as I will use pointers to reference these elements later one, once initialize they need to be
+        // fixed
+        dS_dVm_ = Ybus;
+        dS_dVa_ = Ybus;
+    }
+
+    cplx_type * ds_dvm_x_ptr = dS_dVm_.valuePtr();
+    cplx_type * ds_dva_x_ptr = dS_dVa_.valuePtr();
+    unsigned int pos_el = 0;
+    for (int col_id=0; col_id < size_dS; ++col_id){
+        for (Eigen::Ref<const Eigen::SparseMatrix<cplx_type> >::InnerIterator it(Ybus, col_id); it; ++it)
+        {
+            const int row_id = static_cast<int>(it.row());
+            const cplx_type el_ybus = it.value();
+            cplx_type & ds_dvm_el = ds_dvm_x_ptr[pos_el];
+            cplx_type & ds_dva_el = ds_dva_x_ptr[pos_el];
+
+            // assign the right value (the one in ybus)
+            ds_dvm_el = el_ybus;
+            ds_dva_el = el_ybus;
+
+            // now compute the derivatives properly
+            ds_dvm_el *= Vnorm(col_id);  // dS_dVm[k] *= Vnorm[Yj[k]]
+            ds_dvm_el = std::conj(ds_dvm_el) * V(row_id);  // dS_dVm[k] = conj(dS_dVm[k]) * V[r]
+
+            ds_dva_el *= V(col_id);  // dS_dVa[k] *= V[Yj[k]]
+
+            if(col_id == row_id)
+            {
+                ds_dvm_el += conjIbus_Vnorm(row_id); // std::conj(Ibus(row_id)) * Vnorm(row_id); // dS_dVm[k] += conj(Ibus) * Vnorm
+                ds_dva_x_ptr[pos_el] -= Ibus(row_id);  // dS_dVa[k] = dS_dVa[k] - Ibus[r]
+            }
+            cplx_type tmp = my_i * V(row_id);
+            ds_dva_el = std::conj(-ds_dva_el) * tmp;  // dS_dVa[k] = conj(-dS_dVa[k]) * (1j * V[r])
+
+            // go to next element
+            ++pos_el;
+        }
+    }
+    timer_dSbus_ += timer.duration();
+}
+
+template<class LinearSolver>
+void BaseNRSolver<LinearSolver>::_get_values_J(int & nb_obj_this_col,
+                                 std::vector<Eigen::Index> & inner_index,
+                                 std::vector<real_type> & values,
+                                 const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & mat,  // ex. dS_dVa_r
+                                 const std::vector<int> & index_row_inv, // ex. pvpq_inv
+                                 const Eigen::VectorXi & index_col, // ex. pvpq
+                                 Eigen::Index col_id,
+                                 Eigen::Index row_lag,  // 0 for J11 for example, n_pvpq for J12
+                                 Eigen::Index col_lag  // to remove the ref slack bus from this
+                                 )
+{
+    /**
+    This function will fill the "inner_index" and "values" with the non zero values
+    present in the matrix "mat" for the column of the J matrix with id "col_id"
+    which corresponds to the column "index_col(col_id)" of the matrix mat.
+    The rows need to be converted using another vector too. For example, row "j" of J
+    need to be filled with element k of matrix "mat" with k such that "index_row[j] = k"
+    Hence, we pass as the argument of this function the "inverse" of index_row, which is such
+    that : "j = index_row_inv[k]" is easily computable given k.
+    **/
+    int col_id_mat = index_col(col_id + col_lag);
+
+    _get_values_J(nb_obj_this_col, inner_index, values, mat, index_row_inv, col_id_mat,
+                  row_lag, col_lag);
+}
+
+template<class LinearSolver>
+void BaseNRSolver<LinearSolver>::_get_values_J(int & nb_obj_this_col,
+                                 std::vector<Eigen::Index> & inner_index,
+                                 std::vector<real_type> & values,
+                                 const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & mat,  // ex. dS_dVa_r
+                                 const std::vector<int> & index_row_inv, // ex. pvpq_inv
+                                 Eigen::Index col_id_mat, // ex. pvpq
+                                 Eigen::Index row_lag,  // 0 for J11 for example, n_pvpq for J12
+                                 Eigen::Index col_lag  // to remove the ref slack bus from this
+                                 )
+{
+    /**
+    This function will fill the "inner_index" and "values" with the non zero values
+    present in the matrix "mat" for the column of the J matrix with id "col_id"
+    which corresponds to the column col_id_mat  of the matrix mat.
+    The rows need to be converted using another vector too. For example, row "j" of J
+    need to be filled with element k of matrix "mat" with k such that "index_row[j] = k"
+    Hence, we pass as the argument of this function the "inverse" of index_row, which is such
+    that : "j = index_row_inv[k]" is easily computable given k.
+    **/
+    const Eigen::Index start_id = mat.outerIndexPtr()[col_id_mat];
+    const Eigen::Index end_id = mat.outerIndexPtr()[col_id_mat+1];
+    const real_type * val_prt = mat.valuePtr();
+    for(Eigen::Index obj_id = start_id; obj_id < end_id; ++obj_id)
+    {
+        const Eigen::Index row_id_dS_dVa = mat.innerIndexPtr()[obj_id];
+        // I add the value only if the rows was selected in the indexes
+        const Eigen::Index row_id = index_row_inv[row_id_dS_dVa];
+        if(row_id >= 0)
+        {
+            inner_index.push_back(row_id+row_lag);
+            values.push_back(val_prt[obj_id]);
+            nb_obj_this_col++;
+        }
+    }
+}
+
+template<class LinearSolver>
+void BaseNRSolver<LinearSolver>::fill_jacobian_matrix(const Eigen::SparseMatrix<cplx_type> & Ybus,
+                                        const CplxVect & V,
+                                        Eigen::Index slack_bus_id,
+                                        const RealVect & slack_weights,
+                                        const Eigen::VectorXi & pq,
+                                        const Eigen::VectorXi & pvpq,
+                                        const std::vector<int> & pq_inv,
+                                        const std::vector<int> & pvpq_inv
+                                        )
+{
+    /**
+    Remember, J has the shape:
+    
+    | s | slack_bus |               | (pvpq+1,1) |   (1, pvpq)  |  (1, pq)   |
+    | l |  -------  |               |            | ------------------------- |
+    | a | J11 | J12 | = dimensions: |            | (pvpq, pvpq) | (pvpq, pq) |
+    | c | --------- |               |   ------   | ------------------------- |
+    | k | J21 | J22 |               |  (pq, 1)   |  (pq, pvpq)  | (pq, pq)   |
+
+    python implementation:
+    `J11` = dS_dVa[array([pvpq]).T, pvpq].real
+    `J12` = dS_dVm[array([pvpq]).T, pq].real
+    `J21` = dS_dVa[array([pq]).T, pvpq].imag
+    `J22` = dS_dVm[array([pq]).T, pq].imag
+
+    `slack_bus` = is the representation of the equation for the reference slack bus dS_dVa[slack_bus_id, pvpq].real
+    and dS_dVm[slack_bus_id, pq].real
+
+    `slack` is the representation of the equation connecting together the slack buses (represented by slack_weights)
+    the remaining pq components are all 0.
+    **/
+
+    auto timer = CustTimer();
+    _dSbus_dV(Ybus, V);
+
+    const auto n_pvpq = pvpq.size();
+    const auto n_pq = pq.size();
+    const auto size_j = n_pvpq + n_pq + 1;   // +1 because i add the slack bus
+
+    // TODO to gain a bit more time below, try to compute directly, in _dSbus_dV(Ybus, V);
+    // TODO the `dS_dVa_[pvpq, pvpq]`
+    // TODO so that it's easier to retrieve in the next few lines !
+    if(J_.cols() != size_j)
+    {
+        #ifdef __COUT_TIMES
+            auto timer2 = CustTimer();
+        #endif  // __COUT_TIMES
+        // first time i initialized the matrix, so i need to compute its sparsity pattern
+        fill_jacobian_matrix_unkown_sparsity_pattern(Ybus, V, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv);
+        #ifdef __COUT_TIMES
+            std::cout << "\t\t fill_jacobian_matrix_unkown_sparsity_pattern : " << timer2.duration() << std::endl;
+        #endif  // __COUT_TIMES
+    }else{
+        // the sparsity pattern of J_ is already known, i can reuse it to fill it
+        // properly and faster (approx 3 times faster than the previous one)
+        #ifdef __COUT_TIMES
+            auto timer3 = CustTimer();
+        #endif  // __COUT_TIMES
+        fill_jacobian_matrix_kown_sparsity_pattern(slack_bus_id,
+                                                   pq, pvpq
+                                                   );
+        #ifdef __COUT_TIMES
+            std::cout << "\t\t fill_jacobian_matrix_kown_sparsity_pattern : " << timer3.duration() << std::endl;
+        #endif  // __COUT_TIMES
+    }
+    timer_fillJ_ += timer.duration();;
+}
+
+template<class LinearSolver>
+void BaseNRSolver<LinearSolver>::fill_jacobian_matrix_unkown_sparsity_pattern(
+        const Eigen::SparseMatrix<cplx_type> & Ybus,
+        const CplxVect & V,
+        Eigen::Index slack_bus_id,
+        const RealVect & slack_weights,
+        const Eigen::VectorXi & pq,
+        const Eigen::VectorXi & pvpq,
+        const std::vector<int> & pq_inv,
+        const std::vector<int> & pvpq_inv
+    )
+{
+    /**
+    This functions fills the jacobian matrix when its sparsity pattern is not know in advance (typically
+    the first iteration of the Newton Raphson)
+    For that we need to perform relatively expensive computation from dS_dV* in order to retrieve it.
+    This function is NOT optimized for speed...
+
+    Remember, J has the shape:
+    
+    | s | slack_bus |               | (pvpq+1,1) |   (1, pvpq)  |  (1, pq)   |
+    | l |  -------  |               |            | ------------------------- |
+    | a | J11 | J12 | = dimensions: |            | (pvpq, pvpq) | (pvpq, pq) |
+    | c | --------- |               |   ------   | ------------------------- |
+    | k | J21 | J22 |               |  (pq, 1)   |  (pq, pvpq)  | (pq, pq)   |
+
+    python implementation:
+    `J11` = dS_dVa[array([pvpq]).T, pvpq].real
+    `J12` = dS_dVm[array([pvpq]).T, pq].real
+    `J21` = dS_dVa[array([pq]).T, pvpq].imag
+    `J22` = dS_dVm[array([pq]).T, pq].imag
+
+    `slack_bus` = is the representation of the equation for the reference slack bus dS_dVa[slack_bus_id, pvpq].real
+    and dS_dVm[slack_bus_id, pq].real
+
+    `slack` is the representation of the equation connecting together the slack buses (represented by slack_weights)
+    the remaining pq components are all 0.
+    **/
+   typedef Eigen::SparseMatrix<cplx_type>::StorageIndex StorageIndex;
+
+    const Eigen::Index n_pvpq = pvpq.size();
+    const Eigen::Index n_pq = pq.size();
+    const auto size_j = n_pvpq + n_pq + 1;  // the +1 here to represent the equation for slack bus
+
+    const Eigen::SparseMatrix<real_type> dS_dVa_r = dS_dVa_.real();
+    const Eigen::SparseMatrix<real_type> dS_dVa_i = dS_dVa_.imag();
+    const Eigen::SparseMatrix<real_type> dS_dVm_r = dS_dVm_.real();
+    const Eigen::SparseMatrix<real_type> dS_dVm_i = dS_dVm_.imag();
+
+    // Method (1) seems to be faster than the others
+
+    // optim : if the matrix was already computed, i don't initialize it, i instead reuse as much as i can
+    // i can do that because the matrix will ALWAYS have the same non zero coefficients.
+    // in this if, i allocate it in a "large enough" place to avoid copy when first filling it
+    if(J_.cols() != size_j) J_ = Eigen::SparseMatrix<real_type>(size_j,size_j);
+
+    std::vector<Eigen::Triplet<double> > coeffs;  // HERE FOR PERF OPTIM (3)
+    coeffs.reserve(2*(dS_dVa_.nonZeros()+dS_dVm_.nonZeros())  + slack_weights.size());  // HERE FOR PERF OPTIM (3)
+
+    // i fill the buffer columns per columns
+    int nb_obj_this_col = 0;
+    std::vector<Eigen::Index> inner_index;
+    std::vector<real_type> values;
+
+    // fill n_pvpq leftmost columns
+    for(Eigen::Index col_id=0; col_id < n_pvpq; ++col_id){ 
+        // reset from the previous column
+        nb_obj_this_col = 0;
+        inner_index.clear();
+        values.clear();
+
+        // fill with the first column with the column of dS_dVa[:,pvpq[col_id]]
+        // and check the row order !
+        _get_values_J(nb_obj_this_col, inner_index, values,
+                      dS_dVa_r,
+                      pvpq_inv, pvpq,
+                      col_id,
+                      1,  // added because the first row is for the slack bus
+                      0);
+        // fill the rest of the rows with the first column of dS_dVa_imag[:,pq[col_id]]
+        _get_values_J(nb_obj_this_col, inner_index, values,
+                      dS_dVa_i,
+                      pq_inv, pvpq,
+                      col_id,
+                      n_pvpq + 1, // + 1 added because the first row is for the slack bus
+                      0);
+
+        // "efficient" insert of the element in the matrix
+        for(Eigen::Index in_ind=0; in_ind < nb_obj_this_col; ++in_ind){
+            StorageIndex row_id = static_cast<StorageIndex>(inner_index[in_ind]);
+            coeffs.push_back(Eigen::Triplet<double>(row_id, static_cast<StorageIndex>(col_id) + 1, values[in_ind]));   // HERE FOR PERF OPTIM (3)
+        }
+    }
+
+    //TODO make same for the second part (have a funciton for previous loop)
+    // fill the remaining n_pq columns
+    for(Eigen::Index col_id=0; col_id < n_pq; ++col_id){
+        // reset from the previous column
+        nb_obj_this_col = 0;
+        inner_index.clear();
+        values.clear();
+
+        // fill with the first column with the column of dS_dVa[:,pvpq[col_id]]
+        // and check the row order !
+        _get_values_J(nb_obj_this_col, inner_index, values,
+                      dS_dVm_r,
+                      pvpq_inv, pq,
+                      col_id,
+                      1,  // 1 added because the first row is for the slack bus
+                      0);
+
+        // fill the rest of the rows with the first column of dS_dVa_imag[:,pq[col_id]]
+        _get_values_J(nb_obj_this_col, inner_index, values,
+                      dS_dVm_i,
+                      pq_inv, pq,
+                      col_id,
+                      n_pvpq + 1,   // + 1 added because the first row is for the slack bus
+                      0);
+
+        // "efficient" insert of the element in the matrix
+        for(Eigen::Index in_ind=0; in_ind < nb_obj_this_col; ++in_ind){
+            auto row_id = static_cast<StorageIndex>(inner_index[in_ind]);
+            coeffs.push_back(Eigen::Triplet<double>(row_id, static_cast<StorageIndex>(col_id + n_pvpq) + 1, values[in_ind]));   // HERE FOR PERF OPTIM (3)
+        }
+    }
+
+    // add the slack bus coefficients for the first bus (the "real" slack bus)
+    // first row (which corresponds to slack_bus_id)
+    for (Eigen::Index col_id=0; col_id < n_pvpq; ++col_id){
+        for (Eigen::SparseMatrix<real_type>::InnerIterator it(dS_dVa_r, col_id); it; ++it)
+        {
+            if(it.row() != slack_bus_id) continue;   // don't add it if it's not the ref slack bus
+            const auto J_col = pvpq_inv[it.col()];
+            if(J_col >= 0){
+                // I need to insert it
+                coeffs.push_back(Eigen::Triplet<double>(0, J_col + 1, it.value()));   // HERE FOR PERF OPTIM (3)
+            }
+        }
+    }
+    for (Eigen::Index col_id=0; col_id < n_pq; ++col_id){
+        for (Eigen::SparseMatrix<real_type>::InnerIterator it(dS_dVm_r, col_id); it; ++it)
+        {
+            if(it.row() != slack_bus_id) continue;   // don't add it if it's not the ref slack bus
+            const auto J_col = pq_inv[it.col()];
+            if(J_col >= 0){
+                // I need to insert it
+                coeffs.push_back(Eigen::Triplet<double>(0, static_cast<StorageIndex>(J_col + n_pvpq) + 1, it.value()));   // HERE FOR PERF OPTIM (3)
+            }
+        }
+    }
+
+    // add later on the last column which corresponds to the slack bus equation
+    const StorageIndex last_col = 0; // static_cast<StorageIndex>(size_j) - 1;
+    // add the ref slack bus coeff
+    coeffs.push_back(Eigen::Triplet<double>(0, last_col, slack_weights[slack_bus_id]));   // HERE FOR PERF OPTIM (3)
+    // add the other coeffs (for other buses)
+    auto row_j = 1;
+    for(auto ind: pvpq){
+        auto sl_w  = slack_weights(ind);
+        if(sl_w != 0.) coeffs.push_back(Eigen::Triplet<double>(row_j, last_col, sl_w));   // HERE FOR PERF OPTIM (3)
+        ++row_j;
+    }
+    J_.setFromTriplets(coeffs.begin(), coeffs.end());  // HERE FOR PERF OPTIM (3)
+    // std::cout << "end fill jacobian unknown " << std::endl;
+    J_.makeCompressed();
+    fill_value_map(slack_bus_id, pq, pvpq);
+    // std::cout << "end fill_value_map" << std::endl;
+}
+
+/**
+fill the value of the `value_map_` that stores pointers to the elements of
+dS_dVa_ and dS_dVm_ to be used to fill J_
+it requires that J_ is initialized, in compressed mode.
+**/
+template<class LinearSolver>
+void BaseNRSolver<LinearSolver>::fill_value_map(
+        Eigen::Index slack_bus_id,
+        const Eigen::VectorXi & pq,
+        const Eigen::VectorXi & pvpq
+        )
+{
+    const int n_pvpq = static_cast<int>(pvpq.size());
+    value_map_ = std::vector<cplx_type*> (J_.nonZeros());
+
+    const auto n_row = J_.cols();
+    unsigned int pos_el = 0;
+    for (Eigen::Index col_=1; col_ < n_row; ++col_){  // last column is never updated (slack equation)
+        for (Eigen::SparseMatrix<real_type>::InnerIterator it(J_, col_); it; ++it)
+        {
+            auto row_id = it.row();
+            const auto col_id = it.col() - 1;  // it's equal to "col_"
+            if(row_id==0){
+                // this is the row of the slack bus
+                const Eigen::Index row_id_dS_dVx_r = slack_bus_id;  // same for both matrices
+                if(col_id < n_pvpq){
+                    const int col_id_dS_dVa_r = pvpq[col_id];
+                    value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVa_r);
+                }
+                else{
+                    const int col_id_dS_dVm_r = pq[col_id - n_pvpq];
+                    value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVm_r);
+                }
+            }else{
+                row_id -= 1;  // "do not consider" the row for slack bus (handled above)
+                // this is consistent with the "+1" added in fill_jacobian_matrix_unkown_sparsity_pattern
+                if((col_id < n_pvpq) && (row_id < n_pvpq)){
+                    // this is the J11 part (dS_dVa_r)
+                    const int row_id_dS_dVa_r = pvpq[row_id];
+                    const int col_id_dS_dVa_r = pvpq[col_id];
+                    // this_el = dS_dVa_r.coeff(row_id_dS_dVa_r, col_id_dS_dVa_r);
+                    value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r);
+
+                    // I don't need to perform these checks: if they failed, the element would not be in J_ in the first place
+                    // const int is_row_non_null = pq_inv[row_id_dS_dVa_r];
+                    // const int is_col_non_null = pq_inv[col_id_dS_dVa_r];
+                    // if(is_row_non_null >= 0 && is_col_non_null >= 0)
+                    //     this_el = dS_dVa_r.coeff(row_id_dS_dVa_r, col_id_dS_dVa_r);
+                    // else
+                    //     std::cout << "dS_dVa_r: missed" << std::endl;
+                }else if((col_id < n_pvpq) && (row_id >= n_pvpq)){
+                    // this is the J21 part (dS_dVa_i)
+                    const int row_id_dS_dVa_i = pq[row_id - n_pvpq];
+                    const int col_id_dS_dVa_i = pvpq[col_id];
+                    // this_el = dS_dVa_i.coeff(row_id_dS_dVa_i, col_id_dS_dVa_i);
+                    value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i);
+                }else if((col_id >= n_pvpq) && (row_id < n_pvpq)){
+                    // this is the J12 part (dS_dVm_r)
+                    const int row_id_dS_dVm_r = pvpq[row_id];
+                    const int col_id_dS_dVm_r = pq[col_id - n_pvpq];
+                    // this_el = dS_dVm_r.coeff(row_id_dS_dVm_r, col_id_dS_dVm_r);
+                    value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r);
+                }else if((col_id >= n_pvpq) && (row_id >= n_pvpq)){
+                    // this is the J22 part (dS_dVm_i)
+                    const int row_id_dS_dVm_i = pq[row_id - n_pvpq];
+                    const int col_id_dS_dVm_i = pq[col_id - n_pvpq];
+                    // this_el = dS_dVm_i.coeff(row_id_dS_dVm_i, col_id_dS_dVm_i);
+                    value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i);
+                }
+            }
+            // go to the next element
+            ++pos_el;
+        }
+    }
+}
+
+template<class LinearSolver>
+void BaseNRSolver<LinearSolver>::fill_jacobian_matrix_kown_sparsity_pattern(
+        Eigen::Index slack_bus_id,
+        const Eigen::VectorXi & pq,
+        const Eigen::VectorXi & pvpq
+    )
+{
+    /**
+    This functions fills the jacobian matrix when its sparsity pattern is KNOWN in advance (typically
+    the second and next iterations of the Newton Raphson)
+    We don't need to perform heavy computation but just to read the right data from the right matrix!
+    This function is optimized for speed. In particular, it does not "uncompress" the J_ matrix and only
+    change the value pointer (not the inner nor outer pointer)
+    It is optimized only if J_ is in default Eigen format (column)
+
+    Remember, J has the shape:
+    
+    | s | slack_bus |               | (pvpq+1,1) |   (1, pvpq)  |  (1, pq)   |
+    | l |  -------  |               |            | ------------------------- |
+    | a | J11 | J12 | = dimensions: |            | (pvpq, pvpq) | (pvpq, pq) |
+    | c | --------- |               |   ------   | ------------------------- |
+    | k | J21 | J22 |               |  (pq, 1)   |  (pq, pvpq)  | (pq, pq)   |
+
+    python implementation:
+    `J11` = dS_dVa[array([pvpq]).T, pvpq].real
+    `J12` = dS_dVm[array([pvpq]).T, pq].real
+    `J21` = dS_dVa[array([pq]).T, pvpq].imag
+    `J22` = dS_dVm[array([pq]).T, pq].imag
+
+    `slack_bus` = is the representation of the equation for the reference slack bus dS_dVa[slack_bus_id, pvpq].real
+    and dS_dVm[slack_bus_id, pq].real
+
+    `slack` is the representation of the equation connecting together the slack buses (represented by slack_weights)
+    the remaining pq components are all 0.
+
+    **/
+
+    const auto n_pvpq_1 = pvpq.size() + 1;
+    const auto n_cols = J_.cols();  // equal to nrow
+    unsigned int pos_el = 0;
+    for (Eigen::Index col_id=1; col_id < n_cols; ++col_id){  // last column is not updated (slack equation)
+        for (Eigen::SparseMatrix<real_type>::InnerIterator it(J_, col_id); it; ++it)
+        {
+            const auto row_id = it.row();
+            // only one if is necessary (magic !)
+            // top rows are "real" part and bottom rows are imaginary part (you can check)
+            it.valueRef() = row_id < n_pvpq_1 ? std::real(*value_map_[pos_el]) : std::imag(*value_map_[pos_el]);
+            // go to the next element
+            ++pos_el;
+        }
+    }
+}
diff --git a/src/BaseNRSolverSingleSlack.h b/src/BaseNRSolverSingleSlack.h
new file mode 100644
index 0000000..8cd8522
--- /dev/null
+++ b/src/BaseNRSolverSingleSlack.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2020, RTE (https://www.rte-france.com)
+// See AUTHORS.txt
+// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+// you can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
+// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+#ifndef BASENRSOLVERSINGLESLACK_H
+#define BASENRSOLVERSINGLESLACK_H
+
+#include "BaseNRSolver.h"
+
+/**
+Base class for Newton Raphson based solver (only interesting for single slack)
+**/
+template<class LinearSolver>
+class BaseNRSolverSingleSlack : public BaseNRSolver<LinearSolver>
+{
+    public:
+        BaseNRSolverSingleSlack():BaseNRSolver<LinearSolver>(){}
+
+        ~BaseNRSolverSingleSlack(){}
+
+        virtual
+        bool compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
+                        CplxVect & V,
+                        const CplxVect & Sbus,
+                        const Eigen::VectorXi & slack_ids,
+                        const RealVect & slack_weights,
+                        const Eigen::VectorXi & pv,
+                        const Eigen::VectorXi & pq,
+                        int max_iter,
+                        real_type tol
+                        );
+
+
+    protected:
+        void fill_jacobian_matrix(const Eigen::SparseMatrix<cplx_type> & Ybus,
+                                  const CplxVect & V,
+                                  const Eigen::VectorXi & pq,
+                                  const Eigen::VectorXi & pvpq,
+                                  const std::vector<int> & pq_inv,
+                                  const std::vector<int> & pvpq_inv);
+
+        void fill_jacobian_matrix_kown_sparsity_pattern(
+                 const Eigen::VectorXi & pq,
+                 const Eigen::VectorXi & pvpq
+                 );
+
+        void fill_jacobian_matrix_unkown_sparsity_pattern(
+                 const Eigen::SparseMatrix<cplx_type> & Ybus,
+                 const CplxVect & V,
+                 const Eigen::VectorXi & pq,
+                 const Eigen::VectorXi & pvpq,
+                 const std::vector<int> & pq_inv,
+                 const std::vector<int> & pvpq_inv
+                 );
+
+        void fill_value_map(const Eigen::VectorXi & pq,
+                            const Eigen::VectorXi & pvpq);
+
+};
+
+#include "BaseNRSolverSingleSlack.tpp"
+
+#endif // BASENRSOLVERSINGLESLACK_H
diff --git a/src/BaseNRSolver.cpp b/src/BaseNRSolverSingleSlack.tpp
similarity index 52%
rename from src/BaseNRSolver.cpp
rename to src/BaseNRSolverSingleSlack.tpp
index 42284d2..e07ae9b 100644
--- a/src/BaseNRSolver.cpp
+++ b/src/BaseNRSolverSingleSlack.tpp
@@ -6,16 +6,20 @@
 // SPDX-License-Identifier: MPL-2.0
 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
-#include "BaseNRSolver.h"
-
-bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                              CplxVect & V,
-                              const CplxVect & Sbus,
-                              const Eigen::VectorXi & pv,
-                              const Eigen::VectorXi & pq,
-                              int max_iter,
-                              real_type tol
-                              )
+// #include "BaseNRSolverSingleSlack.h"
+// #include "BaseNRSolver.h"
+
+template<class LinearSolver>
+bool BaseNRSolverSingleSlack<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
+                                         CplxVect & V,
+                                         const CplxVect & Sbus,
+                                         const Eigen::VectorXi & slack_ids,
+                                         const RealVect & slack_weights,
+                                         const Eigen::VectorXi & pv,
+                                         const Eigen::VectorXi & pq,
+                                         int max_iter,
+                                         real_type tol
+                                         )
 {
     /**
     This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus
@@ -36,38 +40,46 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
         exc_ << "V  (" << V.size() << ") and Ybus (" << Ybus.rows()<<", "<<Ybus.rows() << ").";
         throw std::runtime_error(exc_.str());
     }
-    reset_timer();
-    if(err_ > 0) return false; // i don't do anything if there were a problem at the initialization
+    BaseNRSolver<LinearSolver>::reset_timer();
+    
+    if(!BaseNRSolver<LinearSolver>::is_linear_solver_valid()) return false;
+
+    BaseNRSolver<LinearSolver>::err_ = ErrorType::NoError;  // reset the error if previous error happened
     auto timer = CustTimer();
     // initialize once and for all the "inverse" of these vectors
-    const int n_pv = static_cast<int>(pv.size());
+    Eigen::VectorXi my_pv = BaseNRSolver<LinearSolver>::retrieve_pv_with_slack(slack_ids, pv);
+
+    const int n_pv = static_cast<int>(my_pv.size());
     const int n_pq = static_cast<int>(pq.size());
     Eigen::VectorXi pvpq(n_pv + n_pq);
     //  for(int id=0; id < n_pv; ++id) pvpq[id] = pv[id];
     //  for(int id=0; id < n_pq; ++id) pvpq[id + n_pv] = pq[id];
-    pvpq << pv, pq; 
+    pvpq << my_pv, pq; 
     const int n_pvpq = static_cast<int>(pvpq.size());
     std::vector<int> pvpq_inv(V.size(), -1);
     for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id;
     std::vector<int> pq_inv(V.size(), -1);
     for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id;
 
-    V_ = V;
-    Vm_ = V_.array().abs();  // update Vm and Va again in case
-    Va_ = V_.array().arg();  // we wrapped around with a negative Vm
+    BaseNRSolver<LinearSolver>::V_ = V;
+    BaseNRSolver<LinearSolver>::Vm_ = BaseNRSolver<LinearSolver>::V_.array().abs();  // update Vm and Va again in case
+    BaseNRSolver<LinearSolver>::Va_ = BaseNRSolver<LinearSolver>::V_.array().arg();  // we wrapped around with a negative Vm
 
     // first check, if the problem is already solved, i stop there
-    RealVect F = _evaluate_Fx(Ybus, V, Sbus, pv, pq);
-    bool converged = _check_for_convergence(F, tol);
-    nr_iter_ = 0; //current step
+    RealVect F = BaseNRSolver<LinearSolver>::_evaluate_Fx(Ybus, V, Sbus, my_pv, pq);
+    bool converged = BaseNRSolver<LinearSolver>::_check_for_convergence(F, tol);
+    BaseNRSolver<LinearSolver>::nr_iter_ = 0; //current step
     bool res = true;  // have i converged or not
     bool has_just_been_initialized = false;  // to avoid a call to klu_refactor follow a call to klu_factor in the same loop
-    while ((!converged) & (nr_iter_ < max_iter)){
-        nr_iter_++;
-        fill_jacobian_matrix(Ybus, V_, pq, pvpq, pq_inv, pvpq_inv);
-        if(need_factorize_){
-            initialize();
-            if(err_ != 0){
+
+    const cplx_type m_i = BaseNRSolver<LinearSolver>::my_i;  // otherwise it does not compile
+
+    while ((!converged) & (BaseNRSolver<LinearSolver>::nr_iter_ < max_iter)){
+        BaseNRSolver<LinearSolver>::nr_iter_++;
+        fill_jacobian_matrix(Ybus, BaseNRSolver<LinearSolver>::V_, pq, pvpq, pq_inv, pvpq_inv);
+        if(BaseNRSolver<LinearSolver>::need_factorize_){
+            BaseNRSolver<LinearSolver>::initialize();
+            if(BaseNRSolver<LinearSolver>::err_ != ErrorType::NoError){
                 // I got an error during the initialization of the linear system, i need to stop here
                 res = false;
                 break;
@@ -78,159 +90,62 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
             // std::cout << "no need to factorize" << std::endl;
         }
 
-        solve(F, has_just_been_initialized);
+        BaseNRSolver<LinearSolver>::solve(F, has_just_been_initialized);
 
         has_just_been_initialized = false;
-        if(err_ != 0){
+        if(BaseNRSolver<LinearSolver>::err_ != ErrorType::NoError){
             // I got an error during the solving of the linear system, i need to stop here
             res = false;
             break;
         }
-        auto dx = -F;
+        // auto dx = -F;
 
-        Vm_ = V_.array().abs();  // update Vm and Va again in case
-        Va_ = V_.array().arg();  // we wrapped around with a negative Vm
+        BaseNRSolver<LinearSolver>::Vm_ = BaseNRSolver<LinearSolver>::V_.array().abs();  // update Vm and Va again in case
+        BaseNRSolver<LinearSolver>::Va_ = BaseNRSolver<LinearSolver>::V_.array().arg();  // we wrapped around with a negative Vm
 
         // update voltage (this should be done consistently with "klu_solver._evaluate_Fx")
-        if (n_pv > 0) Va_(pv) += dx.segment(0, n_pv);
+        if (n_pv > 0) BaseNRSolver<LinearSolver>::Va_(my_pv) -= F.segment(0, n_pv);
         if (n_pq > 0){
-            Va_(pq) += dx.segment(n_pv,n_pq);
-            Vm_(pq) += dx.segment(n_pv+n_pq, n_pq);
+            BaseNRSolver<LinearSolver>::Va_(pq) -= F.segment(n_pv,n_pq);
+            BaseNRSolver<LinearSolver>::Vm_(pq) -= F.segment(n_pv+n_pq, n_pq);
         }
 
         // TODO change here for not having to cast all the time ... maybe
-        V_ = Vm_.array() * (Va_.array().cos().cast<cplx_type>() + my_i * Va_.array().sin().cast<cplx_type>() );
+        const RealVect & Vm = BaseNRSolver<LinearSolver>::Vm_;  // I am forced to redefine the type for it to compile properly
+        const RealVect & Va = BaseNRSolver<LinearSolver>::Va_;
+        BaseNRSolver<LinearSolver>::V_ = Vm.array() * (Va.array().cos().cast<cplx_type>() + m_i * Va.array().sin().cast<cplx_type>() );
 
-        F = _evaluate_Fx(Ybus, V_, Sbus, pv, pq);
+        F = BaseNRSolver<LinearSolver>::_evaluate_Fx(Ybus, BaseNRSolver<LinearSolver>::V_, Sbus, my_pv, pq);
         bool tmp = F.allFinite();
         if(!tmp) break; // divergence due to Nans
-        converged = _check_for_convergence(F, tol);
+        converged = BaseNRSolver<LinearSolver>::_check_for_convergence(F, tol);
     }
     if(!converged){
-        err_ = 4;
+        if (BaseNRSolver<LinearSolver>::err_ == ErrorType::NoError) BaseNRSolver<LinearSolver>::err_ = ErrorType::TooManyIterations;
         res = false;
     }
-    timer_total_nr_ += timer.duration();
+    BaseNRSolver<LinearSolver>::timer_total_nr_ += timer.duration();
     #ifdef __COUT_TIMES
-        std::cout << "Computation time: " << "\n\t timer_initialize_: " << timer_initialize_
-                  << "\n\t timer_dSbus_ (called in _fillJ_): " << timer_dSbus_
-                  << "\n\t timer_fillJ_: " << timer_fillJ_
-                  << "\n\t timer_Fx_: " << timer_Fx_
-                  << "\n\t timer_check_: " << timer_check_
-                  << "\n\t timer_solve_: " << timer_solve_
-                  << "\n\t timer_total_nr_: " << timer_total_nr_
+        std::cout << "Computation time: " << "\n\t timer_initialize_: " << BaseNRSolver<LinearSolver>::timer_initialize_
+                  << "\n\t timer_dSbus_ (called in _fillJ_): " << BaseNRSolver<LinearSolver>::timer_dSbus_
+                  << "\n\t timer_fillJ_: " << BaseNRSolver<LinearSolver>::timer_fillJ_
+                  << "\n\t timer_Fx_: " << BaseNRSolver<LinearSolver>::timer_Fx_
+                  << "\n\t timer_check_: " << BaseNRSolver<LinearSolver>::timer_check_
+                  << "\n\t timer_solve_: " << BaseNRSolver<LinearSolver>::timer_solve_
+                  << "\n\t timer_total_nr_: " << BaseNRSolver<LinearSolver>::timer_total_nr_
                   << "\n\n";
     #endif // __COUT_TIMES
     return res;
 }
 
-void BaseNRSolver::reset(){
-    BaseSolver::reset();
-    // reset specific attributes
-    J_ = Eigen::SparseMatrix<real_type>();  // the jacobian matrix
-    dS_dVm_ = Eigen::SparseMatrix<cplx_type>();
-    dS_dVa_ = Eigen::SparseMatrix<cplx_type>();
-    need_factorize_ = true;
-}
-
-void BaseNRSolver::_dSbus_dV(const Eigen::Ref<const Eigen::SparseMatrix<cplx_type> > & Ybus,
-                             const Eigen::Ref<const CplxVect > & V){
-    auto timer = CustTimer();
-    const auto size_dS = V.size();
-    const CplxVect Vnorm = V.array() / V.array().abs();
-    const CplxVect Ibus = Ybus * V;
-    const CplxVect conjIbus_Vnorm = Ibus.array().conjugate() * Vnorm.array();
-
-    if (dS_dVm_.cols() != Ybus.cols())
-    {
-        // initiliaze the matrices once (especially for the sparsity pattern)
-        // as I will use pointers to reference these elements later one, once initialize they need to be
-        // fixed
-        dS_dVm_ = Ybus;
-        dS_dVa_ = Ybus;
-    }
-
-    cplx_type * ds_dvm_x_ptr = dS_dVm_.valuePtr();
-    cplx_type * ds_dva_x_ptr = dS_dVa_.valuePtr();
-    unsigned int pos_el = 0;
-    for (int col_id=0; col_id < size_dS; ++col_id){
-        for (Eigen::Ref<const Eigen::SparseMatrix<cplx_type> >::InnerIterator it(Ybus, col_id); it; ++it)
-        {
-            const int row_id = static_cast<int>(it.row());
-            const cplx_type el_ybus = it.value();
-            cplx_type & ds_dvm_el = ds_dvm_x_ptr[pos_el];
-            cplx_type & ds_dva_el = ds_dva_x_ptr[pos_el];
-
-            // assign the right value (the one in ybus)
-            ds_dvm_el = el_ybus;
-            ds_dva_el = el_ybus;
-
-            // now compute the derivatives properly
-            ds_dvm_el *= Vnorm(col_id);  // dS_dVm[k] *= Vnorm[Yj[k]]
-            ds_dvm_el = std::conj(ds_dvm_el) * V(row_id);  // dS_dVm[k] = conj(dS_dVm[k]) * V[r]
-
-            ds_dva_el *= V(col_id);  // dS_dVa[k] *= V[Yj[k]]
-
-            if(col_id == row_id)
-            {
-                ds_dvm_el += conjIbus_Vnorm(row_id); // std::conj(Ibus(row_id)) * Vnorm(row_id); // dS_dVm[k] += conj(Ibus) * Vnorm
-                ds_dva_x_ptr[pos_el] -= Ibus(row_id);  // dS_dVa[k] = -Ibus[r] + dS_dVa[k]
-            }
-            cplx_type tmp = my_i * V(row_id);
-            ds_dva_el = std::conj(-ds_dva_el) * tmp;  // dS_dVa[k] = conj(-dS_dVa[k]) * (1j * V[r])
-
-            // go to next element
-            ++pos_el;
-        }
-    }
-    timer_dSbus_ += timer.duration();
-}
-
-void BaseNRSolver::_get_values_J(int & nb_obj_this_col,
-                                 std::vector<int> & inner_index,
-                                 std::vector<real_type> & values,
-                                 const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & mat,  // ex. dS_dVa_r
-                                 const std::vector<int> & index_row_inv, // ex. pvpq_inv
-                                 const Eigen::VectorXi & index_col, // ex. pvpq
-                                 int col_id,
-                                 int row_lag  // 0 for J11 for example, n_pvpq for J12
-                                 )
-{
-    /**
-    This function will fill the "inner_index" and "values" with the non zero values
-    present in the matrix "mat" for the column of the J matrix with id "col_id"
-    which corresponds to the column "index_col(col_id)" of the matrix mat.
-    The rows need to be converted using another vector too. For example, row "j" of J
-    need to be filled with element k of matrix "mat" with k such that "index_row[j] = k"
-    Hence, we pass as the argument of this function the "inverse" of index_row, which is such
-    that : "j = index_row_inv[k]" is easily computable given k.
-    **/
-    int col_id_mat = index_col(col_id);
-
-    const int start_id = mat.outerIndexPtr()[col_id_mat];
-    const int end_id = mat.outerIndexPtr()[col_id_mat+1];
-    const real_type * val_prt = mat.valuePtr();
-    for(int obj_id = start_id; obj_id < end_id; ++obj_id)
-    {
-        const int row_id_dS_dVa = mat.innerIndexPtr()[obj_id];
-        // I add the value only if the rows was selected in the indexes
-        const int row_id = index_row_inv[row_id_dS_dVa];
-        if(row_id >= 0)
-        {
-            inner_index.push_back(row_id+row_lag);
-            values.push_back(val_prt[obj_id]);
-            nb_obj_this_col++;
-        }
-    }
-}
-
-void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                                        const CplxVect & V,
-                                        const Eigen::VectorXi & pq,
-                                        const Eigen::VectorXi & pvpq,
-                                        const std::vector<int> & pq_inv,
-                                        const std::vector<int> & pvpq_inv
-                                        )
+template<class LinearSolver>
+void BaseNRSolverSingleSlack<LinearSolver>::fill_jacobian_matrix(const Eigen::SparseMatrix<cplx_type> & Ybus,
+                                                   const CplxVect & V,
+                                                   const Eigen::VectorXi & pq,
+                                                   const Eigen::VectorXi & pvpq,
+                                                   const std::vector<int> & pq_inv,
+                                                   const std::vector<int> & pvpq_inv
+                                                   )
 {
     /**
     J has the shape
@@ -245,7 +160,7 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix<cplx_type> & Y
     **/
 
     auto timer = CustTimer();
-    _dSbus_dV(Ybus, V);
+    BaseNRSolver<LinearSolver>::_dSbus_dV(Ybus, V);
 
     const int n_pvpq = static_cast<int>(pvpq.size());
     const int n_pq = static_cast<int>(pq.size());
@@ -254,16 +169,14 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix<cplx_type> & Y
     // TODO to gain a bit more time below, try to compute directly, in _dSbus_dV(Ybus, V);
     // TODO the `dS_dVa_[pvpq, pvpq]`
     // TODO so that it's easier to retrieve in the next few lines !
-    if(J_.cols() != size_j)
+    if(BaseNRSolver<LinearSolver>::J_.cols() != size_j)
     // if(true)
     {
         #ifdef __COUT_TIMES
             auto timer2 = CustTimer();
         #endif  // __COUT_TIMES
         // first time i initialized the matrix, so i need to compute its sparsity pattern
-        fill_jacobian_matrix_unkown_sparsity_pattern(// dS_dVa_r, dS_dVa_i, dS_dVm_r, dS_dVm_i,
-                                                     Ybus, V, pq, pvpq, pq_inv, pvpq_inv
-                                                     );
+        fill_jacobian_matrix_unkown_sparsity_pattern(Ybus, V, pq, pvpq, pq_inv, pvpq_inv);
         #ifdef __COUT_TIMES
             std::cout << "\t\t fill_jacobian_matrix_unkown_sparsity_pattern : " << timer2.duration() << std::endl;
         #endif  // __COUT_TIMES
@@ -273,23 +186,16 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix<cplx_type> & Y
         #ifdef __COUT_TIMES
             auto timer3 = CustTimer();
         #endif  // __COUT_TIMES
-        fill_jacobian_matrix_kown_sparsity_pattern(// dS_dVa_r, dS_dVa_i, dS_dVm_r, dS_dVm_i,
-                                                   //  Ybus,
-                                                   // V,
-                                                   pq, pvpq, pq_inv, pvpq_inv
-                                                   );
+        fill_jacobian_matrix_kown_sparsity_pattern(pq, pvpq);
         #ifdef __COUT_TIMES
             std::cout << "\t\t fill_jacobian_matrix_kown_sparsity_pattern : " << timer3.duration() << std::endl;
         #endif  // __COUT_TIMES
     }
-    timer_fillJ_ += timer.duration();
+    BaseNRSolver<LinearSolver>::timer_fillJ_ += timer.duration();
 }
 
-void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern(
-        // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVa_r,
-        // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVa_i,
-        // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVm_r,
-        // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVm_i,
+template<class LinearSolver>
+void BaseNRSolverSingleSlack<LinearSolver>::fill_jacobian_matrix_unkown_sparsity_pattern(
         const Eigen::SparseMatrix<cplx_type> & Ybus,
         const CplxVect & V,
         const Eigen::VectorXi & pq,
@@ -319,22 +225,22 @@ void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern(
     const int n_pq = static_cast<int>(pq.size());
     const int size_j = n_pvpq + n_pq;
 
-    const Eigen::SparseMatrix<real_type> dS_dVa_r = dS_dVa_.real();
-    const Eigen::SparseMatrix<real_type> dS_dVa_i = dS_dVa_.imag();
-    const Eigen::SparseMatrix<real_type> dS_dVm_r = dS_dVm_.real();
-    const Eigen::SparseMatrix<real_type> dS_dVm_i = dS_dVm_.imag();
+    const Eigen::SparseMatrix<real_type> dS_dVa_r = BaseNRSolver<LinearSolver>::dS_dVa_.real();
+    const Eigen::SparseMatrix<real_type> dS_dVa_i = BaseNRSolver<LinearSolver>::dS_dVa_.imag();
+    const Eigen::SparseMatrix<real_type> dS_dVm_r = BaseNRSolver<LinearSolver>::dS_dVm_.real();
+    const Eigen::SparseMatrix<real_type> dS_dVm_i = BaseNRSolver<LinearSolver>::dS_dVm_.imag();
 
     // Method (1) seems to be faster than the others
 
     // optim : if the matrix was already computed, i don't initialize it, i instead reuse as much as i can
     // i can do that because the matrix will ALWAYS have the same non zero coefficients.
     // in this if, i allocate it in a "large enough" place to avoid copy when first filling it
-    if(J_.cols() != size_j)
+    if(BaseNRSolver<LinearSolver>::J_.cols() != size_j)
     {
         need_insert = true;
-        J_ = Eigen::SparseMatrix<real_type>(size_j,size_j);
+        BaseNRSolver<LinearSolver>::J_ = Eigen::SparseMatrix<real_type>(size_j,size_j);
         // pre allocate a large enough matrix
-        J_.reserve(2*(dS_dVa_.nonZeros()+dS_dVm_.nonZeros()));
+        BaseNRSolver<LinearSolver>::J_.reserve(2*(BaseNRSolver<LinearSolver>::dS_dVa_.nonZeros()+BaseNRSolver<LinearSolver>::dS_dVm_.nonZeros()));
         // from an experiment, outerIndexPtr is initialized, with the number of columns
         // innerIndexPtr and valuePtr are not.
     }
@@ -344,7 +250,7 @@ void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern(
 
     // i fill the buffer columns per columns
     int nb_obj_this_col = 0;
-    std::vector<int> inner_index;
+    std::vector<Eigen::Index> inner_index;
     std::vector<real_type> values;
 
     // TODO use the loop provided above (in dS) if J is already initialized
@@ -357,22 +263,25 @@ void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern(
 
         // fill with the first column with the column of dS_dVa[:,pvpq[col_id]]
         // and check the row order !
-        _get_values_J(nb_obj_this_col, inner_index, values,
+        BaseNRSolver<LinearSolver>::_get_values_J(nb_obj_this_col, inner_index, values,
                       dS_dVa_r,
                       pvpq_inv, pvpq,
-                      col_id, 0);
+                      col_id,
+                      0, 
+                      0);
         // fill the rest of the rows with the first column of dS_dVa_imag[:,pq[col_id]]
-        _get_values_J(nb_obj_this_col, inner_index, values,
+        BaseNRSolver<LinearSolver>::_get_values_J(nb_obj_this_col, inner_index, values,
                       dS_dVa_i,
                       pq_inv, pvpq,
-                      col_id, n_pvpq
-                      );
+                      col_id,
+                      n_pvpq,
+                      0);
 
         // "efficient" insert of the element in the matrix
         for(int in_ind=0; in_ind < nb_obj_this_col; ++in_ind){
             int row_id = inner_index[in_ind];
-            if(need_insert) J_.insert(row_id, col_id) = values[in_ind];  // HERE FOR PERF OPTIM (1)
-            else J_.coeffRef(row_id, col_id) = values[in_ind];  // HERE FOR PERF OPTIM (1)
+            if(need_insert) BaseNRSolver<LinearSolver>::J_.insert(row_id, col_id) = values[in_ind];  // HERE FOR PERF OPTIM (1)
+            else BaseNRSolver<LinearSolver>::J_.coeffRef(row_id, col_id) = values[in_ind];  // HERE FOR PERF OPTIM (1)
             // J_.insert(row_id, col_id) = values[in_ind];  // HERE FOR PERF OPTIM (2)
             // coeffs.push_back(Eigen::Triplet<double>(row_id, col_id, values[in_ind]));   // HERE FOR PERF OPTIM (3)
         }
@@ -386,32 +295,35 @@ void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern(
         inner_index.clear();
         values.clear();
 
-        // fill with the first column with the column of dS_dVa[:,pvpq[col_id]]
+          // fill with the first column with the column of dS_dVa[:,pvpq[col_id]]
         // and check the row order !
-        _get_values_J(nb_obj_this_col, inner_index, values,
+        BaseNRSolver<LinearSolver>::_get_values_J(nb_obj_this_col, inner_index, values,
                       dS_dVm_r,
                       pvpq_inv, pq,
-                      col_id, 0);
+                      col_id,
+                      0,
+                      0);
 
         // fill the rest of the rows with the first column of dS_dVa_imag[:,pq[col_id]]
-        _get_values_J(nb_obj_this_col, inner_index, values,
+        BaseNRSolver<LinearSolver>::_get_values_J(nb_obj_this_col, inner_index, values,
                       dS_dVm_i,
                       pq_inv, pq,
-                      col_id, n_pvpq
-                      );
+                      col_id,
+                      n_pvpq,
+                      0);
 
         // "efficient" insert of the element in the matrix
         for(int in_ind=0; in_ind < nb_obj_this_col; ++in_ind){
             int row_id = inner_index[in_ind];
-            if(need_insert) J_.insert(row_id, col_id + n_pvpq) = values[in_ind];  // HERE FOR PERF OPTIM (1)
-            else J_.coeffRef(row_id, col_id + n_pvpq) = values[in_ind];  // HERE FOR PERF OPTIM (1)
+            if(need_insert) BaseNRSolver<LinearSolver>::J_.insert(row_id, col_id + n_pvpq) = values[in_ind];  // HERE FOR PERF OPTIM (1)
+            else BaseNRSolver<LinearSolver>::J_.coeffRef(row_id, col_id + n_pvpq) = values[in_ind];  // HERE FOR PERF OPTIM (1)
             // J_.insert(row_id, col_id + n_pvpq) = values[in_ind];  // HERE FOR PERF OPTIM (2)
             // coeffs.push_back(Eigen::Triplet<double>(row_id, col_id + n_pvpq, values[in_ind]));   // HERE FOR PERF OPTIM (3)
         }
     }
     // J_.setFromTriplets(coeffs.begin(), coeffs.end());  // HERE FOR PERF OPTIM (3)
-    J_.makeCompressed();
-    fill_value_map(pq, pvpq, pq_inv, pvpq_inv);
+    BaseNRSolver<LinearSolver>::J_.makeCompressed();
+    fill_value_map(pq, pvpq);
 }
 
 /**
@@ -419,20 +331,19 @@ fill the value of the `value_map_` that stores pointers to the elements of
 dS_dVa_ and dS_dVm_ to be used to fill J_
 it requires that J_ is initialized, in compressed mode.
 **/
-void BaseNRSolver::fill_value_map(
+template<class LinearSolver>
+void BaseNRSolverSingleSlack<LinearSolver>::fill_value_map(
         const Eigen::VectorXi & pq,
-        const Eigen::VectorXi & pvpq,
-        const std::vector<int> & pq_inv,
-        const std::vector<int> & pvpq_inv
+        const Eigen::VectorXi & pvpq
         )
 {
     const int n_pvpq = static_cast<int>(pvpq.size());
-    value_map_ = std::vector<cplx_type*> (J_.nonZeros());
+    BaseNRSolver<LinearSolver>::value_map_ = std::vector<cplx_type*> (BaseNRSolver<LinearSolver>::J_.nonZeros());
 
-    const int n_row = static_cast<int>(J_.cols());
+    const int n_row = static_cast<int>(BaseNRSolver<LinearSolver>::J_.cols());
     unsigned int pos_el = 0;
     for (int col_=0; col_ < n_row; ++col_){
-        for (Eigen::SparseMatrix<real_type>::InnerIterator it(J_, col_); it; ++it)
+        for (Eigen::SparseMatrix<real_type>::InnerIterator it(BaseNRSolver<LinearSolver>::J_, col_); it; ++it)
         {
             const int row_id = static_cast<int>(it.row());
             const int col_id = static_cast<int>(it.col());  // it's equal to "col_"
@@ -442,7 +353,7 @@ void BaseNRSolver::fill_value_map(
                 const int row_id_dS_dVa_r = pvpq[row_id];
                 const int col_id_dS_dVa_r = pvpq[col_id];
                 // this_el = dS_dVa_r.coeff(row_id_dS_dVa_r, col_id_dS_dVa_r);
-                value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r);
+                BaseNRSolver<LinearSolver>::value_map_[pos_el] = &BaseNRSolver<LinearSolver>::dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r);
 
                 // I don't need to perform these checks: if they failed, the element would not be in J_ in the first place
                 // const int is_row_non_null = pq_inv[row_id_dS_dVa_r];
@@ -457,19 +368,19 @@ void BaseNRSolver::fill_value_map(
                 const int row_id_dS_dVa_i = pq[row_id - n_pvpq];
                 const int col_id_dS_dVa_i = pvpq[col_id];
                 // this_el = dS_dVa_i.coeff(row_id_dS_dVa_i, col_id_dS_dVa_i);
-                value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i);
+                BaseNRSolver<LinearSolver>::value_map_[pos_el] = &BaseNRSolver<LinearSolver>::dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i);
             }else if((col_id >= n_pvpq) && (row_id < n_pvpq)){
                 // this is the J12 part (dS_dVm_r)
                 const int row_id_dS_dVm_r = pvpq[row_id];
                 const int col_id_dS_dVm_r = pq[col_id - n_pvpq];
                 // this_el = dS_dVm_r.coeff(row_id_dS_dVm_r, col_id_dS_dVm_r);
-                value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r);
+                BaseNRSolver<LinearSolver>::value_map_[pos_el] = &BaseNRSolver<LinearSolver>::dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r);
             }else if((col_id >= n_pvpq) && (row_id >= n_pvpq)){
                 // this is the J22 part (dS_dVm_i)
                 const int row_id_dS_dVm_i = pq[row_id - n_pvpq];
                 const int col_id_dS_dVm_i = pq[col_id - n_pvpq];
                 // this_el = dS_dVm_i.coeff(row_id_dS_dVm_i, col_id_dS_dVm_i);
-                value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i);
+                BaseNRSolver<LinearSolver>::value_map_[pos_el] = &BaseNRSolver<LinearSolver>::dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i);
             }
 
             // go to the next element
@@ -478,17 +389,10 @@ void BaseNRSolver::fill_value_map(
     }
 }
 
-void BaseNRSolver::fill_jacobian_matrix_kown_sparsity_pattern(
-        // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVa_r,
-        // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVa_i,
-        // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVm_r,
-        // const Eigen::Ref<const Eigen::SparseMatrix<real_type> > & dS_dVm_i,
-        // const Eigen::SparseMatrix<cplx_type> & Ybus,
-        // const CplxVect & V,
+template<class LinearSolver>
+void BaseNRSolverSingleSlack<LinearSolver>::fill_jacobian_matrix_kown_sparsity_pattern(
         const Eigen::VectorXi & pq,
-        const Eigen::VectorXi & pvpq,
-        const std::vector<int> & pq_inv,
-        const std::vector<int> & pvpq_inv
+        const Eigen::VectorXi & pvpq
     )
 {
     /**
@@ -512,26 +416,16 @@ void BaseNRSolver::fill_jacobian_matrix_kown_sparsity_pattern(
 
     const int n_pvpq = static_cast<int>(pvpq.size());
 
-    real_type * J_x_ptr = J_.valuePtr();
-    const int n_cols = static_cast<int>(J_.cols());  // equal to nrow
+    // real_type * J_x_ptr = J_.valuePtr();
+    const int n_cols = static_cast<int>(BaseNRSolver<LinearSolver>::J_.cols());  // equal to nrow
     unsigned int pos_el = 0;
     for (int col_id=0; col_id < n_cols; ++col_id){
-        for (Eigen::SparseMatrix<real_type>::InnerIterator it(J_, col_id); it; ++it)
+        for (Eigen::SparseMatrix<real_type>::InnerIterator it(BaseNRSolver<LinearSolver>::J_, col_id); it; ++it)
         {
-            const int row_id = static_cast<int>(it.row());
-            if((col_id < n_pvpq) && (row_id < n_pvpq)){
-                // this is the J11 part (dS_dVa_r)
-                J_x_ptr[pos_el] = std::real(*value_map_[pos_el]);
-            }else if((col_id < n_pvpq) && (row_id >= n_pvpq)){
-                // this is the J21 part (dS_dVa_i)
-                J_x_ptr[pos_el] = std::imag(*value_map_[pos_el]);
-            }else if((col_id >= n_pvpq) && (row_id < n_pvpq)){
-                // this is the J12 part (dS_dVm_r)
-                J_x_ptr[pos_el] = std::real(*value_map_[pos_el]);
-            }else if((col_id >= n_pvpq) && (row_id >= n_pvpq)){
-                // this is the J22 part (dS_dVm_i)
-                J_x_ptr[pos_el] = std::imag(*value_map_[pos_el]);
-            }
+            const auto row_id = it.row();
+            // only one if is necessary (magic !)
+            // top rows are "real" part and bottom rows are imaginary part (you can check)
+            it.valueRef() = row_id < n_pvpq ? std::real(*BaseNRSolver<LinearSolver>::value_map_[pos_el]) : std::imag(*BaseNRSolver<LinearSolver>::value_map_[pos_el]);
             // go to the next element
             ++pos_el;
         }
diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp
index d152926..71b3937 100644
--- a/src/BaseSolver.cpp
+++ b/src/BaseSolver.cpp
@@ -18,7 +18,7 @@ void BaseSolver::reset(){
     Va_= RealVect();  // voltage angle
     V_= RealVect();  // voltage angle
     nr_iter_ = 0;  // number of iteration performs by the algorithm
-    err_ = -1; //error message:
+    err_ = ErrorType::NotInitError; //error message:
 }
 
 RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix<cplx_type> &  Ybus,
@@ -47,11 +47,76 @@ RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix<cplx_type> &  Ybus,
     return res;
 }
 
+RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix<cplx_type> &  Ybus,
+                                  const CplxVect & V,
+                                  const CplxVect & Sbus,
+                                  Eigen::Index slack_id,  // id of the ref slack bus
+                                  real_type slack_absorbed,
+                                  const RealVect & slack_weights,
+                                  const Eigen::VectorXi & pv,
+                                  const Eigen::VectorXi & pq)
+{
+    /**
+    Remember, when this function is used:
+
+    J has the shape:
+    
+    | s | slack_bus |               | (pvpq+1,1) |   (1, pvpq)  |  (1, pq)   |
+    | l |  -------  |               |            | ------------------------- |
+    | a | J11 | J12 | = dimensions: |            | (pvpq, pvpq) | (pvpq, pq) |
+    | c | --------- |               |   ------   | ------------------------- |
+    | k | J21 | J22 |               |  (pq, 1)   |  (pq, pvpq)  | (pq, pq)   |
+
+    python implementation:
+    `J11` = dS_dVa[array([pvpq]).T, pvpq].real
+    `J12` = dS_dVm[array([pvpq]).T, pq].real
+    `J21` = dS_dVa[array([pq]).T, pvpq].imag
+    `J22` = dS_dVm[array([pq]).T, pq].imag
+
+    `slack_bus` = is the representation of the equation for the reference slack bus dS_dVa[slack_bus_id, pvpq].real
+    and dS_dVm[slack_bus_id, pq].real
+
+    `slack` is the representation of the equation connecting together the slack buses (represented by slack_weights)
+    the remaining pq components are all 0.
+
+    So i need to make sure that the returned F has the same properties:
+
+    - first element is the slack bus
+    - then it's Va of pv buses
+    - then it's Va of pq buses
+    - then it's Vm of pq buses
+
+    **/
+
+    // TODO factorize with above (ugly copy paste)
+    auto timer = CustTimer();
+    auto npv = pv.size();
+    auto npq = pq.size();
+
+    // compute the mismatch
+    CplxVect tmp = Ybus * V;  // this is a vector
+    tmp = tmp.array().conjugate();  // i take the conjugate
+    auto mis = V.array() * tmp.array() - Sbus.array() + slack_absorbed * slack_weights.array();
+    RealVect real_ = mis.real();
+    RealVect imag_ = mis.imag();
+
+    // build and fill the result
+    RealVect res(npv + 2 * npq + 1); // slack adds one component hence the '+1' also bellow)
+    res.segment(1, npv) = real_(pv);
+    res.segment(npv + 1, npq) = real_(pq);
+    res.segment(npv + npq + 1, npq) = imag_(pq);
+    res(0) = real_(slack_id);  // slack bus is first variable
+    timer_Fx_ += timer.duration();
+    return res;
+    
+}
+
 bool BaseSolver::_check_for_convergence(const RealVect & F,
                                         real_type tol)
 {
     auto timer = CustTimer();
-    bool res =  F.lpNorm<Eigen::Infinity>()  < tol;
+    const auto norm_inf = F.lpNorm<Eigen::Infinity>();
+    bool res =  norm_inf  < tol;
     timer_check_ += timer.duration();
     return res;
 }
diff --git a/src/BaseSolver.h b/src/BaseSolver.h
index 431be81..3cf3a16 100644
--- a/src/BaseSolver.h
+++ b/src/BaseSolver.h
@@ -28,8 +28,7 @@
 #include "CustTimer.h"
 #include "BaseConstants.h"
 
-// TODO make err_ more explicit: use an enum
-
+enum class ErrorType {NoError, SingularMatrix, TooManyIterations, InifiniteValue, SolverAnalyze, SolverFactor, SolverReFactor, SolverSolve, NotInitError, LicenseError};
 /**
 This class represents a solver to compute powerflow.
 
@@ -38,9 +37,9 @@ It can be derived for different usecase, for example for DC powerflow, AC powerf
 class BaseSolver : public BaseConstants
 {
     public:
-        BaseSolver():BaseConstants(),n_(-1),err_(-1),timer_Fx_(0.),timer_solve_(0.),timer_check_(0.),timer_total_nr_(0.){};
+        BaseSolver():BaseConstants(),n_(-1),err_(ErrorType::NotInitError),timer_Fx_(0.),timer_solve_(0.),timer_check_(0.),timer_total_nr_(0.){};
 
-        ~BaseSolver(){}
+        virtual ~BaseSolver(){}
 
         Eigen::Ref<const RealVect> get_Va() const{
             return Va_;
@@ -51,15 +50,18 @@ class BaseSolver : public BaseConstants
         Eigen::Ref<const CplxVect> get_V() const{
             return V_;
         }
-        int get_error(){
+        ErrorType get_error() const {
             return err_;
         }
-        int get_nb_iter(){
+        int get_nb_iter() const {
             return nr_iter_;
         }
 
-        virtual
-        std::tuple<double, double, double, double> get_timers()
+        bool converged() const{
+            return err_ == ErrorType::NoError;
+        }
+
+        std::tuple<double, double, double, double> get_timers() const
         {
             // TODO change the order of the timers here!
             auto res = std::tuple<double, double, double, double>(
@@ -71,6 +73,8 @@ class BaseSolver : public BaseConstants
         bool compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
                         CplxVect & V,  // store the results of the powerflow and the Vinit !
                         const CplxVect & Sbus,
+                        const Eigen::VectorXi & slack_ids,
+                        const RealVect & slack_weights,
                         const Eigen::VectorXi & pv,
                         const Eigen::VectorXi & pq,
                         int max_iter,
@@ -79,10 +83,7 @@ class BaseSolver : public BaseConstants
 
         virtual
         void reset();
-
-        bool converged(){
-            return err_ == 0;
-        }
+        
     protected:
         virtual void reset_timer(){
             timer_Fx_ = 0.;
@@ -91,6 +92,21 @@ class BaseSolver : public BaseConstants
             timer_total_nr_ = 0.;
         }
 
+        bool is_linear_solver_valid(){
+            // bool res = true;
+            // if((err_ == ErrorType::NotInitError) || (err_ == ErrorType::LicenseError)) res = false;  // cannot use a non intialize solver
+            // return res;
+            return (err_ != ErrorType::LicenseError);
+        }
+        RealVect _evaluate_Fx(const Eigen::SparseMatrix<cplx_type> &  Ybus,
+                              const CplxVect & V,
+                              const CplxVect & Sbus,
+                              Eigen::Index slack_id,  // id of the slack bus
+                              real_type slack_absorbed,
+                              const RealVect & slack_weights,
+                              const Eigen::VectorXi & pv,
+                              const Eigen::VectorXi & pq);
+
         RealVect _evaluate_Fx(const Eigen::SparseMatrix<cplx_type> &  Ybus,
                               const CplxVect & V,
                               const CplxVect & Sbus,
@@ -115,6 +131,36 @@ class BaseSolver : public BaseConstants
                                  const Eigen::VectorXi & pq,
                                  unsigned int nb_bus);
 
+        /**
+        When there are multiple slacks, add the other "slack buses" in the PV buses indexes
+        (behaves as if only the first element is used for the slack !!!)
+        **/
+        Eigen::VectorXi retrieve_pv_with_slack(const Eigen::VectorXi & slack_ids, 
+                                               const Eigen::VectorXi & pv) const {
+            Eigen::VectorXi my_pv = pv;
+            if(slack_ids.size() > 1){
+                const auto nb_slack_added = slack_ids.size() - 1;
+                my_pv = Eigen::VectorXi(pv.size() + nb_slack_added);
+                for(auto i = 0; i < nb_slack_added; ++i){
+                    my_pv(i) = slack_ids[i+1];
+                }
+                for(auto i = 0; i < pv.size(); ++i){
+                    my_pv(i + nb_slack_added) = pv[i];
+                }
+            }
+            return my_pv;
+        }
+
+        /**
+        When there are multiple slacks, add the other "slack buses" in the PV buses indexes
+        **/
+        Eigen::VectorXi add_slack_to_pv(const Eigen::VectorXi & slack_ids, 
+                                        const Eigen::VectorXi & pv) const {
+            Eigen::VectorXi my_pv = Eigen::VectorXi(slack_ids.size() + pv.size());
+            my_pv << slack_ids, pv;
+            return my_pv;
+        }
+        
     protected:
         // solver initialization
         int n_;
@@ -125,7 +171,7 @@ class BaseSolver : public BaseConstants
         CplxVect V_;  // complex voltage
 
         int nr_iter_;  // number of iteration performs by the solver (may vary depending on the solver)
-        int err_; //error message:
+        ErrorType err_; //error message:
         // -1 : the solver has not been initialized (call initialize in this case)
         // 0 everything ok
         // 1: i can't factorize the matrix (klu_factor)
diff --git a/src/ChooseSolver.cpp b/src/ChooseSolver.cpp
index 3396606..165f787 100644
--- a/src/ChooseSolver.cpp
+++ b/src/ChooseSolver.cpp
@@ -7,465 +7,3 @@
 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
 #include "ChooseSolver.h"
-#include <iostream>
-// template specialization
-template<SolverType ST>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp() const
-{
-    throw std::runtime_error("ChooseSolver::get_V_tmp: Unknown solver type.");
-}
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::SparseLU>() const
-{
-    return _solver_lu.get_V();
-}
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::KLU>() const
-{
-    #ifndef KLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_V_tmp: Impossible to use the KLU solver, that is not available on your plaform.");
-    #else
-        return _solver_klu.get_V();
-    #endif
-}
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::NICSLU>() const
-{
-    #ifndef NICSLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_V_tmp: Impossible to use the NICSLU solver, that is not available on your plaform.");
-    #else
-        return _solver_nicslu.get_V();
-    #endif
-}
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::GaussSeidel>() const
-{
-    return _solver_gaussseidel.get_V();
-}
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::GaussSeidelSynch>() const
-{
-    return _solver_gaussseidelsynch.get_V();
-}
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::DC>() const
-{
-    return _solver_dc.get_V();
-}
-
-
-template<SolverType ST>
-bool ChooseSolver::compute_pf_tmp(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                                  CplxVect & V,
-                                  const CplxVect & Sbus,
-                                  const Eigen::VectorXi & pv,
-                                  const Eigen::VectorXi & pq,
-                                  int max_iter,
-                                  real_type tol
-                                  )
-{
-    throw std::runtime_error("ChooseSolver::compute_pf_tmp: Unknown solver type.");
-}
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::SparseLU>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       )
-{
-    return _solver_lu.compute_pf(Ybus, V, Sbus, pv, pq, max_iter, tol);
-}
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::GaussSeidel>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       )
-{
-    return _solver_gaussseidel.compute_pf(Ybus, V, Sbus, pv, pq, max_iter, tol);
-}
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::GaussSeidelSynch>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       )
-{
-    return _solver_gaussseidelsynch.compute_pf(Ybus, V, Sbus, pv, pq, max_iter, tol);
-}
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::DC>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       )
-{
-    return _solver_dc.compute_pf(Ybus, V, Sbus, pv, pq, max_iter, tol);
-}
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::KLU>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       )
-{
-    #ifndef KLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::compute_pf_tmp: Impossible to use the KLU solver, that is not available on your plaform.");
-    #else
-        return _solver_klu.compute_pf(Ybus, V, Sbus, pv, pq, max_iter, tol);
-    #endif
-}
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::NICSLU>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       )
-{
-    #ifndef NICSLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::compute_pf_tmp: Impossible to use the NICSLU solver, that is not available on your plaform.");
-    #else
-        return _solver_nicslu.compute_pf(Ybus, V, Sbus, pv, pq, max_iter, tol);
-    #endif
-}
-
-template<SolverType ST>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp()
-{
-    throw std::runtime_error("ChooseSolver::get_J_tmp: Unknown solver type.");
-}
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::SparseLU>()
-{
-    return _solver_lu.get_J();
-}
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::DC>()
-{
-    throw std::runtime_error("ChooseSolver::get_J_tmp: There is not Jacobian matrix for a DC powerflow.");
-}
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::GaussSeidel>()
-{
-    throw std::runtime_error("ChooseSolver::get_J_tmp: There is not Jacobian matrix for the GaussSeidel powerflow.");
-}
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::GaussSeidelSynch>()
-{
-    throw std::runtime_error("hooseSolver::get_J_tmp: There is not Jacobian matrix for the GaussSeidelSynch powerflow.");
-}
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::KLU>()
-{
-    #ifndef KLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_J_tmp: Impossible to use the KLU solver, that is not available on your plaform.");
-    #else
-        return _solver_klu.get_J();
-    #endif
-}
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::NICSLU>()
-{
-    #ifndef NICSLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_J_tmp: Impossible to use the NICSLU solver, that is not available on your plaform.");
-    #else
-        return _solver_nicslu.get_J();
-    #endif
-}
-
-template<SolverType ST>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp() const
-{
-    throw std::runtime_error("ChooseSolver::get_Va_tmp: Unknown solver type.");
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::SparseLU>() const
-{
-    return _solver_lu.get_Va();
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::GaussSeidel>() const
-{
-    return _solver_gaussseidel.get_Va();
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::GaussSeidelSynch>() const
-{
-    return _solver_gaussseidelsynch.get_Va();
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::DC>() const
-{
-    return _solver_dc.get_Va();
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::KLU>() const
-{
-    #ifndef KLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_Va_tmp: Impossible to use the KLU solver, that is not available on your plaform.");
-    #else
-        return _solver_klu.get_Va();
-    #endif
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::NICSLU>() const
-{
-    #ifndef NICSLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_Va_tmp: Impossible to use the NICSLU solver, that is not available on your plaform.");
-    #else
-        return _solver_nicslu.get_Va();
-    #endif
-}
-template<SolverType ST>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp() const
-{
-    throw std::runtime_error("ChooseSolver::get_Vm_tmp: Unknown solver type.");
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::SparseLU>() const
-{
-    return _solver_lu.get_Vm();
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::DC>() const
-{
-    return _solver_dc.get_Vm();
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::GaussSeidel>() const
-{
-    return _solver_gaussseidel.get_Vm();
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::GaussSeidelSynch>() const
-{
-    return _solver_gaussseidelsynch.get_Vm();
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::KLU>() const
-{
-    #ifndef KLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_Vm_tmp: Impossible to use the KLU solver, that is not available on your plaform.");
-    #else
-        return _solver_klu.get_Vm();
-    #endif
-}
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::NICSLU>() const
-{
-    #ifndef NICSLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_Vm_tmp: Impossible to use the NICSLU solver, that is not available on your plaform.");
-    #else
-        return _solver_nicslu.get_Vm();
-    #endif
-}
-
-template<SolverType ST>
-double ChooseSolver::get_computation_time_tmp()
-{
-    throw std::runtime_error("ChooseSolver::get_computation_time_tmp: Unknown solver type.");
-}
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::SparseLU>()
-{
-    const auto & res =  _solver_lu.get_timers();
-    return std::get<3>(res);
-}
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::GaussSeidel>()
-{
-    const auto & res =  _solver_gaussseidel.get_timers();
-    return std::get<3>(res);
-}
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::GaussSeidelSynch>()
-{
-    const auto & res =  _solver_gaussseidelsynch.get_timers();
-    return std::get<3>(res);
-}
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::KLU>()
-{
-    #ifndef KLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_computation_time_tmp: Impossible to use the KLU solver, that is not available on your plaform.");
-    #else
-        const auto & res =  _solver_klu.get_timers();
-        return std::get<3>(res);
-    #endif
-}
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::NICSLU>()
-{
-    #ifndef NICSLU_SOLVER_AVAILABLE
-        // I asked result of KLU solver without the required libraries
-        throw std::runtime_error("ChooseSolver::get_computation_time_tmp: Impossible to use the NICSLU solver, that is not available on your plaform.");
-    #else
-        const auto & res =  _solver_nicslu.get_timers();
-        return std::get<3>(res);
-    #endif
-}
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::DC>()
-{
-   const auto & res =  _solver_dc.get_timers();
-   return std::get<3>(res);
-}
-//TODO refactor all the functions above by making a template function "get_solver"
-
-// function definition
-Eigen::Ref<const CplxVect> ChooseSolver::get_V() const {
-    check_right_solver();
-    if(_solver_type == SolverType::SparseLU)
-    {
-         return get_V_tmp<SolverType::SparseLU>();
-    }else if(_solver_type == SolverType::KLU){
-         return get_V_tmp<SolverType::KLU>();
-    }else if(_solver_type == SolverType::NICSLU){
-         return get_V_tmp<SolverType::NICSLU>();
-    }else if(_solver_type == SolverType::GaussSeidel){
-         return get_V_tmp<SolverType::GaussSeidel>();
-    }else if(_solver_type == SolverType::GaussSeidelSynch){
-         return get_V_tmp<SolverType::GaussSeidelSynch>();
-    }else if(_solver_type == SolverType::DC){
-         return get_V_tmp<SolverType::DC>();
-    }else{
-        throw std::runtime_error("ChooseSolver::get_V: Unknown solver type.");
-    }
-}
-
-Eigen::Ref<const RealVect> ChooseSolver::get_Va() const {
-    check_right_solver();
-    if(_solver_type == SolverType::SparseLU)
-    {
-         return get_Va_tmp<SolverType::SparseLU>();
-    }else if(_solver_type == SolverType::KLU){
-         return get_Va_tmp<SolverType::KLU>();
-    }else if(_solver_type == SolverType::NICSLU){
-         return get_Va_tmp<SolverType::NICSLU>();
-    }else if(_solver_type == SolverType::GaussSeidel){
-         return get_Va_tmp<SolverType::GaussSeidel>();
-    }else if(_solver_type == SolverType::GaussSeidelSynch){
-         return get_Va_tmp<SolverType::GaussSeidelSynch>();
-    }else if(_solver_type == SolverType::DC){
-         return get_Va_tmp<SolverType::DC>();
-    }else{
-        throw std::runtime_error("ChooseSolver::get_Va: Unknown solver type.");
-    }
-}
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm() const {
-    check_right_solver();
-    if(_solver_type == SolverType::SparseLU)
-    {
-         return get_Vm_tmp<SolverType::SparseLU>();
-    }else if(_solver_type == SolverType::KLU){
-         return get_Vm_tmp<SolverType::KLU>();
-    }else if(_solver_type == SolverType::NICSLU){
-         return get_Vm_tmp<SolverType::NICSLU>();
-    }else if(_solver_type == SolverType::GaussSeidel){
-         return get_Vm_tmp<SolverType::GaussSeidel>();
-    }else if(_solver_type == SolverType::GaussSeidelSynch){
-         return get_Vm_tmp<SolverType::GaussSeidelSynch>();
-    }else if(_solver_type == SolverType::DC){
-         return get_Vm_tmp<SolverType::DC>();
-    }else{
-        throw std::runtime_error("ChooseSolver::get_Vm: Unknown solver type.");
-    }
-}
-
-bool ChooseSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                              CplxVect & V,
-                              const CplxVect & Sbus,
-                              const Eigen::VectorXi & pv,
-                              const Eigen::VectorXi & pq,
-                              int max_iter,
-                              real_type tol
-                              )
-{
-    _type_used_for_nr = _solver_type;
-    if(_solver_type == SolverType::SparseLU)
-    {
-        return compute_pf_tmp<SolverType::SparseLU>(Ybus, V, Sbus, pv, pq, max_iter, tol);
-    }else if(_solver_type == SolverType::KLU){
-        return compute_pf_tmp<SolverType::KLU>(Ybus, V, Sbus, pv, pq, max_iter, tol);
-    }else if(_solver_type == SolverType::NICSLU){
-        return compute_pf_tmp<SolverType::NICSLU>(Ybus, V, Sbus, pv, pq, max_iter, tol);
-    }else if(_solver_type == SolverType::GaussSeidel){
-        return compute_pf_tmp<SolverType::GaussSeidel>(Ybus, V, Sbus, pv, pq, max_iter, tol);
-    }else if(_solver_type == SolverType::GaussSeidelSynch){
-        return compute_pf_tmp<SolverType::GaussSeidelSynch>(Ybus, V, Sbus, pv, pq, max_iter, tol);
-    }else if(_solver_type == SolverType::DC){
-        return compute_pf_tmp<SolverType::DC>(Ybus, V, Sbus, pv, pq, max_iter, tol);
-    }else{
-        throw std::runtime_error("ChooseSolver::compute_pf: Unknown solver type.");
-    }
-}
-
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J(){
-    check_right_solver();
-    if(_solver_type == SolverType::SparseLU)
-    {
-         return get_J_tmp<SolverType::SparseLU>();
-    }else if(_solver_type == SolverType::KLU){
-         return get_J_tmp<SolverType::KLU>();
-    }else if(_solver_type == SolverType::NICSLU){
-         return get_J_tmp<SolverType::NICSLU>();
-    }else if(_solver_type == SolverType::GaussSeidel){
-         return get_J_tmp<SolverType::GaussSeidel>();
-    }else if(_solver_type == SolverType::GaussSeidelSynch){
-         return get_J_tmp<SolverType::GaussSeidelSynch>();
-    }else if(_solver_type == SolverType::DC){
-         return get_J_tmp<SolverType::DC>();
-    }else{
-        throw std::runtime_error("ChooseSolver::get_J: Unknown solver type.");
-    }
-}
-
-double ChooseSolver::get_computation_time()
-{
-    check_right_solver();
-    if(_solver_type == SolverType::SparseLU)
-    {
-         return get_computation_time_tmp<SolverType::SparseLU>();
-    }else if(_solver_type == SolverType::KLU){
-         return get_computation_time_tmp<SolverType::KLU>();
-    }else if(_solver_type == SolverType::NICSLU){
-         return get_computation_time_tmp<SolverType::NICSLU>();
-    }else if(_solver_type == SolverType::GaussSeidel){
-         return get_computation_time_tmp<SolverType::GaussSeidel>();
-    }else if(_solver_type == SolverType::GaussSeidelSynch){
-         return get_computation_time_tmp<SolverType::GaussSeidelSynch>();
-    }else if(_solver_type == SolverType::DC){
-         return get_computation_time_tmp<SolverType::DC>();
-    }else{
-        throw std::runtime_error("ChooseSolver::get_computation_time: Unknown solver type.");
-    }
-}
diff --git a/src/ChooseSolver.h b/src/ChooseSolver.h
index 7a04722..f40b104 100644
--- a/src/ChooseSolver.h
+++ b/src/ChooseSolver.h
@@ -12,18 +12,17 @@
 #include<vector>
 
 // import newton raphson solvers using different linear algebra solvers
-#ifdef KLU_SOLVER_AVAILABLE
-    #include "KLUSolver.h"
-#endif
-#ifdef NICSLU_SOLVER_AVAILABLE
-    #include "NICSLUSolver.h"
-#endif
-#include "SparseLUSolver.h"
+#include "Solvers.h"
 #include "GaussSeidelSolver.h"
 #include "GaussSeidelSynchSolver.h"
 #include "DCSolver.h"
 
-enum class SolverType { SparseLU, KLU, GaussSeidel, DC, GaussSeidelSynch, NICSLU};
+enum class SolverType {SparseLU, KLU, GaussSeidel, DC, GaussSeidelSynch, NICSLU,
+                       SparseLUSingleSlack, KLUSingleSlack, NICSLUSingleSlack,
+                       KLUDC, NICSLUDC};
+
+// TODO define a template class instead of these weird stuff !!!
+// TODO export all methods from base class !
 
 
 // NB: when adding a new solver, you need to specialize the *tmp method (eg get_Va_tmp)
@@ -33,239 +32,259 @@ enum class SolverType { SparseLU, KLU, GaussSeidel, DC, GaussSeidelSynch, NICSLU
 class ChooseSolver
 {
     public:
-         ChooseSolver():_solver_type(SolverType::SparseLU),_type_used_for_nr(SolverType::SparseLU){};
+         ChooseSolver():
+             _solver_type(SolverType::SparseLU),
+             _type_used_for_nr(SolverType::SparseLU)
+             {};
 
-        std::vector<SolverType> available_solvers()
+        std::vector<SolverType> available_solvers() const
         {
             std::vector<SolverType> res;
+            res.reserve(11);
+
             res.push_back(SolverType::SparseLU);
             res.push_back(SolverType::GaussSeidel);
             res.push_back(SolverType::DC);
             res.push_back(SolverType::GaussSeidelSynch);
+            res.push_back(SolverType::SparseLUSingleSlack);
             #ifdef KLU_SOLVER_AVAILABLE
                 res.push_back(SolverType::KLU);
+                res.push_back(SolverType::KLUSingleSlack);
+                res.push_back(SolverType::KLUDC);
             #endif
             #ifdef NICSLU_SOLVER_AVAILABLE
                 res.push_back(SolverType::NICSLU);
+                res.push_back(SolverType::NICSLUSingleSlack);
+                res.push_back(SolverType::NICSLUDC);
             #endif
             return res;
         }
+        
+        bool is_dc(const SolverType & type){
+            bool res;
+            res = (type == SolverType::DC) || (type == SolverType::KLUDC) || (type == SolverType::NICSLUDC);
+            return res;
+        }
         SolverType get_type() const {return _solver_type;}
+
         void change_solver(const SolverType & type)
         {
             if(type == _solver_type) return;
-            std::string msg;
             #ifndef KLU_SOLVER_AVAILABLE
-                // TODO better handling of that :-/
-                msg = "Impossible to change for the KLU solver, that is not available on your platform.";
-                if(type == SolverType::KLU) throw std::runtime_error(msg);
+                if((type == SolverType::KLU) || (type == SolverType::KLUDC) || (type == SolverType::KLUSingleSlack)){
+                    std::string msg;
+                    msg = "Impossible to change for the KLU solver, that is not available on your platform.";
+                    throw std::runtime_error(msg);
+                }
             #endif
+
             #ifndef NICSLU_SOLVER_AVAILABLE
-                // TODO better handling of that :-/
-                msg = "Impossible to change for the NICSLU solver, that is not available on your platform.";
-                if(type == SolverType::NICSLU) throw std::runtime_error(msg);
+                if((type == SolverType::NICSLU) || (type == SolverType::NICSLUDC) || (type ==  SolverType::NICSLUSingleSlack)){
+                    std::string msg;
+                    msg = "Impossible to change for the NICSLU solver, that is not available on your platform.";
+                    throw std::runtime_error(msg);
+                }
             #endif
+
+            // now switch the union (see https://en.cppreference.com/w/cpp/language/union)
+            reset();
+
+            // and assign the right solver
             _solver_type = type;
         }
+
         void reset()
         {
-            // reset all the solvers available
-            _solver_lu.reset();
-            _solver_gaussseidel.reset();
-            _solver_dc.reset();
-            #ifdef KLU_SOLVER_AVAILABLE
-                _solver_klu.reset();
-            #endif  // KLU_SOLVER_AVAILABLE
-            #ifdef NICSLU_SOLVER_AVAILABLE
-                _solver_nicslu.reset();
-            #endif  // NICSLU_SOLVER_AVAILABLE
-            _solver_gaussseidelsynch.reset();
+            auto p_solver = get_prt_solver("reset", false);  // i should not check if it's the right solver when resetting (used in change_solver)
+            return p_solver -> reset();
         }
 
-        // forward to the right solver used
-        //TODO inline all of that
-        bool compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                        CplxVect & V,
-                        const CplxVect & Sbus,
+        // benefit from dynamic stuff and inheritance by having a method that returns a BaseSolver *
+        bool compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,  // size (nb_bus, nb_bus)
+                        CplxVect & V,  // size nb_bus
+                        const CplxVect & Sbus,  // size nb_bus
+                        const Eigen::VectorXi & slack_ids,  // bus ids where thare are slack bus
+                        const RealVect & slack_weights,  // slack weights (size nb_bus)
                         const Eigen::VectorXi & pv,
                         const Eigen::VectorXi & pq,
                         int max_iter,
                         real_type tol
-                        );
-        Eigen::Ref<const CplxVect> get_V() const;
-        Eigen::Ref<const RealVect> get_Va() const;
-        Eigen::Ref<const RealVect> get_Vm() const;
-        Eigen::SparseMatrix<real_type> get_J();
-        double get_computation_time();
+                        )
+        {
+            _type_used_for_nr = _solver_type;
+            auto p_solver = get_prt_solver("compute_pf", true);
+            return p_solver -> compute_pf(Ybus, V, Sbus, slack_ids, slack_weights, pv, pq, max_iter, tol);
+        }
 
-    private:
-        void check_right_solver() const
+        Eigen::Ref<const CplxVect> get_V() const
         {
-            if(_solver_type != _type_used_for_nr) throw std::runtime_error("ChooseSolver: Solver mismatch: current solver is not the last solver used to perform a powerflow");
+            auto p_solver = get_prt_solver("get_V", true);
+            return p_solver -> get_V();
         }
 
-        template<SolverType ST>
-        Eigen::Ref<const CplxVect> get_V_tmp() const;
+        Eigen::Ref<const RealVect> get_Va() const
+        {
+            auto p_solver = get_prt_solver( "get_Va", true);
+            return p_solver -> get_Va();
+        }
+        Eigen::Ref<const RealVect> get_Vm() const
+        {
+            auto p_solver = get_prt_solver("get_Vm", true);
+            return p_solver -> get_Vm();
+        }
+        Eigen::Ref<const Eigen::SparseMatrix<real_type> > get_J() const
+        {
+            check_right_solver( "get_J");
+            if(_solver_type == SolverType::SparseLU){
+                return _solver_lu.get_J();}
+            #ifdef KLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::KLU){
+                return _solver_klu.get_J();}
+            else if(_solver_type == SolverType::KLUSingleSlack){
+                return _solver_klu_single.get_J();}
+            #endif  // KLU_SOLVER_AVAILABLE
+            #ifdef NICSLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::NICSLU){
+                return _solver_nicslu.get_J();}
+            else if(_solver_type == SolverType::NICSLUSingleSlack){
+                return _solver_nicslu_single.get_J();}
+            #endif // NICSLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::GaussSeidel){
+                throw std::runtime_error("ChooseSolver::get_J: There is not Jacobian matrix for the GaussSeidel powerflow.");}
+            else if(_solver_type == SolverType::DC){
+                throw std::runtime_error("ChooseSolver::get_J: There is not Jacobian matrix for the DC powerflow.");}
+            else if(_solver_type == SolverType::GaussSeidelSynch){
+                throw std::runtime_error("ChooseSolver::get_J: There is not Jacobian matrix for the GaussSeidelSynch powerflow.");}
+            else throw std::runtime_error("Unknown solver type encountered");
+        }
 
-        template<SolverType ST>
-        Eigen::Ref<const RealVect> get_Va_tmp() const;
+        /** apparently i cannot pass a const ref for a sparse matrix in python**/
+        Eigen::SparseMatrix<real_type> get_J_python() const{
+            Eigen::SparseMatrix<real_type> res = get_J();
+            return res;
+        }
+        double get_computation_time() const
+        {
+            auto p_solver = get_prt_solver("get_computation_time", true);
+            const auto & res =  p_solver -> get_timers();
+            return std::get<3>(res);
+        }
 
-        template<SolverType ST>
-        Eigen::Ref<const RealVect> get_Vm_tmp() const;
+        ErrorType get_error() const{
+            auto p_solver = get_prt_solver("get_error", true);
+            return p_solver -> get_error();
+        }
+        
+        int get_nb_iter() const {
+            auto p_solver = get_prt_solver("get_nb_iter", true);
+            return p_solver -> get_nb_iter();
+        }
 
-        template<SolverType ST>
-        Eigen::SparseMatrix<real_type> get_J_tmp();
+        bool converged() const{
+            auto p_solver = get_prt_solver("converged", true);
+            return p_solver -> converged();
+        }
 
-        template<SolverType ST>
-        double get_computation_time_tmp();
+    private:
+        void check_right_solver(const std::string & error_msg) const
+        {
+            if(_solver_type != _type_used_for_nr) throw std::runtime_error("ChooseSolver: Solver mismatch when calling '"+error_msg+"': current solver is not the last solver used to perform a powerflow");
+            
+            #ifndef KLU_SOLVER_AVAILABLE
+                if(_solver_type == SolverType::KLU){
+                    std::string msg = "Impossible to use the KLU solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                } else if(_solver_type == SolverType::KLUSingleSlack){
+                    std::string msg = "Impossible to use the KLU solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                } else if(_solver_type == SolverType::KLUDC){
+                    std::string msg = "Impossible to use the KLU solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                }
+            #endif  // KLU_SOLVER_AVAILABLE
 
-        template<SolverType ST>
-        bool compute_pf_tmp(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                            CplxVect & V,
-                            const CplxVect & Sbus,
-                            const Eigen::VectorXi & pv,
-                            const Eigen::VectorXi & pq,
-                            int max_iter,
-                            real_type tol
-                            );
+            #ifndef NICSLU_SOLVER_AVAILABLE
+                if(_solver_type == SolverType::NICSLU){
+                    std::string msg = "Impossible to use the NICSLU solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                } else if(_solver_type == SolverType::NICSLUSingleSlack){
+                    std::string msg = "Impossible to use the NICSLU solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                } else if(_solver_type == SolverType::NICSLUDC){
+                    std::string msg = "Impossible to use the NICSLU solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                }
+            #endif  // NICSLU_SOLVER_AVAILABLE
+        }
 
+    protected:
+        /**
+        returns a pointer to the current solver used
+        **/
+        const BaseSolver * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) const {
+            if (check_right_solver_) check_right_solver(error_msg);
+            const BaseSolver * res;
+            if(_solver_type == SolverType::SparseLU){res = &_solver_lu;}
+            else if(_solver_type == SolverType::SparseLUSingleSlack){res = &_solver_lu_single;}
+            else if(_solver_type == SolverType::DC){res = &_solver_dc;}
+            #ifdef KLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::KLU){res = & _solver_klu;}
+            else if(_solver_type == SolverType::KLUSingleSlack){res = &_solver_klu_single;}
+            else if(_solver_type == SolverType::KLUDC){res = &_solver_klu_dc;}
+            #endif  // KLU_SOLVER_AVAILABLE
+            #ifdef NICSLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::NICSLU){res = &_solver_nicslu;}
+            else if(_solver_type == SolverType::NICSLUSingleSlack){res = &_solver_nicslu_single;}
+            else if(_solver_type == SolverType::NICSLUDC){res = &_solver_nicslu_dc;}
+            #endif // NICSLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::GaussSeidel){res = &_solver_gaussseidel;}
+            else if(_solver_type == SolverType::GaussSeidelSynch){res = &_solver_gaussseidelsynch;}
+            else throw std::runtime_error("Unknown solver type encountered");
+            return res;
+        }
+        BaseSolver * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) {
+            if (check_right_solver_) check_right_solver(error_msg);
+            BaseSolver * res;
+            if(_solver_type == SolverType::SparseLU){res = &_solver_lu;}
+            else if(_solver_type == SolverType::SparseLUSingleSlack){res = &_solver_lu_single;}
+            else if(_solver_type == SolverType::DC){res = &_solver_dc;}
+            #ifdef KLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::KLU){res = & _solver_klu;}
+            else if(_solver_type == SolverType::KLUSingleSlack){res = &_solver_klu_single;}
+            else if(_solver_type == SolverType::KLUDC){res = &_solver_klu_dc;}
+            #endif  // KLU_SOLVER_AVAILABLE
+            #ifdef NICSLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::NICSLU){res = &_solver_nicslu;}
+            else if(_solver_type == SolverType::NICSLUSingleSlack){res = &_solver_nicslu_single;}
+            else if(_solver_type == SolverType::NICSLUDC){res = &_solver_nicslu_dc;}
+            #endif // NICSLU_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::GaussSeidel){res = &_solver_gaussseidel;}
+            else if(_solver_type == SolverType::GaussSeidelSynch){res = &_solver_gaussseidelsynch;}
+            else throw std::runtime_error("Unknown solver type encountered");
+            return res;
+        }
     protected:
         SolverType _solver_type;
         SolverType _type_used_for_nr;
 
         // all types
+        // TODO have a way to use Union here https://en.cppreference.com/w/cpp/language/union
         SparseLUSolver _solver_lu;
+        SparseLUSolverSingleSlack _solver_lu_single;
         GaussSeidelSolver _solver_gaussseidel;
         GaussSeidelSynchSolver _solver_gaussseidelsynch;
         DCSolver _solver_dc;
         #ifdef KLU_SOLVER_AVAILABLE
             KLUSolver _solver_klu;
+            KLUSolverSingleSlack _solver_klu_single;
+            KLUDCSolver _solver_klu_dc;
         #endif  // KLU_SOLVER_AVAILABLE
         #ifdef NICSLU_SOLVER_AVAILABLE
-            KLUSolver _solver_nicslu;
+            NICSLUSolver _solver_nicslu;
+            NICSLUSolverSingleSlack _solver_nicslu_single;
+            NICSLUDCSolver _solver_nicslu_dc;
         #endif  // NICSLU_SOLVER_AVAILABLE
 
 };
 
-
-// template specialization
-
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::SparseLU>() const;
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::KLU>() const;
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::NICSLU>() const;
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::GaussSeidel>() const;
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::GaussSeidelSynch>() const;
-template<>
-Eigen::Ref<const CplxVect> ChooseSolver::get_V_tmp<SolverType::DC>() const;
-
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::SparseLU>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       );
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::KLU>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       );
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::NICSLU>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       );
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::GaussSeidel>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       );
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::GaussSeidelSynch>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       );
-template<>
-bool ChooseSolver::compute_pf_tmp<SolverType::DC>(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                       CplxVect & V,
-                       const CplxVect & Sbus,
-                       const Eigen::VectorXi & pv,
-                       const Eigen::VectorXi & pq,
-                       int max_iter,
-                       real_type tol
-                       );
-
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::SparseLU>();
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::KLU>();
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::NICSLU>();
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::GaussSeidel>();
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::GaussSeidelSynch>();
-template<>
-Eigen::SparseMatrix<real_type> ChooseSolver::get_J_tmp<SolverType::DC>();
-
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::SparseLU>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::KLU>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::NICSLU>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::GaussSeidel>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::GaussSeidelSynch>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Va_tmp<SolverType::DC>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::SparseLU>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::KLU>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::GaussSeidel>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::GaussSeidelSynch>() const;
-template<>
-Eigen::Ref<const RealVect> ChooseSolver::get_Vm_tmp<SolverType::DC>() const;
-
-// computation times
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::SparseLU>();
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::KLU>();
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::NICSLU>();
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::GaussSeidel>();
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::GaussSeidelSynch>();
-template<>
-double ChooseSolver::get_computation_time_tmp<SolverType::DC>();
-
 #endif  //CHOOSESOLVER_H
diff --git a/src/Computers.cpp b/src/Computers.cpp
index 29a7f58..84d6bec 100644
--- a/src/Computers.cpp
+++ b/src/Computers.cpp
@@ -49,6 +49,8 @@ int Computers::compute_Vs(Eigen::Ref<const RealMat> gen_p,
 
     const Eigen::VectorXi & bus_pv = _grid_model.get_pv();
     const Eigen::VectorXi & bus_pq = _grid_model.get_pq();
+    const Eigen::VectorXi & slack_ids = _grid_model.get_slack_ids();
+    const RealVect & slack_weights = _grid_model.get_slack_weights();
     _solver.reset();
 
     // init the computations
@@ -82,6 +84,8 @@ int Computers::compute_Vs(Eigen::Ref<const RealMat> gen_p,
         conv = compute_one_powerflow(Ybus,
                                      V, 
                                      _Sbuses.row(i),
+                                     slack_ids,
+                                     slack_weights,
                                      bus_pv,
                                      bus_pq,
                                      max_iter,
diff --git a/src/Computers.h b/src/Computers.h
index dc3070c..05c0eca 100644
--- a/src/Computers.h
+++ b/src/Computers.h
@@ -59,6 +59,10 @@ class Computers: public BaseMultiplePowerflow
             compute_flows_from_Vs();
             return _amps_flows;
         }
+        Eigen::Ref<const RealMat > compute_power_flows() {
+            compute_flows_from_Vs(false);
+            return _active_power_flows;
+        }
 
     protected:
         template<class T>
@@ -95,7 +99,6 @@ class Computers: public BaseMultiplePowerflow
             const auto & el_status = structure_data.get_status();
             const auto & el_bus_id = structure_data.get_bus_id();
             int  bus_id_solver, bus_id_me;
-            cplx_type tmp;
             for(Eigen::Index el_id = 0; el_id < nb_el; ++el_id){
                 if(!el_status[el_id]) continue;
                 bus_id_me = el_bus_id(el_id);
diff --git a/src/CustTimer.h b/src/CustTimer.h
index bbeb629..57b99c1 100644
--- a/src/CustTimer.h
+++ b/src/CustTimer.h
@@ -17,18 +17,16 @@ most time were taken.
 **/
 class CustTimer{
     public:
-        CustTimer():start_(std::chrono::system_clock::now()){
-            end_ = start_;
-        };
+        CustTimer():start_(std::chrono::steady_clock::now()), end_(start_){};
 
         double duration(){
-            end_ = std::chrono::system_clock::now();
+            end_ = std::chrono::steady_clock::now();
             std::chrono::duration<double> res = end_ - start_;
             return res.count();
         }
     private:
-        std::chrono::time_point<std::chrono::system_clock> start_;
-        std::chrono::time_point<std::chrono::system_clock> end_;
+        std::chrono::time_point<std::chrono::steady_clock> start_;
+        std::chrono::time_point<std::chrono::steady_clock> end_;
 };
 
 #endif //CUSTTIMER_H
diff --git a/src/DCSolver.h b/src/DCSolver.h
index 517afe6..1e1216b 100644
--- a/src/DCSolver.h
+++ b/src/DCSolver.h
@@ -11,19 +11,23 @@
 
 #include "BaseSolver.h"
 // TODO make err_ more explicit: use an enum
-class DCSolver: public BaseSolver
+template<class LinearSolver>
+class BaseDCSolver: public BaseSolver
 {
     public:
-        DCSolver():BaseSolver(), dc_solver_(), need_factorize_(true){};
+        BaseDCSolver():BaseSolver(), _linear_solver(), need_factorize_(true){};
 
-        ~DCSolver(){}
+        ~BaseDCSolver(){}
 
         virtual void reset();
 
+        // TODO SLACK : this should be handled in Sbus by the gridmodel maybe ?
         virtual
         bool compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
                         CplxVect & V,
                         const CplxVect & Sbus,
+                        const Eigen::VectorXi & slack_ids,
+                        const RealVect & slack_weights,  // currently unused
                         const Eigen::VectorXi & pv,
                         const Eigen::VectorXi & pq,
                         int max_iter,
@@ -32,13 +36,15 @@ class DCSolver: public BaseSolver
 
     private:
         // no copy allowed
-        DCSolver( const BaseSolver & ) ;
-        DCSolver & operator=( const BaseSolver & ) ;
+        BaseDCSolver( const BaseSolver & ) =delete ;
+        BaseDCSolver & operator=( const BaseSolver & ) =delete;
 
     protected:
-        Eigen::SparseLU<Eigen::SparseMatrix<real_type>>  dc_solver_;
+        LinearSolver  _linear_solver;
         bool need_factorize_;
 
 };
 
+#include "DCSolver.tpp"
+
 #endif // DCSOLVER_H
diff --git a/src/DCSolver.cpp b/src/DCSolver.tpp
similarity index 68%
rename from src/DCSolver.cpp
rename to src/DCSolver.tpp
index b534f0a..14eed71 100644
--- a/src/DCSolver.cpp
+++ b/src/DCSolver.tpp
@@ -6,16 +6,20 @@
 // SPDX-License-Identifier: MPL-2.0
 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
-#include "DCSolver.h"
-
-bool DCSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                          CplxVect & V,
-                          const CplxVect & Sbus,
-                          const Eigen::VectorXi & pv,
-                          const Eigen::VectorXi & pq,
-                          int max_iter,
-                          real_type tol
-                          )
+// #include "DCSolver.h"
+
+// TODO SLACK !!!
+template<class LinearSolver>
+bool BaseDCSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
+                                        CplxVect & V,
+                                        const CplxVect & Sbus,
+                                        const Eigen::VectorXi & slack_ids,
+                                        const RealVect & slack_weights,
+                                        const Eigen::VectorXi & pv,
+                                        const Eigen::VectorXi & pq,
+                                        int max_iter,
+                                        real_type tol
+                                        )
 {
     // max_iter is ignored
     // tol is ignored
@@ -36,8 +40,13 @@ bool DCSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
     // dcYbus_tmp.makeCompressed();
     const CplxVect & Sbus_tmp = Sbus;
 
+    // TODO SLACK (for now i put all slacks as PV, except the first one)
+    // this should be handled in Sbus, because we know the amount of power absorbed by the slack
+    // so we can compute it correctly !
+    Eigen::VectorXi my_pv = retrieve_pv_with_slack(slack_ids, pv);
+
     // find the slack bus
-    int slack_bus_id_solver = extract_slack_bus_id(pv, pq, nb_bus_solver);
+    int slack_bus_id_solver = extract_slack_bus_id(my_pv, pq, nb_bus_solver);
     // std::cout << "slack_bus_id_solver extracted" << std::endl;
 
     // remove the slack bus from Ybus
@@ -68,23 +77,30 @@ bool DCSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
     #ifdef __COUT_TIMES
         auto timer_solve = CustTimer();
     #endif // __COUT_TIMES
+    bool just_factorize = false;
     if(need_factorize_){
-        dc_solver_.analyzePattern(dcYbus);
+        // dc_solver_.analyzePattern(dcYbus);
+        ErrorType status_init = _linear_solver.initialize(dcYbus);
+        if(status_init != ErrorType::NoError){
+            err_ = status_init;
+            return false;
+        }
         // std::cout << "\t dc: need_factorize_: " << need_factorize_ << std::endl;
         need_factorize_ = false;
+        just_factorize = true;
     }
     // else{
     //     std::cout << "\t dc: no need factorize: " << need_factorize_ << std::endl;
     // }
 
     // factorize the matrix
-    dc_solver_.factorize(dcYbus);
-    if(dc_solver_.info() != Eigen::Success) {
-        // matrix is not connected
-        timer_total_nr_ += timer.duration();
-        err_ = 1;
-        return false;
-    }
+    // dc_solver_.factorize(dcYbus);
+    // if(dc_solver_.info() != Eigen::Success) {
+    //     // matrix is not connected
+    //     timer_total_nr_ += timer.duration();
+    //     err_ = 1;
+    //     return false;
+    // }
 
     // remove the slack bus from Sbus
     RealVect dcSbus = RealVect::Constant(nb_bus_solver - 1, my_zero_);
@@ -96,14 +112,26 @@ bool DCSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
     }
 
     // solve for theta: Sbus = dcY . theta
-    RealVect Va_dc_without_slack = dc_solver_.solve(dcSbus);
-    if(dc_solver_.info() != Eigen::Success) {
-        // solving failed, this should not happen in dc ...
-        // matrix is not connected
+    RealVect Va_dc_without_slack = dcSbus;
+    // for(real_type el: dcSbus){
+    //     std::cout << el << ", ";
+    // }
+    // std::cout << std::endl;
+
+    ErrorType error = _linear_solver.solve(dcYbus, Va_dc_without_slack, just_factorize);
+    if(error != ErrorType::NoError){
+        err_ = error;
         timer_total_nr_ += timer.duration();
-        err_ = 3;
         return false;
     }
+    // RealVect Va_dc_without_slack = dc_solver_.solve(dcSbus);
+    // if(dc_solver_.info() != Eigen::Success) {
+    //     // solving failed, this should not happen in dc ...
+    //     // matrix is not connected
+    //     timer_total_nr_ += timer.duration();
+    //     err_ = 3;
+    //     return false;
+    // }
     #ifdef __COUT_TIMES
         std::cout << "\t dc solve: " << 1000. * timer_solve.duration() << "ms" << std::endl;
         auto timer_postproc = CustTimer();
@@ -130,7 +158,7 @@ bool DCSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
     Vm_(slack_bus_id_solver) = std::abs(V(slack_bus_id_solver));
 
     // now compute the resulting complex voltage
-    V_ = (Va_.array().cos().cast<cplx_type>() + my_i * Va_.array().sin().cast<cplx_type>());
+    V_ = (Va_.array().cos().template cast<cplx_type>() + my_i * Va_.array().sin().template cast<cplx_type>());
     V_.array() *= Vm_.array();
     nr_iter_ = 1;
     V = V_;
@@ -145,7 +173,9 @@ bool DCSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
     return true;
 }
 
-void DCSolver::reset(){
+template<class LinearSolver>
+void BaseDCSolver<LinearSolver>::reset(){
     BaseSolver::reset();
+    _linear_solver.reset();
     need_factorize_ = true;
 }
diff --git a/src/DataGen.cpp b/src/DataGen.cpp
index 62f978c..c4804b8 100644
--- a/src/DataGen.cpp
+++ b/src/DataGen.cpp
@@ -42,6 +42,8 @@ void DataGen::init(const RealVect & generators_p,
         }
     }
     status_ = std::vector<bool>(generators_p.size(), true);
+    gen_slackbus_ = std::vector<bool>(generators_p.size(), false);
+    gen_slack_weight_ = std::vector<real_type>(generators_p.size(), 0.);
 }
 
 
@@ -53,7 +55,9 @@ DataGen::StateRes DataGen::get_state() const
      std::vector<real_type> max_q(max_q_.begin(), max_q_.end());
      std::vector<int> bus_id(bus_id_.begin(), bus_id_.end());
      std::vector<bool> status = status_;
-     DataGen::StateRes res(p_mw, vm_pu, min_q, max_q, bus_id, status);
+     std::vector<bool> slack_bus = gen_slackbus_;
+     std::vector<real_type> slack_weight = gen_slack_weight_;
+     DataGen::StateRes res(p_mw, vm_pu, min_q, max_q, bus_id, status, slack_bus, slack_weight);
      return res;
 }
 void DataGen::set_state(DataGen::StateRes & my_state )
@@ -66,6 +70,8 @@ void DataGen::set_state(DataGen::StateRes & my_state )
     std::vector<real_type> & max_q = std::get<3>(my_state);
     std::vector<int> & bus_id = std::get<4>(my_state);
     std::vector<bool> & status = std::get<5>(my_state);
+    std::vector<bool> & slack_bus = std::get<6>(my_state);
+    std::vector<real_type> & slack_weight = std::get<7>(my_state);
     // TODO check sizes
 
     // input data
@@ -75,8 +81,34 @@ void DataGen::set_state(DataGen::StateRes & my_state )
     max_q_ = RealVect::Map(&max_q[0], max_q.size());
     bus_id_ = Eigen::VectorXi::Map(&bus_id[0], bus_id.size());
     status_ = status;
+    gen_slackbus_ = slack_bus;
+    gen_slack_weight_ = slack_weight;
 }
 
+RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vector<int> & id_grid_to_solver){
+    const int nb_gen = nb();
+    int bus_id_me, bus_id_solver;
+    RealVect res = RealVect::Zero(nb_bus_solver);
+    for(int gen_id = 0; gen_id < nb_gen; ++gen_id){
+        //  i don't do anything if the load is disconnected
+        if(!status_[gen_id]) continue;
+        bus_id_me = bus_id_(gen_id);
+        bus_id_solver = id_grid_to_solver[bus_id_me];
+        if(bus_id_solver == _deactivated_bus_id){
+            // TODO DEBUG MODE: only check in debug mode
+            std::ostringstream exc_;
+            exc_ << "DataGen::get_slack_weights: Generator with id ";
+            exc_ << gen_id;
+            exc_ << " is connected to a disconnected bus while being connected to the grid.";
+            throw std::runtime_error(exc_.str());
+        }
+        if(gen_slackbus_[gen_id])  res.coeffRef(bus_id_solver) += gen_slack_weight_[gen_id];
+    }
+    bus_slack_weight_ = res;
+    real_type sum_res = res.sum();
+    res /= sum_res;
+    return res;
+}
 
 void DataGen::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver){
     const int nb_gen = nb();
@@ -89,6 +121,7 @@ void DataGen::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_gri
         bus_id_me = bus_id_(gen_id);
         bus_id_solver = id_grid_to_solver[bus_id_me];
         if(bus_id_solver == _deactivated_bus_id){
+            // TODO DEBUG MODE only this in debug mode
             std::ostringstream exc_;
             exc_ << "DataGen::fillSbus: Generator with id ";
             exc_ << gen_id;
@@ -102,8 +135,8 @@ void DataGen::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_gri
 
 void DataGen::fillpv(std::vector<int> & bus_pv,
                      std::vector<bool> & has_bus_been_added,
-                     int slack_bus_id_solver,
-                     const std::vector<int> & id_grid_to_solver)
+                     Eigen::VectorXi & slack_bus_id_solver,
+                     const std::vector<int> & id_grid_to_solver) const
 {
     const int nb_gen = nb();
     int bus_id_me, bus_id_solver;
@@ -114,13 +147,15 @@ void DataGen::fillpv(std::vector<int> & bus_pv,
         bus_id_me = bus_id_(gen_id);
         bus_id_solver = id_grid_to_solver[bus_id_me];
         if(bus_id_solver == _deactivated_bus_id){
+            // TODO DEBUG MODE only this in debug mode
             std::ostringstream exc_;
             exc_ << "DataGen::fillpv: Generator with id ";
             exc_ << gen_id;
             exc_ << " is connected to a disconnected bus while being connected to the grid.";
             throw std::runtime_error(exc_.str());
         }
-        if(bus_id_solver == slack_bus_id_solver) continue;  // slack bus is not PV
+        // if(bus_id_solver == slack_bus_id_solver) continue;  // slack bus is not PV
+        if(is_in_vect(bus_id_solver, slack_bus_id_solver)) continue;  // slack bus is not PV
         if(has_bus_been_added[bus_id_solver]) continue; // i already added this bus
         bus_pv.push_back(bus_id_solver);
         has_bus_been_added[bus_id_solver] = true;  // don't add it a second time
@@ -128,12 +163,12 @@ void DataGen::fillpv(std::vector<int> & bus_pv,
 }
 
 void DataGen::compute_results(const Eigen::Ref<const RealVect> & Va,
-                               const Eigen::Ref<const RealVect> & Vm,
-                               const Eigen::Ref<const CplxVect> & V,
-                               const std::vector<int> & id_grid_to_solver,
-                               const RealVect & bus_vn_kv,
-                               real_type sn_mva,
-                               bool ac)
+                              const Eigen::Ref<const RealVect> & Vm,
+                              const Eigen::Ref<const CplxVect> & V,
+                              const std::vector<int> & id_grid_to_solver,
+                              const RealVect & bus_vn_kv,
+                              real_type sn_mva,
+                              bool ac)
 {
     const int nb_gen = nb();
     v_kv_from_vpu(Va, Vm, status_, nb_gen, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_);
@@ -146,6 +181,7 @@ void DataGen::reset_results(){
     res_q_ = RealVect();  // in MVar
     res_v_ = RealVect();  // in kV
     res_theta_ = RealVect();  // in deg
+    bus_slack_weight_ = RealVect();
 }
 
 void DataGen::get_vm_for_dc(RealVect & Vm){
@@ -165,6 +201,7 @@ void DataGen::change_p(int gen_id, real_type new_p, bool & need_reset)
     bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound
     if(!my_status)
     {
+        // TODO DEBUG MODE only this in debug mode
         std::ostringstream exc_;
         exc_ << "DataGen::change_p: Impossible to change the active value of a disconnected generator (check gen. id ";
         exc_ << gen_id;
@@ -179,6 +216,7 @@ void DataGen::change_v(int gen_id, real_type new_v_pu, bool & need_reset)
     bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound
     if(!my_status)
     {
+        // TODO DEBUG MODE only this in debug mode
         std::ostringstream exc_;
         exc_ << "DataGen::change_p: Impossible to change the voltage setpoint of a disconnected generator (check gen. id ";
         exc_ << gen_id;
@@ -199,6 +237,7 @@ void DataGen::set_vm(CplxVect & V, const std::vector<int> & id_grid_to_solver)
         bus_id_me = bus_id_(gen_id);
         bus_id_solver = id_grid_to_solver[bus_id_me];
         if(bus_id_solver == _deactivated_bus_id){
+            // TODO DEBUG MODE only this in debug mode
             std::ostringstream exc_;
             exc_ << "DataGen::set_vm: Generator with id ";
             exc_ << gen_id;
@@ -220,25 +259,48 @@ void DataGen::set_vm(CplxVect & V, const std::vector<int> & id_grid_to_solver)
     }
 }
 
-int DataGen::get_slack_bus_id(int gen_id){
-    bool status = status_.at(gen_id);  // also to ensure gen_id is consistent with number of gen
-    if(!status) throw std::runtime_error("DataGen::get_slack_bus_id: Generator for slack bus is deactivated");
-    int res = bus_id_(gen_id);
+std::vector<int> DataGen::get_slack_bus_id() const{
+    std::vector<int> res;
+    const auto nb_gen = nb();
+    for(int gen_id = 0; gen_id < nb_gen; ++gen_id){
+        if(gen_slackbus_[gen_id]){
+            const auto my_bus = bus_id_(gen_id);
+            // do not add twice the same "slack bus"
+            if(!is_in_vect(my_bus, res)) res.push_back(my_bus);
+        }
+    }
     return res;
 }
 
-void DataGen::set_p_slack(int slack_bus_id, real_type p_slack){
-    bool status = status_.at(slack_bus_id);  // also to ensure gen_id is consistent with number of gen
-    if(!status) throw std::runtime_error("DataGen::set_p_slack: Generator for slack bus is deactivated");
-    res_p_(slack_bus_id) = p_slack;
+void DataGen::set_p_slack(const RealVect& node_mismatch,
+                          const std::vector<int> & id_grid_to_solver)
+{
+    if(bus_slack_weight_.size() == 0){
+        // TODO DEBUG MODE: perform this check only in debug mode
+        throw std::runtime_error("Impossible to set the active value of generators for the slack bus");
+    }
+    const auto nb_gen = nb();
+    for(int gen_id = 0; gen_id < nb_gen; ++gen_id){
+        if(!status_[gen_id]) continue;  // nothing to do if gen is disconnected
+        if(!gen_slackbus_[gen_id]) continue;  // nothing to do if it's not a slack
+        if(gen_slack_weight_[gen_id] == 0.) continue; // nothing to do if no weights are associated to it
+        const auto bus_id_me = bus_id_(gen_id);
+        const auto bus_id_solver = id_grid_to_solver[bus_id_me];
+        // TODO DEBUG MODE: check bus_id_solver >= 0
+        // TODO DEBUG MODE: check bus_slack_weight_[bus_id_solver] > 0
+        const auto total_contrib_slack = bus_slack_weight_(bus_id_solver);
+        const auto my_contrib_slack = gen_slack_weight_[gen_id];
+        // now take "my part"
+        res_p_(gen_id) += node_mismatch(bus_id_solver) * my_contrib_slack / total_contrib_slack;
+    }
 }
 
-void DataGen::init_q_vector(int nb_bus)
+void DataGen::init_q_vector(int nb_bus_total)  // total number of bus on the grid
 {
     const int nb_gen = nb();
-    total_q_min_per_bus_ = RealVect::Constant(nb_bus, 0.);
-    total_q_max_per_bus_ = RealVect::Constant(nb_bus, 0.);
-    total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus, 0);
+    total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.);
+    total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.);
+    total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus_total, 0);
     for(int gen_id = 0; gen_id < nb_gen; ++gen_id)
     {
         if(!status_[gen_id]) continue;
@@ -249,9 +311,8 @@ void DataGen::init_q_vector(int nb_bus)
     }
 }
 
-void DataGen::set_q(const std::vector<real_type> & q_by_bus, bool ac)
+void DataGen::set_q(const RealVect & reactive_mismatch, const std::vector<int> & id_grid_to_solver, bool ac)
 {
-    // for(int bus_id = 0; bus_id < q_by_bus.size(); ++bus_id) std::cout << "bus id " << bus_id << " sum q " << q_by_bus[bus_id] << std::endl;
     const int nb_gen = nb();
     res_q_ = RealVect::Constant(nb_gen, 0.);
     if(!ac) return;  // do not consider Q values in dc mode
@@ -261,7 +322,9 @@ void DataGen::set_q(const std::vector<real_type> & q_by_bus, bool ac)
         real_type real_q = 0.;
         if(!status_[gen_id]) continue;  // set at 0 for disconnected generators
         int bus_id = bus_id_(gen_id);
-        real_type q_to_absorb = q_by_bus[bus_id];
+        const auto bus_solver = id_grid_to_solver[bus_id];
+        // TODO DEBUG MODE: check that the bus is correct!
+        real_type q_to_absorb = reactive_mismatch[bus_solver];
         real_type max_q_me = max_q_(gen_id);
         real_type min_q_me = min_q_(gen_id);
         real_type max_q_bus = total_q_max_per_bus_(bus_id);
diff --git a/src/DataGen.h b/src/DataGen.h
index 86152e6..35a947c 100644
--- a/src/DataGen.h
+++ b/src/DataGen.h
@@ -10,6 +10,7 @@
 #define DATAGEN_H
 
 #include <iostream>
+#include <vector> 
 
 #include "Utils.h"
 
@@ -36,9 +37,13 @@ class DataGen: public DataGeneric
     {
         public:
             // members
+            // TODO add some const here (value should not be changed !) !!!
             int id;  // id of the generator
             bool connected;
             int bus_id;
+            bool is_slack;
+            real_type slack_weight;
+
             real_type target_p_mw;
             real_type target_vm_pu;
             real_type min_q_mvar;
@@ -53,6 +58,8 @@ class DataGen: public DataGeneric
             id(-1),
             connected(false),
             bus_id(-1),
+            is_slack(false),
+            slack_weight(-1.0),
             target_p_mw(0.),
             target_vm_pu(0.),
             min_q_mvar(0.),
@@ -68,6 +75,9 @@ class DataGen: public DataGeneric
                     id = my_id;
                     connected = r_data_gen.status_[my_id];
                     bus_id = r_data_gen.bus_id_[my_id];
+                    is_slack = r_data_gen.gen_slackbus_[my_id];
+                    slack_weight = r_data_gen.gen_slack_weight_[my_id];
+
                     target_p_mw = r_data_gen.p_mw_.coeff(my_id);
                     target_vm_pu = r_data_gen.vm_pu_.coeff(my_id);
                     min_q_mvar = r_data_gen.min_q_.coeff(my_id);
@@ -96,7 +106,9 @@ class DataGen: public DataGeneric
        std::vector<real_type>, // min_q_
        std::vector<real_type>, // max_q_
        std::vector<int>, // bus_id
-       std::vector<bool> // status
+       std::vector<bool>, // status
+       std::vector<bool>, // gen_slackbus
+       std::vector<real_type> // gen_slack_weight_
        >  StateRes;
 
     DataGen() {};
@@ -132,6 +144,28 @@ class DataGen: public DataGeneric
     DataGen::StateRes get_state() const;
     void set_state(DataGen::StateRes & my_state );
 
+    // slack handling
+    /**
+    we suppose that the data are correct (ie gen_id in the proper range, and weight > 0.)
+    This is checked in GridModel, and not at this stage
+    **/
+    void add_slackbus(int gen_id, real_type weight){
+        gen_slackbus_[gen_id] = true;
+        gen_slack_weight_[gen_id] = weight;
+    }
+    void remove_slackbus(int gen_id){
+        gen_slackbus_[gen_id] = false;
+        gen_slack_weight_[gen_id] = 0.;
+    }
+    /**
+    Retrieve the normalized (=sum to 1.000) slack weights for all the buses
+    **/
+    RealVect get_slack_weights(Eigen::Index nb_bus_solver, const std::vector<int> & id_grid_to_solver);
+
+    std::vector<int> get_slack_bus_id() const;
+    void set_p_slack(const RealVect& node_mismatch, const std::vector<int> & id_grid_to_solver);
+
+    // modification
     void deactivate(int gen_id, bool & need_reset) {_deactivate(gen_id, status_, need_reset);}
     void reactivate(int gen_id, bool & need_reset) {_reactivate(gen_id, status_, need_reset);}
     void change_bus(int gen_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(gen_id, new_bus_id, bus_id_, need_reset, nb_bus);}
@@ -144,8 +178,8 @@ class DataGen: public DataGeneric
     virtual void fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver);
     virtual void fillpv(std::vector<int>& bus_pv,
                         std::vector<bool> & has_bus_been_added,
-                        int slack_bus_id_solver,
-                        const std::vector<int> & id_grid_to_solver);
+                        Eigen::VectorXi & slack_bus_id_solver,
+                        const std::vector<int> & id_grid_to_solver) const;
     void init_q_vector(int nb_bus); // delta_q_per_gen_
 
     void compute_results(const Eigen::Ref<const RealVect> & Va,
@@ -156,9 +190,7 @@ class DataGen: public DataGeneric
                          real_type sn_mva,
                          bool ac);
     void reset_results();
-    void set_q(const std::vector<real_type> & q_by_bus, bool ac);
-    int get_slack_bus_id(int gen_id);
-    virtual void set_p_slack(int slack_bus_id, real_type p_slack);
+    void set_q(const RealVect & reactive_mismatch, const std::vector<int> & id_grid_to_solver, bool ac);
 
     void get_vm_for_dc(RealVect & Vm);
     /**
@@ -189,10 +221,15 @@ class DataGen: public DataGeneric
         Eigen::VectorXi bus_id_;
         std::vector<bool> status_;
 
+        // remember which generators are "slack bus"
+        std::vector<bool> gen_slackbus_;  // say for each generator if it's a slack or not
+        std::vector<real_type> gen_slack_weight_;
+
         // intermediate data
         RealVect total_q_min_per_bus_;
         RealVect total_q_max_per_bus_;
         Eigen::VectorXi total_gen_per_bus_;
+        RealVect bus_slack_weight_;  // do not sum to 1., for each node of the grid, say the raw contribution for the generator
 
         //output data
         RealVect res_p_;  // in MW
diff --git a/src/DataGeneric.cpp b/src/DataGeneric.cpp
index afdbfe8..6d246d3 100644
--- a/src/DataGeneric.cpp
+++ b/src/DataGeneric.cpp
@@ -41,6 +41,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el
     // throw error: object id does not exist
     if(el_id >= el_bus_ids.size())
     {
+        // TODO DEBUG MODE: only check in debug mode
         std::ostringstream exc_;
         exc_ << "DataGeneric::_change_bus: Cannot change the bus of element with id ";
         exc_ << el_id;
@@ -51,6 +52,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el
     }
     if(el_id < 0)
     {
+        // TODO DEBUG MODE: only check in debug mode
         std::ostringstream exc_;
         exc_ << "DataGeneric::_change_bus: Cannot change the bus of element with id ";
         exc_ << el_id;
@@ -61,6 +63,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el
     // throw error: bus id does not exist
     if(new_bus_me_id >= nb_bus)
     {
+        // TODO DEBUG MODE: only check in debug mode
         std::ostringstream exc_;
         exc_ << "DataGeneric::_change_bus: Cannot change an element to bus ";
         exc_ << new_bus_me_id;
@@ -71,6 +74,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el
     }
     if(new_bus_me_id < 0)
     {
+        // TODO DEBUG MODE: only check in debug mode
         std::ostringstream exc_;
         exc_ << "DataGeneric::_change_bus: new bus id should be >=0 and not ";
         exc_ << new_bus_me_id;
@@ -107,6 +111,7 @@ void DataGeneric::v_kv_from_vpu(const Eigen::Ref<const RealVect> & Va,
         int el_bus_me_id = bus_me_id(el_id);
         int bus_solver_id = id_grid_to_solver[el_bus_me_id];
         if(bus_solver_id == _deactivated_bus_id){
+            // TODO DEBUG MODE: only check in debug mode
             std::ostringstream exc_;
             exc_ << "DataGeneric::v_kv_from_vpu: The element of id ";
             exc_ << bus_solver_id;
@@ -134,6 +139,7 @@ void DataGeneric::v_deg_from_va(const Eigen::Ref<const RealVect> & Va,
         int el_bus_me_id = bus_me_id(el_id);
         int bus_solver_id = id_grid_to_solver[el_bus_me_id];
         if(bus_solver_id == _deactivated_bus_id){
+            // TODO DEBUG MODE: only check in debug mode
             std::ostringstream exc_;
             exc_ << "DataGeneric::v_deg_from_va: The element of id ";
             exc_ << bus_solver_id;
diff --git a/src/DataGeneric.h b/src/DataGeneric.h
index 7e0792a..048ee31 100644
--- a/src/DataGeneric.h
+++ b/src/DataGeneric.h
@@ -9,6 +9,8 @@
 #ifndef DATAGENERIC_H
 #define DATAGENERIC_H
 
+#include <algorithm>  // for std::find
+
 #include "Utils.h"
 
 #include "Eigen/Core"
@@ -39,7 +41,7 @@ class DataConstIterator
             {};
 
         const DataInfo& operator*() const { return my_info; }
-        bool operator==(const DataConstIterator<DataType> & other) const { return (_p_data_ == other._p_data_) & (my_id == other.my_id); }
+        bool operator==(const DataConstIterator<DataType> & other) const { return (my_id == other.my_id) && (_p_data_ == other._p_data_); }
         bool operator!=(const DataConstIterator<DataType> & other) const { return !(*this == other); }
         DataConstIterator<DataType> & operator++()
         {
@@ -73,14 +75,17 @@ class DataGeneric : public BaseConstants
         virtual void fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver){};
         virtual void fillpv(std::vector<int>& bus_pv,
                             std::vector<bool> & has_bus_been_added,
-                            int slack_bus_id_solver,
-                            const std::vector<int> & id_grid_to_solver) {};
-        virtual real_type get_p_slack(int slack_bus_id) {return my_zero_;}
-        virtual void set_p_slack(int gen_slackbus, real_type p_slack) {};
+                            Eigen::VectorXi & slack_bus_id_solver,
+                            const std::vector<int> & id_grid_to_solver) const {};
+        
         virtual void get_q(std::vector<real_type>& q_by_bus) {};
 
+        void set_p_slack(const RealVect& node_mismatch, const std::vector<int> & id_grid_to_solver) {};
     
         static const int _deactivated_bus_id;
+
+        /**"define" the destructor for compliance with clang (otherwise lots of warnings)**/
+        virtual ~DataGeneric() {};
         
     protected:
         /**
@@ -129,6 +134,12 @@ class DataGeneric : public BaseConstants
         {
             if(container.size() != size) throw std::runtime_error(container_name + " do not have the proper size");
         }
+
+        /**
+        check if an element is in a vector or an Eigen Vector, do not use for other types of containers (might not be efficient at all)
+        **/
+        template<class T>  // a std::vector, or an Eigen::Vector                                                 
+        bool is_in_vect(int val, const T & cont) const {return std::find(cont.begin(), cont.end(), val) != cont.end();}
 };
 
 #endif // DATAGENERIC_H
diff --git a/src/DataLine.cpp b/src/DataLine.cpp
index c3d6011..ac5b4c1 100644
--- a/src/DataLine.cpp
+++ b/src/DataLine.cpp
@@ -75,7 +75,7 @@ void DataLine::set_state(DataLine::StateRes & my_state)
 
 void DataLine::_update_model_coeffs()
 {
-    const int my_size = powerlines_r_.size();
+    const auto my_size = powerlines_r_.size();
 
     yac_ff_ = CplxVect::Zero(my_size);
     yac_ft_ = CplxVect::Zero(my_size);
@@ -272,29 +272,3 @@ void DataLine::compute_results(const Eigen::Ref<const RealVect> & Va,
     _get_amps(res_powerline_aor_, res_powerline_por_, res_powerline_qor_, res_powerline_vor_);
     _get_amps(res_powerline_aex_, res_powerline_pex_, res_powerline_qex_, res_powerline_vex_);
 }
-
-real_type DataLine::get_p_slack(int slack_bus_id)
-{
-    int nb_element = nb();
-    real_type res = 0.;
-    for(int line_id = 0; line_id < nb_element; ++line_id)
-    {
-        if(!status_[line_id]) continue;
-        if(bus_or_id_(line_id) == slack_bus_id) res += res_powerline_por_(line_id);
-        if(bus_ex_id_(line_id) == slack_bus_id) res += res_powerline_pex_(line_id);
-    }
-    return res;
-}
-
-void DataLine::get_q(std::vector<real_type>& q_by_bus)
-{
-    int nb_element = nb();
-    for(int el_id = 0; el_id < nb_element; ++el_id)
-    {
-        if(!status_[el_id]) continue;
-        int bus_id_ex = bus_ex_id_[el_id];
-        int bus_id_or = bus_or_id_[el_id];
-        q_by_bus[bus_id_or] += res_powerline_qor_(el_id);
-        q_by_bus[bus_id_ex] += res_powerline_qex_(el_id);
-    }
-}
diff --git a/src/DataLine.h b/src/DataLine.h
index fc6e43a..943cacb 100644
--- a/src/DataLine.h
+++ b/src/DataLine.h
@@ -184,8 +184,6 @@ class DataLine : public DataGeneric
                          real_type sn_mva,
                          bool ac);
     void reset_results();
-    virtual real_type get_p_slack(int slack_bus_id);
-    virtual void get_q(std::vector<real_type>& q_by_bus);
 
     tuple4d get_lineor_res() const {return tuple4d(res_powerline_por_, res_powerline_qor_, res_powerline_vor_, res_powerline_aor_);}
     tuple4d get_lineex_res() const {return tuple4d(res_powerline_pex_, res_powerline_qex_, res_powerline_vex_, res_powerline_aex_);}
diff --git a/src/DataLoad.cpp b/src/DataLoad.cpp
index f97a298..3c68b75 100644
--- a/src/DataLoad.cpp
+++ b/src/DataLoad.cpp
@@ -118,26 +118,3 @@ void DataLoad::change_q(int load_id, real_type new_q, bool & need_reset)
     }
     q_mvar_(load_id) = new_q;
 }
-
-real_type DataLoad::get_p_slack(int slack_bus_id)
-{
-    int nb_element = nb();
-    real_type res = 0.;
-    for(int load_id = 0; load_id < nb_element; ++load_id)
-    {
-        if(!status_[load_id]) continue;
-        if(bus_id_(load_id) == slack_bus_id) res += res_p_(load_id);
-    }
-    return res;
-}
-
-void DataLoad::get_q(std::vector<real_type>& q_by_bus)
-{
-    int nb_element = nb();
-    for(int load_id = 0; load_id < nb_element; ++load_id)
-    {
-        if(!status_[load_id]) continue;
-        int bus_id = bus_id_[load_id];
-        q_by_bus[bus_id] += res_q_(load_id); //TODO weird that i need to put a + here and a - for the active!
-    }
-}
diff --git a/src/DataLoad.h b/src/DataLoad.h
index e1a40b7..49bf824 100644
--- a/src/DataLoad.h
+++ b/src/DataLoad.h
@@ -36,6 +36,80 @@ class DataLoad : public DataGeneric
     // TODO make a single class for load and shunt and just specialize the part where the
     // TODO powerflow equations are located (when i update the Y matrix)
 
+    // iterators part
+    public:
+        class LoadInfo
+        {
+            public:
+                // members
+                // TODO add some const here (value should not be changed !) !!!
+                int id;  // id of the generator
+                bool connected;
+                int bus_id;
+
+                real_type target_p_mw;
+                real_type target_q_mvar;
+                bool has_res;
+                real_type res_p_mw;
+                real_type res_q_mvar;
+                real_type res_v_kv;
+                real_type res_theta_deg;
+
+                LoadInfo(const DataLoad & r_data_load, int my_id):
+                id(-1),
+                connected(false),
+                bus_id(-1),
+                target_p_mw(0.),
+                target_q_mvar(0.),
+                has_res(false),
+                res_p_mw(0.),
+                res_q_mvar(0.),
+                res_v_kv(0.),
+                res_theta_deg(0.)
+                {
+                    if((my_id >= 0) & (my_id < r_data_load.nb()))
+                    {
+                        id = my_id;
+                        connected = r_data_load.status_[my_id];
+                        bus_id = r_data_load.bus_id_[my_id];
+
+                        target_p_mw = r_data_load.p_mw_.coeff(my_id);
+                        target_q_mvar = r_data_load.q_mvar_.coeff(my_id);
+
+                        has_res = r_data_load.res_p_.size() > 0;
+                        if(has_res)
+                        {
+                            res_p_mw = r_data_load.res_p_.coeff(my_id);
+                            res_q_mvar = r_data_load.res_q_.coeff(my_id);
+                            res_v_kv = r_data_load.res_v_.coeff(my_id);
+                            res_theta_deg = r_data_load.res_theta_.coeff(my_id);
+                        }
+                    }
+                }
+        };
+        typedef LoadInfo DataInfo;
+
+    private:
+        typedef DataConstIterator<DataLoad> DataLoadConstIterator;
+
+    public:
+        typedef DataLoadConstIterator const_iterator_type;
+        const_iterator_type begin() const {return DataLoadConstIterator(this, 0); }
+        const_iterator_type end() const {return DataLoadConstIterator(this, nb()); }
+        LoadInfo operator[](int id) const
+        {
+            if(id < 0)
+            {
+                throw std::range_error("You cannot ask for a negative load id.");
+            }
+            if(id >= nb())
+            {
+                throw std::range_error("Generator out of bound. Not enough loads on the grid.");
+            }
+            return LoadInfo(*this, id);
+        }
+
+    // regular implementation
     public:
     typedef std::tuple<
        std::vector<real_type>, // p_mw
@@ -75,8 +149,6 @@ class DataLoad : public DataGeneric
                          real_type sn_mva,
                          bool ac);
     void reset_results();
-    virtual real_type get_p_slack(int slack_bus_id);
-    virtual void get_q(std::vector<real_type>& q_by_bus);
 
     tuple3d get_res() const {return tuple3d(res_p_, res_q_, res_v_);}
     Eigen::Ref<const RealVect> get_theta() const {return res_theta_;}
diff --git a/src/DataSGen.cpp b/src/DataSGen.cpp
index 5fe1265..ea84e83 100644
--- a/src/DataSGen.cpp
+++ b/src/DataSGen.cpp
@@ -154,26 +154,3 @@ void DataSGen::change_q(int sgen_id, real_type new_q, bool & need_reset)
     }
     q_mvar_(sgen_id) = new_q;
 }
-
-real_type DataSGen::get_p_slack(int slack_bus_id)
-{
-    const int nb_element = nb();
-    real_type res = 0.;
-    for(int sgen_id = 0; sgen_id < nb_element; ++sgen_id)
-    {
-        if(!status_[sgen_id]) continue;
-        if(bus_id_(sgen_id) == slack_bus_id) res -= res_p_(sgen_id);
-    }
-    return res;
-}
-
-void DataSGen::get_q(std::vector<real_type>& q_by_bus)
-{
-    const int nb_element = nb();
-    for(int sgen_id = 0; sgen_id < nb_element; ++sgen_id)
-    {
-        if(!status_[sgen_id]) continue;
-        int bus_id = bus_id_[sgen_id];
-        q_by_bus[bus_id] -= res_q_(sgen_id); //TODO weird that i need to put a + here and a - for the active!
-    }
-}
diff --git a/src/DataSGen.h b/src/DataSGen.h
index f198a52..d2dc393 100644
--- a/src/DataSGen.h
+++ b/src/DataSGen.h
@@ -33,6 +33,95 @@ class DataSGen: public DataGeneric
     // TODO make a single class for load and shunt and just specialize the part where the
     // TODO powerflow equations are located (when i update the Y matrix)
 
+
+    // iterators part
+    public:
+        class SGenInfo
+        {
+            public:
+                // members
+                // TODO add some const here (value should not be changed !) !!!
+                int id;  // id of the generator
+                bool connected;
+                int bus_id;
+
+                real_type min_q_mvar;
+                real_type max_q_mvar;
+                real_type min_p_mw;
+                real_type max_p_mw;
+
+                real_type target_p_mw;
+                real_type target_q_mvar;
+
+                bool has_res;
+                real_type res_p_mw;
+                real_type res_q_mvar;
+                real_type res_v_kv;
+                real_type res_theta_deg;
+
+                SGenInfo(const DataSGen & r_data_sgen, int my_id):
+                id(-1),
+                connected(false),
+                bus_id(-1),
+                min_q_mvar(0.),
+                max_q_mvar(0.),
+                min_p_mw(0.),
+                max_p_mw(0.),
+                target_p_mw(0.),
+                target_q_mvar(0.),
+                has_res(false),
+                res_p_mw(0.),
+                res_q_mvar(0.),
+                res_v_kv(0.),
+                res_theta_deg(0.)
+                {
+                    if((my_id >= 0) & (my_id < r_data_sgen.nb()))
+                    {
+                        id = my_id;
+                        connected = r_data_sgen.status_[my_id];
+                        bus_id = r_data_sgen.bus_id_[my_id];
+
+                        min_q_mvar = r_data_sgen.q_min_mvar_(my_id);
+                        max_q_mvar = r_data_sgen.q_max_mvar_(my_id);
+                        min_p_mw = r_data_sgen.p_min_mw_(my_id);
+                        max_p_mw = r_data_sgen.p_max_mw_(my_id);
+
+                        target_p_mw = r_data_sgen.p_mw_.coeff(my_id);
+                        target_q_mvar = r_data_sgen.q_mvar_.coeff(my_id);
+
+                        has_res = r_data_sgen.res_p_.size() > 0;
+                        if(has_res)
+                        {
+                            res_p_mw = r_data_sgen.res_p_.coeff(my_id);
+                            res_q_mvar = r_data_sgen.res_q_.coeff(my_id);
+                            res_v_kv = r_data_sgen.res_v_.coeff(my_id);
+                            res_theta_deg = r_data_sgen.res_theta_.coeff(my_id);
+                        }
+                    }
+                }
+        };
+        typedef SGenInfo DataInfo;
+
+    private:
+        typedef DataConstIterator<DataSGen> DataSGenConstIterator;
+
+    public:
+        typedef DataSGenConstIterator const_iterator_type;
+        const_iterator_type begin() const {return DataSGenConstIterator(this, 0); }
+        const_iterator_type end() const {return DataSGenConstIterator(this, nb()); }
+        SGenInfo operator[](int id) const
+        {
+            if(id < 0)
+            {
+                throw std::range_error("You cannot ask for a negative static generator");
+            }
+            if(id >= nb())
+            {
+                throw std::range_error("Generator out of bound. Not enough static generators on the grid.");
+            }
+            return SGenInfo(*this, id);
+        }
+
     public:
     typedef std::tuple<
        std::vector<real_type>, // p_mw
@@ -80,8 +169,6 @@ class DataSGen: public DataGeneric
                          real_type sn_mva,
                          bool ac);
     void reset_results();
-    virtual real_type get_p_slack(int slack_bus_id);
-    virtual void get_q(std::vector<real_type>& q_by_bus);
 
     tuple3d get_res() const {return tuple3d(res_p_, res_q_, res_v_);}
     Eigen::Ref<const RealVect> get_theta() const {return res_theta_;}
diff --git a/src/DataShunt.cpp b/src/DataShunt.cpp
index a68e319..d9f563f 100644
--- a/src/DataShunt.cpp
+++ b/src/DataShunt.cpp
@@ -168,28 +168,3 @@ void DataShunt::change_q(int shunt_id, real_type new_q, bool & need_reset)
     if(q_mvar_(shunt_id) != new_q) need_reset = true;
     q_mvar_(shunt_id) = new_q;
 }
-
-real_type DataShunt::get_p_slack(int slack_bus_id)
-{
-    int nb_element = nb();
-    real_type res = 0.;
-    for(int shunt_id = 0; shunt_id < nb_element; ++shunt_id)
-    {
-        if(!status_[shunt_id]) continue;
-        if(bus_id_(shunt_id) == slack_bus_id) res += res_p_(shunt_id);  //TODO plus here or minus ???
-    }
-    return res;
-}
-
-void DataShunt::get_q(std::vector<real_type>& q_by_bus)
-{
-    int nb_element = nb();
-    for(int shunt_id = 0; shunt_id < nb_element; ++shunt_id)
-    {
-        if(!status_[shunt_id]) continue;
-        int bus_id = bus_id_[shunt_id];
-        q_by_bus[bus_id] += res_q_(shunt_id);  //TODO plus here or minus ???
-    }
-}
-
-
diff --git a/src/DataShunt.h b/src/DataShunt.h
index b23184b..d90ebf3 100644
--- a/src/DataShunt.h
+++ b/src/DataShunt.h
@@ -30,6 +30,79 @@ and for modeling of the Ybus matrix:
 **/
 class DataShunt : public DataGeneric
 {
+    // iterators part
+    public:
+        class ShuntInfo
+        {
+            public:
+                // members
+                // TODO add some const here (value should not be changed !) !!!
+                int id;  // id of the generator
+                bool connected;
+                int bus_id;
+
+                real_type target_p_mw;
+                real_type target_q_mvar;
+                bool has_res;
+                real_type res_p_mw;
+                real_type res_q_mvar;
+                real_type res_v_kv;
+                real_type res_theta_deg;
+
+                ShuntInfo(const DataShunt & r_data_shunt, int my_id):
+                id(-1),
+                connected(false),
+                bus_id(-1),
+                target_p_mw(0.),
+                target_q_mvar(0.),
+                has_res(false),
+                res_p_mw(0.),
+                res_q_mvar(0.),
+                res_v_kv(0.),
+                res_theta_deg(0.)
+                {
+                    if((my_id >= 0) & (my_id < r_data_shunt.nb()))
+                    {
+                        id = my_id;
+                        connected = r_data_shunt.status_[my_id];
+                        bus_id = r_data_shunt.bus_id_[my_id];
+
+                        target_p_mw = r_data_shunt.p_mw_.coeff(my_id);
+                        target_q_mvar = r_data_shunt.q_mvar_.coeff(my_id);
+
+                        has_res = r_data_shunt.res_p_.size() > 0;
+                        if(has_res)
+                        {
+                            res_p_mw = r_data_shunt.res_p_.coeff(my_id);
+                            res_q_mvar = r_data_shunt.res_q_.coeff(my_id);
+                            res_v_kv = r_data_shunt.res_v_.coeff(my_id);
+                            res_theta_deg = r_data_shunt.res_theta_.coeff(my_id);
+                        }
+                    }
+                }
+        };
+        typedef ShuntInfo DataInfo;
+
+    private:
+        typedef DataConstIterator<DataShunt> DataShuntConstIterator;
+
+    public:
+        typedef DataShuntConstIterator const_iterator_type;
+        const_iterator_type begin() const {return DataShuntConstIterator(this, 0); }
+        const_iterator_type end() const {return DataShuntConstIterator(this, nb()); }
+        ShuntInfo operator[](int id) const
+        {
+            if(id < 0)
+            {
+                throw std::range_error("You cannot ask for a negative load id.");
+            }
+            if(id >= nb())
+            {
+                throw std::range_error("Generator out of bound. Not enough loads on the grid.");
+            }
+            return ShuntInfo(*this, id);
+        }
+
     public:
     typedef std::tuple<
            std::vector<real_type>, // p_mw
@@ -74,8 +147,6 @@ class DataShunt : public DataGeneric
                          real_type sn_mva,
                          bool ac);
     void reset_results();
-    virtual real_type get_p_slack(int slack_bus_id);
-    virtual void get_q(std::vector<real_type>& q_by_bus);
 
     tuple3d get_res() const {return tuple3d(res_p_, res_q_, res_v_);}
     Eigen::Ref<const RealVect> get_theta() const {return res_theta_;}
diff --git a/src/DataTrafo.cpp b/src/DataTrafo.cpp
index d7a9dcc..ead36ce 100644
--- a/src/DataTrafo.cpp
+++ b/src/DataTrafo.cpp
@@ -216,23 +216,23 @@ void DataTrafo::fillYbus(std::vector<Eigen::Triplet<cplx_type> > & res,
     }
 }
 
-void DataTrafo::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver){
+void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver){
     if(ac) return;
     // return;
     const int nb_trafo = nb();
     int bus_id_me, bus_id_solver_hv, bus_id_solver_lv;
-    cplx_type tmp;
+    // cplx_type tmp;
     for(int trafo_id = 0; trafo_id < nb_trafo; ++trafo_id){
         //  i don't do anything if the load is disconnected
         if(!status_[trafo_id]) continue;
-
+        if(dc_x_tau_shift_[trafo_id] == 0.) continue; // nothing to do if the trafo is not concerned (no phase shifter)
         bus_id_me = bus_lv_id_(trafo_id);
         bus_id_solver_lv = id_grid_to_solver[bus_id_me];
         if(bus_id_solver_lv == _deactivated_bus_id){
             std::ostringstream exc_;
             exc_ << "DataTrafo::fillSbus: the trafo with id ";
             exc_ << trafo_id;
-            exc_ << " is connected (hv side) to a disconnected bus while being connected";
+            exc_ << " is connected (lv side) to a disconnected bus while being connected";
             throw std::runtime_error(exc_.str());
         }
         bus_id_me = bus_hv_id_(trafo_id);
@@ -241,10 +241,9 @@ void DataTrafo::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_g
             std::ostringstream exc_;
             exc_ << "DataTrafo::fillSbus: the trafo with id ";
             exc_ << trafo_id;
-            exc_ << " is connected (lv side) to a disconnected bus while being connected";
+            exc_ << " is connected (hv side) to a disconnected bus while being connected";
             throw std::runtime_error(exc_.str());
         }
-
         Sbus.coeffRef(bus_id_solver_hv) += dc_x_tau_shift_[trafo_id];
         Sbus.coeffRef(bus_id_solver_lv) -= dc_x_tau_shift_[trafo_id];
     }
@@ -352,29 +351,3 @@ void DataTrafo::reset_results(){
     res_v_lv_ = RealVect();  // in kV
     res_a_lv_ = RealVect();  // in kA
 }
-
-real_type DataTrafo::get_p_slack(int slack_bus_id)
-{
-    const int nb_element = nb();
-    real_type res = 0.;
-    for(int line_id = 0; line_id < nb_element; ++line_id)
-    {
-        if(!status_[line_id]) continue;
-        if(bus_hv_id_(line_id) == slack_bus_id) res += res_p_hv_(line_id);
-        if(bus_lv_id_(line_id) == slack_bus_id) res += res_p_lv_(line_id);
-    }
-    return res;
-}
-
-void DataTrafo::get_q(std::vector<real_type>& q_by_bus)
-{
-    const int nb_element = nb();
-    for(int el_id = 0; el_id < nb_element; ++el_id)
-    {
-        if(!status_[el_id]) continue;
-        int bus_id_hv = bus_hv_id_[el_id];
-        int bus_id_lv = bus_lv_id_[el_id];
-        q_by_bus[bus_id_hv] += res_q_hv_(el_id);
-        q_by_bus[bus_id_lv] += res_q_lv_(el_id);
-    }
-}
diff --git a/src/DataTrafo.h b/src/DataTrafo.h
index c620eb1..ec9192f 100644
--- a/src/DataTrafo.h
+++ b/src/DataTrafo.h
@@ -180,7 +180,7 @@ class DataTrafo : public DataGeneric
                           bool ac,
                           const std::vector<int> & id_grid_to_solver,
                           real_type sn_mva);
-    virtual void fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver);  // needed for dc mode
+    virtual void hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver);  // needed for dc mode
 
     void compute_results(const Eigen::Ref<const RealVect> & Va,
                          const Eigen::Ref<const RealVect> & Vm,
@@ -190,8 +190,6 @@ class DataTrafo : public DataGeneric
                          real_type sn_mva,
                          bool ac);
     void reset_results();
-    virtual real_type get_p_slack(int slack_bus_id);
-    virtual void get_q(std::vector<real_type>& q_by_bus);
 
     tuple4d get_res_hv() const {return tuple4d(res_p_hv_, res_q_hv_, res_v_hv_, res_a_hv_);}
     tuple4d get_res_lv() const {return tuple4d(res_p_lv_, res_q_lv_, res_v_lv_, res_a_lv_);}
diff --git a/src/GaussSeidelSolver.cpp b/src/GaussSeidelSolver.cpp
index bae20ca..365d02a 100644
--- a/src/GaussSeidelSolver.cpp
+++ b/src/GaussSeidelSolver.cpp
@@ -11,6 +11,8 @@
 bool GaussSeidelSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
                                    CplxVect & V,
                                    const CplxVect & Sbus,
+                                   const Eigen::VectorXi & slack_ids,
+                                   const RealVect & slack_weights,  // currently unused
                                    const Eigen::VectorXi & pv,
                                    const Eigen::VectorXi & pq,
                                    int max_iter,
@@ -25,15 +27,18 @@ bool GaussSeidelSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
     // TODO check what can be checked: no voltage at 0, Ybus is square, Sbus same size than V and
     // TODO Ybus (nrow or ncol), pv and pq have value that are between 0 and nrow etc.
     reset_timer();
-    if(err_ > 0) return false; // i don't do anything if there were a problem at the initialization
+    err_ = ErrorType::NoError;
     auto timer = CustTimer();
 
+    // TODO SLACK (for now i put all slacks as PV, except the first one)
+    Eigen::VectorXi my_pv = retrieve_pv_with_slack(slack_ids, pv);
+
     V_ = V;
     Vm_ = V_.array().abs();  // update Vm and Va again in case
     Va_ = V_.array().arg();  // we wrapped around with a negative Vm
 
     // first check, if the problem is already solved, i stop there
-    RealVect F = _evaluate_Fx(Ybus, V, Sbus, pv, pq);
+    RealVect F = _evaluate_Fx(Ybus, V, Sbus, my_pv, pq);
     bool converged = _check_for_convergence(F, tol);
     nr_iter_ = 0; //current step
     bool res = true;  // have i converged or not
@@ -49,19 +54,22 @@ bool GaussSeidelSolver::compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
         // https://github.com/rwl/PYPOWER/blob/master/pypower/gausspf.py
 
         auto timer2 = CustTimer();
-        one_iter(tmp_Sbus, Ybus, pv, pq);
+        one_iter(tmp_Sbus, Ybus, my_pv, pq);
         timer_solve_ += timer2.duration();
 
         // #####################
         // stopping criteria
         // #####################
-        F = _evaluate_Fx(Ybus, V_, tmp_Sbus, pv, pq);
+        F = _evaluate_Fx(Ybus, V_, tmp_Sbus, my_pv, pq);
         bool tmp = F.allFinite();
-        if(!tmp) break; // divergence due to Nans
+        if(!tmp){
+            err_ = ErrorType::InifiniteValue;
+            break; // divergence due to Nans
+        }
         converged = _check_for_convergence(F, tol);
     }
     if(!converged){
-        err_ = 4;
+        if (err_ == ErrorType::NoError) err_ = ErrorType::TooManyIterations;
         res = false;
     }
     Vm_ = V_.array().abs();  // update Vm and Va again in case
@@ -82,7 +90,7 @@ void GaussSeidelSolver::one_iter(CplxVect & tmp_Sbus,
     const int n_pq = static_cast<int>(pq.size());
 
     // update PQ buses
-    for(int k_tmp=0; k_tmp<n_pq; ++k_tmp)
+    for(int k_tmp=0; k_tmp < n_pq; ++k_tmp)
     {
         int k = pq.coeff(k_tmp);
         tmp = tmp_Sbus.coeff(k) / V_.coeff(k);
diff --git a/src/GaussSeidelSolver.h b/src/GaussSeidelSolver.h
index 8f509f4..26e0a46 100644
--- a/src/GaussSeidelSolver.h
+++ b/src/GaussSeidelSolver.h
@@ -28,6 +28,8 @@ class GaussSeidelSolver : public BaseSolver
         bool compute_pf(const Eigen::SparseMatrix<cplx_type> & Ybus,
                         CplxVect & V,
                         const CplxVect & Sbus,
+                        const Eigen::VectorXi & slack_ids,
+                        const RealVect & slack_weights,  // currently unused
                         const Eigen::VectorXi & pv,
                         const Eigen::VectorXi & pq,
                         int max_iter,
diff --git a/src/GridModel.cpp b/src/GridModel.cpp
index f22e0cd..091ef2d 100644
--- a/src/GridModel.cpp
+++ b/src/GridModel.cpp
@@ -43,10 +43,6 @@ GridModel::GridModel(const GridModel & other)
     // 8. storage units
     storages_ = other.storages_;
 
-    // 9. slack bus
-    gen_slackbus_ = other.gen_slackbus_;
-    slack_bus_id_ = other.slack_bus_id_;
-
     // copy the attributes specific grid2op (speed optimization)
     n_sub_ = other.n_sub_;
     load_pos_topo_vect_ = other.load_pos_topo_vect_;
@@ -67,6 +63,7 @@ GridModel::GridModel(const GridModel & other)
 
     // assign the right solver
     _solver.change_solver(other._solver.get_type());
+    _dc_solver.change_solver(other._dc_solver.get_type());
     compute_results_ = other.compute_results_;
 }
 
@@ -98,8 +95,7 @@ GridModel::StateRes GridModel::get_state() const
                             res_gen,
                             res_load,
                             res_sgen,
-                            res_storage,
-                            gen_slackbus_
+                            res_storage
                             );
     return res;
 };
@@ -146,7 +142,6 @@ void GridModel::set_state(GridModel::StateRes & my_state)
     DataSGen::StateRes & state_sgens= std::get<12>(my_state);
     // storage units
     DataLoad::StateRes & state_storages = std::get<13>(my_state);
-    int gen_slackbus = std::get<14>(my_state);
 
     // assign it to this instance
 
@@ -171,10 +166,6 @@ void GridModel::set_state(GridModel::StateRes & my_state)
     sgens_.set_state(state_sgens);
     // 7. storage units
     storages_.set_state(state_storages);
-
-    // other stuff
-    gen_slackbus_ = gen_slackbus;
-
 };
 
 //init
@@ -195,25 +186,29 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc)
     if(reset_ac){
         id_me_to_ac_solver_ = std::vector<int>();
         id_ac_solver_to_me_ = std::vector<int>();
-        slack_bus_id_ac_solver_ = -1;
+        slack_bus_id_ac_solver_ = Eigen::VectorXi();
         Ybus_ac_ = Eigen::SparseMatrix<cplx_type>();
     }
 
     if(reset_dc){
         id_me_to_dc_solver_ = std::vector<int>();
         id_dc_solver_to_me_ = std::vector<int>();
-        slack_bus_id_dc_solver_ = -1;
+        slack_bus_id_dc_solver_ = Eigen::VectorXi();
         Ybus_dc_ = Eigen::SparseMatrix<cplx_type>();
     }
 
     Sbus_ = CplxVect();
     bus_pv_ = Eigen::VectorXi();
     bus_pq_ = Eigen::VectorXi();
+    slack_weights_ = RealVect();
     need_reset_ = true;
     topo_changed_ = true;
 
     // reset the solvers
-    if (reset_solver) _solver.reset();
+    if (reset_solver){
+        _solver.reset();
+        _dc_solver.reset();
+    }
     // std::cout << "GridModel::reset called" << std::endl;
 }
 
@@ -221,8 +216,6 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit,
                           int max_iter,
                           real_type tol)
 {
-    // std::cout << "______________________" << std::endl;
-    // std::cout << "computing AC pf" << std::endl;
     const int nb_bus = static_cast<int>(bus_vn_kv_.size());
     if(Vinit.size() != nb_bus){
         std::ostringstream exc_;
@@ -242,7 +235,8 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit,
                                     is_ac, reset_solver);
 
     // start the solver
-    conv = _solver.compute_pf(Ybus_ac_, V, Sbus_, bus_pv_, bus_pq_, max_iter, tol / sn_mva_);
+    slack_weights_ = generators_.get_slack_weights(Ybus_ac_.rows(), id_me_to_ac_solver_); 
+    conv = _solver.compute_pf(Ybus_ac_, V, Sbus_, slack_bus_id_ac_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol / sn_mva_);
 
     // store results (in ac mode)
     process_results(conv, res, Vinit, true, id_me_to_ac_solver_);
@@ -304,9 +298,11 @@ CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_lim
             res.coeffRef(gen.bus_id) = {std::real(res.coeff(gen.bus_id)), my_zero_};
         }
 
-        if(gen.id == gen_slackbus_)
+        // if(gen.id == gen_slackbus_)
+        if(gen.is_slack)
         {
             // slack bus, by definition, can handle all active value
+            // This is probably not the case with distributed slack !
             res.coeffRef(gen.bus_id) = {my_zero_, std::imag(res.coeff(gen.bus_id))};
         }
     }
@@ -324,7 +320,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit,
                                        Eigen::SparseMatrix<cplx_type> & Ybus,
                                        std::vector<int> & id_me_to_solver,
                                        std::vector<int> & id_solver_to_me,
-                                       int & slack_bus_id_solver,
+                                       Eigen::VectorXi & slack_bus_id_solver,
                                        bool is_ac,
                                        bool reset_solver)
 {
@@ -338,35 +334,30 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit,
     if(topo_changed_) reset(reset_solver, reset_ac, reset_dc);  // TODO what if pv and pq changed ? :O
     else{
         // topo is not changed, but i can still reset the solver (TODO: no necessarily needed !)
-        if (reset_solver) _solver.reset();
+        if (reset_solver)
+        {
+            if(is_ac) _solver.reset();
+            else _dc_solver.reset();
+        }
     }
-    slack_bus_id_ = generators_.get_slack_bus_id(gen_slackbus_);
+    slack_bus_id_ = generators_.get_slack_bus_id();
     if(topo_changed_){
         // TODO do not reinit Ybus if the topology does not change
-        init_Ybus(Ybus, id_me_to_solver, id_solver_to_me, slack_bus_id_solver);
+        init_Ybus(Ybus, id_me_to_solver, id_solver_to_me);
         fillYbus(Ybus, is_ac, id_me_to_solver);
     }
-    // std::cout << "GridModel::pre_process_solver : solver and Ybus init " << std::endl;
-    // std::cout << "id_me_to_solver " << id_me_to_solver.size() << std::endl;
-    // std::cout << "id_solver_to_me " << id_solver_to_me.size() << std::endl;
-    // std::cout << "slack_bus_id_solver " << slack_bus_id_solver << std::endl;
-    // std::cout << "Ybus " << Ybus.nonZeros() << std::endl;
     init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver);
-    // std::cout << "GridModel::pre_process_solver : SBus init " << std::endl;
     fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver); // TODO what if pv and pq changed ? :O
-    // std::cout << "GridModel::pre_process_solver : pv|pq init " << std::endl;
     
     generators_.init_q_vector(static_cast<int>(bus_vn_kv_.size()));
-    // }
     fillSbus_me(Sbus_, is_ac, id_me_to_solver, slack_bus_id_solver);
 
     const int nb_bus_solver = static_cast<int>(id_solver_to_me.size());
     CplxVect V = CplxVect::Constant(nb_bus_solver, init_vm_pu_);
     for(int bus_solver_id = 0; bus_solver_id < nb_bus_solver; ++bus_solver_id){
-        int bus_me_id = id_solver_to_me[bus_solver_id];  //POSSIBLE SEGFAULT
+        int bus_me_id = id_solver_to_me[bus_solver_id];  //TODO DEBUG MODE : POSSIBLE SEGFAULT
         cplx_type tmp = Vinit(bus_me_id);
         V(bus_solver_id) = tmp;
-        // TODO save this V somewhere
     }
     generators_.set_vm(V, id_me_to_solver);
     return V;
@@ -402,7 +393,8 @@ void GridModel::process_results(bool conv, CplxVect & res, const CplxVect & Vini
             compute_results(ac);
         }
         need_reset_ = false;
-        const CplxVect & res_tmp = _solver.get_V();
+        const CplxVect & res_tmp = ac ? _solver.get_V(): _dc_solver.get_V() ;
+
         // convert back the results to "big" vector
         res = _get_results_back_to_orig_nodes(res_tmp,
                                               id_me_to_solver,
@@ -416,8 +408,7 @@ void GridModel::process_results(bool conv, CplxVect & res, const CplxVect & Vini
 
 void GridModel::init_Ybus(Eigen::SparseMatrix<cplx_type> & Ybus,
                           std::vector<int>& id_me_to_solver,
-                          std::vector<int>& id_solver_to_me,
-                          int & slack_bus_id_solver){
+                          std::vector<int>& id_solver_to_me){
     //TODO get disconnected bus !!! (and have some conversion for it)
     //1. init the conversion bus
     const int nb_bus_init = static_cast<int>(bus_vn_kv_.size());
@@ -444,14 +435,22 @@ void GridModel::init_Ybus(Eigen::SparseMatrix<cplx_type> & Ybus,
 void GridModel::init_Sbus(CplxVect & Sbus,
                           std::vector<int>& id_me_to_solver,
                           std::vector<int>& id_solver_to_me,
-                          int & slack_bus_id_solver){
+                          Eigen::VectorXi & slack_bus_id_solver){
     
     const int nb_bus = static_cast<int>(id_solver_to_me.size());                 
     Sbus = CplxVect::Constant(nb_bus, 0.);
-    slack_bus_id_solver = id_me_to_solver[slack_bus_id_];
-    if(slack_bus_id_solver == _deactivated_bus_id){
-        //TODO improve error message with the gen_id
-        throw std::runtime_error("The slack bus is disconnected.");
+    slack_bus_id_solver = Eigen::VectorXi::Zero(slack_bus_id_.size());
+
+    size_t i = 0;
+    for(auto el: slack_bus_id_) {
+        slack_bus_id_solver(i) = id_me_to_solver[el];
+        ++i;
+    }
+    
+    if(is_in_vect(_deactivated_bus_id, slack_bus_id_solver)){
+        // TODO improve error message with the gen_id
+        // TODO DEBUG MODE: only check that in debug mode
+        throw std::runtime_error("One of the slack bus is disconnected !");
     }
 }
 void GridModel::fillYbus(Eigen::SparseMatrix<cplx_type> & res, bool ac, const std::vector<int>& id_me_to_solver){
@@ -474,29 +473,25 @@ void GridModel::fillYbus(Eigen::SparseMatrix<cplx_type> & res, bool ac, const st
     res.makeCompressed();
 }
 
-void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector<int>& id_me_to_solver, int slack_bus_id_solver)
+void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector<int>& id_me_to_solver, Eigen::VectorXi & slack_bus_id_solver)
 {
     // init the Sbus vector
     powerlines_.fillSbus(Sbus, true, id_me_to_solver);
-    shunts_.fillSbus(Sbus, true, id_me_to_solver);
     trafos_.fillSbus(Sbus, ac, id_me_to_solver);
+    shunts_.fillSbus(Sbus, true, id_me_to_solver);
     loads_.fillSbus(Sbus, true, id_me_to_solver);
     sgens_.fillSbus(Sbus, true, id_me_to_solver);
     storages_.fillSbus(Sbus, true, id_me_to_solver);
     generators_.fillSbus(Sbus, true, id_me_to_solver);
 
-    // handle slack bus (in ac only)
-    if(ac)
-    {
-        real_type sum_active = Sbus.sum().real();
-        Sbus.coeffRef(slack_bus_id_solver) -= sum_active;
-    }
     if (sn_mva_ != 1.0) Sbus /= sn_mva_;
+    // in dc mode, this is used for the phase shifter, this should not be divided by sn_mva_ !
+    trafos_.hack_Sbus_for_dc_phase_shifter(Sbus, ac, id_me_to_solver);
 }
 
 void GridModel::fillpv_pq(const std::vector<int>& id_me_to_solver,
                           std::vector<int>& id_solver_to_me,
-                          int slack_bus_id_solver)
+                          Eigen::VectorXi & slack_bus_id_solver)
 {
     // init pq and pv vector
     // TODO remove the order here..., i could be faster in this piece of code (looping once through the buses)
@@ -518,7 +513,7 @@ void GridModel::fillpv_pq(const std::vector<int>& id_me_to_solver,
     generators_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver);
 
     for(int bus_id = 0; bus_id< nb_bus; ++bus_id){
-        if(bus_id == slack_bus_id_solver) continue;  // slack bus is not PQ either
+        if(is_in_vect(bus_id, slack_bus_id_solver)) continue;  // slack bus is not PQ either
         if(has_bus_been_added[bus_id]) continue; // a pv bus cannot be PQ
         bus_pq.push_back(bus_id);
         has_bus_been_added[bus_id] = true;  // don't add it a second time
@@ -528,9 +523,9 @@ void GridModel::fillpv_pq(const std::vector<int>& id_me_to_solver,
 }
 void GridModel::compute_results(bool ac){
     // retrieve results from powerflow
-    const auto & Va = _solver.get_Va();
-    const auto & Vm = _solver.get_Vm();
-    const auto & V = _solver.get_V();
+    const auto & Va = ac ? _solver.get_Va() : _dc_solver.get_Va();
+    const auto & Vm = ac ? _solver.get_Vm() : _dc_solver.get_Vm();
+    const auto & V = ac ? _solver.get_V() : _dc_solver.get_V();
 
     const std::vector<int> & id_me_to_solver = ac ? id_me_to_ac_solver_ : id_me_to_dc_solver_;
     // for powerlines
@@ -548,29 +543,29 @@ void GridModel::compute_results(bool ac){
     // for prods
     generators_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac);
 
-    //handle_slack_bus
-    real_type p_slack = powerlines_.get_p_slack(slack_bus_id_);
-    p_slack += trafos_.get_p_slack(slack_bus_id_);
-    p_slack += loads_.get_p_slack(slack_bus_id_);
-    p_slack += sgens_.get_p_slack(slack_bus_id_);
-    p_slack += storages_.get_p_slack(slack_bus_id_);
-    p_slack += shunts_.get_p_slack(slack_bus_id_);
-    generators_.set_p_slack(gen_slackbus_, p_slack);
-
-    // handle gen_q now
-    std::vector<real_type> q_by_bus = std::vector<real_type>(bus_vn_kv_.size(), 0.);
+    //handle_slack_bus active power
+    CplxVect mismatch;  // power mismatch at each bus (SOLVER BUS !!!)
+    RealVect ractive_mismatch;  // not used in dc mode (DO NOT ATTEMPT TO USE IT THERE)
+    RealVect active_mismatch;
     if(ac){
-        // I do not bother with reactive in dc mode
-        powerlines_.get_q(q_by_bus);
-        trafos_.get_q(q_by_bus);
-        loads_.get_q(q_by_bus);
-        storages_.get_q(q_by_bus);
-        sgens_.get_q(q_by_bus);
-        shunts_.get_q(q_by_bus);
+        // In AC mode i am not forced to run through all the grid
+        auto tmp = (Ybus_ac_ * V).conjugate();
+        mismatch = V.array() * tmp.array() - Sbus_.array();
+        active_mismatch = mismatch.real() * sn_mva_;
+    } else{
+        active_mismatch = RealVect::Zero(V.size());
+        //TODO SLACK: improve distributed slack for DC mode !
+        // it is possible to know in advance the contribution of each slack generators (sum(Sbus) MW 
+        // to split among the contributing generators) so it's possible to "mess with" Sbus 
+        // for such purpose
+        const auto id_slack = slack_bus_id_dc_solver_(0);
+        active_mismatch(id_slack) = -Sbus_.real().sum() * sn_mva_;
     }
+    generators_.set_p_slack(active_mismatch, id_me_to_solver);
 
+    if(ac) ractive_mismatch = mismatch.imag() * sn_mva_;
     // mainly to initialize the Q value of the generators in dc (just fill it with 0.)
-    generators_.set_q(q_by_bus, ac);
+    generators_.set_q(ractive_mismatch, id_me_to_solver, ac);
 }
 
 void GridModel::reset_results(){
@@ -583,155 +578,24 @@ void GridModel::reset_results(){
     generators_.reset_results();
 }
 
-CplxVect GridModel::dc_pf_old(const CplxVect & Vinit,
-                              int max_iter,  // not used for DC
-                              real_type tol  // not used for DC
-                              )
-{
-    // TODO refactor that with ac pf, this is mostly done, but only mostly...
-    const int nb_bus = static_cast<int>(bus_vn_kv_.size());
-    if(static_cast<int>(Vinit.size()) != nb_bus){
-        std::ostringstream exc_;
-        exc_ << "GridModel::dc_pf_old: Size of the Vinit should be the same as the total number of buses. Currently:  ";
-        exc_ << "Vinit: " << Vinit.size() << " and there are " << nb_bus << " buses.";
-        exc_ << "(fyi: Components of Vinit corresponding to deactivated bus will be ignored anyway, so you can put whatever you want there).";
-        throw std::runtime_error(exc_.str());
-    }
-    Eigen::SparseMatrix<cplx_type> dcYbus_tmp;
-    CplxVect Sbus_tmp;
-    std::vector<int> id_me_to_solver;
-    std::vector<int> id_solver_to_me;
-    int slack_bus_id_solver;
-
-    //if(need_reset_){
-    slack_bus_id_ = generators_.get_slack_bus_id(gen_slackbus_);
-    init_Ybus(dcYbus_tmp, id_me_to_solver, id_solver_to_me, slack_bus_id_solver);
-    init_Sbus(Sbus_tmp, id_me_to_solver, id_solver_to_me, slack_bus_id_solver);
-    fillYbus(dcYbus_tmp, false, id_me_to_solver);
-    // fillpv_pq(id_me_to_solver);
-    //}
-    fillSbus_me(Sbus_tmp, false, id_me_to_solver, slack_bus_id_solver);
-
-
-    // extract only connected bus from Vinit
-    const int nb_bus_solver = static_cast<int>(id_solver_to_me.size());
-
-    // DC SOLVER STARTS HERE
-    // TODO all this should rather be one in a "dc solver" instead of here
-    // remove the slack bus
-
-    // remove the slack bus from Ybus
-    // TODO see if "prune" might work here https://eigen.tuxfamily.org/dox/classEigen_1_1SparseMatrix.html#title29
-    dcYbus_tmp.makeCompressed();
-    Eigen::SparseMatrix<real_type> dcYbus = Eigen::SparseMatrix<real_type>(nb_bus_solver - 1, nb_bus_solver - 1);
-    std::vector<Eigen::Triplet<real_type> > tripletList;
-    tripletList.reserve(dcYbus_tmp.nonZeros());
-    for (int k=0; k < nb_bus_solver; ++k){
-        if(k == slack_bus_id_solver) continue;  // I don't add anything to the slack bus
-        for (Eigen::SparseMatrix<cplx_type>::InnerIterator it(dcYbus_tmp, k); it; ++it)
-        {
-            int row_res = static_cast<int>(it.row());
-            if(row_res == slack_bus_id_solver) continue;
-            row_res = row_res > slack_bus_id_solver ? row_res - 1 : row_res;
-            int col_res = static_cast<int>(it.col());
-            col_res = col_res > slack_bus_id_solver ? col_res - 1 : col_res;
-            tripletList.push_back(Eigen::Triplet<real_type> (row_res, col_res, std::real(it.value())));
-        }
-    }
-    dcYbus.setFromTriplets(tripletList.begin(), tripletList.end());
-    dcYbus.makeCompressed();
-
-    // initialize the solver
-    Eigen::SparseLU<Eigen::SparseMatrix<real_type>, Eigen::COLAMDOrdering<int> >   solver;
-    solver.analyzePattern(dcYbus);
-    solver.factorize(dcYbus);
-    if(solver.info() != Eigen::Success) {
-        // matrix is not connected
-        return CplxVect();
-    }
-
-    // remove the slack bus from Sbus
-    RealVect Sbus = RealVect::Constant(nb_bus_solver - 1, 0.);
-    for (int k=0; k < nb_bus_solver; ++k){
-        if(k == slack_bus_id_solver) continue;  // I don't add anything to the slack bus
-        int col_res = k;
-        col_res = col_res > slack_bus_id_solver ? col_res - 1 : col_res;
-        Sbus(col_res) = std::real(Sbus_tmp(k));
-    }
-
-    // solve for theta: Sbus = dcY . theta
-    RealVect Va_dc = solver.solve(Sbus);
-    if(solver.info() != Eigen::Success) {
-        // solving failed, this should not happen in dc ...
-        return CplxVect();
-    }
-
-    // retrieve back the results in the proper shape
-    const int nb_bus_me = static_cast<int>(bus_vn_kv_.size());
-    int bus_id_solver;
-    RealVect Va = RealVect::Constant(nb_bus_me, 0.);
-    // fill Va from dc approx
-    for (int bus_id_me=0; bus_id_me < nb_bus_me; ++bus_id_me){
-        if(bus_id_me == slack_bus_id_) continue;  // slack bus is handled elsewhere
-        if(!bus_status_[bus_id_me]) continue;  // nothing is done if the bus is not connected
-
-        bus_id_solver = id_me_to_solver[bus_id_me];
-        if(bus_id_solver == _deactivated_bus_id){
-            //TODO improve error message with the gen_id
-            throw std::runtime_error("One bus is both connected and disconnected");
-        }
-        bus_id_solver = bus_id_solver > slack_bus_id_solver ? bus_id_solver - 1 : bus_id_solver;
-        Va(bus_id_me) = Va_dc(bus_id_solver);
-    }
-    Va.array() +=  std::arg(Vinit(slack_bus_id_));
-
-    // fill Vm either Vinit if pq or Vm if pv (TODO)
-    RealVect Vm;
-    if(false){
-        Vm = Vinit.array().abs();  // fill Vm = Vinit for all
-        // put Vm = 0. for disconnected bus
-        for (int bus_id_me=0; bus_id_me < nb_bus_me; ++bus_id_me){
-            if(bus_status_[bus_id_me]) continue;  // nothing is done if the bus is connected
-            Vm(bus_id_me) = 0.;
-        }
-        // put Vm = Vm of turned on gen
-        // generators_.get_vm_for_dc(Vm);
-        // assign vm of the slack bus
-        Vm(slack_bus_id_) =  std::abs(Vinit(slack_bus_id_));
-    }
-    else{
-        Vm = RealVect::Constant(Vinit.size(), 1.0);
-        for (int bus_id_me=0; bus_id_me < nb_bus_me; ++bus_id_me){
-            if(bus_status_[bus_id_me]) continue;  // nothing is done if the bus is connected
-            Vm(bus_id_me) = 0.;
-        }
-        generators_.get_vm_for_dc(Vm);
-    }
-    //END of the SOLVER PART
-
-    //TODO handle Vm = Vm (gen) for connected generators
-    return Vm.array() * (Va.array().cos().cast<cplx_type>() + my_i * Va.array().sin().cast<cplx_type>());
-}
-
 CplxVect GridModel::dc_pf(const CplxVect & Vinit,
                           int max_iter,  // not used for DC
                           real_type tol  // not used for DC
                           )
 {
-    
-    // std::cout << "______________________" << std::endl;
-    // std::cout << "computing dc pf" << std::endl;
+    //TODO SLACK: improve distributed slack for DC mode !
+    // the idea is to "mess" with the Sbus beforehand to split the "losses"
+    // ie fake the action of generators to adjust Sbus such that sum(Sbus) = 0
+    // and the slack contribution factors are met.
     const int nb_bus = static_cast<int>(bus_vn_kv_.size());
     if(Vinit.size() != nb_bus){
+        //TODO DEBUG MODE: 
         std::ostringstream exc_;
         exc_ << "GridModel::dc_pf: Size of the Vinit should be the same as the total number of buses. Currently:  ";
         exc_ << "Vinit: " << Vinit.size() << " and there are " << nb_bus << " buses.";
         exc_ << "(fyi: Components of Vinit corresponding to deactivated bus will be ignored anyway, so you can put whatever you want there).";
         throw std::runtime_error(exc_.str());
     }
-    const SolverType solver_type = _solver.get_type();
-    _solver.change_solver(SolverType::DC);
-
     bool conv = false;
     CplxVect res = CplxVect();
 
@@ -743,17 +607,11 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit,
                                     is_ac, reset_solver);
 
     // start the solver
-    conv = _solver.compute_pf(Ybus_dc_, V, Sbus_, bus_pv_, bus_pq_, max_iter, tol);
+    slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_);
+    conv = _dc_solver.compute_pf(Ybus_dc_, V, Sbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol);
 
     // store results (fase -> because I am in dc mode)
     process_results(conv, res, Vinit, false, id_me_to_dc_solver_);
-
-    // put back the solver to its original state
-    // TODO add a better handling of this!
-    _solver.change_solver(solver_type);
-
-    // std::cout << "end dc pf" << std::endl;
-    // return the vector of complex voltage at each bus
     return res;
 }
 
@@ -770,7 +628,7 @@ int GridModel::nb_bus() const
     return res;
 }
 
-void GridModel::add_gen_slackbus(int gen_id){
+void GridModel::add_gen_slackbus(int gen_id, real_type weight){
     if(gen_id < 0)
     {
         std::ostringstream exc_;
@@ -785,7 +643,32 @@ void GridModel::add_gen_slackbus(int gen_id){
         exc_ << "Generator with id " << gen_id << " does not exist and can't be the slack bus";
         throw std::runtime_error(exc_.str());
     }
-    gen_slackbus_ = gen_id;
+    if(weight <= 0.){
+        std::ostringstream exc_;
+        exc_ << "GridModel::add_gen_slackbus: please enter a valid weight for the slack bus (> 0.)";
+        throw std::runtime_error(exc_.str());
+    }
+    generators_.add_slackbus(gen_id, weight);
+}
+
+void GridModel::remove_gen_slackbus(int gen_id){
+    if(gen_id < 0)
+    {
+        // TODO DEBUG MODE: only check when in debug mode
+        std::ostringstream exc_;
+        exc_ << "GridModel::remove_gen_slackbus: Slack bus should be an id of a generator, thus positive. You provided: ";
+        exc_ << gen_id;
+        throw std::runtime_error(exc_.str());
+    }
+    if(gen_id > generators_.nb())
+    {
+        // TODO DEBUG MODE: only check when in debug mode
+        std::ostringstream exc_;
+        exc_ << "GridModel::remove_gen_slackbus: There are only " << generators_.nb() << " generators on the grid. ";
+        exc_ << "Generator with id " << gen_id << " does not exist and can't be the slack bus";
+        throw std::runtime_error(exc_.str());
+    }
+    generators_.remove_slackbus(gen_id);
 }
 
 /** GRID2OP SPECIFIC REPRESENTATION **/
diff --git a/src/GridModel.h b/src/GridModel.h
index 4828ff6..a6cee24 100644
--- a/src/GridModel.h
+++ b/src/GridModel.h
@@ -11,6 +11,7 @@
 
 #include <iostream>
 #include <vector>
+// #include <set>
 #include <stdio.h>
 #include <cstdint> // for int32
 #include <chrono>
@@ -63,12 +64,12 @@ class GridModel : public DataGeneric
                 // static generators
                 DataSGen::StateRes,
                 // storage units
-                DataLoad::StateRes,
-                // slack bus generator id
-                int
+                DataLoad::StateRes
                 >  StateRes;
 
-        GridModel():need_reset_(true), topo_changed_(true), compute_results_(true),init_vm_pu_(1.04), sn_mva_(1.0){};
+        GridModel():need_reset_(true), topo_changed_(true), compute_results_(true),init_vm_pu_(1.04), sn_mva_(1.0){
+            _dc_solver.change_solver(SolverType::DC);
+        }
         GridModel(const GridModel & other);
         GridModel copy() const{
             GridModel res(*this);
@@ -77,6 +78,8 @@ class GridModel : public DataGeneric
         Eigen::Index total_bus() const {return bus_vn_kv_.size();}
         const std::vector<int> & id_me_to_ac_solver() const {return id_me_to_ac_solver_;}
         const std::vector<int> & id_ac_solver_to_me() const {return id_ac_solver_to_me_;}
+        const std::vector<int> & id_me_to_dc_solver() const {return id_me_to_dc_solver_;}
+        const std::vector<int> & id_dc_solver_to_me() const {return id_dc_solver_to_me_;}
 
         // retrieve the underlying data (raw class)
         const DataGen & get_generators_as_data() const {return generators_;}
@@ -90,10 +93,14 @@ class GridModel : public DataGeneric
         void change_solver(const SolverType & type){
             need_reset_ = true;
             topo_changed_ = true;
-            _solver.change_solver(type);
+            if(_solver.is_dc(type)) _dc_solver.change_solver(type);
+            else _solver.change_solver(type);
         }
         std::vector<SolverType> available_solvers() {return _solver.available_solvers(); }
         SolverType get_solver_type() {return _solver.get_type(); }
+        SolverType get_dc_solver_type() {return _dc_solver.get_type(); }
+        const ChooseSolver & get_solver() const {return _solver;}
+        const ChooseSolver & get_dc_solver() const {return _dc_solver;}
 
         // do i compute the results (in terms of P,Q,V or loads, generators and flows on lines
         void deactivate_result_computation(){compute_results_=false;}
@@ -160,7 +167,8 @@ class GridModel : public DataGeneric
             storages_.init(storages_p, storages_q, storages_bus_id);
         }
 
-        void add_gen_slackbus(int gen_id);
+        void add_gen_slackbus(int gen_id, real_type weight);
+        void remove_gen_slackbus(int gen_id);
 
         //pickle
         GridModel::StateRes get_state() const ;
@@ -184,10 +192,6 @@ class GridModel : public DataGeneric
         void tell_topo_changed(){topo_changed_ = true;}  //should be used after the powerflow as run, so some vectors will not be recomputed if not needed.
 
         // dc powerflow
-        CplxVect dc_pf_old(const CplxVect & Vinit,
-                                   int max_iter,  // not used for DC
-                                   real_type tol  // not used for DC
-                                   );
         CplxVect dc_pf(const CplxVect & Vinit,
                                int max_iter,  // not used for DC
                                real_type tol  // not used for DC
@@ -206,10 +210,19 @@ class GridModel : public DataGeneric
         void deactivate_bus(int bus_id) {_deactivate(bus_id, bus_status_, topo_changed_); }
         // if a bus is connected, but isolated, it will make the powerflow diverge
         void reactivate_bus(int bus_id) {_reactivate(bus_id, bus_status_, topo_changed_); }
-        int nb_bus() const;
+        int nb_bus() const;  // number of activated buses
         Eigen::Index nb_powerline() const {return powerlines_.nb();}
         Eigen::Index nb_trafo() const {return trafos_.nb();}
 
+        // read only data accessor
+        const DataLine & get_lines() const {return powerlines_;}
+        const DataTrafo & get_trafos() const {return trafos_;}
+        const DataGen & get_generators() const {return generators_;}
+        const DataLoad & get_loads() const {return loads_;}
+        const DataLoad & get_storages() const {return storages_;}
+        const DataSGen & get_static_generators() const {return sgens_;}
+        const DataShunt & get_shunts() const {return shunts_;}
+
         //deactivate a powerline (disconnect it)
         void deactivate_powerline(int powerline_id) {powerlines_.deactivate(powerline_id, topo_changed_); }
         void reactivate_powerline(int powerline_id) {powerlines_.reactivate(powerline_id, topo_changed_); }
@@ -217,7 +230,6 @@ class GridModel : public DataGeneric
         void change_bus_powerline_ex(int powerline_id, int new_bus_id) {powerlines_.change_bus_ex(powerline_id, new_bus_id, topo_changed_, static_cast<int>(bus_vn_kv_.size())); }
         int get_bus_powerline_or(int powerline_id) {return powerlines_.get_bus_or(powerline_id);}
         int get_bus_powerline_ex(int powerline_id) {return powerlines_.get_bus_ex(powerline_id);}
-        const DataLine & get_lines() const {return powerlines_;}
 
         //deactivate trafo
         void deactivate_trafo(int trafo_id) {trafos_.deactivate(trafo_id, topo_changed_); }
@@ -226,7 +238,6 @@ class GridModel : public DataGeneric
         void change_bus_trafo_lv(int trafo_id, int new_bus_id) {trafos_.change_bus_lv(trafo_id, new_bus_id, topo_changed_, static_cast<int>(bus_vn_kv_.size())); }
         int get_bus_trafo_hv(int trafo_id) {return trafos_.get_bus_hv(trafo_id);}
         int get_bus_trafo_lv(int trafo_id) {return trafos_.get_bus_lv(trafo_id);}
-        const DataTrafo & get_trafos() const {return trafos_;}
 
         //load
         void deactivate_load(int load_id) {loads_.deactivate(load_id, topo_changed_); }
@@ -251,7 +262,6 @@ class GridModel : public DataGeneric
         void change_p_shunt(int shunt_id, real_type new_p) {shunts_.change_p(shunt_id, new_p, topo_changed_); }
         void change_q_shunt(int shunt_id, real_type new_q) {shunts_.change_q(shunt_id, new_q, topo_changed_); }
         int get_bus_shunt(int shunt_id) {return shunts_.get_bus(shunt_id);}
-        const DataGen & get_generators() const {return generators_;}
 
         //static gen
         void deactivate_sgen(int sgen_id) {sgens_.deactivate(sgen_id, topo_changed_); }
@@ -312,7 +322,11 @@ class GridModel : public DataGeneric
         Eigen::SparseMatrix<cplx_type> get_Ybus(){
             return Ybus_ac_;  // This is copied to python
         }
-        Eigen::Ref<CplxVect> get_Sbus(){
+        // TODO convert it back to this ID, that will make copies, but who really cares ?
+        Eigen::SparseMatrix<cplx_type> get_dcYbus(){
+            return Ybus_dc_;  // This is copied to python
+        }
+        Eigen::Ref<const CplxVect> get_Sbus() const{
             return Sbus_;
         }
         Eigen::Ref<const Eigen::VectorXi> get_pv() const{
@@ -321,6 +335,13 @@ class GridModel : public DataGeneric
         Eigen::Ref<const Eigen::VectorXi> get_pq() const{
             return bus_pq_;
         }
+        Eigen::Ref<const Eigen::VectorXi> get_slack_ids() const{
+            return slack_bus_id_ac_solver_;
+        }
+        Eigen::Ref<const RealVect> get_slack_weights() const{
+            return slack_weights_;
+        }
+
         Eigen::Ref<const CplxVect> get_V() const{
             return _solver.get_V();
         }
@@ -330,10 +351,14 @@ class GridModel : public DataGeneric
         Eigen::Ref<const RealVect> get_Vm() const{
             return _solver.get_Vm();
         }
-        Eigen::SparseMatrix<real_type> get_J(){
-            return _solver.get_J();  // This is copied to python
+        Eigen::Ref<const Eigen::SparseMatrix<real_type> > get_J() const{
+            return _solver.get_J();
         }
-        real_type get_computation_time(){ return _solver.get_computation_time();}
+        Eigen::SparseMatrix<real_type> get_J_python() const{
+            return _solver.get_J_python();  // This is copied to python
+        }
+        real_type get_computation_time() const{ return _solver.get_computation_time();}
+        real_type get_dc_computation_time() const{ return _dc_solver.get_computation_time();}
 
         // part dedicated to grid2op backend, optimized for grid2op data representation (for speed)
         // this is not recommended to use it outside of its intended usage within grid2op !
@@ -431,21 +456,20 @@ class GridModel : public DataGeneric
                                     Eigen::SparseMatrix<cplx_type> & Ybus,
                                     std::vector<int> & id_me_to_solver,
                                     std::vector<int> & id_solver_to_me,
-                                    int & slack_bus_id_solver,
+                                    Eigen::VectorXi & slack_bus_id_solver,
                                     bool is_ac,
                                     bool reset_solver);
         void init_Ybus(Eigen::SparseMatrix<cplx_type> & Ybus,
                        std::vector<int> & id_me_to_solver,
-                       std::vector<int>& id_solver_to_me,
-                       int & slack_bus_id_solver);
+                       std::vector<int>& id_solver_to_me);
         void init_Sbus(CplxVect & Sbus,
                        std::vector<int> & id_me_to_solver,
                        std::vector<int>& id_solver_to_me,
-                       int & slack_bus_id_solver);
+                       Eigen::VectorXi & slack_bus_id_solver);
         void fillYbus(Eigen::SparseMatrix<cplx_type> & res, bool ac, const std::vector<int>& id_me_to_solver);
-        void fillSbus_me(CplxVect & res, bool ac, const std::vector<int>& id_me_to_solver, int slack_bus_id_solver);
+        void fillSbus_me(CplxVect & res, bool ac, const std::vector<int>& id_me_to_solver, Eigen::VectorXi & slack_bus_id_solver);
         void fillpv_pq(const std::vector<int>& id_me_to_solver, std::vector<int>& id_solver_to_me,
-                      int slack_bus_id_solver);
+                       Eigen::VectorXi & slack_bus_id_solver);
 
         // results
         /**process the results from the solver to this instance
@@ -574,10 +598,10 @@ class GridModel : public DataGeneric
 
         // 8. slack bus
         // TODO multiple slack bus
-        int gen_slackbus_;
-        int slack_bus_id_;
-        int slack_bus_id_ac_solver_;
-        int slack_bus_id_dc_solver_;
+        std::vector<int> slack_bus_id_;
+        Eigen::VectorXi slack_bus_id_ac_solver_;
+        Eigen::VectorXi slack_bus_id_dc_solver_;
+        RealVect slack_weights_;
 
         // as matrix, for the solver
         Eigen::SparseMatrix<cplx_type> Ybus_ac_;
@@ -590,6 +614,7 @@ class GridModel : public DataGeneric
 
         // to solve the newton raphson
         ChooseSolver _solver;
+        ChooseSolver _dc_solver;
 
         // specific grid2op
         int n_sub_;
diff --git a/src/KLUSolver.cpp b/src/KLUSolver.cpp
index d921297..a44524f 100644
--- a/src/KLUSolver.cpp
+++ b/src/KLUSolver.cpp
@@ -8,56 +8,56 @@
 
 #include "KLUSolver.h"
 
-void KLUSolver::reset(){
-    BaseNRSolver::reset();
+ErrorType KLULinearSolver::reset(){
     klu_free_symbolic(&symbolic_, &common_);
     klu_free_numeric(&numeric_, &common_);
-    n_ = -1;
     common_ = klu_common();
-
     symbolic_ = nullptr;
     numeric_ = nullptr;
+    return ErrorType::NoError;
 }
 
-void KLUSolver::initialize(){
+ErrorType KLULinearSolver::initialize(Eigen::SparseMatrix<real_type>&  J){
     // default Eigen representation: column major, which is good for klu !
     // J is const here, even if it's not said in klu_analyze
-    auto timer = CustTimer();
-    n_ = static_cast<int>(J_.cols()); // should be equal to J_.nrows()
-    err_ = 0; // reset error message
+    const auto n = J.cols();
     common_ = klu_common();
-    symbolic_ = klu_analyze(n_, J_.outerIndexPtr(), J_.innerIndexPtr(), &common_);
-    numeric_ = klu_factor(J_.outerIndexPtr(), J_.innerIndexPtr(), J_.valuePtr(), symbolic_, &common_);
-    if (common_.status != KLU_OK) {
-        err_ = 1;
+    ErrorType res = ErrorType::NoError; 
+    symbolic_ = klu_analyze(n, J.outerIndexPtr(), J.innerIndexPtr(), &common_);
+    if(common_.status != KLU_OK){
+        res = ErrorType::SolverAnalyze; 
+    }else{
+        numeric_ = klu_factor(J.outerIndexPtr(), J.innerIndexPtr(), J.valuePtr(), symbolic_, &common_);
+        if(common_.status != KLU_OK) res = ErrorType::SolverFactor; 
     }
-    need_factorize_ = false;
-    timer_initialize_ += timer.duration();
+    return res;
 }
 
-void KLUSolver::solve(RealVect & b, bool has_just_been_initialized){
+ErrorType KLULinearSolver::solve(Eigen::SparseMatrix<real_type>& J, RealVect & b, bool has_just_been_initialized){
     // solves (for x) the linear system J.x = b
     // supposes that the solver has been initialized (call klu_solver.analyze() before calling that)
     // J is const even if it does not compile if said const
-    auto timer = CustTimer();
     int ok;
+    ErrorType err = ErrorType::NoError;
     bool stop = false;
     if(!has_just_been_initialized){
         // if the call to "klu_factor" has been made this iteration, there is no need
         // to re factor again the matrix
         // i'm in the case where it has not
-        ok = klu_refactor(J_.outerIndexPtr(), J_.innerIndexPtr(), J_.valuePtr(), symbolic_, numeric_, &common_);
+        ok = klu_refactor(J.outerIndexPtr(), J.innerIndexPtr(), J.valuePtr(), symbolic_, numeric_, &common_);
         if (ok != 1) {
-            err_ = 2;
+            // std::cout << "\t KLU: refactor error" << std::endl;
+            err = ErrorType::SolverReFactor;
             stop = true;
         }
     }
     if(!stop){
-        ok = klu_solve(symbolic_, numeric_, n_, 1, &b(0), &common_);
+        const auto n = J.cols();
+        ok = klu_solve(symbolic_, numeric_, n, 1, &b(0), &common_);
         if (ok != 1) {
-            err_ = 3;
+            // std::cout << "\t KLU: klu_solve error" << std::endl;
+            err = ErrorType::SolverSolve;
         }
     }
-    // std::cout << "KLUSolver::solve" << std::endl;
-    timer_solve_ += timer.duration();
+    return err;
 }
diff --git a/src/KLUSolver.h b/src/KLUSolver.h
index a839dc8..8dae9fb 100644
--- a/src/KLUSolver.h
+++ b/src/KLUSolver.h
@@ -41,25 +41,21 @@ Reusing the same solver is possible, but "reset" method must be called.
 Otherwise, unexpected behaviour might follow, including "segfault".
 
 **/
-class KLUSolver: public BaseNRSolver
+class KLULinearSolver
 {
     public:
-        KLUSolver():BaseNRSolver(),symbolic_(),numeric_(),common_(){}
+        KLULinearSolver():symbolic_(),numeric_(),common_(){}
 
-        ~KLUSolver()
+        ~KLULinearSolver()
          {
              klu_free_symbolic(&symbolic_, &common_);
              klu_free_numeric(&numeric_, &common_);
          }
 
-        virtual void reset();
-
-    protected:
-        virtual
-        void initialize();
-
-        virtual
-        void solve(RealVect & b, bool has_just_been_inialized);
+        // public api
+        ErrorType reset();
+        ErrorType initialize(Eigen::SparseMatrix<real_type>& J);
+        ErrorType solve(Eigen::SparseMatrix<real_type>& J, RealVect & b, bool has_just_been_inialized);
 
     private:
         // solver initialization
@@ -68,154 +64,9 @@ class KLUSolver: public BaseNRSolver
         klu_common common_;
 
         // no copy allowed
-        KLUSolver( const KLUSolver & ) ;
-        KLUSolver & operator=( const KLUSolver & ) ;
-
-    private:
-        // debug func i don't want to remove yet
-        void analyze_old(int n,
-                      Eigen::Ref<Eigen::VectorXi> Ap,
-                      Eigen::Ref<Eigen::VectorXi> Ai){
-            n_ = n;
-            symbolic_ = klu_analyze(n, &Ap(0), &Ai(0), &common_);
-        }
-        void solve_old(Eigen::Ref<Eigen::VectorXi> Ap,
-                    Eigen::Ref<Eigen::VectorXi> Ai,
-                    Eigen::Ref<RealVect> Ax,
-                    Eigen::Ref<RealVect> b){
-            numeric_ = klu_factor(&Ap(0), &Ai(0), &Ax(0), symbolic_, &common_);
-            klu_solve(symbolic_, numeric_, n_, 1, &b(0), &common_);
-        }
-
-        // TODO re add the references here for the last stuff
-        std::tuple<RealVect, CplxVect> one_iter_test(Eigen::SparseMatrix<real_type> J,
-                              Eigen::Ref<RealVect> F,
-                              Eigen::VectorXi pv,
-                              Eigen::VectorXi pq,
-                              CplxVect V,
-                              Eigen::SparseMatrix<cplx_type>  Ybus,
-                              CplxVect Sbus
-                              ){
-            //TODO do not use, for DEBUG only!!!
-            // get the sizes for convenience
-            auto npv = pv.size();
-            auto npq = pq.size();
-
-            // supposes that "klu_analyze" has been already called ! klu_solver.analyze(J) should have been called
-            numeric_ = klu_factor(J.outerIndexPtr(), J.innerIndexPtr(), J.valuePtr(), symbolic_, &common_);
-            klu_solve(symbolic_, numeric_, n_, 1, &F(0), &common_);
-            auto dx = -1.0*F;
-
-            // update voltage (this should be done consistently with "klu_solver._evaluate_Fx")
-            Vm_ = V.array().abs();  // update Vm and Va again in case
-            Va_ = V.array().arg();  // we wrapped around with a negative Vm
-
-            if (npv > 0) Va_(pv) += dx.segment(0,npv);
-            if (npq > 0){
-                Va_(pq) += dx.segment(npv,npq);
-                Vm_(pq) += dx.segment(npv+npq, npq);
-            }
-
-            // TODO change here for not having to cast all the time ...
-            V = Vm_.array() * (Va_.array().cos().cast<cplx_type>() + my_i * Va_.array().sin().cast<cplx_type>() );
-
-            F = _evaluate_Fx(Ybus, V, Sbus, pv, pq);
-            return std::tuple<RealVect, CplxVect>(F, V);
-        }
-
-        Eigen::SparseMatrix<real_type>
-             create_jacobian_matrix_test(const Eigen::SparseMatrix<cplx_type> & Ybus,
-                                         const CplxVect & V,
-                                         const Eigen::VectorXi & pq,
-                                         const Eigen::VectorXi & pvpq
-                                         ){
-
-            // DO NOT USE, FOR DEBUG ONLY!
-            const auto & n_pvpq = pvpq.size();
-            const auto & n_pq = pvpq.size();
-            std::vector<int> pvpq_inv(V.size(), -1);
-            for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id;
-            std::vector<int> pq_inv(V.size(), -1);
-            for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id;
-            fill_jacobian_matrix(Ybus, V, pq, pvpq, pq_inv, pvpq_inv);
-            return J_;
-        }
-
-        bool initialize_test(Eigen::SparseMatrix<real_type > & J){
-            // default Eigen representation: column major, which is good for klu !
-            // J is const here, even if it's not said in klu_analyze
-            const int n = static_cast<int>(J.cols()); // should be equal to J_.nrows()
-            err_ = 0; // reset error message
-            klu_common common = klu_common();
-            bool res = true;
-            klu_symbolic* symbolic = klu_analyze(n, J.outerIndexPtr(), J.innerIndexPtr(), &common);
-//            auto numeric = klu_factor(J.outerIndexPtr(), J.innerIndexPtr(), J.valuePtr(), symbolic, &common);
-            klu_factor(J.outerIndexPtr(), J.innerIndexPtr(), J.valuePtr(), symbolic, &common);
-            if (common_.status != KLU_OK) {
-                err_ = 1;
-                res = false;
-            }
-            return res;
-        }
-
-        std::tuple<Eigen::SparseMatrix<cplx_type> , Eigen::SparseMatrix<cplx_type> >
-                    _get_ds_test(Eigen::SparseMatrix<cplx_type> & Ybus,
-                                CplxVect & V){
-            BaseNRSolver::_dSbus_dV(Ybus, V);
-            auto res = std::tuple<Eigen::SparseMatrix<cplx_type> , Eigen::SparseMatrix<cplx_type> >(dS_dVm_, dS_dVa_);
-            return res;
-        }
-
-        void _dSbus_dV(Eigen::SparseMatrix<cplx_type> & dS_dVm,
-                           Eigen::SparseMatrix<cplx_type> & dS_dVa,
-                           const Eigen::Ref<const Eigen::SparseMatrix<cplx_type> > & Ybus,
-                           const Eigen::Ref<const CplxVect > & V)
-        {
-            // "slow" implementation close to pypower, but with sparse matrix
-            // TODO check i cannot optimize that with numba code in pandapower instead
-            auto timer = CustTimer();
-            CplxVect Ibus = Ybus * V;
-            Eigen::SparseMatrix<cplx_type> diagV = _make_diagonal_matrix(V);
-
-            CplxVect Ibus_conj = Ibus.conjugate();
-            Eigen::SparseMatrix<cplx_type> diagIbus_conj = _make_diagonal_matrix(Ibus_conj);
-
-            CplxVect Vnorm = V.array() / V.array().abs();
-            Eigen::SparseMatrix<cplx_type> diagVnorm = _make_diagonal_matrix(Vnorm);
-
-            Eigen::SparseMatrix<cplx_type> tmp = Ybus * diagVnorm;
-            tmp = tmp.conjugate();
-            dS_dVm = diagV * tmp + diagIbus_conj * diagVnorm;
-
-            // TODO this is the same code for this "tmp" and the previous one, except for "diagV" and "diagVnorm"
-            tmp = Ybus * diagV;
-            tmp = tmp.conjugate();
-            auto tmp2 = diagIbus_conj - tmp;  // conj(diagIbus - Ybus * diagV)
-            dS_dVa = my_i * diagV * tmp2;
-
-            // python implementation
-            // dS_dVm = diagV * conj(Ybus * diagVnorm) + conj(diagIbus) * diagVnorm
-            // dS_dVa = 1j * diagV * conj(diagIbus - Ybus * diagV)
-            timer_dSbus_ += timer.duration();
-        }
-
-        Eigen::SparseMatrix<cplx_type>
-            _make_diagonal_matrix(const Eigen::Ref<const CplxVect > & diag_val){
-            // TODO their might be a more efficient way to do that
-            auto n = diag_val.size();
-            Eigen::SparseMatrix<cplx_type> res(n,n);
-            // first method, without a loop of mine
-            res.setIdentity();  // segfault if attempt to use this function without this
-            res.diagonal() = diag_val;
-
-            // second method, with an "optimized" loop
-            // res.reserve(Eigen::VectorXi::Constant(n,1)); // i reserve one number per columns (speed optim)
-            // for(unsigned int i = 0; i < n; ++i){
-            //    res.insert(i,i) = diag_val(i);
-            //}
-            return res;
-        }
+        KLULinearSolver( const KLULinearSolver & ) = delete ;
+        KLULinearSolver & operator=( const KLULinearSolver & ) = delete ;
 };
 
 #endif // KLSOLVER_H
-#endif  // KLU_SOLVER_AVAILABLE
+#endif // KLU_SOLVER_AVAILABLE
diff --git a/src/NICSLUSolver.cpp b/src/NICSLUSolver.cpp
index 9ba8289..b0ad85e 100644
--- a/src/NICSLUSolver.cpp
+++ b/src/NICSLUSolver.cpp
@@ -8,11 +8,8 @@
 
 #include "NICSLUSolver.h"
 
-void NICSLUSolver::reset(){
-    BaseNRSolver::reset();
-
+ErrorType NICSLULinearSolver::reset(){
     // free everything
-    n_ = -1;
     solver_.Free();
     if(ai_ != nullptr) delete [] ai_;  // created in NICSLUSolver::initialize
     if(ap_ != nullptr) delete [] ap_;  // created in NICSLUSolver::initialize
@@ -28,81 +25,62 @@ void NICSLUSolver::reset(){
             msg += "Please copy this file at the location you want to use the NICSLU solver.";
             std::cout << msg << std::endl;
         }
-        std::cout << "NICSLUSolver::reset() solver_.Initialize error: "  << ret << std::endl;
-        err_ = 1;  // TODO clarify that
-        return;
+        // std::cout << "NICSLULinearSolver::reset() solver_.Initialize error: "  << ret << std::endl;
+        return ErrorType::LicenseError;
     }
     // solver_.SetConfiguration(0, 1.); //enable timer
+    return ErrorType::NoError;
 }
 
-void NICSLUSolver::initialize(){
+ErrorType NICSLULinearSolver::initialize(Eigen::SparseMatrix<real_type> & J){
     // default Eigen representation: column major, which is good for klu !
-    auto timer = CustTimer();
+    const auto n = J.cols(); // should be equal to J_.nrows()
     int ret;
-    n_ = J_.cols(); // should be equal to J_.nrows()
-    err_ = 0; // reset error message
-    //    symbolic_ = klu_analyze(n_, J_.outerIndexPtr(), J_.innerIndexPtr(), &common_);
-    //    numeric_ = klu_factor(J_.outerIndexPtr(), J_.innerIndexPtr(), J_.valuePtr(), symbolic_, &common_);
-
-    // solver.Analyze(n, ax, ai, ap, MATRIX_ROW_REAL);
-    // convert int * (storage class for Eign) to unsigned int * (expected storage class for NICSLU...)
-
-    // std::cout << "n_ = " << n_ << std::endl;  // DEBUG
-    const unsigned int nnz = J_.nonZeros();  // DEBUG
-    // std::cout << "nnz = " << nnz << std::endl;  // DEBUG
+    ErrorType err = ErrorType::NoError; // reset error message
+    const unsigned int nnz = J.nonZeros();
 
     ai_ = new unsigned int [nnz];  // deleted in destructor and NICSLUSolver::reset
-    const int * ref_ai = J_.innerIndexPtr();
+    const int * ref_ai = J.innerIndexPtr();
     for(unsigned int i = 0; i < nnz; ++i){
         ai_[i] = static_cast<unsigned int>(ref_ai[i]);
-        // std::cout << "ai_["<<i<<"] = " << ai_[i] << std::endl;  // DEBUG
     }
-    // std::cout << std::endl;  // DEBUG
 
-    ap_ = new unsigned int [n_+1];  // deleted in destructor and NICSLUSolver::reset
-    const int * ref_ap = J_.outerIndexPtr();
-    for(int i = 0; i < n_+1; ++i){
+    ap_ = new unsigned int [n+1];  // deleted in destructor and NICSLUSolver::reset
+    const int * ref_ap = J.outerIndexPtr();
+    for(int i = 0; i < n+1; ++i){
         ap_[i] = static_cast<unsigned int>(ref_ap[i]);
-        // std::cout << "ap_["<<i<<"] = " << ap_[i] << std::endl;  // DEBUG
     }
-    // std::cout << std::endl;  // DEBUG
-
-    // const double * ref_ax = J_.valuePtr();  // DEBUG
-    // for(unsigned int i = 0; i < nnz; ++i){  // DEBUG
-    //     std::cout << "ax["<<i<<"] = " << ref_ax[i] << std::endl;  // DEBUG
-    // }  // DEBUG
-
-    ret = solver_.Analyze(n_,
-                          J_.valuePtr(),
+    ret = solver_.Analyze(n,
+                          J.valuePtr(),
                           ai_,
                           ap_,
                           MATRIX_COLUMN_REAL);  // MATRIX_COLUMN_REAL, MATRIX_ROW_REAL
     if (ret < 0){
-        err_ = 1;
-        std::cout << "NICSLUSolver::initialize() solver_.Analyze error: "  << ret << std::endl;
+        err = ErrorType::SolverAnalyze;
+        // std::cout << "NICSLULinearSolver::initialize() solver_.Analyze error: "  << ret << std::endl;
         // https://github.com/chenxm1986/nicslu/blob/master/nicslu202103/include/nicslu.h for error info
-        return;
+        return err;
     }
     // solver.FactorizeMatrix(ax, 0); //use all created threads
-    ret = solver_.FactorizeMatrix(J_.valuePtr(), nb_thread_);
+    ret = solver_.FactorizeMatrix(J.valuePtr(), nb_thread_);
     if (ret < 0){
-        err_ = 1;
-        std::cout << "NICSLUSolver::initialize() solver_.FactorizeMatrix error: "  << ret << std::endl;
+        err = ErrorType::SolverFactor;
+        // std::cout << "NICSLULinearSolver::initialize() solver_.FactorizeMatrix error: "  << ret << std::endl;
         // https://github.com/chenxm1986/nicslu/blob/master/nicslu202103/include/nicslu.h for error info
-        return;
+        return err;
     }
-    need_factorize_ = false;
-    timer_initialize_ += timer.duration();
+    return err;
 }
 
-void NICSLUSolver::solve(RealVect & b, bool has_just_been_inialized){
+ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix<real_type> & J, RealVect & b, bool has_just_been_inialized){
     // solves (for x) the linear system J.x = b
     // supposes that the solver has been initialized (call klu_solver.analyze() before calling that)
     // J is const even if it does not compile if said const
-    auto timer = CustTimer();
     int ret;
     bool stop = false;
     RealVect x;
+    ErrorType err = ErrorType::NoError;
+    const auto n = J.cols(); // should be equal to J_.nrows()
     if(!has_just_been_inialized){
         // if the call to "klu_factor" has been made this iteration, there is no need
         // to re factor again the matrix
@@ -110,24 +88,24 @@ void NICSLUSolver::solve(RealVect & b, bool has_just_been_inialized){
         //        ret = klu_refactor(J_.outerIndexPtr(), J_.innerIndexPtr(), J_.valuePtr(), symbolic_, numeric_, &common_);
 
         // solver.FactorizeMatrix(ax, 0); //use all created threads
-        ret  = solver_.FactorizeMatrix(J_.valuePtr(), nb_thread_);
+        ret  = solver_.FactorizeMatrix(J.valuePtr(), nb_thread_);  // TODO maybe 0 instead of nb_thread_ here, see https://github.com/chenxm1986/nicslu/blob/master/nicslu202110/demo/demo2.cpp
         if (ret < 0) {
-            std::cout << "NICSLUSolver::solve solver_.FactorizeMatrix error: " << ret << std::endl;
+            // std::cout << "NICSLULinearSolver::solve solver_.FactorizeMatrix error: " << ret << std::endl;
             // https://github.com/chenxm1986/nicslu/blob/master/nicslu202103/include/nicslu.h for error info
-            err_ = 2;
+            err = ErrorType::SolverReFactor;
             stop = true;
         }
     }
     if(!stop){
         // ok = klu_solve(symbolic_, numeric_, n_, 1, &b(0), &common_);
         // solver.Solve(b, x);
-        x = RealVect(n_);
+        x = RealVect(n);
         ret = solver_.Solve(&b(0), &x(0));
         if (ret < 0) {
-            std::cout << "NICSLUSolver::solve solver_.Solve error: " << ret << std::endl;
-            err_ = 3;
+            // std::cout << "NICSLULinearSolver::solve solver_.Solve error: " << ret << std::endl;
+            err = ErrorType::SolverSolve;
         }
         b = x;
     }
-    timer_solve_ += timer.duration();
+    return err;
 }
diff --git a/src/NICSLUSolver.h b/src/NICSLUSolver.h
index 08665ba..9d9149f 100644
--- a/src/NICSLUSolver.h
+++ b/src/NICSLUSolver.h
@@ -44,35 +44,32 @@ specific license.
 **/
 
 // TODO use the cpp API instead !
-class NICSLUSolver: public BaseNRSolver
+class NICSLULinearSolver
 {
     public:
-        NICSLUSolver():
-            BaseNRSolver(),
+        NICSLULinearSolver():
             solver_(),
             nb_thread_(1),
             ai_(nullptr), 
             ap_(nullptr){}
 
-        ~NICSLUSolver()
+        ~NICSLULinearSolver()
          {
             solver_.Free();
             if(ai_!= nullptr) delete [] ai_;
             if(ap_!= nullptr) delete [] ap_;
          }
 
-        // prevent copy and assignment
-        NICSLUSolver(const NICSLUSolver & other) = delete;
-        NICSLUSolver & operator=( const NICSLUSolver & ) = delete;
+        // public api
+        ErrorType reset();
+        ErrorType initialize(Eigen::SparseMatrix<real_type> & J);
+        ErrorType solve(Eigen::SparseMatrix<real_type> & J, RealVect & b, bool has_just_been_inialized);
 
-        virtual void reset();
+        // prevent copy and assignment
+        NICSLULinearSolver(const NICSLULinearSolver & other) = delete;
+        NICSLULinearSolver & operator=( const NICSLULinearSolver & ) = delete;
 
-    protected:
-        virtual
-        void initialize();
 
-        virtual
-        void solve(RealVect & b, bool has_just_been_inialized);
 
     private:
         // solver initialization
diff --git a/src/SecurityAnalysis.cpp b/src/SecurityAnalysis.cpp
index 999d87e..e92ce3a 100644
--- a/src/SecurityAnalysis.cpp
+++ b/src/SecurityAnalysis.cpp
@@ -12,8 +12,8 @@
 
 bool SecurityAnalysis::check_invertible(const Eigen::SparseMatrix<cplx_type> & Ybus) const{
     std::vector<bool> visited(Ybus.cols(), false); 
-    std::queue<int> neighborhood;
-    int col_id = 0;  // start by node 0, why not
+    std::queue<Eigen::Index> neighborhood;
+    Eigen::Index col_id = 0;  // start by node 0, why not
     while (true)
     {
         visited[col_id] = true;
@@ -133,6 +133,8 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t
     const auto & id_ac_solver_to_me = _grid_model.id_ac_solver_to_me();
     const Eigen::VectorXi & bus_pv = _grid_model.get_pv();
     const Eigen::VectorXi & bus_pq = _grid_model.get_pq();
+    const Eigen::VectorXi & slack_ids = _grid_model.get_slack_ids();
+    const RealVect & slack_weights = _grid_model.get_slack_weights();
     const Eigen::Index nb_buses_solver = Ybus.cols();  // which is equal to Ybus.rows();
     const auto & id_me_to_ac_solver = _grid_model.id_me_to_ac_solver();
 
@@ -153,6 +155,7 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t
     Eigen::Index cont_id = 0;
     bool conv;
     CplxVect V;
+    // int contingency = 0;
     for(const auto & coeffs_modif: _li_coeffs){
         auto timer_modif_Ybus = CustTimer();
         bool invertible = remove_from_Ybus(Ybus, coeffs_modif);
@@ -164,14 +167,20 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t
         // 5.2ms without it vs 81.9ms with it (for the iee 118)
         // So better make the computation, even if it's not used...
 
-        // if(invertible)
-        // {
-        V = Vinit_solver; // Vinit is reused for each contingencies
-        conv = compute_one_powerflow(Ybus, V, Sbus,
-                                     bus_pv, bus_pq,
-                                     max_iter,
-                                     tol / sn_mva);
-        // }
+        if(invertible)
+        {
+            V = Vinit_solver; // Vinit is reused for each contingencies
+            conv = compute_one_powerflow(Ybus, V, Sbus,
+                                        slack_ids, slack_weights,
+                                        bus_pv, bus_pq,
+                                        max_iter,
+                                        tol / sn_mva);
+        }
+        // std::string conv_str =  conv ? "has converged" : "has diverged";
+        // std::cout << "contingency " << contingency << ": " << conv_str << std::endl;
+        // if(!conv) std::cout << "\t error was: " << _solver.get_error() << std::endl;
+        // ++contingency;
+
         timer_modif_Ybus = CustTimer();
         readd_to_Ybus(Ybus, coeffs_modif);
         _timer_modif_Ybus += timer_modif_Ybus.duration();
@@ -181,16 +190,17 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t
     _timer_total = timer.duration();
 }
 
-void SecurityAnalysis::clean_flows()
+void SecurityAnalysis::clean_flows(bool is_amps)
 {
     auto timer = CustTimer();
     Eigen::Index cont_id = 0;
     for(const auto & l_id_this_cont: _li_defaults){
         for(auto l_id : l_id_this_cont){
-            real_type & el = _amps_flows(cont_id, l_id);
+            real_type & el = is_amps ? _amps_flows(cont_id, l_id): _active_power_flows(cont_id, l_id);
             if(isfinite(el)) el = 0.;
         }
         ++cont_id;
     }
-    _timer_compute_A += timer.duration();
+    if (is_amps) _timer_compute_A += timer.duration();
+    else _timer_compute_P += timer.duration();
 }
diff --git a/src/SecurityAnalysis.h b/src/SecurityAnalysis.h
index 77369ec..714ebd8 100644
--- a/src/SecurityAnalysis.h
+++ b/src/SecurityAnalysis.h
@@ -93,12 +93,19 @@ class SecurityAnalysis: public BaseMultiplePowerflow
 
         // make the computation
         void compute(const CplxVect & Vinit, int max_iter, real_type tol);
+        
         Eigen::Ref<const RealMat > compute_flows() {
             compute_flows_from_Vs();
             clean_flows();
             return _amps_flows;
         }
 
+        Eigen::Ref<const RealMat > compute_power_flows() {
+            compute_flows_from_Vs(false);
+            clean_flows(false);
+            return _active_power_flows;
+        }
+
         // python cannot handle set of sets... (c++ can because set are ordered set, but in python
         // they are unordered one, with hash functions)
         const std::set<std::set<int> > & my_defaults() const {return _li_defaults;}
@@ -141,7 +148,7 @@ class SecurityAnalysis: public BaseMultiplePowerflow
 
         // by default the flows are not 0 when the powerline is connected in the original topology
         // this function sorts this out
-        void clean_flows();
+        void clean_flows(bool is_amps=true);
 
         // sometimes, when i perform some disconnection, I make the graph non connexe
         // in this case, well, i don't use the results of the simulation
diff --git a/src/Solvers.h b/src/Solvers.h
new file mode 100644
index 0000000..d0b60a2
--- /dev/null
+++ b/src/Solvers.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2020, RTE (https://www.rte-france.com)
+// See AUTHORS.txt
+// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+// you can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
+// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+#include "BaseNRSolver.h"
+#include "BaseNRSolverSingleSlack.h"
+#include "DCSolver.h"
+
+#include "SparseLUSolver.h"
+#include "KLUSolver.h"
+#include "NICSLUSolver.h"
+
+/** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen**/
+typedef BaseNRSolver<SparseLULinearSolver> SparseLUSolver;
+/** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen, do not consider multiple slack bus**/
+typedef BaseNRSolverSingleSlack<SparseLULinearSolver> SparseLUSolverSingleSlack;
+/** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen, only suitable for the DC approximation**/
+typedef BaseDCSolver<SparseLULinearSolver> DCSolver;
+
+#ifdef KLU_SOLVER_AVAILABLE
+    /** Solver based on Newton Raphson, using the KLU linear solver**/
+    typedef BaseNRSolver<KLULinearSolver> KLUSolver;
+    /** Solver based on Newton Raphson, using the KLU linear solver, do not consider multiple slack bus**/
+    typedef BaseNRSolverSingleSlack<KLULinearSolver> KLUSolverSingleSlack;
+    /** Solver based on Newton Raphson, using the KLU linear solver, only suitable for the DC approximation**/
+    typedef BaseDCSolver<KLULinearSolver> KLUDCSolver;
+#elif defined(_READ_THE_DOCS)
+    // hack to display accurately the doc in read the doc even if the models are not compiled
+    /** Solver based on Newton Raphson, using the KLU linear solver**/
+    class KLUSolver : public SparseLUSolver {};
+    /** Solver based on Newton Raphson, using the KLU linear solver, do not consider multiple slack bus**/
+    class KLUSolverSingleSlack : public SparseLUSolverSingleSlack {};
+    /** Solver based on Newton Raphson, using the KLU linear solver, only suitable for the DC approximation**/
+    class KLUDCSolver : public DCSolver {};
+#endif  // KLU_SOLVER_AVAILABLE
+
+#ifdef NICSLU_SOLVER_AVAILABLE
+    /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license)**/
+    typedef BaseNRSolver<NICSLULinearSolver> NICSLUSolver;
+    /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license), do not consider multiple slack bus**/
+    typedef BaseNRSolverSingleSlack<NICSLULinearSolver> NICSLUSolverSingleSlack;
+    /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license), only suitable for the DC approximation**/
+    typedef BaseDCSolver<NICSLULinearSolver> NICSLUDCSolver;
+#elif defined(_READ_THE_DOCS)
+    // hack to display accurately the doc in read the doc even if the models are not compiled
+    /** Solver based on Newton Raphson, using the KLU linear solver**/
+    class NICSLUSolver : public SparseLUSolver{};
+    /** Solver based on Newton Raphson, using the KLU linear solver, do not consider multiple slack bus**/
+    class NICSLUSolverSingleSlack : public SparseLUSolverSingleSlack{};
+    /** Solver based on Newton Raphson, using the KLU linear solver, only suitable for the DC approximation**/
+    class NICSLUDCSolver : public DCSolver{};
+#endif  // NICSLU_SOLVER_AVAILABLE
diff --git a/src/SparseLUSolver.cpp b/src/SparseLUSolver.cpp
index ad7ca17..30bc54e 100644
--- a/src/SparseLUSolver.cpp
+++ b/src/SparseLUSolver.cpp
@@ -8,46 +8,39 @@
 
 #include "SparseLUSolver.h"
 
-void SparseLUSolver::initialize(){
+ErrorType SparseLULinearSolver::initialize(const Eigen::SparseMatrix<real_type> & J){
     // default Eigen representation: column major, which is good for klu !
     // J is const here
-    auto timer = CustTimer();
-    n_ = static_cast<int>(J_.cols()); // should be equal to J_.nrows()
-    err_ = 0; // reset error message
-    J_.makeCompressed();
-    solver_.analyzePattern(J_);  //NEW
-    solver_.factorize(J_);  // NEW
-    if (solver_.info() != Eigen::Success) {
-        err_ = 1;
-    }
-    need_factorize_ = false;
-    timer_initialize_ += timer.duration();
+    ErrorType res = ErrorType::NoError; 
+    solver_.analyzePattern(J);
+    // do not check here for "solver_.info" it is not set to "Success"
+    solver_.factorize(J);
+    if(solver_.info() != Eigen::Success) res = ErrorType::SolverFactor; 
+    return res;
 }
 
-void SparseLUSolver::solve(RealVect & b, bool has_just_been_inialized){
-    // NEW
-
+ErrorType SparseLULinearSolver::solve(const Eigen::SparseMatrix<real_type> & J, RealVect & b, bool has_just_been_inialized){
     // solves (for x) the linear system J.x = b
     // supposes that the solver has been initialized (call sparselu_solver.analyze() before calling that)
     // J is const even if it does not compile if said const
-    auto timer = CustTimer();
+    ErrorType err = ErrorType::NoError;
     bool stop = false;
     if(!has_just_been_inialized){
         // if the call to "klu_factor" has been made this iteration, there is no need
         // to re factor again the matrix
         // i'm in the case where it has not
-        solver_.factorize(J_);  // NEW
+        solver_.factorize(J);
         if (solver_.info() != Eigen::Success) {
-            err_ = 2;
+            err = ErrorType::SolverFactor;
             stop = true;
         }
     }
     if(!stop){
-        RealVect Va = solver_.solve(b);  //NEW
+        RealVect Va = solver_.solve(b);
         if (solver_.info() != Eigen::Success) {
-            err_ = 3;
+            err = ErrorType::SolverSolve;
         }
-        b = Va;  // NEW
+        b = Va;
     }
-    timer_solve_ += timer.duration();
+    return err;
 }
diff --git a/src/SparseLUSolver.h b/src/SparseLUSolver.h
index 67f3b9b..dee3b8b 100644
--- a/src/SparseLUSolver.h
+++ b/src/SparseLUSolver.h
@@ -36,28 +36,23 @@ Reusing the same solver is possible, but "reset" method must be called.
 Otherwise, unexpected behaviour might follow, including "segfault".
 
 **/
-class SparseLUSolver : public BaseNRSolver
+class SparseLULinearSolver
 {
     public:
-        SparseLUSolver():BaseNRSolver(),solver_(){}
-
-        ~SparseLUSolver(){}
-
-    protected:
-        virtual
-        void initialize();
-
-        virtual
-        void solve(RealVect & b, bool has_just_been_inialized);
-
+        SparseLULinearSolver():solver_(){}
+        
+        // public api
+        ErrorType initialize(const Eigen::SparseMatrix<real_type> & J);
+        ErrorType solve(const Eigen::SparseMatrix<real_type> & J, RealVect & b, bool has_just_been_inialized);
+        ErrorType reset(){ return ErrorType::NoError; }
 
     private:
         // solver initialization
         Eigen::SparseLU<Eigen::SparseMatrix<real_type>, Eigen::COLAMDOrdering<int> >  solver_;
 
         // no copy allowed
-        SparseLUSolver( const SparseLUSolver & ) ;
-        SparseLUSolver & operator=( const SparseLUSolver & ) ;
+        SparseLULinearSolver( const SparseLULinearSolver & ) =delete ;
+        SparseLULinearSolver & operator=( const SparseLULinearSolver & ) =delete ;
 };
 
 #endif // SPARSELUSOLVER_H
diff --git a/src/help_fun_msg.cpp b/src/help_fun_msg.cpp
new file mode 100644
index 0000000..edbfbea
--- /dev/null
+++ b/src/help_fun_msg.cpp
@@ -0,0 +1,2265 @@
+// Copyright (c) 2020, RTE (https://www.rte-france.com)
+// See AUTHORS.txt
+// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+// you can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
+// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+// containst he help message of some common functions (not to write them dozens of time)
+
+#include "help_fun_msg.h"
+
+const std::string DocSolver::get_J_python = R"mydelimiter(
+    Returns the Jacobian matrix used for solving the powerflow as a scipy sparse CSC matrix matrix of real number.
+)mydelimiter";
+
+const std::string DocSolver::get_Va = R"mydelimiter(
+    Returns the voltage angles for each buses as a numpy vector of real number.
+)mydelimiter";
+
+const std::string DocSolver::get_Vm = R"mydelimiter(
+    Returns the voltage magnitude for each buses as a numpy vector of real number.
+)mydelimiter";
+
+const std::string DocSolver::get_V = R"mydelimiter(
+    Returns the complex voltage for each buses as a numpy vector of complex number.
+)mydelimiter";
+
+const std::string DocSolver::get_error = R"mydelimiter(
+    Returns the error code (as an integer) encountered by the solver (0 = no error). TODO DOC explain better
+)mydelimiter";
+
+const std::string DocSolver::get_nb_iter = R"mydelimiter(
+    Returns the number of iterations effectively performed by the solver (> 0 integer).
+)mydelimiter";
+const std::string DocSolver::reset = R"mydelimiter(
+    Reset the solver. In this context this will clear all data used by the solver. It is mandatory to do it each time the `Ybus` matrix 
+    (or any of the `pv`, or `pq` or `ref` indices vector are changed).
+)mydelimiter";
+const std::string DocSolver::converged = R"mydelimiter(
+    Returns whether or not the solver has converged or not.
+)mydelimiter";
+
+const std::string DocSolver::compute_pf = R"mydelimiter(
+    Function used to perform a powerflow.
+
+    see section :ref:`available-powerflow-solvers` for more information about these. 
+
+    .. warning::
+        There are strong assumption made on the validity of the parameters provided. Please have a look at the section :ref:`available-powerflow-solvers`
+        for more details about this.
+
+        If a non compliant state is provided, it might result in crash of the python virtual machine (session terminates with error like 
+        `segfault`) and there is absolutely no way to do anything and any data not saved on the hard drive will be lost.
+
+    Parameters
+    ------------
+    Ybus: ``scipy.sparse`` matrix, CSC format
+        The admittance matrix of the system
+    V: ``numpy.ndarray``, vector of complex numbers
+        The initial guess (and final result) for the complex angle at each bus (it is modified during the computation :)
+    Sbus: ``numpy.ndarray``, vector of complex numbers
+        Complex power injected at each bus
+    slack_ids: ``numpy.ndarray``, vector of integers
+        Gives all the ids of the buses participating to the distributed slack bus. [might be ignore by some solvers]
+    slack_weights: ``numpy.ndarray``, vector of real numbers
+        For each bus taking part in the distributed slack, it gives its coefficient
+    pv: ``numpy.ndarray``, vector of integers
+        Index of the pv buses
+    pq: ``numpy.ndarray``, vector of integers
+        Index of the pq buses
+    max_iter: ``int``
+        Maximum number of iterations performed by the solver. [might be ignore by some solvers]
+    tol: ``float``
+        Solver tolerance (*eg* 1e-8) [might be ignore by some solvers]
+
+    Examples
+    ---------
+    Some detailed examples are provided in section :ref:`available-powerflow-solvers` of the documentation.
+
+)mydelimiter";
+
+const std::string DocSolver::get_timers = R"mydelimiter(
+    Returns information about the time taken by some part of the solvers (in seconds)
+
+    Times are measured in seconds using the c++ `steady_clock <https://www.cplusplus.com/reference/chrono/steady_clock/>`_ clock.
+
+    Returns
+    ---------
+    timer_Fx_: ``float``
+        Time spent to compute the mismatch at the KCL for each bus (both for active and reactive power)
+
+    timer_solve_: ``float``
+        Total time spent in the underlying linear solver
+
+    timer_check_: ``float``
+        Time spent in checking whether or not the mismatch of the KCL met the specified tolerance
+
+    timer_total_: ``float``
+        Total time spent in the solver
+
+)mydelimiter";
+    
+const std::string DocSolver::SparseLUSolver = R"mydelimiter(
+    This classes implements the Newton Raphson algorithm, allowing for distributed slack and using the default Eigen sparse solver available in Eigen
+    for the linear algebra. 
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `SparseLU` member (*eg* `env_lightsim.backend.set_solver_typelightsim2grid.solver.SolverType.SparseLU)`).
+
+)mydelimiter";
+
+const std::string DocSolver::SparseLUSolverSingleSlack = R"mydelimiter(
+    This classes implements the Newton Raphson algorithm, using the default Eigen sparse solver available in Eigen
+    for the linear algebra. It does not support the distributed slack, but can be slightly faster than the :class:`lightsim2grid.solver.SparseLUSolver` .
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `SparseLUSolverSingleSlack` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.SparseLUSolverSingleSlack)`).
+
+)mydelimiter";
+
+const std::string DocSolver::DCSolver =  R"mydelimiter(
+    Default implementation of the DC solver, it uses the default Eigen sparse lu decomposition to solve for the DC voltage given the DC admitance matrix and
+    the power injected at each nodes.
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `DC` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.DC)`).
+
+)mydelimiter";
+
+const std::string DocSolver::KLUSolver = R"mydelimiter(
+    This classes implements the Newton Raphson algorithm, allowing for distributed slack and using the faster KLU solver available in the SuiteSparse library
+    for the linear algebra (can be unavailable if you build lightsim2grid from source). It is usually faster than the :class:`lightsim2grid.solver.SparseLUSolver`.
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `KLU` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.KLU)`).
+
+)mydelimiter";
+
+const std::string DocSolver::KLUSolverSingleSlack = R"mydelimiter(
+    This classes implements the Newton Raphson algorithm,the faster KLU solver available in the SuiteSparse library
+    for the linear algebra. It does not support the distributed slack, but can be slightly faster than the :class:`lightsim2grid.solver.KLUSolver`.
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `KLUSingleSlack` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.KLUSingleSlack)`).
+
+)mydelimiter";
+
+const std::string DocSolver::KLUDCSolver = R"mydelimiter(
+    Alternative implementation of the DC solver, it uses the faster KLU solver available in the SuiteSparse library to solve for the DC voltage given the DC admitance matrix and
+    the power injected at each nodes (can be unavailable if you build lightsim2grid from source).
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `KLUDC` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.KLUDC)`).
+
+)mydelimiter";
+
+const std::string DocSolver::NICSLUSolver = R"mydelimiter(
+    This classes implements the Newton Raphson algorithm, allowing for distributed slack and using the faster NICSLU solver available in the NICSLU library
+    for the linear algebra. It is usually faster than the :class:`lightsim2grid.solver.SparseLUSolver`. (requires a build from source)
+    
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `NICSLU` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.NICSLU)`).
+
+)mydelimiter";
+
+const std::string DocSolver::NICSLUSolverSingleSlack = R"mydelimiter(
+    This classes implements the Newton Raphson algorithm, the faster NICSLU solver available in the NICSLU library
+    for the linear algebra. It does not support the distributed slack, but can be slightly faster than the :class:`lightsim2grid.solver.NICSLUSolver` .
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `NICSLUSingleSlack` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.NICSLUSingleSlack)`).
+
+)mydelimiter";
+
+const std::string DocSolver::NICSLUDCSolver = R"mydelimiter(
+    Alternative implementation of the DC solver, it uses the faster NICSLU solver available in the NICSLU library to solve for the DC voltage given the DC admitance matrix and
+    the power injected at each nodes (requires a build from source).
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `NICSLUSingleSlack` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.NICSLUSingleSlack)`).
+
+)mydelimiter";
+
+const std::string DocSolver::GaussSeidelSolver = R"mydelimiter(
+    Default implementation of the "Gauss Seidel" powerflow solver. We do not recommend to use it as the Newton Raphson based solvers
+    are usually much faster.
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `GaussSeidel` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.GaussSeidel)`).
+
+)mydelimiter";
+
+const std::string DocSolver::GaussSeidelSynchSolver = R"mydelimiter(
+    Variant implementation of the "Gauss Seidel" powerflow solver, where every buses are updated at once (can be significantly faster than the 
+    :class:`lightsim2grid.solver.GaussSeidelSolver` for larger grid). We still do not recommend to use it as the Newton Raphson based solvers
+    are usually much faster.
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is referred to by the `GaussSeidelSynch` member (*eg* `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SolverType.GaussSeidelSynch)`).
+
+)mydelimiter";
+
+const std::string DocSolver::AnySolver = R"mydelimiter(
+    This is a "wrapper" class that allows the user to perform some powerflow using the same API using different solvers. It is not recommended
+    to use this wrapper directly. It is rather a class exported to be compatible with the `env_lightsim2grid.backend._grid.get_solver()` method.
+
+    Examples
+    ---------
+    This class is built to be used like this:
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+
+        anysolver = env.backend._grid.get_solver()
+
+        anysolver.get_type()  # type of solver currently used
+        anysolver.get_J()  # current Jacobian matrix, if available by the method
+
+)mydelimiter";
+
+const std::string DocSolver::get_type = R"mydelimiter(
+    Retrieve the current solver used. This will return an instance of :class:`lightsim2grid.solver.SolverType` indicating which
+    is the underlying solver in use.
+
+    This should be equivalent to :func:`lightsim2grid.gridmodel.GridModel.get_solver_type()`
+
+)mydelimiter";
+const std::string DocSolver::chooseSolver_get_J_python = R"mydelimiter(
+    Returns the Jacobian matrix used for solving the powerflow as a scipy sparse CSC matrix matrix of real number.
+
+    .. note::
+        Depending on the underlying solver used (*eg* :class:`lightsim2grid.solver.DCSolver` or :class:`lightsim2grid.solver.GaussSeidelSolver`)
+        the jacobian matrix might be irrelevant and an attempt to use this function will throw a RuntimeError. 
+
+)mydelimiter";
+const std::string DocSolver::get_computation_time = R"mydelimiter(
+    Return the total computation time (in second) spend in the solver when performing a powerflow.
+
+    This is equivalent to the `timer_total_` returned value of the`***.get_timers()` function.
+)mydelimiter";
+
+const std::string DocIterator::id = R"mydelimiter(
+    Get the ideas of the element. Ids are integer from 0 to n-1 (if `n` denotes the number of such elements on the grid.)
+
+    Examples
+    --------
+    We give the example only for generators, but it works similarly for every other types of objects
+    in a :class:`lightsim2grid.gridmodel.GridModel`.
+    
+    This gives something like:
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+
+        grid_model = env.backend._grid
+
+        first_gen = grid_model.get_generators()[0]  # or get_loads for loads, etc.
+        first_gen.id  # should be 0
+
+)mydelimiter";
+
+const std::string DocIterator::connected = R"mydelimiter(
+    Get the status (True = connected, False = disconnected) of each element of a :class:`lightsim2grid.gridmodel.GridModel`
+
+)mydelimiter";
+
+const std::string DocIterator::bus_id = R"mydelimiter(
+    Get the bus id (as an integer) at which each element of a :class:`lightsim2grid.gridmodel.GridModel` is connected. If `-1` is returned it means
+    that the object is disconnected.
+
+)mydelimiter";
+
+const std::string DocIterator::target_p_mw = R"mydelimiter(
+    Get the active production (or consumption) setpoint in MW for element of the grid supporting this feature.
+
+    For generators (and static generators) it is given following the "generator convention" (positive = power is injected to the grid)
+    
+    For loads (and storage units) it is given following the "load convention" (positive = power is absorbed from the grid)
+
+)mydelimiter";
+
+const std::string DocIterator::target_q_mvar = R"mydelimiter(
+    Get the reactive production (or consumption) setpoint in MVAr for element of the grid supporting this feature.
+
+    For generators (and static generators) it is given following the "generator convention" (positive = power is injected to the grid)
+    
+    For loads (and storage units) it is given following the "load convention" (positive = power is absorbed from the grid)
+
+)mydelimiter";
+
+const std::string DocIterator::line_model = R"mydelimiter(
+    The "line model" (also valid for transformers) is: 
+    
+    .. code-block :: none
+
+                    ior                      ________            iex
+        `or bus` o------>   -----------------|r + j.x|---------<-------o `ex bus`
+                 |       ) (            |                  |           |
+                 |       ) (         |     |            |     |        |
+                 | vor   ) ( n:1     |1/2*h|            |1/2*h|        | vex
+                 |       ) (         |     |            |     |        |
+                 \/      ) (            |                  |           \/
+        ground---o-------   -------------------------------------------o---- ground
+
+    (fyi: `ior`, `iex`, `n` and `y` are all complex numbers. `r` and `x` are real numbers. `j` is a complex number such that `j^2 = -1`)
+
+)mydelimiter";
+
+const std::string DocIterator::r_pu = R"mydelimiter(
+    Retrieve the resistance (given in pair unit system, and not in Ohm) of the powerlines or the transformers. This is a real number
+    and is represented by the number `r` in the line model.
+
+)mydelimiter" + DocIterator::line_model;
+
+const std::string DocIterator::x_pu = R"mydelimiter(
+    Retrieve the reactance (given in pair unit system, and not in Ohm) of the powerlines or the transformers. This is a real number
+    and is represented by the number `x` in the line model.
+
+)mydelimiter" + DocIterator::line_model;
+
+const std::string DocIterator::h_pu = R"mydelimiter(
+    Retrieve the capacitance (real part) and dielectric conductance (imaginary part)
+    (given in pair unit system) of the powerlines or the transformers. 
+    
+    This is a complex number and is represented by the number `h` in the line model.
+
+)mydelimiter" + DocIterator::line_model;
+
+const std::string DocIterator::res_p_mw = R"mydelimiter(
+    Get the active production (or consumption) in MW for element of the grid supporting this feature.
+
+    For generators (and static generators) it is given following the "generator convention" (positive = power is injected to the grid)
+    
+    For loads (and storage units) it is given following the "load convention" (positive = power is absorbed from the grid)
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_q_mvar = R"mydelimiter(
+    Get the reactive production (or consumption) in MVAr for element of the grid supporting this feature.
+
+    For generators (and static generators) it is given following the "generator convention" (positive = power is injected to the grid)
+    
+    For loads (and storage units) it is given following the "load convention" (positive = power is absorbed from the grid)
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_theta_deg = R"mydelimiter(
+    Get the angle of the complex voltage (in degree, not in radian) of the bus at which this object is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_theta_deg"
+
+)mydelimiter";
+
+const std::string DocIterator::res_v_kv = R"mydelimiter(
+    Get the magnitude of the complex voltage (in kV) of the bus at which this object is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_v_kv"
+
+)mydelimiter";
+
+const std::string DocIterator::target_vm_pu = R"mydelimiter(
+    Get the voltage magnitude setpoint (in pair unit and NOT in kV) for each element of the grid supporting this feature.
+
+    .. warning::
+        This is given in "pair unit" (pu) system and not in kilo Volt (kV) !
+
+)mydelimiter";
+
+const std::string DocIterator::has_res = R"mydelimiter(
+    This property specify whether or not a given element contains some "result" information. If set to ``True`` then the fields
+    starting with `res_` (*eg* `res_p_mw`) are filled otherwise they are initialized with an arbitrary (and meaningless) value.
+
+)mydelimiter";
+
+const std::string DocIterator::DataGen = R"mydelimiter(
+    This class allows to iterate through the generators of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were
+    in a python list.
+
+    In lightsim2grid they are modeled as "pv" meanings you give the active production setpoint and voltage magnitude setpoint
+    (see :attr:`lightsim2grid.elements.DataSGen` for more exotic PQ generators).
+
+    The active production value setpoint are modified only for the generators participating to the slack buses
+    (see :attr:`lightsim2grid.elements.GenInfo.is_slack` and :attr:`lightsim2grid.elements.GenInfo.slack_weight`).
+
+    Generators are modeled as in pandapower and can be represented a the 
+    `pandapower generators <https://pandapower.readthedocs.io/en/latest/elements/gen.html#electric-model>`_ .
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+
+        grid_model = env.backend._grid
+
+        for gen in grid_model.get_generators():
+            # do something with gen !
+            gen.bus_id
+
+        print(f"There are {len(grid_model.get_generators())} generators on the grid.")
+
+        first_generator = grid_model.get_generators()[0]
+
+    You can have a look at :class:`lightsim2grid.elements.GenInfo` for properties of these elements.
+
+)mydelimiter";
+
+const std::string DocIterator::GenInfo = R"mydelimiter(
+    This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataGen`
+
+    It allows to read information from each generator of the powergrid.
+
+    .. warning::
+        Data ca only be accessed from this element. You cannot modify (yet) the grid using this class.
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+
+        grid_model = env.backend._grid
+
+        first_generator = grid_model.get_generators()[0]  # first generator is a `GenInfo`
+
+        for gen in grid_model.get_generators():
+            # gen is a `GenInfo`
+            gen.bus_id
+
+)mydelimiter";
+
+const std::string DocIterator::is_slack = R"mydelimiter(
+    Tells whether or not this generator paticipated to the distributed slack bus.
+
+    .. note:: 
+        Depending on the solver used, it is possible that a generator we asked to participate to the distributed slack bus
+        do not participate to it (for example if there is a more than one generator where `is_slack` is ``True`` but the model used
+        to computed the powerflow do not support distributed slack buses - **eg** :class:`lightsim2grid.solver.SparseLUSolverSingleSlack`)
+
+        This is why we recommend to use the (slower) but more accurate :class:`lightsim2grid.solver.SparseLUSolver` or 
+        :class:`lightsim2grid.solver.KLUSolver` for example.
+
+)mydelimiter";
+
+const std::string DocIterator::slack_weight = R"mydelimiter(
+    For each generators, gives the participation (for the distributed slack) of this particular generator.
+    
+    .. note::
+        Weights do not scale to one for this variable thus this number has no meaning by itself and should be compared with the others.
+
+)mydelimiter";
+
+const std::string DocIterator::min_q_mvar = R"mydelimiter(
+    Minimum reactive value that can be produced / absorbed by this generator given MVAr.
+
+    .. note:: 
+        This is for now not taken into account by the solver. It is only used in :func:`lightsim2grid.gridmodel.check_solution` if `check_q_limits` is
+        set to ``True``
+
+)mydelimiter";
+
+const std::string DocIterator::max_q_mvar = R"mydelimiter(
+    Maximum reactive value that can be produced / absorbed by this generator given MVAr.
+
+    .. note:: 
+        This is for now not taken into account by the solver. It is only used in :func:`lightsim2grid.gridmodel.check_solution` if `check_q_limits` is
+        set to ``True``
+
+)mydelimiter";
+
+const std::string DocIterator::min_p_mw = R"mydelimiter(
+    Minimum active value that can be produced / absorbed by this generator given in MW.
+
+    .. note:: 
+        This is for now not taken into account by the solver. It is only used in :func:`lightsim2grid.gridmodel.check_solution` if `check_q_limits` is
+        set to ``True``
+
+)mydelimiter";
+
+const std::string DocIterator::max_p_mw = R"mydelimiter(
+    Maximum active value that can be produced / absorbed by this generator given in MW.
+
+    .. note:: 
+        This is for now not taken into account by the solver. It is only used in :func:`lightsim2grid.gridmodel.check_solution` if `check_q_limits` is
+        set to ``True``
+
+)mydelimiter";
+
+const std::string DocIterator::DataSGen = R"mydelimiter(
+    This class allows to iterate through the static generators of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were
+    in a python list.
+
+    In lightsim2grid they are two types of generators the more standard PV generators (see :attr:`lightsim2grid.elements.DataGen`). These
+    are more exotic generators known as PQ, where you give the active production value and reactive production value. It's basically like loads,
+    but using the generator convention (if the value is positive, it means power is taken from the grid to the element)
+
+    They cannot participate to the distributed slack bus.
+
+    Static generators are modeled as in pandapower and can be represented a the 
+    `pandapower static generators <https://pandapower.readthedocs.io/en/latest/elements/sgen.html#electric-model>`_ .
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # manipulate the static generators
+        for sgen in grid_model.get_static_generators():
+            # do something with sgen !
+            sgen.bus_id
+
+        print(f"There are {len(grid_model.get_static_generators())} static generators on the grid.")
+
+        first_static_generator = grid_model.get_static_generators()[0]
+
+    You can have a look at :class:`lightsim2grid.elements.SGenInfo` for properties of these elements.
+
+)mydelimiter";
+
+const std::string DocIterator::SGenInfo = R"mydelimiter(
+    This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataSGen`
+
+    It allows to read information from each static generator of the powergrid.
+
+    .. warning::
+        Data ca only be accessed from this element. You cannot modify (yet) the grid using this class.
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # do something with the static generators
+        first_static_generator = grid_model.get_static_generators()[0]  # first static generator is a `SGenInfo`
+
+        for sgen in grid_model.get_static_generators():
+            # sgen is a `SGenInfo`
+            sgen.bus_id
+
+)mydelimiter";
+
+const std::string DocIterator::DataLoad = R"mydelimiter(
+    This class allows to iterate through the loads **and storage units** of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were
+    in a python list.
+
+    They cannot participate to the distributed slack bus yet. If you want this feature, fill free to send us a github issue.
+
+    Loads are modeled as in pandapower and can be represented a the 
+    `pandapower loads <https://pandapower.readthedocs.io/en/latest/elements/load.html#electric-model>`_ .
+
+    .. note::
+        lightsim2grid Storages are modeled as load.
+    
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # manipulate the load
+        for load in grid_model.get_loads():
+            # do something with load !
+            load.bus_id
+
+        print(f"There are {len(grid_model.get_loads())} loads on the grid.")
+
+        first_load = grid_model.get_loads()[0]
+
+        # or the storage units
+        for storage in grid_model.get_storages():
+            # do something with storage !
+            storage.bus_id
+
+        print(f"There are {len(grid_model.get_storages())} storage units on the grid.")
+
+        first_storage_unit = grid_model.get_storages()[0]
+
+    You can have a look at :class:`lightsim2grid.elements.LoadInfo` for properties of these elements.
+
+)mydelimiter";
+
+const std::string DocIterator::LoadInfo = R"mydelimiter(
+    This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataLoad`.
+    We remind the reader that storage units are also modeled as load in lightsim2grid.
+
+    It allows to read information from each load / storage unit of the powergrid.
+
+    .. warning::
+        Data ca only be accessed from this element. You cannot modify (yet) the grid using this class.
+
+    .. note::
+        lightsim2grid Storages are modeled as load.
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # for loads
+        first_load = grid_model.get_loads()[0]  # first static generator is a `LoadInfo`
+        for load in grid_model.get_loads():
+            # load is a `LoadInfo`
+            load.bus_id
+
+
+        # for loads
+        first_storage_unit = grid_model.get_storages()[0]  # first static generator is a `LoadInfo`
+        for storage in grid_model.get_storages():
+            # storage is a `LoadInfo`
+            storage.bus_id
+
+)mydelimiter";
+
+const std::string DocIterator::DataShunt = R"mydelimiter(
+    This class allows to iterate through the load of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were
+    in a python list.
+
+    Shunts are modeled as in pandapower and can be represented a the 
+    `pandapower shunts <https://pandapower.readthedocs.io/en/latest/elements/shunt.html#electric-model>`_ .
+    
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # manipulate the load
+        for shunt in grid_model.get_shunts():
+            # do something with shunt !
+            shunt.bus_id
+
+        print(f"There are {len(grid_model.get_shunts())} shunts on the grid.")
+
+        first_shunt = grid_model.get_shunts()[0]
+
+    You can have a look at :class:`lightsim2grid.elements.ShuntInfo` for properties of these elements.
+
+)mydelimiter";
+
+const std::string DocIterator::ShuntInfo = R"mydelimiter(
+    This class represents what you get from retrieving the shunts from :class:`lightsim2grid.elements.DataShunt`.
+
+    It allows to read information from each shunt of the powergrid.
+
+    .. warning::
+        Data ca only be accessed from this element. You cannot modify (yet) the grid using this class.
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # for shunts
+        first_shunt = grid_model.get_shunts()[0]  # first shunt, this is a `ShuntInfo`
+        for shunt in grid_model.get_shunts():
+            # shunt is a `ShuntInfo`
+            shunt.bus_id
+
+)mydelimiter";
+
+const std::string DocIterator::DataTrafo = R"mydelimiter(
+    This class allows to iterate through the transformers of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were
+    in a python list.
+
+    Transformers are modeled as in pandapower and can be represented a the 
+    `pandapower transformers <https://pandapower.readthedocs.io/en/latest/elements/trafo.html#electric-model>`_ .
+    
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # manipulate the tranformers
+        for trafo in grid_model.get_trafos():
+            # do something with trafo !
+            trafo.bus_hv_id
+
+        print(f"There are {len(grid_model.get_trafos())} transformers on the grid.")
+
+        first_transformer = grid_model.get_trafos()[0]
+
+    You can have a look at :class:`lightsim2grid.elements.TrafoInfo` for properties of these elements.
+
+)mydelimiter";
+
+const std::string DocIterator::TrafoInfo = R"mydelimiter(
+    This class represents what you get from retrieving the transformers from :class:`lightsim2grid.elements.DataTrafo`.
+
+    It allows to read information from each transformer of the powergrid.
+
+    Transformers have two sides, one is "hv" for "high voltage" and one is "lv" for "low voltage" that are connected and linked to each other
+    by some equations.
+
+    For accessing the results, it's basically the same as having two "elements" (so you get two "voltage_magnitude" `res_v_kv`,
+    two "injected power" `res_p_mw` etc.)
+
+    .. warning::
+        Data ca only be accessed from this element. You cannot modify (yet) the grid using this class.
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # for transformers
+        first_transformer = grid_model.get_trafos()[0]  # first transformer, this is a `TrafoInfo`
+        for trafo in grid_model.get_trafos():
+            # trafo is a `TrafoInfo`
+            trafo.bus_hv_id
+
+    Notes
+    -----
+    Transformer are modeled using the "line model".
+
+    Usually, the "or" side is the "hv" side and the "ex" side is the "lv" side.
+
+    The tap ratio `n` bellow is a complex number with its magnitude corresponding to the tap ratio and its
+    angle to the phase shifter.
+
+    For more information about the model and the equations linking all the quantities, please visit 
+    `matpower manual <https://matpower.org/docs/MATPOWER-manual.pdf>`_ , especially the "3. Modeling" and the
+    "3.2 Branches" subsection, as well as the equation 3.1, 3.2 and 3.3 therein.
+    
+)mydelimiter" + DocIterator::line_model;
+
+const std::string DocIterator::ratio = R"mydelimiter(
+    Retrieve the ratio (absolute value of the complex coefficient `n` in the powerline model). It has no units
+
+)mydelimiter" + DocIterator::line_model;
+
+const std::string DocIterator::shift_rad = R"mydelimiter(
+    Retrieve the shift angle (angle of the complex coefficient `n` in the powerline model). It is given in radian (and not in degree)
+
+)mydelimiter" + DocIterator::line_model;
+
+const std::string DocIterator::is_tap_hv_side = R"mydelimiter(
+    Gives whether the tap (both for the ratio and the phase shifter) is located "hv" side (default, when ``True``) or 
+    "lv" side (when ``False``).
+
+)mydelimiter";
+
+const std::string DocIterator::bus_hv_id = R"mydelimiter(
+    Get the bus id (as an integer) at which the "hv" side of the transformer is connected. If `-1` is returned it means
+    that the transformer is disconnected.
+
+)mydelimiter";
+
+const std::string DocIterator::bus_lv_id = R"mydelimiter(
+    Get the bus id (as an integer) at which the "lv" side of the transformer is connected. If `-1` is returned it means
+    that the transformer is disconnected.
+
+)mydelimiter";
+
+const std::string DocIterator::res_p_hv_mw = R"mydelimiter(
+    Get the active power in MW for at the "hv" side of the transformer. If it is positive it means power is absorbed by the transformer.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_p_lv_mw = R"mydelimiter(
+    Get the active power in MW for at the "lv" side of the transformer. If it is positive it means power is absorbed by the transformer.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_q_hv_mvar = R"mydelimiter(
+    Get the reactive power in MVAr for at the "hv" side of the transformer. If it is positive it means power is absorbed by the transformer.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_q_lv_mvar = R"mydelimiter(
+    Get the reactive power in MVAr for at the "lv" side of the transformer. If it is positive it means power is absorbed by the transformer.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_theta_hv_deg = R"mydelimiter(
+    Get the angle of the complex voltage (in degree, not in radian) of the bus at which this "hv" side of the transformer is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_theta_deg"
+
+)mydelimiter";
+
+const std::string DocIterator::res_theta_lv_deg = R"mydelimiter(
+    Get the angle of the complex voltage (in degree, not in radian) of the bus at which this "lv" side of the transformer is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_theta_deg"
+
+)mydelimiter";
+
+const std::string DocIterator::res_v_hv_kv = R"mydelimiter(
+    Get the magnitude of the complex voltage (in kV) of the bus at which this "hv" side of the transformer is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_v_kv"
+
+)mydelimiter";
+
+const std::string DocIterator::res_v_lv_kv = R"mydelimiter(
+    Get the magnitude of the complex voltage (in kV) of the bus at which this "lv" side of the transformer is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_v_kv"
+
+)mydelimiter";
+
+const std::string DocIterator::res_a_lv_ka = R"mydelimiter(
+    Get the current flows (in kA) at the "lv" side of the transformer.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_a_hv_ka = R"mydelimiter(
+    Get the current flows (in kA) at the "hv" side of the transformer.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::DataLine = R"mydelimiter(
+    This class allows to iterate through the powerlines of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were
+    in a python list.
+
+    Powerlines are modeled as in pandapower and can be represented a the 
+    `pandapower lines <https://pandapower.readthedocs.io/en/latest/elements/line.html#electric-model>`_ .
+    
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # manipulate the powerlines
+        for line in grid_model.get_lines():
+            # do something with line !
+            line.bus_or_id
+
+        print(f"There are {len(grid_model.get_lines())} lines on the grid.")
+
+        first_line = grid_model.get_lines()[0]
+
+    You can have a look at :class:`lightsim2grid.elements.LineInfo` for properties of these elements.
+
+)mydelimiter";
+
+const std::string DocIterator::LineInfo = R"mydelimiter(
+    This class represents what you get from retrieving the powerlines from :class:`lightsim2grid.elements.DataLine`.
+
+    It allows to read information from each powerline of the powergrid.
+
+    Powerlines have two sides, one is "or" for "origin" and one is "ex" for "extremity" that are connected and linked to each other
+    by some equations.
+
+    For accessing the results, it's basically the same as having two "elements" (so you get two "voltage_magnitude" `res_v_kv`,
+    two "injected power" `res_p_mw` etc.)
+
+    .. warning::
+        Data ca only be accessed from this element. You cannot modify (yet) the grid using this class.
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+
+        # create a lightsim2grid "gridmodel"
+        env_name = ... # eg. "l2rpn_case14_test"
+        env = grid2op.make(env_name, backend=LightSimBackend())
+        grid_model = env.backend._grid
+
+        # for powerlines
+        first_line = grid_model.get_lines()[0]  # first line, this is a `LineInfo`
+        for line in grid_model.get_lines():
+            # line is a `LineInfo`
+            line.bus_or_id
+
+    Notes
+    -----
+    Line are modeled using the "line model" as shown in the schema at the end of the paragraph.
+
+    The tap ratio `n` on this schema will be 1.0 for all powerline. If you want to model phase shifters, please
+    model them as Trafo (see :class:`lightsim2grid.elements.TrafoInfo`)
+
+    For more information about the model and the equations linking all the quantities, please visit 
+    `matpower manual <https://matpower.org/docs/MATPOWER-manual.pdf>`_ , especially the "3. Modeling" and the
+    "3.2 Branches" subsection, as well as the equation 3.1, 3.2 and 3.3 therein.
+    
+)mydelimiter" + DocIterator::line_model;
+
+const std::string DocIterator::bus_or_id = R"mydelimiter(
+    Get the bus id (as an integer) at which the "or" side of the line is connected. If `-1` is returned it means
+    that the line is disconnected.
+
+)mydelimiter";
+
+const std::string DocIterator::bus_ex_id = R"mydelimiter(
+    Get the bus id (as an integer) at which the "lv" side of the line is connected. If `-1` is returned it means
+    that the line is disconnected.
+
+)mydelimiter";
+
+const std::string DocIterator::res_p_or_mw = R"mydelimiter(
+    Get the active power in MW for at the "or" side of the line. If it is positive it means power is absorbed by the line.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_p_ex_mw = R"mydelimiter(
+    Get the active power in MW for at the "ex" side of the line. If it is positive it means power is absorbed by the line.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_q_or_mvar = R"mydelimiter(
+    Get the reactive power in MVAr for at the "or" side of the line. If it is positive it means power is absorbed by the line.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_q_ex_mvar = R"mydelimiter(
+    Get the reactive power in MVAr for at the "ex" side of the line. If it is positive it means power is absorbed by the line.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_theta_or_deg = R"mydelimiter(
+    Get the angle of the complex voltage (in degree, not in radian) of the bus at which this "or" side of the line is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_theta_deg"
+
+)mydelimiter";
+
+const std::string DocIterator::res_theta_ex_deg = R"mydelimiter(
+    Get the angle of the complex voltage (in degree, not in radian) of the bus at which this "ex" side of the line is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_theta_deg"
+
+)mydelimiter";
+
+const std::string DocIterator::res_v_or_kv = R"mydelimiter(
+    Get the magnitude of the complex voltage (in kV) of the bus at which this "or" side of the line is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_v_kv"
+
+)mydelimiter";
+
+const std::string DocIterator::res_v_ex_kv = R"mydelimiter(
+    Get the magnitude of the complex voltage (in kV) of the bus at which this "ex" side of the line is connected.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_v_kv"
+
+)mydelimiter";
+
+const std::string DocIterator::res_a_or_ka = R"mydelimiter(
+    Get the current flows (in kA) at the "or" side of the line.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocIterator::res_a_ex_ka = R"mydelimiter(
+    Get the current flows (in kA) at the "ex" side of the line.
+
+    .. warning::
+        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+
+)mydelimiter";
+
+const std::string DocGridModel::GridModel = R"mydelimiter(
+    This class represent a lightsim2grid power network. All the elements that can be manipulated by
+    lightsim2grid are represented here.
+
+    We do not recommend to use this class directly, but rather to use a :class:`lightsim2grid.LightSimBackend.LightSimBackend`.
+
+    Examples
+    ---------
+
+    We **DO NOT** recommend to do:
+
+    .. code-block:: python
+
+        import lightsim2grid
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower network for example pp_net = pn.case118() 
+
+        grid_model = init(pp_net)
+
+    It's better to do:
+
+    .. code-block:: python
+
+        import grid2op
+        from lightsim2grid import LightSimBackend
+        env_name = ...  # any grid2op environment
+        grid2op_env = grid2op.make(env_name, backend=LightSimBackend())
+
+        grid_model = grid2op_env.backend._grid
+
+    The best way to use this class is through the `LightSimBackend` and not to use it directly !
+
+)mydelimiter";
+
+const std::string DocGridModel::change_solver =  R"mydelimiter(
+    This function allows to control which solver is used during the powerflow. See the section :ref:`available-powerflow-solvers` for 
+    more information about them.
+
+    .. seealso:: :attr:`lightsim2grid.solver.SolverType` for a list of the available solver (NB: some solvers might not be available on all platform)
+
+    .. note::
+        If the solver type entered is a `DC` solver (**eg** from :attr:`lightsim2grid.solver.SolverType`, 
+        `DC`, `KLUDC` or `NICSLUDC`), it will change the `_dc_solver` otherwise the regular `_solver` 
+        is modified.
+
+    Examples
+    ---------
+
+    .. code-block:: python
+        
+        from lightsim2grid.solver import SolverType
+        # init the grid model
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower grid
+        lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+        # change the solver used for the powerflow
+        # to use internally a solver based on Newton Raphson algorithme using Eigen sparse LU
+        lightsim_grid_model.change_solver(SolverType.SparseLUSolver)  
+
+)mydelimiter";
+
+const std::string DocGridModel::available_solvers =  R"mydelimiter(
+    Return the list of solver available on the current lightsim2grid installation.
+
+    This is a list of :attr:`lightsim2grid.solver.SolverType`.
+
+)mydelimiter";
+const std::string DocGridModel::get_computation_time = R"mydelimiter(
+    Return the total computation time (in second) spend in the solver when performing a powerflow.
+
+    This is equivalent to the `get_computation_time` of the :func:`lightsim2grid.solver.AnySolver.get_computation_time` of
+    the solver used (:func:`lightsim2grid.gridmodel.GridModel.get_solver`)
+    
+)mydelimiter";
+const std::string DocGridModel::get_dc_computation_time = R"mydelimiter(
+    Return the total computation time (in second) spend in the solver (used to perform DC approximation) when performing a DC powerflow.
+
+    This is equivalent to the `get_computation_time` of the :func:`lightsim2grid.solver.AnySolver.get_computation_time` of
+    the DC solver used (:func:`lightsim2grid.gridmodel.GridModel.get_dc_solver`)
+    
+)mydelimiter";
+const std::string DocGridModel::get_solver_type = R"mydelimiter(
+    Return the type of the solver currently used.
+
+    This is equivalent to the `get_type` of the :func:`lightsim2grid.solver.AnySolver.get_type` of
+    the solver used.
+
+)mydelimiter";
+const std::string DocGridModel::get_dc_solver_type = R"mydelimiter(
+    Return the type of the solver currently used to compute DC powerflow.
+
+)mydelimiter";
+const std::string DocGridModel::get_solver = R"mydelimiter(
+    Return the solver currently in use as a :func:`lightsim2grid.solver.AnySolver` instance.
+
+)mydelimiter";
+const std::string DocGridModel::get_dc_solver = R"mydelimiter(
+    Return the solver currently in use as a :func:`lightsim2grid.solver.AnySolver` instance for the dc powerflow.
+
+)mydelimiter";
+
+const std::string DocGridModel::get_lines = R"mydelimiter(
+    This function allows to retrieve the powerlines (as a :class:`lightsim2grid.elements.DataLine` object,
+    see :ref:`elements-modeled` for more information)
+
+    Examples
+    ---------
+
+    .. code-block:: python
+        
+        # init the grid model
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower grid
+        lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+        # usage example: print some information about the powerlines
+        print([el.x_pu for el in lightsim_grid_model.get_lines()]) # to print the "x" for each powerlines
+
+)mydelimiter";
+const std::string DocGridModel::get_trafos = R"mydelimiter(
+    This function allows to retrieve the transformers (as a :class:`lightsim2grid.elements.DataLine` object,
+    see :ref:`elements-modeled` for more information)
+
+    Examples
+    ---------
+
+    .. code-block:: python
+        
+        # init the grid model
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower grid
+        lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+        # usage example: print some information about the trafos
+        print([el.x_pu for el in lightsim_grid_model.get_trafos()]) # to print the "x" for each transformer
+
+)mydelimiter";
+const std::string DocGridModel::get_generators = R"mydelimiter(
+    This function allows to retrieve the (standard) generators (as a :class:`lightsim2grid.elements.DataGen` object,
+    see :ref:`elements-modeled` for more information)
+
+    Examples
+    ---------
+
+    .. code-block:: python
+        
+        # init the grid model
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower grid
+        lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+        # usage example: print some information about the generators
+        print([el.target_p_mw for el in lightsim_grid_model.get_generators()]) # to print the active production setpoint for each generators
+
+)mydelimiter";
+const std::string DocGridModel::get_static_generators = R"mydelimiter(
+    This function allows to retrieve the (more exotic) static generators (as a :class:`lightsim2grid.elements.DataSGen` object,
+    see :ref:`elements-modeled` for more information)
+
+    Examples
+    ---------
+
+    .. code-block:: python
+        
+        # init the grid model
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower grid
+        lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+        # usage example: print some information about the static generators
+        print([el.target_p_mw for el in lightsim_grid_model.get_static_generators()]) # to print the active production setpoint for each static generator
+
+)mydelimiter";
+const std::string DocGridModel::get_shunts = R"mydelimiter(
+    This function allows to retrieve the shunts (as a :class:`lightsim2grid.elements.DataShunt` object,
+    see :ref:`elements-modeled` for more information)
+
+    Examples
+    ---------
+
+    .. code-block:: python
+        
+        # init the grid model
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower grid
+        lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+        # usage example: print some information about the shunts
+        print([el.target_q_mvar for el in lightsim_grid_model.get_shunts()]) # to print the reactive consumption for each shunts
+
+)mydelimiter";
+const std::string DocGridModel::get_storages = R"mydelimiter(
+    This function allows to retrieve the storage units (as a :class:`lightsim2grid.elements.DataLoad` object,
+    see :ref:`elements-modeled` for more information)
+
+    .. note::
+        We want to emphize that, as far as lightsim2grid is concerned, the storage units are modeled as loads. This is why
+        this function will return a :class:`lightsim2grid.elements.DataLoad`.
+
+    Examples
+    ---------
+
+    .. code-block:: python
+        
+        # init the grid model
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower grid
+        lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+        # print the target consumption of each storage units
+        print([el.target_p_mw for el in lightsim_grid_model.get_storages()]) # to print the active consumption for each storage unit
+
+)mydelimiter";
+const std::string DocGridModel::get_loads = R"mydelimiter(
+    This function allows to retrieve the loads (as a :class:`lightsim2grid.elements.DataLoad` object,
+    see :ref:`elements-modeled` for more information)
+
+    Examples
+    ---------
+
+    .. code-block:: python
+        
+        # init the grid model
+        from lightsim2grid.gridmodel import init
+        pp_net = ...  # any pandapower grid
+        lightsim_grid_model = init(pp_net)  # some warnings might be issued as well as some warnings
+
+        # print the target consumption of each loads
+        print([el.target_p_mw for el in lightsim_grid_model.get_loads()]) # to print the active consumption for each load
+
+)mydelimiter";
+const std::string DocGridModel::_internal_do_not_use = R"mydelimiter(
+        INTERNAL
+
+        .. warning:: /!\\ Internal, do not use unless you know what you are doing /!\\
+
+        This is used as part of a dedicated code for :class:`lightsim2grid.LightSimBackend.LightSimBackend`
+
+)mydelimiter";
+
+const std::string DocGridModel::J_description = R"mydelimiter(
+    J has the shape::
+    
+        | s | slack_bus |               | (pvpq+1,1) |   (1, pvpq)  |  (1, pq)   |
+        | l |  -------  |               |            | ------------------------- |
+        | a | J11 | J12 | = dimensions: |            | (pvpq, pvpq) | (pvpq, pq) |
+        | c | --------- |               |   ------   | ------------------------- |
+        | k | J21 | J22 |               |  (pq, 1)   |  (pq, pvpq)  | (pq, pq)   |
+        
+
+    With:
+    
+    - `J11` = dS_dVa[array([pvpq]).T, pvpq].real (= real part of dS / dVa for all pv and pq buses)
+    - `J12` = dS_dVm[array([pvpq]).T, pq].real
+    - `J21` = dS_dVa[array([pq]).T, pvpq].imag
+    - `J22` = dS_dVm[array([pq]).T, pq].imag (= imaginary part of dS / dVm for all pq buses)
+    - `slack_bus` = is the representation of the equation for the reference slack bus dS_dVa[slack_bus_id, pvpq].real 
+      and dS_dVm[slack_bus_id, pq].real
+    - `slack` is the representation of the equation connecting together the slack buses (represented by slack_weights)
+      the remaining pq components are all 0.
+
+    .. note::
+        By default (and this cannot be changed at the moment), all buses in `ref` will be pv buses except the first one.
+
+    .. note::
+        the notation `pvpq` above means "the concatenation of the pv vector and the pq vector" (after the distributed slack is taken into account - see note just above)
+)mydelimiter";
+
+const std::string DocGridModel::get_J_python = R"mydelimiter(
+    Returns the Jacobian matrix used for solving the powerflow as a scipy sparse CSC matrix matrix of real number.
+
+    .. note::
+        Some powerflows (*eg* DC or Gauss Seidel) do not rely on jacobian matrix, in this case, calling this function will return an exception. 
+)mydelimiter" + DocGridModel::J_description;
+
+const std::string DocGridModel::get_Va = R"mydelimiter(
+    Returns the voltage angles for each buses as a numpy vector of real number. This vector have the size of the total number of active buses on the system.
+
+    You can use the :attr:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` (or :attr:`lightsim2grid.gridmodel.GridModel.id_dc_solver_to_me`) to know at which bus
+    (on the grid) they corresponds.
+
+)mydelimiter";
+
+const std::string DocGridModel::get_Vm = R"mydelimiter(
+    Returns the voltage magnitude for each buses as a numpy vector of real number. This vector have the size of the total number of active buses on the system.
+
+    You can use the :attr:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` (or :attr:`lightsim2grid.gridmodel.GridModel.id_dc_solver_to_me`) to know at which bus
+    (on the grid) they corresponds.
+)mydelimiter";
+
+const std::string DocGridModel::get_V = R"mydelimiter(
+    Returns the complex voltage for each buses as a numpy vector of complex number. This vector have the size of the total number of active buses on the system.
+
+    You can use the :attr:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` (or :attr:`lightsim2grid.gridmodel.GridModel.id_dc_solver_to_me`) to know at which bus
+    (on the grid) they corresponds.
+)mydelimiter";
+
+
+const std::string DocGridModel::id_me_to_ac_solver = R"mydelimiter(
+    In lightsim2grid, buses are labelled from `0` to `n-1` (if `n` denotes the total number of buses on the grid) [this is called "**grid model bus id**"]
+
+    At any given point in time, some buses might be deactivated (for example because nothing is connected to them).
+
+    On the other end, the solvers need a contiguous list of only active buses (otherwise they might run into divergence issue) [this will be called 
+    "**solver bus id**" later on]
+
+    This function allows, for all buses of the :class:`lightsim2grid.gridmodel.GridModel` to know on which "solver bus" they are affected. It
+    has the same size as the total number of buses on the grid. And for each of them it tells to which "solver bus" it is connected (unless there is a `-1`,
+    meaning the associated bus is deactivated).
+
+    Examples
+    ---------
+
+    .. code-block:: python
+
+        # create a grid model
+        import grid2op
+        from lightsim2grid import LightSimBackend
+        env_name = ...  # eg "l2rpn_case14_sandbox"
+        env = grid2op.make(env_name, backend=LightSimbackend())
+        grid_model = env.backend._grid
+
+        id_me_to_ac_solver = grid.id_me_to_ac_solver()
+        # is [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
+
+        # put everything to bus 2 on substation O
+        _ = env.step(env.action_space({"set_bus": {"substations_id": [(0, (2, 2, 2))]}}))
+
+        id_me_to_ac_solver2 = grid.id_me_to_ac_solver()
+        # is [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]
+    
+    .. seealso:: :class:`lightsim2grid.gridmodel.GridModel.id_me_to_dc_solver` for its counterpart when a dc powerflow is used
+    
+    .. seealso:: :class:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` for the "reverse" operation (given a "solver bus" id, returns 
+        the "gridmodel bus id")
+
+    Notes
+    -----
+
+    For all steps, you have the propertie that, if `id_ac_solver_to_me = gridmodel.id_ac_solver_to_me()` and `id_me_to_ac_solver = gridmodel.id_me_to_ac_solver()`
+    and by denoting `gridmodel_bus_id = np.arange(gridmodel.total_bus())` and `solver_bus_id = np.arange(gridmodel.nb_bus())`:
+
+    - `solver_bus_id` and `id_ac_solver_to_me` have the same shape
+    - `gridmodel_bus_id` and `id_me_to_ac_solver` have the same shape
+    - `solver_bus_id` is shorter (or of the same length) than `gridmodel_bus_id`
+    - the connected bus (in the grid model) are given by `gridmodel_bus_id[id_ac_solver_to_me]`, and it gives their order
+
+)mydelimiter";
+
+const std::string DocGridModel::id_ac_solver_to_me = R"mydelimiter(
+    In lightsim2grid, buses are labelled from `0` to `n-1` (if `n` denotes the total number of buses on the grid) [this is called "**grid model bus id**"]
+
+    At any given point in time, some buses might be deactivated (for example because nothing is connected to them).
+
+    On the other end, the solvers need a contiguous list of only active buses (otherwise they might run into divergence issue) [this will be called 
+    "**solver bus id**" later on]
+
+    This function allows, for all buses exported in the solver, to retrieve which was the initial bus in the :class:`lightsim2grid.gridmodel.GridModel`. It
+    has the same size as the number of active buses on the grid.
+
+    Examples
+    ---------
+
+    .. code-block:: python
+
+        # create a grid model
+        import grid2op
+        from lightsim2grid import LightSimBackend
+        env_name = ...  # eg "l2rpn_case14_sandbox"
+        env = grid2op.make(env_name, backend=LightSimbackend())
+        grid_model = env.backend._grid
+
+        id_ac_solver_to_me = grid.id_ac_solver_to_me()
+        # is [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
+
+        # put everything to bus 2 on substation O
+        _ = env.step(env.action_space({"set_bus": {"substations_id": [(0, (2, 2, 2))]}}))
+
+        id_ac_solver_to_me2 = grid.id_ac_solver_to_me()
+        # is [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
+    
+    .. seealso:: :class:`lightsim2grid.gridmodel.GridModel.id_dc_solver_to_me` for its counterpart when a dc powerflow is used
+    
+    .. seealso:: :class:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` for the "reverse" operation (given a "solver bus" id, returns 
+        the "gridmodel bus id")
+
+    Notes
+    -----
+
+    For all steps, you have the propertie that, if `id_ac_solver_to_me = gridmodel.id_ac_solver_to_me()` and `id_me_to_ac_solver = gridmodel.id_me_to_ac_solver()`
+    and by denoting `gridmodel_bus_id = np.arange(gridmodel.total_bus())` and `solver_bus_id = np.arange(gridmodel.nb_bus())`:
+
+    - `solver_bus_id` and `id_ac_solver_to_me` have the same shape
+    - `gridmodel_bus_id` and `id_me_to_ac_solver` have the same shape
+    - `solver_bus_id` is shorter (or of the same length) than `gridmodel_bus_id`
+    - the connected bus (in the grid model) are given by `gridmodel_bus_id[id_ac_solver_to_me]`, and it gives their order
+
+)mydelimiter";
+
+const std::string DocGridModel::id_me_to_dc_solver = R"mydelimiter(
+    Same as :class:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` but only used for the DC approximation.
+)mydelimiter";
+
+const std::string DocGridModel::id_dc_solver_to_me = R"mydelimiter(
+    Same as :class:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` but only used for the DC approximation.
+)mydelimiter";
+
+const std::string DocGridModel::total_bus = R"mydelimiter(
+    Returns (>0 integer) the total number of buses in the powergrid (both connected and disconnected)
+)mydelimiter";
+
+const std::string DocGridModel::nb_bus = R"mydelimiter(
+    Returns (>0 integer) the number of connected buses on the powergrid (ignores the disconnected bus).
+)mydelimiter";
+
+const std::string DocGridModel::get_pv = R"mydelimiter(
+    Returns the ids of the buses that are labelled as "PV" (ie the buses on which at least a generator is connected.).
+
+    It returns a vector of integer.
+
+    .. warning:: 
+        The index are given in the "solver bus" convention. This means that it might not be the bus of the original grid model.
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` for
+        ways to link the "grid model" bus id to the "solver" bus id.
+        
+)mydelimiter";
+
+const std::string DocGridModel::get_pq = R"mydelimiter(
+    Returns the ids of the buses that are labelled as "PQ".
+
+    It returns a vector of integer.
+
+    .. warning:: 
+        The index are given in the "solver bus" convention. This means that it will might be the bus of the original grid model.
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` for
+        ways to link the "grid model" bus id to the "solver" bus id.
+
+)mydelimiter";
+
+const std::string DocGridModel::get_slack_ids = R"mydelimiter(
+    Returns the ids of the buses that are part of the distributed slack.
+
+    It returns a vector of integer.
+
+    .. warning:: 
+        The index are given in the "solver bus" convention. This means that it might not be the bus of the original grid model.
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` for
+        ways to link the "grid model" bus id to the "solver" bus id.
+
+)mydelimiter";
+
+const std::string DocGridModel::get_slack_weights = R"mydelimiter(
+    For each bus used by the solver, it outputs its participation to the distributed slack.
+
+    It's 0 if the current bus does not participate to it, otherwise it is made of > 0. real numbers.
+
+    This vector sums to 1 and has the same size as the number of active buses on the grid.
+
+    .. warning:: 
+        This vector represents "solver buses" and not "original grid model buses".
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` for
+        ways to link the "grid model" bus id to the "solver" bus id.
+
+)mydelimiter";
+
+const std::string DocGridModel::get_Ybus = R"mydelimiter(
+    This function returns the (complex) `Ybus` matrix used to compute the powerflow.
+
+    The resulting matrix is a CSC scipy sparse matrix of complex number.
+
+    It is a square matrix, as many rows (columns) as there are connected buses on the grid.
+
+    .. warning:: 
+        Each row / columns of this matrix represents a "solver bus" (and not a "grid model bus"). In other word, the first row / column of this
+        matrix is not necessarily the first bus of the grid model.
+
+    .. warning::
+        This is given in the pair unit system !
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` for
+        ways to link the "grid model" bus id to the "solver" bus id.
+
+    Notes
+    -----
+
+    Suppose that the grid model bus of id k is connected. Then the row / column `id_me_to_ac_solver[k]` (will be >= 0) and will represent this bus:
+    `Ybus[id_me_to_ac_solver[k],:]` (rows of this bus), `Ybus[:, id_me_to_ac_solver[k]]` (column for this bus) 
+
+    .. warning:: 
+        The above only holds when the bus of id `k` is connected which is when `id_me_to_ac_solver[k] >= 0` !
+
+)mydelimiter";
+
+const std::string DocGridModel::get_dcYbus = R"mydelimiter(
+    It is the equivalent of :func:`lightsim2grid.gridmodel.GridModel.get_Ybus` but for the dc solver.
+
+    .. warning::
+        As opposed to some other librairies (for example Matpower of pandapower), the Ybus for the dc approximation in lightsim2grid has no
+        imaginary components. 
+        
+        It could have returned a real matrix, but we choose (out of consistency with other solvers) to keep the representation
+        as a complex numbers.
+    
+)mydelimiter"; 
+
+const std::string DocGridModel::get_Sbus = R"mydelimiter(
+    This function returns the (complex) `Sbus` vector, which is the vector of active / reactive power injected at each active bus
+
+    The resulting vector is a vector of complex number having the size of the number of connected buses on the grid.
+
+    .. warning:: 
+        Each row / columns of this matrix represents a "solver bus" (and not a "grid model bus"). In other word, the first row / column of this
+        matrix is not necessarily the first bus of the grid model.
+
+    .. warning::
+        This is given in the pair unit system and in load convention (so generation will be negative)
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` for
+        ways to link the "grid model" bus id to the "solver" bus id.
+    
+    Notes
+    -----
+
+    Suppose that the grid model bus of id k is connected. Then the row / column `id_me_to_ac_solver[k]` (will be >= 0) and will represent this bus:
+    `Sbus[id_me_to_ac_solver[k]]` is the total power injected at the grid model bus solver `k`.
+
+    .. warning:: 
+        The above only holds when the bus of id `k` is connected which is when `id_me_to_ac_solver[k] >= 0` !
+
+)mydelimiter"; 
+
+const std::string DocGridModel::check_solution = R"mydelimiter(
+    This function allows to check that a given complex voltage vector satisfies the KCL or not, given the state of the sytem.
+
+    .. note::
+        It is expected that you provide a complex number even for the buses that are disconnected in the grid model. They will not be ignored
+        so you can put anything you want. We keep the public interface this way to avoid headaches with the bus order between
+        the grid model and the solver (you can refer to :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and 
+        :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` if you still want to have a look)
+
+    .. seealso:: :class:`lightsim2grid.physical_law_checker.PhysicalLawChecker` for an easier to use, more pythonic function !
+
+    Parameters
+    ------------
+    V:
+      It expects a complex voltage vector (having as many components as the total number of buses in the grid.) representing the
+      vector you want to test.
+
+    check_q_limits: ``bool``
+      whether you want to take into account the reactive limit of generators when performing the check 
+
+    Returns
+    -------
+    mismatch: 
+        A complex vector having the size of the number of total buses on the grid, given, for each of them, the active / reactive power mismatch
+        at each bus (ie the power you would need to take from the grid and have the input vector `V` checking the KCL given the current state of
+        the grid)
+
+)mydelimiter"; 
+
+const std::string DocGridModel::deactivate_result_computation = R"mydelimiter(
+    Allows to deactivate the computation of the flows, reactive power absorbed by generators etc. to gain a bit of time when it is not needed.
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.reactivate_result_computation`
+)mydelimiter";     
+
+const std::string DocGridModel::reactivate_result_computation = R"mydelimiter(
+    Allows to reactivate the computation of the flows, reactive power absorbed by generators etc. when they are needed again after having been
+    deactivated.
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.deactivate_result_computation`
+)mydelimiter";     
+
+const std::string DocGridModel::ac_pf = R"mydelimiter(
+    Allows to perform an AC (alternating current) powerflow.
+
+    .. note::
+        It is expected that you provide a complex number even for the buses that are disconnected in the grid model. They will not be affected (if the powerflow converges)
+        and you can put anything you want there. We keep the public interface this way to avoid headaches with the bus order between
+        the grid model and the solver (you can refer to :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and 
+        :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` if you still want to have a look)
+
+    .. seealso:: :func:`lightsim2grid.gridmodel.GridModel.dc_pf` if you want to perform DC powerflow (same interface, same results, same behaviour)
+
+    .. warning::
+        The input vector `V` is modified (and is equal to the resulting vector `V`)
+
+    Parameters
+    ------------
+    V:
+      It expects a complex voltage vector (having as many components as the total number of buses in the grid.) representing the
+      initial guess of the resulting flows. This vector will be modified !
+
+    max_iter: ``int``
+        Maximum number of iterations allowed (this might be ignored) and should be a >= 0 integer
+    
+    tol: ``float``
+        Tolerance criteria to stop the computation. This should be > 0 real number.
+
+    Returns
+    -------
+    V:
+        A complex vector given the complex voltage at each buses of the grid model. Will be empty when the powerflow diverged.
+
+    Examples
+    --------
+
+    .. code-block:: python
+
+        # create a grid model
+        import grid2op
+        from lightsim2grid import LightSimBackend
+        env_name = ...  # eg "l2rpn_case14_sandbox"
+        env = grid2op.make(env_name, backend=LightSimbackend())
+        grid_model = env.backend._grid
+
+        V = grid_model.ac_pf(V, 10, 1e-8)
+        # if the powerflow has converged, V.shape > 0 otherwise V is empty (size 0)
+        # the original V is modified in the process !
+
+)mydelimiter";     
+
+const std::string DocGridModel::dc_pf = R"mydelimiter(
+    This function has the same interface, inputs, outputs, behaviour, etc. as the :func:`lightsim2grid.gridmodel.GridModel.ac_pf`.
+)mydelimiter";       
+
+const std::string DocComputers::Computers = R"mydelimiter(
+    Allows the computation of time series, that is, the same grid topology is used while the active / reactive power injected
+    at each buse vary. The grid topology is fixed, the injections vary.
+
+    This is a "raw" c++ class, for an easier to use interface, please refer to the python documentation of the 
+    :class:`lightsim2grid.timeSerie.TimeSerie` class.
+
+)mydelimiter";
+
+const std::string DocComputers::total_time = R"mydelimiter(
+    Total time spent in solving the powerflows, pre processing the data, post processing them, initializing everything etc.
+    
+    It is given in seconds (``float``).
+
+)mydelimiter";
+
+const std::string DocComputers::solver_time = R"mydelimiter(
+    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``).
+
+)mydelimiter";
+
+const std::string DocComputers::amps_computation_time = R"mydelimiter(
+    Time spent in computing the flows (in amps) after the voltages have been computed at each nodes
+    
+    It is given in seconds (``float``).
+
+)mydelimiter";
+
+const std::string DocComputers::preprocessing_time = R"mydelimiter(
+    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``).
+
+)mydelimiter";
+
+const std::string DocComputers::nb_solved = R"mydelimiter(
+    Total number of powerflows solved.
+
+)mydelimiter";
+
+const std::string DocComputers::get_status = R"mydelimiter(
+    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).
+
+)mydelimiter";
+
+const std::string DocComputers::compute_Vs = R"mydelimiter(
+    Compute the voltages (at each bus of the grid model) for some time series of injections (productions, loads, storage units, etc.)
+
+    .. note::
+        This function must be called before :func:`lightsim2grid.timeSerie.Computers.compute_flows` and 
+        :func:`lightsim2grid.timeSerie.Computers.get_flows`, :func:`lightsim2grid.timeSerie.Computers.get_voltages` or
+        :func:`lightsim2grid.timeSerie.Computers.get_sbuses`.
+
+    .. note::
+        During this computation, the GIL is released, allowing easier parrallel computation
+
+    Parameters
+    -----------
+    gen_p:  ``numy.ndarray``, float
+        Active generation for each generators. Its counts as many column as the number of generators on the grid and as many rows as
+        the number of steps to compute.
+
+    sgen_p:  ``numy.ndarray``, float
+        Active generation for each static generator. Its counts as many column as the number of static generators on the grid and as many rows as
+        the number of steps to compute.
+
+    load_p:``numy.ndarray``, float
+        Active consumption for each loads. Its counts as many column as the number of loads on the grid and as many rows as
+        the number of steps to compute.
+
+    load_q: ``numy.ndarray``, float
+        Reactive consumption for each loads. Its counts as many column as the number of loads on the grid and as many rows as
+        the number of steps to compute.
+
+    Vinit:  ``numy.ndarray``, complex
+        First voltage at each bus of the grid model (including the disconnected buses)
+
+    max_iter:  ``int``
+        Total number of iteration (>0 integer)
+
+    tol: ``float``
+        Solver tolerance (> 0. float)
+
+    Returns
+    ----------
+    status: ``int``
+        The status of the computation. 1 means "success": all powerflows were computed sucessfully, 0 means there were some errors and that 
+        the computation stopped after a certain number of steps.
+
+)mydelimiter";
+
+const std::string DocComputers::compute_flows = R"mydelimiter(
+    Retrieve the flows (in amps, at the origin of each powerlines / high voltage size of each transformers.
+
+    .. warning::
+        This function must be called after :func:`lightsim2grid.timeSerie.Computers.compute_Vs` has been called.
+
+    .. note::
+        This function must be called before :func:`lightsim2grid.timeSerie.Computers.get_flows`
+
+    .. note::
+        During this computation, the GIL is released, allowing easier parrallel computation
+
+)mydelimiter";
+
+const std::string DocComputers::compute_power_flows = R"mydelimiter(
+    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 :func:`lightsim2grid.timeSerie.Computers.compute_Vs` has been called.
+
+    .. note::
+        This function must be called before :func:`lightsim2grid.timeSerie.Computers.get_flows`
+
+    .. note::
+        During this computation, the GIL is released, allowing easier parrallel computation
+
+)mydelimiter";
+
+const std::string DocComputers::get_flows = R"mydelimiter(
+    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 :func:`lightsim2grid.timeSerie.Computers.compute_flows` has been called.
+        (`compute_flows` also requires that :func:`lightsim2grid.timeSerie.Computers.compute_Vs` has been caleed)
+
+    Returns
+    -------
+    As: ``numpy.ndarry`` (matrix)
+        The flows (in kA) at the origin side / high voltage side of each transformers / powerlines.
+
+)mydelimiter";
+
+const std::string DocComputers::get_power_flows = R"mydelimiter(
+    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 :func:`lightsim2grid.timeSerie.Computers.compute_power_flows` has been called.
+        (`compute_flows` also requires that :func:`lightsim2grid.timeSerie.Computers.compute_Vs` has been caleed)
+
+    Returns
+    -------
+    As: ``numpy.ndarry`` (matrix)
+        The flows (in kA) at the origin side / high voltage side of each transformers / powerlines.
+
+)mydelimiter";
+
+const std::string DocComputers::get_voltages = R"mydelimiter(
+    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 :func:`lightsim2grid.timeSerie.Computers.compute_Vs`.
+
+    Returns
+    -------
+    Vs: ``numpy.ndarry`` (matrix)
+        The complex voltage angles at each bus of the powergrid.
+
+)mydelimiter";
+
+const std::string DocComputers::get_sbuses = R"mydelimiter(
+    Ge the complex power injected at each (solver id) bus of the powergrid. Results are given in pair unit.
+    We do not recommend to use it as it uses the solver id and NOT the powergrid bus id (you can refer to 
+    :func:`lightsim2grid.gridmodel.GridModel.id_me_to_ac_solver` and 
+    :func:`lightsim2grid.gridmodel.GridModel.id_ac_solver_to_me` for more information)
+
+    Each rows correspond to a time step, each column to a bus (bus are identified by their solver id !)
+
+    .. warning::
+        This function must be called after :func:`lightsim2grid.timeSerie.Computers.compute_Vs`.
+
+    Returns
+    -------
+    Sbuses: ``numpy.ndarry`` (matrix)
+        The complex power injected at each bus (pair unit, load sign convention)
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::SecurityAnalysis = R"mydelimiter(
+    Allows the computation of "security analysis", that consists in computing the flows that would result from the disconnection of one or multiple
+    disconnections of some powerlines.
+
+    This is a "raw" c++ class, for an easier to use interface, please refer to the python documentation of the 
+    :class:`lightsim2grid.securityAnalysis.SecurityAnalysis` class.
+
+    .. warning::
+        This function might give wrong result for lightsim2grid version 0.5.5 were they were a bug : when some contingencies made the grid
+        non connex, it made all the other contingencies diverge. This bug has been fixed in version 0.6.0 and this is why we do not recommend
+        to use this feature with lightsim2grid version < 0.6.0 !
+
+    .. note::
+        Even if you instruct it to simulate the same contingency multiple times, it will only do it once.
+
+    .. note::
+        You can only simulate disconnection of powerlines / transformers
+
+    At a glance, this class should be used in three steps:
+
+    1) Modify the list of contingencies to simulate, with the functions:
+
+    - :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_n1`
+    - :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_all_n1`
+    - :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_nk`
+    - :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_multiple_n1`
+    - :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.clear`
+    - :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.remove_n1`
+    - :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.remove_nk`
+    - :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.remove_multiple_n1`
+
+    2) Then you can start the computation of the security analysis with 
+    :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute` then optionally
+    :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute_flows` .
+
+    3) And finally inspect the results with :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.get_flows` and 
+    :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.get_voltages` .
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::preprocessing_time = R"mydelimiter(
+    Time spent in pre processing the data (this involves, the checking whether the grid would be still connex after the contingency for example)
+    
+    It is given in seconds (``float``).
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::modif_Ybus_time = R"mydelimiter(
+    Time spent to modify the Ybus matrix before simulating each contingency.
+    
+    It is given in seconds (``float``).
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::add_all_n1 = R"mydelimiter(
+    This allows to add all the "n-1" in the contingency list to simulate.
+
+    .. seealso:: :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_n1` to add only a single line
+
+    .. seealso::
+        :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_multiple_n1` to add multiple single contingencies in the same call 
+        (but not necessarily all)
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::add_n1 = R"mydelimiter(
+    This allows to add a single  "n-1" in the contingency list to simulate.
+
+    .. seealso:: :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_all_n1` to add all contingencies at the same time
+
+    .. seealso::
+        :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_multiple_n1` to add multiple single contingencies in the same call.
+
+    Parameters
+    ----------
+    line_id: ``int``
+        The line id you would like to see disconnected
+        
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::add_nk = R"mydelimiter(
+    This allows to add a single  "n-k" in the contingency list to simulate (it will only add at most one contingency)
+
+    .. warning::
+        A "n-k" will disconnect multiple powerlines at the same time. It's not the same as adding muliple "n-1" contingencies, where
+        powerlines will be disconnected one after the other.
+
+    Parameters
+    ----------
+    vect_nk: ``list`` (of ``int``)
+        The lines id you want to add in the single contingency added.
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::add_multiple_n1 = R"mydelimiter(
+    This allows to add a multiple "n-1" in the contingency list to simulate (it will add as many contingency as the size of the list)
+    and is equivalent to call multiple times :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_n1`
+
+    .. seealso::
+        :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.add_all_n1` to add all the "n-1" contingencies.
+    
+    .. warning::
+        A "n-k" will disconnect multiple powerlines at the same time. It's not the same as adding muliple "n-1" contingencies, where
+        powerlines will be disconnected one after the other.
+
+    Parameters
+    ----------
+    vect_n1: ``list`` (of ``int``)
+        The lines id you want to add to the contingency list
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::clear = R"mydelimiter(
+    Clear the list of all contingencies. After a call to this method, you will need to re add some contingencies with
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::remove_n1 = R"mydelimiter(
+    Remove a single "n-1" contingency from the contingency list to simulate.
+
+    Parameters
+    ----------
+    line_id: ``int``
+        The line id you would like to remove from contingency list (will remove a single "n-k" contingencies)
+    
+    Returns
+    -------
+    success: ``bool``
+        Whether or not the contingency has been properly removed
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::remove_nk = R"mydelimiter(
+    Remove a single "n-k" contingency from the contingency list to simulate. This removes at much one single contingency
+
+    Parameters
+    ----------
+    vect_nk: ``list`` (of ``int``)
+        The lines id you want to remove from contingency list.
+
+    Returns
+    -------
+    nb_removed: ``int``
+        The total number of contingencies removed from the contingency list
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::remove_multiple_n1 = R"mydelimiter(
+    Remove multiple "n-1" contingency from the contingency list to simulate. This can remove up to `len(vect_n1)` single contingencies
+    from the contingency list.
+
+    Parameters
+    ----------
+    vect_n1: ``list`` (of ``int``)
+        The lines id you want to remove from contingency list (will remove multiple "n-1" single contingency)
+
+    Returns
+    -------
+    success: ``bool``
+        Whether or not the contingency has been properly removed
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::my_defaults_vect = R"mydelimiter(
+    Allows to inspect the contingency list that will be simulated.
+
+    Returns
+    -------
+    my_defaults_vect: ``list``
+        The list (of list) of all the current contingencies. Its length corresponds to the number of contingencies simulated.
+        For each contingency, it gives which powerline will be disconnected.
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::compute = R"mydelimiter(
+    Compute the voltages (at each bus of the grid model) for some time series of injections (productions, loads, storage units, etc.)
+
+    .. note::
+        This function must be called before :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute_flows` and 
+        :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.get_flows` or
+        :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.get_voltages` .
+
+    .. note::
+        During this computation, the GIL is released, allowing easier parrallel computation
+
+    Parameters
+    -----------
+    Vinit:  ``numy.ndarray``, complex
+        First voltage at each bus of the grid model (including the disconnected buses)
+
+    max_iter:  ``int``
+        Total number of iteration (>0 integer)
+
+    tol: ``float``
+        Solver tolerance (> 0. float)
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::compute_flows = R"mydelimiter(
+    Compute the current flows (in amps, at the origin of each powerlines / high voltage size of each transformers.
+
+    .. warning::
+        This function must be called after :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute` has been called.
+
+    .. note::
+        This function must be called before :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.get_flows`
+
+    .. note::
+        During this computation, the GIL is released, allowing easier parrallel computation
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::compute_power_flows = R"mydelimiter(
+    Compute the current flows (in MW, at the origin of each powerlines / high voltage size of each transformers.
+
+    .. warning::
+        This function must be called after :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute` has been called.
+
+    .. note::
+        This function must be called before :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.get_flows`
+
+    .. note::
+        During this computation, the GIL is released, allowing easier parrallel computation
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::get_flows = R"mydelimiter(
+    Get the flows (in kA) at the origin side / high voltage side of each transformers / powerlines.
+
+    Each rows correspond to a contingency, each column to a powerline / transformer
+
+    .. warning::
+        This function must be called after :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute_flows` has been called.
+        (`compute_flows` also requires that :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute` has been caleed)
+
+    .. warning::
+        The order in which the contingencies are computed is **NOT** (in this c++ class) the order in which you enter them. They are computed
+        in the order given by :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.my_defaults`. For an easier, more "human readable" please
+        use the :func:`lightsim2grid.securityAnalysis.SecurityAnalysis.get_flows` method.
+
+    Returns
+    -------
+    As: ``numpy.ndarray`` (matrix)
+        The flows (in kA) at the origin side / high voltage side of each transformers / powerlines.
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::get_voltages = R"mydelimiter(
+    Get the complex voltage angles at each bus of the powergrid.
+
+    Each rows correspond to a contingency, each column to a bus.
+
+    .. warning::
+        This function must be called after :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute`.
+
+    .. warning::
+        The order in which the contingencies are computed is **NOT** (in this c++ class) the order in which you enter them. They are computed
+        in the order given by :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.my_defaults`. For an easier, more "human readable" please
+        use the :func:`lightsim2grid.securityAnalysis.SecurityAnalysis.get_flows` method.
+
+    Returns
+    -------
+    Vs: ``numpy.ndarray`` (matrix)
+        The complex voltage angles at each bus of the powergrid.
+
+)mydelimiter";
+
+const std::string DocSecurityAnalysis::get_power_flows = R"mydelimiter(
+    Get the active flows (in MW) at the origin side / high voltage side of each transformers / powerlines.
+
+    Each rows correspond to a contingency, each column to a powerline / transformer
+
+    .. warning::
+        This function must be called after :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute_power_flows` has been called.
+        (`compute_flows` also requires that :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.compute` has been caleed)
+
+    .. warning::
+        The order in which the contingencies are computed is **NOT** (in this c++ class) the order in which you enter them. They are computed
+        in the order given by :func:`lightsim2grid.securityAnalysis.SecurityAnalysisCPP.my_defaults`. For an easier, more "human readable" please
+        use the :func:`lightsim2grid.securityAnalysis.SecurityAnalysis.get_flows` method.
+
+    Returns
+    -------
+    As: ``numpy.ndarray`` (matrix)
+        The flows (in kA) at the origin side / high voltage side of each transformers / powerlines.
+
+)mydelimiter";
diff --git a/src/help_fun_msg.h b/src/help_fun_msg.h
new file mode 100644
index 0000000..d5e5068
--- /dev/null
+++ b/src/help_fun_msg.h
@@ -0,0 +1,232 @@
+// Copyright (c) 2020, RTE (https://www.rte-france.com)
+// See AUTHORS.txt
+// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+// you can obtain one at http://mozilla.org/MPL/2.0/.
+// SPDX-License-Identifier: MPL-2.0
+// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+// containst he help messages for the functions and classes defined in "main" to avoid "pollute" the "main.cpp" file
+// with all of this and keep it cleaner
+
+#ifndef HELP_FUN_MSG_H
+#define HELP_FUN_MSG_H
+
+#include<string>
+
+struct DocSolver
+{
+    // generic functions
+    static const std::string get_J_python;
+    static const std::string get_Va;
+    static const std::string get_Vm;
+    static const std::string get_V;
+    static const std::string get_error;
+    static const std::string get_nb_iter;
+    static const std::string reset;
+    static const std::string converged;
+    static const std::string compute_pf;
+    static const std::string get_timers;
+
+    // solver description
+    static const std::string SparseLUSolver;
+    static const std::string SparseLUSolverSingleSlack;
+    static const std::string DCSolver;
+
+    static const std::string KLUSolver;
+    static const std::string KLUSolverSingleSlack;
+    static const std::string KLUDCSolver;
+
+    static const std::string NICSLUSolver;
+    static const std::string NICSLUSolverSingleSlack;
+    static const std::string NICSLUDCSolver;
+
+    static const std::string GaussSeidelSolver;
+    static const std::string GaussSeidelSynchSolver;
+
+    // function to select the solver
+    static const std::string AnySolver;
+    static const std::string get_type;
+    static const std::string chooseSolver_get_J_python;
+    static const std::string get_computation_time;
+
+};
+
+struct DocIterator
+{
+    // generic functions
+    static const std::string id;
+    static const std::string connected;
+    static const std::string bus_id;
+    static const std::string target_p_mw;
+    static const std::string target_vm_pu;
+    static const std::string target_q_mvar;
+    static const std::string has_res;
+    static const std::string res_p_mw;
+    static const std::string res_q_mvar;
+    static const std::string res_theta_deg;
+    static const std::string res_v_kv;
+    static const std::string min_p_mw;
+    static const std::string max_p_mw;
+    static const std::string min_q_mvar;
+    static const std::string max_q_mvar;
+    static const std::string line_model;
+    static const std::string r_pu;
+    static const std::string x_pu;
+    static const std::string h_pu;
+
+    // specific to generators
+    static const std::string DataGen;
+    static const std::string GenInfo;
+    static const std::string is_slack;
+    static const std::string slack_weight;
+
+    // specific to sgens
+    static const std::string DataSGen;
+    static const std::string SGenInfo;
+
+    // specific to loads (and storage units)
+    static const std::string DataLoad;
+    static const std::string LoadInfo;
+
+    // specific to shunts
+    static const std::string DataShunt;
+    static const std::string ShuntInfo;
+
+    // specific to transformers
+    static const std::string DataTrafo;
+    static const std::string TrafoInfo;
+    static const std::string bus_hv_id;
+    static const std::string bus_lv_id;
+    static const std::string is_tap_hv_side;
+    static const std::string ratio;
+    static const std::string shift_rad;
+    static const std::string res_p_hv_mw;
+    static const std::string res_q_hv_mvar;
+    static const std::string res_v_hv_kv;
+    static const std::string res_a_hv_ka;
+    static const std::string res_p_lv_mw;
+    static const std::string res_q_lv_mvar;
+    static const std::string res_v_lv_kv;
+    static const std::string res_a_lv_ka;
+    static const std::string res_theta_hv_deg;
+    static const std::string res_theta_lv_deg;
+
+    // specific to powerlines
+    static const std::string DataLine;
+    static const std::string LineInfo;
+    static const std::string bus_or_id;
+    static const std::string bus_ex_id;
+    static const std::string res_p_or_mw;
+    static const std::string res_q_or_mvar;
+    static const std::string res_v_or_kv;
+    static const std::string res_a_or_ka;
+    static const std::string res_p_ex_mw;
+    static const std::string res_q_ex_mvar;
+    static const std::string res_v_ex_kv;
+    static const std::string res_a_ex_ka;
+    static const std::string res_theta_or_deg;
+    static const std::string res_theta_ex_deg;
+
+};
+
+struct DocGridModel
+{
+    static const std::string _internal_do_not_use;
+    static const std::string J_description;
+    
+    static const std::string GridModel;
+
+    static const std::string change_solver;
+    static const std::string available_solvers;
+    static const std::string get_computation_time;
+    static const std::string get_dc_computation_time;
+    static const std::string get_solver_type;
+    static const std::string get_dc_solver_type;
+    static const std::string get_solver;
+    static const std::string get_dc_solver;
+
+    // accessor
+    static const std::string get_lines;
+    static const std::string get_trafos;
+    static const std::string get_generators;
+    static const std::string get_static_generators;
+    static const std::string get_shunts;
+    static const std::string get_storages;
+    static const std::string get_loads;
+    
+    // retrieve the results
+    static const std::string get_J_python;
+    static const std::string get_Va;
+    static const std::string get_Vm;
+    static const std::string get_V;
+    static const std::string id_me_to_ac_solver;
+    static const std::string id_ac_solver_to_me;
+    static const std::string id_me_to_dc_solver;
+    static const std::string id_dc_solver_to_me;
+    static const std::string total_bus;
+    static const std::string nb_bus;
+    static const std::string get_pv;
+    static const std::string get_pq;
+    static const std::string get_slack_ids;
+    static const std::string get_slack_weights;
+    static const std::string get_Ybus;
+    static const std::string get_dcYbus;
+    static const std::string get_Sbus;
+
+    static const std::string check_solution;
+
+    static const std::string deactivate_result_computation;
+    static const std::string reactivate_result_computation;
+    static const std::string ac_pf;
+    static const std::string dc_pf;
+};
+
+struct DocComputers
+{
+    static const std::string Computers;
+    static const std::string total_time;
+    static const std::string solver_time;
+    static const std::string preprocessing_time;
+    static const std::string amps_computation_time;
+    static const std::string nb_solved;
+    static const std::string get_status;
+
+    static const std::string compute_Vs;
+    static const std::string compute_flows;
+    static const std::string compute_power_flows;
+
+    static const std::string get_flows;
+    static const std::string get_power_flows;
+    static const std::string get_voltages;
+    static const std::string get_sbuses;
+};
+
+struct DocSecurityAnalysis
+{
+    static const std::string SecurityAnalysis;
+
+    static const std::string preprocessing_time;
+    static const std::string modif_Ybus_time;
+
+    static const std::string add_all_n1;
+    static const std::string add_n1;
+    static const std::string add_nk;
+    static const std::string add_multiple_n1;
+    static const std::string clear;
+    static const std::string remove_n1;
+    static const std::string remove_nk;
+    static const std::string remove_multiple_n1;
+
+    static const std::string my_defaults_vect;
+
+    static const std::string compute;
+    static const std::string compute_flows;
+    static const std::string compute_power_flows;
+
+    static const std::string get_flows;
+    static const std::string get_voltages;
+    static const std::string get_power_flows;
+};
+
+#endif  // HELP_FUN_MSG_H
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index a281909..928b01a 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -16,179 +16,358 @@
 #include "Computers.h"
 #include "SecurityAnalysis.h"
 
+#include "help_fun_msg.h"
+
 namespace py = pybind11;
 
 PYBIND11_MODULE(lightsim2grid_cpp, m)
 {
 
     // solvers
-    py::enum_<SolverType>(m, "SolverType")
-        .value("SparseLU", SolverType::SparseLU)
-        .value("KLU", SolverType::KLU)
-        .value("GaussSeidel", SolverType::GaussSeidel)
-        .value("GaussSeidelSynch", SolverType::GaussSeidelSynch)
-        .value("DC", SolverType::DC)
-        .value("NICSLU", SolverType::NICSLU)
+    py::enum_<SolverType>(m, "SolverType", "This enum controls the solver you want to use.")
+        .value("GaussSeidel", SolverType::GaussSeidel, "denotes the :class:`lightsim2grid.solver.GaussSeidelSolver`")
+        .value("GaussSeidelSynch", SolverType::GaussSeidelSynch, "denotes the :class:`lightsim2grid.solver.GaussSeidelSynchSolver`")
+        .value("SparseLU", SolverType::SparseLU, "denotes the :class:`lightsim2grid.solver.SparseLUSolver`")
+        .value("SparseLUSingleSlack", SolverType::SparseLUSingleSlack, "denotes the :class:`lightsim2grid.solver.SparseLUSolverSingleSlack`")
+        .value("DC", SolverType::DC, "denotes the :class:`lightsim2grid.solver.DCSolver`")
+        .value("KLU", SolverType::KLU, "denotes the :class:`lightsim2grid.solver.KLUSolver`")
+        .value("KLUSingleSlack", SolverType::KLUSingleSlack, "denotes the :class:`lightsim2grid.solver.KLUSolverSingleSlack`")
+        .value("KLUDC", SolverType::KLUDC, "denotes the :class:`lightsim2grid.solver.KLUDCSolver`")
+        .value("NICSLU", SolverType::NICSLU, "denotes the :class:`lightsim2grid.solver.NICSLUSolver`")
+        .value("NICSLUSingleSlack", SolverType::NICSLUSingleSlack, "denotes the :class:`lightsim2grid.solver.NICSLUSolverSingleSlack`")
+        .value("NICSLUDC", SolverType::NICSLUDC, "denotes the :class:`lightsim2grid.solver.NICSLUDCSolver`")
+        .export_values();
+
+    py::enum_<ErrorType>(m, "ErrorType", "This enum controls the error encountered in the solver")
+        .value("NoError", ErrorType::NoError, "No error were encountered")
+        .value("SingularMatrix", ErrorType::SingularMatrix, "The Jacobian matrix was singular and could not be factorized (most likely, the grid is not connex)")
+        .value("TooManyIterations", ErrorType::TooManyIterations, "The solver reached the maximum number of iterations allowed")
+        .value("InifiniteValue", ErrorType::InifiniteValue, "Some infinite values were encountered in the update vector (to update Vm or Va)")
+        .value("SolverAnalyze", ErrorType::SolverAnalyze, "The linear solver failed at the 'analyze' step (*eg* `analyzePattern` for Eigen, `klu_analyze` for KLU or `Initialize` for NICSLU")
+        .value("SolverFactor", ErrorType::SolverFactor, "The linear solver failed to factor the jacobian matrix (*eg* `factorize` for Eigen (first call), `klu_factor` for KLU or `FactorizeMatrix` for NICSLU (first call)")
+        .value("SolverReFactor", ErrorType::SolverReFactor, "The linear solver failed to (re)factor the jacobian matrix (*eg* `factorize` for Eigen (later calls), `klu_refactor` for KLU or `FactorizeMatrix` for NICSLU (later calls)")
+        .value("SolverSolve", ErrorType::SolverSolve, "The linear solve failed to solve the linear system J.X = b (*eg* `solve` for Eigen, `klu_solve` for KLU or `Solve` for NICSLU")
+        .value("NotInitError", ErrorType::NotInitError, "Attempt to perform some powerflow computation when the linear solver is not initiliazed")
+        .value("LicenseError", ErrorType::LicenseError, "Impossible to use the linear solver as the license cannot be found (*eg* unable to locate the `nicslu.lic` file")
         .export_values();
 
-    #ifdef KLU_SOLVER_AVAILABLE
-    py::class_<KLUSolver>(m, "KLUSolver")
+    py::class_<SparseLUSolver>(m, "SparseLUSolver", DocSolver::SparseLUSolver.c_str())
         .def(py::init<>())
-        .def("get_J", &KLUSolver::get_J)  // (get the jacobian matrix, sparse csc matrix)
-        .def("get_Va", &KLUSolver::get_Va)  // get the voltage angle vector (vector of double)
-        .def("get_Vm", &KLUSolver::get_Vm)  // get the voltage magnitude vector (vector of double)
-        .def("get_error", &KLUSolver::get_error)  // get the error message, see the definition of "err_" for more information
-        .def("get_nb_iter", &KLUSolver::get_nb_iter)  // return the number of iteration performed at the last optimization
-        .def("reset", &KLUSolver::reset)  // reset the solver to its original state
-        .def("converged", &KLUSolver::converged)  // whether the solver has converged
-        .def("compute_pf", &KLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>())  // perform the newton raphson optimization
-        .def("get_timers", &KLUSolver::get_timers)  // returns the timers corresponding to times the solver spent in different part
-        .def("solve", &KLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>() );  // perform the newton raphson optimization
-    #endif  // KLU_SOLVER_AVAILABLE
-
-    #ifdef NICSLU_SOLVER_AVAILABLE
-    py::class_<NICSLUSolver>(m, "NICSLUSolver")
+        .def("get_J", &SparseLUSolver::get_J_python, DocSolver::get_J_python.c_str())  // (get the jacobian matrix, sparse csc matrix)
+        .def("get_Va", &SparseLUSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+        .def("get_Vm", &SparseLUSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+        .def("get_V", &SparseLUSolver::get_V, DocSolver::get_V.c_str()) 
+        .def("get_error", &SparseLUSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+        .def("get_nb_iter", &SparseLUSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+        .def("reset", &SparseLUSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+        .def("converged", &SparseLUSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+        .def("compute_pf", &SparseLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+        .def("get_timers", &SparseLUSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+        .def("solve", &SparseLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+    
+    py::class_<SparseLUSolverSingleSlack>(m, "SparseLUSolverSingleSlack", DocSolver::SparseLUSolverSingleSlack.c_str())
         .def(py::init<>())
-        .def("get_J", &NICSLUSolver::get_J)  // (get the jacobian matrix, sparse csc matrix)
-        .def("get_Va", &NICSLUSolver::get_Va)  // get the voltage angle vector (vector of double)
-        .def("get_Vm", &NICSLUSolver::get_Vm)  // get the voltage magnitude vector (vector of double)
-        .def("get_error", &NICSLUSolver::get_error)  // get the error message, see the definition of "err_" for more information
-        .def("get_nb_iter", &NICSLUSolver::get_nb_iter)  // return the number of iteration performed at the last optimization
-        .def("reset", &NICSLUSolver::reset)  // reset the solver to its original state
-        .def("converged", &NICSLUSolver::converged)  // whether the solver has converged
-        .def("compute_pf", &NICSLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>())  // perform the newton raphson optimization
-        .def("get_timers", &NICSLUSolver::get_timers)  // returns the timers corresponding to times the solver spent in different part
-        .def("solve", &NICSLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>() );  // perform the newton raphson optimization
-    #endif  // NICSLU_SOLVER_AVAILABLE
-
-    py::class_<SparseLUSolver>(m, "SparseLUSolver")
+        .def("get_J", &SparseLUSolverSingleSlack::get_J_python, DocSolver::get_J_python.c_str())  // (get the jacobian matrix, sparse csc matrix)
+        .def("get_Va", &SparseLUSolverSingleSlack::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+        .def("get_Vm", &SparseLUSolverSingleSlack::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+        .def("get_V", &SparseLUSolverSingleSlack::get_V, DocSolver::get_V.c_str()) 
+        .def("get_error", &SparseLUSolverSingleSlack::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+        .def("get_nb_iter", &SparseLUSolverSingleSlack::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+        .def("reset", &SparseLUSolverSingleSlack::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+        .def("converged", &SparseLUSolverSingleSlack::converged, DocSolver::converged.c_str())  // whether the solver has converged
+        .def("compute_pf", &SparseLUSolverSingleSlack::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+        .def("get_timers", &SparseLUSolverSingleSlack::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+        .def("solve", &SparseLUSolverSingleSlack::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+
+    py::class_<DCSolver>(m, "DCSolver", DocSolver::DCSolver.c_str())
         .def(py::init<>())
-        .def("get_J", &SparseLUSolver::get_J)  // (get the jacobian matrix, sparse csc matrix)
-        .def("get_Va", &SparseLUSolver::get_Va)  // get the voltage angle vector (vector of double)
-        .def("get_Vm", &SparseLUSolver::get_Vm)  // get the voltage magnitude vector (vector of double)
-        .def("get_error", &SparseLUSolver::get_error)  // get the error message, see the definition of "err_" for more information
-        .def("get_nb_iter", &SparseLUSolver::get_nb_iter)  // return the number of iteration performed at the last optimization
-        .def("reset", &SparseLUSolver::reset)  // reset the solver to its original state
-        .def("converged", &SparseLUSolver::converged)  // whether the solver has converged
-        .def("compute_pf", &SparseLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>())  // perform the newton raphson optimization
-        .def("get_timers", &SparseLUSolver::get_timers)  // returns the timers corresponding to times the solver spent in different part
-        .def("solve", &SparseLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>() );  // perform the newton raphson optimization
-
-    py::class_<GaussSeidelSolver>(m, "GaussSeidelSolver")
+        .def("get_Va", &DCSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+        .def("get_Vm", &DCSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+        .def("get_V", &DCSolver::get_V, DocSolver::get_V.c_str()) 
+        .def("get_error", &DCSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+        .def("get_nb_iter", &DCSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+        .def("reset", &DCSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+        .def("converged", &DCSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+        .def("compute_pf", &DCSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // compute the powerflow
+        .def("get_timers", &DCSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+        .def("solve", &DCSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+
+    #if defined(KLU_SOLVER_AVAILABLE) || defined(_READ_THE_DOCS)
+        py::class_<KLUSolver>(m, "KLUSolver", DocSolver::KLUSolver.c_str())
+            .def(py::init<>())
+            .def("get_J", &KLUSolver::get_J_python, DocSolver::get_J_python.c_str())  // (get the jacobian matrix, sparse csc matrix)
+            .def("get_Va", &KLUSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &KLUSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &KLUSolver::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &KLUSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &KLUSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &KLUSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &KLUSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &KLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &KLUSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &KLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str() );  // perform the newton raphson optimization
+        
+        py::class_<KLUSolverSingleSlack>(m, "KLUSolverSingleSlack", DocSolver::KLUSolverSingleSlack.c_str())
+            .def(py::init<>())
+            .def("get_J", &KLUSolverSingleSlack::get_J_python, DocSolver::get_J_python.c_str())  // (get the jacobian matrix, sparse csc matrix)
+            .def("get_Va", &KLUSolverSingleSlack::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &KLUSolverSingleSlack::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &KLUSolverSingleSlack::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &KLUSolverSingleSlack::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &KLUSolverSingleSlack::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &KLUSolverSingleSlack::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &KLUSolverSingleSlack::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &KLUSolverSingleSlack::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &KLUSolverSingleSlack::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &KLUSolverSingleSlack::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+        
+        py::class_<KLUDCSolver>(m, "KLUDCSolver", DocSolver::KLUDCSolver.c_str())
+            .def(py::init<>())
+            .def("get_Va", &KLUDCSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &KLUDCSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &KLUDCSolver::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &KLUDCSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &KLUDCSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &KLUDCSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &KLUDCSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &KLUDCSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &KLUDCSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &KLUDCSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+    #endif  // KLU_SOLVER_AVAILABLE (or )
+
+    #if defined(NICSLU_SOLVER_AVAILABLE) || defined(_READ_THE_DOCS)
+        py::class_<NICSLUSolver>(m, "NICSLUSolver", DocSolver::NICSLUSolver.c_str())
+            .def(py::init<>())
+            .def("get_J", &NICSLUSolver::get_J_python, DocSolver::get_J_python.c_str())  // (get the jacobian matrix, sparse csc matrix)
+            .def("get_Va", &NICSLUSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &NICSLUSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &NICSLUSolver::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &NICSLUSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &NICSLUSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &NICSLUSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &NICSLUSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &NICSLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &NICSLUSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &NICSLUSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+        
+        py::class_<NICSLUSolverSingleSlack>(m, "NICSLUSolverSingleSlack", DocSolver::NICSLUSolverSingleSlack.c_str())
+            .def(py::init<>())
+            .def("get_J", &NICSLUSolverSingleSlack::get_J_python, DocSolver::get_J_python.c_str())  // (get the jacobian matrix, sparse csc matrix)
+            .def("get_Va", &NICSLUSolverSingleSlack::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &NICSLUSolverSingleSlack::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &NICSLUSolverSingleSlack::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &NICSLUSolverSingleSlack::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &NICSLUSolverSingleSlack::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &NICSLUSolverSingleSlack::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &NICSLUSolverSingleSlack::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &NICSLUSolverSingleSlack::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &NICSLUSolverSingleSlack::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &NICSLUSolverSingleSlack::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+        
+        py::class_<NICSLUDCSolver>(m, "NICSLUDCSolver", DocSolver::NICSLUDCSolver.c_str())
+            .def(py::init<>())
+            .def("get_Va", &NICSLUDCSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &NICSLUDCSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &NICSLUDCSolver::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &NICSLUDCSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &NICSLUDCSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &NICSLUDCSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &NICSLUDCSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &NICSLUDCSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &NICSLUDCSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &NICSLUDCSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+    #endif  // NICSLU_SOLVER_AVAILABLE (or _READ_THE_DOCS)
+
+    py::class_<GaussSeidelSolver>(m, "GaussSeidelSolver", DocSolver::GaussSeidelSolver.c_str())
         .def(py::init<>())
-        .def("get_Va", &GaussSeidelSolver::get_Va)  // get the voltage angle vector (vector of double)
-        .def("get_Vm", &GaussSeidelSolver::get_Vm)  // get the voltage magnitude vector (vector of double)
-        .def("get_error", &GaussSeidelSolver::get_error)  // get the error message, see the definition of "err_" for more information
-        .def("get_nb_iter", &GaussSeidelSolver::get_nb_iter)  // return the number of iteration performed at the last optimization
-        .def("reset", &GaussSeidelSolver::reset)  // reset the solver to its original state
-        .def("converged", &GaussSeidelSolver::converged)  // whether the solver has converged
-        .def("compute_pf", &GaussSeidelSolver::compute_pf, py::call_guard<py::gil_scoped_release>())  // compute the powerflow
-        .def("get_timers", &GaussSeidelSolver::get_timers)  // returns the timers corresponding to times the solver spent in different part
-        .def("solve", &GaussSeidelSolver::compute_pf, py::call_guard<py::gil_scoped_release>() );  // perform the newton raphson optimization
-
-    py::class_<GaussSeidelSynchSolver>(m, "GaussSeidelSynchSolver")
+        .def("get_Va", &GaussSeidelSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+        .def("get_Vm", &GaussSeidelSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+        .def("get_V", &GaussSeidelSolver::get_V, DocSolver::get_V.c_str()) 
+        .def("get_error", &GaussSeidelSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+        .def("get_nb_iter", &GaussSeidelSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+        .def("reset", &GaussSeidelSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+        .def("converged", &GaussSeidelSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+        .def("compute_pf", &GaussSeidelSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // compute the powerflow
+        .def("get_timers", &GaussSeidelSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+        .def("solve", &GaussSeidelSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+
+    py::class_<GaussSeidelSynchSolver>(m, "GaussSeidelSynchSolver", DocSolver::GaussSeidelSynchSolver.c_str())
         .def(py::init<>())
-        .def("get_Va", &GaussSeidelSynchSolver::get_Va)  // get the voltage angle vector (vector of double)
-        .def("get_Vm", &GaussSeidelSynchSolver::get_Vm)  // get the voltage magnitude vector (vector of double)
-        .def("get_error", &GaussSeidelSynchSolver::get_error)  // get the error message, see the definition of "err_" for more information
-        .def("get_nb_iter", &GaussSeidelSynchSolver::get_nb_iter)  // return the number of iteration performed at the last optimization
-        .def("reset", &GaussSeidelSynchSolver::reset)  // reset the solver to its original state
-        .def("converged", &GaussSeidelSynchSolver::converged)  // whether the solver has converged
-        .def("compute_pf", &GaussSeidelSynchSolver::compute_pf, py::call_guard<py::gil_scoped_release>())  // compute the powerflow
-        .def("get_timers", &GaussSeidelSynchSolver::get_timers)  // returns the timers corresponding to times the solver spent in different part
-        .def("solve", &GaussSeidelSynchSolver::compute_pf, py::call_guard<py::gil_scoped_release>() );  // perform the newton raphson optimization
-
-    py::class_<DCSolver>(m, "DCSolver")
+        .def("get_Va", &GaussSeidelSynchSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+        .def("get_Vm", &GaussSeidelSynchSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+        .def("get_V", &GaussSeidelSynchSolver::get_V, DocSolver::get_V.c_str()) 
+        .def("get_error", &GaussSeidelSynchSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+        .def("get_nb_iter", &GaussSeidelSynchSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+        .def("reset", &GaussSeidelSynchSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+        .def("converged", &GaussSeidelSynchSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+        .def("compute_pf", &GaussSeidelSynchSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // compute the powerflow
+        .def("get_timers", &GaussSeidelSynchSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+        .def("solve", &GaussSeidelSynchSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+
+    // Only "const" method are exported
+    // it is so that i cannot modify the internal solver of a gridmodel python side
+    py::class_<ChooseSolver>(m, "AnySolver", DocSolver::AnySolver.c_str())
         .def(py::init<>())
-        .def("get_Va", &DCSolver::get_Va)  // get the voltage angle vector (vector of double)
-        .def("get_Vm", &DCSolver::get_Vm)  // get the voltage magnitude vector (vector of double)
-        .def("get_error", &DCSolver::get_error)  // get the error message, see the definition of "err_" for more information
-        .def("get_nb_iter", &DCSolver::get_nb_iter)  // return the number of iteration performed at the last optimization
-        .def("reset", &DCSolver::reset)  // reset the solver to its original state
-        .def("converged", &DCSolver::converged)  // whether the solver has converged
-        .def("compute_pf", &DCSolver::compute_pf, py::call_guard<py::gil_scoped_release>())  // compute the powerflow
-        .def("get_timers", &DCSolver::get_timers)  // returns the timers corresponding to times the solver spent in different part
-        .def("solve", &DCSolver::compute_pf, py::call_guard<py::gil_scoped_release>() );  // perform the newton raphson optimization
+        .def("get_type", &ChooseSolver::get_type, DocSolver::get_type.c_str())
+        // .def("change_solver", &ChooseSolver::change_solver)
+        // .def("reset", &ChooseSolver::reset)
+        // .def("compute_pf", &ChooseSolver::compute_pf, py::call_guard<py::gil_scoped_release>())  // compute the powerflow
+        // .def("solve", &ChooseSolver::compute_pf, py::call_guard<py::gil_scoped_release>() )
+        .def("get_Va", &ChooseSolver::get_Va, DocSolver::get_Va.c_str())  
+        .def("get_Vm", &ChooseSolver::get_Vm, DocSolver::get_Vm.c_str()) 
+        .def("get_V", &ChooseSolver::get_V, DocSolver::get_V.c_str()) 
+        .def("get_J", &ChooseSolver::get_J_python, DocSolver::chooseSolver_get_J_python.c_str()) 
+        .def("get_error", &ChooseSolver::get_error, DocSolver::get_V.c_str()) 
+        .def("get_nb_iter", &ChooseSolver::get_nb_iter, DocSolver::get_nb_iter.c_str()) 
+        .def("converged", &ChooseSolver::converged, DocSolver::converged.c_str()) 
+        .def("get_computation_time", &ChooseSolver::get_computation_time, DocSolver::get_computation_time.c_str());
 
     // iterator for generators
-    py::class_<DataGen>(m, "DataGen")
+    py::class_<DataGen>(m, "DataGen", DocIterator::DataGen.c_str())
         .def("__len__", [](const DataGen & data) { return data.nb(); })
         .def("__getitem__", [](const DataGen & data, int k){return data[k]; } )
         .def("__iter__", [](const DataGen & data) {
        return py::make_iterator(data.begin(), data.end());
     }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
 
-    py::class_<DataGen::GenInfo>(m, "GenInfo")
-        .def_readonly("id", &DataGen::GenInfo::id)
-        .def_readonly("connected", &DataGen::GenInfo::connected)
-        .def_readonly("bus_id", &DataGen::GenInfo::bus_id)
-        .def_readonly("target_p_mw", &DataGen::GenInfo::target_p_mw)
-        .def_readonly("target_vm_pu", &DataGen::GenInfo::target_vm_pu)
-        .def_readonly("min_q_mvar", &DataGen::GenInfo::min_q_mvar)
-        .def_readonly("max_q_mvar", &DataGen::GenInfo::max_q_mvar)
-        .def_readonly("has_res", &DataGen::GenInfo::has_res)
-        .def_readonly("res_p_mw", &DataGen::GenInfo::res_p_mw)
-        .def_readonly("res_q_mvar", &DataGen::GenInfo::res_q_mvar)
-        .def_readonly("res_v_kv", &DataGen::GenInfo::res_v_kv);
+    py::class_<DataGen::GenInfo>(m, "GenInfo", DocIterator::GenInfo.c_str())
+        .def_readonly("id", &DataGen::GenInfo::id, DocIterator::id.c_str())
+        .def_readonly("connected", &DataGen::GenInfo::connected, DocIterator::connected.c_str())
+        .def_readonly("bus_id", &DataGen::GenInfo::bus_id, DocIterator::bus_id.c_str())
+        .def_readonly("is_slack", &DataGen::GenInfo::is_slack, DocIterator::is_slack.c_str())
+        .def_readonly("slack_weight", &DataGen::GenInfo::slack_weight, DocIterator::slack_weight.c_str())
+        .def_readonly("target_p_mw", &DataGen::GenInfo::target_p_mw, DocIterator::target_p_mw.c_str())
+        .def_readonly("target_vm_pu", &DataGen::GenInfo::target_vm_pu, DocIterator::target_vm_pu.c_str())
+        .def_readonly("min_q_mvar", &DataGen::GenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str())
+        .def_readonly("max_q_mvar", &DataGen::GenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str())
+        .def_readonly("has_res", &DataGen::GenInfo::has_res, DocIterator::has_res.c_str())
+        .def_readonly("res_p_mw", &DataGen::GenInfo::res_p_mw, DocIterator::res_p_mw.c_str())
+        .def_readonly("res_q_mvar", &DataGen::GenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str())
+        .def_readonly("res_theta_deg", &DataGen::GenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str())
+        .def_readonly("res_v_kv", &DataGen::GenInfo::res_v_kv, DocIterator::res_v_kv.c_str());
+
+    // iterator for sgens
+    py::class_<DataSGen>(m, "DataSGen", DocIterator::DataSGen.c_str())
+        .def("__len__", [](const DataSGen & data) { return data.nb(); })
+        .def("__getitem__", [](const DataSGen & data, int k){return data[k]; } )
+        .def("__iter__", [](const DataSGen & data) {
+       return py::make_iterator(data.begin(), data.end());
+    }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
+
+    py::class_<DataSGen::SGenInfo>(m, "SGenInfo", DocIterator::SGenInfo.c_str())
+        .def_readonly("id", &DataSGen::SGenInfo::id, DocIterator::id.c_str())
+        .def_readonly("connected", &DataSGen::SGenInfo::connected, DocIterator::connected.c_str())
+        .def_readonly("bus_id", &DataSGen::SGenInfo::bus_id, DocIterator::bus_id.c_str())
+        .def_readonly("min_q_mvar", &DataSGen::SGenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str())
+        .def_readonly("max_q_mvar", &DataSGen::SGenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str())
+        .def_readonly("min_p_mw", &DataSGen::SGenInfo::min_p_mw, DocIterator::min_p_mw.c_str())
+        .def_readonly("max_p_mw", &DataSGen::SGenInfo::max_p_mw, DocIterator::max_p_mw.c_str())
+        .def_readonly("target_p_mw", &DataSGen::SGenInfo::target_p_mw, DocIterator::target_p_mw.c_str())
+        .def_readonly("target_q_mvar", &DataSGen::SGenInfo::target_q_mvar, DocIterator::target_q_mvar.c_str())
+        .def_readonly("has_res", &DataSGen::SGenInfo::has_res, DocIterator::has_res.c_str())
+        .def_readonly("res_p_mw", &DataSGen::SGenInfo::res_p_mw, DocIterator::res_p_mw.c_str())
+        .def_readonly("res_q_mvar", &DataSGen::SGenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str())
+        .def_readonly("res_theta_deg", &DataSGen::SGenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str())
+        .def_readonly("res_v_kv", &DataSGen::SGenInfo::res_v_kv, DocIterator::res_v_kv.c_str());
+
+    // iterator for loads (and storage units)
+    py::class_<DataLoad>(m, "DataLoad", DocIterator::DataLoad.c_str())
+        .def("__len__", [](const DataLoad & data) { return data.nb(); })
+        .def("__getitem__", [](const DataLoad & data, int k){return data[k]; } )
+        .def("__iter__", [](const DataLoad & data) {
+       return py::make_iterator(data.begin(), data.end());
+    }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
+
+    py::class_<DataLoad::LoadInfo>(m, "LoadInfo", DocIterator::LoadInfo.c_str())
+        .def_readonly("id", &DataLoad::LoadInfo::id, DocIterator::id.c_str())
+        .def_readonly("connected", &DataLoad::LoadInfo::connected, DocIterator::connected.c_str())
+        .def_readonly("bus_id", &DataLoad::LoadInfo::bus_id, DocIterator::bus_id.c_str())
+        .def_readonly("target_p_mw", &DataLoad::LoadInfo::target_p_mw, DocIterator::target_p_mw.c_str())
+        .def_readonly("target_q_mvar", &DataLoad::LoadInfo::target_q_mvar, DocIterator::target_q_mvar.c_str())
+        .def_readonly("has_res", &DataLoad::LoadInfo::has_res, DocIterator::has_res.c_str())
+        .def_readonly("res_p_mw", &DataLoad::LoadInfo::res_p_mw, DocIterator::res_p_mw.c_str())
+        .def_readonly("res_q_mvar", &DataLoad::LoadInfo::res_q_mvar, DocIterator::res_q_mvar.c_str())
+        .def_readonly("res_theta_deg", &DataLoad::LoadInfo::res_theta_deg, DocIterator::res_theta_deg.c_str())
+        .def_readonly("res_v_kv", &DataLoad::LoadInfo::res_v_kv, DocIterator::res_v_kv.c_str());
+
+    // iterator for shunts
+    py::class_<DataShunt>(m, "DataShunt", DocIterator::DataShunt.c_str())
+        .def("__len__", [](const DataShunt & data) { return data.nb(); })
+        .def("__getitem__", [](const DataShunt & data, int k){return data[k]; } )
+        .def("__iter__", [](const DataShunt & data) {
+       return py::make_iterator(data.begin(), data.end());
+    }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
+
+    py::class_<DataShunt::ShuntInfo>(m, "ShuntInfo", DocIterator::ShuntInfo.c_str())
+        .def_readonly("id", &DataShunt::ShuntInfo::id, DocIterator::id.c_str())
+        .def_readonly("connected", &DataShunt::ShuntInfo::connected, DocIterator::connected.c_str())
+        .def_readonly("bus_id", &DataShunt::ShuntInfo::bus_id, DocIterator::bus_id.c_str())
+        .def_readonly("target_p_mw", &DataShunt::ShuntInfo::target_p_mw, DocIterator::target_p_mw.c_str())
+        .def_readonly("target_q_mvar", &DataShunt::ShuntInfo::target_q_mvar, DocIterator::target_q_mvar.c_str())
+        .def_readonly("has_res", &DataShunt::ShuntInfo::has_res, DocIterator::has_res.c_str())
+        .def_readonly("res_p_mw", &DataShunt::ShuntInfo::res_p_mw, DocIterator::res_p_mw.c_str())
+        .def_readonly("res_q_mvar", &DataShunt::ShuntInfo::res_q_mvar, DocIterator::res_q_mvar.c_str())
+        .def_readonly("res_theta_deg", &DataShunt::ShuntInfo::res_theta_deg, DocIterator::res_theta_deg.c_str())
+        .def_readonly("res_v_kv", &DataShunt::ShuntInfo::res_v_kv, DocIterator::res_v_kv.c_str());
 
     // iterator for trafos
-    py::class_<DataTrafo>(m, "DataTrafo")
+    py::class_<DataTrafo>(m, "DataTrafo", DocIterator::DataTrafo.c_str())
         .def("__len__", [](const DataTrafo & data) { return data.nb(); })
         .def("__getitem__", [](const DataTrafo & data, int k){return data[k]; } )
         .def("__iter__", [](const DataTrafo & data) {
        return py::make_iterator(data.begin(), data.end());
     }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
 
-    py::class_<DataTrafo::TrafoInfo>(m, "TrafoInfo")
-        .def_readonly("id", &DataTrafo::TrafoInfo::id)
-        .def_readonly("connected", &DataTrafo::TrafoInfo::connected)
-        .def_readonly("bus_hv_id", &DataTrafo::TrafoInfo::bus_hv_id)
-        .def_readonly("bus_lv_id", &DataTrafo::TrafoInfo::bus_lv_id)
-        .def_readonly("r_pu", &DataTrafo::TrafoInfo::r_pu)
-        .def_readonly("x_pu", &DataTrafo::TrafoInfo::x_pu)
-        .def_readonly("h_pu", &DataTrafo::TrafoInfo::h_pu)
-        .def_readonly("is_tap_hv_side", &DataTrafo::TrafoInfo::is_tap_hv_side)
-        .def_readonly("ratio", &DataTrafo::TrafoInfo::ratio)
-        .def_readonly("shift_rad", &DataTrafo::TrafoInfo::shift_rad)
-        .def_readonly("has_res", &DataTrafo::TrafoInfo::has_res)
-        .def_readonly("res_p_hv_mw", &DataTrafo::TrafoInfo::res_p_hv_mw)
-        .def_readonly("res_q_hv_mvar", &DataTrafo::TrafoInfo::res_q_hv_mvar)
-        .def_readonly("res_v_hv_kv", &DataTrafo::TrafoInfo::res_v_hv_kv)
-        .def_readonly("res_a_hv_ka", &DataTrafo::TrafoInfo::res_a_hv_ka)
-        .def_readonly("res_p_lv_mw", &DataTrafo::TrafoInfo::res_p_lv_mw)
-        .def_readonly("res_q_lv_mvar", &DataTrafo::TrafoInfo::res_q_lv_mvar)
-        .def_readonly("res_v_lv_kv", &DataTrafo::TrafoInfo::res_v_lv_kv)
-        .def_readonly("res_a_lv_ka", &DataTrafo::TrafoInfo::res_a_lv_ka)
-        .def_readonly("res_theta_hv_deg", &DataTrafo::TrafoInfo::res_theta_hv_deg)
-        .def_readonly("res_theta_lv_deg", &DataTrafo::TrafoInfo::res_theta_lv_deg);
+    py::class_<DataTrafo::TrafoInfo>(m, "TrafoInfo", DocIterator::TrafoInfo.c_str())
+        .def_readonly("id", &DataTrafo::TrafoInfo::id, DocIterator::id.c_str())
+        .def_readonly("connected", &DataTrafo::TrafoInfo::connected, DocIterator::connected.c_str())
+        .def_readonly("bus_hv_id", &DataTrafo::TrafoInfo::bus_hv_id, DocIterator::bus_hv_id.c_str())
+        .def_readonly("bus_lv_id", &DataTrafo::TrafoInfo::bus_lv_id, DocIterator::bus_lv_id.c_str())
+        .def_readonly("r_pu", &DataTrafo::TrafoInfo::r_pu, DocIterator::r_pu.c_str())
+        .def_readonly("x_pu", &DataTrafo::TrafoInfo::x_pu, DocIterator::x_pu.c_str())
+        .def_readonly("h_pu", &DataTrafo::TrafoInfo::h_pu, DocIterator::h_pu.c_str())
+        .def_readonly("is_tap_hv_side", &DataTrafo::TrafoInfo::is_tap_hv_side, DocIterator::is_tap_hv_side.c_str())
+        .def_readonly("ratio", &DataTrafo::TrafoInfo::ratio, DocIterator::ratio.c_str())
+        .def_readonly("shift_rad", &DataTrafo::TrafoInfo::shift_rad, DocIterator::shift_rad.c_str())
+        .def_readonly("has_res", &DataTrafo::TrafoInfo::has_res, DocIterator::has_res.c_str())
+        .def_readonly("res_p_hv_mw", &DataTrafo::TrafoInfo::res_p_hv_mw, DocIterator::res_p_hv_mw.c_str())
+        .def_readonly("res_q_hv_mvar", &DataTrafo::TrafoInfo::res_q_hv_mvar, DocIterator::res_q_hv_mvar.c_str())
+        .def_readonly("res_v_hv_kv", &DataTrafo::TrafoInfo::res_v_hv_kv, DocIterator::res_v_hv_kv.c_str())
+        .def_readonly("res_a_hv_ka", &DataTrafo::TrafoInfo::res_a_hv_ka, DocIterator::res_a_hv_ka.c_str())
+        .def_readonly("res_p_lv_mw", &DataTrafo::TrafoInfo::res_p_lv_mw, DocIterator::res_p_lv_mw.c_str())
+        .def_readonly("res_q_lv_mvar", &DataTrafo::TrafoInfo::res_q_lv_mvar, DocIterator::res_q_lv_mvar.c_str())
+        .def_readonly("res_v_lv_kv", &DataTrafo::TrafoInfo::res_v_lv_kv, DocIterator::res_v_lv_kv.c_str())
+        .def_readonly("res_a_lv_ka", &DataTrafo::TrafoInfo::res_a_lv_ka, DocIterator::res_a_lv_ka.c_str())
+        .def_readonly("res_theta_hv_deg", &DataTrafo::TrafoInfo::res_theta_hv_deg, DocIterator::res_theta_hv_deg.c_str())
+        .def_readonly("res_theta_lv_deg", &DataTrafo::TrafoInfo::res_theta_lv_deg, DocIterator::res_theta_lv_deg.c_str());
 
     // iterator for trafos
-    py::class_<DataLine>(m, "DataLine")
+    py::class_<DataLine>(m, "DataLine", DocIterator::DataLine.c_str())
         .def("__len__", [](const DataLine & data) { return data.nb(); })
         .def("__getitem__", [](const DataLine & data, int k){return data[k]; } )
         .def("__iter__", [](const DataLine & data) {
        return py::make_iterator(data.begin(), data.end());
     }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
 
-    py::class_<DataLine::LineInfo>(m, "LineInfo")
-        .def_readonly("id", &DataLine::LineInfo::id)
-        .def_readonly("connected", &DataLine::LineInfo::connected)
-        .def_readonly("bus_or_id", &DataLine::LineInfo::bus_or_id)
-        .def_readonly("bus_ex_id", &DataLine::LineInfo::bus_ex_id)
-        .def_readonly("r_pu", &DataLine::LineInfo::r_pu)
-        .def_readonly("x_pu", &DataLine::LineInfo::x_pu)
-        .def_readonly("h_pu", &DataLine::LineInfo::h_pu)
-        .def_readonly("has_res", &DataLine::LineInfo::has_res)
-        .def_readonly("res_p_or_mw", &DataLine::LineInfo::res_p_or_mw)
-        .def_readonly("res_q_or_mvar", &DataLine::LineInfo::res_q_or_mvar)
-        .def_readonly("res_v_or_kv", &DataLine::LineInfo::res_v_or_kv)
-        .def_readonly("res_a_or_ka", &DataLine::LineInfo::res_a_or_ka)
-        .def_readonly("res_p_ex_mw", &DataLine::LineInfo::res_p_ex_mw)
-        .def_readonly("res_q_ex_mvar", &DataLine::LineInfo::res_q_ex_mvar)
-        .def_readonly("res_v_ex_kv", &DataLine::LineInfo::res_v_ex_kv)
-        .def_readonly("res_a_ex_ka", &DataLine::LineInfo::res_a_ex_ka)
-        .def_readonly("res_theta_or_deg", &DataLine::LineInfo::res_theta_or_deg)
-        .def_readonly("res_theta_ex_deg", &DataLine::LineInfo::res_theta_ex_deg);
+    py::class_<DataLine::LineInfo>(m, "LineInfo", DocIterator::LineInfo.c_str())
+        .def_readonly("id", &DataLine::LineInfo::id, DocIterator::id.c_str())
+        .def_readonly("connected", &DataLine::LineInfo::connected, DocIterator::connected.c_str())
+        .def_readonly("bus_or_id", &DataLine::LineInfo::bus_or_id, DocIterator::bus_or_id.c_str())
+        .def_readonly("bus_ex_id", &DataLine::LineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str())
+        .def_readonly("r_pu", &DataLine::LineInfo::r_pu, DocIterator::r_pu.c_str())
+        .def_readonly("x_pu", &DataLine::LineInfo::x_pu, DocIterator::x_pu.c_str())
+        .def_readonly("h_pu", &DataLine::LineInfo::h_pu, DocIterator::h_pu.c_str())
+        .def_readonly("has_res", &DataLine::LineInfo::has_res, DocIterator::has_res.c_str())
+        .def_readonly("res_p_or_mw", &DataLine::LineInfo::res_p_or_mw, DocIterator::res_p_or_mw.c_str())
+        .def_readonly("res_q_or_mvar", &DataLine::LineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar.c_str())
+        .def_readonly("res_v_or_kv", &DataLine::LineInfo::res_v_or_kv, DocIterator::res_v_or_kv.c_str())
+        .def_readonly("res_a_or_ka", &DataLine::LineInfo::res_a_or_ka, DocIterator::res_a_or_ka.c_str())
+        .def_readonly("res_p_ex_mw", &DataLine::LineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw.c_str())
+        .def_readonly("res_q_ex_mvar", &DataLine::LineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar.c_str())
+        .def_readonly("res_v_ex_kv", &DataLine::LineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv.c_str())
+        .def_readonly("res_a_ex_ka", &DataLine::LineInfo::res_a_ex_ka, DocIterator::res_a_ex_ka.c_str())
+        .def_readonly("res_theta_or_deg", &DataLine::LineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg.c_str())
+        .def_readonly("res_theta_ex_deg", &DataLine::LineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg.c_str());
 
     // converters
     py::class_<PandaPowerConverter>(m, "PandaPowerConverter")
@@ -198,7 +377,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
         .def("get_line_param", &PandaPowerConverter::get_line_param)
         .def("get_trafo_param", &PandaPowerConverter::get_trafo_param);
 
-    py::class_<GridModel>(m, "GridModel")
+    py::class_<GridModel>(m, "GridModel", DocGridModel::GridModel.c_str())
         .def(py::init<>())
         .def("copy", &GridModel::copy)
 
@@ -225,230 +404,250 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
 
         // general parameters
         // solver control
-        .def("change_solver", &GridModel::change_solver)  // change the solver to use (KLU - faster or SparseLU - available everywhere)
-        .def("available_solvers", &GridModel::available_solvers)  // retrieve the solver available for your installation
-        .def("get_computation_time", &GridModel::get_computation_time)  // get the computation time spent in the solver
-        .def("get_solver_type", &GridModel::get_solver_type)  // get the type of solver used
+        .def("change_solver", &GridModel::change_solver, DocGridModel::change_solver.c_str())
+        .def("available_solvers", &GridModel::available_solvers, DocGridModel::available_solvers.c_str())  // retrieve the solver available for your installation
+        .def("get_computation_time", &GridModel::get_computation_time, DocGridModel::get_computation_time.c_str())  // get the computation time spent in the solver
+        .def("get_dc_computation_time", &GridModel::get_dc_computation_time, DocGridModel::get_dc_computation_time.c_str())  // get the computation time spent in the solver
+        .def("get_solver_type", &GridModel::get_solver_type, DocGridModel::get_solver_type.c_str())  // get the type of solver used
+        .def("get_dc_solver_type", &GridModel::get_dc_solver_type, DocGridModel::get_dc_solver_type.c_str())  // get the type of solver used
+        .def("get_solver", &GridModel::get_solver, py::return_value_policy::reference, DocGridModel::get_solver.c_str())  // get the solver (AnySolver type python side) used
+        .def("get_dc_solver", &GridModel::get_dc_solver, py::return_value_policy::reference, DocGridModel::get_dc_solver.c_str())  // get the solver (AnySolver type python side) used
 
         // init the grid
-        .def("init_bus", &GridModel::init_bus)
-        .def("set_init_vm_pu", &GridModel::set_init_vm_pu)  // TODO use python "property" for that
-        .def("get_init_vm_pu", &GridModel::get_init_vm_pu)
-        .def("set_sn_mva", &GridModel::set_sn_mva)
-        .def("get_sn_mva", &GridModel::get_sn_mva)
-        .def("init_powerlines", &GridModel::init_powerlines)
-        .def("init_shunt", &GridModel::init_shunt)
-        .def("init_trafo", &GridModel::init_trafo)
-        .def("init_generators", &GridModel::init_generators)
-        .def("init_loads", &GridModel::init_loads)
-        .def("init_storages", &GridModel::init_storages)
-        .def("init_sgens", &GridModel::init_sgens)
-        .def("add_gen_slackbus", &GridModel::add_gen_slackbus)
+        .def("init_bus", &GridModel::init_bus, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_init_vm_pu", &GridModel::set_init_vm_pu, DocGridModel::_internal_do_not_use.c_str())  // TODO use python "property" for that
+        .def("get_init_vm_pu", &GridModel::get_init_vm_pu, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_sn_mva", &GridModel::set_sn_mva, DocGridModel::_internal_do_not_use.c_str())   // TODO use python "property" for that
+        .def("get_sn_mva", &GridModel::get_sn_mva, DocGridModel::_internal_do_not_use.c_str())
+
+        // init its elements
+        .def("init_powerlines", &GridModel::init_powerlines, DocGridModel::_internal_do_not_use.c_str())  // TODO code the possibility to add / remove a powerline after creation
+        .def("init_shunt", &GridModel::init_shunt, DocGridModel::_internal_do_not_use.c_str())  // same
+        .def("init_trafo", &GridModel::init_trafo, DocGridModel::_internal_do_not_use.c_str())  // same 
+        .def("init_generators", &GridModel::init_generators, DocGridModel::_internal_do_not_use.c_str())  // same
+        .def("init_loads", &GridModel::init_loads, DocGridModel::_internal_do_not_use.c_str())  // same
+        .def("init_storages", &GridModel::init_storages, DocGridModel::_internal_do_not_use.c_str())  // same
+        .def("init_sgens", &GridModel::init_sgens, DocGridModel::_internal_do_not_use.c_str())  // same
+        .def("add_gen_slackbus", &GridModel::add_gen_slackbus, DocGridModel::_internal_do_not_use.c_str()) // same
+        .def("remove_gen_slackbus", &GridModel::remove_gen_slackbus, DocGridModel::_internal_do_not_use.c_str())  // same
+
+        // inspect the grid
+        .def("get_lines", &GridModel::get_lines, DocGridModel::get_lines.c_str())
+        .def("get_trafos", &GridModel::get_trafos, DocGridModel::get_trafos.c_str())
+        .def("get_generators", &GridModel::get_generators, DocGridModel::get_generators.c_str())
+        .def("get_static_generators", &GridModel::get_static_generators, DocGridModel::get_static_generators.c_str())
+        .def("get_shunts", &GridModel::get_shunts, DocGridModel::get_shunts.c_str())
+        .def("get_storages", &GridModel::get_storages, DocGridModel::get_storages.c_str())
+        .def("get_loads", &GridModel::get_loads, DocGridModel::get_loads.c_str())
 
         // modify the grid
-        .def("deactivate_bus", &GridModel::deactivate_bus)
-        .def("reactivate_bus", &GridModel::reactivate_bus)
-        .def("nb_bus", &GridModel::nb_bus)
-
-        .def("deactivate_powerline", &GridModel::deactivate_powerline)
-        .def("reactivate_powerline", &GridModel::reactivate_powerline)
-        .def("change_bus_powerline_or", &GridModel::change_bus_powerline_or)
-        .def("change_bus_powerline_ex", &GridModel::change_bus_powerline_ex)
-        .def("get_bus_powerline_or", &GridModel::get_bus_powerline_or)
-        .def("get_bus_powerline_ex", &GridModel::get_bus_powerline_ex)
-        .def("get_lines", &GridModel::get_lines)
-
-        .def("deactivate_trafo", &GridModel::deactivate_trafo)
-        .def("reactivate_trafo", &GridModel::reactivate_trafo)
-        .def("change_bus_trafo_hv", &GridModel::change_bus_trafo_hv)
-        .def("change_bus_trafo_lv", &GridModel::change_bus_trafo_lv)
-        .def("get_bus_trafo_hv", &GridModel::get_bus_trafo_hv)
-        .def("get_bus_trafo_lv", &GridModel::get_bus_trafo_lv)
-        .def("get_trafos", &GridModel::get_trafos)
-
-        .def("deactivate_load", &GridModel::deactivate_load)
-        .def("reactivate_load", &GridModel::reactivate_load)
-        .def("change_bus_load", &GridModel::change_bus_load)
-        .def("get_bus_load", &GridModel::get_bus_load)
-        .def("change_p_load", &GridModel::change_p_load)
-        .def("change_q_load", &GridModel::change_q_load)
-
-        .def("deactivate_gen", &GridModel::deactivate_gen)
-        .def("reactivate_gen", &GridModel::reactivate_gen)
-        .def("change_bus_gen", &GridModel::change_bus_gen)
-        .def("get_bus_gen", &GridModel::get_bus_gen)
-        .def("change_p_gen", &GridModel::change_p_gen)
-        .def("change_v_gen", &GridModel::change_v_gen)
-        .def("get_generators", &GridModel::get_generators)
-
-        .def("deactivate_shunt", &GridModel::deactivate_shunt)
-        .def("reactivate_shunt", &GridModel::reactivate_shunt)
-        .def("change_bus_shunt", &GridModel::change_bus_shunt)
-        .def("get_bus_shunt", &GridModel::get_bus_shunt)
-        .def("change_p_shunt", &GridModel::change_p_shunt)
-        .def("change_q_shunt", &GridModel::change_q_shunt)
-
-        .def("deactivate_sgen", &GridModel::deactivate_sgen)
-        .def("reactivate_sgen", &GridModel::reactivate_sgen)
-        .def("change_bus_sgen", &GridModel::change_bus_sgen)
-        .def("get_bus_sgen", &GridModel::get_bus_sgen)
-        .def("change_p_sgen", &GridModel::change_p_sgen)
-        .def("change_q_sgen", &GridModel::change_q_sgen)
-
-        .def("deactivate_storage", &GridModel::deactivate_storage)
-        .def("reactivate_storage", &GridModel::reactivate_storage)
-        .def("change_bus_storage", &GridModel::change_bus_storage)
-        .def("get_bus_storage", &GridModel::get_bus_storage)
-        .def("change_p_storage", &GridModel::change_p_storage)
-        .def("change_q_storage", &GridModel::change_q_storage)
+        .def("deactivate_bus", &GridModel::deactivate_bus, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_bus", &GridModel::reactivate_bus, DocGridModel::_internal_do_not_use.c_str())
+
+        .def("deactivate_powerline", &GridModel::deactivate_powerline, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_powerline", &GridModel::reactivate_powerline, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_powerline_or", &GridModel::change_bus_powerline_or, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_powerline_ex", &GridModel::change_bus_powerline_ex, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_powerline_or", &GridModel::get_bus_powerline_or, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_powerline_ex", &GridModel::get_bus_powerline_ex, DocGridModel::_internal_do_not_use.c_str())
+
+        .def("deactivate_trafo", &GridModel::deactivate_trafo, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_trafo", &GridModel::reactivate_trafo, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_trafo_hv", &GridModel::change_bus_trafo_hv, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_trafo_lv", &GridModel::change_bus_trafo_lv, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_trafo_hv", &GridModel::get_bus_trafo_hv, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_trafo_lv", &GridModel::get_bus_trafo_lv, DocGridModel::_internal_do_not_use.c_str())
+
+        .def("deactivate_load", &GridModel::deactivate_load, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_load", &GridModel::reactivate_load, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_load", &GridModel::change_bus_load, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_load", &GridModel::get_bus_load, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_p_load", &GridModel::change_p_load, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_q_load", &GridModel::change_q_load, DocGridModel::_internal_do_not_use.c_str())
+
+        .def("deactivate_gen", &GridModel::deactivate_gen, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_gen", &GridModel::reactivate_gen, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_gen", &GridModel::change_bus_gen, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_gen", &GridModel::get_bus_gen, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_p_gen", &GridModel::change_p_gen, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_v_gen", &GridModel::change_v_gen, DocGridModel::_internal_do_not_use.c_str())
+
+        .def("deactivate_shunt", &GridModel::deactivate_shunt, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_shunt", &GridModel::reactivate_shunt, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_shunt", &GridModel::change_bus_shunt, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_shunt", &GridModel::get_bus_shunt, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_p_shunt", &GridModel::change_p_shunt, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_q_shunt", &GridModel::change_q_shunt, DocGridModel::_internal_do_not_use.c_str())
+
+        .def("deactivate_sgen", &GridModel::deactivate_sgen, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_sgen", &GridModel::reactivate_sgen, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_sgen", &GridModel::change_bus_sgen, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_sgen", &GridModel::get_bus_sgen, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_p_sgen", &GridModel::change_p_sgen, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_q_sgen", &GridModel::change_q_sgen, DocGridModel::_internal_do_not_use.c_str())
+
+        .def("deactivate_storage", &GridModel::deactivate_storage, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_storage", &GridModel::reactivate_storage, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_storage", &GridModel::change_bus_storage, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_storage", &GridModel::get_bus_storage, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_p_storage", &GridModel::change_p_storage, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_q_storage", &GridModel::change_q_storage, DocGridModel::_internal_do_not_use.c_str())
 
         // get back the results
-        .def("get_V", &GridModel::get_V)
-        .def("get_Va", &GridModel::get_Va)
-        .def("get_Vm", &GridModel::get_Vm)
-        .def("get_J", &GridModel::get_J)
-        .def("check_solution", &GridModel::check_solution)
+        .def("get_V", &GridModel::get_V, DocGridModel::get_V.c_str())
+        .def("get_Va", &GridModel::get_Va, DocGridModel::get_Va.c_str())
+        .def("get_Vm", &GridModel::get_Vm, DocGridModel::get_Vm.c_str())
+        .def("get_J", &GridModel::get_J_python, DocGridModel::get_J_python.c_str())
+        .def("id_me_to_ac_solver", &GridModel::id_me_to_ac_solver, DocGridModel::id_me_to_ac_solver.c_str())
+        .def("id_ac_solver_to_me", &GridModel::id_ac_solver_to_me, DocGridModel::id_ac_solver_to_me.c_str())
+        .def("id_me_to_dc_solver", &GridModel::id_me_to_dc_solver, DocGridModel::id_me_to_dc_solver.c_str())
+        .def("id_dc_solver_to_me", &GridModel::id_dc_solver_to_me, DocGridModel::id_dc_solver_to_me.c_str())
+        .def("total_bus", &GridModel::total_bus, DocGridModel::total_bus.c_str())
+        .def("nb_bus", &GridModel::nb_bus, DocGridModel::nb_bus.c_str())
+        .def("get_pv", &GridModel::get_pv, DocGridModel::get_pv.c_str())
+        .def("get_pq", &GridModel::get_pq, DocGridModel::get_pq.c_str())
+        .def("get_slack_ids", &GridModel::get_slack_ids, DocGridModel::get_slack_ids.c_str())
+        .def("get_slack_weights", &GridModel::get_slack_weights, DocGridModel::get_slack_weights.c_str())
+        .def("get_Ybus", &GridModel::get_Ybus, DocGridModel::get_Ybus.c_str())
+        .def("get_dcYbus", &GridModel::get_dcYbus, DocGridModel::get_dcYbus.c_str())
+        .def("get_Sbus", &GridModel::get_Sbus, DocGridModel::get_Sbus.c_str())
+
+        .def("check_solution", &GridModel::check_solution, DocGridModel::check_solution.c_str())
 
         // TODO optimize that for speed, results are copied apparently
-        .def("get_loads_res", &GridModel::get_loads_res)
-        .def("get_loads_status", &GridModel::get_loads_status)
-        .def("get_shunts_res", &GridModel::get_shunts_res)
-        .def("get_shunts_status", &GridModel::get_shunts_status)
-        .def("get_gen_res", &GridModel::get_gen_res)
-        .def("get_gen_status", &GridModel::get_gen_status)
-        .def("get_lineor_res", &GridModel::get_lineor_res)
-        .def("get_lineex_res", &GridModel::get_lineex_res)
-        .def("get_lines_status", &GridModel::get_lines_status)
-        .def("get_trafohv_res", &GridModel::get_trafohv_res)
-        .def("get_trafolv_res", &GridModel::get_trafolv_res)
-        .def("get_trafo_status", &GridModel::get_trafo_status)
-        .def("get_storages_res", &GridModel::get_storages_res)
-        .def("get_storages_status", &GridModel::get_storages_status)
-        .def("get_sgens_res", &GridModel::get_sgens_res)
-        .def("get_sgens_status", &GridModel::get_sgens_status)
-
-        .def("get_gen_theta", &GridModel::get_gen_theta)
-        .def("get_load_theta", &GridModel::get_load_theta)
-        .def("get_shunt_theta", &GridModel::get_shunt_theta)
-        .def("get_storage_theta", &GridModel::get_storage_theta)
-        .def("get_lineor_theta", &GridModel::get_lineor_theta)
-        .def("get_lineex_theta", &GridModel::get_lineex_theta)
-        .def("get_trafohv_theta", &GridModel::get_trafohv_theta)
-        .def("get_trafolv_theta", &GridModel::get_trafolv_theta)
+        .def("get_loads_res", &GridModel::get_loads_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_loads_status", &GridModel::get_loads_status, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_shunts_res", &GridModel::get_shunts_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_shunts_status", &GridModel::get_shunts_status, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_gen_res", &GridModel::get_gen_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_gen_status", &GridModel::get_gen_status, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_lineor_res", &GridModel::get_lineor_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_lineex_res", &GridModel::get_lineex_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_lines_status", &GridModel::get_lines_status, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_trafohv_res", &GridModel::get_trafohv_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_trafolv_res", &GridModel::get_trafolv_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_trafo_status", &GridModel::get_trafo_status, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_storages_res", &GridModel::get_storages_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_storages_status", &GridModel::get_storages_status, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_sgens_res", &GridModel::get_sgens_res, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_sgens_status", &GridModel::get_sgens_status, DocGridModel::_internal_do_not_use.c_str())
+
+        .def("get_gen_theta", &GridModel::get_gen_theta, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_load_theta", &GridModel::get_load_theta, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_shunt_theta", &GridModel::get_shunt_theta, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_storage_theta", &GridModel::get_storage_theta, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_lineor_theta", &GridModel::get_lineor_theta, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_lineex_theta", &GridModel::get_lineex_theta, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_trafohv_theta", &GridModel::get_trafohv_theta, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_trafolv_theta", &GridModel::get_trafolv_theta, DocGridModel::_internal_do_not_use.c_str())
 
         // do something with the grid
         // .def("init_Ybus", &DataModel::init_Ybus) // temporary
-        .def("get_Ybus", &GridModel::get_Ybus)
-        .def("get_Sbus", &GridModel::get_Sbus)
-        .def("get_pv", &GridModel::get_pv)
-        .def("get_pq", &GridModel::get_pq)
-
-        .def("deactivate_result_computation", &GridModel::deactivate_result_computation)
-        .def("reactivate_result_computation", &GridModel::reactivate_result_computation)
-        .def("dc_pf", &GridModel::dc_pf)
-        .def("dc_pf_old", &GridModel::dc_pf_old)
-        .def("ac_pf", &GridModel::ac_pf)
-        .def("unset_topo_changed", &GridModel::unset_topo_changed)
-        .def("tell_topo_changed", &GridModel::tell_topo_changed)
-        .def("compute_newton", &GridModel::ac_pf)
+        .def("deactivate_result_computation", &GridModel::deactivate_result_computation, DocGridModel::deactivate_result_computation.c_str())
+        .def("reactivate_result_computation", &GridModel::reactivate_result_computation, DocGridModel::reactivate_result_computation.c_str())
+        .def("dc_pf", &GridModel::dc_pf, DocGridModel::dc_pf.c_str())
+        .def("ac_pf", &GridModel::ac_pf, DocGridModel::ac_pf.c_str())
+        .def("unset_topo_changed", &GridModel::unset_topo_changed, DocGridModel::_internal_do_not_use.c_str())
+        .def("tell_topo_changed", &GridModel::tell_topo_changed, DocGridModel::_internal_do_not_use.c_str())
+        .def("compute_newton", &GridModel::ac_pf, DocGridModel::ac_pf.c_str())
 
          // apply action faster (optimized for grid2op representation)
          // it is not recommended to use it outside of grid2Op.
-        .def("update_bus_status", &GridModel::update_bus_status)
-        .def("update_gens_p", &GridModel::update_gens_p)
-        .def("update_gens_v", &GridModel::update_gens_v)
-        .def("update_loads_p", &GridModel::update_loads_p)
-        .def("update_loads_q", &GridModel::update_loads_q)
-        .def("update_topo", &GridModel::update_topo)
-        .def("update_storages_p", &GridModel::update_storages_p)
+        .def("update_bus_status", &GridModel::update_bus_status, DocGridModel::_internal_do_not_use.c_str())
+        .def("update_gens_p", &GridModel::update_gens_p, DocGridModel::_internal_do_not_use.c_str())
+        .def("update_gens_v", &GridModel::update_gens_v, DocGridModel::_internal_do_not_use.c_str())
+        .def("update_loads_p", &GridModel::update_loads_p, DocGridModel::_internal_do_not_use.c_str())
+        .def("update_loads_q", &GridModel::update_loads_q, DocGridModel::_internal_do_not_use.c_str())
+        .def("update_topo", &GridModel::update_topo, DocGridModel::_internal_do_not_use.c_str())
+        .def("update_storages_p", &GridModel::update_storages_p, DocGridModel::_internal_do_not_use.c_str())
 
         // auxiliary functions
-        .def("set_n_sub", &GridModel::set_n_sub)
-        .def("set_load_pos_topo_vect", &GridModel::set_load_pos_topo_vect)
-        .def("set_gen_pos_topo_vect", &GridModel::set_gen_pos_topo_vect)
-        .def("set_line_or_pos_topo_vect", &GridModel::set_line_or_pos_topo_vect)
-        .def("set_line_ex_pos_topo_vect", &GridModel::set_line_ex_pos_topo_vect)
-        .def("set_trafo_hv_pos_topo_vect", &GridModel::set_trafo_hv_pos_topo_vect)
-        .def("set_trafo_lv_pos_topo_vect", &GridModel::set_trafo_lv_pos_topo_vect)
-        .def("set_storage_pos_topo_vect", &GridModel::set_storage_pos_topo_vect)
-        .def("set_load_to_subid", &GridModel::set_load_to_subid)
-        .def("set_gen_to_subid", &GridModel::set_gen_to_subid)
-        .def("set_line_or_to_subid", &GridModel::set_line_or_to_subid)
-        .def("set_line_ex_to_subid", &GridModel::set_line_ex_to_subid)
-        .def("set_trafo_hv_to_subid", &GridModel::set_trafo_hv_to_subid)
-        .def("set_trafo_lv_to_subid", &GridModel::set_trafo_lv_to_subid)
-        .def("set_storage_to_subid", &GridModel::set_storage_to_subid)
+        .def("set_n_sub", &GridModel::set_n_sub, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_load_pos_topo_vect", &GridModel::set_load_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_gen_pos_topo_vect", &GridModel::set_gen_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_line_or_pos_topo_vect", &GridModel::set_line_or_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_line_ex_pos_topo_vect", &GridModel::set_line_ex_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_trafo_hv_pos_topo_vect", &GridModel::set_trafo_hv_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_trafo_lv_pos_topo_vect", &GridModel::set_trafo_lv_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_storage_pos_topo_vect", &GridModel::set_storage_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_load_to_subid", &GridModel::set_load_to_subid, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_gen_to_subid", &GridModel::set_gen_to_subid, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_line_or_to_subid", &GridModel::set_line_or_to_subid, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_line_ex_to_subid", &GridModel::set_line_ex_to_subid, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_trafo_hv_to_subid", &GridModel::set_trafo_hv_to_subid, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_trafo_lv_to_subid", &GridModel::set_trafo_lv_to_subid, DocGridModel::_internal_do_not_use.c_str())
+        .def("set_storage_to_subid", &GridModel::set_storage_to_subid, DocGridModel::_internal_do_not_use.c_str())
         ;
 
-    py::class_<Computers>(m, "Computers")
+    py::class_<Computers>(m, "Computers", DocComputers::Computers.c_str())
         .def(py::init<const GridModel &>())
 
         // solver control
-        .def("change_solver", &Computers::change_solver)
-        .def("available_solvers", &Computers::available_solvers)
-        .def("get_solver_type", &Computers::get_solver_type)
+        .def("change_solver", &Computers::change_solver, DocGridModel::change_solver.c_str())
+        .def("available_solvers", &Computers::available_solvers, DocGridModel::available_solvers.c_str())
+        .def("get_solver_type", &Computers::get_solver_type, DocGridModel::get_solver_type.c_str())
 
         // timers
-        .def("total_time", &Computers::total_time)
-        .def("solver_time", &Computers::solver_time)
-        .def("preprocessing_time", &Computers::preprocessing_time)
-        .def("amps_computation_time", &Computers::amps_computation_time)
-        .def("nb_solved", &Computers::nb_solved)
+        .def("total_time", &Computers::total_time, DocComputers::total_time.c_str())
+        .def("solver_time", &Computers::solver_time, DocComputers::solver_time.c_str())
+        .def("preprocessing_time", &Computers::preprocessing_time, DocComputers::preprocessing_time.c_str())
+        .def("amps_computation_time", &Computers::amps_computation_time, DocComputers::amps_computation_time.c_str())
+        .def("nb_solved", &Computers::nb_solved, DocComputers::nb_solved.c_str())
 
         // status
-        .def("get_status", &Computers::get_status)
-
-        // computation control
-        .def("deactivate_flow_computations", &Computers::deactivate_flow_computations)
-        .def("activate_flow_computations", &Computers::activate_flow_computations)
+        .def("get_status", &Computers::get_status, DocComputers::get_status.c_str())
 
         // perform the computations
-        .def("compute_Vs", &Computers::compute_Vs, py::call_guard<py::gil_scoped_release>())
-        .def("compute_flows", &Computers::compute_flows, py::call_guard<py::gil_scoped_release>()) // need to be done after compute_Vs
-
+        .def("compute_Vs", &Computers::compute_Vs, py::call_guard<py::gil_scoped_release>(), DocComputers::compute_Vs.c_str())
+        .def("compute_flows", &Computers::compute_flows, py::call_guard<py::gil_scoped_release>(), DocComputers::compute_flows.c_str())
+        .def("compute_power_flows", &Computers::compute_power_flows, DocComputers::compute_power_flows.c_str())  // need to be done after "compute_Vs"  and "compute_flows"
+        
         // results (for now only flow (at each -line origin- or voltages -at each buses)
-        .def("get_flows", &Computers::get_flows)  // need to be done after "compute_Vs"  and "compute_flows"
-        .def("get_voltages", &Computers::get_voltages)  // need to be done after "compute_Vs" 
-        .def("get_sbuses", &Computers::get_sbuses)  // need to be done after "compute_Vs" 
+        .def("get_flows", &Computers::get_flows, DocComputers::get_flows.c_str())  // need to be done after "compute_Vs"  and "compute_flows"
+        .def("get_power_flows", &Computers::get_power_flows, DocComputers::get_power_flows.c_str())  // need to be done after "compute_Vs"  and "compute_flows"
+        .def("get_voltages", &Computers::get_voltages, DocComputers::get_voltages.c_str())  // need to be done after "compute_Vs" 
+        .def("get_sbuses", &Computers::get_sbuses, DocComputers::get_sbuses.c_str())  // need to be done after "compute_Vs" 
         ;
 
-    py::class_<SecurityAnalysis>(m, "SecurityAnalysis")
+    py::class_<SecurityAnalysis>(m, "SecurityAnalysisCPP", DocSecurityAnalysis::SecurityAnalysis.c_str())
         .def(py::init<const GridModel &>())
         // solver control
-        .def("change_solver", &Computers::change_solver)
-        .def("available_solvers", &Computers::available_solvers)
-        .def("get_solver_type", &Computers::get_solver_type)
+        .def("change_solver", &Computers::change_solver, DocGridModel::change_solver.c_str())
+        .def("available_solvers", &Computers::available_solvers, DocGridModel::available_solvers.c_str())
+        .def("get_solver_type", &Computers::get_solver_type, DocGridModel::get_solver_type.c_str())
 
         // add some defaults
-        .def("add_all_n1", &SecurityAnalysis::add_all_n1)
-        .def("add_n1", &SecurityAnalysis::add_n1)
-        .def("add_nk", &SecurityAnalysis::add_nk)
-        .def("add_multiple_n1", &SecurityAnalysis::add_multiple_n1)
+        .def("add_all_n1", &SecurityAnalysis::add_all_n1, DocSecurityAnalysis::add_all_n1.c_str())
+        .def("add_n1", &SecurityAnalysis::add_n1, DocSecurityAnalysis::add_n1.c_str())
+        .def("add_nk", &SecurityAnalysis::add_nk, DocSecurityAnalysis::add_nk.c_str())
+        .def("add_multiple_n1", &SecurityAnalysis::add_multiple_n1, DocSecurityAnalysis::add_multiple_n1.c_str())
 
         // remove some defaults (TODO)
-        .def("reset", &SecurityAnalysis::clear)
-        .def("clear", &SecurityAnalysis::clear)
-        .def("remove_n1", &SecurityAnalysis::remove_n1)
-        .def("remove_nk", &SecurityAnalysis::remove_nk)
-        .def("remove_multiple_n1", &SecurityAnalysis::remove_multiple_n1)
+        .def("reset", &SecurityAnalysis::clear, DocSecurityAnalysis::clear.c_str())
+        .def("clear", &SecurityAnalysis::clear, DocSecurityAnalysis::clear.c_str())
+        .def("remove_n1", &SecurityAnalysis::remove_n1, DocSecurityAnalysis::remove_n1.c_str())
+        .def("remove_nk", &SecurityAnalysis::remove_nk, DocSecurityAnalysis::remove_nk.c_str())
+        .def("remove_multiple_n1", &SecurityAnalysis::remove_multiple_n1, DocSecurityAnalysis::remove_multiple_n1.c_str())
         
         // inspect the class
-        .def("my_defaults", &SecurityAnalysis::my_defaults_vect)
+        .def("my_defaults", &SecurityAnalysis::my_defaults_vect, DocSecurityAnalysis::my_defaults_vect.c_str())
 
         // perform the computation
-        .def("compute", &SecurityAnalysis::compute, py::call_guard<py::gil_scoped_release>())
+        .def("compute", &SecurityAnalysis::compute, py::call_guard<py::gil_scoped_release>(), DocSecurityAnalysis::compute.c_str())
+        .def("compute_flows", &SecurityAnalysis::compute_flows, py::call_guard<py::gil_scoped_release>(), DocSecurityAnalysis::compute_flows.c_str())
+        .def("compute_power_flows", &SecurityAnalysis::compute_power_flows, DocSecurityAnalysis::compute_power_flows.c_str())
 
         // results (for now only flow (at each -line origin- or voltages -at each buses)
-        .def("get_flows", &SecurityAnalysis::get_flows)  // need to be done after "compute" and "compute_flows"
-        .def("get_voltages", &SecurityAnalysis::get_voltages) // need to be done after "compute"
-        .def("compute_flows", &SecurityAnalysis::compute_flows, py::call_guard<py::gil_scoped_release>())  // need to be done after "compute"
+        .def("get_flows", &SecurityAnalysis::get_flows, DocSecurityAnalysis::get_flows.c_str())
+        .def("get_voltages", &SecurityAnalysis::get_voltages, DocSecurityAnalysis::get_voltages.c_str())
+        .def("get_power_flows", &SecurityAnalysis::get_power_flows, DocSecurityAnalysis::get_power_flows.c_str())
 
         // timers
-        .def("total_time", &SecurityAnalysis::total_time)
-        .def("solver_time", &SecurityAnalysis::solver_time)
-        .def("preprocessing_time", &SecurityAnalysis::preprocessing_time)
-        .def("amps_computation_time", &SecurityAnalysis::amps_computation_time)
-        .def("modif_Ybus_time", &SecurityAnalysis::modif_Ybus_time)
-        .def("nb_solved", &SecurityAnalysis::nb_solved)
+        .def("total_time", &SecurityAnalysis::total_time, DocComputers::total_time.c_str())
+        .def("solver_time", &SecurityAnalysis::solver_time, DocComputers::solver_time.c_str())
+        .def("preprocessing_time", &SecurityAnalysis::preprocessing_time, DocSecurityAnalysis::preprocessing_time.c_str())
+        .def("amps_computation_time", &SecurityAnalysis::amps_computation_time, DocComputers::amps_computation_time.c_str())
+        .def("modif_Ybus_time", &SecurityAnalysis::modif_Ybus_time, DocSecurityAnalysis::modif_Ybus_time.c_str())
+        .def("nb_solved", &SecurityAnalysis::nb_solved, DocComputers::nb_solved.c_str())
         ;
 }


Test patch:
diff --git a/lightsim2grid/tests/test_Computers.py b/lightsim2grid/tests/test_Computers.py
index 74c98e0..15940a9 100644
--- a/lightsim2grid/tests/test_Computers.py
+++ b/lightsim2grid/tests/test_Computers.py
@@ -6,7 +6,6 @@
 # SPDX-License-Identifier: MPL-2.0
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
-from os import truncate
 import unittest
 import grid2op
 from grid2op.Parameters import Parameters
@@ -27,7 +26,7 @@ def test_basic(self):
         param.NO_OVERFLOW_DISCONNECTION = True
         with warnings.catch_warnings():
             warnings.filterwarnings("ignore")
-            env = grid2op.make(env_name, backend=LightSimBackend(), param=param, test=truncate)
+            env = grid2op.make(env_name, backend=LightSimBackend(), param=param, test=True)
 
         nb_bus = env.n_sub
         obs = env.reset()
@@ -73,7 +72,7 @@ def test_amps(self):
         param.NO_OVERFLOW_DISCONNECTION = True
         with warnings.catch_warnings():
             warnings.filterwarnings("ignore")
-            env = grid2op.make(env_name, backend=LightSimBackend(), param=param, test=truncate)
+            env = grid2op.make(env_name, backend=LightSimBackend(), param=param, test=True)
 
         nb_bus = env.n_sub
         obs = env.reset()
diff --git a/lightsim2grid/tests/test_DCPF.py b/lightsim2grid/tests/test_DCPF.py
index c49c82a..4570815 100644
--- a/lightsim2grid/tests/test_DCPF.py
+++ b/lightsim2grid/tests/test_DCPF.py
@@ -15,6 +15,7 @@
 import numpy as np
 from pandapower.std_types import parameter_from_std_type
 import scipy
+import pdb
 import warnings
 
 from lightsim2grid import LightSimBackend
@@ -25,19 +26,46 @@
     from lightsim2grid.solver import SparseLUSolver
     ClassSolver = SparseLUSolver
 
+import pdb
 TIMER_INFO = False  # do i print information regarding computation time
 
 
 class TestDCPF(unittest.TestCase):
     def setUp(self) -> None:
         self.tol = 1e-4  # results are equal if they match up to tol
-        self.tol_big = 0.1  # for P = C
+        self.tol_big = 0.01  # for P = C
 
     def test_case14(self):
         case = pn.case14()
         self.tol = 2e-3
         self._aux_test(case)
 
+    def test_case14_with_phaseshift(self):
+        case = pn.case14()
+        # case.trafo.iloc[[0]]["shift_degree"] = -5.0
+        
+        hv_bus=0
+        lv_bus=2
+        pp.create_transformer_from_parameters(case,
+                                              hv_bus=hv_bus,
+                                              lv_bus=lv_bus,
+                                            #   sn_mva=1184.0,   # case RTE
+                                              sn_mva=9900.0,
+                                              vn_hv_kv=case.bus.iloc[hv_bus]["vn_kv"],
+                                              vn_lv_kv=case.bus.iloc[lv_bus]["vn_kv"],
+                                            #   i0_percent=-0.05152,   # case RTE
+                                              i0_percent=0.0,
+                                            #   vk_percent=0.404445,  # case RTE
+                                              vk_percent=2070.288000,
+                                            #   vkr_percent=0.049728, # case RTE
+                                              vkr_percent=0.0,
+                                              shift_degree=-10.0,  # case RTE
+                                            #   shift_degree=0.,
+                                              pfe_kw=0.
+                                              )
+        self.tol = 2e-3
+        self._aux_test(case)
+
     def test_case39(self):
         case = pn.case39()
         self.tol = 3e-4
@@ -53,7 +81,7 @@ def test_case1888rte(self):
         self._aux_test(case)
 
     # def test_case300(self):
-    # TODO make it work
+    #     # issue with Bbus / Ybus matrix
     #     case = pn.case300()
     #     self._aux_test(case)
 
@@ -69,6 +97,7 @@ def test_case2848rte(self):
 
     def test_case6470rte(self):
         case = pn.case6470rte()
+        self.tol_big = 0.1  # for P = C
         self.tol = 1e-2
         self._aux_test(case)
 
@@ -79,6 +108,7 @@ def test_case6495rte(self):
 
     def test_case6515rte(self):
         case = pn.case6515rte()
+        self.tol_big = 0.1  # for P = C
         self.tol = 1e-2
         self._aux_test(case)
 
@@ -114,17 +144,49 @@ def _aux_test(self, pn_net):
         load_p_pp, load_q_pp, load_v_pp = copy.deepcopy(backend.init_pp_backend.loads_info())
         gen_p_pp, gen_q_pp, gen_v_pp = copy.deepcopy(backend.init_pp_backend.generators_info())
         sh_p_pp, sh_q_pp, sh_v_pp, *_ = copy.deepcopy(backend.init_pp_backend.shunt_info())
+        sgen_p_pp = copy.deepcopy(backend.init_pp_backend._grid.res_sgen["p_mw"].values)
+        init_gen_p = copy.deepcopy(backend.init_pp_backend._grid.gen["p_mw"].values)
+        init_load_p = copy.deepcopy(backend.init_pp_backend._grid.load["p_mw"].values)
+        init_sgen_p = copy.deepcopy(backend.init_pp_backend._grid.sgen["p_mw"].values)
 
         # I- Check for divergence and equality of flows"
         por_ls, qor_ls, vor_ls, aor_ls = backend.lines_or_info()
+        big_err_lid = np.where(np.abs(por_ls - por_pp) > 5000)[0]
+        backend.line_ex_to_subid[big_err_lid]
+        psub_ls, qsub_ls, pbus_ls, qbus_ls, diff_v_bus_ls = backend.check_kirchoff()
+        psub_pp, qsub_pp, pbus_pp, qbus_pp, diff_v_bus_pp = backend.init_pp_backend.check_kirchoff()
+        
+        # check voltages
+        line_or_theta_pp, line_ex_theta_pp, *_ = backend.init_pp_backend.get_theta()
+        line_or_theta_ls, line_ex_theta_ls, *_ = backend.get_theta()
+        assert np.all(np.abs(line_or_theta_ls - line_or_theta_pp) <= self.tol), "error in voltage angles (theta_or)"
+        assert np.all(np.abs(line_ex_theta_ls - line_ex_theta_pp) <= self.tol), "error in voltage angles (theta_ex)"
+        
         max_mis = np.max(np.abs(por_ls - por_pp))
+        # max_error_id = np.argmax(np.abs(por_ls - por_pp))
+        # nb_line = pp_net.line.shape[0]
+        # trafo_id = max_error_id - nb_line
+        # por_ls[max_error_id]
+        # np.abs(por_ls - por_pp)[25 + nb_line]
+        # backend._grid.get_trafos()[trafo_id]
+        # pp_net.trafo.iloc[trafo_id]
+        # check the Ybus for DC
+        Ybus =  backend._grid.get_dcYbus()
+        Bbus = pp_net._ppc["internal"]["Bbus"]
+        assert np.abs(Ybus - Bbus).max() <= self.tol
+        # check the voltage angles
+        Va_pp = pp_net.res_bus["va_degree"].values[:nb_sub]
+        Va_ls = np.rad2deg(np.angle(backend.V[:nb_sub]))
+
+        # check flows
+        assert np.abs(Va_pp - Va_ls).max() <= self.tol
         assert max_mis <= self.tol, f"Error: por do not match, maximum absolute error is {max_mis:.5f} MW"
         max_mis = np.max(np.abs(qor_ls - qor_pp))
         assert max_mis <= self.tol, f"Error: qor do not match, maximum absolute error is {max_mis:.5f} MVAr"
         max_mis = np.max(np.abs(vor_ls - vor_pp))
         assert max_mis <= self.tol, f"Error: vor do not match, maximum absolute error is {max_mis:.5f} kV"
         max_mis = np.max(np.abs(aor_ls - aor_pp))
-        assert max_mis <= self.tol, f"Error: aor do not match, maximum absolute error is {max_mis:.5f} A"
+        assert max_mis <= 10. * self.tol, f"Error: aor do not match, maximum absolute error is {max_mis:.5f} A"
         
         load_p, load_q, load_v = backend.loads_info()
         max_mis = np.max(np.abs(load_p - load_p_pp))
@@ -140,6 +202,7 @@ def _aux_test(self, pn_net):
         # max_mis = np.max(np.abs(gen_p - gen_p_pp))
         # assert max_mis <= self.tol, f"Error: gen_p do not match, maximum absolute error is {max_mis:.5f} MW"
         assert abs(np.sum(gen_p) + np.sum(sgen_p) - np.sum(load_p)) <= self.tol_big
+        # np.sum(gen_p_pp) + np.sum(sgen_p_pp) - np.sum(load_p_pp)
         # pandapower also does weird things in dc for gen_q... lightsim2grid puts everything at 0.
         # max_mis = np.max(np.abs(gen_q - gen_q_pp))
         # assert max_mis <= self.tol, f"Error: gen_q do not match, maximum absolute error is {max_mis:.5f} MVAr"
diff --git a/lightsim2grid/tests/test_DataConverter.py b/lightsim2grid/tests/test_DataConverter.py
index 790b963..0e6c5de 100644
--- a/lightsim2grid/tests/test_DataConverter.py
+++ b/lightsim2grid/tests/test_DataConverter.py
@@ -9,6 +9,7 @@
 import unittest
 import numpy as np
 import pdb
+from numpy.lib.arraysetops import isin
 import pandapower.networks as pn
 from lightsim2grid_cpp import PandaPowerConverter
 
@@ -18,9 +19,9 @@ def setUp(self):
         self.converter = PandaPowerConverter()
         self.tol = 1e-8
 
-    def assert_equal(self, tmp, ref):
-        assert np.max(np.abs(tmp - ref)) <= self.tol
-        assert np.sum(np.abs(tmp - ref)) <= tmp.shape[0] * self.tol
+    def assert_equal(self, tmp, ref, name=""):
+        assert np.max(np.abs(tmp - ref)) <= self.tol, f"maximum error {np.max(np.abs(tmp - ref))} {'for '+name if name else''}"
+        assert np.sum(np.abs(tmp - ref)) <= tmp.shape[0] * self.tol, f"average error {np.sum(np.abs(tmp - ref))}  {'for '+name if name else''}"
 
     def test_case6_data(self):
         net = pn.case6ww()
@@ -37,9 +38,12 @@ def test_case6_data(self):
         res_r = np.array([0.001, 0.0005, 0.001, 0.0008, 0.0005, 0.0005, 0.001, 0.0007, 0.0012, 0.0002, 0.002])
         res_x = np.array([0.002, 0.002, 0.003, 0.003, 0.0025, 0.001, 0.003, 0.002, 0.0026, 0.001, 0.004])
         res_h = np.array([4.+0.j, 4.+0.j, 6.+0.j, 6.+0.j, 6.+0.j, 2.+0.j, 4.+0.j, 5.+0.j, 5.+0.j, 2.+0.j, 8.+0.j])
-        self.assert_equal(line_r, res_r)
-        self.assert_equal(line_x, res_x)
-        self.assert_equal(line_h, res_h)
+        # new in pandapower 2.7.0 : order changed, sn_mva changed!
+        order_270 = [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 2]
+        # for el_r, el_x in zip(line_r, line_x): print(np.where((np.abs(res_r*100.-el_r) <= 1e-6) & (np.abs(res_x*100.-el_x) <= 1e-6))[0])
+        self.assert_equal(line_r, res_r[order_270] * net.sn_mva, "resistance (r)")
+        self.assert_equal(line_x, res_x[order_270] * net.sn_mva, "reactance (x)")
+        self.assert_equal(line_h, res_h[order_270] / net.sn_mva, "susceptance (h)")
 
     def test_case30_data(self):
         net = pn.case30()
@@ -71,9 +75,16 @@ def test_case30_data(self):
                            0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,
                            0.+0.j, 2.+0.j, 2.+0.j, 1.+0.j, 2.+0.j, 0.+0.j, 1.+0.j, 1.+0.j,
                            0.+0.j])
-        self.assert_equal(line_r, res_r)
-        self.assert_equal(line_x, res_x)
-        self.assert_equal(line_h, res_h)
+        # new in pandapower 2.7.0 : order changed, sn_mva changed!
+        # for el_r, el_x, el_h in zip(line_r, line_x, line_h): 
+        #     print(np.where((np.abs(res_r*100.-el_r) <= 1e-6) & 
+        #                    (np.abs(res_x*100.-el_x) <= 1e-6) & 
+        #                    (np.abs(res_h/100.-el_h) <= 1e-6))[0])
+        order_270 = [0, 1, 12, 23, 34, 36, 37, 38, 39, 40, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19,
+                     20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35]
+        self.assert_equal(line_r, res_r[order_270] * net.sn_mva, "line r")
+        self.assert_equal(line_x, res_x[order_270] * net.sn_mva, "line x")
+        self.assert_equal(line_h, res_h[order_270] / net.sn_mva, "line h")
 
     def test_case118_data(self):
         net = pn.case118()
@@ -180,9 +191,22 @@ def test_case118_data(self):
                             21.6  +0.j, 104.6  +0.j,   1.738+0.j,  38.   +0.j,   2.48 +0.j,
                              2.48 +0.j,   5.78 +0.j,   3.1  +0.j,   2.682+0.j,   7.092+0.j,
                              8.28 +0.j,  12.2  +0.j,  10.198+0.j])
-        self.assert_equal(line_r, res_r)
-        self.assert_equal(line_x, res_x)
-        self.assert_equal(line_h, res_h)
+        # new in pandapower 2.7.0 : order changed, sn_mva changed!
+        # for el_r, el_x in zip(line_r, line_x): print(np.where((np.abs(res_r*100.-el_r) <= 1e-6) & (np.abs(res_x*100.-el_x) <= 1e-6))[0])
+        # for el in [el for el in np.arange(line_r.shape[0]) if not np.isin(el, order_270)]: print(el)
+        order_270 = [0, 1, 85, 96, 107, 118, 129, 140, 151, 162, 2, 13, 24, 35, 46, 57, 68, 79, 83, 84, 86, 87, 88, 89,
+                    90, 91, 92, 93, 94, 95, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 108, 109, 110, 111, 112, 113, 
+                    114, 115, 116, 117, 119, 120, 121, 122, 123,124, 125, 126, 127, 128, 130,
+                    131, 132, 133, 134, 135, 136, 137, 138, 139, 141, 142, 
+                    143, 144, 145, 146, 147, 148, 149, 150, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 163, 164, 165, 
+                    166, 167, 168, 169, 170, 171, 172, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+                    23, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 47, 48, 49, 50, 51, 
+                    52, 53, 54, 55, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 80, 81,
+                    82]
+
+        self.assert_equal(line_r, res_r[order_270] * net.sn_mva, "line r")
+        self.assert_equal(line_x, res_x[order_270] * net.sn_mva, "line x")
+        self.assert_equal(line_h, res_h[order_270] / net.sn_mva, "line h")
 
         pp_net = net
         # fix the missing values
@@ -226,9 +250,13 @@ def test_case118_data(self):
                                 0.        -0.j        ,  0.        -0.j        ,
                                63.96323106-0.01411497j,  0.        -0.j        ,
                                81.1310369 -0.02879733j])
-        self.assert_equal(trafo_r, trafo_r_res)
-        self.assert_equal(trafo_x, trafo_x_res)
-        self.assert_equal(trafo_b, trafo_h_res)
+        # new in pandapower 2.7.0 : order changed, sn_mva changed!
+        # for el_r, el_x in zip(trafo_r, trafo_x): print(np.where((np.abs(trafo_r_res*100.-el_r) <= 1e-6) & (np.abs(trafo_x_res*100.-el_x) <= 1e-6))[0])
+        # for el in [el for el in np.arange(line_r.shape[0]) if not np.isin(el, order_270)]: print(el)
+        order_270_trafo = [0, 1, 5, 6, 7, 8, 9, 10, 11, 12, 2, 3, 4]
+        self.assert_equal(trafo_r, trafo_r_res[order_270_trafo] * net.sn_mva, "trafo r")
+        self.assert_equal(trafo_x, trafo_x_res[order_270_trafo] * net.sn_mva, "trafo x")
+        self.assert_equal(trafo_b, trafo_h_res[order_270_trafo] / net.sn_mva, "trafo h")
 
 
 if __name__ == "__main__":
diff --git a/lightsim2grid/tests/test_Deactivatedbus.py b/lightsim2grid/tests/test_Deactivatedbus.py
index 59623e7..3e67c29 100644
--- a/lightsim2grid/tests/test_Deactivatedbus.py
+++ b/lightsim2grid/tests/test_Deactivatedbus.py
@@ -11,7 +11,7 @@
 import copy
 import numpy as np
 from scipy import sparse
-from lightsim2grid.initGridModel import init
+from lightsim2grid.gridmodel import init
 import pandapower.networks as pn
 import pandapower as pp
 import warnings
diff --git a/lightsim2grid/tests/test_GaussSeidelSolver.py b/lightsim2grid/tests/test_GaussSeidelSolver.py
index e5d77e4..29c28cb 100644
--- a/lightsim2grid/tests/test_GaussSeidelSolver.py
+++ b/lightsim2grid/tests/test_GaussSeidelSolver.py
@@ -180,18 +180,28 @@ def solver_aux(self):
 
         ppopt = {"PF_TOL": self.tol, "PF_MAX_IT_GS": self.max_it, "VERBOSE": False}
 
-        has_conv = self.solver.compute_pf(self.Ybus, self.V_init, self.Sbus, self.pv,
+        # start = time.perf_counter()
+        ref = set(np.arange(self.Sbus.shape[0])) - set(self.pv) - set(self.pq)
+        ref = np.array(list(ref))
+        # build the slack weights
+        slack_weights = np.zeros(self.Sbus.shape[0])
+        slack_weights[ref] = 1.0 / ref.shape[0]
+
+        has_conv = self.solver.compute_pf(self.Ybus, self.V_init, self.Sbus, 
+                                          ref, slack_weights, self.pv,
                                           self.pq, self.max_it, self.tol)
-        # start = time.time()
-        Vgs, convergedgs, max_itgs = gausspf(copy.deepcopy(self.Ybus), copy.deepcopy(self.Sbus),
+
+        Vgs, convergedgs, max_itgs = gausspf(copy.deepcopy(self.Ybus),
+                                             copy.deepcopy(self.Sbus),
                                              copy.deepcopy(self.V_init),
                                              ref=None, pv=self.pv, pq=self.pq, ppopt=ppopt)
-        # end = time.time()
+        # end = time.perf_counter()
         # print("end - start: {}".format(end - start))
         # print("have i conv ? {}".format(has_conv))
         # print("print timer solve: {}".format(self.solver.get_timers()[1]))
 
-        assert has_conv, "the load flow has diverged for {}".format(self.path)
+        assert convergedgs, "the load flow has diverged for pandapower for {}".format(self.path)
+        assert has_conv, "the load flow has diverged for {} with error {}".format(self.path, self.solver.get_error())
         Va = self.solver.get_Va()
         Vm = self.solver.get_Vm()
         iter_max = self.solver.get_nb_iter()
diff --git a/lightsim2grid/tests/test_GridModel.py b/lightsim2grid/tests/test_GridModel.py
index e8b27a2..1528d89 100644
--- a/lightsim2grid/tests/test_GridModel.py
+++ b/lightsim2grid/tests/test_GridModel.py
@@ -11,7 +11,7 @@
 import pandapower.networks as pn
 import pandapower as pp
 
-from lightsim2grid.initGridModel import init
+from lightsim2grid.gridmodel import init
 import warnings
 import pdb
 
@@ -77,7 +77,7 @@ def check_res(self, Vfinal, net):
         g_is = self.net_ref.gen["in_service"]
         prod_p, prod_q, prod_v = self.model.get_gen_res()
         # test the slack bus is properly modeled as a generator
-        assert np.abs(np.sum(net._ppc["gen"][:, 1]) - np.sum(prod_p)) <= self.tol
+        assert np.abs(np.sum(net._ppc["gen"][:, 1]) - np.sum(prod_p)) <= self.tol_test
         if len(prod_p) != g_is.shape[0]:
             # it means a generator has been added for the slack bus
             prod_p = prod_p[:-1]
diff --git a/lightsim2grid/tests/test_Iterators.py b/lightsim2grid/tests/test_Iterators.py
index 64932a2..1ba5ea9 100644
--- a/lightsim2grid/tests/test_Iterators.py
+++ b/lightsim2grid/tests/test_Iterators.py
@@ -12,6 +12,7 @@
 import warnings
 import pdb
 import numpy as np
+from scipy.sparse import data
 from lightsim2grid import LightSimBackend
 
 SparseLUSolver_AVAILBLE = False
@@ -145,6 +146,47 @@ def test_getters_line(self):
         with self.assertRaises(ValueError):
             line_info = data_lines[nb_line]
 
+    def test_getters_load(self):
+        """test that the load getter return the right values"""
+        data_loads = self.env.backend._grid.get_loads()
+        load_p, load_q, load_v = self.env.backend.loads_info()
+        
+        tol = 1e-5
+        nb_load = len(data_loads)
+        for el in data_loads:
+            load_id = el.id
+            assert np.abs(el.res_p_mw - load_p[load_id]) <= tol, f"load {load_id} has wrong res_p"
+            assert np.abs(el.res_q_mvar - load_q[load_id]) <= tol, f"load {load_id} has wrong res_q"
+            assert np.abs(el.res_v_kv - load_v[load_id]) <= tol, f"load {load_id} has wrong res_v"
+
+        with self.assertRaises(ValueError):
+            load_ = data_loads[-1]
+
+        with self.assertRaises(ValueError):
+            load_ = data_loads[nb_load]
+
+    def test_getters_generic(self):
+        """
+        basic tests on the other getters, to make sure i can call them basically (there is none on this grid so 
+        not easy to test
+        """
+        data_shunts = self.env.backend._grid.get_shunts()
+        with self.assertRaises(ValueError):
+            shunt_ = data_shunts[-1]
+        with self.assertRaises(ValueError):
+            shunt_ = data_shunts[len(data_shunts)]
+
+        data_sgens = self.env.backend._grid.get_static_generators()
+        with self.assertRaises(ValueError):
+            sgen_ = data_sgens[-1]
+        with self.assertRaises(ValueError):
+            sgen_ = data_sgens[len(data_sgens)]
+
+        data_storages = self.env.backend._grid.get_storages()
+        with self.assertRaises(ValueError):
+            stor_ = data_storages[-1]
+        with self.assertRaises(ValueError):
+            stor_ = data_storages[len(data_storages)]
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/lightsim2grid/tests/test_KLUBackendAction.py b/lightsim2grid/tests/test_KLUBackendAction.py
index 0e25188..ee605a1 100644
--- a/lightsim2grid/tests/test_KLUBackendAction.py
+++ b/lightsim2grid/tests/test_KLUBackendAction.py
@@ -11,7 +11,7 @@
 from grid2op.Parameters import Parameters
 from grid2op.Rules import AlwaysLegal
 from grid2op.Chronics import ChangeNothing
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 import numpy as np
 import unittest
 import warnings
diff --git a/lightsim2grid/tests/test_KLUBackendDN.py b/lightsim2grid/tests/test_KLUBackendDN.py
index ef8af27..6366d4c 100644
--- a/lightsim2grid/tests/test_KLUBackendDN.py
+++ b/lightsim2grid/tests/test_KLUBackendDN.py
@@ -10,7 +10,7 @@
 from grid2op.Agent import DoNothingAgent
 from grid2op.Parameters import Parameters
 from grid2op.Rules import AlwaysLegal
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 import numpy as np
 import unittest
 from abc import ABC, abstractmethod
diff --git a/lightsim2grid/tests/test_KLUBackendTestAgent.py b/lightsim2grid/tests/test_KLUBackendTestAgent.py
index de0fcc6..c61dbc8 100644
--- a/lightsim2grid/tests/test_KLUBackendTestAgent.py
+++ b/lightsim2grid/tests/test_KLUBackendTestAgent.py
@@ -11,7 +11,7 @@
 from grid2op.Parameters import Parameters
 from grid2op.Rules import AlwaysLegal
 from grid2op.Converter import IdToAct
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 import numpy as np
 import unittest
 from abc import ABC, abstractmethod
diff --git a/lightsim2grid/tests/test_KLUSolver.py b/lightsim2grid/tests/test_KLUSolver.py
index 67ec943..8e276b1 100644
--- a/lightsim2grid/tests/test_KLUSolver.py
+++ b/lightsim2grid/tests/test_KLUSolver.py
@@ -84,7 +84,7 @@ def compare_sparse_mat(self, J, J_pp, pv, pq):
         """
         pvpq = np.r_[pv, pq]
 
-        comp_val = np.abs(J - J_pp)
+        comp_val = np.abs(J[1:, 1:] - J_pp)  # new in version 0.5.6 : distributed slack added a component to J
         comp_val = comp_val
         assert np.sum(np.abs(comp_val[:len(pvpq), :len(pvpq)])) <= self.tol_test, "J11 (dS_dVa_r) are not equal"
         assert np.sum(np.abs(comp_val[len(pvpq):, :len(pvpq)])) <= self.tol_test, "J21 (dS_dVa_i) are not equal"
@@ -93,7 +93,15 @@ def compare_sparse_mat(self, J, J_pp, pv, pq):
 
     def solver_aux(self):
         self.solver.reset()
-        has_conv = self.solver.compute_pf(self.Ybus, self.V_init, self.Sbus, self.pv, self.pq, self.max_it, self.tol)
+        # start = time.perf_counter()
+        ref = set(np.arange(self.Sbus.shape[0])) - set(self.pv) - set(self.pq)
+        ref = np.array(list(ref))
+        # build the slack weights
+        slack_weights = np.zeros(self.Sbus.shape[0])
+        slack_weights[ref] = 1.0 / ref.shape[0]
+
+        has_conv = self.solver.compute_pf(self.Ybus, self.V_init, self.Sbus,  ref, slack_weights, 
+                                          self.pv, self.pq, self.max_it, self.tol)
         assert has_conv, "the load flow has diverged for {}".format(self.path)
         J = self.solver.get_J()
         Va = self.solver.get_Va()
diff --git a/lightsim2grid/tests/test_LightSimBackend.py b/lightsim2grid/tests/test_LightSimBackend.py
index 77e8143..0e344a8 100644
--- a/lightsim2grid/tests/test_LightSimBackend.py
+++ b/lightsim2grid/tests/test_LightSimBackend.py
@@ -30,7 +30,7 @@
 
 PATH_DATA_TEST_INIT = PATH_DATA_TEST
 PATH_DATA_TEST = PATH_DATA_TEST_PP
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 
 
 class TestNames(HelperTests, BaseTestNames):
diff --git a/lightsim2grid/tests/test_NICSLUSolver.py b/lightsim2grid/tests/test_NICSLUSolver.py
index 88a9408..d0f10dd 100644
--- a/lightsim2grid/tests/test_NICSLUSolver.py
+++ b/lightsim2grid/tests/test_NICSLUSolver.py
@@ -94,7 +94,16 @@ def compare_sparse_mat(self, J, J_pp, pv, pq):
 
     def solver_aux(self):
         self.solver.reset()
-        has_conv = self.solver.compute_pf(self.Ybus, self.V_init, self.Sbus, self.pv, self.pq, self.max_it, self.tol)
+
+        ref = set(np.arange(self.Sbus.shape[0])) - set(self.pv) - set(self.pq)
+        ref = np.array(list(ref))
+        # build the slack weights
+        slack_weights = np.zeros(self.Sbus.shape[0])
+        slack_weights[ref] = 1.0 / ref.shape[0]
+
+
+        has_conv = self.solver.compute_pf(self.Ybus, self.V_init, self.Sbus, ref, slack_weights, 
+                                          self.pv, self.pq, self.max_it, self.tol)
         assert has_conv, "the load flow has diverged for {}".format(self.path)
         J = self.solver.get_J()
         Va = self.solver.get_Va()
diff --git a/lightsim2grid/tests/test_NewtonPF.py b/lightsim2grid/tests/test_NewtonPF.py
index e49b9c8..565a16e 100644
--- a/lightsim2grid/tests/test_NewtonPF.py
+++ b/lightsim2grid/tests/test_NewtonPF.py
@@ -11,7 +11,7 @@
 import numpy as np
 import pdb
 import zipfile
-from lightsim2grid.newtonpf import newtonpf
+from lightsim2grid.newtonpf import newtonpf_old as newtonpf
 from scipy import sparse
 
 
@@ -77,7 +77,7 @@ def compare_sparse_mat(self, J, J_pp, pv, pq):
         """
         pvpq = np.r_[pv, pq]
 
-        comp_val = np.abs(J - J_pp)
+        comp_val = np.abs(J[1:, 1:] - J_pp)  # new in version 0.5.6 : distributed slack added a component to J
         comp_val = comp_val
         assert np.sum(np.abs(comp_val[:len(pvpq), :len(pvpq)])) <= self.tol_test, "J11 (dS_dVa_r) are not equal"
         assert np.sum(np.abs(comp_val[len(pvpq):, :len(pvpq)])) <= self.tol_test, "J21 (dS_dVa_i) are not equal"
diff --git a/lightsim2grid/tests/test_RedispatchEnv.py b/lightsim2grid/tests/test_RedispatchEnv.py
index 3bb1a3d..f49451b 100644
--- a/lightsim2grid/tests/test_RedispatchEnv.py
+++ b/lightsim2grid/tests/test_RedispatchEnv.py
@@ -15,7 +15,7 @@
 from grid2op.tests.BaseRedispTest import BaseTestRedispTooLowHigh, BaseTestDispatchRampingIllegalETC
 from grid2op.tests.BaseRedispTest import BaseTestLoadingAcceptAlmostZeroSumRedisp
 
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 
 PATH_DATA_TEST_INIT = PATH_DATA_TEST
 PATH_DATA_TEST = PATH_DATA_TEST_PP
diff --git a/lightsim2grid/tests/test_Runner.py b/lightsim2grid/tests/test_Runner.py
index 2e1ad39..e55b802 100644
--- a/lightsim2grid/tests/test_Runner.py
+++ b/lightsim2grid/tests/test_Runner.py
@@ -14,7 +14,7 @@
 from grid2op.tests.test_Runner import HelperTests, L2RPNReward, make
 from grid2op.Runner import Runner
 
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 
 import timeit
 
diff --git a/lightsim2grid/tests/test_SameResPP.py b/lightsim2grid/tests/test_SameResPP.py
index 5ce570c..a136849 100644
--- a/lightsim2grid/tests/test_SameResPP.py
+++ b/lightsim2grid/tests/test_SameResPP.py
@@ -101,7 +101,8 @@ def _aux_test(self, pn_net):
         pp_net.ext_grid["in_service"].iloc[0] = True
         conv, exc_ = backend.runpf()
         conv_pp, exc_pp = backend.init_pp_backend.runpf()
-
+        # import pdb
+        # pdb.set_trace()
         assert conv_pp, "Error: pandapower do not converge, impossible to perform the necessary checks"
         assert conv, "Error: lightsim do not converge"
 
@@ -126,7 +127,9 @@ def _aux_test(self, pn_net):
         v_tmp = backend.init_pp_backend._grid.res_bus["vm_pu"].values[:nb_sub] + 0j
         v_tmp *= np.exp(1j * np.pi / 180. * backend.init_pp_backend._grid.res_bus["va_degree"].values[:nb_sub])
         v_tmp = np.concatenate((v_tmp, v_tmp))
-        backend._grid.ac_pf(v_tmp, 1000, 1e-5)
+
+        V = backend._grid.ac_pf(v_tmp, 10, 1e-5)
+        assert V.shape[0], "? lightsim diverge when initialized with pp final voltage ?"
         backend._grid.tell_topo_changed()
 
         Y_pp = backend.init_pp_backend._grid._ppc["internal"]["Ybus"]
@@ -146,7 +149,16 @@ def _aux_test(self, pn_net):
             # V_init *= np.exp(1j * AllVas[:, 0])
             V_init_ref = copy.deepcopy(V_init)
             solver = ClassSolver()
-            solver.solve(scipy.sparse.csc_matrix(Y_pp), V_init, Sbus, pv_, pq_, max_iter, tol_this)
+            
+            # extract the slack bus
+            ref = set(np.arange(V_init.shape[0])) - set(pv_) - set(pq_)
+            ref = np.array(list(ref))
+            # build the slack weights
+            slack_weights = np.zeros(V_init.shape[0])
+            slack_weights[ref] = 1.0 / ref.shape[0]
+
+            solver.solve(scipy.sparse.csc_matrix(Y_pp), V_init, Sbus,
+                         ref, slack_weights, pv_, pq_, max_iter, tol_this)
             time_for_nr = solver.get_timers()[3]
             if TIMER_INFO:
                 print(f"\t Info: Time to perform {nb_iter - index_V} NR iterations for a grid with {nb_sub} "
@@ -171,12 +183,14 @@ def _aux_test(self, pn_net):
         Sbus_pp = backend.init_pp_backend._grid._ppc["internal"]["Sbus"]
         Sbus_pp_right_order = Sbus_pp[pp_vect_converter]
         Sbus_me = backend._grid.get_Sbus()
-        error_p = np.abs(np.real(Sbus_me) - np.real(Sbus_pp_right_order))
+        # slack bus is not the same
+        all_but_slack = np.array(list(set(pv_).union(set(pq_))))
+        error_p = np.abs(np.real(Sbus_me[all_but_slack]) - np.real(Sbus_pp_right_order[all_but_slack]))
         assert np.max(error_p) <= self.tol, f"\t Error: P do not match for Sbus, maximum absolute error is " \
                                             f"{np.max(error_p):.5f} MW, \t Error: significative difference for bus " \
                                             f"index (lightsim): {np.where(error_p > self.tol)[0]}"
 
-        error_q = np.abs(np.imag(Sbus_me) - np.imag(Sbus_pp_right_order))
+        error_q = np.abs(np.imag(Sbus_me[all_but_slack]) - np.imag(Sbus_pp_right_order[all_but_slack]))
         assert np.max(error_q) <= self.tol, f"\t Error: Q do not match for Sbus, maximum absolute error is " \
                                             f"{np.max(error_q):.5f} MVAr, \t Error: significative difference for bus " \
                                             f"index (lightsim): {np.where(error_q > self.tol)[0]}"
@@ -195,7 +209,11 @@ def _aux_test(self, pn_net):
 
         # "IV - Check for the initialization (dc powerflow)"
         # 1) check that the results are same for dc lightsim and dc pandapower
-        Vinit = np.ones(backend.nb_bus_total, dtype=np.complex_) * pp_net["_options"]["init_vm_pu"]
+        if isinstance(pp_net["_options"]["init_vm_pu"], str):
+            mult_ = 1.0
+        else:
+            mult_ = pp_net["_options"]["init_vm_pu"]
+        Vinit = np.ones(backend.nb_bus_total, dtype=np.complex_) * mult_
         backend._grid.deactivate_result_computation()
         Vdc = backend._grid.dc_pf(Vinit, max_iter, tol_this)
         backend._grid.reactivate_result_computation()
@@ -269,7 +287,11 @@ def _aux_test(self, pn_net):
                                                 f"is {np.max(error_q):.5f}"
 
         # "3) check that lightsim ac pf init with pp dc pf give same results (than pp)"
-        Vinit = np.ones(backend.nb_bus_total, dtype=np.complex_) * pp_net["_options"]["init_vm_pu"]
+        if isinstance(pp_net["_options"]["init_vm_pu"], str):
+            mult_coeff = 1.0
+        else:
+            mult_coeff = pp_net["_options"]["init_vm_pu"]
+        Vinit = np.ones(backend.nb_bus_total, dtype=np.complex_) * mult_coeff
         Vinit[:nb_sub] = V_init_ref[pp_vect_converter]
         conv = backend._grid.ac_pf(Vinit, max_iter, tol_this)
         assert conv.shape[0] > 0, "\t Error: the lightsim diverge when initialized with pandapower Vinit_dc"
diff --git a/lightsim2grid/tests/test_SecurityAnlysis.py b/lightsim2grid/tests/test_SecurityAnlysis.py
index ee89d2e..4927f6f 100644
--- a/lightsim2grid/tests/test_SecurityAnlysis.py
+++ b/lightsim2grid/tests/test_SecurityAnlysis.py
@@ -102,7 +102,7 @@ def test_get_flows_simple(self):
         contingencies are given in the right order"""
         sa = SecurityAnalysis(self.env)
         sa.add_multiple_contingencies(0, 1, 2)
-        res_a, res_v = sa.get_flows()
+        res_p, res_a, res_v = sa.get_flows()
         assert res_a.shape == (3, self.env.n_line)
         assert abs(res_a[0,0]) <= 1e-6
         assert abs(res_a[1,1]) <= 1e-6
@@ -113,7 +113,7 @@ def test_get_flows_1(self):
         contingencies are NOT given in the right order"""
         sa = SecurityAnalysis(self.env)
         sa.add_multiple_contingencies(0, 2, 1)
-        res_a, res_v = sa.get_flows()
+        res_p, res_a, res_v = sa.get_flows()
         assert res_a.shape == (3, self.env.n_line)
         assert abs(res_a[0,0]) <= 1e-6
         assert abs(res_a[1,2]) <= 1e-6
@@ -124,7 +124,7 @@ def test_get_flows_2(self):
         contingencies are given in the right order"""
         sa = SecurityAnalysis(self.env)
         sa.add_multiple_contingencies(0, 1, 2)
-        res_a, res_v = sa.get_flows(0, 1)
+        res_p, res_a, res_v = sa.get_flows(0, 1)
         assert res_a.shape == (2, self.env.n_line)
         assert abs(res_a[0,0]) <= 1e-6
         assert abs(res_a[1,1]) <= 1e-6
@@ -134,7 +134,7 @@ def test_get_flows_3(self):
         contingencies are given in the right order"""
         sa = SecurityAnalysis(self.env)
         sa.add_multiple_contingencies(0, 1, 2)
-        res_a, res_v = sa.get_flows(0, 2)
+        res_p, res_a, res_v = sa.get_flows(0, 2)
         assert res_a.shape == (2, self.env.n_line)
         assert abs(res_a[0,0]) <= 1e-6
         assert abs(res_a[1,2]) <= 1e-6
@@ -144,7 +144,7 @@ def test_get_flows_4(self):
         contingencies are NOT given in the right order"""
         sa = SecurityAnalysis(self.env)
         sa.add_multiple_contingencies(0, 2, 1)
-        res_a, res_v = sa.get_flows(0, 2)
+        res_p, res_a, res_v = sa.get_flows(0, 2)
         assert res_a.shape == (2, self.env.n_line)
         assert abs(res_a[0,0]) <= 1e-6
         assert abs(res_a[1,2]) <= 1e-6
@@ -154,7 +154,7 @@ def test_get_flows_5(self):
         contingencies are NOT given in the right order"""
         sa = SecurityAnalysis(self.env)
         sa.add_multiple_contingencies(0, 2, 1)
-        res_a, res_v = sa.get_flows(0, 1)
+        res_p, res_a, res_v = sa.get_flows(0, 1)
         assert res_a.shape == (2, self.env.n_line)
         assert abs(res_a[0,0]) <= 1e-6
         assert abs(res_a[1,1]) <= 1e-6
@@ -165,7 +165,7 @@ def test_get_flows_multiple(self):
         sa.add_multiple_contingencies(0, [0, 4], [5, 7], 4)
 
         # everything
-        res_a, res_v = sa.get_flows()
+        res_p, res_a, res_v = sa.get_flows()
         assert res_a.shape == (4, self.env.n_line)
         assert abs(res_a[0, 0]) <= 1e-6
         assert abs(res_a[1, 0]) + abs(res_a[1, 4]) <= 1e-6
@@ -173,7 +173,7 @@ def test_get_flows_multiple(self):
         assert abs(res_a[3, 4]) <= 1e-6
 
         # only some
-        res_a, res_v = sa.get_flows([0, 4], [5, 7], 4, 0)
+        res_p, res_a, res_v = sa.get_flows([0, 4], [5, 7], 4, 0)
         assert res_a.shape == (4, self.env.n_line)
         assert abs(res_a[3, 0]) <= 1e-6
         assert abs(res_a[0, 0]) + abs(res_a[0, 4]) <= 1e-6
@@ -181,15 +181,19 @@ def test_get_flows_multiple(self):
         assert abs(res_a[2, 4]) <= 1e-6
 
         # only some, but not all
-        res_a, res_v = sa.get_flows([5, 7], [0, 4], 4)
+        res_p, res_a, res_v = sa.get_flows([5, 7], [0, 4], 4)
         assert res_a.shape == (3, self.env.n_line)
         assert abs(res_a[1, 0]) + abs(res_a[1, 4]) <= 1e-6
         assert abs(res_a[0, 5]) + abs(res_a[0, 7]) <= 1e-6
         assert abs(res_a[2, 4]) <= 1e-6
+        
+        assert abs(res_p[1, 0]) + abs(res_p[1, 4]) <= 1e-6
+        assert abs(res_p[0, 5]) + abs(res_p[0, 7]) <= 1e-6
+        assert abs(res_p[2, 4]) <= 1e-6
 
         # ask for a non simulated contingencies
         with self.assertRaises(RuntimeError):
-            res_a, res_v = sa.get_flows(5)
+            res_p, res_a, res_v = sa.get_flows(5)
 
     def test_change_injection(self):
         """test the capacity of the things to handle different steps"""
@@ -200,8 +204,8 @@ def test_change_injection(self):
         sa2 = SecurityAnalysis(self.env)
         sa2.add_multiple_contingencies(*conts)
 
-        res_a1, res_v1 = sa1.get_flows()
-        res_a2, res_v2 = sa2.get_flows()
+        res_p1, res_a1, res_v1 = sa1.get_flows()
+        res_p2, res_a2, res_v2 = sa2.get_flows()
         
         # basic check that the right flows are 0.
         assert abs(res_a1[0, 0]) <= 1e-6
@@ -212,6 +216,16 @@ def test_change_injection(self):
         assert abs(res_a2[1, 0]) + abs(res_a2[1, 4]) <= 1e-6
         assert abs(res_a2[2, 5]) + abs(res_a2[2, 7]) <= 1e-6
         assert abs(res_a2[3, 4]) <= 1e-6
+        assert abs(res_a1[0, 0]) <= 1e-6
+        
+        assert abs(res_p1[1, 0]) + abs(res_p1[1, 4]) <= 1e-6
+        assert abs(res_p1[2, 5]) + abs(res_p1[2, 7]) <= 1e-6
+        assert abs(res_p1[3, 4]) <= 1e-6
+        assert abs(res_p2[0, 0]) <= 1e-6
+        assert abs(res_p2[1, 0]) + abs(res_p2[1, 4]) <= 1e-6
+        assert abs(res_p2[2, 5]) + abs(res_p2[2, 7]) <= 1e-6
+        assert abs(res_p2[3, 4]) <= 1e-6
 
         # check that indeed the matrix are different
         assert np.max(np.abs(res_a1 - res_a2)) > 1.
+        assert np.max(np.abs(res_p1 - res_p2)) > 1.
diff --git a/lightsim2grid/tests/test_SecurityAnlysis_cpp.py b/lightsim2grid/tests/test_SecurityAnlysis_cpp.py
index 405e586..bbf73ae 100644
--- a/lightsim2grid/tests/test_SecurityAnlysis_cpp.py
+++ b/lightsim2grid/tests/test_SecurityAnlysis_cpp.py
@@ -7,11 +7,10 @@
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
 import unittest
-from grid2op import Backend
 import numpy as np
 import grid2op
 
-from lightsim2grid_cpp import SecurityAnalysis
+from lightsim2grid_cpp import SecurityAnalysisCPP
 from lightsim2grid import LightSimBackend
 import warnings
 import pdb
@@ -30,11 +29,11 @@ def tearDown(self) -> None:
         return super().tearDown()
 
     def test_can_create(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         assert len(SA.my_defaults()) == 0
 
     def test_add_n1(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         SA.add_n1(0)
         all_def = SA.my_defaults()
         assert len(all_def) == 1
@@ -65,7 +64,7 @@ def test_add_n1(self):
             SA.add_n1(20)
 
     def test_add_multiple_n1(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         SA.add_multiple_n1([0])
         all_def = SA.my_defaults()
         assert len(all_def) == 1
@@ -100,7 +99,7 @@ def test_add_multiple_n1(self):
             SA.add_multiple_n1([20])
 
     def test_add_nk(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         SA.add_nk([0])
         all_def = SA.my_defaults()
         assert len(all_def) == 1
@@ -133,13 +132,13 @@ def test_add_nk(self):
             SA.add_nk([20])
 
     def test_add_all_n1(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         SA.add_all_n1()
         all_def = SA.my_defaults()
         assert len(all_def) == self.env.n_line
 
     def test_remove_n1(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         SA.add_all_n1()
         assert SA.remove_n1(0)  # this should remove it and return true (because the removing is a success)
         all_def = SA.my_defaults()
@@ -149,7 +148,7 @@ def test_remove_n1(self):
         assert len(all_def) == self.env.n_line - 1 
 
     def test_clear(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         SA.add_all_n1()
         all_def = SA.my_defaults()
         assert len(all_def) == self.env.n_line
@@ -158,7 +157,7 @@ def test_clear(self):
         assert len(all_def) == 0
 
     def test_remove_multiple_n1(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         SA.add_all_n1()
         nb_removed = SA.remove_multiple_n1([0, 1, 2])
         assert nb_removed == 3
@@ -170,7 +169,7 @@ def test_remove_multiple_n1(self):
         assert len(all_def) == self.env.n_line - 5
 
     def test_remove_nk(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         SA.add_nk([0, 1])
         SA.add_nk([0, 2])
         SA.add_nk([0, 3])
@@ -189,7 +188,7 @@ def test_remove_nk(self):
         assert len(all_def) == 2
 
     def test_compute(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
         lid_cont = [0, 1, 2, 3]
         nb_sub = self.env.n_sub
         SA.add_multiple_n1(lid_cont)
@@ -208,8 +207,8 @@ def test_compute(self):
             assert np.max(np.abs(res_flows[cont_id] - sim_obs.a_or*1e-3)) <= 1e-6, f"error in flows when disconnecting line {l_id} (contingency nb {cont_id})"
     
     def test_compute_nonconnected_graph(self):
-        SA = SecurityAnalysis(self.env.backend._grid)
-        lid_cont = [18]
+        SA = SecurityAnalysisCPP(self.env.backend._grid)
+        lid_cont = [17, 18, 19]  # 17 is ok, 18 lead to divergence, i need to check then that 19 is correct (no divergence)
         nb_sub = self.env.n_sub
         SA.add_multiple_n1(lid_cont)
         SA.compute(self.env.backend.V, self.env.backend.max_it, self.env.backend.tol)
@@ -224,6 +223,7 @@ def test_compute_nonconnected_graph(self):
                                                               time_step=0)
             if not sim_done:
                 Vref = obs._obs_env.backend.V
+                assert np.all(np.isfinite(res_flows[cont_id])), f"amps should not be Nan for cont {cont_id} (converged)"
                 assert np.max(np.abs(Vref[:nb_sub] - res_SA[cont_id, :nb_sub])) <= 1e-6, f"error in V when disconnecting line {l_id} (contingency nb {cont_id})"
                 assert np.max(np.abs(res_flows[cont_id] - sim_obs.a_or*1e-3)) <= 1e-6, f"error in flows when disconnecting line {l_id} (contingency nb {cont_id})"
             else:
diff --git a/lightsim2grid/tests/test_SparseLUSolver.py b/lightsim2grid/tests/test_SparseLUSolver.py
index ec4b101..e1c1b55 100644
--- a/lightsim2grid/tests/test_SparseLUSolver.py
+++ b/lightsim2grid/tests/test_SparseLUSolver.py
@@ -86,7 +86,7 @@ def compare_sparse_mat(self, J, J_pp, pv, pq):
         """
         pvpq = np.r_[pv, pq]
 
-        comp_val = np.abs(J - J_pp)
+        comp_val = np.abs(J[1:, 1:] - J_pp)  # new in version 0.5.6 : distributed slack added a component to J
         comp_val = comp_val
         assert np.sum(np.abs(comp_val[:len(pvpq), :len(pvpq)])) <= self.tol_test, "J11 (dS_dVa_r) are not equal"
         assert np.sum(np.abs(comp_val[len(pvpq):, :len(pvpq)])) <= self.tol_test, "J21 (dS_dVa_i) are not equal"
@@ -95,7 +95,14 @@ def compare_sparse_mat(self, J, J_pp, pv, pq):
 
     def solver_aux(self):
         self.solver.reset()
-        has_conv = self.solver.compute_pf(self.Ybus, self.V_init, self.Sbus, self.pv, self.pq, self.max_it, self.tol)
+        ref = set(np.arange(self.Sbus.shape[0])) - set(self.pv) - set(self.pq)
+        ref = np.array(list(ref))
+        # build the slack weights
+        slack_weights = np.zeros(self.Sbus.shape[0])
+        slack_weights[ref] = 1.0 / ref.shape[0]
+
+        has_conv = self.solver.compute_pf(self.Ybus, self.V_init, self.Sbus, ref, slack_weights, 
+                                          self.pv, self.pq, self.max_it, self.tol)
         assert has_conv, "the load flow has diverged for {}".format(self.path)
         J = self.solver.get_J()
         Va = self.solver.get_Va()
diff --git a/lightsim2grid/tests/test_TimeSerie.py b/lightsim2grid/tests/test_TimeSerie.py
index 1dd7c92..2c30144 100644
--- a/lightsim2grid/tests/test_TimeSerie.py
+++ b/lightsim2grid/tests/test_TimeSerie.py
@@ -10,7 +10,7 @@
 import warnings
 from lightsim2grid import TimeSerie
 import grid2op
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 import numpy as np
 
 class TimeSerieTester(unittest.TestCase):
@@ -24,6 +24,7 @@ def test_behaviour(self):
         time_series = TimeSerie(env)
         time_series.compute_V(scenario_id=0)
         As = time_series.compute_A()  # will contain the flows, in amps at each step (rows) for each powerline (column)
+        Ps = time_series.compute_P()  # will contain the flows, in amps at each step (rows) for each powerline (column)
         
         env.set_id(0)
         env.reset()
@@ -31,4 +32,6 @@ def test_behaviour(self):
         for it_num in range(100):
             obs, *_ = env.step(env.action_space())
             if np.max(np.abs(As[1 + it_num] - obs.a_or))  > 1e-3:
-                raise RuntimeError(f"error at it {it_num}")
+                raise RuntimeError(f"error at it {it_num} for A")
+            if np.max(np.abs(Ps[1 + it_num] - obs.p_or))  > 1e-3:
+                raise RuntimeError(f"error at it {it_num} for P")
diff --git a/lightsim2grid/tests/test_case118.py b/lightsim2grid/tests/test_case118.py
new file mode 100644
index 0000000..c4e4890
--- /dev/null
+++ b/lightsim2grid/tests/test_case118.py
@@ -0,0 +1,243 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform.
+
+import warnings
+import pandapower as pp
+import numpy as np
+import pandapower.networks as pn
+import grid2op
+import pdb
+import copy
+import tempfile
+import unittest
+from lightsim2grid import LightSimBackend
+from lightsim2grid.gridmodel import init
+from grid2op.Chronics import GridStateFromFileWithForecastsWithoutMaintenance as GridStateFromFile
+
+
+VAR_GEN = ["bus", "p_mw", "vm_pu", "sn_mva", "name", "index", "max_q_mvar", "min_q_mvar", "min_p_mw",
+            "max_p_mw", "scaling", "type", "slack", "controllable", "vn_kv", "xdss_pu", "rdss_pu",
+            "cos_phi", "in_service"]
+            
+# adding a slack bus
+def make_grid_multiple_slack(case):
+    """create an equivalent grid (for case 14) with generator as slack buses"""
+    pp.runpp(case)  # forced to do it to retrieve the power of the slack bus
+    var_pp = ["bus", 'in_service', "name", "max_p_mw", "min_p_mw", "max_q_mvar", "min_q_mvar", "slack_weight"]
+    if "slack_weight" not in case.ext_grid:
+        var_pp = var_pp[:-1]
+
+    slack_bus_gen_id_ppc = 0  # checked manually
+    pp.create_gen(case,
+                **case.ext_grid[var_pp].iloc[0],
+                p_mw=case._ppc['gen'][slack_bus_gen_id_ppc, 1],
+                slack=True)
+    # "deactivate" the "ext_grid" 
+    case.ext_grid["in_service"][0] = False
+    pp.runpp(case)
+
+    # now create a copy of it, by removing the ext_grid completely (to be sure)
+    net = pp.create_empty_network("case14_custom", sn_mva=1.0 * case.sn_mva, f_hz= 1.0 * case.f_hz)
+    # create bus
+    for i in range(case.bus.shape[0]):
+        pp.create_bus(net, **case.bus.iloc[i])
+
+    # create lines
+    var_line = ["from_bus", "to_bus", "length_km", "r_ohm_per_km", "x_ohm_per_km", "c_nf_per_km", "max_i_ka", "name",
+                "index", "type", "geodata", "in_service", "df", "parallel", "g_us_per_km",
+                "max_loading_percent", "alpha", "temperature_degree_celsius"]
+    var_line = [el for el in var_line if el in case.line]
+    for i in range(case.line.shape[0]):
+        pp.create_line_from_parameters(net, **case.line[var_line].iloc[i])
+
+    # create trafos
+    var_trafo = ["hv_bus", "lv_bus", "sn_mva", "vn_hv_kv", "vn_lv_kv", "vkr_percent", "vk_percent", "pfe_kw", "i0_percent", "shift_degree",
+                "tap_side", "tap_neutral", "tap_max", "tap_min", "tap_step_percent", "tap_step_degree", 
+                "tap_pos", "tap_phase_shifter", "in_service", "name", "index", "max_loading_percent",
+                "parallel", "df"]
+    var_trafo = [el for el in var_trafo if el in case.trafo]
+    for i in range(case.trafo.shape[0]):
+        pp.create_transformer_from_parameters(net, **case.trafo[var_trafo].iloc[i])
+
+    # create shunts
+    var_shunt = ["bus", "q_mvar", "p_mw", "vn_kv", "step", "max_step", "name",
+                "in_service", "index"]
+    var_shunt = [el for el in var_shunt if el in case.shunt]
+    for i in range(case.shunt.shape[0]):
+        pp.create_shunt(net, **case.shunt[var_shunt].iloc[i])
+
+    # create loads
+    var_load = ["bus", "p_mw", "q_mvar", "const_z_percent", "const_i_percent", "sn_mva", "name",
+                "scaling", "index", "in_service", "type", "max_p_mw", "min_p_mw", "max_q_mvar",
+                "min_q_mvar", "controllable"]
+    var_load = [el for el in var_load if el in case.load]
+    for i in range(case.load.shape[0]):
+        pp.create_load(net, **case.load[var_load].iloc[i])
+
+    # create gens
+    var_gen = [el for el in VAR_GEN if el in case.gen]
+    for i in range(case.gen.shape[0]):
+        pp.create_gen(net, **case.gen[var_gen].iloc[i])
+
+    id_ref_slack = net.gen.shape[0]-1  # initial generator added as the slack bus added
+    net.gen["min_p_mw"][[id_ref_slack]] = 0.
+    net.gen["max_p_mw"][[id_ref_slack]] = 300.
+    assert (net.gen["min_p_mw"] >= 0.).all()
+    assert (net.gen["min_p_mw"] <= 400.).all()
+
+    # we can test that is the same grid if we want to
+    # pp.runpp(case, init="flat")
+    # pp.runpp(net, init="flat")
+
+    # assert ((case.res_gen - net.res_gen) <= 1e-6).all().all()
+    # assert ((case.res_load - net.res_load) <= 1e-6).all().all()
+    # assert ((case.res_shunt - net.res_shunt) <= 1e-6).all().all()
+    # assert ((case.res_line - net.res_line) <= 1e-6).all().all()
+    # assert ((case.res_trafo - net.res_trafo) <= 1e-6).all().all()
+    # assert ((case.res_bus.iloc[:net.bus.shape[0]] - net.res_bus) <= 1e-6).all().all()
+
+    return net
+
+class TestMultipleL2RPN(unittest.TestCase):
+    def setUp(self) -> None:
+        self.max_it = 10
+        self.tol = 1e-8
+        self.nb_bus_total = 118
+
+    def test_neurips_track2(self):
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            env = grid2op.make("l2rpn_neurips_2020_track2",
+                               test=True,
+                               data_feeding_kwargs={"gridvalueClass": GridStateFromFile})  
+
+        li_envs = list(env.keys())
+
+        for el in li_envs:
+            self.pp_net = env[el].backend._grid
+            self.idx_slack = np.where(self.pp_net.gen["slack"].values)[0]
+            if "slack_weight" not in self.pp_net.gen:
+                warnings.warn("Unable to peform required tests because install pandapower version does not "
+                              "allow multiple slack.")
+                return
+            self.pp_net.gen["slack_weight"][self.idx_slack] = 1.
+            pp.rundcpp(self.pp_net)  # to forget the result
+            pp.runpp(self.pp_net, distributed_slack=True, init_vm_pu="flat", init_va_degree="flat")
+            with warnings.catch_warnings():
+                warnings.filterwarnings("ignore")
+                ls_grid = init(self.pp_net)
+            ls_grid.tell_topo_changed()
+            V = np.ones(2 * self.nb_bus_total, dtype=np.complex_)
+            V = ls_grid.ac_pf(V, self.max_it, self.tol)
+            self.check_results(V[:self.nb_bus_total], ls_grid, self.pp_net)
+
+    def check_results(self, V_ls, ls_grid, pp_net):
+        assert len(V_ls), "lightsim diverged !"
+        my_ref = np.where(np.angle(V_ls) == 0.)[0][0]
+        V_pp = pp_net.res_bus["vm_pu"].values * np.exp(1j*np.pi / 180. *  pp_net.res_bus["va_degree"].values)
+        V_pp *= np.exp(-1j * np.angle(V_pp)[my_ref])
+        V_pp = V_pp[:self.nb_bus_total]
+        assert np.abs(V_pp - V_ls).max() <= 1e-6, "wrong voltages"
+        assert np.all(np.abs([el.res_p_or_mw for el in ls_grid.get_lines()] - pp_net.res_line["p_from_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_a_or_ka for el in ls_grid.get_lines()] - pp_net.res_line["i_from_ka"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_p_hv_mw for el in ls_grid.get_trafos()] - pp_net.res_trafo["p_hv_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_p_mw for el in ls_grid.get_generators()] - pp_net.res_gen["p_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_q_mvar for el in ls_grid.get_generators()] - pp_net.res_gen["q_mvar"].values) <= 1e-6)
+        
+
+class Test118LightsimBackend(unittest.TestCase):
+    def setUp(self) -> None:
+        self.max_it = 10
+        self.tol = 1e-8
+        self.nb_bus_total = 118
+
+    def test_make_and_pf(self):
+        env_name_input = "l2rpn_neurips_2020_track2"
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            self.env_ls = grid2op.make(env_name_input, backend=LightSimBackend(), test=True,
+                                       data_feeding_kwargs={"gridvalueClass": GridStateFromFile})
+
+class TestMultipleSlack118(unittest.TestCase):
+    def setUp(self) -> None:
+        # LF PARAMETERS
+        self.max_it = 10
+        self.tol = 1e-8
+        self.nb_bus_total = 118
+
+        # retrieve the case14 and remove the "ext_grid" => put a generator as slack bus instead
+        self.case = pn.case118()
+        self.net = make_grid_multiple_slack(self.case)
+        if "slack_weight" in self.net.gen:
+            id_ref_slack = self.net.gen.shape[0]-1  # initial generator added as the slack bus added
+            self.net.gen["slack_weight"][[id_ref_slack]] = 0.5
+
+        self.unable_to_run_due_to_pp = False
+        if "slack_weight" not in self.net.gen:
+            msg_ = "Unable to peform required tests because install pandapower version does not " \
+                   "allow multiple slack."
+            warnings.warn(msg_)
+            self.unable_to_run_due_to_pp = True
+            self.skipTest(msg_)
+
+    def test_single_slack(self):
+        """check pandapower and lightsim get the same results when there is only one
+           slack bus
+        """
+        pp.runpp(self.net,
+                 init_vm_pu="flat",
+                 init_va_degree="flat")
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            ls_grid_single = init(self.net)
+        V = np.ones(self.nb_bus_total, dtype=np.complex_)
+        V = ls_grid_single.ac_pf(V, self.max_it, self.tol)
+        self.check_results(V, ls_grid_single, self.net)
+
+        # now run with the option "dist_slack=True"
+        pp.runpp(self.net,
+                 distributed_slack=True,
+                 init_vm_pu="flat",
+                 init_va_degree="flat")
+        self.check_results(V, ls_grid_single, self.net)
+
+    def test_two_slacks_diff_bus(self):
+        """test the results when there are two slacks, in most simple setting"""
+        # now activate more slack bus
+        self.net.gen["slack"][[1]] = True
+        id_ref_slack = self.net.gen.shape[0] - 1
+        if "slack_weight" in self.net.gen:
+            self.net.gen["slack_weight"][[1, id_ref_slack]] = 0.5
+        
+        # just to make sure pp forgot previous results
+        pp.runpp(self.net,
+                 distributed_slack=True,
+                 init_vm_pu="flat",
+                 init_va_degree="flat")  
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            ls_grid = init(self.net)
+        V = np.ones(self.nb_bus_total, dtype=np.complex_)
+        V = ls_grid.ac_pf(V, self.max_it, self.tol)
+        self.check_results(V, ls_grid, self.net)
+
+    def check_results(self, V_ls, ls_grid, pp_net):
+        assert len(V_ls), "lightsim diverged !"
+        my_ref = np.where(np.angle(V_ls) == 0.)[0][0]
+        V_pp = pp_net.res_bus["vm_pu"].values * np.exp(1j*np.pi / 180. *  pp_net.res_bus["va_degree"].values)
+        V_pp *= np.exp(-1j * np.angle(V_pp)[my_ref])
+        assert np.abs(V_pp - V_ls).max() <= 1e-6, "wrong voltages"
+        assert np.all(np.abs([el.res_p_or_mw for el in ls_grid.get_lines()] - pp_net.res_line["p_from_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_a_or_ka for el in ls_grid.get_lines()] - pp_net.res_line["i_from_ka"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_p_hv_mw for el in ls_grid.get_trafos()] - pp_net.res_trafo["p_hv_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_p_mw for el in ls_grid.get_generators()] - pp_net.res_gen["p_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_q_mvar for el in ls_grid.get_generators()] - pp_net.res_gen["q_mvar"].values) <= 1e-6)
+        
+
+if __name__ == "__main__":
+    unittest.main()
\ No newline at end of file
diff --git a/lightsim2grid/tests/test_multiple_slack.py b/lightsim2grid/tests/test_multiple_slack.py
new file mode 100644
index 0000000..4078bce
--- /dev/null
+++ b/lightsim2grid/tests/test_multiple_slack.py
@@ -0,0 +1,282 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform.
+
+import warnings
+import pandapower as pp
+import numpy as np
+import pandapower.networks as pn
+import pdb
+import copy
+import tempfile
+import unittest
+from lightsim2grid.gridmodel import init
+
+
+VAR_GEN = ["bus", "p_mw", "vm_pu", "sn_mva", "name", "index", "max_q_mvar", "min_q_mvar", "min_p_mw",
+            "max_p_mw", "scaling", "type", "slack", "controllable", "vn_kv", "xdss_pu", "rdss_pu",
+            "cos_phi", "in_service"]
+# adding a slack bus
+def make_grid_multiple_slack(case):
+    """create an equivalent grid (for case 14) with generator as slack buses"""
+    pp.runpp(case)  # forced to do it to retrieve the power of the slack bus
+    var_pp = ["bus", 'in_service', "name", "max_p_mw", "min_p_mw", "max_q_mvar", "min_q_mvar", "slack_weight"]
+    if "slack_weight" not in case.ext_grid:
+        var_pp = var_pp[:-1]
+
+    slack_bus_gen_id_ppc = 0  # checked manually
+    pp.create_gen(case,
+                **case.ext_grid[var_pp].iloc[0],
+                p_mw=case._ppc['gen'][slack_bus_gen_id_ppc, 1],
+                slack=True)
+    # "deactivate" the "ext_grid" 
+    case.ext_grid["in_service"][0] = False
+    pp.runpp(case)
+
+    # now create a copy of it, by removing the ext_grid completely (to be sure)
+    net = pp.create_empty_network("case14_custom", sn_mva=1.0 * case.sn_mva, f_hz= 1.0 * case.f_hz)
+    # create bus
+    for i in range(case.bus.shape[0]):
+        pp.create_bus(net, **case.bus.iloc[i])
+
+    # create lines
+    var_line = ["from_bus", "to_bus", "length_km", "r_ohm_per_km", "x_ohm_per_km", "c_nf_per_km", "max_i_ka", "name",
+                "index", "type", "geodata", "in_service", "df", "parallel", "g_us_per_km",
+                "max_loading_percent", "alpha", "temperature_degree_celsius"]
+    var_line = [el for el in var_line if el in case.line]
+    for i in range(case.line.shape[0]):
+        pp.create_line_from_parameters(net, **case.line[var_line].iloc[i])
+
+    # create trafos
+    var_trafo = ["hv_bus", "lv_bus", "sn_mva", "vn_hv_kv", "vn_lv_kv", "vkr_percent", "vk_percent", "pfe_kw", "i0_percent", "shift_degree",
+                "tap_side", "tap_neutral", "tap_max", "tap_min", "tap_step_percent", "tap_step_degree", 
+                "tap_pos", "tap_phase_shifter", "in_service", "name", "index", "max_loading_percent",
+                "parallel", "df"]
+    var_trafo = [el for el in var_trafo if el in case.trafo]
+    for i in range(case.trafo.shape[0]):
+        pp.create_transformer_from_parameters(net, **case.trafo[var_trafo].iloc[i])
+
+    # create shunts
+    var_shunt = ["bus", "q_mvar", "p_mw", "vn_kv", "step", "max_step", "name",
+                "in_service", "index"]
+    var_shunt = [el for el in var_shunt if el in case.shunt]
+    for i in range(case.shunt.shape[0]):
+        pp.create_shunt(net, **case.shunt[var_shunt].iloc[i])
+
+    # create loads
+    var_load = ["bus", "p_mw", "q_mvar", "const_z_percent", "const_i_percent", "sn_mva", "name",
+                "scaling", "index", "in_service", "type", "max_p_mw", "min_p_mw", "max_q_mvar",
+                "min_q_mvar", "controllable"]
+    var_load = [el for el in var_load if el in case.load]
+    for i in range(case.load.shape[0]):
+        pp.create_load(net, **case.load[var_load].iloc[i])
+
+    # create gens
+    var_gen = [el for el in VAR_GEN if el in case.gen]
+    for i in range(case.gen.shape[0]):
+        pp.create_gen(net, **case.gen[var_gen].iloc[i])
+
+    id_ref_slack = net.gen.shape[0]-1  # initial generator added as the slack bus added
+    net.gen["min_p_mw"][[id_ref_slack]] = 0.
+    net.gen["max_p_mw"][[id_ref_slack]] = 300.
+    assert (net.gen["min_p_mw"] >= 0.).all()
+    assert (net.gen["min_p_mw"] <= 400.).all()
+
+    # we can test that is the same grid if we want to
+    # pp.runpp(case, init="flat")
+    # pp.runpp(net, init="flat")
+
+    # assert ((case.res_gen - net.res_gen) <= 1e-6).all().all()
+    # assert ((case.res_load - net.res_load) <= 1e-6).all().all()
+    # assert ((case.res_shunt - net.res_shunt) <= 1e-6).all().all()
+    # assert ((case.res_line - net.res_line) <= 1e-6).all().all()
+    # assert ((case.res_trafo - net.res_trafo) <= 1e-6).all().all()
+    # assert ((case.res_bus.iloc[:net.bus.shape[0]] - net.res_bus) <= 1e-6).all().all()
+
+    return net
+
+class TestMultipleSlack14(unittest.TestCase):
+    def setUp(self) -> None:
+        # LF PARAMETERS
+        self.max_it = 10
+        self.tol = 1e-8
+        self.nb_bus_total = 14
+
+        # retrieve the case14 and remove the "ext_grid" => put a generator as slack bus instead
+        self.case = pn.case14()
+        self.net = make_grid_multiple_slack(self.case)
+        if "slack_weight" in self.net.gen:
+            id_ref_slack = self.net.gen.shape[0]-1  # initial generator added as the slack bus added
+            self.net.gen["slack_weight"][[id_ref_slack]] = 0.5
+
+        if "slack_weight" not in self.net.gen:
+            msg_ = "Unable to peform required tests because install pandapower version does not " \
+                   "allow multiple slack."
+            warnings.warn(msg_)
+            self.unable_to_run_due_to_pp = True
+            self.skipTest(msg_)
+    
+    def test_single_slack(self):
+        """check pandapower and lightsim get the same results when there is only one
+           slack bus
+        """
+        pp.runpp(self.net,
+                 init_vm_pu="flat",
+                 init_va_degree="flat")
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            ls_grid_single = init(self.net)
+        V = np.ones(self.nb_bus_total, dtype=np.complex_)
+        V = ls_grid_single.ac_pf(V, self.max_it, self.tol)
+        self.check_results(V, ls_grid_single, self.net)
+
+        # now run with the option "dist_slack=True"
+        pp.runpp(self.net,
+                 distributed_slack=True,
+                 init_vm_pu="flat",
+                 init_va_degree="flat")
+        self.check_results(V, ls_grid_single, self.net)
+
+    def test_two_slacks_diff_bus(self):
+        """test the results when there are two slacks, in most simple setting"""
+        # now activate more slack bus
+        self.net.gen["slack"][[1]] = True
+        id_ref_slack = self.net.gen.shape[0] - 1
+        if "slack_weight" in self.net.gen:
+            self.net.gen["slack_weight"][[1, id_ref_slack]] = 0.5
+        
+        # just to make sure pp forgot previous results
+        pp.runpp(self.net,
+                 distributed_slack=True,
+                 init_vm_pu="flat",
+                 init_va_degree="flat")  
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            ls_grid = init(self.net)
+        V = np.ones(self.nb_bus_total, dtype=np.complex_)
+        V = ls_grid.ac_pf(V, self.max_it, self.tol)
+        self.check_results(V, ls_grid, self.net)
+
+    def test_two_slacks_diff_bus_diff_weights(self):
+        """
+        test the results when there are two slacks, in less simple setting
+        where the generator do not have the same weight
+        """
+        # now activate more slack bus
+        gen_id_added = 1
+        self.net.gen["slack"][[gen_id_added]] = True
+        id_ref_slack = self.net.gen.shape[0] - 1
+        if "slack_weight" in self.net.gen:
+            self.net.gen["slack_weight"][[gen_id_added, id_ref_slack]] = 0.9, 0.3
+        
+        # just to make sure pp forgot previous results
+        pp.runpp(self.net,
+                 distributed_slack=True,
+                 init_vm_pu="flat",
+                 init_va_degree="flat")  
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            ls_grid = init(self.net)
+        V = np.ones(self.nb_bus_total, dtype=np.complex_)
+        V = ls_grid.ac_pf(V, self.max_it, self.tol)
+        self.check_results(V, ls_grid, self.net)
+
+    def test_multiple_slack_same_bus(self):
+        """
+        test the results when there are three slacks, two connected at the same bus
+        """
+
+        # create gens
+        gen_id_added = 1
+        var_gen = [el for el in VAR_GEN if el in self.net.gen]
+        id_ref_slack = self.net.gen.shape[0] - 1
+        pp.create_gen(self.net, **self.net.gen[var_gen].iloc[id_ref_slack])
+        init_p_mw = self.net.gen["p_mw"].values[id_ref_slack]
+        last_gen_id = self.net.gen.shape[0] - 1
+        self.net.gen["slack"][[gen_id_added]] = True
+        self.net.gen["slack"][[last_gen_id]] = True
+        coefs_slack = 0.4, 0.3, 0.6
+        self.net.gen["slack_weight"][[gen_id_added, id_ref_slack, last_gen_id]] = coefs_slack
+        self.net.gen["p_mw"][[id_ref_slack, last_gen_id]] = 0.5 * init_p_mw
+
+        # start the powerflow
+        pp.rundcpp(self.net)
+        pp.runpp(self.net, distributed_slack=True,
+                 init_vm_pu="flat",
+                 init_va_degree="flat") 
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            ls_grid = init(self.net)
+        
+        V = np.ones(self.nb_bus_total, dtype=np.complex_)
+        V = ls_grid.ac_pf(V, self.max_it, self.tol)
+        assert len(V), "lightsim diverged !"
+        # check that the losses have been properly split
+        last = len(ls_grid.get_generators()) - 1
+        last2 = len(ls_grid.get_generators()) - 2
+        res_slack = np.array((ls_grid.get_generators()[last2].res_p_mw , ls_grid.get_generators()[last].res_p_mw ))
+        res_slack -= 0.5 * init_p_mw
+        tartine_0 = ls_grid.get_generators()[gen_id_added].res_p_mw
+        # res_slack = self.net.res_gen["p_mw"].values[[-2, -1]] - 0.5 * init_p_mw
+        # tartine_0 = self.net.res_gen["p_mw"].values[gen_id_added]
+        assert abs(coefs_slack[2] * res_slack[0] - coefs_slack[1] * res_slack[1]) <= 1e-6
+        assert abs(coefs_slack[1] * tartine_0 - coefs_slack[0] * res_slack[0]) <= 1e-6
+
+        self.check_results(V, ls_grid, self.net)
+
+    def test_multiple_slack_one_gen_not_slack(self):
+        """
+        test the results when there are two slacks, but at one node where there is a slack, another non slack gen is connected
+        """
+
+        # create gens
+        gen_id_added = 1
+        var_gen = [el for el in VAR_GEN if el in self.net.gen]
+        id_ref_slack = self.net.gen.shape[0] - 1
+        self.net.gen["slack"][[id_ref_slack]] = False  # this gen will be copied, so i remove it
+        self.net.gen["slack"][[gen_id_added]] = True
+        # create the gen on the same bus as one of the slack
+        pp.create_gen(self.net, **self.net.gen[var_gen].iloc[id_ref_slack])
+        self.net.gen["slack"][[id_ref_slack]] = True  # I reactivate it
+        init_p_mw = self.net.gen["p_mw"].values[id_ref_slack]
+        coefs_slack = 0.1, 0.9
+        self.net.gen["slack_weight"][[gen_id_added, id_ref_slack]] = coefs_slack
+        self.net.gen["p_mw"][[id_ref_slack, id_ref_slack]] = 0.5 * init_p_mw
+
+        # start the powerflow
+        pp.rundcpp(self.net)
+        pp.runpp(self.net, distributed_slack=True,
+                 init_vm_pu="flat",
+                 init_va_degree="flat") 
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            ls_grid = init(self.net)
+        
+        V = np.ones(self.nb_bus_total, dtype=np.complex_)
+        V = ls_grid.ac_pf(V, self.max_it, self.tol)
+        self.check_results(V, ls_grid, self.net)
+
+    def check_results(self, V_ls, ls_grid, pp_net):
+        # NB: the test bellow only works because pandapower and lightsim have the
+        # bus in the same order !
+        assert len(V_ls), "lightsim diverged !"
+        Ybus_me = ls_grid.get_Ybus()
+        Ybus_ref = pp_net._ppc["internal"]["Ybus"]
+        assert np.abs((Ybus_me - Ybus_ref).todense()).max() <= 1e-6, "wrong Ybus"
+
+        # check that the same results as pandapower
+        my_ref = np.where(np.angle(V_ls) == 0.)[0][0]
+        V_pp = pp_net.res_bus["vm_pu"].values * np.exp(1j*np.pi / 180. *  pp_net.res_bus["va_degree"].values)
+        V_pp *= np.exp(-1j * np.angle(V_pp)[my_ref])
+        assert np.abs(V_pp - V_ls).max() <= 1e-6, "wrong voltages"
+        assert np.all(np.abs([el.res_p_or_mw for el in ls_grid.get_lines()] - pp_net.res_line["p_from_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_p_hv_mw for el in ls_grid.get_trafos()] - pp_net.res_trafo["p_hv_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_p_mw for el in ls_grid.get_generators()] - pp_net.res_gen["p_mw"].values) <= 1e-6)
+        assert np.all(np.abs([el.res_q_mvar for el in ls_grid.get_generators()] - pp_net.res_gen["q_mvar"].values) <= 1e-6)
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/lightsim2grid/tests/test_pickleable.py b/lightsim2grid/tests/test_pickleable.py
index e736ff8..bfd6c3c 100644
--- a/lightsim2grid/tests/test_pickleable.py
+++ b/lightsim2grid/tests/test_pickleable.py
@@ -15,7 +15,7 @@
 import numpy as np
 
 from grid2op import make
-from lightsim2grid.LightSimBackend import LightSimBackend
+from lightsim2grid.lightSimBackend import LightSimBackend
 import pdb
 
 
diff --git a/old/test_klu.py b/old/test_klu.py
index 89dbd52..0820d03 100644
--- a/old/test_klu.py
+++ b/old/test_klu.py
@@ -79,21 +79,21 @@
 cpp_solver = KLU4Pandapower()
 # remove the first call with numba, that compiles the code
 pp.runpp(grid1, max_iteration=nb_max_newton_it, numba=True)
-start_time_pp = time.time()
+start_time_pp = time.perf_counter()
 for i in range(nb_iteration):
-    start_time_pp = time.time()
+    start_time_pp = time.perf_counter()
     try:
         pp.runpp(grid1, max_iteration=nb_max_newton_it, numba=True)
     except pp.powerflow.LoadflowNotConverged:
         pass
-    total_time_pp += time.time() - start_time_pp
+    total_time_pp += time.perf_counter() - start_time_pp
     powerflow_time_pp += grid1._ppc['et']
     nb_it_pp = grid1._ppc['iterations']
     sucees_pp = grid1._ppc['success']
-end_time_pp = time.time()
+end_time_pp = time.perf_counter()
 
 for i in range(nb_iteration):
-    start_time_cpp = time.time()
+    start_time_cpp = time.perf_counter()
     try:
         cpp_solver.runpp(grid2, max_iteration=nb_max_newton_it,
                          need_reset=True   # reset the KLU solver to an original state, need to be done each time the Ymatrix is changed (might be slow)
@@ -102,7 +102,7 @@
         if i == 0:
             print("I didn't converge for c++")
         pass
-    total_time_cpp += time.time() - start_time_cpp
+    total_time_cpp += time.perf_counter() - start_time_cpp
 
     powerflow_time_cpp += grid2._ppc['et']
     sucees_cpp = grid2._ppc['success']