Repository: Grid2op/lightsim2grid (language: python)

Issue:
Support the latest cktso sparse solver

CKTSO is the successor of NICSLU, with better performance. Support of CKTSO should be beneficial for pushing the computing of power flow to the next level.
Feature Request : Multi-Slack  (Non Distributed) 
Hello Mr Bonnot,

We are using PandaPower to perform our power flow calculations, we have discovered LightSim2grid via PandaPower, we are interested in using it because of it's good shown performance.

We have several sub-stations which means several slacks non distributed, a use case example is mv_oberrhein in PandaPower Network, we have remarked that LightSim2grid is not activated for this use case.  We would like to use it in our works in order to help us to improve the speed performance.

Is there any way to use LighSim2Grid on a multi-slack (Non Distributed) network ?, if yes, is it easy to handle ?, are you planning to add this feature in the future ?

Thank you so much in advance.

Regards




Discussion / hints:
Hello,

This is a nice news.

By any chance, would you have the time to make a pull request to include it ? I will unfortunately not have the time to do it in 2022.
The code example are: https://github.com/BDonnot/lightsim2grid/blob/master/src/NICSLUSolver.h and https://github.com/BDonnot/lightsim2grid/blob/master/src/NICSLUSolver.cpp 

This should be relatively quick to do if the new method is similar to NICSLU (which I suspect if CKTSO is the successor of NICSLU)

In any case, thanks for the information.

Benjamin

Hello,

Thanks for your interest in this package.

Currently only single or distributed slack i'are supported. I will have a lookin how to add multi slacks. And depending on how complicated it is I might add the feature in the future.

Note however that this will take some time (best case scenario I can look end of September) as I cannot work on lightsim2grid at the moment.

Benjamin 
Hello Bonnot,

Hope you are doing well, did you have some time to check the feasibility of introducing Multi-Slack on Lightsim2grid ? can you share with us your update and plans? thank you in advance.

Regards
Hello,

I am on parental leave until beginning of December. And then I have a 2 weeks holiday for Christmas.

I cannot work on this issue before early 2023 at best. Any pull request will drastically reduce the time it takes for me to do it.

I think it should be possible to implement it rather easily as I'm pretty sure it does not require heavy development.

If I may ask, out of curiosity, why would you like multiple slack in a non distributed way? This model does not really make sense for me but I'm not a power system expert (but I'm really curious 😉) 

Best

Benjamin 
Hello,

Congratulation for the new baby 🎉, and thanks for your interest. In fact, it's for the power flow calculation of several sub-stations with each one having its own Slack, it will be very quick to compute PF for all sub-stations at once instead of each one separately, Pandapower actually handles this parallel computation of PF (power flow) over multiple sub-stations, this is much faster compared to the sequential computation of PF for those multiple sub-stations.

you can imagine the task as parallelization of newton-raphson computation over multiple grids.

Thanks 
Thanks 😊


Oh OK I see. In this case it makes perfect sense indeed. I'll see what I can do asap (but again probably not in 2022 :-/)

Best

Benjamin 
Hello,
I think i just need to change one single function in the c++ side so it should be easy. By any chance, do you have any file you are using (for the grid) and script (for the code) so that i can make experiments with it asap ?

If not i will actually spend more time to find a suitable grid and then create a code with pandapower to do what you want to do rather than to code the actual feature in lightsim2grid.

Thanks

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index fedaa1d..4671900 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -183,7 +183,7 @@ jobs:
         resource_class: small
         steps:
             - checkout
-            - run: apt-get update && apt-get install python3-pip -y
+            - run: apt-get update && apt-get install python3-pip git -y
             - run: python3 -m pip install virtualenv
             - run: python3 -m virtualenv venv_test
             - run:
@@ -201,7 +201,7 @@ jobs:
         resource_class: small
         steps:
             - checkout
-            - run: apt-get update && apt-get install python3-pip -y
+            - run: apt-get update && apt-get install python3-pip git -y
             - run: python3 -m pip install virtualenv
             - run: python3 -m virtualenv venv_test
             - run:
@@ -219,7 +219,7 @@ jobs:
         resource_class: small
         steps:
             - checkout
-            - run: apt-get update && apt-get install python3-pip -y
+            - run: apt-get update && apt-get install python3-pip git -y
             - run: python3 -m pip install virtualenv
             - run: python3 -m virtualenv venv_test
             - run:
@@ -237,7 +237,7 @@ jobs:
         resource_class: small
         steps:
             - checkout
-            - run: apt-get update && apt-get install python3-pip -y
+            - run: apt-get update && apt-get install python3-pip git -y
             - run: python3 -m pip install virtualenv
             - run: python3 -m virtualenv venv_test
             - run:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index cf497c0..bd576c0 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -61,18 +61,22 @@ jobs:
 
       - name: Build wheel
         run: |
-          export __O3_OPTIM=1
+          # export __O3_OPTIM=1
           python3 setup.py bdist_wheel
           auditwheel repair dist/*.whl
 
       - name: Install wheel
         run: pip3 install wheelhouse/*.whl --user
 
+      - name: Install GDB
+        run: yum install -y gdb
+
       - name: Check package can be imported
         run: |
-          python3 -c "import lightsim2grid"
-          python3 -c "from lightsim2grid import *"
-          python3 -c "from lightsim2grid.newtonpf import newtonpf"
+          # python3 -c "import lightsim2grid"
+          # python3 -c "from lightsim2grid import *"
+          # python3 -c "from lightsim2grid.newtonpf import newtonpf"
+          gdb -batch -ex r --args python3 -c "from lightsim2grid import *" -ex backtrace
 
       - name: Check LightSimBackend can be imported
         run: |
diff --git a/.gitignore b/.gitignore
index 687764c..9b18bfa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -217,6 +217,7 @@ benchmarks/debug_speed_improvments.py
 perf.data
 perf.data.old
 test_profile.py.lprof
+benchmarks/case14.json
 benchmarks/case9241pegase.json
 benchmarks/case118.json
 benchmarks/case_illinois200.json
@@ -228,6 +229,22 @@ benchmarks/case2848rte.json
 benchmarks/case2869pegase.json
 benchmarks/case3120sp.json
 benchmarks/case6495rte.json
+benchmarks/case6515rte.json
+benchmarks/case9241pegase.json.old
+benchmarks/case118.json.old
+benchmarks/case_illinois200.json.old
+benchmarks/case300.json.old
+benchmarks/case1354pegase.json.old
+benchmarks/case1888rte.json.old
+benchmarks/GBnetwork.json.old
+benchmarks/case2848rte.json.old
+benchmarks/case2869pegase.json.old
+benchmarks/case3120sp.json.old
+benchmarks/case6495rte.json.old
+benchmarks/case6515rte.json.old
+benchmarks/benchmark.png
+benchmarks/benchmark2.png
+cktso
 _pandapower
 benchmarks/slacks.py
 _gridcal
@@ -240,6 +257,9 @@ lightsim2grid/tests/bug_file.zip
 lightsim2grid/tests/test.json
 pyproject.toml.old
 test_bug_as.py
-**cktso.lic
 cktso
-test_milad.py
\ No newline at end of file
+**cktso.lic
+nicslu
+**nicslu.lic
+test_milad.py
+test_issue_50.py
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 31f58a6..b8acd9c 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -3,10 +3,14 @@ Change Log
 
 [TODO]
 --------
+- make an `init` function from pypowsybl
+- support DC powerline (as modeled in pandapower)
+- support 3w trafo (as modeled in pandapower)
 - 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
 - code parrallelism directly in the `Computer` and `SecurityAnalysisCPP` classes
+- use the "multi slack hack" (see issue #50) for SecurityAnalysis or Computer for example
 - code `helm` powerflow method
 - possibility to read CGMES files
 - possibility to read XIIDM files
@@ -15,6 +19,29 @@ Change Log
 - easier building (get rid of the "make" part)
 - code NR with dense matrices
 
+[0.7.2] 2023-06-06
+--------------------
+- [FIXED] a bug in the `init` function that caused issue when importing a grid with multiple slack
+  on some cases
+- [FIXED] some bugs in the "SecurityAnalysis" and "TimeSerie" modules especially in DC mode.
+- [FIXED] a bug in the DC comptuation: some "divergence" were not catched
+- [FIXED] a bug in the "Computer" (cpp) class where the intial voltage could lead to generator not
+  participating correctly to the voltage regulation (wrong output voltage level).
+- [FIXED] a bug in the "set_bus" of shunt (wrong bus was assigned cpp side)
+- [FIXED] an issue when slack bus is added from ext grid (wrong active power value - sign issue)
+- [ADDED] support for the CKTSO linear solver (on linux), which is slightly faster than SparseLU, KLU and NICSLU
+  (this requires a compilation from source)
+- [ADDED] support for distributed slack bus in `LightSimBackend`
+- [ADDED] support for "generator with p=0. do not participate in voltage regulation" in `LightSimBackend`
+- [ADDED] support for the DC computation for "SecurityAnalysis" and "TimeSerie" modules
+- [ADDED] support for DC powerline (in lightsim, they are still not handled in grid2op)
+- [IMPROVED] now that multiple slacks is fully supported, the warnings when importing a grid with multiple slacks
+  are irrelevant. They have been removed.
+- [IMPROVED] the documentation on the "sovlers" part
+- [IMPROVED] move the "how to compile" section of the readme in the documentation
+- [IMPROVED] `SuiteSparse` is upgraded to version 5.13 (issue with build system based on cmake and BLAS for SuiteSparse >= 6.0)
+- [IMPROVED] upgrade to eigen `3.4.0` (stable release)
+
 [0.7.1] 2023-01-11
 ---------------------
 - [BREAKING] drop support for numpy version < 1.20 (to be consistent with grid2op)
@@ -110,7 +137,7 @@ Change Log
 - [FIXED] An issue where the backend could get "stuck" in a wrong state because of the way the Vinit was computed (see
   `Issue 30 <https://github.com/BDonnot/lightsim2grid/issues/30>`_)
 - [ADDED] experimental support for the `NICSLU` linear solver (requires a proper license and library, see
-  https://github.com/chenxm1986/nicslu for more information. Support does not include multi threaded at the moment.
+  https://github.com/chenxm1986/nicslu for more information. Support does not include multi threaded at the moment).
 - [IMPROVED] minor performance improvements for the solvers based on Newton Raphson (faster filling of the Jacobian
   matrix after the first iteration)
 
diff --git a/Makefile b/Makefile
index a8a4603..5ef3f93 100644
--- a/Makefile
+++ b/Makefile
@@ -54,12 +54,12 @@ distclean: purge
 all: prelude
 
 prelude:
-	(cd $(LIBPATH)/SuiteSparse_config/ && make CC=gcc)
-	(cd $(LIBPATH)/CXSparse/Lib/ && make CC=gcc)
-	(cd $(LIBPATH)/AMD/Lib/ && make CC=gcc)
-	(cd $(LIBPATH)/BTF/Lib/ && make CC=gcc)
-	(cd $(LIBPATH)/COLAMD/Lib/ && make CC=gcc)
-	(cd $(LIBPATH)/KLU/Lib/ && make CC=gcc)
+	(cd $(LIBPATH)/SuiteSparse_config/ && CC=gcc make)
+	(cd $(LIBPATH)/CXSparse/Lib/ && CC=gcc make )
+	(cd $(LIBPATH)/AMD/Lib/ && Cc=gcc make)
+	(cd $(LIBPATH)/BTF/Lib/ && CC=gcc make)
+	(cd $(LIBPATH)/COLAMD/Lib/ && CC=gcc make)
+	(cd $(LIBPATH)/KLU/Lib/ && CC=gcc make)
 
 ##################################
 # this is the documentation
diff --git a/README.md b/README.md
index dd962d1..b94f0c2 100644
--- a/README.md
+++ b/README.md
@@ -6,20 +6,11 @@ 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)
@@ -29,6 +20,7 @@ this package should not be used for detailed power system computations or simula
     * [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
@@ -95,139 +87,8 @@ Pypi packages are available for linux, windows and macos with python versions:
 - 3.10 (lightsim2grid >= 0.6.1)
 
 ## Installation (from source, for more advanced user)
-You need 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
-
-### Important note
-This package relies on the excellent `pybind11` package to integrate c++ code into python easily. 
-
-So to install lightsim2grid you need `pybind11` and its requirement, which include a working compiler: for example 
-(as of writing) 
-gcc (default on ubuntu, version >= 4.8), clang (default on MacOS, version >= 5.0.0) or 
-Microsoft visual studio (Microsoft Visual Studio 2015 Update 3 or newer). 
-
-This readme does not cover the install of such compilers. Please refer to the documentation of 
-[pybind11](https://pybind11.readthedocs.io/en/latest/) for more information. Do not hesitate to write github issues
-if you encounter a problem in installing such compiler (**nb** on windows you have to install
-visual studio, on linux of MacOs you might already have a working compiler installed).
-
-### 1. Retrieve the sources
-First, you can download it with git with:
-```commandline
-git clone https://github.com/BDonnot/lightsim2grid.git
-cd lightsim2grid
-# it is recommended to do a python virtual environment
-python -m virtualenv venv  # optional
-source venv/bin/activate  # optional
-
-# retrieve the code of SparseSuite and Eigen (dependencies, mandatory)
-git submodule init
-git submodule update
-```
-
-### (optional, recommended) Compilation of SuiteSparse
-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. You can 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).
-
-**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
-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.
-
-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
-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"](#\(optional\)-option-B.-Compilation-of-SuiteSparse-using-"cmake")
-
-Anyway, in this case, it's super easy. Just do:
-
-```commandline
-# compile static libraries of SparseSuite
-make
-```
-And you are good to go. Nothing more.
-
-#### (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).
-
-### (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:
-
-```commandline
-export __O3_OPTIM=1
-export __COMPILE_MARCHNATIVE=1
-```
-
-If you want to disable them, you simply need to set their respective value to 0.
-
-### 2. Installation of the python package
-Now you simply need to install the lightsim2grid package this way, like any python package:
-
-```commandline
-# install the dependency
-pip install -U pybind11
-# compile and install the python package
-pip install -U .
-```
-
-And you are done :-)
 
+See the official documentation at [Install from source](https://lightsim2grid.readthedocs.io/en/latest/install_from_source.html) for more information
 
 ## Benchmarks
 
@@ -372,6 +233,11 @@ For that, you need to declare the environment variables `PATH_NICSLU` that point
 the NICSLU package (see https://github.com/chenxm1986/nicslu). 
 For example: `export PATH_NICSLU=/home/user/Documents/nicslu/nicslu202103`
 
+#### Enable CKTSO
+For that, you need to declare the environment variables `PATH_CKTSO` that points to a valid installation of
+the NICSLU package (see https://github.com/chenxm1986/cktso). 
+For example: `export PATH_NICSLU=/home/user/Documents/cktso`
+
 #### Enable 03 optimization
 By default, at least on ubuntu, only the "-O2" compiler flags is used. To use the O3 optimization flag, you need
 to specify the `__COMPLILE_O3` environment variable: `set __COMPLILE_O3=1` before the compilation (so before
diff --git a/SuiteSparse b/SuiteSparse
index 538273c..f8ed019 160000
--- a/SuiteSparse
+++ b/SuiteSparse
@@ -1,1 +1,1 @@
-Subproject commit 538273cfd53720a10e34a3d80d3779b607e1ac26
+Subproject commit f8ed0196d35a98caacdf660ac55765915f67298d
diff --git a/benchmarks/benchmark_grid_size.py b/benchmarks/benchmark_grid_size.py
new file mode 100644
index 0000000..b7a15ad
--- /dev/null
+++ b/benchmarks/benchmark_grid_size.py
@@ -0,0 +1,379 @@
+# 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
+from scipy.interpolate import interp1d
+import matplotlib.pyplot as plt
+from grid2op import make, Parameters
+from grid2op.Chronics import FromNPY
+from lightsim2grid import LightSimBackend, TimeSerie, SecurityAnalysis
+from tqdm import tqdm
+import os
+from utils_benchmark import print_configuration, get_env_name_displayed
+from benchmark_solvers import solver_names
+
+try:
+    from tabulate import tabulate
+    TABULATE_AVAIL = True
+except ImportError:
+    print("The tabulate package is not installed. Some output might not work properly")
+    TABULATE_AVAIL = False
+    
+VERBOSE = False
+MAKE_PLOT = False
+
+case_names = [
+              "case14.json",
+              "case118.json",
+              "case_illinois200.json",
+              "case300.json",
+              "case1354pegase.json",
+              "case1888rte.json",
+              "GBnetwork.json",  # 2224 buses
+              "case2848rte.json",
+              "case2869pegase.json",
+              "case3120sp.json",
+              "case6495rte.json",
+              "case6515rte.json",
+              "case9241pegase.json"
+              ]
+
+def make_grid2op_env(pp_case, casse_name, load_p, load_q, gen_p, sgen_p):
+    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(),
+                            chronics_class=FromNPY,
+                            data_feeding_kwargs={"load_p": load_p,
+                                                 "load_q": load_q,
+                                                 "prod_p": gen_p
+                                                },
+                            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"]["oct"] * 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)
+    
+    case_names_displayed = [get_env_name_displayed(el) for el in case_names]
+    g2op_times = []
+    g2op_speeds = []
+    g2op_sizes = []
+    g2op_step_time = []
+    
+    ts_times = []
+    ts_speeds = []
+    ts_sizes = []
+    sa_times = []
+    sa_speeds = []
+    sa_sizes = []
+    for case_name in tqdm(case_names):
+
+        if not os.path.exists(case_name):
+            import pandapower.networks as pn
+            case = getattr(pn, os.path.splitext(case_name)[0])()
+            pp.to_json(case, case_name)
+
+        # load the case file
+        case = pp.from_json(case_name)
+        pp.runpp(case)  # for slack
+        
+        # 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
+        nb_ts = gen_p.shape[0]
+        # add slack !
+        slack_gens =  np.zeros((nb_ts, case.ext_grid.shape[0]))
+        if "res_ext_grid" in case:
+            slack_gens += np.tile(case.res_ext_grid["p_mw"].values.reshape(1,-1), (nb_ts, 1))
+        gen_p_g2op = np.concatenate((gen_p, slack_gens), axis=1)  
+        # get the env
+        env_lightsim = make_grid2op_env(case,
+                                        case_name,
+                                        load_p,
+                                        load_q,
+                                        gen_p_g2op,
+                                        sgen_p)
+        
+        
+        # Perform the computation using grid2op
+        _ = env_lightsim.reset()
+        done = False
+        nb_step = 0
+        changed_sgen = case.sgen["in_service"].values
+        while not done:
+            # hack for static gen...
+            env_lightsim.backend._grid.update_sgens_p(changed_sgen,
+                                                      sgen_p[nb_step, changed_sgen].astype(np.float32))
+            obs, reward, done, info = env_lightsim.step(env_lightsim.action_space())
+            nb_step += 1
+        # NB lightsim2grid does not handle "static gen" because I cannot set "p" in gen in grid2op
+        # so results will vary between TimeSeries and grid2op !
+            
+        if nb_step != nb_ts:
+            warnings.warn(f"only able to make {nb_step} (out of {nb_ts}) for {case_name} in grid2op. Results will not be availabe for grid2op step")
+            g2op_times.append(None)
+            g2op_speeds.append(None)
+            g2op_step_time.append(None)
+            g2op_sizes.append(env_lightsim.n_sub)
+        else:
+            total_time = env_lightsim.backend._timer_preproc + env_lightsim.backend._timer_solver # + env_lightsim.backend._timer_postproc
+            g2op_times.append(total_time)
+            g2op_speeds.append(1.0 * nb_step / total_time)
+            g2op_step_time.append(1.0 * env_lightsim._time_step / nb_step)
+            g2op_sizes.append(env_lightsim.n_sub)
+        
+        # Perform the computation using TimeSerie
+        env_lightsim.reset()
+        time_serie = TimeSerie(env_lightsim)
+        computer = time_serie.computer
+        computer_ts = 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}: {computer.nb_solved()} "
+
+        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)")
+        
+        ts_times.append(computer.total_time() + computer.amps_computation_time())
+        ts_speeds.append(computer.nb_solved() / (computer.total_time() + computer.amps_computation_time()) )
+        ts_sizes.append(env_lightsim.n_sub)
+
+        # Perform a securtiy analysis (up to 1000 contingencies)
+        env_lightsim.reset()
+        sa = SecurityAnalysis(env_lightsim)
+        for i in range(env_lightsim.n_line):
+            sa.add_single_contingency(i)
+            if i >= 1000:
+                break
+        p_or, a_or, voltages = sa.get_flows()
+        computer_sa = sa.computer
+        sa_times.append(computer_sa.total_time() + computer_sa.amps_computation_time())
+        sa_speeds.append(computer_sa.nb_solved() / (computer_sa.total_time() + computer_sa.amps_computation_time()) )
+        sa_sizes.append(env_lightsim.n_sub)
+                
+        # close the env
+        linear_solver_used_str = solver_names[env_lightsim.backend._grid.get_solver_type()]
+        env_lightsim.close()
+
+    print("Configuration:")
+    print_configuration()
+    print(f"Solver used for linear algebra: {linear_solver_used_str}")
+    print()
+    
+    print("Results using grid2op.steps (288 consecutive steps, only measuring 'dc pf [init] + ac pf')")
+    tab_g2op = []
+    for i, nm_ in enumerate(case_names_displayed):
+        tab_g2op.append((nm_, ts_sizes[i], 1000. / g2op_speeds[i] if g2op_speeds[i] else None, g2op_speeds[i],
+                         1000. * g2op_step_time[i] if g2op_step_time[i] else None))
+    if TABULATE_AVAIL:
+        res_use_with_grid2op_2 = tabulate(tab_g2op,
+                                          headers=["grid", "size (nb bus)", "time (ms / pf)", "speed (pf / s)", "avg step duration (ms)"], 
+                                          tablefmt="rst")
+        print(res_use_with_grid2op_2)
+    else:
+        print(tab_g2op)
+    print()
+        
+        
+    print("Results for TimeSeries (288 consecutive steps)")
+    tab_ts = []
+    for i, nm_ in enumerate(case_names_displayed):
+        tab_ts.append((nm_, ts_sizes[i], 1000. / ts_speeds[i] if ts_speeds[i] else None, ts_speeds[i]))
+    if TABULATE_AVAIL:
+        res_use_with_grid2op_2 = tabulate(tab_ts,
+                                          headers=["grid", "size (nb bus)", "time (ms / pf)", "speed (pf / s)"], 
+                                          tablefmt="rst")
+        print(res_use_with_grid2op_2)
+    else:
+        print(tab_ts)
+    print()
+
+    print("Results for Contingency Analysis (up to 1000 contingencies - or all the powerlines)")
+    tab_sa = []
+    for i, nm_ in enumerate(case_names_displayed):
+        tab_sa.append((nm_, sa_sizes[i], 1000. / sa_speeds[i] if sa_speeds[i] else None, sa_speeds[i]))
+    if TABULATE_AVAIL:
+        res_use_with_grid2op_2 = tabulate(tab_sa,
+                                          headers=["grid", "size (nb bus)", "time (ms / cont.)", "speed (cont. / s)"], 
+                                          tablefmt="rst")
+        print(res_use_with_grid2op_2)
+    else:
+        print(tab_sa)
+    print()
+        
+    if MAKE_PLOT:
+        # make the plot summarizing all results
+        plt.plot(g2op_sizes, g2op_times, linestyle='solid', marker='+', markersize=8)
+        plt.xlabel("Size (number of substation)")
+        plt.ylabel("Time taken (s)")
+        plt.title(f"Time to compute {g2op_sizes[0]} powerflows using Grid2Op.step (dc pf [init] + ac pf)")
+        plt.show()
+
+        plt.plot(g2op_sizes, g2op_speeds, linestyle='solid', marker='+', markersize=8)
+        plt.xlabel("Size (number of substation)")
+        plt.ylabel("Speed (pf / s)")
+        plt.title(f"Computation speed using Grid2Op.step (dc pf [init] + ac pf)")
+        plt.yscale("log")
+        plt.show()
+        
+        # make the plot summarizing all results
+        plt.plot(ts_sizes, ts_times, linestyle='solid', marker='+', markersize=8)
+        plt.xlabel("Size (number of substation)")
+        plt.ylabel("Time taken (s)")
+        plt.title(f"Time to compute {computer_ts.nb_solved()} powerflows using TimeSeries")
+        plt.show()
+
+        plt.plot(ts_sizes, ts_speeds, linestyle='solid', marker='+', markersize=8)
+        plt.xlabel("Size (number of substation)")
+        plt.ylabel("Speed (pf / s)")
+        plt.title(f"Computation speed for TimeSeries")
+        plt.yscale("log")
+        plt.show()
+        
+        # make the plot summarizing all results
+        plt.plot(sa_sizes, [1000. / el for el in sa_speeds], linestyle='solid', marker='+', markersize=8)
+        plt.xlabel("Size (number of substation)")
+        plt.ylabel("Time taken per contingency (ms)")
+        plt.title(f"Average time per contingencies")
+        plt.show()
+
+        plt.plot(sa_sizes, sa_speeds, linestyle='solid', marker='+', markersize=8)
+        plt.xlabel("Size (number of substation)")
+        plt.ylabel("Speed (contingency / s)")
+        plt.title(f"Computation speed for Security Analysis")
+        plt.yscale("log")
+        plt.show()
diff --git a/benchmarks/benchmark_solvers.py b/benchmarks/benchmark_solvers.py
index 9a6bb30..ae8ed89 100644
--- a/benchmarks/benchmark_solvers.py
+++ b/benchmarks/benchmark_solvers.py
@@ -46,6 +46,8 @@
                 lightsim2grid.SolverType.SparseLUSingleSlack: "LS+SLU (single)",
                 lightsim2grid.SolverType.KLUSingleSlack: "LS+KLU (single)",
                 lightsim2grid.SolverType.NICSLUSingleSlack: "LS+NICSLU (single)",
+                lightsim2grid.SolverType.CKTSO: "LS+CKTSO",
+                lightsim2grid.SolverType.CKTSOSingleSlack: "LS+CKTSO (single)",
                 # lightsim2grid.SolverType.DC: "LS+DC",
                 # lightsim2grid.SolverType.KLUDC: "LS+SLU",
                 # lightsim2grid.SolverType.NICSLUDC: "LS+SLU"
@@ -62,6 +64,8 @@
     lightsim2grid.SolverType.KLU,
     lightsim2grid.SolverType.NICSLUSingleSlack,
     lightsim2grid.SolverType.NICSLU,
+    lightsim2grid.SolverType.CKTSOSingleSlack,
+    lightsim2grid.SolverType.CKTSO,
 ]
 
 
diff --git a/benchmarks/grid_size.py b/benchmarks/grid_size.py
deleted file mode 100644
index 95ec71b..0000000
--- a/benchmarks/grid_size.py
+++ /dev/null
@@ -1,217 +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 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
-import os
-
-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):
-
-        if not os.path.exists(case_name):
-            import pandapower.networks as pn
-            case = getattr(pn, os.path.splitext(case_name)[0])()
-            pp.to_json(case, case_name)
-
-        # 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.yscale("log")
-    plt.show()
diff --git a/benchmarks/utils_benchmark.py b/benchmarks/utils_benchmark.py
index b7246df..1286737 100644
--- a/benchmarks/utils_benchmark.py
+++ b/benchmarks/utils_benchmark.py
@@ -19,6 +19,7 @@ def get_env_name_displayed(env_name):
     res = re.sub("^l2rpn_", "", env_name)
     res = re.sub("_small$", "", res)
     res = re.sub("_large$", "", res)
+    res = re.sub("\\.json$", "", res)
     return res
 
 
diff --git a/docs/benchmarks.rst b/docs/benchmarks.rst
index 83371c8..f363b29 100644
--- a/docs/benchmarks.rst
+++ b/docs/benchmarks.rst
@@ -39,7 +39,7 @@ To run the benchmark `cd` in the [benchmark](./benchmarks) folder and type:
 (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:
+We compare up to 11 different solvers:
 
 - **PP**: PandaPowerBackend (default grid2op backend) which is the reference in our benchmarks (uses the numba
   acceleration). It is our reference solver.
@@ -63,8 +63,15 @@ We compare up to 9 different solvers:
   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.
+- **LS+CKTSO** (Newton Raphson+CKTSO): he grid2op backend based on lightsim2grid that uses the 
+  "Newton Raphson" algorithm coupled with the linear solver 
+  "CKTSO". [**NB** CKTSO 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+CKTSO (single)** (Newton Raphson+CKTSO): 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.
+All benchmarks where done with all the customization (for speed, *eg* `-O3` and `-march=native` for linux). 
+See the readme for more information.
 
 Computation time
 ~~~~~~~~~~~~~~~~~~~
diff --git a/docs/conf.py b/docs/conf.py
index 73d4b53..80ab3e8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -22,7 +22,7 @@
 author = 'Benjamin DONNOT'
 
 # The full version, including alpha/beta/rc tags
-release = '0.7.1'
+release = "0.7.2"
 version = '0.7'
 
 # -- General configuration ---------------------------------------------------
diff --git a/docs/gridmodel.rst b/docs/gridmodel.rst
index e22abfd..2a9fa69 100644
--- a/docs/gridmodel.rst
+++ b/docs/gridmodel.rst
@@ -103,6 +103,18 @@ Transformers
     :autosummary:
 
 
+DC Powerlines (more exotic)
+++++++++++++++++++++++++++++
+
+.. autoclass:: lightsim2grid.elements.DataDCLine
+    :members:
+    :autosummary:
+
+.. autoclass:: lightsim2grid.elements.DCLineInfo
+    :members:
+    :autosummary:
+
+
 Detailed documentation
 --------------------------
 
diff --git a/docs/index.rst b/docs/index.rst
index 72639bf..f13c36d 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -43,6 +43,7 @@ This is a work in progress at the moment
    :caption: Technical Documentation
 
    gridmodel
+   install_from_source
    lightsimbackend
    solvers
    time_series
diff --git a/docs/install_from_source.rst b/docs/install_from_source.rst
new file mode 100644
index 0000000..10e8447
--- /dev/null
+++ b/docs/install_from_source.rst
@@ -0,0 +1,199 @@
+Installation from source
+==========================
+
+.. toctree::
+  :maxdepth: 2
+  :numbered:
+  :caption: Main Steps
+
+At a glance, to install from source you will have to:
+
+- :ref:`clone_repo` clone this repository and get the code of Eigen (mandatory for compilation) and SparseSuite (optional, but recommended)
+
+  - :ref:`compile_suitesparse` compile a piece of SparseSuite
+  - :ref:`include_nicslu`: retrieve and get a proper license for the NICSLU linear solver (see https://github.com/chenxm1986/nicslu)
+  - :ref:`include_cktso`: retrieve and get a proper license for the CKTSO linear solver (see https://github.com/chenxm1986/cktso)
+  - :ref:`other_customization` specify some compilation flags to make the package run faster on your machine see 
+
+- :ref:`install_python`: install the python package see 
+
+
+Important note
+----------------
+This package relies on the excellent `pybind11` package to integrate c++ code into python easily. 
+
+So to install lightsim2grid you need `pybind11` and its requirement, which include a working compiler: for example 
+(as of writing) 
+gcc (default on ubuntu, version >= 4.8), clang (default on MacOS, version >= 5.0.0) or 
+Microsoft visual studio (Microsoft Visual Studio 2015 Update 3 or newer). 
+
+This readme does not cover the install of such compilers. Please refer to the documentation of 
+`pybind11 <https://pybind11.readthedocs.io/en/latest/>`_ for more information. Do not hesitate to write github issues
+if you encounter a problem in installing such compiler (**nb** on windows you have to install
+visual studio, on linux of MacOs you might already have a working compiler installed).
+
+.. _clone_repo:
+
+1. Retrieve the sources
+--------------------------
+First, you can download it with git with:
+
+.. code-block::
+
+    git clone https://github.com/BDonnot/lightsim2grid.git
+    cd lightsim2grid
+    # it is recommended to do a python virtual environment
+    python -m virtualenv venv  # optional
+    source venv/bin/activate  # optional
+
+    # retrieve the code of SparseSuite and Eigen (dependencies, mandatory)
+    git submodule init
+    git submodule update
+
+.. _compile_suitesparse:
+
+(optional, recommended) Compilation of SuiteSparse
+++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+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. You can 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).
+
+**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
+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.
+
+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
+available (*eg* Linux or MacOS) but this will not work on Windows... The compilation on windows is covered in the next
+paragraph :ref:`optionB`
+
+Anyway, in this case, it's super easy. Just do:
+
+.. code-block::
+
+    make
+
+And you are good to go. Nothing more.
+
+.. _optionB:
+
+(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).
+
+.. _include_nicslu:
+
+(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).
+
+.. _include_cktso:
+
+(optional) Include CKTSO linear solver (experimental)
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+
+Another linear solver that can be used with lighsim2grid is the "CKTSO" linear solver (a newer version of NICSLU) that might, 
+in some cases, be even faster than the NICSLU 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/cktso and save
+   it on your machine. Say you clone this github repository in `$CKTSO_GIT` 
+   (*eg* CKTSO_GIT="/home/user/Documents/cktso/"). Also note that you need to check that your usage
+   is compliant with their license !
+2) define the "PATH_CKTSO" environment variable **before** compiling lightsim2grid, on linux you can do
+   `export PATH_CKTSO=$CKTSO_GIT` 
+   (for example `export PATH_CKTSO=/home/user/Documents/cktso` if you cloned the repository 
+   as the example of `step 1`)
+
+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 as the library used when ligthsim2grid is compiled. It should be on `$CKTSO_GIT/ubuntu1804_x64_gcc750`
+on linux and `$CKTSO_GIT/win7_x64` on windows.
+
+.. _other_customization:
+
+(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::
+
+    export __O3_OPTIM=1
+    export __COMPILE_MARCHNATIVE=1
+
+If you want to disable them, you simply need to set their respective value to 0.
+
+.. _install_python:
+
+2. Installation of the python package
+----------------------------------------
+
+Now you simply need to install the lightsim2grid package this way, like any python package:
+
+.. code-block::
+
+    # install the dependency
+    pip install -U pybind11
+    # compile and install the python package
+    pip install -U .
+
+And you are done :-)
+
+
diff --git a/docs/lightsimbackend.rst b/docs/lightsimbackend.rst
index 839bf4a..62f333c 100644
--- a/docs/lightsimbackend.rst
+++ b/docs/lightsimbackend.rst
@@ -43,6 +43,47 @@ 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`
 
+Customization of the backend
+-------------------------------
+.. warning::
+    Use grid2op > 1.7.1 for this feature to work properly. Otherwise some bugs (hard to detect) will occur.
+
+You can customize the way the backend behaves in different ways:
+
+- `max_iter`: maximum number of iterations you allow the solver to perform. If a valid solution to the Kirchhoff Current Laws (KCL)
+  is not found after this number of iterations, then the backend will "diverge". Default is 10 which is a good value for medium size
+  powergrid if you use a Newton Raphson based method (default)
+- `tol`: During its internal iterations, the underlying solver will say the Kirchhoff Current Laws (KCL) are matched if the 
+  maximum value of the difference is lower than this. Default is `1e-8`.
+- `solver_type`: which type of "solver" you want to use. See :ref:`solvers_doc` for more information. By default it uses 
+  what it considers the fastest solver available which is likely to be :class:`lightsim2grid.solver.SolverType.KLUSolverSingleSlack`
+- `turned_off_pv` : by default (set to `turned_off_pv=True`) all generators partipate in the voltage regulation, which is not completely realistic.
+  When you initialize a backend with `turned_off_pv=False` then the generators that do not produce power (*eg* "p=0.") or that are
+  turned off are excluded from the voltage regulation.
+- `dist_slack_non_renew`: by default in most grid2op environment, the slack bus is "centralize" / "single slack". This parameters
+  allows to bypass this restriction and use all non renewable generators (and turned on and with  > 0.) in a distributed
+  slack bus setting. It might change the default `solver_type` used.
+- \* `detailed_infos_for_cascading_failures`: for exhaustivity, do not modify.
+- \* `can_be_copied`: for exhaustivity, do not modify.
+
+The easiest way to customize your backend is when you create the grid2op environment, like this:
+
+.. code-block:: python
+
+    import grid2op
+    import lightsim2grid
+    from lightsim2grid import LightSimBackend
+
+    env_name = ...
+    env = grid2op.make(env_name,
+                       backend=LightSimBackend(
+                        max_iter=15,
+                        tol=1e-9,
+                        solver_type=lightsim2grid.solver.SolverType.KLUSolverSingleSlack,
+                        # etc.
+                        )
+                      )
+
 Detailed documentation
 --------------------------
 
diff --git a/docs/security_analysis.rst b/docs/security_analysis.rst
index f86e176..9e568c2 100644
--- a/docs/security_analysis.rst
+++ b/docs/security_analysis.rst
@@ -34,11 +34,13 @@ It can be used as:
 
     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.
-    # you can retrieve it with `security_analysis.contingency_order[row_id]`
+    # res_p[row_id] will be the active power flows (origin side), on all powerlines corresponding to the `row_id` contingency.
+    # res_a[row_id] will be the current flows, on all powerlines corresponding to step "row_id"
+    # res_v[row_id] will be the complex voltage, on all bus of the grid corresponding to the `row_id` contingency.
+    # you can retrieve which contingency is id'ed `row_id` with `security_analysis.contingency_order[row_id]`
 
 For now this relies on grid2op, but we could imagine a version of this class that can read
 to / from other data sources.
diff --git a/docs/solvers.rst b/docs/solvers.rst
index d3f6701..7f6ef75 100644
--- a/docs/solvers.rst
+++ b/docs/solvers.rst
@@ -1,3 +1,7 @@
+.. currentmodule:: lightsim2grid.solver
+
+.. _solvers_doc:
+
 Available "solvers" (doc in progress)
 =======================================
 
@@ -9,28 +13,91 @@ If you are interested in collaborating to improve this section, let us know.
 Type of solvers available
 --------------------------
 
-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.
-- **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.
+In lightsim2grid you can have 4 different types of solvers:
+
+- `GaussSeidel` methods: :class:`lightsim2grid.solver.GaussSeidelSolver` and :class:`lightsim2grid.solver.GaussSeidelSynchSolver`
+  solves the AC powerflow using the Gauss Seidel method (an example of this algorithm is available in the
+  great matpower library here `gausspf <https://matpower.org/docs/ref/matpower5.0/gausspf.html>`_ )
+- `DC` methods: solve the DC approximation of the AC powerflow. To solve them it requires manipulating sparse matrices
+  and you can use different linear algebra library for that. This is why you have up to 4 different DC solvers:
+  :class:`lightsim2grid.solver.DCSolver` (use `Eigen SparseLU <https://eigen.tuxfamily.org/dox/group__SparseLU__Module.html>`_ ),
+  :class:`lightsim2grid.solver.KLUDCSolver` (uses `KLU <https://github.com/DrTimothyAldenDavis/SuiteSparse/tree/dev/KLU>`_ )
+  :class:`lightsim2grid.solver.NICSLUDCSolver` (uses `NICSLU <https://github.com/chenxm1986/nicslu>`_  and requires and license 
+  and to compile lightsim2grid from source)
+  :class:`lightsim2grid.solver.CKTSODCSolver` (uses `CKTSO <https://github.com/chenxm1986/cktso>`_  and requires and license 
+  and to compile lightsim2grid from source)
+- `AC with single slack` methods: solves the AC equations where only one bus is the slack bus (if multiple slack buses are 
+  detected, only the first one will be used as slack bus, the others will be treated as "pv" buses). It also exists
+  in different "flavours" that uses different linear albrea libraries (same as DC) which are: 
+  :class:`lightsim2grid.solver.SparseLUSolverSingleSlack`, :class:`lightsim2grid.solver.KLUSolverSingleSlack`,
+  :class:`lightsim2grid.solver.NICSLUSolverSingleSlack` and :class:`lightsim2grid.solver.CKTSOSolverSingleSlack`
+- `AC with distributed slack` methods: solves the AC equations with multple slack buses. As for DC and AC with single slack,
+  this is avaialble in 4 different flavours (each using internally a different linear albrea solver): 
+  :class:`lightsim2grid.solver.SparseLUSolver`, :class:`lightsim2grid.solver.KLUSolver`,
+  :class:`lightsim2grid.solver.NICSLUSolver` and :class:`lightsim2grid.solver.CKTSOSolver`
+
+.. warning::
+  Solvers based on `NICSLU` and `CKTSO` require a compilation from source. Solvers based on CKTSO are (for now)
+  only tested on linux.
+
+By default, when avaialble, lightsim2grid try to use the `KLU` linear solver, so the :class:`lightsim2grid.solver.KLUDCSolver`,
+:class:`lightsim2grid.solver.KLUSolverSingleSlack` and :class:`lightsim2grid.solver.KLUSolver`. If not available
+(for example if you compiled from source without including the KLU package) it falls back to the "SparseLU" linear solver
+so :class:`lightsim2grid.solver.DCSolver`, :class:`lightsim2grid.solver.SparseLUSolverSingleSlack` and 
+:class:`lightsim2grid.solver.SparseLUSolver`.
+
+If it detects that the grid is "single slack" it uses the "SingleSlack" version (:class:`lightsim2grid.solver.KLUSolverSingleSlack` or
+:class:`lightsim2grid.solver.SparseLUSolverSingleSlack`).
+
+At any moment, you can change the solver used by lightsim2grid with:
+
+.. code-block:: python
+
+    import grid2op
+    import lightsim2grid
+    from lightsim2grid import LightSimBackend
+
+    # create an environment
+    env_name = "l2rpn_case14_sandbox"
+    env_lightsim = grid2op.make(env_name, backend=LightSimBackend())
+
+    env_lightsim.backend.set_solver_type(lightsim2grid.SolverType.KLU)  # for KLU solver
+
+Or alternatively, you can change it when you create the backend:
+
+.. code-block:: python
+
+    import grid2op
+    import lightsim2grid
+    from lightsim2grid import LightSimBackend
+
+    # create an environment
+    env_name = "l2rpn_case14_sandbox"
+    env_lightsim = grid2op.make(env_name,
+                                backend=LightSimBackend(solver_type=lightsim2grid.SolverType.KLU))
+
+The correspondance between the type of solver used (in the above example :class:`lightsim2grid.solver.KLUSolver` )  
+and its "name" in the `lightsim2grid.SolverType` (in the above example `lightsim2grid.SolverType.KLU` ) 
+module is :
+
+========================================================   =============================================================================
+Solver                                                     name in "SolverType"
+========================================================   =============================================================================
+:class:`lightsim2grid.solver.GaussSeidelSolver`            `GaussSeidel` (SolverType.GaussSeidel)
+:class:`lightsim2grid.solver.GaussSeidelSynchSolver`       `GaussSeidelSynch` (SolverType.GaussSeidelSynch)
+:class:`lightsim2grid.solver.DCSolver`                     `DC` (SolverType.DC)
+:class:`lightsim2grid.solver.KLUDCSolver`                  `KLUDC` (SolverType.KLUDC)
+:class:`lightsim2grid.solver.NICSLUDCSolver`               `NICSLUDC` (SolverType.NICSLUDC) 
+:class:`lightsim2grid.solver.CKTSODCSolver`                `CKTSODC` (SolverType.CKTSODC)
+:class:`lightsim2grid.solver.SparseLUSolverSingleSlack`    `SparseLUSingleSlack` (SolverType.SparseLUSingleSlack)
+:class:`lightsim2grid.solver.KLUSolverSingleSlack`         `KLUSingleSlack` (SolverType.KLUSingleSlack)
+:class:`lightsim2grid.solver.NICSLUSolverSingleSlack`      `NICSLUSingleSlack` (SolverType.NICSLUSingleSlack)
+:class:`lightsim2grid.solver.CKTSOSolverSingleSlack`       `CKTSOSingleSlack` (SolverType.CKTSOSingleSlack)
+:class:`lightsim2grid.solver.SparseLUSolver`               `SparseLU` (SolverType.SparseLU)
+:class:`lightsim2grid.solver.KLUSolver`                    `KLU` (SolverType.KLU)
+:class:`lightsim2grid.solver.NICSLUSolver`                 `NICSLU` (SolverType.NICSLU)
+:class:`lightsim2grid.solver.CKTSOSolver`                  `CKTSO` (SolverType.CKTSO)
+========================================================   =============================================================================
 
 Usage
 --------------------------
@@ -40,7 +107,26 @@ is given in the
 script available in the `"benchmarks" <https://github.com/BDonnot/lightsim2grid/tree/master/benchmarks/>`_
 directory of the lightsim2grid repository.
 
-A concrete example of how to change the solver in this backend is:
+To change the solver used by the backend, the preferred solution is to set it once you create it:
+
+.. code-block:: python
+
+    import grid2op
+    import lightsim2grid
+    from lightsim2grid import LightSimBackend
+
+    # create an environment
+    env_name = "l2rpn_case14_sandbox"
+    env_lightsim = grid2op.make(env_name,
+                                backend=LightSimBackend(solver_type=lightsim2grid.SolverType.KLU)
+                               )
+
+.. note::
+
+  For the list of availbale solvers, you can consult the "enum" :class:`lightsim2grid.solver.SolverType`.
+
+
+You can also (so it's not recommended) change the solver after the backend is created with:
 
 .. code-block:: python
 
@@ -63,7 +149,8 @@ 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`.
+    env_lightsim.reset()  # do not forget to reset
+
 
 
 Detailed usage
@@ -73,7 +160,6 @@ Detailed usage
     :members:
     :autosummary:
 
-
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
\ No newline at end of file
diff --git a/docs/time_series.rst b/docs/time_series.rst
index 00ac354..426e95f 100644
--- a/docs/time_series.rst
+++ b/docs/time_series.rst
@@ -24,8 +24,12 @@ It can be used as:
     env = grid2op.make(env_name, backend=LightSimBackend())
 
     time_series = TimeSerie(env)
-    Vs = time_series.compute_V(scenario_id=..., seed=...)
-    As = time_series.compute_A()  # will contain the flows, in amps at each step (rows) for each powerline (column)
+    res_p, res_a, res_v = time_series.get_flows(scenario_id=..., seed=...)
+
+    # we have:
+    # res_p[row_id] will be the active power flows (origin side), on all powerlines corresponding to step "row_id"
+    # res_a[row_id] will be the current flows, on all powerlines corresponding to step "row_id"
+    # res_v[row_id] will be the complex voltage, on all bus of the grid at step "row_id"
 
 For now this relies on grid2op, but we could imagine a version of this class that can read
 to / from other data sources (for now please use the more basic :class:`lightsim2grid.timeSerie.Computers` for such purpose)
diff --git a/eigen b/eigen
index 3147391..e7248b2 160000
--- a/eigen
+++ b/eigen
@@ -1,1 +1,1 @@
-Subproject commit 3147391d946bb4b6c68edd901f2add6ac1f31f8c
+Subproject commit e7248b26a1ed53fa030c5c459f7ea095dfd276ac
diff --git a/lightsim2grid/__init__.py b/lightsim2grid/__init__.py
index 5cfd33b..6bc58d5 100644
--- a/lightsim2grid/__init__.py
+++ b/lightsim2grid/__init__.py
@@ -6,7 +6,10 @@
 # SPDX-License-Identifier: MPL-2.0
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
-__version__ = "0.7.1"
+import faulthandler
+faulthandler.enable()
+
+__version__ = "0.7.2"
 
 __all__ = ["newtonpf", "SolverType", "ErrorType", "solver"]
 
diff --git a/lightsim2grid/gridmodel/_aux_add_dc_line.py b/lightsim2grid/gridmodel/_aux_add_dc_line.py
new file mode 100644
index 0000000..41a9bee
--- /dev/null
+++ b/lightsim2grid/gridmodel/_aux_add_dc_line.py
@@ -0,0 +1,77 @@
+# Copyright (c) 2023, 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 ._aux_add_sgen import SOME_KIND_OF_INF_FOR_PMIN_PMAX
+
+
+def _aux_add_dc_line(model, pp_net):
+    """
+    Add the transformers of the pp_net into the lightsim2grid "model"
+
+    Parameters
+    ----------
+    converter
+    model
+    pp_net
+
+    Returns
+    -------
+
+    """
+    if "parallel" in pp_net.dcline and np.any(pp_net.dcline["parallel"].values != 1):
+        raise RuntimeError("Cannot handle 'parallel' dcline columns. Please duplicate the rows if that is the case. "
+                           "Some pp_net.dcline[\"parallel\"] != 1 it is not handled by lightsim yet.")
+
+
+    branch_from_id = pp_net.dcline["from_bus"].values
+    branch_to_id = pp_net.dcline["to_bus"].values
+    p_mw = -pp_net.dcline["p_mw"].values
+    if np.any(~np.isfinite(p_mw)):
+        warnings.warn("Some non finite values are found for p_mw, they have been replaced by 0.")
+        p_mw[~np.isfinite(p_mw)] = 0.
+        
+    loss_percent = pp_net.dcline["loss_percent"].values
+    loss_mw = pp_net.dcline["loss_mw"].values
+    vm_or_pu = pp_net.dcline["vm_from_pu"].values
+    vm_ex_pu = pp_net.dcline["vm_to_pu"].values
+    
+    min_q_or = pp_net.dcline["min_q_from_mvar"].values
+    if np.any(~np.isfinite(min_q_or)):
+        min_q_or[~np.isfinite(min_q_or)] = -SOME_KIND_OF_INF_FOR_PMIN_PMAX
+        
+    max_q_or = pp_net.dcline["max_q_from_mvar"].values
+    if np.any(~np.isfinite(max_q_or)):
+        max_q_or[~np.isfinite(max_q_or)] = SOME_KIND_OF_INF_FOR_PMIN_PMAX
+        
+    min_q_ex = pp_net.dcline["min_q_to_mvar"].values
+    if np.any(~np.isfinite(min_q_ex)):
+        min_q_ex[~np.isfinite(min_q_ex)] = -SOME_KIND_OF_INF_FOR_PMIN_PMAX
+        
+    max_q_ex = pp_net.dcline["max_q_to_mvar"].values
+    if np.any(~np.isfinite(max_q_ex)):
+        max_q_ex[~np.isfinite(max_q_ex)] = +SOME_KIND_OF_INF_FOR_PMIN_PMAX
+                          
+    model.init_dclines(branch_from_id.astype(np.int32),
+                       branch_to_id.astype(np.int32),
+                       p_mw,
+                       loss_percent,
+                       loss_mw,
+                       vm_or_pu,
+                       vm_ex_pu,
+                       min_q_or,
+                       max_q_or,
+                       min_q_ex,
+                       max_q_ex
+                       )
+    
+    for dcl_id, is_connected in enumerate(pp_net.dcline["in_service"].values):
+        if not is_connected:
+            # trafo is deactivated
+            model.deactivate_dcline(dcl_id)
diff --git a/lightsim2grid/gridmodel/_aux_add_slack.py b/lightsim2grid/gridmodel/_aux_add_slack.py
index 3a4f0ba..8900c78 100644
--- a/lightsim2grid/gridmodel/_aux_add_slack.py
+++ b/lightsim2grid/gridmodel/_aux_add_slack.py
@@ -31,10 +31,10 @@ def _aux_add_slack(model, pp_net):
     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.")
+        # 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]
@@ -60,9 +60,9 @@ def _aux_add_slack(model, pp_net):
         # 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 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
@@ -96,7 +96,7 @@ def _aux_add_slack(model, pp_net):
                 slack_coeff[:] = 1.0
                 
             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
+            slack_contrib = -1.0 * (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))
@@ -104,7 +104,6 @@ def _aux_add_slack(model, pp_net):
             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:
diff --git a/lightsim2grid/gridmodel/_aux_check_legit.py b/lightsim2grid/gridmodel/_aux_check_legit.py
index a3e18f8..c7bf09b 100644
--- a/lightsim2grid/gridmodel/_aux_check_legit.py
+++ b/lightsim2grid/gridmodel/_aux_check_legit.py
@@ -41,9 +41,10 @@ def _aux_check_legit(pp_net):
     if "xward" in pp_net and pp_net.xward.shape[0]:
         raise RuntimeError("Unsupported element found (Extended Ward - \"pp_net.xward\") "
                            "in pandapower network")
-    if "dcline" in pp_net and pp_net.dcline.shape[0]:
-        raise RuntimeError("Unsupported element found (DC Line - \"pp_net.dcline\") "
-                           "in pandapower network")
+    # supported now
+    # if "dcline" in pp_net and pp_net.dcline.shape[0]:
+    #     raise RuntimeError("Unsupported element found (DC Line - \"pp_net.dcline\") "
+    #                        "in pandapower network")
 
     # bus indexes should start at 0 and be contiguous
     if np.any(np.sort(pp_net.bus.index) != np.arange(pp_net.bus.shape[0])):
diff --git a/lightsim2grid/gridmodel/initGridModel.py b/lightsim2grid/gridmodel/initGridModel.py
index aedfab2..e13f0f5 100644
--- a/lightsim2grid/gridmodel/initGridModel.py
+++ b/lightsim2grid/gridmodel/initGridModel.py
@@ -25,6 +25,7 @@
 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
+from lightsim2grid.gridmodel._aux_add_dc_line import _aux_add_dc_line
 
 
 def init(pp_net):
@@ -109,6 +110,9 @@ def init(pp_net):
     # handle storage units
     _aux_add_storage(model, pp_net)
 
+    # handle dc line
+    _aux_add_dc_line(model, pp_net)
+
     # deal with slack bus
     _aux_add_slack(model, pp_net)
 
diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py
index 81ae11d..a091dd4 100644
--- a/lightsim2grid/lightSimBackend.py
+++ b/lightsim2grid/lightSimBackend.py
@@ -7,7 +7,7 @@
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
 import copy
-from typing import Optional, Type
+from typing import Optional, Union
 import warnings
 import numpy as np
 import time
@@ -28,11 +28,15 @@ class LightSimBackend(Backend):
     coded in c++, aiming at speeding up the computations.
     """
     def __init__(self,
-                 detailed_infos_for_cascading_failures: bool =False,
-                 can_be_copied: bool =True,
+                 detailed_infos_for_cascading_failures: bool=False,
+                 can_be_copied: bool=True,
                  max_iter: int=10,
                  tol: float=1e-8,
-                 solver_type: Optional[SolverType] =None):
+                 solver_type: Optional[SolverType]=None,
+                 turned_off_pv : bool=True,  # are gen turned off (or with p=0) contributing to voltage or not
+                 dist_slack_non_renew: bool=False,  # distribute the slack on non renewable turned on (and with P>0) generators
+                 use_static_gen: bool=False, # add the static generators as generator gri2dop side
+                 ):
         try:
             # for grid2Op >= 1.7.1
             Backend.__init__(self,
@@ -40,7 +44,10 @@ def __init__(self,
                              can_be_copied=can_be_copied,
                              solver_type=solver_type,
                              max_iter=max_iter,
-                             tol=tol)
+                             tol=tol,
+                             turned_off_pv=turned_off_pv,
+                             dist_slack_non_renew=dist_slack_non_renew,
+                             use_static_gen=use_static_gen)
         except TypeError as exc_:
             warnings.warn("Please use grid2op >= 1.7.1: with older grid2op versions, "
                           "you cannot set max_iter, tol nor solver_type arguments.")
@@ -148,7 +155,25 @@ def __init__(self,
         # TODO and should rather be handled in pandapower backend
         # backend SHOULD not do these kind of stuff
         self._idx_hack_storage = []
-
+        
+        # does the "turned off" generators (including when p=0)
+        # are pv buses
+        self._turned_off_pv = turned_off_pv
+        
+        # distributed slack, on non renewable gen with P > 0
+        self._dist_slack_non_renew = dist_slack_non_renew
+        
+        # add the static gen to the list of controlable gen in grid2Op
+        self._use_static_gen = use_static_gen  # TODO implement it
+        
+    def turnedoff_no_pv(self):
+        self._turned_off_pv = False
+        self._grid.turnedoff_no_pv()
+        
+    def turnedoff_pv(self):
+        self._turned_off_pv = True
+        self._grid.turnedoff_pv()
+        
     def _fill_theta(self):
         # line_or_theta = np.empty(self.n_line)
         self.line_or_theta[:self.__nb_powerline] = self._grid.get_lineor_theta()
@@ -188,7 +213,33 @@ def get_theta(self):
                self.cst_1 * self.gen_theta, \
                self.cst_1 * self.storage_theta
 
-    def set_solver_type(self, solver_type):
+    def get_solver_types(self) -> Union[SolverType, SolverType]:
+        """Return the types of solver that are used in the form a tuple with 2 elements.
+        
+        The first one is the solver used for AC computation, the second one for DC computation (and also for
+        initialization of AC computations)
+        
+        You can use it to check what is used:
+        
+        .. code-block:: python
+
+            import grid2op
+            import lightsim2grid
+            from ligthsim2grid import LightSimBackend
+            
+            env_name = ...
+            env = grid2op.make(env_name, backend=LightSimBackend())
+            print(env.backend.get_solver_types())
+            # >>> (<SolverType.KLUSingleSlack: 7>, <SolverType.KLUDC: 9>)  [can depend on your installation of lightsim2grid]
+            
+            env2 = grid2op.make(env_name, backend=LightSimBackend(solver_type=lightsim2grid.solver.SolverType.SparseLU))
+            print(env2.backend.get_solver_types())
+            # >>> (<SolverType.SparseLU: 0>, <SolverType.KLUDC: 9>)  [can depend on your installation of lightsim2grid]
+            
+        """
+        return self._grid.get_solver_type(), self._grid.get_dc_solver_type()
+        
+    def set_solver_type(self, solver_type: SolverType):
         """
         Change the type of solver you want to use.
 
@@ -237,11 +288,10 @@ def set_solver_max_iter(self, max_iter):
         We do not recommend to modify the default value (10), unless you are using the GaussSeidel powerflow.
         This powerflow being slower, we do not recommend to use it.
 
-        Recommendation:
+        Recommendation, for medium sized grid (**eg** based on the ieee 118):
 
         - for SolverType.SparseLU: 10
         - for SolverType.GaussSeidel: 10000
-        - for SolverType.DC: this has no effect
         - for SolverType.SparseKLU: 10
 
         Parameters
@@ -286,38 +336,49 @@ def set_tol(self, new_tol):
         try:
             new_tol = float(new_tol)
         except Exception as exc_:
-            raise BackendError(f"Impossible to convert \"new_tol={new_tol}\" to an float with error \"{exc_}\"")
+            raise BackendError(f"Impossible to convert \"new_tol={new_tol}\" to an float with error \"{exc_}\"") from exc_
         if new_tol <= 0:
             raise BackendError("new_tol should be a strictly positive float (float > 0)")
         self.tol = new_tol
         self._idx_hack_storage = np.zeros(0, dtype=dt_int)
 
+    def _handle_turnedoff_pv(self):
+        if self._turned_off_pv:
+            self._grid.turnedoff_pv()
+        else:
+            self._grid.turnedoff_no_pv()
+    
+    def _assign_right_solver(self):
+        has_single_slack = np.where(np.array([el.slack_weight for el in self._grid.get_generators()]) != 0.)[0].shape[0] == 1
+        if has_single_slack and not self._dist_slack_non_renew:
+            if SolverType.KLUSingleSlack in self.available_solvers:
+                # use the faster KLU if available
+                self._grid.change_solver(SolverType.KLUSingleSlack)
+            else:
+                self._grid.change_solver(SolverType.SparseLUSingleSlack)
+        else:
+            # grid has multiple slack      
+            if SolverType.KLU in self.available_solvers:
+                # use the faster KLU if available
+                self._grid.change_solver(SolverType.KLU)
+            else:
+                self._grid.change_solver(SolverType.SparseLU)
+        
     def load_grid(self, path=None, filename=None):
         # if self.init_pp_backend is None:
         self.init_pp_backend.load_grid(path, filename)
         self.can_output_theta = True  # i can compute the "theta" and output it to grid2op
 
         self._grid = init(self.init_pp_backend._grid)
-
+        
+        self._handle_turnedoff_pv()
+            
         self.available_solvers = self._grid.available_solvers()
         if self.__current_solver_type is None:
             # previous default behaviour (< 0.7)
             # by default it builds the backend with the fastest solver
             # automatically found
-            has_single_slack = np.where(np.array([el.slack_weight for el in self._grid.get_generators()]) != 0.)[0].shape[0] == 1
-            if has_single_slack:
-                if SolverType.KLUSingleSlack in self.available_solvers:
-                    # use the faster KLU if available
-                    self._grid.change_solver(SolverType.KLUSingleSlack)
-                else:
-                    self._grid.change_solver(SolverType.SparseLUSingleSlack)
-            else:
-                # grid has multiple slack      
-                if SolverType.KLUSingleSlack in self.available_solvers:
-                    # use the faster KLU if available
-                    self._grid.change_solver(SolverType.KLU)
-                else:
-                    self._grid.change_solver(SolverType.SparseLU)
+            self._assign_right_solver()
             
             if SolverType.KLUDC in self.available_solvers:
                 # use the faster KLU if available even for DC approximation
@@ -353,7 +414,7 @@ def load_grid(self, path=None, filename=None):
         self.name_load = self.init_pp_backend.name_load
         self.name_line = self.init_pp_backend.name_line
         self.name_sub = self.init_pp_backend.name_sub
-
+            
         # TODO storage check grid2op version and see if storage is available !
         if self.__has_storage:
             self.n_storage = self.init_pp_backend.n_storage
@@ -617,8 +678,10 @@ def apply_action(self, backendAction):
                     self._grid.deactivate_shunt(sh_id)
                 else:
                     self._grid.reactivate_shunt(sh_id)
-                    self._grid.change_bus_shunt(sh_id, self.shunt_to_subid[sh_id] * new_bus)
-
+                    if hasattr(type(self), "local_bus_to_global_int"):
+                        self._grid.change_bus_shunt(sh_id, type(self).local_bus_to_global_int(new_bus, self.shunt_to_subid[sh_id]))
+                    else:
+                        self._grid.change_bus_shunt(sh_id, self.shunt_to_subid[sh_id] + (new_bus == 2) * type(self).n_sub)
             for sh_id, new_p in shunt_p:
                 self._grid.change_p_shunt(sh_id, new_p)
             for sh_id, new_q in shunt_q:
@@ -645,6 +708,12 @@ def apply_action(self, backendAction):
         self.topo_vect[chgt] = backendAction.current_topo.values[chgt]
         # TODO c++ side: have a check to be sure that the set_***_pos_topo_vect and set_***_to_sub_id
         # TODO have been correctly called before calling the function self._grid.update_topo
+        
+        self._handle_dist_slack()
+    
+    def _handle_dist_slack(self):
+        if self._dist_slack_non_renew:
+            self._grid.update_slack_weights(type(self).gen_redispatchable)
 
     def runpf(self, is_dc=False):
         my_exc_ = None
@@ -829,7 +898,8 @@ def copy(self):
                            "_big_topo_to_obj", "max_it", "tol", "dim_topo",
                            "_idx_hack_storage",
                            "_timer_preproc", "_timer_postproc", "_timer_solver",
-                           "_my_kwargs"
+                           "_my_kwargs",
+                           "_turned_off_pv", "_dist_slack_non_renew"
                            ]
         for attr_nm in li_regular_attr:
             if hasattr(self, attr_nm):
@@ -962,6 +1032,7 @@ def reset(self, grid_path, grid_filename=None):
         self._grid = self.__me_at_init.copy()
         self._grid.tell_topo_changed()
         self._grid.change_solver(self.__current_solver_type)
+        self._handle_turnedoff_pv()
         self.topo_vect[:] = self.__init_topo_vect
         self.comp_time = 0.
         self._timer_postproc = 0.
diff --git a/lightsim2grid/newtonpf/__init__.py b/lightsim2grid/newtonpf/__init__.py
index a6a1f45..31a97ce 100644
--- a/lightsim2grid/newtonpf/__init__.py
+++ b/lightsim2grid/newtonpf/__init__.py
@@ -6,6 +6,6 @@
 # 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"]
+
+from lightsim2grid.newtonpf.newtonpf import newtonpf, newtonpf_new, newtonpf_old
diff --git a/lightsim2grid/securityAnalysis.py b/lightsim2grid/securityAnalysis.py
index 298fdc9..3455ccb 100644
--- a/lightsim2grid/securityAnalysis.py
+++ b/lightsim2grid/securityAnalysis.py
@@ -243,7 +243,7 @@ def get_flows(self, *args):
         """
         Retrieve the flows after each contingencies has been simulated.
 
-        Each row of the resulting flow matrix will correspond of a contingency simulated in the arguments.
+        Each row of the resulting flow matrix will correspond to a contingency simulated in the arguments.
 
         You can require only the result on some contingencies with the `args` argument, but in each case, all the results will
         be computed. If you don't specify anything, the results will be returned for all contingencies (which we recommend to do)
@@ -342,3 +342,9 @@ def compute_P(self):
             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
+
+    def close(self):
+        """permanently close the object"""
+        self.grid2op_env.close()
+        self.clear()
+        self.computer.close()
diff --git a/lightsim2grid/solver/__init__.py b/lightsim2grid/solver/__init__.py
index d33d344..a3f495b 100644
--- a/lightsim2grid/solver/__init__.py
+++ b/lightsim2grid/solver/__init__.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# Copyright (c) 2020-2023, 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,
@@ -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.
 
-# TODO add the other solver now !
 __all__ = ["SolverType",
            "ErrorType",
            "AnySolver",  
@@ -19,17 +18,17 @@
 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
+               
+from lightsim2grid_cpp import GaussSeidelSolver  # SolverType.GaussSeidel
+from lightsim2grid_cpp import GaussSeidelSynchSolver  # SolverType.GaussSeidelSynch
+from lightsim2grid_cpp import SparseLUSolver  # SolverType.SparseLU
+from lightsim2grid_cpp import SparseLUSolverSingleSlack  # SolverType.SparseLUSingleSlack
+from lightsim2grid_cpp import DCSolver  # SolverType.DC
 
 try:
-    from lightsim2grid_cpp import KLUSolver
-    from lightsim2grid_cpp import KLUSolverSingleSlack
-    from lightsim2grid_cpp import KLUDCSolver
+    from lightsim2grid_cpp import KLUSolver  # SolverType.KLU
+    from lightsim2grid_cpp import KLUSolverSingleSlack  # SolverType.KLUSingleSlack
+    from lightsim2grid_cpp import KLUDCSolver  # SolverType.KLUDC
     __all__.append("KLUSolver")
     __all__.append("KLUSolverSingleSlack")
     __all__.append("KLUDCSolver")
@@ -38,12 +37,23 @@
     pass
 
 try:
-    from lightsim2grid_cpp import NICSLUSolver
-    from lightsim2grid_cpp import NICSLUSolverSingleSlack
-    from lightsim2grid_cpp import NICSLUDCSolver
+    from lightsim2grid_cpp import NICSLUSolver  # SolverType.NICSLU
+    from lightsim2grid_cpp import NICSLUSolverSingleSlack  # SolverType.NICSLUSingleSlack
+    from lightsim2grid_cpp import NICSLUDCSolver  # SolverType.NICSLUDC
     __all__.append("NICSLUSolver")
     __all__.append("NICSLUSolverSingleSlack")
     __all__.append("NICSLUDCSolver")
 except Exception as exc_:
     # NICSLU is not available
     pass
+
+try:
+    from lightsim2grid_cpp import CKTSOSolver  # SolverType.CKTSO
+    from lightsim2grid_cpp import CKTSOSolverSingleSlack  # SolverType.CKTSOSingleSlack
+    from lightsim2grid_cpp import CKTSODCSolver  # SolverType.CKTSODC
+    __all__.append("CKTSOSolver")
+    __all__.append("CKTSOSolverSingleSlack")
+    __all__.append("CKTSODCSolver")
+except Exception as exc_:
+    # NICSLU is not available
+    pass
diff --git a/lightsim2grid/timeSerie.py b/lightsim2grid/timeSerie.py
index 032f2ea..75bbaf1 100644
--- a/lightsim2grid/timeSerie.py
+++ b/lightsim2grid/timeSerie.py
@@ -7,7 +7,7 @@
 # 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
 
@@ -17,6 +17,7 @@
 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
@@ -67,8 +68,7 @@ class TimeSerie:
         env = grid2op.make(env_name, param=param, backend=LightSimBackend())
 
         time_series = TimeSerie(env)
-        Vs = time_series.compute_V(scenario_id=..., seed=...)
-        As = time_series.compute_A()
+        res_p, res_a, res_v = time_series.get_flows(scenario_id=..., seed=...)
 
     """
     def __init__(self, grid2op_env):
@@ -137,12 +137,14 @@ def compute_V_from_inj(self, prod_p, load_p, load_q, v_init=None, ignore_errors=
                                f"is different that the number of columns of the provided load_q data: "
                                f"load_q.shape[1] = {load_q.shape[1]}")
         if v_init is None:
-            v_init = self.grid2op_env.backend.V
+            v_init_comp = self.grid2op_env.backend.V
+        else:
+            v_init_comp = 1.0 * v_init  # make a copy !
         status = self.computer.compute_Vs(prod_p,
                                           np.zeros((prod_p.shape[0], 0)),  # no static generators for now !
                                           load_p,
                                           load_q,
-                                          v_init,
+                                          v_init_comp,
                                           self.grid2op_env.backend.max_it,
                                           self.grid2op_env.backend.tol)
         if status != 1 and not ignore_errors:
@@ -151,7 +153,8 @@ def compute_V_from_inj(self, prod_p, load_p, load_q, v_init=None, ignore_errors=
         elif status != 1:
             # only raise a warning in this case
             warnings.warn(f"Some error occurred, the powerflow has diverged after {self.computer.nb_solved()} step(s)")
-        Vs = self.computer.get_voltages()
+        Vs = 1.0 * self.computer.get_voltages()  # If I don't copy, lazy eval may break stuff... 
+        # eg test_time_series_dc.py does behave stochastically
         self.__computed = True
         return Vs
         
@@ -190,9 +193,57 @@ def compute_P(self):
         """
         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()
+        mws = 1. * self.computer.compute_power_flows() # If I don't copy, lazy eval may break stuff... 
+        # eg test_time_series_dc.py does behave stochastically
         return mws
 
+    def get_flows(self, scenario_id=None, seed=None, v_init=None, ignore_errors=False):
+        """
+        Retrieve the flows for each step simulated.
+
+        Each row of the resulting flow matrix will correspond to a step.
+
+        Examples
+        --------
+
+        .. code-block:: python
+
+            import grid2op
+            from lightsim2grid import TimeSerie
+            from lightsim2grid import LightSimBackend
+            env_name = ...
+            env = grid2op.make(env_name, backend=LightSimBackend())
+
+            timeserie = TimeSerie(env)
+            res_p, res_a, res_v = timeserie.get_flows(scenario_id, seed, v_init, ignore_errors)
+
+            # in this results, then
+            # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
+            # you can retrieve it with `security_analysis.contingency_order[row_id]`
+        """
+        
+        Vs = self.compute_V(scenario_id, seed, v_init, ignore_errors)
+        amps = self.compute_A()
+        Ps = self.compute_P()
+        return Ps, amps, Vs
+    
+    def clear(self):
+        """
+        Clear everything, as if nothing has been computed
+        """
+        self.computer.clear()
+        self.__computed = False
+        
+        self.prod_p = None
+        self.load_p = None
+        self.load_q = None
+    
+    def close(self):
+        """permanently close the object"""
+        self.grid2op_env.close()
+        self.clear()
+        self.computer.close()
+         
     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 6fc31a9..126d84e 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@
 from pybind11.setup_helpers import Pybind11Extension, build_ext
 
 
-__version__ = "0.7.1"
+__version__ = "0.7.2"
 KLU_SOLVER_AVAILABLE = False
 
 # Try to link against SuiteSparse (if available)
@@ -149,9 +149,7 @@
              "src/DataLoad.cpp",
              "src/DataGen.cpp",
              "src/DataSGen.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/DataDCLine.cpp",
              "src/ChooseSolver.cpp",
              "src/GaussSeidelSolver.cpp",
              "src/GaussSeidelSynchSolver.cpp",
@@ -212,6 +210,54 @@
         extra_compile_args.append("-DNICSLU_SOLVER_AVAILABLE")
         print("INFO: Using NICSLU package")
 
+# Try to locate the CKTSO sparse linear solver
+if "PATH_CKTSO" in os.environ:
+    # user indicate the path for the CKTSO library (see https://github.com/chenxm1986/cktso)
+    # eg "/home/user/Documents/cktso/"
+    
+    path_cktso = os.path.abspath(os.environ["PATH_CKTSO"])
+    include_cktso = True
+    # check for appropriate license
+    if not os.path.exists(path_cktso):
+        print(f"WARNING: nothing for CKTSO at at: {path_cktso}")
+        include_cktso = False
+    license_path = os.path.join(path_cktso, "license")
+    if include_cktso and not os.path.exists(license_path):
+        # license not located at the right directory
+        print(f"WARNING: no license path found for CKTSO at: {license_path}")
+        include_cktso = False
+    license_file = os.path.join(license_path, "cktso.lic")
+    if include_cktso and not os.path.exists(license_file):
+        # no license found
+        print(f"WARNING: no license file is found for CKTSO at: {license_file}")
+        include_cktso = False
+    libcktso_path = None
+    if include_cktso:
+        if sys.platform.startswith("linux") or sys.platform.startswith("darwin"):
+            libcktso_path = os.path.join(path_cktso, "ubuntu1804_x64_gcc750/libcktso.so")  # TODO CHANGE THAT !
+            if not os.path.exists(libcktso_path):
+                print(f"WARNING: cannot locate the CKTSO shared object that should be at: {libcktso_path}")
+                include_cktso = False
+                libcktso_path = None
+        elif sys.platform.startswith("win"):
+            libcktso_path = os.path.join(path_cktso, "win7_x64/cktso.lib")  # TODO CHANGE THAT !
+            if not os.path.exists(libcktso_path):
+                print(f"WARNING: cannot locate the CKTSO shared object that should be at: {libcktso_path}")
+                include_cktso = False
+                libcktso_path = None
+        else:
+            print(f"WARNING: CKTSO can only be added when using linux, darwin (MacOS) or win (Windows) python version, you are using {sys.platform}")
+            include_cktso = False
+            libcktso_path = None
+
+    if include_cktso and libcktso_path is not None:
+        LIBS.append(os.path.join(path_cktso, libcktso_path))
+        include_dirs.append(os.path.join(path_cktso, "include"))
+        src_files.append("src/CKTSOSolver.cpp")
+        extra_compile_args.append("-DCKTSO_SOLVER_AVAILABLE")
+        print("INFO: Using CKTSO package")
+        
+        
 if "__COUT_TIMES" in os.environ:
     # to add extra info in cout for the computation times, we do not recommend to use it !
     if os.environ["__COUT_TIMES"] == "1":
@@ -266,8 +312,16 @@
 # python 3.10+
 req_pkgs = [
         "pandapower" if sys.version_info < (3, 10) else "pandapower>=2.8",
+        "pytest",  # for pandapower see https://github.com/e2nIEE/pandapower/issues/1988
     ]
 
+if sys.version_info.major == 3 and sys.version_info.minor <= 7:
+    # typing "Literal" not available on python 3.7
+    req_pkgs.append("typing_extensions")
+    # do not use pandapower 2.12 (broken on python 3.7 
+    # see https://github.com/e2nIEE/pandapower/issues/1985
+    req_pkgs[0] = "pandapower>=2.2.2,<2.12"
+
 pkgs = {
     "required": req_pkgs,
     "extras": {
diff --git a/src/BaseMultiplePowerflow.cpp b/src/BaseMultiplePowerflow.cpp
index 168ee60..6793869 100644
--- a/src/BaseMultiplePowerflow.cpp
+++ b/src/BaseMultiplePowerflow.cpp
@@ -55,12 +55,13 @@ void BaseMultiplePowerflow::compute_flows_from_Vs(bool amps)
     
     // compute the flows for the powerlines
     Eigen::Index lag_id = 0;
-    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);
+    if (amps) compute_amps_flows(_grid_model.get_powerlines_as_data(), sn_mva, lag_id, false);
+    else compute_active_power_flows(_grid_model.get_powerlines_as_data(), sn_mva, lag_id, false);
+
     // compute the flows for the trafos
     lag_id = n_line_;
-    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) compute_amps_flows(_grid_model.get_trafos_as_data(), sn_mva, lag_id, true);
+    else compute_active_power_flows(_grid_model.get_trafos_as_data(), sn_mva, lag_id, true);
 
     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 f79f2b0..aa02282 100644
--- a/src/BaseMultiplePowerflow.h
+++ b/src/BaseMultiplePowerflow.h
@@ -28,6 +28,7 @@ class BaseMultiplePowerflow
             n_total_(n_line_ + n_trafos_),
             _solver(),
             _amps_flows(),
+            _active_power_flows(),
             _voltages(),
             _nb_solved(0),
             _timer_compute_A(0.),
@@ -59,6 +60,16 @@ class BaseMultiplePowerflow
         double amps_computation_time() const {return _timer_compute_A;}
         double solver_time() const {return _timer_solver;}
         int nb_solved() const {return _nb_solved;}
+        virtual void clear() {
+            _solver.reset();
+            _amps_flows = RealMat();
+            _active_power_flows = RealMat();
+            _voltages = CplxMat();
+            _nb_solved = 0;
+            _timer_compute_A = 0;
+            _timer_compute_P = 0;
+            _timer_solver = 0;
+        }
 
         // results
         Eigen::Ref<const RealMat > get_flows() const {return _amps_flows;}
@@ -69,17 +80,23 @@ class BaseMultiplePowerflow
         template<class T>
         void compute_amps_flows(const T & structure_data,
                                 real_type sn_mva,
-                                Eigen::Index lag_id) 
+                                Eigen::Index lag_id,
+                                bool is_trafo) 
         {
             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();
+            bool is_ac = _solver.ac_solver_used();
+
+            const auto & vect_y_ff = is_ac ? structure_data.yac_ff() : structure_data.ydc_ff();
+            const auto & vect_y_ft = is_ac ? structure_data.yac_ft() : structure_data.ydc_ft();
+            Eigen::Ref<const RealVect> dc_x_tau_shift = structure_data.dc_x_tau_shift(); // not used in AC nor if it's powerline anyway
+
             Eigen::Index nb_el = structure_data.nb();
             real_type sqrt_3 = sqrt(3.);
 
+            RealVect res;
             for(Eigen::Index el_id = 0; el_id < nb_el; ++el_id){
                 if(!el_status[el_id]) continue;
 
@@ -95,16 +112,22 @@ class BaseMultiplePowerflow
                 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().abs() * sn_mva;
+                const cplx_type y_ff = vect_y_ff(el_id);  // scalar
+                const cplx_type y_ft = vect_y_ft(el_id);
+
+                if(is_ac){
+                    // trafo equations (to get the power at the "from" side)
+                    CplxVect I_ft =  y_ff * Efrom + y_ft * Eto;
+                    I_ft = I_ft.array().conjugate();
+                    const CplxVect S_ft = Efrom.array() * I_ft.array();
+
+                    // now compute the current flow
+                    res = S_ft.array().abs() * sn_mva;
+                }else{
+                    res = (std::real(y_ff) * Efrom.array().arg() + std::real(y_ft) * Eto.array().arg()) * sn_mva;
+                    if(is_trafo) res.array() -= dc_x_tau_shift(el_id);
+                    res.array() = res.array().abs();
+                }
                 res.array() /= sqrt_3 * v_f_kv.array();
                 _amps_flows.col(el_id + lag_id) = res;
             }
@@ -112,16 +135,22 @@ class BaseMultiplePowerflow
         template<class T>
         void compute_active_power_flows(const T & structure_data,
                                         real_type sn_mva,
-                                        Eigen::Index lag_id) 
+                                        Eigen::Index lag_id,
+                                        bool is_trafo) 
         {
             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();
+            bool is_ac = _solver.ac_solver_used();
+
+            Eigen::Ref<const CplxVect> vect_y_ff = is_ac ? structure_data.yac_ff() : structure_data.ydc_ff();
+            Eigen::Ref<const CplxVect> vect_y_ft = is_ac ? structure_data.yac_ft() : structure_data.ydc_ft();
+            Eigen::Ref<const RealVect> dc_x_tau_shift = structure_data.dc_x_tau_shift(); // not used in AC nor if it's powerline anyway
+
             Eigen::Index nb_el = structure_data.nb();
 
+            RealVect res;
             for(Eigen::Index el_id = 0; el_id < nb_el; ++el_id){
                 if(!el_status[el_id]) continue;
 
@@ -137,16 +166,21 @@ class BaseMultiplePowerflow
                 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);
+                const cplx_type y_ff = vect_y_ff(el_id);  // scalar
+                const cplx_type y_ft = vect_y_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;
+                if(is_ac){
+                    CplxVect I_ft = y_ff * Efrom + y_ft * Eto;
+                    I_ft = I_ft.array().conjugate();
+                    const CplxVect S_ft = Efrom.array() * I_ft.array();
+
+                    // now compute the active flow
+                    res = S_ft.array().real() * sn_mva;
+                }else{
+                    res = (std::real(y_ff) * Efrom.array().arg() + std::real(y_ft) * Eto.array().arg()) * sn_mva;
+                    if(is_trafo) res.array() -= dc_x_tau_shift(el_id);
+                }
                 _active_power_flows.col(el_id + lag_id) = res;
             }
         }
diff --git a/src/BaseNRSolver.h b/src/BaseNRSolver.h
index 3a392c6..8b7c5fa 100644
--- a/src/BaseNRSolver.h
+++ b/src/BaseNRSolver.h
@@ -18,7 +18,7 @@ template<class LinearSolver>
 class BaseNRSolver : public BaseSolver
 {
     public:
-        BaseNRSolver():need_factorize_(true), timer_initialize_(0.), timer_dSbus_(0.), timer_fillJ_(0.) {}
+        BaseNRSolver():BaseSolver(true), need_factorize_(true), timer_initialize_(0.), timer_dSbus_(0.), timer_fillJ_(0.) {}
 
         virtual
         Eigen::Ref<const Eigen::SparseMatrix<real_type> > get_J() const {
diff --git a/src/BaseNRSolverSingleSlack.tpp b/src/BaseNRSolverSingleSlack.tpp
index e07ae9b..65fc365 100644
--- a/src/BaseNRSolverSingleSlack.tpp
+++ b/src/BaseNRSolverSingleSlack.tpp
@@ -14,7 +14,7 @@ bool BaseNRSolverSingleSlack<LinearSolver>::compute_pf(const Eigen::SparseMatrix
                                          CplxVect & V,
                                          const CplxVect & Sbus,
                                          const Eigen::VectorXi & slack_ids,
-                                         const RealVect & slack_weights,
+                                         const RealVect & slack_weights,  // unused here
                                          const Eigen::VectorXi & pv,
                                          const Eigen::VectorXi & pq,
                                          int max_iter,
@@ -48,12 +48,11 @@ bool BaseNRSolverSingleSlack<LinearSolver>::compute_pf(const Eigen::SparseMatrix
     auto timer = CustTimer();
     // initialize once and for all the "inverse" of these vectors
     Eigen::VectorXi my_pv = BaseNRSolver<LinearSolver>::retrieve_pv_with_slack(slack_ids, pv);
+    // Eigen::VectorXi my_pv = 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 << my_pv, pq; 
     const int n_pvpq = static_cast<int>(pvpq.size());
     std::vector<int> pvpq_inv(V.size(), -1);
diff --git a/src/BaseSolver.cpp b/src/BaseSolver.cpp
index 71b3937..108d92f 100644
--- a/src/BaseSolver.cpp
+++ b/src/BaseSolver.cpp
@@ -132,6 +132,7 @@ int BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv,
     // /!\ does not support multiple slack bus!!!
 
     int res=-1;
+    bool found=false;
     // run through both pv and pq nodes and declare they are not slack bus
     std::vector<bool> tmp(nb_bus, true);
     for(unsigned int k=0; k < pv.size(); ++k)
@@ -147,8 +148,12 @@ int BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv,
     {
         if(tmp[k])
         {
-            res = k;
-            break;
+            if(!found){
+                res = k;
+                found = true;
+            }else{
+                throw std::runtime_error("BaseSolver::extract_slack_bus_id: multiple slack bus found on your grid !");
+            }
         }
     }
     if(res == -1){
diff --git a/src/BaseSolver.h b/src/BaseSolver.h
index 3cf3a16..d31fddf 100644
--- a/src/BaseSolver.h
+++ b/src/BaseSolver.h
@@ -28,7 +28,6 @@
 #include "CustTimer.h"
 #include "BaseConstants.h"
 
-enum class ErrorType {NoError, SingularMatrix, TooManyIterations, InifiniteValue, SolverAnalyze, SolverFactor, SolverReFactor, SolverSolve, NotInitError, LicenseError};
 /**
 This class represents a solver to compute powerflow.
 
@@ -37,7 +36,18 @@ It can be derived for different usecase, for example for DC powerflow, AC powerf
 class BaseSolver : public BaseConstants
 {
     public:
-        BaseSolver():BaseConstants(),n_(-1),err_(ErrorType::NotInitError),timer_Fx_(0.),timer_solve_(0.),timer_check_(0.),timer_total_nr_(0.){};
+        const bool IS_AC;  // should be static ideally...
+
+    public:
+        BaseSolver(bool is_ac=true):
+            BaseConstants(),
+            IS_AC(is_ac),
+            n_(-1),
+            err_(ErrorType::NotInitError),
+            timer_Fx_(0.),
+            timer_solve_(0.),
+            timer_check_(0.),
+            timer_total_nr_(0.){};
 
         virtual ~BaseSolver(){}
 
diff --git a/src/CKTSOSolver.cpp b/src/CKTSOSolver.cpp
new file mode 100644
index 0000000..0b673f7
--- /dev/null
+++ b/src/CKTSOSolver.cpp
@@ -0,0 +1,116 @@
+// 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 file has been inspired from https://github.com/chenxm1986/cktso/blob/master/demo/demo.cpp
+#include "CKTSOSolver.h"
+#include <iostream>
+
+
+ErrorType CKTSOLinearSolver::reset(){
+    // free everything
+    if(solver_ != nullptr) solver_->DestroySolver();
+    if(ai_ != nullptr) delete [] ai_;
+    if(ap_ != nullptr) delete [] ap_;
+
+    // should not be deleted, see https://github.com/BDonnot/lightsim2grid/issues/52#issuecomment-1333565959
+    // if(iparm_!= nullptr) delete iparm_;
+    // if(oparm_!= nullptr) delete oparm_;
+
+    ai_ = nullptr;
+    ap_ = nullptr;
+    iparm_ = nullptr;
+    oparm_ = nullptr;
+
+    solver_ = nullptr;
+    return ErrorType::NoError;
+}
+
+ErrorType CKTSOLinearSolver::initialize(Eigen::SparseMatrix<real_type> & J){
+    const long long *oparm = oparm_;
+    int ret_ = CKTSO_CreateSolver(&solver_, &iparm_, &oparm);
+    if (ret_ < 0){   // fail
+        if (ret_ == -99){
+            // fail to initialize because of a license issue
+            std::string msg = "Fail to initilize the CKTSO solver because we cannot find the cktso.lic file. ";
+            msg += "Please copy this file at the location you want to use the CKTSO solver.";
+            std::cout << msg << std::endl;
+        }
+        return ErrorType::LicenseError;
+    }
+
+    const auto n = J.cols(); // should be equal to J_.nrows()
+    int ret;
+    ErrorType err = ErrorType::NoError; // reset error message
+    const unsigned int nnz = J.nonZeros();
+
+    ai_ = new int [nnz];
+    const int * ref_ai = J.innerIndexPtr();
+    for(unsigned int i = 0; i < nnz; ++i){
+        ai_[i] = static_cast<int>(ref_ai[i]);
+    }
+
+    ap_ = new int [n+1];
+    const int * ref_ap = J.outerIndexPtr();
+    for(int i = 0; i < n+1; ++i){
+        ap_[i] = static_cast<int>(ref_ap[i]);
+    }
+    ret = solver_->Analyze(false,  // complex or real
+                           n,
+                           ap_,
+                           ai_,
+                           J.valuePtr(),
+                           nb_thread_);
+    if (ret < 0){
+        err = ErrorType::SolverAnalyze;
+        // std::cout << "CKTSOLinearSolver::initialize() solver_->Analyze error: "  << ret << std::endl;
+        // https://github.com/chenxm1986/cktso/blob/master/include/cktso.h for error info
+        return err;
+    }
+
+    ret = solver_->Factorize(J.valuePtr(),
+                             true // @fast: whether to use fast factorization => was set to "false" in the demo
+                             );
+    if (ret < 0){
+        err = ErrorType::SolverFactor;
+        // std::cout << "CKTSOLinearSolver::initialize() solver_->Factorize error: "  << ret << std::endl;
+        // https://github.com/chenxm1986/cktso/blob/master/include/cktso.h for error info
+        return err;
+    }
+    return err;
+}
+
+ErrorType CKTSOLinearSolver::solve(Eigen::SparseMatrix<real_type> & J, RealVect & b, bool has_just_been_inialized){
+    // solves (for x) the linear system J.x = b
+    // with standard use of lightsim2grid, the solver should have already been initialized
+    // J is const even if it does not compile if said const
+    int ret;
+    bool stop = false;
+    RealVect x;
+    ErrorType err = ErrorType::NoError;
+    if(!has_just_been_inialized){
+        ret  = solver_->Refactorize(J.valuePtr()); 
+        if (ret < 0) {
+            // std::cout << "CKTSOLinearSolver::solve solver_->Refactorize error: " << ret << std::endl;
+            // https://github.com/chenxm1986/cktso/blob/master/include/cktso.h for error info
+            err = ErrorType::SolverReFactor;
+            stop = true;
+        }
+    }
+    if(!stop){
+        const auto n = J.cols(); // should be equal to J_.nrows()
+        x = RealVect(n);
+        ret = solver_->Solve(&b(0), &x(0), false, 1);
+        if (ret < 0) {
+            // std::cout << "CKTSOLinearSolver::solve solver_.Solve error: " << ret << std::endl;
+            err = ErrorType::SolverSolve;
+        }
+        b = x;
+    }
+    return err;
+}
diff --git a/src/CKTSOSolver.h b/src/CKTSOSolver.h
new file mode 100644
index 0000000..101cb62
--- /dev/null
+++ b/src/CKTSOSolver.h
@@ -0,0 +1,83 @@
+// 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.
+
+#ifdef CKTSO_SOLVER_AVAILABLE
+#ifndef CKTSOSOLVER_H
+#define CKTSOSOLVER_H
+
+// eigen is necessary to easily pass data from numpy to c++ without any copy.
+// and to optimize the matrix operations
+#include "Utils.h"
+#include "Eigen/Core"
+#include "Eigen/Dense"
+#include "Eigen/SparseCore"
+
+// import cktso package
+#include "cktso.h"
+
+/**
+class to handle the solver using newton-raphson method, using CKTSO algorithm and sparse matrices.
+CKTSO, according to https://github.com/BDonnot/lightsim2grid/issues/52
+is the successor of NICSLU.
+
+As long as the admittance matrix of the system does not change, you can reuse the same solver.
+Reusing the same solver is possible, but "reset" method must be called.
+
+Otherwise, unexpected behaviour might follow, including "segfault".
+
+NB: the code of CKTSO is not included in this repository. This class is only compiled if the "setup.py"
+can find a version of `https://github.com/chenxm1986/cktso`. Be careful though, this code is under some
+specific license.
+
+**/
+class CKTSOLinearSolver
+{
+    public:
+        CKTSOLinearSolver():
+            solver_(nullptr),
+            nb_thread_(1),
+            ai_(nullptr), 
+            ap_(nullptr),
+            iparm_(nullptr),
+            oparm_(nullptr)
+            {}
+
+        ~CKTSOLinearSolver()
+         {
+            if(solver_!= nullptr) solver_->DestroySolver();
+            if(ai_!= nullptr) delete [] ai_;
+            if(ap_!= nullptr) delete [] ap_;
+            
+            // should not be deleted, see https://github.com/BDonnot/lightsim2grid/issues/52#issuecomment-1333565959
+            // if(iparm_!= nullptr) delete iparm_;
+            // if(oparm_!= nullptr) delete oparm_;
+            
+         }
+
+        // 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);
+
+        // prevent copy and assignment
+        CKTSOLinearSolver(const CKTSOLinearSolver & other) = delete;
+        CKTSOLinearSolver & operator=( const CKTSOLinearSolver & ) = delete;
+        
+    private:
+        // solver initialization
+        ICktSo solver_;
+        const unsigned int nb_thread_;
+        int * ai_;
+        int * ap_;
+        int * iparm_;
+        long long * oparm_;
+
+};
+
+#endif // CKTSOSOLVER_H
+#endif  // CKTSO_SOLVER_AVAILABLE
diff --git a/src/ChooseSolver.h b/src/ChooseSolver.h
index f40b104..764bb4f 100644
--- a/src/ChooseSolver.h
+++ b/src/ChooseSolver.h
@@ -17,9 +17,10 @@
 #include "GaussSeidelSynchSolver.h"
 #include "DCSolver.h"
 
-enum class SolverType {SparseLU, KLU, GaussSeidel, DC, GaussSeidelSynch, NICSLU,
-                       SparseLUSingleSlack, KLUSingleSlack, NICSLUSingleSlack,
-                       KLUDC, NICSLUDC};
+enum class SolverType {SparseLU, KLU, GaussSeidel, DC, GaussSeidelSynch, NICSLU, 
+                       SparseLUSingleSlack, KLUSingleSlack, NICSLUSingleSlack, 
+                       KLUDC, NICSLUDC,
+                       CKTSO, CKTSOSingleSlack, CKTSODC};
 
 // TODO define a template class instead of these weird stuff !!!
 // TODO export all methods from base class !
@@ -40,7 +41,7 @@ class ChooseSolver
         std::vector<SolverType> available_solvers() const
         {
             std::vector<SolverType> res;
-            res.reserve(11);
+            res.reserve(14);
 
             res.push_back(SolverType::SparseLU);
             res.push_back(SolverType::GaussSeidel);
@@ -57,23 +58,37 @@ class ChooseSolver
                 res.push_back(SolverType::NICSLUSingleSlack);
                 res.push_back(SolverType::NICSLUDC);
             #endif
+            #ifdef CKTSO_SOLVER_AVAILABLE
+                res.push_back(SolverType::CKTSO);
+                res.push_back(SolverType::CKTSOSingleSlack);
+                res.push_back(SolverType::CKTSODC);
+            #endif
             return res;
         }
         
         bool is_dc(const SolverType & type){
             bool res;
-            res = (type == SolverType::DC) || (type == SolverType::KLUDC) || (type == SolverType::NICSLUDC);
+            res = (type == SolverType::DC) || 
+                  (type == SolverType::KLUDC) || 
+                  (type == SolverType::NICSLUDC) ||
+                  (type == SolverType::CKTSODC);
             return res;
         }
         SolverType get_type() const {return _solver_type;}
+        
+        bool ac_solver_used() const{
+            auto p_solver = get_prt_solver("ac_solver_used", false);
+            return p_solver->IS_AC;
+        }
 
         void change_solver(const SolverType & type)
         {
             if(type == _solver_type) return;
+
             #ifndef KLU_SOLVER_AVAILABLE
                 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.";
+                    msg = "Impossible to change for a solver using KLU for linear algebra. Please compile lightsim2grid from source to benefit from this.";
                     throw std::runtime_error(msg);
                 }
             #endif
@@ -81,7 +96,15 @@ class ChooseSolver
             #ifndef NICSLU_SOLVER_AVAILABLE
                 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.";
+                    msg = "Impossible to change for a solver using NICSLU for linear algebra. Please compile lightsim2grid from source to benefit from this.";
+                    throw std::runtime_error(msg);
+                }
+            #endif
+
+            #ifndef CKTSO_SOLVER_AVAILABLE
+                if((type == SolverType::CKTSO) || (type == SolverType::CKTSODC) || (type ==  SolverType::CKTSOSingleSlack)){
+                    std::string msg;
+                    msg = "Impossible to change for a solver using CKTSO for linear algebra. Please compile lightsim2grid from source to benefit from this.";
                     throw std::runtime_error(msg);
                 }
             #endif
@@ -134,7 +157,7 @@ class ChooseSolver
         }
         Eigen::Ref<const Eigen::SparseMatrix<real_type> > get_J() const
         {
-            check_right_solver( "get_J");
+            check_right_solver("get_J");
             if(_solver_type == SolverType::SparseLU){
                 return _solver_lu.get_J();}
             #ifdef KLU_SOLVER_AVAILABLE
@@ -149,9 +172,18 @@ class ChooseSolver
             else if(_solver_type == SolverType::NICSLUSingleSlack){
                 return _solver_nicslu_single.get_J();}
             #endif // NICSLU_SOLVER_AVAILABLE
+            #ifdef CKTSO_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::CKTSO){
+                return _solver_cktso.get_J();}
+            else if(_solver_type == SolverType::CKTSOSingleSlack){
+                return _solver_cktso_single.get_J();}
+            #endif // CKTSO_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){
+            else if(_solver_type == SolverType::DC || 
+                    _solver_type == SolverType::KLUDC || 
+                    _solver_type == SolverType::NICSLUDC ||
+                    _solver_type == SolverType::CKTSODC){
                 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.");}
@@ -215,6 +247,19 @@ class ChooseSolver
                     throw std::runtime_error(msg);
                 }
             #endif  // NICSLU_SOLVER_AVAILABLE
+
+            #ifndef CKTSO_SOLVER_AVAILABLE
+                if(_solver_type == SolverType::CKTSO){
+                    std::string msg = "Impossible to use the CKTSO solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                } else if(_solver_type == SolverType::CKTSOSingleSlack){
+                    std::string msg = "Impossible to use the CKTSO solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                } else if(_solver_type == SolverType::CKTSODC){
+                    std::string msg = "Impossible to use the CKTSO solver, it is not available on your platform.";
+                    throw std::runtime_error(msg);
+                }
+            #endif  // CKTSO_SOLVER_AVAILABLE
         }
 
     protected:
@@ -237,6 +282,11 @@ class ChooseSolver
             else if(_solver_type == SolverType::NICSLUSingleSlack){res = &_solver_nicslu_single;}
             else if(_solver_type == SolverType::NICSLUDC){res = &_solver_nicslu_dc;}
             #endif // NICSLU_SOLVER_AVAILABLE
+            #ifdef CKTSO_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::CKTSO){res = &_solver_cktso;}
+            else if(_solver_type == SolverType::CKTSOSingleSlack){res = &_solver_cktso_single;}
+            else if(_solver_type == SolverType::CKTSODC){res = &_solver_cktso_dc;}
+            #endif // CKTSO_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");
@@ -258,6 +308,11 @@ class ChooseSolver
             else if(_solver_type == SolverType::NICSLUSingleSlack){res = &_solver_nicslu_single;}
             else if(_solver_type == SolverType::NICSLUDC){res = &_solver_nicslu_dc;}
             #endif // NICSLU_SOLVER_AVAILABLE
+            #ifdef CKTSO_SOLVER_AVAILABLE
+            else if(_solver_type == SolverType::CKTSO){res = &_solver_cktso;}
+            else if(_solver_type == SolverType::CKTSOSingleSlack){res = &_solver_cktso_single;}
+            else if(_solver_type == SolverType::CKTSODC){res = &_solver_cktso_dc;}
+            #endif // CKTSO_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");
@@ -284,7 +339,11 @@ class ChooseSolver
             NICSLUSolverSingleSlack _solver_nicslu_single;
             NICSLUDCSolver _solver_nicslu_dc;
         #endif  // NICSLU_SOLVER_AVAILABLE
-
+        #ifdef CKTSO_SOLVER_AVAILABLE
+            CKTSOSolver _solver_cktso;
+            CKTSOSolverSingleSlack _solver_cktso_single;
+            CKTSODCSolver _solver_cktso_dc;
+        #endif  // CKTSO_SOLVER_AVAILABLE
 };
 
 #endif  //CHOOSESOLVER_H
diff --git a/src/Computers.cpp b/src/Computers.cpp
index 84d6bec..2641c7b 100644
--- a/src/Computers.cpp
+++ b/src/Computers.cpp
@@ -36,49 +36,56 @@ int Computers::compute_Vs(Eigen::Ref<const RealMat> gen_p,
     _timer_solver = 0.;
 
     auto timer_preproc = CustTimer();
+
+    // init the computations
     const auto & sn_mva = _grid_model.get_sn_mva();
-    Eigen::SparseMatrix<cplx_type> Ybus = _grid_model.get_Ybus(); 
-    const auto & id_me_to_ac_solver = _grid_model.id_me_to_ac_solver();
-    const auto & id_ac_solver_to_me = _grid_model.id_ac_solver_to_me();
+    const bool ac_solver_used = _solver.ac_solver_used();
+    Eigen::SparseMatrix<cplx_type> Ybus = ac_solver_used ? _grid_model.get_Ybus() : _grid_model.get_dcYbus();
+    const Eigen::Index nb_buses_solver = Ybus.cols();
+
+    const auto & id_me_to_solver = ac_solver_used ? _grid_model.id_me_to_ac_solver() :  _grid_model.id_me_to_dc_solver();
+    const auto & id_solver_to_me = ac_solver_used ? _grid_model.id_ac_solver_to_me() : _grid_model.id_dc_solver_to_me();
     const auto & generators = _grid_model.get_generators_as_data();
     const auto & s_generators = _grid_model.get_static_generators_as_data();
     const auto & loads = _grid_model.get_loads_as_data();
 
     const Eigen::Index nb_steps = gen_p.rows();
-    const Eigen::Index nb_buses_solver = Ybus.cols();  // which is equal to Ybus.rows();
 
     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 Eigen::VectorXi & slack_ids  = ac_solver_used ? _grid_model.get_slack_ids(): _grid_model.get_slack_ids_dc();
     const RealVect & slack_weights = _grid_model.get_slack_weights();
     _solver.reset();
 
-    // init the computations
     // now build the Sbus
     _Sbuses = CplxMat::Zero(nb_steps, nb_buses_solver);
 
     bool add_ = true;
-    fill_SBus_real(_Sbuses, generators, gen_p, id_me_to_ac_solver, add_);
-    fill_SBus_real(_Sbuses, s_generators, sgen_p, id_me_to_ac_solver, add_);
+    fill_SBus_real(_Sbuses, generators, gen_p, id_me_to_solver, add_);
+    fill_SBus_real(_Sbuses, s_generators, sgen_p, id_me_to_solver, add_);
     add_ = false;
-    fill_SBus_real(_Sbuses, loads, load_p, id_me_to_ac_solver, add_);
-    fill_SBus_imag(_Sbuses, loads, load_q, id_me_to_ac_solver, add_);
+    fill_SBus_real(_Sbuses, loads, load_p, id_me_to_solver, add_);
+    fill_SBus_imag(_Sbuses, loads, load_q, id_me_to_solver, add_);
     if(sn_mva != 1.0) _Sbuses.array() /= static_cast<cplx_type>(sn_mva);
+    // TODO trafo hack for Sbus !
 
     // init the results matrices
     _voltages = BaseMultiplePowerflow::CplxMat::Zero(nb_steps, nb_total_bus); 
     _amps_flows = RealMat::Zero(0, n_total_);
 
     // extract V solver from the given V
-    CplxVect Vinit_solver = extract_Vsolver_from_Vinit(Vinit, nb_buses_solver, nb_total_bus, id_me_to_ac_solver);
+    CplxVect Vinit_solver = extract_Vsolver_from_Vinit(Vinit, nb_buses_solver, nb_total_bus, id_me_to_solver);
     _timer_pre_proc = timer_preproc.duration();
 
     // compute the powerflows
-    // do the computa   tion for each step
+    // set the "right" init vector
     CplxVect V = Vinit_solver;
+    _grid_model.get_generators().set_vm(V, id_me_to_solver);
+
     bool conv;
     Eigen::Index step_diverge = -1;
     const real_type tol_ = tol / sn_mva; 
+    // do the computation for each step
     for(Eigen::Index i = 0; i < nb_steps; ++i){
         conv = false;
         conv = compute_one_powerflow(Ybus,
@@ -94,7 +101,7 @@ int Computers::compute_Vs(Eigen::Ref<const RealMat> gen_p,
             _timer_total = timer.duration();
             return _status;
         }
-        if(conv && step_diverge < 0) _voltages.row(i)(id_ac_solver_to_me) = V.array();
+        if(conv && step_diverge < 0) _voltages.row(i)(id_solver_to_me) = V.array();
         else step_diverge = i;
     }
     if(step_diverge > 0){
diff --git a/src/Computers.h b/src/Computers.h
index 05c0eca..091583e 100644
--- a/src/Computers.h
+++ b/src/Computers.h
@@ -53,6 +53,15 @@ class Computers: public BaseMultiplePowerflow
                        const CplxVect & Vinit,
                        const int max_iter,
                        const real_type tol);
+                       
+        virtual void clear(){
+            BaseMultiplePowerflow::clear();
+            _Sbuses = CplxMat();
+            _status = 1;
+            _compute_flows = true;
+            _timer_total = 0.;
+            _timer_pre_proc = 0.;
+        }
 
         Eigen::Ref<const CplxMat > get_sbuses() const {return _Sbuses;}
         Eigen::Ref<const RealMat > compute_flows() {
@@ -105,7 +114,7 @@ class Computers: public BaseMultiplePowerflow
                 bus_id_solver = id_me_to_ac_solver[bus_id_me];
                 const auto & tmp = temporal_data.col(el_id).cast<cplx_type>();
                 if(add) Sbuses.col(bus_id_solver) += BaseConstants::my_i * tmp;
-                else Sbuses.col(bus_id_solver) -= BaseConstants::my_i * tmp;;
+                else Sbuses.col(bus_id_solver) -= BaseConstants::my_i * tmp;
             }
         }
 
diff --git a/src/CustTimer.h b/src/CustTimer.h
index 57b99c1..b037fa1 100644
--- a/src/CustTimer.h
+++ b/src/CustTimer.h
@@ -9,6 +9,8 @@
 #ifndef CUSTTIMER_H
 #define CUSTTIMER_H
 
+#include <chrono>
+
 /**
 
 This class presents a basic timer that is used in KLUSolver to know on which part of the solver
diff --git a/src/DCSolver.h b/src/DCSolver.h
index 1e1216b..54e1657 100644
--- a/src/DCSolver.h
+++ b/src/DCSolver.h
@@ -15,7 +15,7 @@ template<class LinearSolver>
 class BaseDCSolver: public BaseSolver
 {
     public:
-        BaseDCSolver():BaseSolver(), _linear_solver(), need_factorize_(true){};
+        BaseDCSolver():BaseSolver(false), _linear_solver(), need_factorize_(true){};
 
         ~BaseDCSolver(){}
 
diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp
index 14eed71..c46abcc 100644
--- a/src/DCSolver.tpp
+++ b/src/DCSolver.tpp
@@ -11,15 +11,15 @@
 // 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
-                                        )
+                                            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
@@ -27,17 +27,14 @@ bool BaseDCSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type>
     //   and for the slack bus both the magnitude and the angle are used.
 
     auto timer = CustTimer();
-    // std::cout << "entering DCSolver::compute_pf" << std::endl;
+
     const int nb_bus_solver = static_cast<int>(Ybus.rows());
-    // std::cout << "computed nb_bus_solver << " << nb_bus_solver << std::endl;
 
     #ifdef __COUT_TIMES
         auto timer_preproc = CustTimer();
     #endif // __COUT_TIMES
     Eigen::SparseMatrix<real_type> dcYbus = Eigen::SparseMatrix<real_type>(nb_bus_solver - 1, nb_bus_solver - 1);
 
-    // Eigen::SparseMatrix<cplx_type> dcYbus_tmp = Ybus;
-    // dcYbus_tmp.makeCompressed();
     const CplxVect & Sbus_tmp = Sbus;
 
     // TODO SLACK (for now i put all slacks as PV, except the first one)
@@ -47,7 +44,6 @@ bool BaseDCSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type>
 
     // find the slack bus
     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
     // and extract only real part
@@ -68,7 +64,7 @@ bool BaseDCSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type>
     }
     dcYbus.setFromTriplets(tripletList.begin(), tripletList.end());
     dcYbus.makeCompressed();
-    // std::cout << "dcYbus made" << std::endl;
+    
     #ifdef __COUT_TIMES
         std::cout << "\t dc: preproc: " << 1000. * timer_preproc.duration() << "ms" << std::endl;
     #endif // __COUT_TIMES
@@ -79,28 +75,14 @@ bool BaseDCSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type>
     #endif // __COUT_TIMES
     bool just_factorize = false;
     if(need_factorize_){
-        // 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;
-    // }
 
     // remove the slack bus from Sbus
     RealVect dcSbus = RealVect::Constant(nb_bus_solver - 1, my_zero_);
@@ -113,10 +95,6 @@ bool BaseDCSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type>
 
     // solve for theta: Sbus = dcY . theta
     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){
@@ -124,14 +102,18 @@ bool BaseDCSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type>
         timer_total_nr_ += timer.duration();
         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;
-    // }
+
+    if(!Va_dc_without_slack.array().allFinite()){
+        // for convergence, all values should be finite
+        err_ = ErrorType::SolverSolve;
+        V = CplxVect();
+        V_ = CplxVect();
+        Vm_ = RealVect();
+        Va_ = RealVect();
+        timer_total_nr_ += timer.duration();
+        return false;
+    }
+
     #ifdef __COUT_TIMES
         std::cout << "\t dc solve: " << 1000. * timer_solve.duration() << "ms" << std::endl;
         auto timer_postproc = CustTimer();
@@ -151,24 +133,22 @@ bool BaseDCSolver<LinearSolver>::compute_pf(const Eigen::SparseMatrix<cplx_type>
 
     // save the results
     Va_ = Va_dc;
+
     // add the Voltage setpoints of the generator
-    Vm_ = V.array().abs(); // RealVect::Constant(V.size(), my_one_);
-    //    Vm_(pv) = V(pv).array().abs();
-    // for(int i = 0; i < pq.size(); ++i) Vm_(pq[i]) = 1.;  // ADDED
+    Vm_ = V.array().abs();
     Vm_(slack_bus_id_solver) = std::abs(V(slack_bus_id_solver));
 
     // now compute the resulting complex voltage
     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_;
-
+    
     #ifdef __COUT_TIMES
         std::cout << "\t dc postproc: " << 1000. * timer_postproc.duration() << "ms" << std::endl;
     #endif // __COUT_TIMES
 
-    // Vm_ = V.array().abs();
-    // Va_ = V.array().arg();
     timer_total_nr_ += timer.duration();
     return true;
 }
diff --git a/src/DataDCLine.cpp b/src/DataDCLine.cpp
new file mode 100644
index 0000000..cc008f9
--- /dev/null
+++ b/src/DataDCLine.cpp
@@ -0,0 +1,60 @@
+// Copyright (c) 2023, 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 "DataDCLine.h"
+#include <iostream>
+#include <sstream>
+
+DataDCLine::StateRes DataDCLine::get_state() const
+{
+    std::vector<real_type> loss_percent(loss_percent_.begin(), loss_percent_.end());
+    std::vector<real_type> loss_mw(loss_mw_.begin(), loss_mw_.end());
+     std::vector<bool> status = status_;
+    DataDCLine::StateRes res(from_gen_.get_state(),
+                             to_gen_.get_state(),
+                             loss_percent,
+                             loss_mw,
+                             status);
+    return res;
+}
+
+void DataDCLine::set_state(DataDCLine::StateRes & my_state){
+    reset_results();
+    from_gen_.set_state(std::get<0>(my_state));
+    to_gen_.set_state(std::get<1>(my_state));
+    std::vector<real_type> & loss_percent = std::get<2>(my_state);
+    std::vector<real_type> & loss_mw = std::get<3>(my_state);
+    std::vector<bool> & status = std::get<4>(my_state);
+    status_ = status;
+    loss_percent_ = RealVect::Map(&loss_percent[0], loss_percent.size());
+    loss_mw_ = RealVect::Map(&loss_mw[0], loss_percent.size());
+}
+
+void DataDCLine::init(const Eigen::VectorXi & branch_from_id,
+                      const Eigen::VectorXi & branch_to_id,
+                      const RealVect & p_mw,
+                      const RealVect & loss_percent,
+                      const RealVect & loss_mw,
+                      const RealVect & vm_or_pu,
+                      const RealVect & vm_ex_pu,
+                      const RealVect & min_q_or,
+                      const RealVect & max_q_or,
+                      const RealVect & min_q_ex,
+                      const RealVect & max_q_ex){
+    loss_percent_ = loss_percent;
+    loss_mw_ = loss_mw;
+    status_ = std::vector<bool>(branch_from_id.size(), true);
+
+    from_gen_.init(p_mw, vm_or_pu, min_q_or, max_q_or, branch_from_id);
+    RealVect p_ex = p_mw;
+    unsigned int size_ = p_mw.size();
+    for(unsigned int i = 0; i < size_; ++i){
+        p_ex(i) = get_to_mw(i, p_ex(i));
+    }
+    to_gen_.init(p_ex, vm_ex_pu, min_q_ex, max_q_ex, branch_to_id);
+}
diff --git a/src/DataDCLine.h b/src/DataDCLine.h
new file mode 100644
index 0000000..7a84470
--- /dev/null
+++ b/src/DataDCLine.h
@@ -0,0 +1,289 @@
+// Copyright (c) 2023, 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 DATADCLINE_H
+#define DATADCLINE_H
+
+#include <iostream>
+
+#include "Utils.h"
+
+#include "Eigen/Core"
+#include "Eigen/Dense"
+#include "Eigen/SparseCore"
+#include "Eigen/SparseLU"
+
+#include "DataGeneric.h"
+#include "DataGen.h"
+
+
+class DataDCLine : public DataGeneric
+{
+    public:
+        class DCLineInfo
+        {
+            public:
+                // members
+                int id;  // id of the dcline
+                bool connected;
+                int bus_or_id;
+                int bus_ex_id;
+                real_type target_p_or_mw;
+                real_type target_vm_or_pu;
+                real_type target_vm_ex_pu;
+                real_type loss_pct;
+                real_type loss_mw;
+                DataGen::GenInfo gen_or;
+                DataGen::GenInfo gen_ex;
+
+                bool has_res;
+                real_type res_p_or_mw;
+                real_type res_q_or_mvar;
+                real_type res_v_or_kv;
+                real_type res_theta_or_deg;
+                real_type res_p_ex_mw;
+                real_type res_q_ex_mvar;
+                real_type res_v_ex_kv;
+                real_type res_theta_ex_deg;
+
+                DCLineInfo(const DataDCLine & r_data_dcline, int my_id):
+                    id(-1),
+                    connected(false),
+                    bus_or_id(-1),
+                    bus_ex_id(-1),
+                    target_p_or_mw(0.),
+                    target_vm_or_pu(0.),
+                    target_vm_ex_pu(0.),
+                    loss_pct(0.),
+                    loss_mw(0.),
+                    gen_or(r_data_dcline.from_gen_, my_id),
+                    gen_ex(r_data_dcline.to_gen_, my_id),
+                    has_res(false),
+                    res_p_or_mw(0.),
+                    res_q_or_mvar(0.),
+                    res_v_or_kv(0.),
+                    res_theta_or_deg(0.),
+                    res_p_ex_mw(0.),
+                    res_q_ex_mvar(0.),
+                    res_v_ex_kv(0.),
+                    res_theta_ex_deg(0.)
+                {
+                    if((my_id >= 0) & (my_id < r_data_dcline.nb()))
+                    {
+                        id = my_id;
+                        loss_pct = r_data_dcline.loss_percent_(my_id);
+                        loss_mw = r_data_dcline.loss_mw_(my_id);
+
+                        bus_or_id = gen_or.bus_id;
+                        target_p_or_mw = gen_or.target_p_mw;
+                        target_vm_or_pu = gen_or.target_vm_pu;
+
+                        bus_ex_id = gen_ex.bus_id;
+                        target_vm_ex_pu = gen_ex.target_vm_pu;
+
+                        has_res = gen_or.has_res;
+                        if(has_res){
+                            res_p_or_mw = gen_or.res_p_mw;
+                            res_q_or_mvar = gen_or.res_q_mvar;
+                            res_v_or_kv = gen_or.res_v_kv;
+                            res_theta_or_deg = gen_or.res_theta_deg;
+                            res_p_ex_mw = gen_ex.res_p_mw;
+                            res_q_ex_mvar = gen_ex.res_q_mvar;
+                            res_v_ex_kv = gen_ex.res_v_kv;
+                            res_theta_ex_deg = gen_ex.res_theta_deg;
+                        }
+                    }
+
+                }
+        };
+        typedef DCLineInfo DataInfo;
+
+    private:
+        typedef DataConstIterator<DataDCLine> DataDCLineConstIterator;
+
+    public:
+    typedef std::tuple<
+               DataGen::StateRes,
+               DataGen::StateRes,
+               std::vector<double>, // loss_percent
+               std::vector<double>, // vm_to_pu
+               std::vector<bool> // loss_mw
+               >  StateRes;
+
+    int nb() const { return static_cast<int>(from_gen_.nb()); }
+
+    // iterator
+    typedef DataDCLineConstIterator const_iterator_type;
+    const_iterator_type begin() const {return DataDCLineConstIterator(this, 0); }
+    const_iterator_type end() const {return DataDCLineConstIterator(this, nb()); }
+    DCLineInfo operator[](int id) const
+    {
+        if(id < 0)
+        {
+            throw std::range_error("You cannot ask for a negative dc line");
+        }
+        if(id >= nb())
+        {
+            throw std::range_error("DCLine out of bound. Not enough dc line on the grid.");
+        }
+        return DCLineInfo(*this, id);
+    }
+
+    // underlying generators are not pv when powerline is off
+    DataDCLine(): from_gen_(false), to_gen_(false) {};
+
+    // pickle
+    DataDCLine::StateRes get_state() const;
+    void set_state(DataDCLine::StateRes & my_state);
+
+    // TODO min_p, max_p
+    void init(const Eigen::VectorXi & branch_from_id,
+              const Eigen::VectorXi & branch_to_id,
+              const RealVect & p_mw,
+              const RealVect & loss_percent,
+              const RealVect & loss_mw,
+              const RealVect & vm_or_pu,
+              const RealVect & vm_ex_pu,
+              const RealVect & min_q_or,
+              const RealVect & max_q_or,
+              const RealVect & min_q_ex,
+              const RealVect & max_q_ex
+              );
+
+    // accessor / modifiers
+    void deactivate(int dcline_id, bool & need_reset) {
+        _deactivate(dcline_id, status_, need_reset);
+        from_gen_.deactivate(dcline_id, need_reset);
+        to_gen_.deactivate(dcline_id, need_reset);
+        }
+    void reactivate(int dcline_id, bool & need_reset) {
+        _reactivate(dcline_id, status_, need_reset);
+        from_gen_.reactivate(dcline_id, need_reset);
+        to_gen_.reactivate(dcline_id, need_reset);
+        }
+    void change_bus_or(int dcline_id, int new_bus_id, bool & need_reset, int nb_bus) {
+        from_gen_.change_bus(dcline_id, new_bus_id, need_reset, nb_bus);}
+    void change_bus_ex(int dcline_id, int new_bus_id, bool & need_reset, int nb_bus) {
+        to_gen_.change_bus(dcline_id, new_bus_id, need_reset, nb_bus);}
+    int get_bus_or(int dcline_id) {return from_gen_.get_bus(dcline_id);}
+    int get_bus_ex(int dcline_id) {return to_gen_.get_bus(dcline_id);}
+    real_type get_qmin_or(int dcline_id) {return from_gen_.get_qmin(dcline_id);}
+    real_type get_qmax_or(int dcline_id) {return  from_gen_.get_qmax(dcline_id);}
+    real_type get_qmin_ex(int dcline_id) {return to_gen_.get_qmin(dcline_id);}
+    real_type get_qmax_ex(int dcline_id) {return  to_gen_.get_qmax(dcline_id);}
+
+    real_type get_to_mw(int dcline_id, real_type from_mw){
+        // TODO set it to load convention instead of gen convention as in lightsim2grid
+        real_type new_p_ext = from_mw >= 0 ? 
+                              -(from_mw + loss_mw_(dcline_id)) / (1.0 - 0.01 * loss_percent_(dcline_id)) :
+                              -from_mw * (1.0 - 0.01 * loss_percent_(dcline_id)) - loss_mw_(dcline_id)
+                              ;
+        return new_p_ext;
+    }
+    void change_p(int dcline_id, real_type new_p, bool & need_reset){
+        from_gen_.change_p(dcline_id, -1.0 * new_p, need_reset);
+
+        to_gen_.change_p(dcline_id, -1.0 * get_to_mw(dcline_id, new_p), need_reset);
+    }
+    void change_v_or(int dcline_id, real_type new_v_pu, bool & need_reset){
+        from_gen_.change_v(dcline_id, new_v_pu, need_reset);
+    }
+    void change_v_ex(int dcline_id, real_type new_v_pu, bool & need_reset){
+        to_gen_.change_v(dcline_id, new_v_pu, need_reset);
+    }
+
+    // solver stuff
+    virtual void fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver){
+        from_gen_.fillSbus(Sbus, id_grid_to_solver);   
+        to_gen_.fillSbus(Sbus, id_grid_to_solver);   
+    }
+
+    virtual void fillpv(std::vector<int>& bus_pv,
+                        std::vector<bool> & has_bus_been_added,
+                        Eigen::VectorXi & slack_bus_id_solver,
+                        const std::vector<int> & id_grid_to_solver) const {
+        from_gen_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_grid_to_solver);   
+        to_gen_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_grid_to_solver);   
+    }
+    void init_q_vector(int nb_bus,
+                       Eigen::VectorXi & total_gen_per_bus,
+                       RealVect & total_q_min_per_bus,
+                       RealVect & total_q_max_per_bus) const{
+        from_gen_.init_q_vector(nb_bus, total_gen_per_bus, total_q_min_per_bus, total_q_max_per_bus);
+        to_gen_.init_q_vector(nb_bus, total_gen_per_bus, total_q_min_per_bus, total_q_max_per_bus);
+    }
+    void 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){
+        from_gen_.compute_results(Va, Vm, V,
+                                  id_grid_to_solver,
+                                  bus_vn_kv, sn_mva, ac);
+        to_gen_.compute_results(Va, Vm, V,
+                                id_grid_to_solver,
+                                bus_vn_kv, sn_mva, ac);
+    }
+
+    void reset_results(){
+        from_gen_.reset_results();
+        to_gen_.reset_results();
+    }
+    void set_q(const RealVect & reactive_mismatch,
+               const std::vector<int> & id_grid_to_solver,
+               bool ac,
+               const Eigen::VectorXi & total_gen_per_bus,
+               const RealVect & total_q_min_per_bus,
+               const RealVect & total_q_max_per_bus){
+        // TODO set it to load convention instead of gen convention as in lightsim2grid
+        from_gen_.set_q(reactive_mismatch, id_grid_to_solver, ac, total_gen_per_bus, total_q_min_per_bus, total_q_max_per_bus);
+        to_gen_.set_q(reactive_mismatch, id_grid_to_solver, ac, total_gen_per_bus, total_q_min_per_bus, total_q_max_per_bus);
+    }
+    void get_vm_for_dc(RealVect & Vm){
+        from_gen_.get_vm_for_dc(Vm);
+        to_gen_.get_vm_for_dc(Vm);
+    }
+    void set_vm_or(CplxVect & V, const std::vector<int> & id_grid_to_solver) const{
+        from_gen_.set_vm(V, id_grid_to_solver);
+    }
+    void set_vm_ex(CplxVect & V, const std::vector<int> & id_grid_to_solver) const{
+        to_gen_.set_vm(V, id_grid_to_solver);
+    }
+
+    /**
+    this functions makes sure that the voltage magnitude of every connected bus is properly used to initialize
+    the ac powerflow
+    **/
+    void set_vm(CplxVect & V, const std::vector<int> & id_grid_to_solver) const{
+        from_gen_.set_vm(V, id_grid_to_solver);
+        to_gen_.set_vm(V, id_grid_to_solver);
+    }
+
+    const std::vector<bool>& get_status() const {return status_;}
+    const Eigen::VectorXi & get_bus_id_or() const {return from_gen_.get_bus_id();}
+    const Eigen::VectorXi & get_bus_id_ex() const {return to_gen_.get_bus_id();}
+    
+    tuple3d get_or_res() const {return from_gen_.get_res();}
+    tuple3d get_ex_res() const {return to_gen_.get_res();}
+    Eigen::Ref<const RealVect> get_theta_or() const {return from_gen_.get_theta();}
+    Eigen::Ref<const RealVect> get_theta_ex() const {return to_gen_.get_theta();}
+
+    protected:
+        // it is modeled as 2 generators that are "linked" together
+        // see https://pandapower.readthedocs.io/en/v2.0.1/elements/dcline.html#electric-model
+        DataGen from_gen_;
+        DataGen to_gen_;
+        RealVect loss_percent_;
+        RealVect loss_mw_;
+        std::vector<bool> status_;
+
+};
+
+#endif  //DATADCLINE_H
\ No newline at end of file
diff --git a/src/DataGen.cpp b/src/DataGen.cpp
index c4804b8..5f3d893 100644
--- a/src/DataGen.cpp
+++ b/src/DataGen.cpp
@@ -44,6 +44,7 @@ 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.);
+    turnedoff_gen_pv_ = true;
 }
 
 
@@ -57,21 +58,25 @@ DataGen::StateRes DataGen::get_state() const
      std::vector<bool> status = 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);
+     DataGen::StateRes res(turnedoff_gen_pv_, 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 )
 {
     reset_results();
+    
+    turnedoff_gen_pv_ = std::get<0>(my_state);
 
-    std::vector<real_type> & p_mw = std::get<0>(my_state);
-    std::vector<real_type> & vm_pu = std::get<1>(my_state);
-    std::vector<real_type> & min_q = std::get<2>(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);
+    // the generators themelves
+    std::vector<real_type> & p_mw = std::get<1>(my_state);
+    std::vector<real_type> & vm_pu = std::get<2>(my_state);
+    std::vector<real_type> & min_q = std::get<3>(my_state);
+    std::vector<real_type> & max_q = std::get<4>(my_state);
+    std::vector<int> & bus_id = std::get<5>(my_state);
+    std::vector<bool> & status = std::get<6>(my_state);
+    std::vector<bool> & slack_bus = std::get<7>(my_state);
+    std::vector<real_type> & slack_weight = std::get<8>(my_state);
     // TODO check sizes
 
     // input data
@@ -102,7 +107,7 @@ RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vecto
             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];
+        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();
@@ -110,7 +115,7 @@ RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vecto
     return res;
 }
 
-void DataGen::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver){
+void DataGen::fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver){
     const int nb_gen = nb();
     int bus_id_me, bus_id_solver;
     real_type tmp;
@@ -146,6 +151,9 @@ 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 ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue;  // in this case turned off generators are not pv
+
         if(bus_id_solver == _deactivated_bus_id){
             // TODO DEBUG MODE only this in debug mode
             std::ostringstream exc_;
@@ -190,6 +198,9 @@ void DataGen::get_vm_for_dc(RealVect & Vm){
     for(int gen_id = 0; gen_id < nb_gen; ++gen_id){
         //  i don't do anything if the generator is disconnected
         if(!status_[gen_id]) continue;
+
+        if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue;  // in this case turned off generators are not pv
+
         bus_id_me = bus_id_(gen_id);
         real_type tmp = vm_pu_(gen_id);
         if(tmp != 0.) Vm(bus_id_me) = tmp;
@@ -208,6 +219,16 @@ void DataGen::change_p(int gen_id, real_type new_p, bool & need_reset)
         exc_ << ")";
         throw std::runtime_error(exc_.str());
     }
+    if(!turnedoff_gen_pv_){
+        // if turned off generators (including these with p==0)
+        // are not pv, if we change the active generation, it changes
+        // the list of pv buses, so I need to refactorize the solver
+        // on the other hand, if all generators are pv then I do not need to refactorize in this case
+        if((p_mw_(gen_id) == 0. && new_p != 0.) || 
+           (p_mw_(gen_id) != 0. && new_p == 0.)){
+            need_reset = true;
+           }
+    }
     p_mw_(gen_id) = new_p;
 }
 
@@ -226,13 +247,15 @@ void DataGen::change_v(int gen_id, real_type new_v_pu, bool & need_reset)
     vm_pu_(gen_id) = new_v_pu;
 }
 
-void DataGen::set_vm(CplxVect & V, const std::vector<int> & id_grid_to_solver)
+void DataGen::set_vm(CplxVect & V, const std::vector<int> & id_grid_to_solver) const
 {
     const int nb_gen = nb();
     int bus_id_me, bus_id_solver;
     for(int gen_id = 0; gen_id < nb_gen; ++gen_id){
         //  i don't do anything if the generator is disconnected
         if(!status_[gen_id]) continue;
+        
+        if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue;  // in this case turned off generators are not pv
 
         bus_id_me = bus_id_(gen_id);
         bus_id_solver = id_grid_to_solver[bus_id_me];
@@ -291,27 +314,36 @@ void DataGen::set_p_slack(const RealVect& node_mismatch,
         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"
+        // std::cout << "gen_id " << gen_id << " my_contrib_slack " << my_contrib_slack << ", " << total_contrib_slack << " node_mismatch " << node_mismatch(bus_id_solver) << std::endl;
         res_p_(gen_id) += node_mismatch(bus_id_solver) * my_contrib_slack / total_contrib_slack;
     }
 }
 
-void DataGen::init_q_vector(int nb_bus_total)  // total number of bus on the grid
+void DataGen::init_q_vector(int nb_bus,
+                            Eigen::VectorXi & total_gen_per_bus,
+                            RealVect & total_q_min_per_bus,
+                            RealVect & total_q_max_per_bus) const // delta_q_per_gen_)  // total number of bus on the grid
 {
     const int nb_gen = nb();
-    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;
+
+        if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue;  // in this case turned off generators are not pv
+        
         int bus_id = bus_id_(gen_id);
-        total_q_min_per_bus_(bus_id) += min_q_(gen_id);
-        total_q_max_per_bus_(bus_id) += max_q_(gen_id);
-        total_gen_per_bus_(bus_id) += 1;
+        total_q_min_per_bus(bus_id) += min_q_(gen_id);
+        total_q_max_per_bus(bus_id) += max_q_(gen_id);
+        total_gen_per_bus(bus_id) += 1;
     }
 }
 
-void DataGen::set_q(const RealVect & reactive_mismatch, const std::vector<int> & id_grid_to_solver, bool ac)
+void DataGen::set_q(const RealVect & reactive_mismatch,
+                    const std::vector<int> & id_grid_to_solver,
+                    bool ac,
+                    const Eigen::VectorXi & total_gen_per_bus,
+                    const RealVect & total_q_min_per_bus,
+                    const RealVect & total_q_max_per_bus)
 {
     const int nb_gen = nb();
     res_q_ = RealVect::Constant(nb_gen, 0.);
@@ -321,15 +353,18 @@ void DataGen::set_q(const RealVect & reactive_mismatch, const std::vector<int> &
     {
         real_type real_q = 0.;
         if(!status_[gen_id]) continue;  // set at 0 for disconnected generators
+
+        if ((!turnedoff_gen_pv_) && p_mw_(gen_id) == 0.) continue;  // in this case turned off generators are not pv
+
         int bus_id = bus_id_(gen_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);
-        real_type min_q_bus = total_q_min_per_bus_(bus_id);
-        int nb_gen_with_me = total_gen_per_bus_(bus_id);
+        real_type max_q_bus = total_q_max_per_bus(bus_id);
+        real_type min_q_bus = total_q_min_per_bus(bus_id);
+        int nb_gen_with_me = total_gen_per_bus(bus_id);
         if(nb_gen_with_me == 1){
             real_q = q_to_absorb;
         }else{
@@ -340,3 +375,28 @@ void DataGen::set_q(const RealVect & reactive_mismatch, const std::vector<int> &
     }
 }
 
+
+void DataGen::update_slack_weights(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > could_be_slack,
+                                   bool & need_reset)
+{
+    const int nb_gen = nb();
+    for(int gen_id = 0; gen_id < nb_gen; ++gen_id)
+    {
+        if(could_be_slack(gen_id) && status_[gen_id]){
+            // gen is connected and participate to the slack
+            if(p_mw_(gen_id) > 0.){
+                // gen is properly connected
+                if(!gen_slackbus_[gen_id]) need_reset = true; // it was not in the slack before, so I need to reset the solver
+                add_slackbus(gen_id, p_mw_(gen_id));
+
+            }else{
+                // gen is now "turned off"
+                if(gen_slackbus_[gen_id]) need_reset = true;  // it was in the slack before, so I need to reset the solver
+                remove_slackbus(gen_id);
+            }
+        }else{
+            if(gen_slackbus_[gen_id]) need_reset = true;  // it was in the slack before, I need to reset the solver
+            remove_slackbus(gen_id);
+        }
+    }
+}
diff --git a/src/DataGen.h b/src/DataGen.h
index 35a947c..99f2ae9 100644
--- a/src/DataGen.h
+++ b/src/DataGen.h
@@ -101,6 +101,7 @@ class DataGen: public DataGeneric
 
     public:
     typedef std::tuple<
+       bool,
        std::vector<real_type>, // p_mw
        std::vector<real_type>, // vm_pu_
        std::vector<real_type>, // min_q_
@@ -111,7 +112,8 @@ class DataGen: public DataGeneric
        std::vector<real_type> // gen_slack_weight_
        >  StateRes;
 
-    DataGen() {};
+    DataGen():turnedoff_gen_pv_(true){};
+    DataGen(bool turnedoff_gen_pv):turnedoff_gen_pv_(turnedoff_gen_pv) {};
 
     // TODO add pmin and pmax here !
     void init(const RealVect & generators_p,
@@ -166,6 +168,12 @@ class DataGen: public DataGeneric
     void set_p_slack(const RealVect& node_mismatch, const std::vector<int> & id_grid_to_solver);
 
     // modification
+    void turnedoff_no_pv(){turnedoff_gen_pv_=false;}  // turned off generators are not pv
+    void turnedoff_pv(){turnedoff_gen_pv_=true;}  // turned off generators are pv
+    bool get_turnedoff_gen_pv() const {return turnedoff_gen_pv_;}
+    void update_slack_weights(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > could_be_slack,
+                              bool & need_reset);
+
     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);}
@@ -175,12 +183,15 @@ class DataGen: public DataGeneric
     void change_p(int gen_id, real_type new_p, bool & need_reset);
     void change_v(int gen_id, real_type new_v_pu, bool & need_reset);
 
-    virtual void fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver);
+    virtual void fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver);
     virtual void fillpv(std::vector<int>& bus_pv,
                         std::vector<bool> & has_bus_been_added,
                         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 init_q_vector(int nb_bus,
+                       Eigen::VectorXi & total_gen_per_bus,
+                       RealVect & total_q_min_per_bus,
+                       RealVect & total_q_max_per_bus) const; // delta_q_per_gen_
 
     void compute_results(const Eigen::Ref<const RealVect> & Va,
                          const Eigen::Ref<const RealVect> & Vm,
@@ -190,14 +201,19 @@ class DataGen: public DataGeneric
                          real_type sn_mva,
                          bool ac);
     void reset_results();
-    void set_q(const RealVect & reactive_mismatch, const std::vector<int> & id_grid_to_solver, bool ac);
+    void set_q(const RealVect & reactive_mismatch,
+               const std::vector<int> & id_grid_to_solver,
+               bool ac,
+               const Eigen::VectorXi & total_gen_per_bus,
+               const RealVect & total_q_min_per_bus,
+               const RealVect & total_q_max_per_bus);
 
     void get_vm_for_dc(RealVect & Vm);
     /**
     this functions makes sure that the voltage magnitude of every connected bus is properly used to initialize
     the ac powerflow
     **/
-    void set_vm(CplxVect & V, const std::vector<int> & id_grid_to_solver);
+    void set_vm(CplxVect & V, const std::vector<int> & id_grid_to_solver) const;
 
     tuple3d get_res() const {return tuple3d(res_p_, res_q_, res_v_);}
     Eigen::Ref<const RealVect> get_theta() const {return res_theta_;}
@@ -226,9 +242,7 @@ class DataGen: public DataGeneric
         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_;
+        // 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
@@ -236,6 +250,9 @@ class DataGen: public DataGeneric
         RealVect res_q_;  // in MVar
         RealVect res_v_;  // in kV
         RealVect res_theta_;  // in deg (and not rad)
+
+        // different parameter of the behaviour of the class
+        bool turnedoff_gen_pv_;  // are turned off generators (including one with p=0) pv ?
 };
 
 #endif  //DATAGEN_H
diff --git a/src/DataGeneric.h b/src/DataGeneric.h
index 048ee31..a2be43f 100644
--- a/src/DataGeneric.h
+++ b/src/DataGeneric.h
@@ -72,7 +72,7 @@ class DataGeneric : public BaseConstants
                               const std::vector<int> & id_grid_to_solver,
                               real_type sn_mva) {};
         virtual void fillYbus(Eigen::SparseMatrix<cplx_type> & res, bool ac, const std::vector<int> & id_grid_to_solver) {};
-        virtual void fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver){};
+        virtual void fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver){};
         virtual void fillpv(std::vector<int>& bus_pv,
                             std::vector<bool> & has_bus_been_added,
                             Eigen::VectorXi & slack_bus_id_solver,
diff --git a/src/DataLine.h b/src/DataLine.h
index 943cacb..317ceee 100644
--- a/src/DataLine.h
+++ b/src/DataLine.h
@@ -200,6 +200,13 @@ class DataLine : public DataGeneric
     Eigen::Ref<const CplxVect> yac_tf() const {return yac_tf_;}
     Eigen::Ref<const CplxVect> yac_tt() const {return yac_tt_;}
 
+    Eigen::Ref<const CplxVect> ydc_ff() const {return ydc_ff_;}
+    Eigen::Ref<const CplxVect> ydc_ft() const {return ydc_ft_;}
+    Eigen::Ref<const CplxVect> ydc_tf() const {return ydc_tf_;}
+    Eigen::Ref<const CplxVect> ydc_tt() const {return ydc_tt_;}
+    // for consistency with trafo, when used for example in BaseMultiplePowerflow...
+    Eigen::Ref<const RealVect> dc_x_tau_shift() const {return RealVect();}
+
     protected:
         void _update_model_coeffs();
 
diff --git a/src/DataLoad.cpp b/src/DataLoad.cpp
index 3c68b75..70e8f6a 100644
--- a/src/DataLoad.cpp
+++ b/src/DataLoad.cpp
@@ -47,7 +47,7 @@ void DataLoad::set_state(DataLoad::StateRes & my_state )
 }
 
 
-void DataLoad::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver){
+void DataLoad::fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver){
     int nb_load = nb();
     int bus_id_me, bus_id_solver;
     cplx_type tmp;
@@ -65,7 +65,7 @@ void DataLoad::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_gr
             throw std::runtime_error(exc_.str());
         }
         tmp = static_cast<cplx_type>(p_mw_(load_id));
-        if(ac) tmp += my_i * q_mvar_(load_id);
+        tmp += my_i * q_mvar_(load_id);
         Sbus.coeffRef(bus_id_solver) -= tmp;
     }
 }
diff --git a/src/DataLoad.h b/src/DataLoad.h
index 49bf824..89fe216 100644
--- a/src/DataLoad.h
+++ b/src/DataLoad.h
@@ -139,7 +139,7 @@ class DataLoad : public DataGeneric
     void change_p(int load_id, real_type new_p, bool & need_reset);
     void change_q(int load_id, real_type new_q, bool & need_reset);
 
-    virtual void fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver);
+    virtual void fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver);
 
     void compute_results(const Eigen::Ref<const RealVect> & Va,
                          const Eigen::Ref<const RealVect> & Vm,
diff --git a/src/DataSGen.cpp b/src/DataSGen.cpp
index ea84e83..d6ebbd9 100644
--- a/src/DataSGen.cpp
+++ b/src/DataSGen.cpp
@@ -82,7 +82,7 @@ void DataSGen::set_state(DataSGen::StateRes & my_state )
 }
 
 
-void DataSGen::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver){
+void DataSGen::fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver){
     const int nb_sgen = nb();
     int bus_id_me, bus_id_solver;
     cplx_type tmp;
@@ -100,7 +100,7 @@ void DataSGen::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_gr
             throw std::runtime_error(exc_.str());
         }
         tmp = static_cast<cplx_type>(p_mw_(sgen_id));
-        if(ac) tmp += my_i * q_mvar_(sgen_id);
+        tmp += my_i * q_mvar_(sgen_id);
         Sbus.coeffRef(bus_id_solver) += tmp;
     }
 }
diff --git a/src/DataSGen.h b/src/DataSGen.h
index d2dc393..2a99517 100644
--- a/src/DataSGen.h
+++ b/src/DataSGen.h
@@ -159,7 +159,7 @@ class DataSGen: public DataGeneric
     void change_p(int sgen_id, real_type new_p, bool & need_reset);
     void change_q(int sgen_id, real_type new_q, bool & need_reset);
 
-    virtual void fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver);
+    virtual void fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver);
 
     void compute_results(const Eigen::Ref<const RealVect> & Va,
                          const Eigen::Ref<const RealVect> & Vm,
diff --git a/src/DataShunt.cpp b/src/DataShunt.cpp
index d9f563f..fe685fe 100644
--- a/src/DataShunt.cpp
+++ b/src/DataShunt.cpp
@@ -75,9 +75,9 @@ void DataShunt::fillYbus(std::vector<Eigen::Triplet<cplx_type> > & res,
     }
 }
 
-void DataShunt::fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver)  // in DC i need that
+void DataShunt::fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver)  // in DC i need that
 {
-    if(ac) return;  // in AC I do not do that
+    // if(ac) return;  // in AC I do not do that
     // std::cout << " ok i use this function" << std::endl;
     // - bus[:, GS] / baseMVA  # in pandapower
     // yish=gish+jbish -> so g is the MW !
diff --git a/src/DataShunt.h b/src/DataShunt.h
index d90ebf3..26f856c 100644
--- a/src/DataShunt.h
+++ b/src/DataShunt.h
@@ -137,7 +137,7 @@ class DataShunt : public DataGeneric
                           const std::vector<int> & id_grid_to_solver,
                           real_type sn_mva);
     virtual void fillYbus_spmat(Eigen::SparseMatrix<cplx_type> & res, bool ac, const std::vector<int> & id_grid_to_solver);
-    virtual void fillSbus(CplxVect & Sbus, bool ac, const std::vector<int> & id_grid_to_solver);  // in DC i need that
+    virtual void fillSbus(CplxVect & Sbus, const std::vector<int> & id_grid_to_solver);  // in DC i need that
 
     void compute_results(const Eigen::Ref<const RealVect> & Va,
                          const Eigen::Ref<const RealVect> & Vm,
diff --git a/src/DataTrafo.h b/src/DataTrafo.h
index ec9192f..3400b9d 100644
--- a/src/DataTrafo.h
+++ b/src/DataTrafo.h
@@ -204,6 +204,12 @@ class DataTrafo : public DataGeneric
     Eigen::Ref<const CplxVect> yac_tf() const {return yac_tf_;}
     Eigen::Ref<const CplxVect> yac_tt() const {return yac_tt_;}
 
+    Eigen::Ref<const CplxVect> ydc_ff() const {return ydc_ff_;}
+    Eigen::Ref<const CplxVect> ydc_ft() const {return ydc_ft_;}
+    Eigen::Ref<const CplxVect> ydc_tf() const {return ydc_tf_;}
+    Eigen::Ref<const CplxVect> ydc_tt() const {return ydc_tt_;}
+    Eigen::Ref<const RealVect> dc_x_tau_shift() const {return dc_x_tau_shift_;}
+    
     const std::vector<bool>& get_status() const {return status_;}
 
     protected:
diff --git a/src/GridModel.cpp b/src/GridModel.cpp
index 091ef2d..614fdd1 100644
--- a/src/GridModel.cpp
+++ b/src/GridModel.cpp
@@ -43,6 +43,9 @@ GridModel::GridModel(const GridModel & other)
     // 8. storage units
     storages_ = other.storages_;
 
+    // dc lines
+    dc_lines_ = other.dc_lines_;
+
     // copy the attributes specific grid2op (speed optimization)
     n_sub_ = other.n_sub_;
     load_pos_topo_vect_ = other.load_pos_topo_vect_;
@@ -81,6 +84,7 @@ GridModel::StateRes GridModel::get_state() const
     auto res_load = loads_.get_state();
     auto res_sgen = sgens_.get_state();
     auto res_storage = storages_.get_state();
+    auto res_dc_line = dc_lines_.get_state();
 
     GridModel::StateRes res(version_major,
                             version_medium,
@@ -95,7 +99,8 @@ GridModel::StateRes GridModel::get_state() const
                             res_gen,
                             res_load,
                             res_sgen,
-                            res_storage
+                            res_storage,
+                            res_dc_line
                             );
     return res;
 };
@@ -142,6 +147,8 @@ 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);
+    // dc lines
+    DataDCLine::StateRes & state_dc_lines = std::get<14>(my_state);
 
     // assign it to this instance
 
@@ -166,6 +173,8 @@ void GridModel::set_state(GridModel::StateRes & my_state)
     sgens_.set_state(state_sgens);
     // 7. storage units
     storages_.set_state(state_storages);
+    // dc lines
+    dc_lines_.set_state(state_dc_lines);
 };
 
 //init
@@ -231,7 +240,9 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit,
     bool is_ac = true;
     bool reset_solver = topo_changed_;   // I reset the solver only if the topology change
     CplxVect V = pre_process_solver(Vinit, Ybus_ac_,
-                                    id_me_to_ac_solver_, id_ac_solver_to_me_, slack_bus_id_ac_solver_,
+                                    id_me_to_ac_solver_,
+                                    id_ac_solver_to_me_,
+                                    slack_bus_id_ac_solver_,
                                     is_ac, reset_solver);
 
     // start the solver
@@ -245,6 +256,65 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit,
     return res;
 };
 
+void GridModel::check_solution_q_values_onegen(CplxVect & res,
+                                               const DataGen::GenInfo& gen,
+                                               bool check_q_limits) const{
+    if(check_q_limits)
+    {
+        // i need to check the reactive can be absorbed / produced by the generator
+        real_type new_q = my_zero_;
+        real_type react_this_bus = std::imag(res.coeff(gen.bus_id));
+        if((react_this_bus >= gen.min_q_mvar) && (react_this_bus <= gen.max_q_mvar))
+        {
+            // this generator is able to handle all reactive
+            new_q = my_zero_;
+        }else if(react_this_bus < gen.min_q_mvar){
+            // generator cannot absorb enough reactive power
+            new_q = react_this_bus - gen.min_q_mvar; //ex. need -50, qmin is -30, remains: (-50) - (-30) = -20 MVAr
+        }else{
+            // generator cannot produce enough reactive power
+            new_q = react_this_bus - gen.max_q_mvar;  // ex. need 50, qmax is 30, remains: 50 - 30 = 20 MVAr
+        }
+        res.coeffRef(gen.bus_id) = {std::real(res.coeff(gen.bus_id)), new_q};
+    }else{
+        // the q value for the bus at which the generator is connected will be 0
+        res.coeffRef(gen.bus_id) = {std::real(res.coeff(gen.bus_id)), my_zero_};
+    }
+}
+
+void GridModel::check_solution_q_values(CplxVect & res, bool check_q_limits) const{
+    // test for iterator though generators
+    for(const auto & gen: generators_)
+    {
+        if(!gen.connected)
+        {
+            // the generator is disconnected, I do nothing
+            continue;
+        }
+        check_solution_q_values_onegen(res, gen, check_q_limits);
+
+        // 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))};
+        }
+    }
+
+    // then do the same for dc powerlines
+    for(const auto & dcline: dc_lines_)
+    {
+        if(!dcline.connected)
+        {
+            // the generator is disconnected, I do nothing
+            continue;
+        }
+        check_solution_q_values_onegen(res, dcline.gen_or, check_q_limits);
+        check_solution_q_values_onegen(res, dcline.gen_ex, check_q_limits);
+    }
+}
+
 CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_limits)
 {
     // pre process the data to define a proper jacobian matrix, the proper voltage vector etc.
@@ -268,44 +338,7 @@ CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_lim
     if(sn_mva_ != 1.) res *= sn_mva_;
 
     // now check reactive values for buses where there are generators and active values of slack bus
-    // test for iterator though generator
-    for(const auto & gen: generators_)
-    {
-        if(!gen.connected)
-        {
-            // the generator is disconnected, I do nothing
-            continue;
-        }
-        if(check_q_limits)
-        {
-            // i need to check the reactive can be absorbed / produced by the generator
-            real_type new_q = my_zero_;
-            real_type react_this_bus = std::imag(res.coeff(gen.bus_id));
-            if((react_this_bus >= gen.min_q_mvar) && (react_this_bus <= gen.max_q_mvar))
-            {
-                // this generator is able to handle all reactive
-                new_q = my_zero_;
-            }else if(react_this_bus < gen.min_q_mvar){
-                // generator cannot absorb enough reactive power
-                new_q = react_this_bus - gen.min_q_mvar; //ex. need -50, qmin is -30, remains: (-50) - (-30) = -20 MVAr
-            }else{
-                // generator cannot produce enough reactive power
-                new_q = react_this_bus - gen.max_q_mvar;  // ex. need 50, qmax is 30, remains: 50 - 30 = 20 MVAr
-            }
-            res.coeffRef(gen.bus_id) = {std::real(res.coeff(gen.bus_id)), new_q};
-        }else{
-            // the q value for the bus at which the generator is connected will be 0
-            res.coeffRef(gen.bus_id) = {std::real(res.coeff(gen.bus_id)), my_zero_};
-        }
-
-        // 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))};
-        }
-    }
+    check_solution_q_values(res, check_q_limits);
 
     // set to 0 the error on the disconnected bus (it is not initialized at 0.0 in _get_results_back_to_orig_nodes)
     for(int bus_id = 0; bus_id < nb_bus; ++bus_id)
@@ -349,8 +382,13 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit,
     init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver);
     fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver); // TODO what if pv and pq changed ? :O
     
-    generators_.init_q_vector(static_cast<int>(bus_vn_kv_.size()));
-    fillSbus_me(Sbus_, is_ac, id_me_to_solver, slack_bus_id_solver);
+    int nb_bus_total = static_cast<int>(bus_vn_kv_.size());
+    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);
+    generators_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
+    dc_lines_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
+    fillSbus_me(Sbus_, is_ac, id_me_to_solver);
 
     const int nb_bus_solver = static_cast<int>(id_solver_to_me.size());
     CplxVect V = CplxVect::Constant(nb_bus_solver, init_vm_pu_);
@@ -360,6 +398,7 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit,
         V(bus_solver_id) = tmp;
     }
     generators_.set_vm(V, id_me_to_solver);
+    dc_lines_.set_vm(V, id_me_to_solver);
     return V;
 }
 
@@ -384,7 +423,10 @@ CplxVect GridModel::_get_results_back_to_orig_nodes(const CplxVect & res_tmp,
     return res;
 }
 
-void GridModel::process_results(bool conv, CplxVect & res, const CplxVect & Vinit, bool ac,
+void GridModel::process_results(bool conv,
+                                CplxVect & res,
+                                const CplxVect & Vinit,
+                                bool ac,
                                 std::vector<int> & id_me_to_solver)
 {
     if (conv){
@@ -428,8 +470,6 @@ void GridModel::init_Ybus(Eigen::SparseMatrix<cplx_type> & Ybus,
 
     Ybus = Eigen::SparseMatrix<cplx_type>(nb_bus, nb_bus);
     Ybus.reserve(nb_bus + 2*powerlines_.nb() + 2*trafos_.nb());
-
-
 }
 
 void GridModel::init_Sbus(CplxVect & Sbus,
@@ -469,21 +509,22 @@ void GridModel::fillYbus(Eigen::SparseMatrix<cplx_type> & res, bool ac, const st
     sgens_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
     storages_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
     generators_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
+    dc_lines_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
     res.setFromTriplets(tripletList.begin(), tripletList.end());
     res.makeCompressed();
 }
 
-void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector<int>& id_me_to_solver, Eigen::VectorXi & slack_bus_id_solver)
+void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector<int>& id_me_to_solver)
 {
     // init the Sbus vector
-    powerlines_.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);
-
+    powerlines_.fillSbus(Sbus, id_me_to_solver);
+    trafos_.fillSbus(Sbus, id_me_to_solver);
+    shunts_.fillSbus(Sbus, id_me_to_solver);
+    loads_.fillSbus(Sbus, id_me_to_solver);
+    sgens_.fillSbus(Sbus, id_me_to_solver);
+    storages_.fillSbus(Sbus, id_me_to_solver);
+    generators_.fillSbus(Sbus, id_me_to_solver);
+    dc_lines_.fillSbus(Sbus, id_me_to_solver);
     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);
@@ -511,6 +552,7 @@ void GridModel::fillpv_pq(const std::vector<int>& id_me_to_solver,
     storages_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver);
     sgens_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver);
     generators_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver);
+    dc_lines_.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(is_in_vect(bus_id, slack_bus_id_solver)) continue;  // slack bus is not PQ either
@@ -542,6 +584,8 @@ void GridModel::compute_results(bool ac){
     shunts_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac);
     // for prods
     generators_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac);
+    // for dclines
+    dc_lines_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac);
 
     //handle_slack_bus active power
     CplxVect mismatch;  // power mismatch at each bus (SOLVER BUS !!!)
@@ -565,7 +609,10 @@ void GridModel::compute_results(bool ac){
 
     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(ractive_mismatch, id_me_to_solver, ac);
+    generators_.set_q(ractive_mismatch, id_me_to_solver, ac,
+                      total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
+    dc_lines_.set_q(ractive_mismatch, id_me_to_solver, ac,
+                    total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
 }
 
 void GridModel::reset_results(){
@@ -576,6 +623,7 @@ void GridModel::reset_results(){
     sgens_.reset_results();
     storages_.reset_results();
     generators_.reset_results();
+    dc_lines_.reset_results();
 }
 
 CplxVect GridModel::dc_pf(const CplxVect & Vinit,
@@ -695,21 +743,31 @@ void GridModel::update_gens_p(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eige
 {
     update_continuous_values(has_changed, new_values, &GridModel::change_p_gen);
 }
+
+void GridModel::update_sgens_p(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
+                              Eigen::Ref<Eigen::Array<float, Eigen::Dynamic, Eigen::RowMajor> > new_values)
+{
+    update_continuous_values(has_changed, new_values, &GridModel::change_p_sgen);
+}
+
 void GridModel::update_gens_v(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
                               Eigen::Ref<Eigen::Array<float, Eigen::Dynamic, Eigen::RowMajor> > new_values)
 {
     update_continuous_values(has_changed, new_values, &GridModel::change_v_gen);
 }
+
 void GridModel::update_loads_p(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
                               Eigen::Ref<Eigen::Array<float, Eigen::Dynamic, Eigen::RowMajor> > new_values)
 {
     update_continuous_values(has_changed, new_values, &GridModel::change_p_load);
 }
+
 void GridModel::update_loads_q(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
                               Eigen::Ref<Eigen::Array<float, Eigen::Dynamic, Eigen::RowMajor> > new_values)
 {
     update_continuous_values(has_changed, new_values, &GridModel::change_q_load);
 }
+
 void GridModel::update_storages_p(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
                               Eigen::Ref<Eigen::Array<float, Eigen::Dynamic, Eigen::RowMajor> > new_values)
 {
diff --git a/src/GridModel.h b/src/GridModel.h
index a6cee24..a731b6e 100644
--- a/src/GridModel.h
+++ b/src/GridModel.h
@@ -34,6 +34,7 @@
 #include "DataLoad.h"
 #include "DataGen.h"
 #include "DataSGen.h"
+#include "DataDCLine.h"
 
 
 // import newton raphson solvers using different linear algebra solvers
@@ -64,10 +65,12 @@ class GridModel : public DataGeneric
                 // static generators
                 DataSGen::StateRes,
                 // storage units
-                DataLoad::StateRes
+                DataLoad::StateRes,
+                //dc lines
+                DataDCLine::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);
@@ -83,10 +86,18 @@ class GridModel : public DataGeneric
 
         // retrieve the underlying data (raw class)
         const DataGen & get_generators_as_data() const {return generators_;}
+        void turnedoff_no_pv(){generators_.turnedoff_no_pv();}  // turned off generators are not pv
+        void turnedoff_pv(){generators_.turnedoff_pv();}  // turned off generators are pv
+        bool get_turnedoff_gen_pv() {return generators_.get_turnedoff_gen_pv();}
+        void update_slack_weights(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > could_be_slack){
+            generators_.update_slack_weights(could_be_slack, topo_changed_);
+        }
+
         const DataSGen & get_static_generators_as_data() const {return sgens_;}
         const DataLoad & get_loads_as_data() const {return loads_;}
         const DataLine & get_powerlines_as_data() const {return powerlines_;}
         const DataTrafo & get_trafos_as_data() const {return trafos_;}
+        const DataDCLine & get_dclines_as_data() const {return dc_lines_;}
         Eigen::Ref<const RealVect> get_bus_vn_kv() const {return bus_vn_kv_;}
 
         // solver "control"
@@ -166,6 +177,21 @@ class GridModel : public DataGeneric
                            const Eigen::VectorXi & storages_bus_id){
             storages_.init(storages_p, storages_q, storages_bus_id);
         }
+        void init_dclines(const Eigen::VectorXi & branch_from_id,
+                          const Eigen::VectorXi & branch_to_id,
+                          const RealVect & p_mw,
+                          const RealVect & loss_percent,
+                          const RealVect & loss_mw,
+                          const RealVect & vm_or_pu,
+                          const RealVect & vm_ex_pu,
+                          const RealVect & min_q_or,
+                          const RealVect & max_q_or,
+                          const RealVect & min_q_ex,
+                          const RealVect & max_q_ex){
+            dc_lines_.init(branch_from_id, branch_to_id, p_mw,
+                           loss_percent, loss_mw, vm_or_pu, vm_ex_pu,
+                           min_q_or, max_q_or, min_q_ex, max_q_ex);
+        }
 
         void add_gen_slackbus(int gen_id, real_type weight);
         void remove_gen_slackbus(int gen_id);
@@ -193,14 +219,14 @@ class GridModel : public DataGeneric
 
         // dc powerflow
         CplxVect dc_pf(const CplxVect & Vinit,
-                               int max_iter,  // not used for DC
-                               real_type tol  // not used for DC
-                               );
+                       int max_iter,  // not used for DC
+                       real_type tol  // not used for DC
+                       );
 
         // ac powerflow
         CplxVect ac_pf(const CplxVect & Vinit,
-                               int max_iter,
-                               real_type tol);
+                       int max_iter,
+                       real_type tol);
 
         // check the kirchoff law
         CplxVect check_solution(const CplxVect & V, bool check_q_limits);
@@ -216,6 +242,7 @@ class GridModel : public DataGeneric
 
         // read only data accessor
         const DataLine & get_lines() const {return powerlines_;}
+        const DataDCLine & get_dclines() const {return dc_lines_;}
         const DataTrafo & get_trafos() const {return trafos_;}
         const DataGen & get_generators() const {return generators_;}
         const DataLoad & get_loads() const {return loads_;}
@@ -290,6 +317,17 @@ class GridModel : public DataGeneric
         void change_q_storage(int storage_id, real_type new_q) {storages_.change_q(storage_id, new_q, topo_changed_); }
         int get_bus_storage(int storage_id) {return storages_.get_bus(storage_id);}
 
+        //deactivate a powerline (disconnect it)
+        void deactivate_dcline(int dcline_id) {dc_lines_.deactivate(dcline_id, topo_changed_); }
+        void reactivate_dcline(int dcline_id) {dc_lines_.reactivate(dcline_id, topo_changed_); }
+        void change_p_dcline(int dcline_id, real_type new_p) {dc_lines_.change_p(dcline_id, new_p, topo_changed_); }
+        void change_v_or_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_or(dcline_id, new_v_pu, topo_changed_); }
+        void change_v_ex_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_ex(dcline_id, new_v_pu, topo_changed_); }
+        void change_bus_dcline_or(int dcline_id, int new_bus_id) {dc_lines_.change_bus_or(dcline_id, new_bus_id, topo_changed_, static_cast<int>(bus_vn_kv_.size())); }
+        void change_bus_dcline_ex(int dcline_id, int new_bus_id) {dc_lines_.change_bus_ex(dcline_id, new_bus_id, topo_changed_, static_cast<int>(bus_vn_kv_.size())); }
+        int get_bus_dcline_or(int dcline_id) {return dc_lines_.get_bus_or(dcline_id);}
+        int get_bus_dcline_ex(int dcline_id) {return dc_lines_.get_bus_ex(dcline_id);}
+
         // All results access
         tuple3d get_loads_res() const {return loads_.get_res();}
         const std::vector<bool>& get_loads_status() const { return loads_.get_status();}
@@ -307,6 +345,9 @@ class GridModel : public DataGeneric
         const std::vector<bool>& get_storages_status() const { return storages_.get_status();}
         tuple3d get_sgens_res() const {return sgens_.get_res();}
         const std::vector<bool>& get_sgens_status() const { return sgens_.get_status();}
+        tuple3d get_dclineor_res() const {return dc_lines_.get_or_res();}
+        tuple3d get_dclineex_res() const {return dc_lines_.get_ex_res();}
+        const std::vector<bool>& get_dclines_status() const { return dc_lines_.get_status();}
 
         Eigen::Ref<const RealVect> get_gen_theta() const  {return generators_.get_theta();}
         Eigen::Ref<const RealVect> get_load_theta() const  {return loads_.get_theta();}
@@ -316,6 +357,8 @@ class GridModel : public DataGeneric
         Eigen::Ref<const RealVect> get_lineex_theta() const {return powerlines_.get_theta_ex();}
         Eigen::Ref<const RealVect> get_trafohv_theta() const {return trafos_.get_theta_hv();}
         Eigen::Ref<const RealVect> get_trafolv_theta() const {return trafos_.get_theta_lv();}
+        Eigen::Ref<const RealVect> get_dclineor_theta() const {return dc_lines_.get_theta_or();}
+        Eigen::Ref<const RealVect> get_dclineex_theta() const {return dc_lines_.get_theta_ex();}
 
         // get some internal information, be cerafull the ID of the buses might not be the same
         // TODO convert it back to this ID, that will make copies, but who really cares ?
@@ -338,6 +381,9 @@ class GridModel : public DataGeneric
         Eigen::Ref<const Eigen::VectorXi> get_slack_ids() const{
             return slack_bus_id_ac_solver_;
         }
+        Eigen::Ref<const Eigen::VectorXi> get_slack_ids_dc() const{
+            return slack_bus_id_dc_solver_;
+        }
         Eigen::Ref<const RealVect> get_slack_weights() const{
             return slack_weights_;
         }
@@ -366,6 +412,8 @@ class GridModel : public DataGeneric
                                Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, 2, Eigen::RowMajor> > active_bus);
         void update_gens_p(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
                            Eigen::Ref<Eigen::Array<float, Eigen::Dynamic, Eigen::RowMajor> > new_values);
+        void update_sgens_p(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
+                           Eigen::Ref<Eigen::Array<float, Eigen::Dynamic, Eigen::RowMajor> > new_values);
         void update_gens_v(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
                            Eigen::Ref<Eigen::Array<float, Eigen::Dynamic, Eigen::RowMajor> > new_values);
         void update_loads_p(Eigen::Ref<Eigen::Array<bool, Eigen::Dynamic, Eigen::RowMajor> > has_changed,
@@ -438,6 +486,10 @@ class GridModel : public DataGeneric
         {
             n_sub_ = n_sub;
         }
+        
+        void fillSbus_other(CplxVect & res, bool ac, const std::vector<int>& id_me_to_solver){
+            fillSbus_me(res, ac, id_me_to_solver);
+        }
 
     protected:
     // add method to change topology, change ratio of transformers, change
@@ -467,7 +519,7 @@ class GridModel : public DataGeneric
                        std::vector<int>& id_solver_to_me,
                        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, Eigen::VectorXi & slack_bus_id_solver);
+        void fillSbus_me(CplxVect & res, bool ac, const std::vector<int>& id_me_to_solver);
         void fillpv_pq(const std::vector<int>& id_me_to_solver, std::vector<int>& id_solver_to_me,
                        Eigen::VectorXi & slack_bus_id_solver);
 
@@ -548,6 +600,10 @@ class GridModel : public DataGeneric
         CplxVect _get_results_back_to_orig_nodes(const CplxVect & res_tmp,
                                                  std::vector<int> & id_me_to_solver,
                                                  int size);
+
+        void check_solution_q_values( CplxVect & res, bool check_q_limits) const;
+        void check_solution_q_values_onegen(CplxVect & res, const DataGen::GenInfo& gen, bool check_q_limits) const;
+
     protected:
         // member of the grid
         // static const int _deactivated_bus_id;
@@ -585,6 +641,9 @@ class GridModel : public DataGeneric
         DataTrafo trafos_;
 
         // 5. generators
+        RealVect total_q_min_per_bus_;
+        RealVect total_q_max_per_bus_;
+        Eigen::VectorXi total_gen_per_bus_;
         DataGen generators_;
 
         // 6. loads
@@ -596,6 +655,9 @@ class GridModel : public DataGeneric
         // 7. storage units
         DataLoad storages_;
 
+        // hvdc
+        DataDCLine dc_lines_;
+
         // 8. slack bus
         // TODO multiple slack bus
         std::vector<int> slack_bus_id_;
diff --git a/src/KLUSolver.cpp b/src/KLUSolver.cpp
index a44524f..316712a 100644
--- a/src/KLUSolver.cpp
+++ b/src/KLUSolver.cpp
@@ -8,6 +8,8 @@
 
 #include "KLUSolver.h"
 
+#include <iostream>
+
 ErrorType KLULinearSolver::reset(){
     klu_free_symbolic(&symbolic_, &common_);
     klu_free_numeric(&numeric_, &common_);
diff --git a/src/KLUSolver.h b/src/KLUSolver.h
index 8dae9fb..c7aff02 100644
--- a/src/KLUSolver.h
+++ b/src/KLUSolver.h
@@ -10,13 +10,6 @@
 #ifndef KLSOLVER_H
 #define KLSOLVER_H
 
-#include <iostream>
-#include <vector>
-#include <stdio.h>
-#include <cstdint> // for int32
-#include <chrono>
-#include <cmath>  // for PI
-
 // eigen is necessary to easily pass data from numpy to c++ without any copy.
 // and to optimize the matrix operations
 #include "Utils.h"
@@ -30,8 +23,6 @@ extern "C" {
     #include "klu.h"
 }
 
-#include "CustTimer.h"
-#include "BaseNRSolver.h"
 /**
 class to handle the solver using newton-raphson method, using KLU algorithm and sparse matrices.
 
diff --git a/src/NICSLUSolver.cpp b/src/NICSLUSolver.cpp
index b0ad85e..18fed9e 100644
--- a/src/NICSLUSolver.cpp
+++ b/src/NICSLUSolver.cpp
@@ -7,6 +7,7 @@
 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
 #include "NICSLUSolver.h"
+#include <iostream>
 
 ErrorType NICSLULinearSolver::reset(){
     // free everything
@@ -80,14 +81,7 @@ ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix<real_type> & J, RealVect
     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
-        // i'm in the case where it has not
-        //        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_);  // 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 << "NICSLULinearSolver::solve solver_.FactorizeMatrix error: " << ret << std::endl;
@@ -97,8 +91,7 @@ ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix<real_type> & J, RealVect
         }
     }
     if(!stop){
-        // ok = klu_solve(symbolic_, numeric_, n_, 1, &b(0), &common_);
-        // solver.Solve(b, x);
+        const auto n = J.cols(); // should be equal to J_.nrows()
         x = RealVect(n);
         ret = solver_.Solve(&b(0), &x(0));
         if (ret < 0) {
diff --git a/src/NICSLUSolver.h b/src/NICSLUSolver.h
index 60f619d..825bc98 100644
--- a/src/NICSLUSolver.h
+++ b/src/NICSLUSolver.h
@@ -10,13 +10,6 @@
 #ifndef NICSLUSOLVER_H
 #define NICSLUSOLVER_H
 
-#include <iostream>
-#include <vector>
-#include <stdio.h>
-#include <cstdint> // for int32
-#include <chrono>
-#include <cmath>  // for PI
-
 // eigen is necessary to easily pass data from numpy to c++ without any copy.
 // and to optimize the matrix operations
 #include "Utils.h"
@@ -27,8 +20,6 @@
 // import nicslu package
 #include "nicslu_cpp.inl"
 
-#include "CustTimer.h"
-#include "BaseNRSolver.h"
 /**
 class to handle the solver using newton-raphson method, using NICSLU algorithm and sparse matrices.
 
diff --git a/src/SecurityAnalysis.cpp b/src/SecurityAnalysis.cpp
index e92ce3a..0b9fd57 100644
--- a/src/SecurityAnalysis.cpp
+++ b/src/SecurityAnalysis.cpp
@@ -41,36 +41,50 @@ bool SecurityAnalysis::check_invertible(const Eigen::SparseMatrix<cplx_type> & Y
     return ok;
 }
 
-void SecurityAnalysis::init_li_coeffs(){
+void SecurityAnalysis::init_li_coeffs(bool ac_solver_used){
     _li_coeffs.clear();
     _li_coeffs.reserve(_li_defaults.size());
     const auto & powerlines = _grid_model.get_powerlines_as_data();
     const auto & trafos = _grid_model.get_trafos_as_data();
-    const auto & id_me_to_ac_solver = _grid_model.id_me_to_ac_solver();
+    const auto & id_me_to_solver = ac_solver_used ? _grid_model.id_me_to_ac_solver(): _grid_model.id_me_to_dc_solver();
     Eigen::Index bus_1_id, bus_2_id;
     cplx_type y_ff, y_ft, y_tf, y_tt;
     for(const auto & this_cont_id: _li_defaults){
         std::vector<Coeff> this_cont_coeffs;
-        this_cont_coeffs.reserve(this_cont_id.size() * 4);  // usually there are 4 coeffs per powerlines
+        this_cont_coeffs.reserve(this_cont_id.size() * 4);  // usually there are 4 coeffs per powerlines / trafos
         for(auto line_id : this_cont_id){
             if(line_id < n_line_)
             {
                 // this is a powerline
-                bus_1_id = id_me_to_ac_solver[powerlines.get_bus_from()[line_id]];
-                bus_2_id = id_me_to_ac_solver[powerlines.get_bus_to()[line_id]];
-                y_ff = powerlines.yac_ff()[line_id];
-                y_ft = powerlines.yac_ft()[line_id];
-                y_tf = powerlines.yac_tf()[line_id];
-                y_tt = powerlines.yac_tt()[line_id];
+                bus_1_id = id_me_to_solver[powerlines.get_bus_from()[line_id]];
+                bus_2_id = id_me_to_solver[powerlines.get_bus_to()[line_id]];
+                if(ac_solver_used){
+                    y_ff = powerlines.yac_ff()[line_id];
+                    y_ft = powerlines.yac_ft()[line_id];
+                    y_tf = powerlines.yac_tf()[line_id];
+                    y_tt = powerlines.yac_tt()[line_id];
+                }else{
+                    y_ff = powerlines.ydc_ff()[line_id];
+                    y_ft = powerlines.ydc_ft()[line_id];
+                    y_tf = powerlines.ydc_tf()[line_id];
+                    y_tt = powerlines.ydc_tt()[line_id];
+                }
             }else{
                 // this is a trafo
                 const auto trafo_id = line_id - n_line_;
-                bus_1_id = id_me_to_ac_solver[trafos.get_bus_from()[trafo_id]];
-                bus_2_id = id_me_to_ac_solver[trafos.get_bus_to()[trafo_id]];
-                y_ff = trafos.yac_ff()[trafo_id];
-                y_ft = trafos.yac_ft()[trafo_id];
-                y_tf = trafos.yac_tf()[trafo_id];
-                y_tt = trafos.yac_tt()[trafo_id];
+                bus_1_id = id_me_to_solver[trafos.get_bus_from()[trafo_id]];
+                bus_2_id = id_me_to_solver[trafos.get_bus_to()[trafo_id]];
+                if(ac_solver_used){
+                    y_ff = trafos.yac_ff()[trafo_id];
+                    y_ft = trafos.yac_ft()[trafo_id];
+                    y_tf = trafos.yac_tf()[trafo_id];
+                    y_tt = trafos.yac_tt()[trafo_id];
+                }else{
+                    y_ff = trafos.ydc_ff()[trafo_id];
+                    y_ft = trafos.ydc_ft()[trafo_id];
+                    y_tf = trafos.ydc_tf()[trafo_id];
+                    y_tt = trafos.ydc_tt()[trafo_id];
+                }
             }
 
             if(bus_1_id != DataGeneric::_deactivated_bus_id && bus_2_id != DataGeneric::_deactivated_bus_id)
@@ -122,21 +136,25 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t
         throw std::runtime_error(exc_.str());
     }
 
-    // initialize properly the coefficients that I will need to remove
-    init_li_coeffs();
-    Eigen::Index nb_steps = _li_defaults.size();
-
     // read from the grid the usefull information
     const auto & sn_mva = _grid_model.get_sn_mva();
-    Eigen::SparseMatrix<cplx_type> Ybus = _grid_model.get_Ybus(); 
-    const auto & Sbus = _grid_model.get_Sbus(); 
-    const auto & id_ac_solver_to_me = _grid_model.id_ac_solver_to_me();
+    const bool ac_solver_used = _solver.ac_solver_used();
+    Eigen::SparseMatrix<cplx_type> Ybus = ac_solver_used ? _grid_model.get_Ybus() : _grid_model.get_dcYbus();
+    const Eigen::Index nb_buses_solver = Ybus.cols();
+    const auto & id_solver_to_me = ac_solver_used ? _grid_model.id_ac_solver_to_me() : _grid_model.id_dc_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 Eigen::VectorXi & slack_ids = ac_solver_used ? _grid_model.get_slack_ids(): _grid_model.get_slack_ids_dc();
     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();
+    const auto & id_me_to_solver = ac_solver_used ? _grid_model.id_me_to_ac_solver() :  _grid_model.id_me_to_dc_solver();
+    
+    // get the proper Sbus vector
+    CplxVect Sbus = CplxVect::Zero(nb_buses_solver);
+    _grid_model.fillSbus_other(Sbus, ac_solver_used, id_me_to_solver); 
+
+    // initialize properly the coefficients that I will need to remove
+    init_li_coeffs(ac_solver_used);
+    Eigen::Index nb_steps = _li_defaults.size();
 
     // init the results matrices
     _voltages = BaseMultiplePowerflow::CplxMat::Zero(nb_steps, nb_total_bus); 
@@ -146,7 +164,7 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t
     _solver.reset();
 
     // compute the right Vinit to send to the solver
-    CplxVect Vinit_solver = extract_Vsolver_from_Vinit(Vinit, nb_buses_solver, nb_total_bus, id_me_to_ac_solver);
+    CplxVect Vinit_solver = extract_Vsolver_from_Vinit(Vinit, nb_buses_solver, nb_total_bus, id_me_to_solver);
 
     // end of pre processing
     _timer_pre_proc = timer_preproc.duration();
@@ -171,10 +189,10 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t
         {
             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);
+                                         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;
@@ -184,7 +202,7 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t
         timer_modif_Ybus = CustTimer();
         readd_to_Ybus(Ybus, coeffs_modif);
         _timer_modif_Ybus += timer_modif_Ybus.duration();
-        if (conv && invertible) _voltages.row(cont_id)(id_ac_solver_to_me) = V.array();
+        if (conv && invertible) _voltages.row(cont_id)(id_solver_to_me) = V.array();
         ++cont_id;
     }
     _timer_total = timer.duration();
diff --git a/src/SecurityAnalysis.h b/src/SecurityAnalysis.h
index 714ebd8..ce7fe2d 100644
--- a/src/SecurityAnalysis.h
+++ b/src/SecurityAnalysis.h
@@ -64,7 +64,15 @@ class SecurityAnalysis: public BaseMultiplePowerflow
         }
 
         // utilities to remove defaults to simulate (TODO)
-        void clear(){_li_defaults.clear();}
+        virtual void clear(){
+            BaseMultiplePowerflow::clear();
+            _li_defaults.clear();
+            _li_coeffs.clear();
+            _timer_total = 0.;
+            _timer_modif_Ybus = 0.;
+            _timer_pre_proc = 0.;
+        }
+        
         bool remove_n1(int line_id){
             check_ok_el(line_id);
             std::set<int> this_default = {line_id};
@@ -140,7 +148,7 @@ class SecurityAnalysis: public BaseMultiplePowerflow
                 throw std::runtime_error(exc_.str());
             }
         }
-        void init_li_coeffs();
+        void init_li_coeffs(bool ac_solver_used);
         // remove the line parameters from Ybus, this is to emulate its disconnection
         bool remove_from_Ybus(Eigen::SparseMatrix<cplx_type> & Ybus, const std::vector<Coeff> & coeffs) const;
         // after the coefficient has been removed with "remove_from_Ybus", add it back to Ybus
diff --git a/src/Solvers.h b/src/Solvers.h
index d0b60a2..350c254 100644
--- a/src/Solvers.h
+++ b/src/Solvers.h
@@ -13,6 +13,7 @@
 #include "SparseLUSolver.h"
 #include "KLUSolver.h"
 #include "NICSLUSolver.h"
+#include "CKTSOSolver.h"
 
 /** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen**/
 typedef BaseNRSolver<SparseLULinearSolver> SparseLUSolver;
@@ -54,3 +55,20 @@ typedef BaseDCSolver<SparseLULinearSolver> DCSolver;
     /** Solver based on Newton Raphson, using the KLU linear solver, only suitable for the DC approximation**/
     class NICSLUDCSolver : public DCSolver{};
 #endif  // NICSLU_SOLVER_AVAILABLE
+
+#ifdef CKTSO_SOLVER_AVAILABLE
+    /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license)**/
+    typedef BaseNRSolver<CKTSOLinearSolver> CKTSOSolver;
+    /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license), do not consider multiple slack bus**/
+    typedef BaseNRSolverSingleSlack<CKTSOLinearSolver> CKTSOSolverSingleSlack;
+    /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license), only suitable for the DC approximation**/
+    typedef BaseDCSolver<CKTSOLinearSolver> CKTSODCSolver;
+#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 CKTSOSolver : public SparseLUSolver{};
+    /** Solver based on Newton Raphson, using the KLU linear solver, do not consider multiple slack bus**/
+    class CKTSOSolverSingleSlack : public SparseLUSolverSingleSlack{};
+    /** Solver based on Newton Raphson, using the KLU linear solver, only suitable for the DC approximation**/
+    class CKTSODCSolver : public DCSolver{};
+#endif  // CKTSO_SOLVER_AVAILABLE
diff --git a/src/SparseLUSolver.cpp b/src/SparseLUSolver.cpp
index 30bc54e..640a879 100644
--- a/src/SparseLUSolver.cpp
+++ b/src/SparseLUSolver.cpp
@@ -8,6 +8,8 @@
 
 #include "SparseLUSolver.h"
 
+#include <iostream>
+
 ErrorType SparseLULinearSolver::initialize(const Eigen::SparseMatrix<real_type> & J){
     // default Eigen representation: column major, which is good for klu !
     // J is const here
diff --git a/src/SparseLUSolver.h b/src/SparseLUSolver.h
index dee3b8b..4be807a 100644
--- a/src/SparseLUSolver.h
+++ b/src/SparseLUSolver.h
@@ -9,13 +9,6 @@
 #ifndef SPARSELUSOLVER_H
 #define SPARSELUSOLVER_H
 
-#include <iostream>
-#include <vector>
-#include <stdio.h>
-#include <cstdint> // for int32
-#include <chrono>
-#include <cmath>  // for PI
-
 // eigen is necessary to easily pass data from numpy to c++ without any copy.
 // and to optimize the matrix operations
 #include "Utils.h"
@@ -24,8 +17,6 @@
 #include "Eigen/SparseCore"
 #include "Eigen/SparseLU"
 
-#include "CustTimer.h"
-#include "BaseNRSolver.h"
 /**
 class to handle the solver using newton-raphson method, using a "SparseLU" algorithm from Eigein
 and sparse matrices.
diff --git a/src/Utils.h b/src/Utils.h
index a6d9a7c..319b40d 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -32,4 +32,20 @@ typedef std::tuple<Eigen::Ref<const EigenPythonNumType>,
 typedef Eigen::Matrix<real_type, Eigen::Dynamic, 1> RealVect;
 typedef Eigen::Matrix<cplx_type, Eigen::Dynamic, 1> CplxVect;
 
+// type of error in the different solvers
+enum class ErrorType {NoError, SingularMatrix, TooManyIterations, InifiniteValue, SolverAnalyze, SolverFactor, SolverReFactor, SolverSolve, NotInitError, LicenseError};
+
+// define some constant for compilation outside of "setup.py"
+#ifndef VERSION_MAJOR
+#define VERSION_MAJOR -1
+#endif
+
+#ifndef VERSION_MEDIUM
+#define VERSION_MEDIUM -1
+#endif
+
+#ifndef VERSION_MINOR
+#define VERSION_MINOR -1
+#endif
+
 #endif // UTILS_H
diff --git a/src/help_fun_msg.cpp b/src/help_fun_msg.cpp
index edbfbea..fb4d857 100644
--- a/src/help_fun_msg.cpp
+++ b/src/help_fun_msg.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, RTE (https://www.rte-france.com)
+// Copyright (c) 2020-2023, 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,
@@ -109,7 +109,16 @@ const std::string DocSolver::SparseLUSolver = R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `SparseLU`.
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SparseLU)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.SparseLU)` at creation time    
+    
+    .. note::
+        Available on all plateform, this is the default solver used when :class:`lightsim2grid.solver.KLUSolverSingleSlack`
+        is not found (when a "single slack" is detected).
 
 )mydelimiter";
 
@@ -121,7 +130,16 @@ const std::string DocSolver::SparseLUSolverSingleSlack = R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `SparseLUSingleSlack` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.SparseLUSingleSlack)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.SparseLUSingleSlack)` at creation time
+
+    .. note::
+        Available on all plateform, this is the default solver used when a distributed slack bus is detected and :class:`lightsim2grid.solver.SolverType.KLUSolver`
+        is not found.
 
 )mydelimiter";
 
@@ -133,7 +151,18 @@ const std::string DocSolver::DCSolver =  R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `DC` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.DC)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.DC)` at creation time
+
+    .. warning::
+        This is a DC solver that uses the DC approximation. If you want to use this approximation, you need to specified
+        it when you create the grid2op environment, for example with "param.ENV_DC=True".
+
+        Otherwise, it is used internally to find good starting point to intialize the real AC solver.
 
 )mydelimiter";
 
@@ -145,7 +174,15 @@ const std::string DocSolver::KLUSolver = R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `KLU` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.KLU)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.KLU)` at creation time
+
+    .. note::
+        This is the default solver used when a distributed slack bus is detected (when it's available, otherwise see :class:`lightsim2grid.solver.SparseLUSolver`).
 
 )mydelimiter";
 
@@ -157,7 +194,15 @@ const std::string DocSolver::KLUSolverSingleSlack = R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `KLUSingleSlack` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.KLUSingleSlack)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.KLUSingleSlack)` at creation time
+
+    .. note::
+        This is the default solver used when available.
 
 )mydelimiter";
 
@@ -169,7 +214,18 @@ const std::string DocSolver::KLUDCSolver = R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `KLUDC` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.KLUDC)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.KLUDC)` at creation time
+
+    .. warning::
+        This is a DC solver that uses the DC approximation. If you want to use this approximation, you need to specified
+        it when you create the grid2op environment, for example with "param.ENV_DC=True".
+
+        Otherwise, it is used internally to find good starting point to intialize the real AC solver.
 
 )mydelimiter";
 
@@ -181,8 +237,17 @@ const std::string DocSolver::NICSLUSolver = R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `NICSLU` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.NICSLU)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.NICSLU)` at creation time
 
+    .. note::
+
+        NICSLU is available at https://github.com/chenxm1986/nicslu
+        
 )mydelimiter";
 
 const std::string DocSolver::NICSLUSolverSingleSlack = R"mydelimiter(
@@ -193,7 +258,16 @@ const std::string DocSolver::NICSLUSolverSingleSlack = R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `NICSLUSingleSlack` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.NICSLUSingleSlack)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.NICSLUSingleSlack)` at creation time    
+    
+    .. note::
+
+        NICSLU is available at https://github.com/chenxm1986/nicslu
 
 )mydelimiter";
 
@@ -205,33 +279,133 @@ const std::string DocSolver::NICSLUDCSolver = R"mydelimiter(
 
     .. 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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `NICSLUDC` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.NICSLUDC)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.NICSLUDC)` at creation time
+
+    .. warning::
+        This is a DC solver that uses the DC approximation. If you want to use this approximation, you need to specified
+        it when you create the grid2op environment, for example with "param.ENV_DC=True".
+
+        Otherwise, it is used internally to find good starting point to intialize the real AC solver.
+
+    .. note::
+
+        NICSLU is available at https://github.com/chenxm1986/nicslu
+ 
+)mydelimiter";
+
+const std::string DocSolver::CKTSOSolver = R"mydelimiter(
+    This classes implements the Newton Raphson algorithm, allowing for distributed slack and using the faster CKTSO solver available in the CKTSO library
+    for the linear algebra (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 called `CKTSO` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.CKTSO)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.CKTSO)` at creation time
+
+    .. note::
+
+        CKTSO is available at https://github.com/chenxm1986/cktso
+ 
+)mydelimiter";
+
+const std::string DocSolver::CKTSOSolverSingleSlack = R"mydelimiter(
+    This classes implements the Newton Raphson algorithm, the faster CKTSO solver available in the CKTSO library
+    for the linear algebra. It does not support the distributed slack, but can be slightly faster than the :class:`lightsim2grid.solver.CKTSOSolver` .
+
+    See :ref:`available-powerflow-solvers` for more information on how to use it.
+
+    .. note::
+
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `CKTSOSingleSlack` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.CKTSOSingleSlack)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.CKTSOSingleSlack)` at creation time
+
+    .. note::
+
+        CKTSO is available at https://github.com/chenxm1986/cktso
+ 
+)mydelimiter";
+
+const std::string DocSolver::CKTSODCSolver = R"mydelimiter(
+    Alternative implementation of the DC solver, it uses the faster CKTSO solver available in the CKTSO 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 called `CKTSODC` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.CKTSODC)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.CKTSODC)` at creation time
+
+    .. warning::
+        This is a DC solver that uses the DC approximation. If you want to use this approximation, you need to specified
+        it when you create the grid2op environment, for example with "param.ENV_DC=True".
+
+        Otherwise, it is used internally to find good starting point to intialize the real AC solver.
+
+    .. note::
+
+        CKTSO is available at https://github.com/chenxm1986/cktso
 
 )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.
+    are usually much (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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it is called `GaussSeidel` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.GaussSeidel)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.GaussSeidel)` at creation time
+
+    .. warning::
+        It currently does not support distributed slack.
 
 )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.
+    are usually much (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)`).
+        In the enum :attr:`lightsim2grid.solver.SolverType`, it called `GaussSeidelSynch` 
+        
+        You can use it with:
+        
+        - `env_lightsim.backend.set_solver_type(lightsim2grid.solver.GaussSeidelSynch)` after creation
+        - `LightSimBackend(solver_type=lightsim2grid.solver.GaussSeidelSynch)` at creation time
 
+    .. warning::
+        It currently does not support distributed slack.
+        
 )mydelimiter";
 
 const std::string DocSolver::AnySolver = R"mydelimiter(
@@ -370,6 +544,14 @@ const std::string DocIterator::h_pu = R"mydelimiter(
 
 )mydelimiter" + DocIterator::line_model;
 
+
+const std::string DocIterator::only_avail_res = R"mydelimiter(
+    
+    .. 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_mw = R"mydelimiter(
     Get the active production (or consumption) in MW for element of the grid supporting this feature.
 
@@ -377,10 +559,7 @@ const std::string DocIterator::res_p_mw = R"mydelimiter(
     
     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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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.
@@ -389,32 +568,23 @@ const std::string DocIterator::res_q_mvar = R"mydelimiter(
     
     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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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.
@@ -503,7 +673,7 @@ const std::string DocIterator::is_slack = R"mydelimiter(
     .. 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`)
+        to computed the powerflow do not support distributed slack buses - **eg** :class:`lightsim2grid.solver.SparseLUSingleSlack`)
 
         This is why we recommend to use the (slower) but more accurate :class:`lightsim2grid.solver.SparseLUSolver` or 
         :class:`lightsim2grid.solver.KLUSolver` for example.
@@ -884,94 +1054,64 @@ const std::string DocIterator::bus_lv_id = R"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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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
@@ -1067,95 +1207,277 @@ const std::string DocIterator::bus_ex_id = R"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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 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";
+)mydelimiter" + DocIterator::only_avail_res;
 
 const std::string DocIterator::res_a_ex_ka = R"mydelimiter(
     Get the current flows (in kA) at the "ex" side of the line.
 
+)mydelimiter" + DocIterator::only_avail_res;
+
+
+const std::string DocIterator::DataDCLine = R"mydelimiter(
+    This class allows to iterate through the dc lines of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were
+    in a python list.
+
+    DC lines are modeled as in pandapower and can be represented a the 
+    `pandapower dclines <https://pandapower.readthedocs.io/en/latest/elements/dcline.html#electric-model>`_ . Basically
+    a dc line is made of 2 generators (one at each side `or` or `ex`). These
+    two generators are linked together: if one produces xx MW the other one consume yy MW (and there 
+    exists a relation between xx and yy).
+
+    To dive a bit into the modelling, the two underlying generators can be controlled independantly for the voltage
+    setpoint. But they are linked together for their active value. A dc powerline has some losses (both in MW and in 
+    percent) and the formula for computing the power injected / produced by each generator is:
+
+    - if `xx` is positive, then `yy = -1.0 * (xx - loss_mw) * (1.0 - 0.01 * loss_percent)`
+    - if `xx` is negative, then `yy = -1.0 * xx / (1.0 - 0.01 * loss_percent) + loss_mw
+
+    The first formula directly comes from pandapower. The second one ensures that if the direction of the flow is 
+    inverted, then flows should also be inverted (`xx` becomes `yy` and reciprocally).
+
+    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 dc powerlines (usually there are none...)
+        for dcline in grid_model.get_dclines():
+            # do something with line !
+            dcline.bus_or_id
+
+        print(f"There are {len(grid_model.get_dclines())} lines on the grid.")
+
+        // first_dcline = grid_model.get_dclines()[0]
+
+    You can have a look at :class:`lightsim2grid.elements.DCLineInfo` for properties of these elements.
+
+)mydelimiter";
+
+
+const std::string DocIterator::DCLineInfo = R"mydelimiter(
+    This class represents what you get from retrieving the dc powerlines from :class:`lightsim2grid.elements.DataDCLine`.
+
+    It allows to read information from each dc powerline of the powergrid.
+
+    DC 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::
-        This feature is only relevant if the results have been computed (for example if a powerflow has successfully run)
+        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_dclines()[0]  # first dcline, this is a `DCLineInfo`
+        for dcline in grid_model.get_dclines():
+            # dcline is a `LineInfo`
+            dcline.bus_or_id
+
+    Notes
+    -----
+    DC lines are modeled by two underlying generators.
+    
+    Each of them can be controlled independantly for the voltage setpoint. 
+    
+    But they are linked together for their active value. A dc powerline has some losses (both in MW `loss_mw` and in 
+    percent `loss_percent`) and the formula for computing the power injected / produced by each generator is:
+
+    - if `xx` is positive, then `yy = -1.0 * (xx - loss_mw) * (1.0 - 0.01 * loss_percent)`
+    - if `xx` is negative, then `yy = -1.0 * xx / (1.0 - 0.01 * loss_percent) + loss_mw
+
+    The first formula directly comes from pandapower. The second one ensures that if the direction of the flow is 
+    inverted, then flows should also be inverted (`xx` becomes `yy` and reciprocally).
+
+    For the sake of simplicity, you can only control the active value at the `or` side of the dc powerline. The
+    active value at the `ex` side 
+)mydelimiter";
+
+const std::string DocIterator::target_p_or_mw = R"mydelimiter(
+    The target active production setpoint at the `or` side of the dc powerline (in MW).
+
+    .. note:: 
+        If it's positive it means that power is actually injected at the `or` side, so the
+        power flows from `ex` to `or`.
+        
 )mydelimiter";
 
+const std::string DocIterator::target_vm_or_pu = R"mydelimiter(
+    The target active voltage setpoint at the `or` side of the powerline (in pu NOT in kV).
+)mydelimiter";
+
+const std::string DocIterator::target_vm_ex_pu = R"mydelimiter(
+    The target active voltage setpoint at the `ex` side of the powerline (in pu NOT in kV).
+)mydelimiter";
+
+const std::string DocIterator::dc_line_formula = R"mydelimiter(
+    .. note::
+        A DC line is modeled by two connected generators and some losses to convert the power from one to the other.
+
+        Two types of losses are considered:
+        
+        - `flat` losses: `loss_mw` (in MW) 
+        - `relative` losses: `loss_percent` (no unit) 
+        
+        The formula for computing the power injected / produced by each generator is:
+
+        - if `or_mw` is positive, then `ex_mw = -1.0 * (or_mw - loss_mw) * (1.0 - 0.01 * loss_percent)`
+        - if `or_mw` is negative, then `ex_mw = -1.0 * or_mw / (1.0 - 0.01 * loss_percent) + loss_mw
+
+        Where `or_mw` denotes the power injected at the origin side and `ex_mw` the power injected at the `extremity`
+        side.
+
+    .. note::
+        By convention, a dc powerline adopts the `load convention`.
+
+        This means that if `or_mw` is positive then power is consumed at the `or` side, so the
+        power flows from `or` to `ex` and vice versa.
+
+)mydelimiter";
+
+const std::string DocIterator::loss_pct = R"mydelimiter(
+    The value of the `loss percent` parameter for the dc line.
+
+)mydelimiter" + DocIterator::dc_line_formula;
+
+const std::string DocIterator::loss_mw = R"mydelimiter(
+    The value of the `loss_mw` parameter for the dc line.
+
+)mydelimiter" + DocIterator::dc_line_formula;
+
+const std::string DocIterator::res_p_or_mw_dcline = R"mydelimiter(
+    The amount of active power injected at the `or` side of the dc powerline (in MW).
+
+)mydelimiter" + DocIterator::only_avail_res + DocIterator::dc_line_formula;
+
+const std::string DocIterator::res_p_ex_mw_dcline = R"mydelimiter(
+    The amount of active power injected at the `ex` side of the dc powerline (in MW).
+
+)mydelimiter" + DocIterator::only_avail_res + DocIterator::dc_line_formula;
+
+const std::string DocIterator::res_q_or_mvar_dcline = R"mydelimiter(
+    The amount of reactive power injected at the `or` side of the dc powerline (in MVAr).
+
+)mydelimiter" + DocIterator::only_avail_res;
+
+const std::string DocIterator::res_q_ex_mvar_dcline = R"mydelimiter(
+    The amount of reactive power injected at the `ex` side of the dc powerline (in MVAr).
+
+)mydelimiter" + DocIterator::only_avail_res;
+
+const std::string DocIterator::res_v_or_kv_dcline = R"mydelimiter(
+    Get the magnitude of the complex voltage (in kV) of the bus at which this "or" side of the dc line is connected.
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_v_kv"
+
+)mydelimiter" + DocIterator::only_avail_res;
+
+const std::string DocIterator::res_v_ex_kv_dcline = R"mydelimiter(
+    Get the magnitude of the complex voltage (in kV) of the bus at which this "ex" side of the dc line is connected.
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_v_kv"
+
+)mydelimiter" + DocIterator::only_avail_res;
+
+const std::string DocIterator::res_theta_or_deg_dcline = R"mydelimiter(
+    Get the angle of the complex voltage (in degree, not in radian) of the bus at which this "or" side of the dc line is connected.
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_theta_deg"
+
+)mydelimiter" + DocIterator::only_avail_res;
+
+const std::string DocIterator::res_theta_ex_deg_dcline = R"mydelimiter(
+    Get the angle of the complex voltage (in degree, not in radian) of the bus at which this "ex" side of the dc line is connected.
+
+    .. note::
+        All elements (load, generators, side of powerline etc.) connected at the same bus have the same "res_theta_deg"
+
+)mydelimiter" + DocIterator::only_avail_res;
+
+const std::string DocIterator::gen_or = R"mydelimiter(
+    Direct access to the "or" generators, directly returns a :class:`lightsim2grid.elements.GenInfo`
+)mydelimiter" + DocIterator::dc_line_formula;
+
+const std::string DocIterator::gen_ex = R"mydelimiter(
+    Direct access to the "ex" generators, directly returns a :class:`lightsim2grid.elements.GenInfo`
+)mydelimiter" + DocIterator::dc_line_formula;
+
 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.
@@ -1388,6 +1710,26 @@ const std::string DocGridModel::get_loads = R"mydelimiter(
         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::get_dclines = R"mydelimiter(
+    This function allows to retrieve the dc powerlines (as a :class:`lightsim2grid.elements.DataDCLine` 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_dclines()]) # to print the "x" for each powerlines
+
+)mydelimiter";
+
 const std::string DocGridModel::_internal_do_not_use = R"mydelimiter(
         INTERNAL
 
@@ -1959,7 +2301,7 @@ const std::string DocComputers::get_voltages = R"mydelimiter(
 )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.
+    Get 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)
@@ -1976,6 +2318,11 @@ const std::string DocComputers::get_sbuses = R"mydelimiter(
 
 )mydelimiter";
 
+const std::string DocComputers::clear = R"mydelimiter(
+    Clear the solver and to as if the class never performed any powerflow.
+
+)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.
diff --git a/src/help_fun_msg.h b/src/help_fun_msg.h
index d5e5068..113f1dd 100644
--- a/src/help_fun_msg.h
+++ b/src/help_fun_msg.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2020, RTE (https://www.rte-france.com)
+// Copyright (c) 2020-2023, 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,
@@ -41,6 +41,10 @@ struct DocSolver
     static const std::string NICSLUSolverSingleSlack;
     static const std::string NICSLUDCSolver;
 
+    static const std::string CKTSOSolver;
+    static const std::string CKTSOSolverSingleSlack;
+    static const std::string CKTSODCSolver;
+
     static const std::string GaussSeidelSolver;
     static const std::string GaussSeidelSynchSolver;
 
@@ -55,6 +59,7 @@ struct DocSolver
 struct DocIterator
 {
     // generic functions
+    static const std::string only_avail_res;
     static const std::string id;
     static const std::string connected;
     static const std::string bus_id;
@@ -128,6 +133,26 @@ struct DocIterator
     static const std::string res_theta_or_deg;
     static const std::string res_theta_ex_deg;
 
+    // specific to dc lines
+    static const std::string dc_line_formula;
+    static const std::string DataDCLine;
+    static const std::string DCLineInfo;
+    static const std::string target_p_or_mw;
+    static const std::string target_vm_or_pu;
+    static const std::string target_vm_ex_pu;
+    static const std::string loss_pct;
+    static const std::string loss_mw;
+    static const std::string res_p_or_mw_dcline;
+    static const std::string res_p_ex_mw_dcline;
+    static const std::string res_q_or_mvar_dcline;
+    static const std::string res_q_ex_mvar_dcline;
+    static const std::string res_v_or_kv_dcline;
+    static const std::string res_v_ex_kv_dcline;
+    static const std::string res_theta_or_deg_dcline;
+    static const std::string res_theta_ex_deg_dcline;
+    static const std::string gen_or;
+    static const std::string gen_ex;
+    
 };
 
 struct DocGridModel
@@ -154,6 +179,7 @@ struct DocGridModel
     static const std::string get_shunts;
     static const std::string get_storages;
     static const std::string get_loads;
+    static const std::string get_dclines;
     
     // retrieve the results
     static const std::string get_J_python;
@@ -200,6 +226,7 @@ struct DocComputers
     static const std::string get_power_flows;
     static const std::string get_voltages;
     static const std::string get_sbuses;
+    static const std::string clear;
 };
 
 struct DocSecurityAnalysis
diff --git a/src/main.cpp b/src/main.cpp
index 928b01a..4b061ba 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -36,6 +36,9 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
         .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`")
+        .value("CKTSO", SolverType::CKTSO, "denotes the :class:`lightsim2grid.solver.CKTSOSolver`")
+        .value("CKTSOSingleSlack", SolverType::CKTSOSingleSlack, "denotes the :class:`lightsim2grid.solver.CKTSOSolverSingleSlack`")
+        .value("CKTSODC", SolverType::CKTSODC, "denotes the :class:`lightsim2grid.solver.CKTSODCSolver`")
         .export_values();
 
     py::enum_<ErrorType>(m, "ErrorType", "This enum controls the error encountered in the solver")
@@ -133,7 +136,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
             .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 )
+    #endif  // KLU_SOLVER_AVAILABLE (or _READ_THE_DOCS)
 
     #if defined(NICSLU_SOLVER_AVAILABLE) || defined(_READ_THE_DOCS)
         py::class_<NICSLUSolver>(m, "NICSLUSolver", DocSolver::NICSLUSolver.c_str())
@@ -178,6 +181,49 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
             .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)
 
+    #if defined(CKTSO_SOLVER_AVAILABLE) || defined(_READ_THE_DOCS)
+        py::class_<CKTSOSolver>(m, "CKTSOSolver", DocSolver::CKTSOSolver.c_str())
+            .def(py::init<>())
+            .def("get_J", &CKTSOSolver::get_J_python, DocSolver::get_J_python.c_str())  // (get the jacobian matrix, sparse csc matrix)
+            .def("get_Va", &CKTSOSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &CKTSOSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &CKTSOSolver::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &CKTSOSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &CKTSOSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &CKTSOSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &CKTSOSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &CKTSOSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &CKTSOSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &CKTSOSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+        
+        py::class_<CKTSOSolverSingleSlack>(m, "CKTSOSolverSingleSlack", DocSolver::CKTSOSolverSingleSlack.c_str())
+            .def(py::init<>())
+            .def("get_J", &CKTSOSolverSingleSlack::get_J_python, DocSolver::get_J_python.c_str())  // (get the jacobian matrix, sparse csc matrix)
+            .def("get_Va", &CKTSOSolverSingleSlack::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &CKTSOSolverSingleSlack::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &CKTSOSolverSingleSlack::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &CKTSOSolverSingleSlack::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &CKTSOSolverSingleSlack::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &CKTSOSolverSingleSlack::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &CKTSOSolverSingleSlack::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &CKTSOSolverSingleSlack::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &CKTSOSolverSingleSlack::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &CKTSOSolverSingleSlack::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+        
+        py::class_<CKTSODCSolver>(m, "CKTSODCSolver", DocSolver::CKTSODCSolver.c_str())
+            .def(py::init<>())
+            .def("get_Va", &CKTSODCSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
+            .def("get_Vm", &CKTSODCSolver::get_Vm, DocSolver::get_Vm.c_str())  // get the voltage magnitude vector (vector of double)
+            .def("get_V", &CKTSODCSolver::get_V, DocSolver::get_V.c_str()) 
+            .def("get_error", &CKTSODCSolver::get_error, DocSolver::get_error.c_str())  // get the error message, see the definition of "err_" for more information
+            .def("get_nb_iter", &CKTSODCSolver::get_nb_iter, DocSolver::get_nb_iter.c_str())  // return the number of iteration performed at the last optimization
+            .def("reset", &CKTSODCSolver::reset, DocSolver::reset.c_str())  // reset the solver to its original state
+            .def("converged", &CKTSODCSolver::converged, DocSolver::converged.c_str())  // whether the solver has converged
+            .def("compute_pf", &CKTSODCSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str())  // perform the newton raphson optimization
+            .def("get_timers", &CKTSODCSolver::get_timers, DocSolver::get_timers.c_str())  // returns the timers corresponding to times the solver spent in different part
+            .def("solve", &CKTSODCSolver::compute_pf, py::call_guard<py::gil_scoped_release>(), DocSolver::compute_pf.c_str());  // perform the newton raphson optimization
+    #endif  // CKTSO_SOLVER_AVAILABLE (or _READ_THE_DOCS)
+
     py::class_<GaussSeidelSolver>(m, "GaussSeidelSolver", DocSolver::GaussSeidelSolver.c_str())
         .def(py::init<>())
         .def("get_Va", &GaussSeidelSolver::get_Va, DocSolver::get_Va.c_str())  // get the voltage angle vector (vector of double)
@@ -369,6 +415,37 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
         .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());
 
+    // iterator for dc lines
+    py::class_<DataDCLine>(m, "DataDCLine", DocIterator::DataDCLine.c_str())
+        .def("__len__", [](const DataDCLine & data) { return data.nb(); })
+        .def("__getitem__", [](const DataDCLine & data, int k){return data[k]; } )
+        .def("__iter__", [](const DataDCLine & data) {
+       return py::make_iterator(data.begin(), data.end());
+    }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */
+
+    py::class_<DataDCLine::DCLineInfo>(m, "DCLineInfo", DocIterator::DCLineInfo.c_str())
+        .def_readonly("id", &DataDCLine::DCLineInfo::id, DocIterator::id.c_str())
+        .def_readonly("connected", &DataDCLine::DCLineInfo::connected, DocIterator::connected.c_str())
+        .def_readonly("bus_or_id", &DataDCLine::DCLineInfo::bus_or_id, DocIterator::bus_or_id.c_str())
+        .def_readonly("bus_ex_id", &DataDCLine::DCLineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str())
+        .def_readonly("target_p_or_mw", &DataDCLine::DCLineInfo::target_p_or_mw, DocIterator::target_p_or_mw.c_str())
+        .def_readonly("target_vm_or_pu", &DataDCLine::DCLineInfo::target_vm_or_pu, DocIterator::target_vm_or_pu.c_str())
+        .def_readonly("target_vm_ex_pu", &DataDCLine::DCLineInfo::target_vm_ex_pu, DocIterator::target_vm_ex_pu.c_str())
+        .def_readonly("loss_pct", &DataDCLine::DCLineInfo::loss_pct, DocIterator::loss_pct.c_str())
+        .def_readonly("loss_mw", &DataDCLine::DCLineInfo::loss_mw, DocIterator::loss_mw.c_str())
+        .def_readonly("gen_or", &DataDCLine::DCLineInfo::gen_or, DocIterator::gen_or.c_str())
+        .def_readonly("gen_ex", &DataDCLine::DCLineInfo::gen_ex, DocIterator::gen_ex.c_str())
+        .def_readonly("has_res", &DataDCLine::DCLineInfo::has_res, DocIterator::has_res.c_str())
+        .def_readonly("res_p_or_mw", &DataDCLine::DCLineInfo::res_p_or_mw, DocIterator::res_p_or_mw_dcline.c_str())
+        .def_readonly("res_p_ex_mw", &DataDCLine::DCLineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw_dcline.c_str())
+        .def_readonly("res_q_or_mvar", &DataDCLine::DCLineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar_dcline.c_str())
+        .def_readonly("res_q_ex_mvar", &DataDCLine::DCLineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar_dcline.c_str())
+        .def_readonly("res_v_or_kv", &DataDCLine::DCLineInfo::res_v_or_kv, DocIterator::res_v_or_kv_dcline.c_str())
+        .def_readonly("res_v_ex_kv", &DataDCLine::DCLineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv_dcline.c_str())
+        .def_readonly("res_theta_or_deg", &DataDCLine::DCLineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg_dcline.c_str())
+        .def_readonly("res_theta_ex_deg", &DataDCLine::DCLineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg_dcline.c_str())
+        ;
+
     // converters
     py::class_<PandaPowerConverter>(m, "PandaPowerConverter")
         .def(py::init<>())
@@ -428,11 +505,13 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
         .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("init_dclines", &GridModel::init_dclines, 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_dclines", &GridModel::get_dclines, DocGridModel::get_dclines.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())
@@ -441,6 +520,12 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
         .def("get_loads", &GridModel::get_loads, DocGridModel::get_loads.c_str())
 
         // modify the grid
+        .def("turnedoff_no_pv", &GridModel::turnedoff_no_pv, "Turned off (or generators with p = 0) generators will not be pv buses, they will not maintain voltage")
+        .def("turnedoff_pv", &GridModel::turnedoff_pv, "Turned off (or generators with p = 0) generators will be pv buses, they will maintain voltage (default)")
+        .def("get_turnedoff_gen_pv", &GridModel::get_turnedoff_gen_pv, "TODO")
+        .def("update_slack_weights", &GridModel::update_slack_weights, "TODO")
+        
+        .def("deactivate_bus", &GridModel::deactivate_bus, DocGridModel::_internal_do_not_use.c_str())
         .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())
 
@@ -493,6 +578,16 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
         .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())
 
+        .def("deactivate_dcline", &GridModel::deactivate_dcline, DocGridModel::_internal_do_not_use.c_str())
+        .def("reactivate_dcline", &GridModel::reactivate_dcline, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_p_dcline", &GridModel::change_p_dcline, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_v_or_dcline", &GridModel::change_v_or_dcline, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_v_ex_dcline", &GridModel::change_v_ex_dcline, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_dcline_or", &GridModel::change_bus_dcline_or, DocGridModel::_internal_do_not_use.c_str())
+        .def("change_bus_dcline_ex", &GridModel::change_bus_dcline_ex, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_dcline_or", &GridModel::get_bus_dcline_or, DocGridModel::_internal_do_not_use.c_str())
+        .def("get_bus_dcline_ex", &GridModel::get_bus_dcline_ex, DocGridModel::_internal_do_not_use.c_str())
+
         // get back the results
         .def("get_V", &GridModel::get_V, DocGridModel::get_V.c_str())
         .def("get_Va", &GridModel::get_Va, DocGridModel::get_Va.c_str())
@@ -555,6 +650,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
          // it is not recommended to use it outside of grid2Op.
         .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_sgens_p", &GridModel::update_sgens_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())
@@ -596,6 +692,8 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
 
         // status
         .def("get_status", &Computers::get_status, DocComputers::get_status.c_str())
+        .def("clear", &Computers::clear, DocComputers::clear.c_str())
+        .def("close", &Computers::clear, DocComputers::clear.c_str())
 
         // perform the computations
         .def("compute_Vs", &Computers::compute_Vs, py::call_guard<py::gil_scoped_release>(), DocComputers::compute_Vs.c_str())
@@ -625,6 +723,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m)
         // remove some defaults (TODO)
         .def("reset", &SecurityAnalysis::clear, DocSecurityAnalysis::clear.c_str())
         .def("clear", &SecurityAnalysis::clear, DocSecurityAnalysis::clear.c_str())
+        .def("close", &SecurityAnalysis::clear, DocComputers::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())


Test patch:
diff --git a/lightsim2grid/tests/test_CKTSOSolver.py b/lightsim2grid/tests/test_CKTSOSolver.py
new file mode 100644
index 0000000..53e92e7
--- /dev/null
+++ b/lightsim2grid/tests/test_CKTSOSolver.py
@@ -0,0 +1,133 @@
+# 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 os
+import unittest
+import numpy as np
+import pdb
+import zipfile
+from scipy import sparse
+import re
+CKTSO_AVAILBLE = False
+try:
+    from lightsim2grid_cpp import CKTSOSolver
+    CKTSO_AVAILBLE = True
+except ImportError:
+    # KLU solver is not available, these tests cannot be carried out
+    pass
+
+
+class MakeTests(unittest.TestCase):
+    def __init__(self, methodName='runTest'):
+        unittest.TestCase.__init__(self, methodName=methodName)
+        self.methodName = methodName
+
+        self.max_it = 10
+        self.tol = 1e-8  # tolerance for the solver
+        self.tol_test = 1e-4  # tolerance for the test (2 matrices are equal if the l_1 of their difference is less than this)
+        if not CKTSO_AVAILBLE:
+            return
+        self.solver = CKTSOSolver()
+
+        self.path = None
+        self.V_init = None
+        self.pq = None
+        self.pv = None
+        self.Sbus = None
+        self.Ybus = None
+
+    def load_array(self, myzip, nm):
+        arr = myzip.extract(nm)
+        res = np.load(arr)
+        os.remove(arr)
+        return res
+
+    def load_path(self, path):
+        try:
+            self.path = path
+            with zipfile.ZipFile(self.path) as myzip:
+                self.V_init = self.load_array(myzip, "V0.npy")
+                self.pq = self.load_array(myzip, "pq.npy")
+                self.pv = self.load_array(myzip, "pv.npy")
+                self.Sbus = self.load_array(myzip, "Sbus.npy")
+                self.Ybus = self.load_array(myzip, "Ybus.npy")
+                self.Ybus = sparse.csc_matrix(self.Ybus)
+            return True
+        except Exception as exc_:
+            return False
+
+    def _load_vect(self, myzip, iter_max, nm):
+        try:
+            res = self.load_array(myzip, "{}_{}.npy".format(nm, iter_max))
+        except Exception as exc_:
+            raise RuntimeError("{} is not defined for iteration {} for case {}".format(nm, iter_max, self.path))
+        return res
+
+    def load_res(self, iter_max):
+        with zipfile.ZipFile(self.path) as myzip:
+            J = self._load_vect(myzip, iter_max, "J")
+            V = self._load_vect(myzip, iter_max, "V")
+        return J, V
+
+    def compare_sparse_mat(self, J, J_pp, pv, pq):
+        """
+        Test that the matrices J and J_pp are almost equal
+        :param J:
+        :param J_pp:
+        :param pv:
+        :param pq:
+        :return:
+        """
+        pvpq = np.r_[pv, pq]
+
+        comp_val = np.abs(J[1:, 1:] - J_pp)
+        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"
+        assert np.sum(np.abs(comp_val[:len(pvpq), len(pvpq):])) <= self.tol_test, "J12 (dS_dVm_r) are not equal"
+        assert np.sum(np.abs(comp_val[len(pvpq):, len(pvpq):])) <= self.tol_test, "J22 (dS_dVm_i) are not equal"
+
+    def solver_aux(self):
+        self.solver.reset()
+
+        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 {} with error {}".format(self.path, self.solver.get_error())
+        J = self.solver.get_J()
+        Va = self.solver.get_Va()
+        Vm = self.solver.get_Vm()
+        J_pp, V_pp = self.load_res(iter_max=self.solver.get_nb_iter())
+        self.compare_sparse_mat(J, J_pp, self.pv, self.pq)
+        Va_pp = np.angle(V_pp)
+        Vm_pp = np.abs(V_pp)
+        assert np.sum(np.abs(Va - Va_pp)) <= self.tol_test, "voltages angles are not the same"
+        assert np.sum(np.abs(Vm - Vm_pp)) <= self.tol_test, "voltages magnitude are not the same"
+
+    def test_dir(self):
+        if not CKTSO_AVAILBLE:
+            self.skipTest("CKTSO is not installed")
+        nb_tested = 0
+        for path in os.listdir("."):
+            _, ext = os.path.splitext(path)
+            if ext == ".zip":
+                path_ok = self.load_path(path)
+                if path_ok:
+                    self.solver_aux()
+                    nb_tested += 1
+        assert nb_tested == 5, "incorrect number of test cases found, found {} while there should be 5".format(nb_tested)
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/lightsim2grid/tests/test_DCPF.py b/lightsim2grid/tests/test_DCPF.py
index 4570815..93604c1 100644
--- a/lightsim2grid/tests/test_DCPF.py
+++ b/lightsim2grid/tests/test_DCPF.py
@@ -154,7 +154,8 @@ def _aux_test(self, pn_net):
         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()
+        # below it does not work due to a bug fixed in dev_1.8.2 (after 1.8.2.dev4)
+        # 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()
diff --git a/lightsim2grid/tests/test_KLUBackendTestAgent.py b/lightsim2grid/tests/test_KLUBackendTestAgent.py
index c61dbc8..59fb41b 100644
--- a/lightsim2grid/tests/test_KLUBackendTestAgent.py
+++ b/lightsim2grid/tests/test_KLUBackendTestAgent.py
@@ -7,6 +7,7 @@
 # This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
 
 from grid2op import make
+from grid2op.dtypes import dt_int
 from grid2op.Agent import AgentWithConverter
 from grid2op.Parameters import Parameters
 from grid2op.Rules import AlwaysLegal
@@ -149,8 +150,8 @@ def __init__(self, action_space, env_name, action_space_converter=IdToAct, **kwa
         self.action_space.all_actions = all_actions
 
         # add the action "reset everything to 1 bus"
-        self.action_space.all_actions.append(action_space({"set_bus": np.ones(action_space.dim_topo, dtype=np.int),
-                                                           "set_line_status": np.ones(action_space.n_line, dtype=np.int)}))
+        self.action_space.all_actions.append(action_space({"set_bus": np.ones(action_space.dim_topo, dtype=dt_int),
+                                                           "set_line_status": np.ones(action_space.n_line, dtype=dt_int)}))
         self.nb_act_done = 0
         self.act_this = True
 
diff --git a/lightsim2grid/tests/test_LightSimBackend.py b/lightsim2grid/tests/test_LightSimBackend.py
index 5d92105..0a1408a 100644
--- a/lightsim2grid/tests/test_LightSimBackend.py
+++ b/lightsim2grid/tests/test_LightSimBackend.py
@@ -205,8 +205,8 @@ def make_backend(self, detailed_infos_for_cascading_failures=False):
 
 
 class TestLoadingBackendLightSim(TestLoadingBackendPandaPower):
-    def get_backend(self):
-        return LightSimBackend()
+    def get_backend(self, detailed_infos_for_cascading_failures=True):
+        return LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures)
 
 
 class TestResetOkLS(TestResetOk):
diff --git a/lightsim2grid/tests/test_NICSLUSolver.py b/lightsim2grid/tests/test_NICSLUSolver.py
index d0f10dd..1ae6a97 100644
--- a/lightsim2grid/tests/test_NICSLUSolver.py
+++ b/lightsim2grid/tests/test_NICSLUSolver.py
@@ -85,7 +85,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)
         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_SameResPP.py b/lightsim2grid/tests/test_SameResPP.py
index 6cb59bf..43d746c 100644
--- a/lightsim2grid/tests/test_SameResPP.py
+++ b/lightsim2grid/tests/test_SameResPP.py
@@ -243,10 +243,19 @@ def _aux_test(self, pn_net):
                                   "gen": np.array([], dtype=int), "branch": np.array([], dtype=int)}
         # convert pandapower net to ppc
         ppc, ppci = _pd2ppc(pp_net)
-        baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, _, refgen = _get_pf_variables_from_ppci(ppci)
+        try:
+            baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, _, refgen = _get_pf_variables_from_ppci(ppci)
+        except ValueError:
+            # change in pandapower 2.12
+            baseMVA, bus, gen, branch, svc, tcsc, ref, pv, pq, on, gbus, V0, ref_gens = _get_pf_variables_from_ppci(ppci)
+            
         Va0 = bus[:, VA] * (np.pi / 180.)
-        B, Bf, Pbusinj, Pfinj = makeBdc(bus, branch)
-
+        try:
+            B, Bf, Pbusinj, Pfinj = makeBdc(bus, branch)
+        except ValueError:
+            # change in pandapower 2.12
+            B, Bf, Pbusinj, Pfinj, Cft = makeBdc(bus, branch)
+            
         Pbus = makeSbus(baseMVA, bus, gen) - Pbusinj - bus[:, GS] / baseMVA
         Pbus_pp_ro = Pbus[pp_vect_converter]
         error_p = np.abs(np.real(Sdc_me) - np.real(Pbus_pp_ro))
diff --git a/lightsim2grid/tests/test_dcline.py b/lightsim2grid/tests/test_dcline.py
new file mode 100644
index 0000000..51ae5b7
--- /dev/null
+++ b/lightsim2grid/tests/test_dcline.py
@@ -0,0 +1,122 @@
+# Copyright (c) 2023, 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 lightsim2grid
+from lightsim2grid.gridmodel import init
+import pandapower as pp
+import pandapower.networks as pn
+import unittest
+import numpy as np
+import warnings
+import pdb
+
+
+class TestDCLine(unittest.TestCase):
+    def setUp(self) -> None:
+        self.net = pn.case14()
+        pp.create_dcline(self.net, from_bus=12, to_bus=13, p_mw=1e2, loss_percent=1.2, loss_mw=25, vm_from_pu=1.01, vm_to_pu=1.02)
+        return super().setUp()
+    
+    def test_init(self):
+        
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            model = init(self.net)
+        # different convention in pandapower and lightsim for now
+        assert model.get_dclines()[0].target_p_or_mw == -100.
+
+    def _aux_test_dc(self):
+        # init ls
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            model = init(self.net)
+        assert model.get_dclines()[0].target_p_or_mw == -self.net.dcline["p_mw"].values
+        
+        # run the dc powerflow for the reference
+        pp.rundcpp(self.net, lightsim2grid=False, init="flat")
+        theta_ref = 1.0 * self.net.res_bus["va_degree"].values
+        p_ref = 1.0 * self.net.res_bus["p_mw"].values
+        
+        # run the dc powerflow for lightsim2grid
+        Vinit = np.ones(self.net.bus.shape[0], dtype=np.complex128) * model.get_init_vm_pu()
+        V_ls = model.dc_pf(Vinit, 1, 1.)
+        theta_ls = np.rad2deg(np.angle(V_ls))
+        p_ls = 1.0 * model.get_Sbus() * model.get_sn_mva()
+        
+        # different convention in pandapower and lightsim for now
+        assert np.allclose(model.get_dclines()[0].res_p_or_mw, -self.net.res_dcline["p_from_mw"].values)
+        assert np.allclose(model.get_dclines()[0].res_p_ex_mw, -self.net.res_dcline["p_to_mw"].values)
+        # check results are the same
+        assert np.allclose(theta_ref, theta_ls)
+        # assert np.allclose(p_ref, -1.0 * np.real(p_ls))  # pp does not use that
+        
+        BDC_ref = self.net._ppc["internal"]["Bbus"].todense()
+        BDC_ls = model.get_dcYbus().todense()
+        assert np.abs(BDC_ls - BDC_ref).max() <= 1e-6
+
+    def _aux_test_ac(self, max_iteration=10, tolerance_mva=1e-08):
+        # init ls
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            model = init(self.net)
+        assert model.get_dclines()[0].target_p_or_mw == -self.net.dcline["p_mw"].values
+        
+        # run the dc powerflow for the reference
+        pp.runpp(self.net, lightsim2grid=False, init="flat", v_debug=True,
+                 max_iteration=max_iteration, tolerance_mva=tolerance_mva)
+        theta_ref = 1.0 * self.net.res_bus["va_degree"].values
+        p_ref = 1.0 * self.net.res_bus["p_mw"].values
+        
+        # run the dc powerflow for lightsim2grid (init from V or pp)
+        Vinit_pp = np.ones(self.net.bus.shape[0], dtype=np.complex128)
+        Vinit_pp *= self.net._ppc["internal"]["Vm_it"][:,0]
+        Vinit_pp *= np.exp(1J * self.net._ppc["internal"]["Va_it"][:,0])
+        V_ls = model.ac_pf(Vinit_pp, max_iteration, tolerance_mva)
+        theta_ls = np.rad2deg(np.angle(V_ls))
+        p_ls = 1.0 * model.get_Sbus() * model.get_sn_mva()
+        
+        # different convention in pandapower and lightsim for now
+        assert np.allclose(model.get_dclines()[0].res_p_or_mw, -self.net.res_dcline["p_from_mw"].values)
+        assert np.allclose(model.get_dclines()[0].res_p_ex_mw, -self.net.res_dcline["p_to_mw"].values)
+        # check results are the same
+        assert np.allclose(theta_ref, theta_ls)
+        # assert np.allclose(p_ref, -1.0 * np.real(p_ls))  # pp does not use that
+        
+        Y_ref = self.net._ppc["internal"]["Ybus"].todense()
+        Y_ls = model.get_Ybus().todense()
+        assert np.abs(Y_ls - Y_ref).max() <= 1e-6
+        
+    def test_dc_powerflow_without_loss(self):
+        self.net.dcline["loss_mw"] = 0.
+        self.net.dcline["loss_percent"] = 0.
+        self._aux_test_dc()
+
+    def test_dc_powerflow_without_loss_mw(self):
+        self.net.dcline["loss_mw"] = 0.
+        self._aux_test_dc()
+
+    def test_dc_powerflow_without_loss_pct(self):
+        self.net.dcline["loss_percent"] = 0.
+        self._aux_test_dc()
+
+    def test_dc_powerflow(self):
+        self._aux_test_dc()
+
+    def test_ac_powerflow(self):
+        self._aux_test_ac()
+        self.net.dcline["p_mw"] = 10.
+        self._aux_test_ac()
+        self.net.dcline["p_mw"] = 30.
+        self._aux_test_ac()
+        self.net.dcline["p_mw"] = 300.
+        self._aux_test_ac()
+        
+        
+if __name__ == "__main__":
+    unittest.main()
diff --git a/lightsim2grid/tests/test_dist_slack_backend.py b/lightsim2grid/tests/test_dist_slack_backend.py
new file mode 100644
index 0000000..5025a5c
--- /dev/null
+++ b/lightsim2grid/tests/test_dist_slack_backend.py
@@ -0,0 +1,121 @@
+# Copyright (c) 2020-2023, 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 unittest
+import warnings
+
+import numpy as np
+import pdb
+
+
+import numpy as np
+import grid2op
+from grid2op.Runner import Runner
+from lightsim2grid import LightSimBackend
+import pdb
+
+
+class TestDistSlackBackend(unittest.TestCase):
+    def setUp(self) -> None:
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            self.env_ss = grid2op.make("l2rpn_wcci_2022",
+                                       backend=LightSimBackend(dist_slack_non_renew=False),
+                                       test=True)
+            self.env_ds = grid2op.make("l2rpn_wcci_2022",
+                                       backend=LightSimBackend(dist_slack_non_renew=True),
+                                       test=True)
+            
+        for env in [self.env_ss, self.env_ds]:
+            self._prepare_env(env)
+        self.max_iter_real = 10
+        return super().setUp()
+    
+    def _prepare_env(self, env):
+        param = env.parameters
+        param.NO_OVERFLOW_DISCONNECTION = True
+        env.change_parameters(param)
+        env.seed(0)
+        env.set_id(0)
+    
+    def _run_env(self, env):
+        obs = env.reset()
+        done = False
+        ts = 0
+        aor = np.zeros((self.max_iter_real, env.n_line))
+        gen_p = np.zeros((self.max_iter_real, env.n_gen))
+        while not done:
+            obs, reward, done, info = env.step(env.action_space())
+            aor[ts,:] = obs.a_or
+            gen_p[ts,:] = obs.gen_p
+            ts += 1
+            if ts >= self.max_iter_real:
+                break
+        return ts, done, aor, gen_p
+        
+    def test_different(self):
+        self._aux_test_different(self.env_ss, self.env_ds)
+        
+    def _aux_test_different(self, env_ss, env_ds):
+        ts_ss, done_ss, aor_ss, gen_p_ss = self._run_env(env_ss)
+        ts_ds, done_ds, aor_ds, gen_p_ds = self._run_env(env_ds)
+        
+        assert ts_ss == ts_ds
+        assert done_ss == done_ds
+        
+        # non redispatchable gen are not affected
+        assert np.all(gen_p_ss[:,~self.env_ds.gen_redispatchable] == gen_p_ds[:,~self.env_ds.gen_redispatchable])
+        
+        # redispatchable gen are affected (unless p=0)
+        for ts in range(self.max_iter_real):
+            different = gen_p_ss[ts,self.env_ds.gen_redispatchable] != gen_p_ds[ts,self.env_ds.gen_redispatchable]
+            are_zero = gen_p_ss[ts,self.env_ds.gen_redispatchable] == 0.
+            assert np.all(different | are_zero), f"error at iteration {ts}"
+            # if p ==0. then it's not modified
+            assert np.all(gen_p_ds[ts,self.env_ds.gen_redispatchable][are_zero] == 0.), f"error at iteration {ts}"
+    
+    def test_copy(self):
+        """test the properties are maintained after a copy"""
+        env_ds = self.env_ds.copy()
+        self._prepare_env(env_ds)
+        self._aux_test_different(self.env_ss, env_ds)
+        
+    def test_after_reset(self):
+        """test the "reset" does not lose the property"""
+        _ = self.env_ds.reset()
+        _ = self.env_ds.reset()
+        self._prepare_env(self.env_ds)
+        self._aux_test_different(self.env_ss, self.env_ds)
+        
+    def test_after_runner(self):
+        """test I can use the runner"""
+        runner_ss = Runner(**self.env_ss.get_params_for_runner())
+        runner_ds = Runner(**self.env_ds.get_params_for_runner())
+        res_ss = runner_ss.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True)
+        res_ds = runner_ds.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True)
+        assert res_ss[0][3] == res_ds[0][3]  # same number of steps survived
+        assert res_ss[0][2] != res_ds[0][2]  # not the same reward
+        ep_ss = res_ss[0][-1]
+        ep_ds = res_ds[0][-1]
+        
+        # redispatchable gen are affected
+        for ts in range(self.max_iter_real):
+            gen_p_ss = ep_ss.observations[ts].gen_p
+            gen_p_ds = ep_ds.observations[ts].gen_p
+            # renewables are not impacted
+            assert np.all(gen_p_ss[~self.env_ds.gen_redispatchable] == gen_p_ds[~self.env_ds.gen_redispatchable]), f"error at iteration {ts}"
+            # non renewable are impacted (unless p=0.)
+            different = gen_p_ss[self.env_ds.gen_redispatchable] != gen_p_ds[self.env_ds.gen_redispatchable]
+            are_zero = gen_p_ss[self.env_ds.gen_redispatchable] == 0.
+            assert np.all(different | are_zero), f"error at iteration {ts}"
+            # if p ==0. then it's not modified
+            assert np.all(gen_p_ds[self.env_ds.gen_redispatchable][are_zero] == 0.), f"error at iteration {ts}"
+    
+        
+if __name__ == "__main__":
+    unittest.main()
diff --git a/lightsim2grid/tests/test_issue_56.py b/lightsim2grid/tests/test_issue_56.py
new file mode 100644
index 0000000..6b0a3a6
--- /dev/null
+++ b/lightsim2grid/tests/test_issue_56.py
@@ -0,0 +1,94 @@
+# Copyright (c) 2020-2023, 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 unittest
+import warnings
+
+import pdb
+
+
+import numpy as np
+import grid2op
+from lightsim2grid import LightSimBackend, SolverType
+from lightsim2grid.securityAnalysis import SecurityAnalysis
+import pdb
+
+
+class TestSADC_14(unittest.TestCase):
+    def setUp(self) -> None:
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            self.env = grid2op.make("l2rpn_case14_sandbox", test=True, backend=LightSimBackend())
+            self.sa = SecurityAnalysis(self.env)
+            
+    def test_dc(self):
+        self.sa.add_all_n1_contingencies()
+        res_p, res_a, res_v  = self.sa.get_flows()
+        
+        self.sa.clear()
+        self.sa.computer.change_solver(SolverType.DC)
+        self.sa.add_all_n1_contingencies()
+        res_p_dc, res_a_dc, res_v_dc  = self.sa.get_flows()
+        
+        assert np.any(res_p != res_p_dc)
+        assert np.any(res_a != res_a_dc)
+        assert np.any(res_v != res_v_dc)
+        assert self.sa.computer.get_solver_type() == SolverType.DC
+        
+        nb_bus = self.env.n_sub
+        nb_powerline = len(self.env.backend._grid.get_lines())
+        # now check with the DC computation
+        for l_id in range(self.env.n_line):
+            grid_model = self.env.backend._grid.copy()
+            if l_id < nb_powerline:
+                grid_model.deactivate_powerline(l_id)
+            else:
+                grid_model.deactivate_trafo(l_id - nb_powerline)
+            grid_model.tell_topo_changed()
+            V = 1.0 * self.env.backend.V  # np.ones(2 * self.env.n_sub, dtype=np.complex_)
+            res = grid_model.dc_pf(V, 10, 1e-8)
+            if len(res):
+                # model has converged, I check the results are the same
+                # check voltages
+                assert np.allclose(res_v_dc[l_id, :nb_bus], res[:nb_bus]), f"error for contingency {l_id}"
+                # now check the flows
+                pl_dc, ql_dc, vl_dc, al_dc = grid_model.get_lineor_res()
+                pt_dc, qt_dc, vt_dc, at_dc = grid_model.get_trafohv_res()
+                # check active power
+                p_dc_ref = np.concatenate((pl_dc, pt_dc))
+                assert np.allclose(res_p_dc[l_id], p_dc_ref), f"error for contingency {l_id}"
+                # check amps
+                a_dc_ref = np.concatenate((al_dc, at_dc)) * 1000.
+                assert np.allclose(res_a_dc[l_id], a_dc_ref), f"error for contingency {l_id}"
+            else:
+                # model has diverged, I check that it has diverge the same way in the security_analysis
+                assert np.all(np.abs(res_v_dc[l_id]) == 0.), f"error for contingency {l_id}"
+                assert np.all(np.abs(res_p_dc[l_id]) == 0.), f"error for contingency {l_id}"
+                assert np.all(np.isnan(res_a_dc[l_id])), f"error for contingency {l_id}"
+
+
+class TestSADC_36(TestSADC_14):
+    def setUp(self) -> None:
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            self.env = grid2op.make("l2rpn_neurips_2020_track1", test=True, backend=LightSimBackend())
+            self.sa = SecurityAnalysis(self.env)
+
+
+class TestSADC_118(TestSADC_14):
+    def setUp(self) -> None:
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            self.env = grid2op.make("l2rpn_wcci_2022", test=True, backend=LightSimBackend())
+            self.sa = SecurityAnalysis(self.env)
+           
+          
+if __name__ == "__main__":
+    unittest.main()
+
+            
\ No newline at end of file
diff --git a/lightsim2grid/tests/test_time_series_dc.py b/lightsim2grid/tests/test_time_series_dc.py
new file mode 100644
index 0000000..96f9bb8
--- /dev/null
+++ b/lightsim2grid/tests/test_time_series_dc.py
@@ -0,0 +1,103 @@
+# Copyright (c) 2020-2023, 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 unittest
+import warnings
+
+import pdb
+
+
+import numpy as np
+import grid2op
+from lightsim2grid import LightSimBackend, SolverType
+from lightsim2grid.timeSerie import TimeSerie
+import pdb
+
+
+class TestTSDC_14(unittest.TestCase):
+    def make_env(self):
+        return grid2op.make("l2rpn_case14_sandbox", test=True, backend=LightSimBackend())
+    
+    def setUp(self) -> None:
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            self.env = self.make_env()
+            self.ts = TimeSerie(self.env)
+            
+        self.scenario_id = 1
+        self.seed = 0
+        
+    def tearDown(self) -> None:
+        self.env.close()
+        self.ts.close()
+        return super().tearDown()
+    
+    def test_dc(self):
+        V_init = 1.0 * np.abs(self.env.backend.V) + 0j
+        
+        self.ts.computer.change_solver(SolverType.SparseLUSingleSlack)
+        self.ts.clear()
+        res_p, res_a, res_v = self.ts.get_flows(scenario_id=self.scenario_id,
+                                                seed=self.seed,
+                                                v_init=V_init)
+        assert self.ts.computer.get_solver_type() == SolverType.SparseLUSingleSlack
+        self.ts.clear()
+        self.ts.computer.change_solver(SolverType.DC)
+        res_p_dc, res_a_dc, res_v_dc  = self.ts.get_flows(scenario_id=self.scenario_id,
+                                                          seed=self.seed,
+                                                          v_init=V_init)
+        assert self.ts.computer.get_solver_type() == SolverType.DC
+        assert np.any(res_p != res_p_dc)
+        assert np.any(res_a != res_a_dc)
+        assert np.any(res_v != res_v_dc)
+            
+        nb_bus = self.env.n_sub
+        
+        # now check with the DC computation
+        all_load = np.ones(self.env.n_load).astype(bool)
+        all_gen = np.ones(self.env.n_gen).astype(bool)
+        V = 1.0 * V_init
+        for ts in range(self.ts.load_p.shape[0]):
+            grid_model = self.env.backend._grid.copy()
+            grid_model.update_loads_p(all_load, self.ts.load_p[ts, :].astype(np.float32))
+            grid_model.update_loads_q(all_load, self.ts.load_q[ts, :].astype(np.float32))
+            grid_model.update_gens_p(all_gen, self.ts.prod_p[ts, :].astype(np.float32))
+            res = grid_model.dc_pf(V, 10, 1e-8)
+            if len(res):
+                # model has converged, I check the results are the same
+                # check voltages
+                assert np.allclose(res_v_dc[ts, :nb_bus], res[:nb_bus]), f"error for step {ts}"
+                # now check the flows
+                pl_dc, ql_dc, vl_dc, al_dc = grid_model.get_lineor_res()
+                pt_dc, qt_dc, vt_dc, at_dc = grid_model.get_trafohv_res()
+                # check active power
+                p_dc_ref = np.concatenate((pl_dc, pt_dc))
+                assert np.allclose(res_p_dc[ts], p_dc_ref), f"error for step {ts}"
+                # check amps
+                a_dc_ref = np.concatenate((al_dc, at_dc)) * 1000.
+                assert np.allclose(res_a_dc[ts], a_dc_ref), f"error for step {ts}"
+            else:
+                # model has diverged, I check that it has diverge the same way in the security_analysis
+                raise AssertionError("The DC computation should have reached the end.")
+            V[:] = res
+
+
+class TestSADC_36(TestTSDC_14):
+    def make_env(self):
+        return grid2op.make("l2rpn_neurips_2020_track1", test=True, backend=LightSimBackend())
+
+
+class TestSADC_118(TestTSDC_14):
+    def make_env(self):
+        return grid2op.make("l2rpn_wcci_2022", test=True, backend=LightSimBackend())
+           
+          
+if __name__ == "__main__":
+    unittest.main()
+
+            
\ No newline at end of file
diff --git a/lightsim2grid/tests/test_turnedoff_nopv.py b/lightsim2grid/tests/test_turnedoff_nopv.py
new file mode 100644
index 0000000..49fb8d3
--- /dev/null
+++ b/lightsim2grid/tests/test_turnedoff_nopv.py
@@ -0,0 +1,129 @@
+# Copyright (c) 2020-2023, 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 unittest
+import warnings
+
+import numpy as np
+import pdb
+
+
+import numpy as np
+import grid2op
+from grid2op.Runner import Runner
+from lightsim2grid import LightSimBackend
+import pdb
+
+
+class TestTurnedOffNoPv(unittest.TestCase):
+    def setUp(self) -> None:
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore")
+            self.env_pv = grid2op.make("l2rpn_wcci_2022",
+                                       backend=LightSimBackend(turned_off_pv=True),
+                                       test=True)
+            self.env_npv = grid2op.make("l2rpn_wcci_2022",
+                                       backend=LightSimBackend(turned_off_pv=False),
+                                       test=True)
+            
+        for env in [self.env_pv, self.env_npv]:
+            self._prepare_env(env)
+        self.max_iter_real = 10
+        return super().setUp()
+    
+    def _prepare_env(self, env):
+        param = env.parameters
+        param.NO_OVERFLOW_DISCONNECTION = True
+        env.change_parameters(param)
+        env.seed(0)
+        env.set_id(0)
+    
+    def _run_env(self, env):
+        obs = env.reset()
+        done = False
+        ts = 0
+        aor = np.zeros((self.max_iter_real, env.n_line))
+        gen_v = np.zeros((self.max_iter_real, env.n_gen))
+        gen_p = np.zeros((self.max_iter_real, env.n_gen))
+        while not done:
+            obs, reward, done, info = env.step(env.action_space())
+            aor[ts,:] = obs.a_or
+            gen_v[ts,:] = obs.gen_v
+            gen_p[ts,:] = obs.gen_p
+            ts += 1
+            if ts >= self.max_iter_real:
+                break
+        return ts, done, aor, gen_v, gen_p
+        
+    def test_different(self):
+        self._aux_test_different(self.env_pv, self.env_npv)
+        
+    def _aux_test_different(self, env_pv, env_npv):
+        ts_pv, done_pv, aor_pv, gen_v_pv, gen_p_pv = self._run_env(env_pv)
+        ts_npv, done_npv, aor_npv, gen_v_npv, gen_p_npv = self._run_env(env_npv)
+        
+        assert ts_pv == ts_npv
+        assert done_pv == done_npv
+        
+        # redispatchable gen are affected
+        for ts in range(self.max_iter_real):
+            are_zero = gen_p_pv[ts,:] == 0.
+            # non zero should not be modified
+            assert np.all(gen_v_pv[ts, ~are_zero] == gen_v_npv[ts, ~are_zero]), f"error at iteration {ts}"
+            # at least one p=0 should be modified...
+            # all gen with p=0 without other gen such that p=0 at the same bus should be modified
+            are_all_zero = ~np.isin(self.env_pv.gen_to_subid[are_zero], self.env_pv.gen_to_subid[~are_zero])
+            assert np.all(gen_v_pv[ts, are_zero][are_all_zero] != gen_v_npv[ts, are_zero][are_all_zero]), f"error at iteration {ts}"
+            # all gens with p=0 with another gen with p > 0. at the same bus should be the same
+            are_some_zero = np.isin(self.env_pv.gen_to_subid[are_zero], self.env_pv.gen_to_subid[~are_zero])
+            assert np.all(gen_v_pv[ts, are_zero][are_some_zero] == gen_v_npv[ts, are_zero][are_some_zero]), f"error at iteration {ts}"
+    
+    def test_copy(self):
+        """test the properties are maintained after a copy"""
+        env_npv = self.env_npv.copy()
+        self._prepare_env(env_npv)
+        self._aux_test_different(self.env_pv, env_npv)
+        
+    def test_after_reset(self):
+        """test the "reset" does not lose the property"""
+        _ = self.env_npv.reset()
+        _ = self.env_npv.reset()
+        self._prepare_env(self.env_npv)
+        self._aux_test_different(self.env_pv, self.env_npv)
+        
+    def test_after_runner(self):
+        """test I can use the runner"""
+        runner_pv = Runner(**self.env_pv.get_params_for_runner())
+        runner_npv = Runner(**self.env_npv.get_params_for_runner())
+        res_pv = runner_pv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True)
+        res_npv = runner_npv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True)
+        assert res_pv[0][3] == res_npv[0][3]  # same number of steps survived
+        assert res_pv[0][2] != res_npv[0][2]  # not the same reward
+        ep_pv = res_pv[0][-1]
+        ep_npv = res_npv[0][-1]
+        
+        # redispatchable gen are affected
+        for ts in range(self.max_iter_real):
+            gen_v_pv = ep_pv.observations[ts].gen_v
+            gen_v_npv = ep_npv.observations[ts].gen_v
+            gen_p_pv = ep_pv.observations[ts].gen_p
+            
+            are_zero = gen_p_pv == 0.
+            # non zero should not be modified
+            assert np.all(gen_v_pv[~are_zero] == gen_v_npv[~are_zero]), f"error at iteration {ts}"
+            # at least one p=0 should be modified...
+            # all gen with p=0 without other gen such that p=0 at the same bus should be modified
+            are_all_zero = ~np.isin(ep_pv.observations[ts].gen_to_subid[are_zero], ep_pv.observations[ts].gen_to_subid[~are_zero])
+            assert np.all(gen_v_pv[are_zero][are_all_zero] != gen_v_npv[are_zero][are_all_zero]), f"error at iteration {ts}"
+            # all gens with p=0 with another gen with p > 0. at the same bus should be the same
+            are_some_zero = np.isin(ep_pv.observations[ts].gen_to_subid[are_zero], ep_pv.observations[ts].gen_to_subid[~are_zero])
+            assert np.all(gen_v_pv[are_zero][are_some_zero] == gen_v_npv[are_zero][are_some_zero]), f"error at iteration {ts}"
+    
+        
+if __name__ == "__main__":
+    unittest.main()