diff --git a/.gitignore b/.gitignore
index 7922bbcd34c9c406283a3483bd5b50c3784ada2e..8705d7e24365a0f5840b2a62ff2bf9f6f48285d0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,8 @@
 *.class
 
 tmp
+mage8
+adists
 
 *.tar
 *.tar.*
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..26d33521af10bcc7fd8cea344038eaaeb78d0ef5
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..105ce2da2d6447d11dfe32bfb846c3d5b199fc99
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="USE_PROJECT_PROFILE" value="false" />
+    <version value="1.0" />
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000000000000000000000000000000000000..94c069ce408206d6c87cf00b7f89a3b4d8512b68
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Black">
+    <option name="sdkName" value="Python 3.10 (pamhyr)" />
+  </component>
+  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (pamhyr)" project-jdk-type="Python SDK" />
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8ad567a589205c549c5c5fd2c87d7b60fb8e2c7b
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/pamhyr.iml" filepath="$PROJECT_DIR$/.idea/pamhyr.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/pamhyr.iml b/.idea/pamhyr.iml
new file mode 100644
index 0000000000000000000000000000000000000000..c422cdfc5a6269c23ac82259efc8958bc893c067
--- /dev/null
+++ b/.idea/pamhyr.iml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="PYTHON_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/venv" />
+    </content>
+    <orderEntry type="jdk" jdkName="Python 3.10 (pamhyr)" jdkType="Python SDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+  <component name="PyDocumentationSettings">
+    <option name="format" value="PLAIN" />
+    <option name="myDocStringFormat" value="Plain" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/adists/adists b/adists/adists
new file mode 100755
index 0000000000000000000000000000000000000000..114beafa7a4f5688063d95d3f87ee7ef1c2cfd2a
Binary files /dev/null and b/adists/adists differ
diff --git a/doc/users/Tuto1/data/Bief_1.ST b/doc/users/Tuto1/data/Bief_1.ST
index 021c35eaea772b603ad2619620b74527d391686c..291d40f2337ac5f19f55faca846b303051fc9523 100644
--- a/doc/users/Tuto1/data/Bief_1.ST
+++ b/doc/users/Tuto1/data/Bief_1.ST
@@ -431,3 +431,12 @@
    36270.0000      21.3000      17.0100 rd
    36270.0000      23.0000      19.5000 
      999.9990     999.9990     999.9990 
+    39     0     0     7   36271.0000   S1bis
+   36271.0000       0.0000      20.0000 
+   36271.0000       1.8000      17.0100 rg
+   36271.0000       3.0000      13.0800 
+   36271.0000      11.5000      13.0500 
+   36271.0000      20.0000      13.0800 
+   36271.0000      21.3000      17.0100 rd
+   36271.0000      23.0000      19.5000 
+     999.9990     999.9990     999.9990 
diff --git a/doc/users/Tuto1/img/Geo.png b/doc/users/Tuto1/img/Geo.png
index 2cb4e3f3ad0b15eb9d5b9f93be8931d1bc272705..f6a03ab92c2d1265ca17cc1c5cf53f54d955eeec 100644
Binary files a/doc/users/Tuto1/img/Geo.png and b/doc/users/Tuto1/img/Geo.png differ
diff --git a/doc/users/Tuto1/img/Geo2.png b/doc/users/Tuto1/img/Geo2.png
index 77098a74be0f415c42cdf5e21f493ad635374579..62540aba20e536b1d52168f8ae3d7536418b6ac1 100644
Binary files a/doc/users/Tuto1/img/Geo2.png and b/doc/users/Tuto1/img/Geo2.png differ
diff --git a/doc/users/Tuto1/img/Seuil.png b/doc/users/Tuto1/img/Seuil.png
new file mode 100644
index 0000000000000000000000000000000000000000..a5238829b8bf59210355032590e55271231fdf7b
Binary files /dev/null and b/doc/users/Tuto1/img/Seuil.png differ
diff --git a/doc/users/Tuto1/img/dl.png b/doc/users/Tuto1/img/dl.png
index 40f6158cd25d74fea439867d1c762048d280cbc8..a0eb62798b92e53c0ec4eea85a1e77275118f8ce 100644
Binary files a/doc/users/Tuto1/img/dl.png and b/doc/users/Tuto1/img/dl.png differ
diff --git a/doc/users/Tuto1/img/fev2002.png b/doc/users/Tuto1/img/fev2002.png
index 15edc30cd2f5353c8fb4a22bd5618ef1e426c592..a10b001caac2d9980cebb31ae83d04dd4f9c63ae 100644
Binary files a/doc/users/Tuto1/img/fev2002.png and b/doc/users/Tuto1/img/fev2002.png differ
diff --git a/doc/users/Tuto1/img/frictions.png b/doc/users/Tuto1/img/frictions.png
index 2f66cc7339c0c25de291db9d13f7f06cea1260c5..e5fd52999cede4e9001d893865c1c3519b1a99a5 100644
Binary files a/doc/users/Tuto1/img/frictions.png and b/doc/users/Tuto1/img/frictions.png differ
diff --git a/doc/users/Tuto1/img/frictions2.png b/doc/users/Tuto1/img/frictions2.png
deleted file mode 100644
index d28d2eb50a7c910e25a4d82fdc19e4f02d4ebc1f..0000000000000000000000000000000000000000
Binary files a/doc/users/Tuto1/img/frictions2.png and /dev/null differ
diff --git a/doc/users/Tuto1/img/hs.png b/doc/users/Tuto1/img/hs.png
index 96326e7c6a644089571e0da5a2a033c792f0873c..f095ee4bcbfb6a5241ffddf39f3beca3d52f0cfa 100644
Binary files a/doc/users/Tuto1/img/hs.png and b/doc/users/Tuto1/img/hs.png differ
diff --git a/doc/users/Tuto1/img/ic.png b/doc/users/Tuto1/img/ic.png
index 489187afdb792f971a7e191a702af66b89e81fdf..dc0bffce8184713b75098524fed81e6f379add79 100644
Binary files a/doc/users/Tuto1/img/ic.png and b/doc/users/Tuto1/img/ic.png differ
diff --git a/doc/users/Tuto1/img/maillage.png b/doc/users/Tuto1/img/maillage.png
index f76f62336f2943f49b4d3c6e8a336ef68e9d85fd..c36138b6d697a03c4902cd67e6d46ab43ba7f04a 100644
Binary files a/doc/users/Tuto1/img/maillage.png and b/doc/users/Tuto1/img/maillage.png differ
diff --git a/doc/users/Tuto1/img/mjpk.png b/doc/users/Tuto1/img/mjpk.png
deleted file mode 100644
index 48204ef892dcb43191d9d443db928280b2bd6a09..0000000000000000000000000000000000000000
Binary files a/doc/users/Tuto1/img/mjpk.png and /dev/null differ
diff --git a/doc/users/Tuto1/img/param.png b/doc/users/Tuto1/img/param.png
deleted file mode 100644
index 468264d4efea32e626c9eb6fabc320a17bcf7f9f..0000000000000000000000000000000000000000
Binary files a/doc/users/Tuto1/img/param.png and /dev/null differ
diff --git a/doc/users/Tuto1/pas-a-pas.tex b/doc/users/Tuto1/pas-a-pas.tex
index cefa116969c229fa4925f51193eefe1b21221304..313a139d30eb604d1b7c7b584e3d13c9e274be35 100644
--- a/doc/users/Tuto1/pas-a-pas.tex
+++ b/doc/users/Tuto1/pas-a-pas.tex
@@ -80,7 +80,7 @@ RiverLy, Hydraulique des rivi
 
 \begin{center}
 \begin{tabular}{lll}
-Autors : & Pierre-Antoine Rouby & pierre-antoine.rouby@inrae.fr\tabularnewline
+Auteurs : & Pierre-Antoine Rouby & pierre-antoine.rouby@inrae.fr\tabularnewline
 & Théophile TERRAZ & theophile.terraz@inrae.fr\tabularnewline
 & Lionel Pénard & lionel.penard@inrae.fr\tabularnewline
 
@@ -110,13 +110,14 @@ Autors : & Pierre-Antoine Rouby & pierre-antoine.rouby@inrae.fr\tabularnewline
 
 Pamhyr2 peut être téléchargé ici : \url{https://gitlab.irstea.fr/theophile.terraz/pamhyr}.
 
+Si vous arrivez sur une fenêtre de connexion, cliquez sur "parcourir la liste des dépôts publics" et cherchez l'URL donnée plus haut.
+Utilisez le bouton de téléchargement GNU Linux ou Windows en fonction de votre système.
+
 \begin{center}
 \includegraphics[width=15cm]{img/dl.png}
 \par\end{center}
 
-
-Utilisez le bouton de téléchargement GNU Linux ou Windows en fonction de votre système. Sous Windows, lancez le programme d'installation. Sous Linux, décompressez l'archive et lancez Pamhyr2.
-
+Décompressez l'archive et lancez Pamhyr2.
 
 \section{Créer une première étude}
 
@@ -127,16 +128,16 @@ Choisissez un nom, par exemple \textit{Hogneau}, et validez.
 \includegraphics[width=15cm]{img/NEWSTUDY.png}
 \par\end{center}
 
-Pendant que vous travaillez sur votre étude, noubliez pas de sauvegarder régulièrement, à l'aide du bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/save.png} de la fenêtre principale.
+Pendant que vous travaillez sur votre étude, n'oubliez pas de sauvegarder régulièrement, à l'aide du bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/save.png} de la fenêtre principale.
 
 \section{Créer la structure de la rivière}
 
-Cliquez sur \texttt{[Réseau] => [Modifier le réseau]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/network.png} pour créer la structure de votre rivière.
+Cliquez sur \texttt{[Réseau] => [Éditer le réseau]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/network.png} pour créer la structure de votre rivière.
 Nous voici dans la fenêtre \textit{Réseau}.
 Dans cette fenêtre, nous allons définir un graphe orienté qui représente les biefs de notre réseau fluvial : les arêtes sont les biefs, les n½uds sont soit des conditions limites amont, soit des conditions limites aval, soit des jonctions entre biefs.
 Un bief par défaut existe dans la nouvelle étude.
-Pour les besoins de ce tutoriel, nous allons le suprimer :
-cliquez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/del.png} pour entrer dans le mode \textit{Supression} puis cliquez sur les n½uds.
+Pour les besoins de ce tutoriel, nous allons le supprimer :
+cliquez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/del.png} pour entrer dans le mode \textit{Suppression} puis cliquez sur les n½uds.
 Nous voilà repartis sur une fenêtre vierge.
 Appuyez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour entrer dans le mode \textit{Ajout}. Créez deux n½uds en cliquant dans la zone grise de la fenêtre, et créez un lien en cliquant à nouveau sur chaque n½ud.
 Appuyez à nouveau sur \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour quitter le mode \textit{Ajout}.
@@ -151,9 +152,9 @@ La fen
 
 Fermez la fenêtre \textit{Réseau}.
 
-\section{Éditer la géometrie de la rivière}
+\section{Éditer la géométrie de la rivière}
 
-Cliquez sur \texttt{[Géometrie] => [Modifier la géometrie]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/geometry.png} pour définir la géométrie du bief sélectionné.
+Cliquez sur \texttt{[Géométrie] => [Éditer la géométrie]} ou sur l'icône \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/geometry.png} pour définir la géométrie du bief sélectionné.
 Pour Importer une géométrie depuis un fichier, cliquez sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/import.png}.
 Sélectionnez le fichier \texttt{Data/Bief\_1.st}.
 Vous devriez voir :
@@ -185,7 +186,7 @@ Vous pouvez ensuite enlever cette ligne avec \texttt{[clic molette]}.
 Vous pouvez maintenant fermer la fenêtre d'édition de la section en travers.
 
 Revenons à la fenêtre \textit{Géométrie}.
-Le jeu de sections importé est inégalement espacé, ce qui peut entrainer des difficultés de calcul.
+Le jeu de sections importé est inégalement espacé, ce qui peut entraîner des difficultés de calcul.
 Nous allons utiliser l'outil de maillage pour interpoler des nouvelles sections entre les sections trop espacées.
 Cliquez sur \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/meshing.png"}.
 Dans la fenêtre \textit{Maillage}, vous avez accès à quelques options basiques.
@@ -194,26 +195,26 @@ L'option \textit{Lignes directrices pour le calcul des distances} permet de sp
 La distance sera la moyenne des deux distances entre les points de chaque sections qui portent ces lignes directrices.
 Pour ne choisir qu'une seule ligne, rentrez simplement deux fois la même valeur.
 Il existe toujours au moins deux lignes directrices : les deux extrémités des sections, nommés \textit{un} et \textit{np}, même si les points correspondants ne portent pas de nom.
-Les options \textit{Paramètres} permettent de définit l'interval souhaité entre les nouvelles sections, et la méthode d'interpolation.
+Les options \textit{Paramètres} permettent de définir l'interval souhaité entre les nouvelles sections, et la méthode d'interpolation.
 Choisissez les options suivantes, et cliquez sur \texttt{[OK]} :
 
 \begin{center}
 \includegraphics[width=15cm]{img/maillage.png}
 \par\end{center}
 
-L'option d'interpolation \textit{Spline} modifie les longueurs longitudinales.
-Les absices en long (Pk) des sections sont donc obsolètes.
-Pour les recalculer, cliquez sur \texttt{[Mise à jour des PK]} dans la fenêtre \textit{Géométrie}.
-Vous êtes maintenant dans la fenêtre \textit{Mise à jour des PK}.
-Vous pouvez choisir la section d'origine pour le calcul des PK, ainsi que la valeur du PK à cette section.
-Le choix des deux lignes directrices sert à calculer les distances entre sections, comme dans la fenêtre \textit{Maillage}.
-L'orientation vous permet de choisir dans quelle direction les PK seront croissants.
-Cette option n'a pas d'impact sur les résultats du calcul hydraulique.
-Choisissez les options suivantes, et cliquez sur \texttt{[OK]} :
-
-\begin{center}
-\includegraphics[width=15cm]{img/mjpk.png}
-\par\end{center}
+% L'option d'interpolation \textit{Spline} modifie les longueurs longitudinales.
+% Les abscices en long (Pk) des sections sont donc obsolètes.
+% Pour les recalculer, cliquez sur \texttt{[Mise à jour des PK]} dans la fenêtre \textit{Géométrie}.
+% Vous êtes maintenant dans la fenêtre \textit{Mise à jour des PK}.
+% Vous pouvez choisir la section d'origine pour le calcul des PK, ainsi que la valeur du PK à cette section.
+% Le choix des deux lignes directrices sert à calculer les distances entre sections, comme dans la fenêtre \textit{Maillage}.
+% L'orientation vous permet de choisir dans quelle direction les PK seront croissants.
+% Cette option n'a pas d'impact sur les résultats du calcul hydraulique.
+% Choisissez les options suivantes, et cliquez sur \texttt{[OK]} :
+%
+% \begin{center}
+% \includegraphics[width=15cm]{img/mjpk.png}
+% \par\end{center}
 
 La fenêtre \textit{Géométrie} doit maintenant ressembler à ça :
 
@@ -233,39 +234,54 @@ Vous arrivez sur la fen
 
 Utilisez le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} en haut à gauche de la fenêtre pour ajouter une condition limite pour le liquide.
 Nous pouvons cliquer sur la nouvelle ligne pour sélectionner la ligne en entier, et double-cliquer pour sélectionner une seule cellule.
-Sélectionnez la cellule \textit{Nom} pour donner un nom à la condition limite.
 Ici, nous définirons le débit mesuré lors de la crue de février 2002.
-Vous pouvez nommer cette condition limite "crue2002".
-Sélectionner la cellule \textit{Type} et utiliser la combo box pour mettre une loi \textit{Q(t)} : débit en fonction du temps.
-Sélectionnez la cellule \textit{Noeud} et attribuez cette condition au noeud amont.
-Les noms des noeuds sont rappelés dans le panneau de droite, avec une vue du réseau.
+Sélectionnez la cellule \textit{Nom} pour donner un nom à la condition limite par exemple "crue2002".
+Sélectionner la cellule \textit{Type} et utiliser la combo box pour mettre une loi \textit{Q(t)} : débit en fonction du temps (hydrogramme).
+Sélectionnez la cellule \textit{N½ud} et attribuez cette condition au n½ud amont.
+Les noms des n½uds sont rappelés dans le panneau de droite, avec une vue du réseau.
 Sélectionnez maintenant la ligne entière et cliquez sur le bouton d'édition \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}.
-Vous avez ouvert la fenêtre \textit{Modifier les conditions aux limites}.
+Vous avez ouvert la fenêtre \textit{Éditer les conditions aux limites}.
 Dans un éditeur de texte, ouvrez le fichier \texttt{data/Fevrier\_2002.txt}.
-Copiez le contenu du fichier (par exemple avec \textit{ctrl+a} puis \textit{ctrl+c}) et collez-le dans le panneau de gauche de la fenêtre \textit{Modifier les conditions aux limites} avec \textit{ctrl+v}.
+Copiez le contenu du fichier (par exemple avec \textit{ctrl+a} puis \textit{ctrl+c}) et collez-le dans le panneau de gauche de la fenêtre \textit{Éditer les conditions aux limites} avec \textit{ctrl+v}.
 Vous pouvez maintenant voir la courbe de débit :
 
 \begin{center}
 \includegraphics[width=15cm]{img/fev2002.png}
 \par\end{center}
 
-Fermer cette fenêtre.
-Revenez sur la fenêtre \textit{Conditions aux limites}.
-Ajoutez une nouvelle ligne, donnez lui un nom, donnez lui le type \textit{Q(Z)} (courbe de tarage) et associez-la au noeud aval du réseau.
-Cette condition limite se trouve au niveau d'un seuil.
-A cet endroit, l'écoulement passe d'un régime fluvial à un régime torentiel.
-Nous allons donc calculer une courbe de tarage qui correspond au régime critique de l'écoulement au niveau du seuil.
-Sélectionnez la condition limite et ouvrez la fenêtre \textit{Modifier les conditions aux limites} :  (\includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}).
-Dans la fenêtre \textit{Modifier les conditions aux limites} cliquez sur \texttt{[Générer régime critique]} pour générer cette courbe.
-Cliquez enduite sur \texttt{[Rendre croissant]} pour suprimer les points de la courbe qui ne sont pas strictement croissants.
-Vous pouvez fermer les fenêtres \textit{Modifier les conditions aux limites} et \textit{Conditions aux limites}.
+Fermez cette fenêtre. Revenez sur la fenêtre \textit{Conditions aux limites}.
+Ajoutez une nouvelle ligne, donnez lui un nom, donnez lui le type \textit{Z(T)} (limnigramme) et associez là au n½ud aval du réseau.
+Ouvrez la fenêtre d'édition des conditions aux limites (\includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}).
+Ajoutez deux lignes et rentrez les valeurs suivantes :
+\begin{center}
+\begin{tabular}{|c|c|}
+\hline
+temps & cote \\ \hline
+0.00.00 & 15.000\\ \hline
+1.00.00 & 15.000\\ \hline
+\end{tabular}\\
+\par\end{center}
+Cela crée une cote constante de l'eau en aval.
+Pour le calcul, le solveur extrapolera continûment l'élévation de l'eau si le temps de la simulation dépasse le dernier point de la courbe.
+Vous pouvez fermer les fenêtres \textit{Éditer les conditions aux limites} et \textit{Conditions aux limites}.
+
+% Fermer cette fenêtre.
+% Revenez sur la fenêtre \textit{Conditions aux limites}.
+% Ajoutez une nouvelle ligne, donnez lui un nom, donnez lui le type \textit{Q(Z)} (courbe de tarage) et associez-la au noeud aval du réseau.
+% Cette condition limite se trouve au niveau d'un seuil.
+% A cet endroit, l'écoulement passe d'un régime fluvial à un régime torentiel.
+% Nous allons donc calculer une courbe de tarage qui correspond au régime critique de l'écoulement au niveau du seuil.
+% Sélectionnez la condition limite et ouvrez la fenêtre \textit{Éditer les conditions aux limites} :  (\includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"}).
+% Dans la fenêtre \textit{Éditer les conditions aux limites} cliquez sur \texttt{[Générer régime critique]} pour générer cette courbe.
+% Cliquez ensuite sur \texttt{[Rendre croissant]} pour suprimer les points de la courbe qui ne sont pas strictement croissants.
+% Vous pouvez fermer les fenêtres \textit{Éditer les conditions aux limites} et \textit{Conditions aux limites}.
 
 \section{Créer les conditions initiales}
 
-Dans la fenêtre principale, cliquez sur \texttt{[Hydraulique] => [Conditions initiales]} ou sur le racourci \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/boundary_condition.png"}.
-Pour démarer, le code de calcul hydraulique a besoin de connaitre le débit et la cote de la surface libre de la rivière en tout point à l'instant initial.
-Si vous ne connaissez pas ces conditions initiales, vous pouvez utiliser les boutons \texttt{[Générer une profondeur uniforme]}, \texttt{[Générer un débit uniforme]} ou \texttt{[Générer une cote uniforme]} pour laisser Pamhyr2 estimer une condition initiale à l'aide de la formule de Manning-Strickler.
-Cliquez sur \texttt{[Générer un débit uniforme]} et saisissez un débit de $4 m^3/s$ dans la fenêtre contextuelle, et cochez \texttt{[Générer une profondeur]} pour générer une condition initiale de hauteur d'eau basée sur la formule de Manning-Strickler pour le débit donné.
+Dans la fenêtre principale, cliquez sur \texttt{[Hydraulique] => [Conditions initiales]} ou sur le raccourci \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/boundary_condition.png"}.
+Pour démarrer, le code de calcul hydraulique a besoin de connaître le débit et la cote de la surface libre de la rivière en tout point à l'instant initial.
+Si vous ne connaissez pas ces conditions initiales, vous pouvez utiliser les boutons \texttt{[Générer une profondeur uniforme]}, \texttt{[Générer un débit uniforme]} ou \texttt{[Générer une cote uniforme]} pour laisser Pamhyr2 estimer une condition initiale.
+Cliquez sur \texttt{[Générer un débit uniforme]} et saisissez un débit de 4 m$^3$/s dans la fenêtre contextuelle, et cochez \texttt{[Générer une profondeur]} pour générer une condition initiale de hauteur d'eau basée sur la formule de Manning-Strickler pour le débit donné.
 Vous devriez voir :
 
 
@@ -273,14 +289,14 @@ Vous devriez voir :
 \includegraphics[width=15cm]{img/ic.png}
 \par\end{center}
 
-Vous pouvez également utiliser \texttt{[Générer une cote uniforme]} et saisir une cote de $21 m$ à l'amont et à l'aval avec un débit nul.
-Cela revient à créer une bassine et à laisser le solveur la vider pour trouver un écoulement initial satisfaisant.
+% Vous pouvez également utiliser \texttt{[Générer une cote uniforme]} et saisir une cote de 21 m à l'amont et à l'aval avec un débit nul.
+% Cela revient à créer une bassine et à laisser le solveur la vider pour trouver un écoulement initial satisfaisant.
 
-Lors des prochaines simulations, vous pouvez utiliser l'état final de la simulation précédente comme état initial.
+Lors des prochaines simulations, vous pourrez utiliser l'état final de la simulation précédente comme état initial.
 Pour cela, cliquez sur \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/import.png} et retrouvez le résultat sous forme de fichier \textit{.BIN}.
-Ce fichier de résultats se trouve normalement dans le sous-dossier \textit{\_PAMHYR\_/Hogneau/default-mage}.
+Ce fichier de résultats se trouve normalement dans le sous-dossier \texttt{\_PAMHYR\_/Hogneau/default-mage}.
 
-Fermer la fenêtre \textit{Conditions initiales}
+Fermer la fenêtre \textit{Conditions initiales}.
 
 
 \section{Éditer les coefficients de frottement}
@@ -301,13 +317,13 @@ Vous pouvez utiliser le bouton \includegraphics[width=0.5cm]{../../../src/View/u
 Fermez la fenêtre \textit{Strickler}.
 
 Dans la fenêtre \textit{Éditer les frottements}, ajoutez quatre lignes avec le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour créer quatre zones de frottement.
-Chaque zone est définie par un PK de \textit{début} et de \textit{fin} associés à un couple de coefficients de Strickler de \textit{début} et de \textit{fin}.
-Les couples de coefficients de Strickler à l'intérieur d'une zone sont interpolés à partir des couples \textit{début} et \textit{fin}.
-Dans notre cas, nous utiliserons des coefficients uniformes par zone.
+Chaque zone est définie par un PK de \textit{début} et de \textit{fin} associés à un couple de coefficients de Strickler.
+% Les couples de coefficients de Strickler à l'intérieur d'une zone sont interpolés à partir des couples \textit{début} et \textit{fin}.
+% Dans notre cas, nous utiliserons des coefficients uniformes par zone.
 Définissez les zones comme suit :
 
 \begin{center}
-\includegraphics[width=15cm]{img/frictions2.png}
+\includegraphics[width=15cm]{img/frictions.png}
 \par\end{center}
 
 La zone sélectionnée est surlignée en bleu.
@@ -316,12 +332,13 @@ Vous pouvez maintenant fermer la fen
 \section{Modélisation des ouvrages hydrauliques}
 
 Parfois, il peut y avoir des sections en travers dans lesquelles les équations de Saint-Venant ne peuvent pas être utilisées pour modéliser l'écoulement de l'eau.
-Dans ce cas, nous devons définir une autre loi pour relier la côte de l'eau et le débit.
+Dans ce cas, nous devons définir une autre loi pour relier la cote de l'eau et le débit.
 C'est le cas, par exemple, au niveau des ponts lorsque la hauteur d'eau est trop élevée et que l'écoulement passe en charge.
-Pamhyr2 permet de définir différents ouvrages hydrauliques avec des lois paramétrisables.
-Dans notre cas, nous allons devoir modéliser deux ponts par des ouvrages hydrauliques.
+Pamhyr2 permet de définir différents ouvrages hydrauliques avec des lois paramétrables.
+Dans notre cas, nous allons devoir modéliser deux ponts et un seuil par des ouvrages hydrauliques.
 Dans la fenêtre principale, cliquez sur \texttt{[Hydraulique] => [Ouvrages hydrauliques]} pour ouvrir la fenêtre des ouvrages hydrauliques.
-Cliquez deux fois sur le bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} pour créer deux ouvrages hydrauliques.
+Cliquez trois fois sur le bouton \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/add.png"} pour créer trois ouvrages hydrauliques.
+% Cliquez deux fois sur le bouton \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/add.png"} pour créer deux ouvrages hydrauliques.
 Chaque structure peut avoir un nom et doit avoir un bief et un PK.
 Définissez-les comme suit :
 
@@ -329,9 +346,17 @@ D
 \includegraphics[width=15cm]{img/hs.png}
 \par\end{center}
 
+Sélectionnez le seuil aval et cliquez sur \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"} pour modifier les lois de cet ouvrage. Les ouvrages hydrauliques sont composés d'ouvrages hydrauliques élémentaires. Vous pouvez combiner les lois de plusieurs ouvrages hydrauliques élémentaires pour créer votre ouvrage. Pour ce seuil, nous n'avons besoin que d'un ouvrage hydraulique élémentaire de type déversoir. Cliquez sur \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/add.png"} pour ajouter un nouvel ouvrage hydraulique élémentaire, donnez-lui le type \textit{Déversoir rectangulaire} et configurez-le comme suit :
+
+\begin{center}
+\includegraphics[width=15cm]{img/Seuil.png}
+\par\end{center}
+
+Retournez à la fenêtre \textit{Ouvrages hydrauliques}.
+
 Sélectionnez le pont RD101 et cliquez sur \includegraphics[width=0.5cm]{"../../../src/View/ui/ressources/edit.png"} pour éditer les lois de cet ouvrage.
-Les ouvrages hydrauliques sont composés d'ouvrages hydrauliques élémentaires.
-Vous pouvez combiner les lois de plusieurs ouvrages hydrauliques élémentaires pour créer votre ouvrage.
+% Les ouvrages hydrauliques sont composés d'ouvrages hydrauliques élémentaires.
+% Vous pouvez combiner les lois de plusieurs ouvrages hydrauliques élémentaires pour créer votre ouvrage.
 Un pont peut être modélisé comme une combinaison d'un orifice pour l'écoulement sous le pont et d'un déversoir pour l'écoulement au-dessus du pont.
 Créez deux ouvrages hydrauliques élémentaires à l'aide du bouton \includegraphics[width=0.5cm]{../../../src/View/ui/ressources/add.png} et définissez-les comme suit :
 
@@ -343,10 +368,30 @@ Cr
 
 Revenez à la fenêtre \textit{Ouvrages hydrauliques} et appliquez la même procédure pour le pont de Thivencelle :
 
-\begin{center}
-\includegraphics[width=15cm]{img/SeuilThivencelle.png}
+% \begin{center}
+% \includegraphics[width=15cm]{img/SeuilThivencelle.png}
+%
+% \includegraphics[width=15cm]{img/OrificeThivencelle.png}
+% \par\end{center}
 
-\includegraphics[width=15cm]{img/OrificeThivencelle.png}
+\begin{center}
+  \begin{tabular}{|c|c|}
+  \hline
+  \multicolumn{2}{|c|}{Orifice Thivencelle} \\ \hline
+  Largeur & 9.07 \\ \hline
+  cote & 17.28\\ \hline
+  cote de mise en charge & 20.74\\ \hline
+  coefficient de débit & 0.4\\ \hline
+  cote de mise en charge maximale & 9999.999\\ \hline
+  \end{tabular}
+  \hspace{1cm}
+  \begin{tabular}{|c|c|}
+  \hline
+  \multicolumn{2}{|c|}{Seuil Thivencelle} \\ \hline
+  Largeur & 13.0 \\ \hline
+  cote & 21.74\\ \hline
+  coefficient de débit & 0.4\\ \hline
+  \end{tabular}\\
 \par\end{center}
 
 
@@ -356,13 +401,19 @@ Si vous r
 \section{Paramètres du solveur}
 
 Dans la fenêtre principale, cliquez sur \texttt{[Exécuter] => [Parameters numériques des solveurs]}.
-Dans la fenêtre \textit{Paramètres du solveur}, sélectionnez l'onglet \textit{Mage v8}.
-Gardez les paramètres du solveur par défaut, sauf le pas de temps minimum, que vous mettrez à 0.1.
-En effet, durant la montée du pic de crue, le solveur a besoin de réduire le pas de temps suffisament pour permettre la convergence des itérations.
-% Pour accélérer les calculs, nous pouvons également dégrader la précision, à l'aide des facteurs de réduction de la précision.
-% Les précisions internes du solveur sont de $10{-9}$.
-% Cette précision est multipliée par le facteur de réduction de la précision : un facteur de 1000 ramènera donc la précision à $10{-5}$.
-% Pour utiliser ce facteur de réduction de la précision, il faut donner un \textit{nombre d'itérations à précision maximum} inférieur au \textit{nombre maximum d'itérations} : le solveur va d'abord tenter de converger avec un certain nombre d'itératons à la précision maximum avant de basculer sur une précision dégradée pour le reste des itérations.
+Dans la fenêtre \textit{Paramètres du solveur}, sélectionnez l'onglet \texttt{[Mage v8]}.
+Ces paramètres pilotent le comportement du solveur numérique.
+la valeur 999:99:00:00 du temps final indique au solveur de s'arrêter lorsqu'il a atteint un régime permanent.
+Vous pouvez changer la fréquence d'écriture des résultats dans la ligne \textit{Pas de temps d'écriture dans le fichier .BIN}.
+Une valeur inférieure à 1 seconde indique que la valeur de la ligne  \textit{Pas de temps d'écriture dans le fichier .TRA} s sera prise à la place.
+% En effet, durant la montée du pic de crue, le solveur a besoin de réduire le pas de temps suffisament pour permettre la convergence des itérations.
+Pour accélérer les calculs et pour aider le solveur à démarer, nous allons l'autoriser à dégrader la précision, à l'aide des facteurs de réduction de la précision.
+Les précisions internes du solveur sont de 10$^{-9}$.
+Cette précision est multipliée par le facteur de réduction de la précision : un facteur de 1000 ramènera donc la précision à 10$^{-5}$.
+Pour utiliser ce facteur de réduction de la précision, il faut donner un \textit{nombre d'itérations à précision maximum} inférieur au \textit{nombre maximum d'itérations} : le solveur va d'abord tenter de converger avec un certain nombre d'itératons à la précision maximum avant de basculer sur une précision dégradée pour le reste des itérations.
+Dans notre cas, c'est nécessaire pour lancer le solveur à partir de la condition initiale calculée par Pamhyr2.
+rentrez 1000 dans les trois lignes \textit{facteurs de réduction de la précision}, rentrez 99 pour le \textit{Nombre d'itérations} et 5 pour le \textit{nombre d'itérations à la précision maximum}.
+Gardez les autres paramètres du solveur par défaut.
 Fermer la fenêtre \textit{Paramètres du solveur}.
 
 \section{Lancer la simulation}
diff --git a/doc/users/Tuto1/step1.pamhyr b/doc/users/Tuto1/step1.pamhyr
index 89ec2024871ab1b3328daf51957a5a394d87cb30..1808572d116fb8aeee88abc90da6846be1df9866 100644
Binary files a/doc/users/Tuto1/step1.pamhyr and b/doc/users/Tuto1/step1.pamhyr differ
diff --git a/src/.pamhyr b/src/.pamhyr
new file mode 100644
index 0000000000000000000000000000000000000000..f84c9cfc61d1518be825460524be475ce771ef7a
Binary files /dev/null and b/src/.pamhyr differ
diff --git a/src/Checker/Adists.py b/src/Checker/Adists.py
new file mode 100644
index 0000000000000000000000000000000000000000..f47b441b71072379ba51c74bb397951f659fd645
--- /dev/null
+++ b/src/Checker/Adists.py
@@ -0,0 +1,51 @@
+# Adists.py -- Pamhyr study checkers
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import time
+import logging
+from functools import reduce
+
+from PyQt5.QtCore import QCoreApplication
+
+from Modules import Modules
+from Checker.Checker import AbstractModelChecker, STATUS
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+class AdistsOutputRKChecker(AbstractModelChecker):
+    def __init__(self):
+        super(AdistsOutputRKChecker, self).__init__()
+
+        self._name = _translate("Checker", "AdisTS output RK checker")
+        self._description = _translate(
+            "Checker", "Check output RK"
+        )
+        self._modules = Modules.OUTPUT_RK
+
+    def run(self, study):
+        ok = True
+        nerror = 0
+        self._summary = "ok"
+        self._status = STATUS.OK
+
+        if not self.basic_check(study):
+            return False
+
+        return ok
diff --git a/src/Meshing/Mage.py b/src/Meshing/Mage.py
index fb5912925fe4e174222a3949b7c001f9c3f0a3b1..30b5a7dcb03c20b449bf362869f6c0da4e8e7d72 100644
--- a/src/Meshing/Mage.py
+++ b/src/Meshing/Mage.py
@@ -395,7 +395,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
             logger.info(
                 f"! {self._exe_path()} " +
                 f"{st_file} {m_file} " +
-                f"update_kp " +
+                f"update_rk " +
                 f"{str(step)} " +
                 f"{limites[0]} {limites[1]} " +
                 f"{directrices[0]} {directrices[1]} " +
@@ -410,7 +410,7 @@ class MeshingWithMageMailleurTT(AMeshingTool):
                         str,
                         [
                             st_file, m_file,
-                            "update_kp", step,
+                            "update_rk", step,
                             limites[0], limites[1],
                             directrices[0], directrices[1],
                             orientation, lm, linear, origin, origin_value
diff --git a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..68a91ae705c0bacaf3ccbded5c4a24a93c7bb2ae
--- /dev/null
+++ b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionAdisTS.py
@@ -0,0 +1,221 @@
+# BoundaryConditionsAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import (
+    trace, timer,
+    old_pamhyr_date_to_timestamp,
+    date_iso_to_timestamp,
+    date_dmy_to_timestamp,
+)
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+
+class BoundaryConditionAdisTS(SQLSubModel):
+    _sub_classes = []
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1,
+                 pollutant: int = -1, status=None):
+        super(BoundaryConditionAdisTS, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = BoundaryConditionAdisTS._id_cnt
+        else:
+            self.id = id
+
+        self._type = ""
+        self._node = None
+        self._pollutant = pollutant
+        self._data = []
+        self._header = []
+        self._types = [self.time_convert, float]
+
+        BoundaryConditionAdisTS._id_cnt = max(BoundaryConditionAdisTS._id_cnt + 1, self.id)
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+          CREATE TABLE boundary_condition_adists(
+            id INTEGER NOT NULL PRIMARY KEY,
+            pollutant INTEGER NOT NULL,
+            type TEXT NOT NULL,
+            node INTEGER,
+            FOREIGN KEY(pollutant) REFERENCES Pollutants(id),
+            FOREIGN KEY(node) REFERENCES river_node(id)
+          )
+        """)
+
+        execute("""
+          CREATE TABLE boundary_condition_data_adists(
+            id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+            data0 TEXT NOT NULL,
+            data1 TEXT NOT NULL,
+            bc INTEGER,
+            FOREIGN KEY(bc) REFERENCES boundary_condition_adists(id)
+          )
+        """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, pollutant, type, node " +
+            "FROM boundary_condition_adists"
+        )
+
+        if table is not None:
+            for row in table:
+                bc = cls(
+                    id=row[0],
+                    pollutant=row[1],
+                    status=data['status']
+                )
+
+                bc.type = row[2]
+
+                bc.node = None
+                if row[3] != -1:
+                    bc.node = next(filter(lambda n: n.id == row[3], data["nodes"])).id
+
+                values = execute(
+                    "SELECT data0, data1 FROM boundary_condition_data_adists " +
+                    f"WHERE bc = '{bc.id}'"
+                )
+
+                # Write data
+                for v in values:
+                    data0 = bc._types[0](v[0])
+                    data1 = bc._types[1](v[1])
+                    # Replace data at pos ind
+                    bc._data.append((data0, data1))
+
+                new.append(bc)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+
+        execute(f"DELETE FROM boundary_condition_adists WHERE id = {self.id}")
+        execute(f"DELETE FROM boundary_condition_data_adists WHERE bc = {self.id}")
+
+        node = -1
+        if self._node is not None:
+            node = self._node
+
+        sql = (
+            "INSERT INTO " +
+            "boundary_condition_adists(id, pollutant, type, node) " +
+            "VALUES (" +
+            f"{self.id}, {self._pollutant}, " +
+            f"'{self._db_format(self._type)}', {node}" +
+            ")"
+        )
+        execute(sql)
+
+        for d in self._data:
+            data0 = self._db_format(str(d[0]))
+            data1 = self._db_format(str(d[1]))
+
+            sql = (
+                "INSERT INTO " +
+                "boundary_condition_data_adists(data0, data1, bc) " +
+                f"VALUES ('{data0}', {data1}, {self.id})"
+            )
+            execute(sql)
+
+        return True
+
+    def __len__(self):
+        return len(self._data)
+
+    @classmethod
+    def time_convert(cls, data):
+        if type(data) is str:
+            if data.count("-") == 2:
+                return date_iso_to_timestamp(data)
+            if data.count("/") == 2:
+                return date_dmy_to_timestamp(data)
+            if data.count(":") == 3:
+                return old_pamhyr_date_to_timestamp(data)
+            if data.count(":") == 2:
+                return old_pamhyr_date_to_timestamp("00:" + data)
+            if data.count(".") == 1:
+                return round(float(data))
+
+        return int(data)
+
+    @property
+    def node(self):
+        return self._node
+
+    @node.setter
+    def node(self, node):
+        self._node = node
+        self._status.modified()
+
+    @property
+    def header(self):
+        return self._header.copy()
+
+    @header.setter
+    def header(self, header):
+        self._header = header
+        self._status.modified()
+
+    @property
+    def pollutant(self):
+        return self._pollutant
+
+    @property
+    def type(self):
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        self._type = type
+        self._status.modified()
+
+    @property
+    def data(self):
+        return self._data.copy()
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Model/BoundaryConditionsAdisTS/BoundaryConditionsAdisTSList.py b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionsAdisTSList.py
new file mode 100644
index 0000000000000000000000000000000000000000..beb0ced6b2df8b768c2cc8a2a04543a640159265
--- /dev/null
+++ b/src/Model/BoundaryConditionsAdisTS/BoundaryConditionsAdisTSList.py
@@ -0,0 +1,68 @@
+# BoundaryConditionsAdisTSList.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.Except import NotImplementedMethodeError
+
+from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS import BoundaryConditionAdisTS
+
+class BoundaryConditionsAdisTSList(PamhyrModelList):
+    _sub_classes = [
+        BoundaryConditionAdisTS,
+    ]
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = cls(status=data['status'])
+
+        if data is None:
+            data = {}
+
+        new._lst = BoundaryConditionAdisTS._db_load(
+            execute, data
+        )
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute("DELETE FROM boundary_condition_adists")
+        execute("DELETE FROM boundary_condition_data_adists")
+
+        if data is None:
+            data = {}
+
+        for bc in self._lst:
+            bc._db_save(execute, data=data)
+
+        return True
+
+    def new(self, index, pollutant):
+        n = BoundaryConditionAdisTS(pollutant=pollutant, status=self._status)
+        self._lst.insert(index, n)
+        self._status.modified()
+        return n
+
+    @property
+    def BCs_AdisTS_List(self):
+        return self.lst
+
+
+
diff --git a/src/Model/D90AdisTS/D90AdisTS.py b/src/Model/D90AdisTS/D90AdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..39dac26af309c4c879218fe873f5521e99e71f01
--- /dev/null
+++ b/src/Model/D90AdisTS/D90AdisTS.py
@@ -0,0 +1,201 @@
+# D90AdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+from functools import reduce
+
+from tools import trace, timer, old_pamhyr_date_to_timestamp
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+from Model.D90AdisTS.D90AdisTSSpec import D90AdisTSSpec
+
+logger = logging.getLogger()
+
+class D90AdisTS(SQLSubModel):
+    _sub_classes = [
+        D90AdisTSSpec,
+    ]
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, name: str = "default",
+                 status=None):
+        super(D90AdisTS, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = D90AdisTS._id_cnt
+        else:
+            self.id = id
+
+        self._name = name
+        self._d90 = None
+        self._enabled = True
+        self._data = []
+
+        D90AdisTS._id_cnt = max(
+            D90AdisTS._id_cnt + 1,
+            self.id
+        )
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+                  CREATE TABLE d90_adists(
+                    id INTEGER NOT NULL PRIMARY KEY,
+                    name TEXT NOT NULL,
+                    d90 REAL NOT NULL,
+                    enabled BOOLEAN NOT NULL
+                  )
+                """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        major, minor, release = version.strip().split(".")
+        if major == minor == "0":
+            if int(release) < 6:
+                cls._db_create(execute)
+
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, name, d90, enabled " +
+            "FROM d90_adists"
+        )
+
+        if table is not None:
+            for row in table:
+                d90_id = row[0]
+                name = row[1]
+                d90 = row[2]
+                enabled = (row[3] == 1)
+
+                D90 = cls(
+                    id=d90_id,
+                    name=name,
+                    status=data['status']
+                )
+
+                D90.d90 = d90
+                D90.enabled = enabled
+
+                data['d90_default_id'] = d90_id
+                D90._data = D90AdisTSSpec._db_load(execute, data)
+
+                new.append(D90)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute(f"DELETE FROM d90_adists WHERE id = {self.id}")
+
+        d90 = -1.
+        if self.d90 is not None:
+            d90 = self.d90
+
+        sql = (
+            "INSERT INTO " +
+            "d90_adists(" +
+            "id, name, d90, enabled" +
+            ") " +
+            "VALUES (" +
+            f"{self.id}, '{self._db_format(self._name)}', " +
+            f"{d90}, {self._enabled}" +
+            ")"
+        )
+
+        execute(sql)
+
+        data['d90_default_id'] = self.id
+        execute(
+            "DELETE FROM d90_spec " +
+            f"WHERE d90_default = {self.id}"
+        )
+
+        for d90_spec in self._data:
+            d90_spec._db_save(execute, data)
+
+        return True
+
+    def __len__(self):
+        return len(self._data)
+
+    @property
+    def name(self):
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        self._name = name
+        self._status.modified()
+
+    @property
+    def d90(self):
+        return self._d90
+
+    @d90.setter
+    def d90(self, d90):
+        self._d90 = d90
+        self._status.modified()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+    def new(self, index):
+        n = D90AdisTSSpec(status=self._status)
+        self._data.insert(index, n)
+        self._status.modified()
+        return n
+
+    def delete(self, data):
+        self._data = list(
+            filter(
+                lambda x: x not in data,
+                self._data
+            )
+        )
+        self._status.modified()
+
+    def delete_i(self, indexes):
+        for ind in indexes:
+            del self._data[ind]
+        self._status.modified()
+
+    def insert(self, index, data):
+        self._data.insert(index, data)
+        self._status.modified()
+
+
+
+
+
+
diff --git a/src/Model/D90AdisTS/D90AdisTSList.py b/src/Model/D90AdisTS/D90AdisTSList.py
new file mode 100644
index 0000000000000000000000000000000000000000..076267ef234f0e893f8c54cc757d7d43d2c83986
--- /dev/null
+++ b/src/Model/D90AdisTS/D90AdisTSList.py
@@ -0,0 +1,66 @@
+# D90AdisTSList.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.D90AdisTS.D90AdisTS import D90AdisTS
+
+class D90AdisTSList(PamhyrModelList):
+    _sub_classes = [
+        D90AdisTS,
+    ]
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = cls(status=data['status'])
+
+        if data is None:
+            data = {}
+
+        new._lst = D90AdisTS._db_load(
+            execute, data
+        )
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute("DELETE FROM d90_adists")
+
+        if data is None:
+            data = {}
+
+        for d90 in self._lst:
+            d90._db_save(execute, data=data)
+
+        return True
+
+    def new(self, index):
+        n = D90AdisTS(status=self._status)
+        self._lst.insert(index, n)
+        self._status.modified()
+        return n
+
+    @property
+    def D90_AdisTS_List(self):
+        return self.lst
+
+
+
+
diff --git a/src/Model/D90AdisTS/D90AdisTSSpec.py b/src/Model/D90AdisTS/D90AdisTSSpec.py
new file mode 100644
index 0000000000000000000000000000000000000000..78992c67f3c1a8a27aee9900ffbc1a73ce0adb64
--- /dev/null
+++ b/src/Model/D90AdisTS/D90AdisTSSpec.py
@@ -0,0 +1,200 @@
+# D90AdisTSSpec.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import trace, timer
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+class D90AdisTSSpec(SQLSubModel):
+    _sub_classes = [
+    ]
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, name: str = "",
+                 status=None):
+        super(D90AdisTSSpec, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = D90AdisTSSpec._id_cnt
+        else:
+            self.id = id
+
+        self._name_section = name
+        self._reach = None
+        self._start_rk = None
+        self._end_rk = None
+        self._d90 = None
+        self._enabled = True
+
+        D90AdisTSSpec._id_cnt = max(D90AdisTSSpec._id_cnt + 1, self.id)
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+              CREATE TABLE d90_spec(
+                id INTEGER NOT NULL PRIMARY KEY,
+                d90_default INTEGER NOT NULL,
+                name TEXT NOT NULL,
+                reach INTEGER NOT NULL,
+                start_rk REAL NOT NULL,
+                end_rk REAL NOT NULL,
+                d90 REAL NOT NULL,
+                enabled BOOLEAN NOT NULL,
+                FOREIGN KEY(d90_default) REFERENCES d90_adists(id),
+                FOREIGN KEY(reach) REFERENCES river_reach(id)
+              )
+            """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        major, minor, release = version.strip().split(".")
+        if major == minor == "0":
+            if int(release) < 6:
+                cls._db_create(execute)
+
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, d90_default, name, reach, start_rk, end_rk, " +
+            "d90, enabled " +
+            "FROM d90_spec " +
+            f"WHERE d90_default = {data['d90_default_id']} "
+        )
+
+        for row in table:
+            id            = row[0]
+            name          = row[2]
+            reach         = row[3]
+            start_rk      = row[4]
+            end_rk        = row[5]
+            d90 = row[6]
+            enabled       = (row[7] == 1)
+
+            new_spec = cls(
+                id=id,
+                name=name,
+                status=data['status']
+            )
+
+            new_spec.reach = reach
+            new_spec.start_rk = start_rk
+            new_spec.end_rk = end_rk
+            new_spec.d90 = d90
+            new_spec.enabled = enabled
+
+            new.append(new_spec)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        d90_default = data['d90_default_id']
+
+        sql = (
+            "INSERT INTO " +
+            "d90_spec(id, d90_default, name, reach, " +
+            "start_rk, end_rk, d90, enabled) " +
+            "VALUES (" +
+            f"{self.id}, " +
+            f"{d90_default}, " +
+            f"'{self._db_format(self._name_section)}', " +
+            f"{self._reach}, " +
+            f"{self._start_rk}, " +
+            f"{self._end_rk}, " +
+            f"{self._d90}, " +
+            f"{self._enabled}" +
+            ")"
+        )
+        execute(sql)
+
+        return True
+
+    @property
+    def name(self):
+        return self._name_section
+
+    @name.setter
+    def name(self, name):
+        self._name_section = name
+        self._status.modified()
+
+    @property
+    def reach(self):
+        return self._reach
+
+    @reach.setter
+    def reach(self, reach):
+        self._reach = reach
+        self._status.modified()
+
+    @property
+    def start_rk(self):
+        return self._start_rk
+
+    @start_rk.setter
+    def start_rk(self, start_rk):
+        self._start_rk = start_rk
+        self._status.modified()
+
+    @property
+    def end_rk(self):
+        return self._end_rk
+
+    @end_rk.setter
+    def end_rk(self, end_rk):
+        self._end_rk = end_rk
+        self._status.modified()
+
+    @property
+    def d90(self):
+        return self._d90
+
+    @d90.setter
+    def d90(self, d90):
+        self._d90 = d90
+        self._status.modified()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+
+
+
+
+
+
+
+
diff --git a/src/Model/DIFAdisTS/DIFAdisTS.py b/src/Model/DIFAdisTS/DIFAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..c04479416e45e7d53b5933b164db4306e8847a4e
--- /dev/null
+++ b/src/Model/DIFAdisTS/DIFAdisTS.py
@@ -0,0 +1,258 @@
+# DIFAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+from functools import reduce
+
+from tools import trace, timer, old_pamhyr_date_to_timestamp
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+from Model.DIFAdisTS.DIFAdisTSSpec import DIFAdisTSSpec
+
+logger = logging.getLogger()
+
+class DIFAdisTS(SQLSubModel):
+    _sub_classes = [
+        DIFAdisTSSpec,
+    ]
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, name: str = "default",
+                 status=None):
+        super(DIFAdisTS, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = DIFAdisTS._id_cnt
+        else:
+            self.id = id
+
+        self._name = name
+        self._method = None
+        self._dif = None
+        self._b = None
+        self._c = None
+        self._enabled = True
+        self._types = ["iwasa", "fisher", "elder", "constante", "generique"]
+        self._data = []
+
+        DIFAdisTS._id_cnt = max(
+            DIFAdisTS._id_cnt + 1,
+            self.id
+        )
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+                  CREATE TABLE dif_adists(
+                    id INTEGER NOT NULL PRIMARY KEY,
+                    name TEXT NOT NULL,
+                    method TEXT NOT NULL,
+                    dif REAL NOT NULL,
+                    b REAL,
+                    c REAL,
+                    enabled BOOLEAN NOT NULL
+                  )
+                """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        major, minor, release = version.strip().split(".")
+        if major == minor == "0":
+            if int(release) < 6:
+                cls._db_create(execute)
+
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, name, method, dif, b, c, enabled " +
+            "FROM dif_adists"
+        )
+
+        if table is not None:
+            for row in table:
+                dif_id = row[0]
+                name = row[1]
+                method = row[2]
+                dif = row[3]
+                b = row[4]
+                c = row[5]
+                enabled = (row[6] == 1)
+
+                DIF = cls(
+                    id=dif_id,
+                    name=name,
+                    status=data['status']
+                )
+
+                DIF.method = method
+                DIF.dif = dif
+                DIF.b = b
+                DIF.c = c
+                DIF.enabled = enabled
+
+                data['dif_default_id'] = dif_id
+                DIF._data = DIFAdisTSSpec._db_load(execute, data)
+
+                new.append(DIF)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute(f"DELETE FROM dif_adists WHERE id = {self.id}")
+
+        method = ""
+        if self.method is not None:
+            method = self.method
+
+        dif = -1.
+        if self.dif is not None:
+            dif = self.dif
+
+        b = -1.
+        if self.b is not None:
+            b = self.b
+
+        c = -1.
+        if self.dif is not None:
+            c = self.c
+
+        sql = (
+            "INSERT INTO " +
+            "dif_adists(" +
+            "id, name, method, dif, b, c, enabled" +
+            ") " +
+            "VALUES (" +
+            f"{self.id}, '{self._db_format(self._name)}', " +
+            f"'{self._db_format(self._method)}', " +
+            f"{dif}, {b}, {c}, {self._enabled}" +
+            ")"
+        )
+
+        execute(sql)
+
+        data['dif_default_id'] = self.id
+        execute(
+            "DELETE FROM dif_spec " +
+            f"WHERE dif_default = {self.id}"
+        )
+
+        for dif_spec in self._data:
+            dif_spec._db_save(execute, data)
+
+        return True
+
+    def __len__(self):
+        return len(self._data)
+
+    @property
+    def name(self):
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        self._name = name
+        self._status.modified()
+
+    @property
+    def method(self):
+        return self._method
+
+    @method.setter
+    def method(self, method):
+        self._method = method
+        self._status.modified()
+
+    @property
+    def types(self):
+        return self._types
+
+    @property
+    def dif(self):
+        return self._dif
+
+    @dif.setter
+    def dif(self, dif):
+        self._dif = dif
+        self._status.modified()
+
+    @property
+    def b(self):
+        return self._b
+
+    @b.setter
+    def b(self, b):
+        self._b = b
+        self._status.modified()
+
+    @property
+    def c(self):
+        return self._c
+
+    @c.setter
+    def c(self, c):
+        self._c = c
+        self._status.modified()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+    def new(self, index):
+        n = DIFAdisTSSpec(status=self._status)
+        self._data.insert(index, n)
+        self._status.modified()
+        return n
+
+    def delete(self, data):
+        self._data = list(
+            filter(
+                lambda x: x not in data,
+                self._data
+            )
+        )
+        self._status.modified()
+
+    def delete_i(self, indexes):
+        for ind in indexes:
+            del self._data[ind]
+        self._status.modified()
+
+    def insert(self, index, data):
+        self._data.insert(index, data)
+        self._status.modified()
+
+
+
+
+
+
diff --git a/src/Model/DIFAdisTS/DIFAdisTSList.py b/src/Model/DIFAdisTS/DIFAdisTSList.py
new file mode 100644
index 0000000000000000000000000000000000000000..87a0f9360e6e8c35a26989c532bc52491aceb3d5
--- /dev/null
+++ b/src/Model/DIFAdisTS/DIFAdisTSList.py
@@ -0,0 +1,66 @@
+# DIFAdisTSList.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.DIFAdisTS.DIFAdisTS import DIFAdisTS
+
+class DIFAdisTSList(PamhyrModelList):
+    _sub_classes = [
+        DIFAdisTS,
+    ]
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = cls(status=data['status'])
+
+        if data is None:
+            data = {}
+
+        new._lst = DIFAdisTS._db_load(
+            execute, data
+        )
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute("DELETE FROM dif_adists")
+
+        if data is None:
+            data = {}
+
+        for dif in self._lst:
+            dif._db_save(execute, data=data)
+
+        return True
+
+    def new(self, index):
+        n = DIFAdisTS(status=self._status)
+        self._lst.insert(index, n)
+        self._status.modified()
+        return n
+
+    @property
+    def DIF_AdisTS_List(self):
+        return self.lst
+
+
+
+
diff --git a/src/Model/DIFAdisTS/DIFAdisTSSpec.py b/src/Model/DIFAdisTS/DIFAdisTSSpec.py
new file mode 100644
index 0000000000000000000000000000000000000000..306c12fcb7cb9a860ee7fee0ceb177d3ddd8848b
--- /dev/null
+++ b/src/Model/DIFAdisTS/DIFAdisTSSpec.py
@@ -0,0 +1,228 @@
+# DIFAdisTSSpec.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import trace, timer
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+class DIFAdisTSSpec(SQLSubModel):
+    _sub_classes = [
+    ]
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, method: str = "",
+                 status=None):
+        super(DIFAdisTSSpec, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = DIFAdisTSSpec._id_cnt
+        else:
+            self.id = id
+
+        self._method = method
+        self._reach = None
+        self._start_rk = None
+        self._end_rk = None
+        self._dif = None
+        self._b = None
+        self._c = None
+        self._enabled = True
+
+        DIFAdisTSSpec._id_cnt = max(DIFAdisTSSpec._id_cnt + 1, self.id)
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+              CREATE TABLE dif_spec(
+                id INTEGER NOT NULL PRIMARY KEY,
+                dif_default INTEGER NOT NULL,
+                method TEXT NOT NULL,
+                reach INTEGER NOT NULL,
+                start_rk REAL NOT NULL,
+                end_rk REAL NOT NULL,
+                dif REAL NOT NULL,
+                b REAL,
+                c REAL,
+                enabled BOOLEAN NOT NULL,
+                FOREIGN KEY(dif_default) REFERENCES dif_adists(id),
+                FOREIGN KEY(reach) REFERENCES river_reach(id)
+              )
+            """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        major, minor, release = version.strip().split(".")
+        if major == minor == "0":
+            if int(release) < 6:
+                cls._db_create(execute)
+
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, dif_default, method, reach, start_rk, end_rk, " +
+            "dif, b, c, enabled " +
+            "FROM dif_spec " +
+            f"WHERE dif_default = {data['dif_default_id']} "
+        )
+
+        for row in table:
+            id            = row[0]
+            method          = row[2]
+            reach         = row[3]
+            start_rk      = row[4]
+            end_rk        = row[5]
+            dif = row[6]
+            b = row[7]
+            c = row[8]
+            enabled       = (row[9] == 1)
+
+            new_spec = cls(
+                id=id,
+                method=method,
+                status=data['status']
+            )
+
+            new_spec.reach = reach
+            new_spec.start_rk = start_rk
+            new_spec.end_rk = end_rk
+            new_spec.dif = dif
+            new_spec.b = b
+            new_spec.c = c
+            new_spec.enabled = enabled
+
+            new.append(new_spec)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        dif_default = data['dif_default_id']
+
+        sql = (
+            "INSERT INTO " +
+            "dif_spec(id, dif_default, method, reach, " +
+            "start_rk, end_rk, dif, b, c, enabled) " +
+            "VALUES (" +
+            f"{self.id}, " +
+            f"{dif_default}, " +
+            f"'{self._db_format(self._method)}', " +
+            f"{self._reach}, " +
+            f"{self._start_rk}, " +
+            f"{self._end_rk}, " +
+            f"{self._dif}, " +
+            f"{self._b}, " +
+            f"{self._c}, " +
+            f"{self._enabled}" +
+            ")"
+        )
+        execute(sql)
+
+        return True
+
+    @property
+    def method(self):
+        return self._method
+
+    @method.setter
+    def method(self, method):
+        self._method = method
+        self._status.modified()
+
+    @property
+    def reach(self):
+        return self._reach
+
+    @reach.setter
+    def reach(self, reach):
+        self._reach = reach
+        self._status.modified()
+
+    @property
+    def start_rk(self):
+        return self._start_rk
+
+    @start_rk.setter
+    def start_rk(self, start_rk):
+        self._start_rk = start_rk
+        self._status.modified()
+
+    @property
+    def end_rk(self):
+        return self._end_rk
+
+    @end_rk.setter
+    def end_rk(self, end_rk):
+        self._end_rk = end_rk
+        self._status.modified()
+
+    @property
+    def dif(self):
+        return self._dif
+
+    @dif.setter
+    def dif(self, dif):
+        self._dif = dif
+        self._status.modified()
+
+    @property
+    def b(self):
+        return self._b
+
+    @b.setter
+    def b(self, b):
+        self._b = b
+        self._status.modified()
+
+    @property
+    def c(self):
+        return self._c
+
+    @c.setter
+    def c(self, c):
+        self._c = c
+        self._status.modified()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+
+
+
+
+
+
+
+
diff --git a/src/Model/Friction/Friction.py b/src/Model/Friction/Friction.py
index 9f0e0f3ca2c74c3505a3560f15901eab9725860e..a71a72f34c7b723e4f58c0c71eefec1de089d334 100644
--- a/src/Model/Friction/Friction.py
+++ b/src/Model/Friction/Friction.py
@@ -75,8 +75,8 @@ class Friction(SQLSubModel):
         if major == minor == "0":
             if int(release) < 11:
                 execute("ALTER TABLE friction " +
-                        "RENAME COLUMN begin_kp TO begin_rk")
-                execute("ALTER TABLE friction RENAME COLUMN end_kp TO end_rk")
+                        "RENAME COLUMN begin_rk TO begin_rk")
+                execute("ALTER TABLE friction RENAME COLUMN end_rk TO end_rk")
 
         return True
 
diff --git a/src/Model/Geometry/ProfileXYZ.py b/src/Model/Geometry/ProfileXYZ.py
index bb3faa0b7eba7908f72b5eb4d004e94ec24cbab0..f4ca99c4c4e9a263f43d2c38c146b92171c814aa 100644
--- a/src/Model/Geometry/ProfileXYZ.py
+++ b/src/Model/Geometry/ProfileXYZ.py
@@ -108,7 +108,7 @@ class ProfileXYZ(Profile, SQLSubModel):
                 execute(
                     """
                     ALTER TABLE geometry_profileXYZ
-                    RENAME COLUMN kp TO rk
+                    RENAME COLUMN rk TO rk
                     """
                 )
 
diff --git a/src/Model/HydraulicStructures/HydraulicStructures.py b/src/Model/HydraulicStructures/HydraulicStructures.py
index f7cf409786a685d2cd0d949979dbed740de3c8f8..579e0802fe04f2842671015b5ae46402ef103be6 100644
--- a/src/Model/HydraulicStructures/HydraulicStructures.py
+++ b/src/Model/HydraulicStructures/HydraulicStructures.py
@@ -64,6 +64,7 @@ class HydraulicStructure(SQLSubModel):
 
     @classmethod
     def _db_create(cls, execute):
+
         execute("""
           CREATE TABLE hydraulic_structures(
             id INTEGER NOT NULL PRIMARY KEY,
@@ -93,13 +94,13 @@ class HydraulicStructure(SQLSubModel):
                 execute(
                     """
                     ALTER TABLE hydraulic_structures
-                    RENAME COLUMN input_kp TO input_rk
+                    RENAME COLUMN input_rk TO input_rk
                     """
                 )
                 execute(
                     """
                     ALTER TABLE hydraulic_structures
-                    RENAME COLUMN output_kp TO output_rk
+                    RENAME COLUMN output_rk TO output_rk
                     """
                 )
 
@@ -154,6 +155,7 @@ class HydraulicStructure(SQLSubModel):
         return new
 
     def _db_save(self, execute, data=None):
+        print("save hs unit")
         execute(f"DELETE FROM hydraulic_structures WHERE id = {self.id}")
 
         input_reach_id = -1
diff --git a/src/Model/InitialConditions/InitialConditions.py b/src/Model/InitialConditions/InitialConditions.py
index 82756070569a3ca624913d26db09dcca19d19a41..acf2e87b71fdcdb37c60e213b2201d903b6b2df1 100644
--- a/src/Model/InitialConditions/InitialConditions.py
+++ b/src/Model/InitialConditions/InitialConditions.py
@@ -80,7 +80,7 @@ class Data(SQLSubModel):
         if major == minor == "0":
             if int(release) < 11:
                 execute(
-                    "ALTER TABLE initial_conditions RENAME COLUMN kp TO rk"
+                    "ALTER TABLE initial_conditions RENAME COLUMN rk TO rk"
                 )
 
         return cls._update_submodel(execute, version)
diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8f3b7d07f477c7febb7c09dab65de44261cdbf4
--- /dev/null
+++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTS.py
@@ -0,0 +1,272 @@
+# InitialConditionsAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+from functools import reduce
+
+from tools import trace, timer, old_pamhyr_date_to_timestamp
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+from Model.InitialConditionsAdisTS.InitialConditionsAdisTSSpec import ICAdisTSSpec
+
+logger = logging.getLogger()
+
+class InitialConditionsAdisTS(SQLSubModel):
+    _sub_classes = [
+        ICAdisTSSpec,
+    ]
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, name: str = "default",
+                 pollutant: int = -1, status=None):
+        super(InitialConditionsAdisTS, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = InitialConditionsAdisTS._id_cnt
+        else:
+            self.id = id
+
+        self._name = name
+        self._pollutant = pollutant
+        self._concentration = None
+        self._eg = None
+        self._em = None
+        self._ed = None
+        self._enabled = True
+        self._data = []
+
+        InitialConditionsAdisTS._id_cnt = max(
+            InitialConditionsAdisTS._id_cnt + 1,
+            self.id
+        )
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+                  CREATE TABLE initial_conditions_adists(
+                    id INTEGER NOT NULL PRIMARY KEY,
+                    pollutant INTEGER NOT NULL,
+                    name TEXT NOT NULL,
+                    concentration REAL NOT NULL,
+                    eg REAL NOT NULL,
+                    em REAL NOT NULL,
+                    ed REAL NOT NULL,
+                    enabled BOOLEAN NOT NULL,
+                    FOREIGN KEY(pollutant) REFERENCES Pollutants(id)
+                  )
+                """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        major, minor, release = version.strip().split(".")
+        if major == minor == "0":
+            if int(release) < 6:
+                cls._db_create(execute)
+
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, pollutant, name, concentration, eg, em, ed, " +
+            "enabled " +
+            "FROM initial_conditions_adists"
+        )
+
+        if table is not None:
+            for row in table:
+                IC_id = row[0]
+                pollutant = row[1]
+                name = row[2]
+                concentration = row[3]
+                eg = row[4]
+                em = row[5]
+                ed = row[6]
+                enabled = (row[7] == 1)
+
+                IC = cls(
+                    id=IC_id,
+                    name=name,
+                    status=data['status']
+                )
+
+                IC.pollutant = pollutant
+                IC.concentration = concentration
+                IC.eg = eg
+                IC.em = em
+                IC.ed = ed
+                IC.enabled = enabled
+
+                data['ic_default_id'] = IC_id
+                IC._data = ICAdisTSSpec._db_load(execute, data)
+
+                new.append(IC)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute(f"DELETE FROM initial_conditions_adists WHERE id = {self.id}")
+
+        pollutant = -1
+        if self.pollutant is not None:
+            pollutant = self.pollutant
+
+        concentration = -1.
+        if self.concentration is not None:
+            concentration = self.concentration
+
+        eg = -1.
+        if self.eg is not None:
+            eg = self.eg
+
+        em = -1.
+        if self.em is not None:
+            em = self.em
+
+        ed = -1.
+        if self.ed is not None:
+            ed = self.ed
+
+        sql = (
+            "INSERT INTO " +
+            "initial_conditions_adists(" +
+            "id, pollutant, name, concentration, " +
+            "eg, em, ed, enabled" +
+            ") " +
+            "VALUES (" +
+            f"{self.id}, {pollutant}, '{self._db_format(self._name)}', " +
+            f"{concentration}, {eg}, {em}, {ed}, {self._enabled}" +
+            ")"
+        )
+
+        execute(sql)
+
+        data['ic_default_id'] = self.id
+        execute(
+            "DELETE FROM initial_conditions_spec " +
+            f"WHERE ic_default = {self.id}"
+        )
+
+        for ic_spec in self._data:
+            ic_spec._db_save(execute, data)
+
+        return True
+
+    def __len__(self):
+        return len(self._data)
+
+    @property
+    def name(self):
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        self._name = name
+        self._status.modified()
+
+    @property
+    def pollutant(self):
+        return self._pollutant
+
+    @pollutant.setter
+    def pollutant(self, pollutant):
+        self._pollutant = pollutant
+        self._status.modified()
+
+    @property
+    def concentration(self):
+        return self._concentration
+
+    @concentration.setter
+    def concentration(self, concentration):
+        self._concentration = concentration
+        self._status.modified()
+
+    @property
+    def eg(self):
+        return self._eg
+
+    @eg.setter
+    def eg(self, eg):
+        self._eg = eg
+        self._status.modified()
+
+    @property
+    def em(self):
+        return self._em
+
+    @em.setter
+    def em(self, em):
+        self._em = em
+        self._status.modified()
+
+    @property
+    def ed(self):
+        return self._ed
+
+    @ed.setter
+    def ed(self, ed):
+        self._ed = ed
+        self._status.modified()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+    def new(self, index):
+        n = ICAdisTSSpec(status=self._status)
+        self._data.insert(index, n)
+        self._status.modified()
+        return n
+
+    def delete(self, data):
+        self._data = list(
+            filter(
+                lambda x: x not in data,
+                self._data
+            )
+        )
+        self._status.modified()
+
+    def delete_i(self, indexes):
+        for ind in indexes:
+            del self._data[ind]
+        self._status.modified()
+
+    def insert(self, index, data):
+        self._data.insert(index, data)
+        self._status.modified()
+
+
+
+
+
+
diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSList.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSList.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ac16cebab55ef56917f6b1b8ad6ec4cc0e28a5e
--- /dev/null
+++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSList.py
@@ -0,0 +1,66 @@
+# InitialConditionsAdisTSList.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.InitialConditionsAdisTS.InitialConditionsAdisTS import InitialConditionsAdisTS
+
+class InitialConditionsAdisTSList(PamhyrModelList):
+    _sub_classes = [
+        InitialConditionsAdisTS,
+    ]
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = cls(status=data['status'])
+
+        if data is None:
+            data = {}
+
+        new._lst = InitialConditionsAdisTS._db_load(
+            execute, data
+        )
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute("DELETE FROM initial_conditions_adists")
+
+        if data is None:
+            data = {}
+
+        for ic in self._lst:
+            ic._db_save(execute, data=data)
+
+        return True
+
+    def new(self, index, pollutant):
+        n = InitialConditionsAdisTS(pollutant=pollutant, status=self._status)
+        self._lst.insert(index, n)
+        self._status.modified()
+        return n
+
+    @property
+    def Initial_Conditions_List(self):
+        return self.lst
+
+
+
+
diff --git a/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py
new file mode 100644
index 0000000000000000000000000000000000000000..b98815e6428209638203064c3292689987aa02e6
--- /dev/null
+++ b/src/Model/InitialConditionsAdisTS/InitialConditionsAdisTSSpec.py
@@ -0,0 +1,258 @@
+# InitialConditionsAdisTSSpec.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import trace, timer
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+class ICAdisTSSpec(SQLSubModel):
+    _sub_classes = [
+    ]
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, name: str = "",
+                 status=None):
+        super(ICAdisTSSpec, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = ICAdisTSSpec._id_cnt
+        else:
+            self.id = id
+
+        self._name_section = name
+        self._reach = None
+        self._start_rk = None
+        self._end_rk = None
+        self._concentration = None
+        self._eg = None
+        self._em = None
+        self._ed = None
+        self._rate = None
+        self._enabled = True
+
+        ICAdisTSSpec._id_cnt = max(ICAdisTSSpec._id_cnt + 1, self.id)
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+              CREATE TABLE initial_conditions_spec(
+                id INTEGER NOT NULL PRIMARY KEY,
+                ic_default INTEGER NOT NULL,
+                name TEXT NOT NULL,
+                reach INTEGER NOT NULL,
+                start_rk REAL NOT NULL,
+                end_rk REAL NOT NULL,
+                concentration REAL NOT NULL,
+                eg REAL NOT NULL,
+                em REAL NOT NULL,
+                ed REAL NOT NULL,
+                rate REAL NOT NULL,
+                enabled BOOLEAN NOT NULL,
+                FOREIGN KEY(ic_default) REFERENCES initial_conditions_adists(id),
+                FOREIGN KEY(reach) REFERENCES river_reach(id)
+              )
+            """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        major, minor, release = version.strip().split(".")
+        if major == minor == "0":
+            if int(release) < 6:
+                cls._db_create(execute)
+
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, ic_default, name, reach, start_rk, end_rk, " +
+            "concentration, eg, em, ed, rate, enabled " +
+            "FROM initial_conditions_spec " +
+            f"WHERE ic_default = {data['ic_default_id']} "
+        )
+
+        for row in table:
+            id            = row[0]
+            name          = row[2]
+            reach         = row[3]
+            start_rk      = row[4]
+            end_rk        = row[5]
+            concentration = row[6]
+            eg            = row[7]
+            em            = row[8]
+            ed            = row[9]
+            rate          = row[10]
+            enabled       = (row[11] == 1)
+
+            #new_spec = [name, reach, start_rk, end_rk, concentration, eg, em, ed, rate, enabled]
+
+            new_spec = cls(
+                id=id,
+                name=name,
+                status=data['status']
+            )
+
+            new_spec.reach = reach
+            new_spec.start_rk = start_rk
+            new_spec.end_rk = end_rk
+            new_spec.concentration = concentration
+            new_spec.eg = eg
+            new_spec.em = em
+            new_spec.ed = ed
+            new_spec.rate = rate
+            new_spec.enabled = enabled
+
+            new.append(new_spec)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        ic_default = data['ic_default_id']
+
+        sql = (
+            "INSERT INTO " +
+            "initial_conditions_spec(id, ic_default, name, reach, " +
+            "start_rk, end_rk, concentration, eg, em, ed, rate, enabled) " +
+            "VALUES (" +
+            f"{self.id}, " +
+            f"{ic_default}, " +
+            f"'{self._db_format(self._name_section)}', " +
+            f"{self._reach}, " +
+            f"{self._start_rk}, " +
+            f"{self._end_rk}, " +
+            f"{self._concentration}, " +
+            f"{self._eg}, " +
+            f"{self._em}, " +
+            f"{self._ed}, " +
+            f"{self._rate}, " +
+            f"{self._enabled}" +
+            ")"
+        )
+        execute(sql)
+
+        return True
+
+    @property
+    def name(self):
+        return self._name_section
+
+    @name.setter
+    def name(self, name):
+        self._name_section = name
+        self._status.modified()
+
+    @property
+    def reach(self):
+        return self._reach
+
+    @reach.setter
+    def reach(self, reach):
+        self._reach = reach
+        self._status.modified()
+
+    @property
+    def start_rk(self):
+        return self._start_rk
+
+    @start_rk.setter
+    def start_rk(self, start_rk):
+        self._start_rk = start_rk
+        self._status.modified()
+
+    @property
+    def end_rk(self):
+        return self._end_rk
+
+    @end_rk.setter
+    def end_rk(self, end_rk):
+        self._end_rk = end_rk
+        self._status.modified()
+
+    @property
+    def concentration(self):
+        return self._concentration
+
+    @concentration.setter
+    def concentration(self, concentration):
+        self._concentration = concentration
+        self._status.modified()
+
+    @property
+    def eg(self):
+        return self._eg
+
+    @eg.setter
+    def eg(self, eg):
+        self._eg = eg
+        self._status.modified()
+
+    @property
+    def em(self):
+        return self._em
+
+    @em.setter
+    def em(self, em):
+        self._em = em
+        self._status.modified()
+
+    @property
+    def ed(self):
+        return self._ed
+
+    @ed.setter
+    def ed(self, ed):
+        self._ed = ed
+        self._status.modified()
+
+    @property
+    def rate(self):
+        return self._rate
+
+    @rate.setter
+    def rate(self, rate):
+        self._rate = rate
+        self._status.modified()
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+
+
+
+
+
+
+
+
diff --git a/src/Model/LateralContribution/LateralContribution.py b/src/Model/LateralContribution/LateralContribution.py
index bfed1d14ca97567049a2fb698803e690df288a35..2196d8357476733f3cd9f24245cdeaba626b4a9c 100644
--- a/src/Model/LateralContribution/LateralContribution.py
+++ b/src/Model/LateralContribution/LateralContribution.py
@@ -93,13 +93,13 @@ class LateralContribution(SQLSubModel):
                 execute(
                     """
                     ALTER TABLE lateral_contribution
-                    RENAME COLUMN begin_kp TO begin_rk
+                    RENAME COLUMN begin_rk TO begin_rk
                     """
                 )
                 execute(
                     """
                     ALTER TABLE lateral_contribution
-                    RENAME COLUMN end_kp TO end_rk
+                    RENAME COLUMN end_rk TO end_rk
                     """
                 )
 
diff --git a/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py b/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..742d7f875b00c26a96401fcd20ad04eab468c9ea
--- /dev/null
+++ b/src/Model/LateralContributionsAdisTS/LateralContributionAdisTS.py
@@ -0,0 +1,215 @@
+# LateralContributionAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import (
+    trace, timer,
+    old_pamhyr_date_to_timestamp,
+    date_iso_to_timestamp,
+    date_dmy_to_timestamp,
+)
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+class LateralContributionAdisTS(SQLSubModel):
+    _sub_classes = []
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, pollutant: int = -1, name: str = "", status=None):
+        super(LateralContributionAdisTS, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = LateralContributionAdisTS._id_cnt
+        else:
+            self.id = id
+
+        self._pollutant = pollutant
+        self._edge = None
+        self._begin_rk = 0.0
+        self._end_rk = 0.0
+        self._data = []
+        self._header = ["time", "rate"]
+        self._types = [self.time_convert, float]
+
+        LateralContributionAdisTS._id_cnt = max(
+            LateralContributionAdisTS._id_cnt + 1, self.id)
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+          CREATE TABLE lateral_contribution_adists(
+            id INTEGER NOT NULL PRIMARY KEY,
+            pollutant INTEGER NOT NULL,
+            edge INTEGER NOT NULL,
+            begin_rk REAL NOT NULL,
+            end_rk REAL NOT NULL,
+            FOREIGN KEY(pollutant) REFERENCES Pollutants(id),
+            FOREIGN KEY(edge) REFERENCES river_reach(id)
+          )
+        """)
+
+        execute("""
+          CREATE TABLE lateral_contribution_data_adists(
+            id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+            data0 TEXT NOT NULL,
+            data1 TEXT NOT NULL,
+            lc INTEGER,
+            FOREIGN KEY(lc) REFERENCES lateral_contribution(id)
+          )
+        """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        table = execute(
+            "SELECT id, pollutant, edge, begin_rk, end_rk " +
+            "FROM lateral_contribution_adists"
+        )
+
+        if table is not None:
+            for row in table:
+                lc = cls(
+                    id=row[0],
+                    pollutant=row[1],
+                    status=data['status']
+                )
+
+                lc.edge = row[2]
+                lc.begin_rk = row[3]
+                lc.end_rk = row[4]
+
+                values = execute(
+                    "SELECT data0, data1 FROM lateral_contribution_data_adists " +
+                    f"WHERE lc = '{lc.id}'"
+                )
+
+                # Write data
+                for v in values:
+                    data0 = lc._types[0](v[0])
+                    data1 = lc._types[1](v[1])
+                    # Replace data at pos ind
+                    lc._data.append((data0, data1))
+
+                new.append(lc)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+
+        execute(f"DELETE FROM lateral_contribution_adists WHERE id = {self.id}")
+        execute(f"DELETE FROM lateral_contribution_data_adists WHERE lc = {self.id}")
+
+        sql = (
+            "INSERT INTO " +
+            "lateral_contribution_adists(id, pollutant, edge, begin_rk, end_rk) " +
+            "VALUES (" +
+            f"{self.id}, {self._pollutant}, {self.edge}, " +
+            f"{self._begin_rk}, {self._end_rk}" +
+            ")"
+        )
+        execute(sql)
+
+        for d in self._data:
+            data0 = self._db_format(str(d[0]))
+            data1 = self._db_format(str(d[1]))
+
+            sql = (
+                "INSERT INTO " +
+                "lateral_contribution_data_adists(data0, data1, lc) " +
+                f"VALUES ('{data0}', {data1}, {self.id})"
+            )
+            execute(sql)
+
+        return True
+
+    def __len__(self):
+        return len(self._data)
+
+    @classmethod
+    def time_convert(cls, data):
+        if type(data) is str:
+            if data.count("-") == 2:
+                return date_iso_to_timestamp(data)
+            if data.count("/") == 2:
+                return date_dmy_to_timestamp(data)
+            if data.count(":") == 3:
+                return old_pamhyr_date_to_timestamp(data)
+            if data.count(":") == 2:
+                return old_pamhyr_date_to_timestamp("00:" + data)
+            if data.count(".") == 1:
+                return round(float(data))
+
+        return int(data)
+
+    @property
+    def edge(self):
+        return self._edge
+
+    @edge.setter
+    def edge(self, edge):
+        self._edge = edge
+        self._status.modified()
+
+    @property
+    def header(self):
+        return self._header.copy()
+
+    @header.setter
+    def header(self, header):
+        self._header = header
+        self._status.modified()
+
+    @property
+    def pollutant(self):
+        return self._pollutant
+
+    @property
+    def data(self):
+        return self._data.copy()
+
+    @property
+    def begin_rk(self):
+        return self._begin_rk
+
+    @begin_rk.setter
+    def begin_rk(self, begin_rk):
+        self._begin_rk = begin_rk
+        self._status.modified()
+
+    @property
+    def end_rk(self):
+        return self._end_rk
+
+    @end_rk.setter
+    def end_rk(self, end_rk):
+        self._end_rk = end_rk
+        self._status.modified()
+
diff --git a/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py b/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py
new file mode 100644
index 0000000000000000000000000000000000000000..af619eba9ec33a70467fe931956fc7777388ac87
--- /dev/null
+++ b/src/Model/LateralContributionsAdisTS/LateralContributionsAdisTSList.py
@@ -0,0 +1,65 @@
+# LateralContributionsAdisTSList.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import copy
+from tools import trace, timer
+
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.Except import NotImplementedMethodeError
+
+from Model.LateralContributionsAdisTS.LateralContributionAdisTS import LateralContributionAdisTS
+
+class LateralContributionsAdisTSList(PamhyrModelList):
+    _sub_classes = [
+        LateralContributionAdisTS,
+    ]
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = cls(status=data['status'])
+
+        if data is None:
+            data = {}
+
+        new._lst = LateralContributionAdisTS._db_load(
+            execute, data
+        )
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        execute("DELETE FROM lateral_contribution_adists")
+        execute("DELETE FROM lateral_condition_data_adists")
+
+        if data is None:
+            data = {}
+
+        for lc in self._lst:
+            lc._db_save(execute, data=data)
+
+        return True
+
+    def new(self, index, pollutant):
+        n = LateralContributionAdisTS(pollutant=pollutant, status=self._status)
+        self._lst.insert(index, n)
+        self._status.modified()
+        return n
+
+    @property
+    def Lat_Cont_List(self):
+        return self.lst
diff --git a/src/Model/OutputRKAdists/OutputRKAdists.py b/src/Model/OutputRKAdists/OutputRKAdists.py
new file mode 100644
index 0000000000000000000000000000000000000000..259d28d3ebc5e3b937f8c2aa57ba80d438168602
--- /dev/null
+++ b/src/Model/OutputRKAdists/OutputRKAdists.py
@@ -0,0 +1,164 @@
+# OutputRKAdists.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import (
+    trace, timer,
+    old_pamhyr_date_to_timestamp,
+    date_iso_to_timestamp,
+    date_dmy_to_timestamp,
+)
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+class OutputRKAdists(SQLSubModel):
+    _sub_classes = []
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, reach = None, rk = None, title: str = "",  status=None):
+        super(OutputRKAdists, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = OutputRKAdists._id_cnt
+        else:
+            self.id = id
+
+        self._reach = reach
+        self._rk = rk
+        self._title = str(title)
+        self._enabled = True
+
+        OutputRKAdists._id_cnt = max(
+            OutputRKAdists._id_cnt + 1, self.id)
+
+    @property
+    def reach(self):
+        return self._reach
+
+    @reach.setter
+    def reach(self, reach_id):
+        self._reach = reach_id
+        self._status.modified()
+
+    @property
+    def rk(self):
+        return self._rk
+
+    @rk.setter
+    def rk(self, profile_id):
+        self._rk = profile_id
+        self._status.modified()
+
+    @property
+    def title(self):
+        return self._title
+
+    @title.setter
+    def title(self, title):
+        self._title = title
+        self._status.modified()
+
+
+    @classmethod
+    def _db_create(cls, execute):
+
+        sql = (
+            "CREATE TABLE OutputRKAdists(" +
+            "id INTEGER NOT NULL PRIMARY KEY, " +
+            "reach INTEGER NOT NULL, " +
+            "rk REAL NOT NULL, " +
+            "title TEXT NOT NULL, " +
+            "FOREIGN KEY(reach) REFERENCES river_reach(id)" +
+            ")"
+        )
+
+        execute(sql)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        #reach   = data["reach"]
+        #profile = data["profile"]
+        status  = data["status"]
+
+        table = execute(
+            "SELECT id, reach, rk, title " +
+            f"FROM OutputRKAdists"
+        )
+
+        if table is not None:
+            for row in table:
+                id       = row[0]
+                id_reach = row[1]
+                id_rk    = row[2]
+                title    = row[3]
+
+                new_output = cls(
+                    id=id, reach=id_reach,
+                    rk=id_rk, title=title,
+                    status=status
+                )
+
+                new.append(new_output)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+
+        execute(f"DELETE FROM OutputRKAdists WHERE id = {self.id}")
+
+        sql = (
+            "INSERT INTO " +
+            "OutputRKAdists(id, reach, rk, title) " +
+            "VALUES (" +
+            f"{self.id}, {self._reach}, {self._rk}, " +
+            f"'{self._db_format(self._title)}'" +
+            ")"
+        )
+
+        execute(sql)
+
+        return True
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+
+
+
+
+
diff --git a/src/Model/OutputRKAdists/OutputRKListAdists.py b/src/Model/OutputRKAdists/OutputRKListAdists.py
new file mode 100644
index 0000000000000000000000000000000000000000..4b2d8e0717b12eb775ff873573b551bff329bb76
--- /dev/null
+++ b/src/Model/OutputRKAdists/OutputRKListAdists.py
@@ -0,0 +1,56 @@
+# OutputRKListAdists.py -- Pamhyr
+# Copyright (C) 2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from Model.Except import NotImplementedMethodeError
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.OutputRKAdists.OutputRKAdists import OutputRKAdists
+
+
+class OutputRKAdistsList(PamhyrModelList):
+    _sub_classes = [OutputRKAdists]
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = cls(status=data["status"])
+
+        new._lst = OutputRKAdists._db_load(execute, data)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        ok = True
+
+        # Delete previous data
+        execute("DELETE FROM OutputRKAdists")
+
+        for sl in self._lst:
+            ok &= sl._db_save(execute, data)
+
+        return ok
+
+    @property
+    def OutputRK_List(self):
+        return self.lst
+
+    def new(self, lst, index):
+        n = OutputRKAdists(status=self._status)
+        self._lst.insert(index, n)
+        self._status.modified()
+        return n
diff --git a/src/Model/Pollutants/Pollutants.py b/src/Model/Pollutants/Pollutants.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ca6bb06eac2b6e6550de64b258d61a73698e0c8
--- /dev/null
+++ b/src/Model/Pollutants/Pollutants.py
@@ -0,0 +1,186 @@
+# Pollutants.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import (
+    trace, timer,
+    old_pamhyr_date_to_timestamp,
+    date_iso_to_timestamp,
+    date_dmy_to_timestamp,
+)
+
+from Model.Tools.PamhyrDB import SQLSubModel
+from Model.Except import NotImplementedMethodeError
+
+logger = logging.getLogger()
+
+class Pollutants(SQLSubModel):
+    _sub_classes = []
+    _id_cnt = 0
+
+    def __init__(self, id: int = -1, name: str = "",  status=None):
+        super(Pollutants, self).__init__()
+
+        self._status = status
+
+        if id == -1:
+            self.id = Pollutants._id_cnt
+        else:
+            self.id = id
+
+        self._name = str(name)
+        self._enabled = True
+
+        self._data = []
+
+        Pollutants._id_cnt = max(
+            Pollutants._id_cnt + 1, self.id)
+
+    @property
+    def name(self):
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        self._name = name
+        self._status.modified()
+
+    @property
+    def data(self):
+        return self._data.copy()
+
+    @classmethod
+    def _db_create(cls, execute):
+        execute("""
+          CREATE TABLE Pollutants(
+            id INTEGER NOT NULL PRIMARY KEY,
+            name TEXT NOT NULL UNIQUE
+          )
+        """)
+
+        execute("""
+          CREATE TABLE Pollutants_characteristics(
+            id INTEGER NOT NULL PRIMARY KEY,
+            type INTEGER NOT NULL,
+            diametre REAL NOT NULL,
+            rho REAL NOT NULL,
+            porosity REAL NOT NULL,
+            cdc_riv REAL NOT NULL,
+            cdc_cas REAL NOT NULL,
+            apd REAL NOT NULL,
+            ac REAL NOT NULL,
+            bc REAL NOT NULL,
+            pollutant INTEGER NOT NULL,
+            FOREIGN KEY(pollutant) REFERENCES Pollutants(id)
+          )
+        """)
+
+        return cls._create_submodel(execute)
+
+    @classmethod
+    def _db_update(cls, execute, version):
+        return True
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = []
+
+        status  = data["status"]
+
+        table = execute(
+            "SELECT id, name " +
+            f"FROM Pollutants"
+        )
+
+        if table is not None:
+            for row in table:
+                id   = row[0]
+                name = row[1]
+
+                new_pollutant = cls(
+                    id=id, name=name,
+                    status=status
+                )
+
+                new_data = []
+                table = execute(
+                    "SELECT * " +
+                    "FROM Pollutants_characteristics " +
+                    f"WHERE pollutant = {id}"
+                )
+
+                if table is not None:
+                    for t in table:
+                        new_data = t[1:]
+
+                new_pollutant._data.append(new_data)
+
+                new.append(new_pollutant)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+
+        execute(f"DELETE FROM Pollutants WHERE id = {self.id}")
+        execute(f"DELETE FROM Pollutants_characteristics WHERE pollutant = {self.id}")
+
+        sql = (
+            "INSERT INTO " +
+            "Pollutants(id, name) " +
+            "VALUES (" +
+            f"{self.id}, " +
+            f"'{self._db_format(self._name)}'" +
+            ")"
+        )
+
+        execute(sql)
+
+        for d in self._data:
+            sql = (
+                "INSERT INTO " +
+                "Pollutants_characteristics(type, diametre, rho, porosity, " +
+                "cdc_riv, cdc_cas, apd, ac, bc, pollutant) " +
+                f"VALUES ({d[0]}, {d[1]}, {d[2]},{d[3]}, {d[4]}, "
+                f"{d[5]}, {d[6]}, {d[7]}, {d[8]}, {self.id})"
+            )
+            execute(sql)
+
+        return True
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        self._enabled = enabled
+        self._status.modified()
+
+    def is_define(self):
+        return len(self._data) != 0
+
+    def __len__(self):
+        return len(self._data)
+
+
+
+
+
+
+
diff --git a/src/Model/Pollutants/PollutantsList.py b/src/Model/Pollutants/PollutantsList.py
new file mode 100644
index 0000000000000000000000000000000000000000..2526329a76b0f356f803dee125218eb598aa695f
--- /dev/null
+++ b/src/Model/Pollutants/PollutantsList.py
@@ -0,0 +1,56 @@
+# PollutantsList.py -- Pamhyr
+# Copyright (C) 2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from Model.Except import NotImplementedMethodeError
+from Model.Tools.PamhyrList import PamhyrModelList
+from Model.Pollutants.Pollutants import Pollutants
+
+
+class PollutantsList(PamhyrModelList):
+    _sub_classes = [Pollutants]
+
+    @classmethod
+    def _db_load(cls, execute, data=None):
+        new = cls(status=data["status"])
+
+        new._lst = Pollutants._db_load(execute, data)
+
+        return new
+
+    def _db_save(self, execute, data=None):
+        ok = True
+
+        # Delete previous data
+        execute("DELETE FROM Pollutants")
+
+        for sl in self._lst:
+            ok &= sl._db_save(execute, data)
+
+        return ok
+
+    @property
+    def Pollutants_List(self):
+        return self.lst
+
+    def new(self, lst, index):
+        n = Pollutants(status=self._status)
+        self._lst.insert(index, n)
+        self._status.modified()
+        return n
diff --git a/src/Model/Results/ResultsAdisTS.py b/src/Model/Results/ResultsAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd7d09f9c78f34581b236d2b1645eba813e440de
--- /dev/null
+++ b/src/Model/Results/ResultsAdisTS.py
@@ -0,0 +1,83 @@
+# Results.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import logging
+import numpy as np
+import os, glob
+
+from copy import deepcopy
+from datetime import datetime
+
+from Model.Results.River.RiverAdisTS import River
+
+logger = logging.getLogger()
+
+
+class Results(object):
+    def __init__(self, study=None, solver=None,
+                 repertory="", name="0"):
+        self._study = study
+        self._solver = solver
+        self._repertory = repertory
+        self._name = name
+
+        self._river = River(self._study)
+
+        self._meta_data = {
+            # Keep results creation date
+            "creation_date": datetime.now(),
+        }
+
+        repertory_results = os.path.join(repertory, "resultats")
+        self._pollutants_list = [el.split("/")[-1][0:-4] for el in glob.glob(repertory_results + "/*.bin")]
+
+        self._phys_var_list = ["C", "G", "M", "D", "L", "N", "R"]
+
+        print("*********files names resultats from Results Object: ", self._pollutants_list)
+
+    @property
+    def date(self):
+        date = self._meta_data["creation_date"]
+        return f"{date.isoformat(sep=' ')}"
+
+    @property
+    def river(self):
+        return self._river
+
+    @property
+    def study(self):
+        return self._study
+
+    @property
+    def pollutants_list(self):
+        return self._pollutants_list
+
+    @property
+    def phys_var_list(self):
+        return self._phys_var_list
+
+    def set(self, key, value):
+        self._meta_data[key] = value
+
+    def get(self, key):
+        return self._meta_data[key]
+
+    def reload(self):
+        return self._solver.results(
+            self._study,
+            self._repertory,
+            qlog=None,
+        )
diff --git a/src/Model/Results/River/RiverAdisTS.py b/src/Model/Results/River/RiverAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..c37479e658fad824c1aad66dce8da0642447ce26
--- /dev/null
+++ b/src/Model/Results/River/RiverAdisTS.py
@@ -0,0 +1,136 @@
+# River.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+import logging
+
+from datetime import datetime
+
+logger = logging.getLogger()
+
+
+class Profile(object):
+    def __init__(self, profile, study):
+        self._study = study
+        self._profile = profile  # Source profile in the study
+        self._data = {}  # Dict of dict {<ts>: {<key>: <value>, ...}, ...}
+
+    def __len__(self):
+        return len(self._data)
+
+    @property
+    def name(self):
+        return self._profile.name
+
+    @property
+    def rk(self):
+        return self._profile.rk
+
+    @property
+    def geometry(self):
+        return self._profile
+
+    def set(self, timestamp, key, data):
+        if timestamp not in self._data:
+            self._data[timestamp] = {}
+
+        self._data[timestamp][key] = data
+
+    def get_ts(self, timestamp):
+        return self._data[timestamp]
+
+    def get_key(self, key):
+        return list(
+            map(lambda ts: self._data[ts][key], self._data)
+        )
+
+    def get_ts_key(self, timestamp, key):
+        return self._data[timestamp][key]
+
+    def has_sediment(self):
+        return any(map(lambda ts: "sl" in self._data[ts], self._data))
+
+
+class Reach(object):
+    def __init__(self, reach, study):
+        self._study = study
+        self._reach = reach  # Source reach in the study
+        self._profiles = list(
+            map(
+                lambda p: Profile(p, self._study),
+                reach.profiles
+            )
+        )
+
+    def __len__(self):
+        return len(self._profiles)
+
+    @property
+    def name(self):
+        return self._reach.name
+
+    @property
+    def geometry(self):
+        return self._reach
+
+    @property
+    def profiles(self):
+        return self._profiles.copy()
+
+    def profile(self, id):
+        return self._profiles[id]
+
+    def set(self, profile_id, timestamp, key, data):
+        self._profiles[profile_id].set(timestamp, key, data)
+
+    def has_sediment(self):
+        return any(map(lambda profile: profile.has_sediment(), self._profiles))
+
+
+class River(object):
+    def __init__(self, study):
+        self._study = study
+
+        # Dict with timestamps as key
+        self._reachs = []
+
+    def __len__(self):
+        return len(self._reachs)
+
+    @property
+    def reachs(self):
+        return self._reachs.copy()
+
+    def reach(self, id):
+        return self._reachs[id]
+
+    def has_reach(self, id):
+        return 0 <= id < len(self._reachs)
+
+    def add(self, reach_id):
+        reachs = self._study.river.enable_edges()
+
+        new = Reach(reachs[reach_id].reach, self._study)
+
+        self._reachs.append(new)
+        return new
+
+    def get_reach_by_geometry(self, geometry_reach):
+        return next(
+            filter(
+                lambda r: r.geometry is geometry_reach,
+                self._reachs
+            )
+        )
diff --git a/src/Model/River.py b/src/Model/River.py
index 55c17c634b72808e3c0a4e3ad3a5d173ae8783d7..0b1d024812fe9945d6a5332c7947f3b58ec3d1ca 100644
--- a/src/Model/River.py
+++ b/src/Model/River.py
@@ -45,6 +45,14 @@ from Model.REPLine.REPLineList import REPLineList
 
 from Solver.Solvers import solver_type_list
 
+from Model.OutputRKAdists.OutputRKListAdists import OutputRKAdistsList
+from Model.Pollutants.PollutantsList import PollutantsList
+from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList import InitialConditionsAdisTSList
+from Model.BoundaryConditionsAdisTS.BoundaryConditionsAdisTSList import BoundaryConditionsAdisTSList
+from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList import LateralContributionsAdisTSList
+from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
+from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
+
 
 class RiverNode(Node, SQLSubModel):
     _sub_classes = []
@@ -228,6 +236,13 @@ class River(Graph, SQLSubModel):
         HydraulicStructureList,
         AddFileList,
         REPLineList,
+        OutputRKAdistsList,
+        PollutantsList,
+        InitialConditionsAdisTSList,
+        BoundaryConditionsAdisTSList,
+        LateralContributionsAdisTSList,
+        D90AdisTSList,
+        DIFAdisTSList,
     ]
 
     def __init__(self, status=None):
@@ -251,6 +266,13 @@ class River(Graph, SQLSubModel):
         )
         self._additional_files = AddFileList(status=self._status)
         self._rep_lines = REPLineList(status=self._status)
+        self._Output_rk_adists = OutputRKAdistsList(status=self._status)
+        self._Pollutants = PollutantsList(status=self._status)
+        self._InitialConditionsAdisTS = InitialConditionsAdisTSList(status=self._status)
+        self._BoundaryConditionsAdisTS = BoundaryConditionsAdisTSList(status=self._status)
+        self._LateralContributionsAdisTS = LateralContributionsAdisTSList(status=self._status)
+        self._D90AdisTS = D90AdisTSList(status=self._status)
+        self._DIFAdisTS = DIFAdisTSList(status=self._status)
 
     @classmethod
     def _db_create(cls, execute):
@@ -325,6 +347,22 @@ class River(Graph, SQLSubModel):
         )
         new._rep_lines = REPLineList._db_load(execute, data)
 
+        new._Output_rk_adists = OutputRKAdistsList._db_load(
+            execute, data
+        )
+
+        new._Pollutants = PollutantsList._db_load(execute, data)
+
+        new._InitialConditionsAdisTS = InitialConditionsAdisTSList._db_load(execute, data)
+
+        new._BoundaryConditionsAdisTS = BoundaryConditionsAdisTSList._db_load(execute, data)
+
+        new._LateralContributionsAdisTS = LateralContributionsAdisTSList._db_load(execute, data)
+
+        new._D90AdisTS = D90AdisTSList._db_load(execute, data)
+
+        new._DIFAdisTS = DIFAdisTSList._db_load(execute, data)
+
         return new
 
     def _db_save(self, execute, data=None):
@@ -344,6 +382,14 @@ class River(Graph, SQLSubModel):
         for solver in self._parameters:
             objs.append(self._parameters[solver])
 
+        objs.append(self._Output_rk_adists)
+        objs.append(self._Pollutants)
+        objs.append(self._InitialConditionsAdisTS)
+        objs.append(self._BoundaryConditionsAdisTS)
+        objs.append(self._LateralContributionsAdisTS)
+        objs.append(self._D90AdisTS)
+        objs.append(self._DIFAdisTS)
+
         self._save_submodel(execute, objs, data)
         return True
 
@@ -466,6 +512,34 @@ Last export at: @date."""
     def parameters(self):
         return self._parameters
 
+    @property
+    def Output_rk_adists(self):
+        return self._Output_rk_adists
+
+    @property
+    def Pollutants(self):
+        return self._Pollutants
+
+    @property
+    def initial_conditions_adists(self):
+        return self._InitialConditionsAdisTS
+
+    @property
+    def boundary_conditions_adists(self):
+        return self._BoundaryConditionsAdisTS
+
+    @property
+    def lateral_contributions_adists(self):
+        return self._LateralContributionsAdisTS
+
+    @property
+    def d90_adists(self):
+        return self._D90AdisTS
+
+    @property
+    def dif_adists(self):
+        return self._DIFAdisTS
+
     def get_params(self, solver):
         if solver in self._parameters:
             return self._parameters[solver]
diff --git a/src/Model/Study.py b/src/Model/Study.py
index aed5d68905f24147c8f77d6f74086a1c0b7a51fd..f4448be3a6de49e222c6d7f7d30c5a3b408d9291 100644
--- a/src/Model/Study.py
+++ b/src/Model/Study.py
@@ -72,7 +72,7 @@ class Study(SQLModel):
             StudyNetworkReachChecker(),
             StudyGeometryChecker(),
             StudyInitialConditionsChecker(),
-            StudyBoundaryConditionChecker(),
+            #StudyBoundaryConditionChecker(),
             # DummyOK(),
             # DummyWARNING(),
             # DummyERROR(),
diff --git a/src/Modules.py b/src/Modules.py
index f8ed5b65d8cfeb67004cffe205de6e06dcc9c910..40e903f60822d047d9af9726402274880d829344 100644
--- a/src/Modules.py
+++ b/src/Modules.py
@@ -54,6 +54,7 @@ class Modules(IterableFlag):
     RESERVOIR = auto()
     SEDIMENT_LAYER = auto()
     ADDITIONAL_FILES = auto()
+    OUTPUT_RK = auto()
 
     # Results
     RESULTS = auto()
@@ -77,6 +78,7 @@ class Modules(IterableFlag):
             cls.ADDITIONAL_FILES,
             cls.RESULTS,
             cls.WINDOW_LIST,
+            cls.OUTPUT_RK,
         ]
 
     @classmethod
diff --git a/src/Solver/AdisTS.py b/src/Solver/AdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e3fbd61449961391b167a779efd59a501abf6f8
--- /dev/null
+++ b/src/Solver/AdisTS.py
@@ -0,0 +1,731 @@
+# AdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import os, glob
+import logging
+from http.cookiejar import reach
+
+import numpy as np
+
+import shutil
+
+from tools import (
+    trace, timer, logger_exception,
+    timestamp_to_old_pamhyr_date,
+    old_pamhyr_date_to_timestamp,
+    timestamp_to_old_pamhyr_date_adists
+)
+
+from Solver.CommandLine import CommandLineSolver
+
+from Model.Results.ResultsAdisTS import Results
+from Model.Results.River.River import River, Reach, Profile
+
+from Checker.Adists import (
+    AdistsOutputRKChecker,
+)
+
+logger = logging.getLogger()
+
+def adists_file_open(filepath, mode):
+    f = open(filepath, mode)
+
+    if "w" in mode:
+        # Write header
+        comment = "*"
+        if ".ST" in filepath:
+            comment = "#"
+
+        f.write(
+            f"{comment} " +
+            "This file is generated by PAMHYR, please don't modify\n"
+        )
+
+    return f
+
+class AdisTS(CommandLineSolver):
+    _type = "adists"
+
+    def __init__(self, name):
+        super(AdisTS, self).__init__(name)
+
+        self._type = "adists"
+
+        self._cmd_input = ""
+        self._cmd_solver = "@path @input -o @output"
+        self._cmd_output = ""
+
+    @classmethod
+    def default_parameters(cls):
+        lst = super(AdisTS, cls).default_parameters()
+
+        lst += [
+            ("adists_implicitation_parameter", "0.5"),
+            ("adists_timestep_screen", "60"),
+            ("adists_timestep_bin", "60"),
+            ("adists_timestep_csv", "60"),
+            ("adists_timestep_mage", "60"),
+            ("adists_initial_concentration", "60"),
+        ]
+
+        return lst
+
+    @classmethod
+    def checkers(cls):
+        lst = [
+            AdistsOutputRKChecker(),
+        ]
+
+        return lst
+
+    def cmd_args(self, study):
+        lst = super(AdisTS, self).cmd_args(study)
+        return lst
+
+    def input_param(self):
+        name = self._study.name
+        return f"{name}.REP"
+
+    def log_file(self):
+        name = self._study.name
+        return f"{name}.TRA"
+
+    def _export_REP_additional_lines(self, study, rep_file):
+        lines = filter(
+            lambda line: line.is_enabled(),
+            study.river.rep_lines.lines
+        )
+
+        for line in lines:
+            rep_file.write(line.line)
+
+    def _export_REP(self, study, repertory, mage_rep, files, qlog, name="0"):
+
+        if qlog is not None:
+            qlog.put("Export REP file")
+
+        # Write header
+        with adists_file_open(
+                os.path.join(
+                    repertory, f"{name}.REP"
+                ), "w+"
+        ) as f:
+
+            f.write(f"NET ../{mage_rep}/{name}.NET\n")
+            f.write(f"REP ../{mage_rep}/{name}.REP\n")
+
+            for file in files:
+                EXT = file.split('.')[1]
+                f.write(f"{EXT} {file}\n")
+
+            self._export_REP_additional_lines(study, f)
+
+        path_mage_net = os.path.join(os.path.abspath(os.path.join(repertory, os.pardir)), f"{mage_rep}/net")
+        path_adists_net = os.path.join(repertory, "net")
+
+        if os.path.exists(path_mage_net):
+            shutil.copytree(path_mage_net, path_adists_net, dirs_exist_ok=True)
+
+    @timer
+    def export(self, study, repertory, qlog=None):
+        self._study = study
+        name = study.name.replace(" ", "_")
+
+        self.export_additional_files(study, repertory, qlog, name=name)
+
+        return True
+
+    ###########
+    # RESULTS #
+    ###########
+
+    def read_bin(self, study, repertory, results, qlog=None, name="0"):
+        return
+
+    @timer
+    def results(self, study, repertory, qlog=None, name="0"):
+        results = Results(
+            study=study,
+            solver=self,
+            repertory=repertory,
+            name=name,
+        )
+        self.read_bin(study, repertory, results, qlog, name=name)
+
+        return results
+
+    def output_param(self):
+        name = ""
+        return f"{name}"
+
+################################
+# Adis-TS in low coupling mode #
+################################
+
+
+class AdisTSlc(AdisTS):
+    _type = "adistslc"
+
+    def __init__(self, name):
+        super(AdisTSlc, self).__init__(name)
+
+        self._type = "adistslc"
+
+    @classmethod
+    def default_parameters(cls):
+        lst = super(AdisTSlc, cls).default_parameters()
+
+        # Insert new parameters at specific position
+        names = list(map(lambda t: t[0], lst))
+
+        return lst
+
+    ##########
+    # Export #
+    ##########
+
+    def cmd_args(self, study):
+        lst = super(AdisTSlc, self).cmd_args(study)
+
+        return lst
+
+    def _export_POLs(self, study, repertory, qlog=None, name="0"):
+
+        files = []
+
+        if qlog is not None:
+            qlog.put("Export POLS files")
+
+        pollutants = study.river.Pollutants.Pollutants_List
+
+        for pollutant in pollutants:
+            name = pollutant.name
+            with adists_file_open(os.path.join(repertory, f"{name}.POL"), "w+") as f:
+                files.append(f"{name}.POL")
+                f.write(f"*Polluant A contaminé aux PCB\n")
+                f.write(f"name = {name}\n")
+
+                self._export_POL_Characteristics(study, pollutant._data, f, qlog)
+
+                POL_ICs = next(filter(lambda ic: ic.pollutant == pollutant.id,\
+                                      study.river.initial_conditions_adists.Initial_Conditions_List))
+
+                if POL_ICs.concentration != None:
+                    f.write(f"file_ini = {name}.INI\n")
+                    self._export_ICs_AdisTS(study, repertory, POL_ICs, qlog, name)
+
+                POL_BCs = list(filter(lambda bc: bc.pollutant == pollutant.id,\
+                                      study.river.boundary_conditions_adists.BCs_AdisTS_List))
+
+                if len(POL_BCs) != 0:
+                    f.write(f"file_cl = {name}.CDT\n")
+                    self._export_BCs_AdisTS(study, repertory, POL_BCs, qlog, name)
+
+                POL_LAT_Cont = list(filter(lambda lc: lc.pollutant == pollutant.id,\
+                                      study.river.lateral_contributions_adists.Lat_Cont_List))
+
+                if len(POL_LAT_Cont) != 0:
+                    f.write(f"file_ald = {name}.ALD\n")
+                    f.write(f"*\n")
+                    self._export_Lat_AdisTS(study, repertory, POL_LAT_Cont, qlog, name)
+
+        return files
+
+    def _export_Lat_AdisTS(self, study, repertory, POL_LC, qlog, POL_name):
+
+        if qlog is not None:
+            qlog.put("Export POL LCs files")
+
+        with adists_file_open(os.path.join(repertory, f"{POL_name}.ALD"), "w+") as f:
+            for LC in POL_LC:
+                reach_name = next(filter(lambda edge: edge.id == LC.edge, study.river.edges())).name
+                f.write(f"${reach_name} {LC.begin_rk} {LC.end_rk}\n")
+                f.write(f"*temps   |débit massique (kg/s)\n")
+                f.write(f"*---------++++++++++\n")
+
+                for LC_data in LC._data:
+                    f.write(f"{timestamp_to_old_pamhyr_date_adists(int(LC_data[0]))} {LC_data[1]}\n")
+                f.write(f"*\n")
+
+        return True
+
+    def _export_BCs_AdisTS(self, study, repertory, POL_BC, qlog, POL_name):
+
+        if qlog is not None:
+            qlog.put("Export POL BCs files")
+
+        with adists_file_open(os.path.join(repertory, f"{POL_name}.CDT"), "w+") as f:
+            for BC in POL_BC:
+                node_name = next(filter(lambda x: x.id == BC.node, study.river._nodes)).name
+                f.write(f"${node_name}\n")
+
+                if BC.type == "Concentration":
+                    f.write(f"*temps |concentration\n")
+                    f.write(f"*JJ:HH:MM | (g/L)\n")
+                    f.write(f"*---------++++++++++\n")
+                else:
+                    f.write(f"*temps |rate\n")
+                    f.write(f"*JJ:HH:MM | (kg/s)\n")
+                    f.write(f"*---------++++++++++\n")
+
+                for BC_data in BC._data:
+                    f.write(f"{timestamp_to_old_pamhyr_date_adists(int(BC_data[0]))} {BC_data[1]}\n")
+                f.write(f"*\n")
+
+        return True
+
+    def _export_ICs_AdisTS(self, study, repertory, POL_IC_default, qlog, POL_name):
+
+        if qlog is not None:
+            qlog.put("Export POL ICs files")
+
+        with adists_file_open(os.path.join(repertory, f"{POL_name}.INI"), "w+") as f:
+            f.write(f"*État initial pour le polluant {POL_name}\n")
+            f.write(f"{POL_IC_default.name} = {POL_IC_default.concentration} {POL_IC_default.eg} "+
+            f"{POL_IC_default.em} {POL_IC_default.ed}\n")
+
+            if len(POL_IC_default._data) != 0:
+                self._export_ICs_AdisTS_Spec(study, POL_IC_default._data, f, qlog)
+
+    def _export_ICs_AdisTS_Spec(self, study, pol_ics_spec_data, f, qlog, name="0"):
+
+        for ic_spec in pol_ics_spec_data:
+            f.write(f"{ic_spec.name} = {ic_spec.reach} {ic_spec.start_rk} {ic_spec.end_rk} " +
+                    f"{ic_spec.concentration} {ic_spec.eg} {ic_spec.em} {ic_spec.ed} {ic_spec.rate}")
+
+        return True
+
+    def _export_POL_Characteristics(self, study, pol_data, f, qlog, name="0"):
+
+        list_characteristics = ["type", "diametre", "rho", "porosity", "cdc_riv", "cdc_cas", "apd", "ac", "bc"]
+
+        if len(list_characteristics) == (len(pol_data[0])-1):
+            for l in range(len(list_characteristics)):
+                f.write(f"{list_characteristics[l]} = {pol_data[0][l]}\n")
+
+    def _export_D90(self, study, repertory, qlog=None, name="0"):
+
+        files = []
+
+        if qlog is not None:
+            qlog.put("Export D90 file")
+
+        with adists_file_open(os.path.join(repertory, f"{name}.D90"), "w+") as f:
+            files.append(f"{name}.D90")
+
+            f.write(f"*Diamètres caractéristiques du fond stable\n")
+
+            d90AdisTS = study.river.d90_adists.D90_AdisTS_List
+
+            f.write(f"{d90AdisTS[0].name} = {d90AdisTS[0].d90}\n")
+
+            self._export_d90_spec(study, d90AdisTS[0]._data, f, qlog)
+
+        return files
+
+    def _export_d90_spec(self, study, d90_spec_data, f, qlog, name="0"):
+
+        for d90_spec in d90_spec_data:
+            if (d90_spec.name is None) or (d90_spec.reach is None) or (d90_spec.start_rk is None) or \
+                (d90_spec.end_rk is None) or (d90_spec.d90 is None):
+                return
+
+            edges = study.river.enable_edges()
+
+            id_edges = list(map(lambda x: x.id, edges))
+
+            id_reach = d90_spec.reach
+
+            if id_reach not in id_edges:
+                return
+
+            f.write(f"{d90_spec.name} = {id_reach} {d90_spec.start_rk} {d90_spec.end_rk} {d90_spec.d90}\n")
+
+    def _export_DIF(self, study, repertory, qlog=None, name="0"):
+
+        files = []
+
+        if qlog is not None:
+            qlog.put("Export DIF file")
+
+        with adists_file_open(os.path.join(repertory, f"{name}.DIF"), "w+") as f:
+            files.append(f"{name}.DIF")
+
+            f.write(f"*Définition des paramètres des fonctions de calcul du\n")
+            f.write(f"*coefficient de diffusion\n")
+
+            difAdisTS = study.river.dif_adists.DIF_AdisTS_List
+
+            if difAdisTS[0].method != "generique":
+                f.write(f"defaut = {difAdisTS[0].method} {difAdisTS[0].dif }\n")
+            else:
+                f.write(f"defaut = {difAdisTS[0].method} {difAdisTS[0].dif} {difAdisTS[0].b} {difAdisTS[0].c}\n")
+
+            self._export_dif_spec(study, difAdisTS[0]._data, f, qlog)
+
+        return files
+
+    def _export_dif_spec(self, study, dif_spec_data, f, qlog, name="0"):
+
+        for dif_spec in dif_spec_data:
+            if (dif_spec.reach is None) or (dif_spec.start_rk is None) or \
+                (dif_spec.end_rk is None) or (dif_spec.dif is None) or (dif_spec.b is None) or (dif_spec.c is None):
+                return
+
+            edges = study.river.enable_edges()
+
+            id_edges = list(map(lambda x: x.id, edges))
+
+            id_reach = dif_spec.reach
+
+            if id_reach not in id_edges:
+                return
+
+            if dif_spec.method != "generique":
+                f.write(f"{dif_spec.method} = {id_reach} {dif_spec.start_rk} {dif_spec.end_rk} {dif_spec.dif}\n")
+            else:
+                f.write(f"{dif_spec.method} = {id_reach} {dif_spec.start_rk} {dif_spec.end_rk} {dif_spec.dif}" +
+                f"{dif_spec.b} {dif_spec.c}\n")
+
+    def _export_NUM(self, study, repertory, qlog=None, name="0"):
+
+        dict_names = {"init_time":"start_date",
+                      "final_time":"end_date",
+                      "timestep":"dt0",
+                      "implicitation_parameter":"theta",
+                      "timestep_screen":"dtscr",
+                      "timestep_bin":"dtbin",
+                      "timestep_csv":"dtcsv",
+                      "timestep_mage":"dtMage",
+                      "initial_concentration":"c_initiale"}
+        files = []
+
+        if qlog is not None:
+            qlog.put("Export NUM file")
+
+        with adists_file_open(os.path.join(repertory, f"{name}.NUM"), "w+") as f:
+            files.append(f"{name}.NUM")
+
+            params = study.river.get_params(self.type).parameters
+            for p in params:
+                name = p.name\
+                        .replace("all_", "")\
+                        .replace("adists_", "")
+                value = p.value
+
+                logger.debug(
+                    f"export: NUM: {name}: {value} ({p.value})"
+                )
+
+                if name != "command_line_arguments":
+                    f.write(f"{dict_names[name]} = {value}\n")
+
+            outputrks = study.river.Output_rk_adists.OutputRK_List
+
+            for outputrk in outputrks:
+                self._export_outputrk(study, outputrk, f, qlog)
+
+        return files
+
+    def _export_outputrk(self, study, outputrk, f, qlog, name="0"):
+        if (outputrk.reach is None) or (outputrk.rk is None) or (outputrk.title is None):
+            return
+
+        edges = study.river.enable_edges()
+
+        id_edges = list(map(lambda x: x.id, edges))
+
+        id_reach = outputrk.reach
+        rk = outputrk.rk
+        title = outputrk.title
+
+        if id_reach not in id_edges:
+            return
+
+        f.write(f"output = {id_reach} {rk} {title}\n")
+
+    @timer
+    def read_bin(self, study, repertory, results, qlog=None, name="0"):
+        repertory_results = os.path.join(repertory, "resultats")
+
+        files_bin_names = [el.split("/")[-1] for el in glob.glob(repertory_results+"/*.bin")]
+        print("files names resultats: ", files_bin_names)
+
+        ifilename = os.path.join(repertory_results, files_bin_names[0])
+
+        logger.info(f"read_bin: Start reading '{ifilename}' ...")
+
+        print("reading ", ifilename)
+        with open(ifilename, 'rb') as f:
+            # header
+            # first line
+            data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+            data = np.fromfile(f, dtype=np.int32, count=3)
+            ibmax = data[0]  # number of reaches
+            ismax = data[1]  # total number of cross sections
+            kbl = data[2] * -1  # block size for .BIN header
+            data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+            # second line
+            data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+            ibu = np.fromfile(f, dtype=np.int32, count=ibmax)
+            data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+            # third line
+            data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+            data = np.fromfile(f, dtype=np.int32, count=2 * ibmax)
+            is1 = np.zeros(ibmax, dtype=np.int32)
+            is2 = np.zeros(ibmax, dtype=np.int32)
+            print("nombre de biefs : ", ibmax)
+
+            logger.debug(f"read_bin: nb_reach = {ibmax}")
+            logger.debug(f"read_bin: nb_profile = {ismax}")
+
+            results.set("nb_reach", f"{ibmax}")
+            results.set("nb_profile", f"{ismax}")
+
+            reachs = []
+            iprofiles = {}
+            reach_offset = {}
+
+            for i in range(ibmax):
+                # Add results reach to reach list
+                r = results.river.add(i)
+                reachs.append(r)
+
+                is1[i] = data[2 * i] - 1 # first section of reach i (FORTRAN numbering)
+                is2[i] = data[2 * i + 1] - 1  # last section of reach i (FORTRAN numbering)
+
+                key = (is1[i], is2[i])
+                iprofiles[key] = r
+
+                reach_offset[r] = is1[i]
+
+            logger.debug(f"read_bin: iprofiles = {iprofiles}")
+
+            data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+            # fourth line
+            pk = np.zeros(ismax, dtype=np.float32)
+            for k in range(0, ismax, kbl):
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                pk[k:min(k + kbl, ismax)] = np.fromfile(f, dtype=np.float32, count=min(k + kbl, ismax) - k)
+                print("pk : ", pk)
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+
+                # fifth line (useless)
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                zmin_OLD = np.fromfile(f, dtype=np.float32, count=1)[0]
+                print("zmin_OLD : ", zmin_OLD)
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                # sixth line
+                zf = np.zeros(ismax, dtype=np.float32)
+                z = np.zeros(ismax * 3, dtype=np.float32)
+                for k in range(0, ismax, kbl):
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                    z[3 * k:3 * min(k + kbl, ismax)] = np.fromfile(f, dtype=np.float32,
+                                                                   count=3 * (min(k + kbl, ismax) - k))
+                    # z[i*3+1] and z[i*3+2] are useless
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                zf = [z[i * 3] for i in range(ismax)]
+                print("zf : ", zf)
+                # seventh line (useless)
+                for k in range(0, ismax, kbl):
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                    zero = np.fromfile(f, dtype=np.int32, count=ismax)
+                    print("zero : ", zero)
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                # end header
+
+        def ip_to_r(i): return iprofiles[
+            next(
+                filter(
+                    lambda k: k[0] <= i <= k[1],
+                    iprofiles
+                )
+            )
+        ]
+
+        def ip_to_ri(r, i): return i - reach_offset[r]
+
+        path_files = map(lambda file: os.path.join(repertory_results, file), files_bin_names)
+
+        data_tmp = {}
+
+        for file_bin in path_files:
+            key_pol = file_bin.split("/")[-1][0:-4]
+            data_tmp[key_pol] = {}
+            with open(file_bin, 'rb') as f:
+                # header
+                # first line
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                data = np.fromfile(f, dtype=np.int32, count=3)
+                ibmax = data[0]  # number of reaches
+                ismax = data[1]  # total number of cross sections
+                kbl = data[2] * -1  # block size for .BIN header
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                # second line
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                ibu = np.fromfile(f, dtype=np.int32, count=ibmax)
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                # third line
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                data = np.fromfile(f, dtype=np.int32, count=2 * ibmax)
+                is1 = np.zeros(ibmax, dtype=np.int32)
+                is2 = np.zeros(ibmax, dtype=np.int32)
+                for i in range(ibmax):
+                    is1[i] = data[2 * i]  # first section of reach i (FORTRAN numbering)
+                    is2[i] = data[2 * i + 1]  # last section of reach i (FORTRAN numbering)
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                # fourth line
+                pk = np.zeros(ismax, dtype=np.float32)
+                for k in range(0, ismax, kbl):
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                    pk[k:min(k + kbl, ismax)] = np.fromfile(f, dtype=np.float32, count=min(k + kbl, ismax) - k)
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                # fifth line (useless)
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                zmin_OLD = np.fromfile(f, dtype=np.float32, count=1)[0]
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                # sixth line
+                zf = np.zeros(ismax, dtype=np.float32)
+                z = np.zeros(ismax * 3, dtype=np.float32)
+                for k in range(0, ismax, kbl):
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                    z[3 * k:3 * min(k + kbl, ismax)] = np.fromfile(f, dtype=np.float32,
+                                                                   count=3 * (min(k + kbl, ismax) - k))
+                    # z[i*3+1] and z[i*3+2] are useless
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                zf = [z[i * 3] for i in range(ismax)]
+                # seventh line (useless)
+                for k in range(0, ismax, kbl):
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                    zero = np.fromfile(f, dtype=np.int32, count=ismax)
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                # end header
+                # data
+                data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                while data.size > 0:
+                    ismax = np.fromfile(f, dtype=np.int32, count=1)[0]
+                    t = np.fromfile(f, dtype=np.float64, count=1)[0]
+                    if not t in data_tmp[key_pol]:
+                        data_tmp[key_pol][t] = {}
+                    c = np.fromfile(f, dtype=np.byte, count=1)
+                    # possible values :
+                    # sediment : C, G, M, D, L, N, R
+                    # polutant : C, G, M, D
+                    phys_var = bytearray(c).decode()
+                    data_tmp[key_pol][t][phys_var] = {}
+                    real_data = np.fromfile(f, dtype=np.float32, count=ismax)
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (end)
+                    data_tmp[key_pol][t][phys_var] = real_data
+                    data = np.fromfile(f, dtype=np.int32, count=1)  # line length (bytes) (start)
+                # end data
+
+        ###print("dta tmp AAA")
+        ###print("-----------")
+        ###print(data_tmp["AAA-silt"])
+
+        pollutants_keys = list(data_tmp.keys())
+        timestamps_keys = list(data_tmp[pollutants_keys[0]].keys())
+        phys_data_names = list(data_tmp[pollutants_keys[0]][timestamps_keys[0]].keys())
+
+        ###print("pol keys: ", pollutants_keys)
+        ###print("t keys: ", timestamps_keys)
+        ###print("phys var: ", phys_data_names)
+        #print("set timestamps keys: ", set(timestamps_keys))
+        #print("isma")
+        ###print("iprofiles: ", iprofiles)
+
+        pi_tmp = []
+        reach_tmp = []
+
+        for i in range(ismax-1):
+            #print("first i: ", i)
+            reach = ip_to_r(i)
+            reach_tmp.append(reach)
+            #print("reach i:", reach)
+            #print("second i: ", i)
+            p_i = ip_to_ri(reach, i)
+            pi_tmp.append(p_i)
+
+            for t_data in timestamps_keys:
+                pol_view = []
+                for pol in pollutants_keys:
+                    #print("pol results: ", type(list(data_tmp[pol][t_data].values())))
+                    pol_view.append(tuple( list(map(lambda data_el: data_el[p_i], list(data_tmp[pol][t_data].values()))) ))
+
+                reach.set(p_i, t_data, "pols", pol_view)
+
+        ###print("pi_tmp: ", pi_tmp)
+        ###print("pol view: ", pol_view)
+        ###print("reach from i: ", reach_tmp)
+        #print("pol view: ", pol_view)
+        #print("results: ", results)
+        results.set("timestamps", set(timestamps_keys))
+        #print("------------------------set timestamps results meta data: ", set(timestamps_keys))
+
+        ###print("debug profiles for draw:")
+        ###print("------------------------")
+        ###print(data_tmp["AAA-silt"][0.0]["C"][0])
+        ###print(data_tmp["AAA-silt"][600.0]["C"][0])
+        ###print(data_tmp["AAA-silt"][1205.7208829578194]["C"][0])
+        ###print("========================")
+
+    @timer
+    def results(self, study, repertory, qlog=None, name=None):
+        self._study = study
+        if name is None:
+            name = study.name.replace(" ", "_")
+
+        results = super(AdisTSlc, self).results(study, repertory, qlog, name=name)
+
+        return results
+
+    def export_func_dict(self):
+        return [
+            self._export_NUM,
+            self._export_DIF,
+            self._export_D90,
+            self._export_POLs,
+        ]
+
+    @timer
+    def export(self, study, repertory, mage_rep, qlog=None, name="0"):
+        print("cmd solver adistslc : ", self._cmd_solver)
+        self._study = study
+        name = study.name.replace(" ", "_")
+
+        # Generate files
+        files = []
+
+        try:
+            for func in self.export_func_dict():
+                files = files + func(study, repertory, qlog, name=name)
+
+            self.export_additional_files(study, repertory, qlog, name=name)
+            self._export_REP(study, repertory, mage_rep, files, qlog, name=name)
+
+            return True
+        except Exception as e:
+            logger.error(f"Failed to export study to {self._type}")
+            logger_exception(e)
+            return False
+
+
+
+
+
+
diff --git a/src/Solver/Mage.py b/src/Solver/Mage.py
index aa1bc521430e3d525f515ef7add939b1ef0b0c7b..fe0823d528843bf379e5770551c517cf0827c029 100644
--- a/src/Solver/Mage.py
+++ b/src/Solver/Mage.py
@@ -980,6 +980,7 @@ class Mage8(Mage):
 
                 # Add profile id correspondance to reach
                 key = (i1, i2)
+                print("mage keys reachs: ", key)
                 iprofiles[key] = r
 
                 # Profile ID offset
@@ -1012,6 +1013,8 @@ class Mage8(Mage):
             ]
             def ip_to_ri(r, i): return i - reach_offset[r]
 
+            print("mage iprofiles: ", iprofiles)
+
             ts = set()
             end = False
             while not end:
@@ -1047,6 +1050,7 @@ class Mage8(Mage):
 
             logger.debug(reachs[0].profiles[0]._data)
             results.set("timestamps", ts)
+            print("set timestamps mage: ", ts)
             logger.info(f"read_bin: ... end with {len(ts)} timestamp read")
 
     @timer
diff --git a/src/Solver/Solvers.py b/src/Solver/Solvers.py
index d0a8a9817fdfd94ffe475c09d230cdbc73a1387f..ca92af7baeefdd1306369a04860333567e31a368 100644
--- a/src/Solver/Solvers.py
+++ b/src/Solver/Solvers.py
@@ -22,6 +22,7 @@ from Solver.GenericSolver import GenericSolver
 from Solver.Mage import (
     Mage7, Mage8, MageFake7,
 )
+from Solver.AdisTS import AdisTSlc
 from Solver.RubarBE import Rubar3, RubarBE
 
 _translate = QCoreApplication.translate
@@ -31,6 +32,7 @@ solver_long_name = {
     # "mage7": "Mage v7",
     "mage8": "Mage v8",
     # "mage_fake7": "Mage fake v7",
+    "adistslc": "Adis-TS_LC",
     # "rubarbe": "RubarBE",
     # "rubar3": "Rubar3",
 }
@@ -40,6 +42,7 @@ solver_type_list = {
     # "mage7": Mage7,
     "mage8": Mage8,
     # "mage_fake7": MageFake7,
+    "adistslc": AdisTSlc,
     # "rubarbe": RubarBE,
     # "rubar3": Rubar3,
 }
diff --git a/src/View/BoundaryConditionsAdisTS/Edit/Plot.py b/src/View/BoundaryConditionsAdisTS/Edit/Plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..8ecc288d57e1e7e0848c79473645488e2f144679
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/Edit/Plot.py
@@ -0,0 +1,105 @@
+# Plot.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from datetime import datetime
+
+from tools import timer, trace
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class Plot(PamhyrPlot):
+    def __init__(self, mode="time", data=None,
+                 trad=None, canvas=None, toolbar=None,
+                 parent=None):
+        super(Plot, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=data,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self._table_headers = self._trad.get_dict("table_headers")
+
+        header = self.data.header
+
+        self.label_x = self._table_headers[header[0]]
+        self.label_y = self._table_headers[header[1]]
+
+        self._mode = mode
+        self._isometric_axis = False
+
+        self._auto_relim_update = True
+        self._autoscale_update = True
+
+    def custom_ticks(self):
+        if self.data.header[0] != "time":
+            return
+
+        self.set_ticks_time_formater()
+
+    @timer
+    def draw(self):
+        self.init_axes()
+
+        if len(self.data) == 0:
+            self._init = False
+            return
+
+        self.draw_data()
+        self.custom_ticks()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self):
+        # Plot data
+        x = list(map(lambda v: v[0], self.data.data))
+        y = list(map(lambda v: v[1], self.data.data))
+
+        self._line, = self.canvas.axes.plot(
+            x, y,
+            color=self.color_plot,
+            **self.plot_default_kargs
+        )
+
+    @timer
+    def update(self, ind=None):
+        if not self._init:
+            self.draw()
+            return
+
+        self.update_data()
+
+        self.update_idle()
+
+    def update_data(self):
+        x = list(map(lambda v: v[0], self.data.data))
+        y = list(map(lambda v: v[1], self.data.data))
+
+        self._line.set_data(x, y)
diff --git a/src/View/BoundaryConditionsAdisTS/Edit/Table.py b/src/View/BoundaryConditionsAdisTS/Edit/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..729f842052d33eb4db714f0be534b74083589a12
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/Edit/Table.py
@@ -0,0 +1,121 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+
+from datetime import date, time, datetime, timedelta
+
+from tools import (
+    trace, timer,
+    timestamp_to_old_pamhyr_date,
+    old_pamhyr_date_to_timestamp
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect, QTime, QDateTime,
+)
+
+from PyQt5.QtWidgets import (
+    QTableView, QAbstractItemView, QSpinBox,
+    QTimeEdit, QDateTimeEdit, QItemDelegate,
+)
+
+from View.BoundaryConditionsAdisTS.Edit.UndoCommand import (
+    AddCommand, DelCommand, SetDataCommand,
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class TableModel(PamhyrTableModel):
+    def data(self, index, role):
+        if role == Qt.TextAlignmentRole:
+            return Qt.AlignHCenter | Qt.AlignVCenter
+
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        value = QVariant()
+
+        if 0 <= column < 2:
+            v = self._data._data[row][column]
+            if self._data._types[column] == float:
+                value = f"{v:.4f}"
+            elif self._data.header[column] == "time":
+                if self._opt_data == "time":
+                    value = timestamp_to_old_pamhyr_date(int(v))
+                else:
+                    value = str(datetime.fromtimestamp(v))
+            else:
+                value = f"{v}"
+
+        return value
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            self._undo.push(
+                SetDataCommand(
+                    self._data, row, column, value
+                )
+            )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._data, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._data, rows
+            )
+        )
+
+        self.endRemoveRows()
+
diff --git a/src/View/BoundaryConditionsAdisTS/Edit/UndoCommand.py b/src/View/BoundaryConditionsAdisTS/Edit/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..a706207785bef44dc23e8c87dc3ec97cab0849f6
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/Edit/UndoCommand.py
@@ -0,0 +1,95 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS import BoundaryConditionAdisTS
+
+logger = logging.getLogger()
+
+
+class SetDataCommand(QUndoCommand):
+    def __init__(self, data, index, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._column = column
+        self._old = self._data._data[self._index][self._column]
+        _type = self._data._types[self._column]
+        self._new = _type(new_value)
+
+    def undo(self):
+        if self._column == 0:
+            self._data._data[self._index] = (self._old,self._data._data[self._index][1])
+        else:
+            self._data._data[self._index] = (self._data._data[self._index][0], self._old)
+
+    def redo(self):
+        if self._column == 0:
+            self._data._data[self._index] = (self._new,self._data._data[self._index][1])
+        else:
+            self._data._data[self._index] = (self._data._data[self._index][0], self._new)
+
+class AddCommand(QUndoCommand):
+    def __init__(self, data, index):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        del self._data._data[self._index]
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._data._data.insert(self._index, (self._data._types[0](0), self._data._types[1](0.0)))
+        else:
+            self._data._data.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, data, rows):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._rows = rows
+
+        self._bc = []
+        for row in rows:
+            self._bc.append((row, self._data._data[row]))
+        self._bc.sort()
+
+    def undo(self):
+        for row, el in self._bc:
+            self._data._data.insert(row, el)
+
+    def redo(self):
+        for row in self._rows:
+            del self._data._data[row]
+
+
+
+
diff --git a/src/View/BoundaryConditionsAdisTS/Edit/Window.py b/src/View/BoundaryConditionsAdisTS/Edit/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..43223b84dfbecae6f110d0a36ba1673102127439
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/Edit/Window.py
@@ -0,0 +1,206 @@
+ # Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import timer, trace
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+from View.Tools.PamhyrWidget import PamhyrWidget
+from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5 import QtCore
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel, QCoreApplication,
+    pyqtSlot, pyqtSignal,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QHeaderView, QDoubleSpinBox, QVBoxLayout,
+)
+
+from View.Tools.Plot.PamhyrCanvas import MplCanvas
+from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
+
+from View.BoundaryConditionsAdisTS.Edit.translate import BCETranslate
+from View.BoundaryConditionsAdisTS.Edit.Table import TableModel
+from View.BoundaryConditionsAdisTS.Edit.Plot import Plot
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+class EditBoundaryConditionWindow(PamhyrWindow):
+    _pamhyr_ui = "EditBoundaryConditionsAdisTS"
+    _pamhyr_name = "Edit Boundary Conditions AdisTS"
+
+    def __init__(self, data=None, study=None, config=None, parent=None):
+        self._data = data
+        trad = BCETranslate()
+
+        name = trad[self._pamhyr_name]
+
+        super(EditBoundaryConditionWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        if self._data is not None:
+            n = self._data.node
+            node_name = next(filter(lambda x: x.id == n, self._study.river._nodes)).name
+            name += (
+                f" - {study.name} " +
+                f"({node_name})"
+            )
+
+        self._hash_data.append(data)
+
+        self.setup_table()
+        self.setup_plot()
+        self.setup_connections()
+
+    def setup_table(self):
+        if self._data.type == "Concentration":
+            self._data.header = ["time", "concentration"]
+        else:
+            self._data.header = ["time", "rate"]
+
+        headers = {}
+        table_headers = self._trad.get_dict("table_headers")
+        for h in self._data.header:
+            headers[h] = table_headers[h]
+
+        self._delegate_time = PamhyrExTimeDelegate(
+            data=self._data,
+            mode=self._study.time_system,
+            parent=self
+        )
+
+        table = self.find(QTableView, "tableView")
+        self._table = TableModel(
+            table_view=table,
+            table_headers=headers,
+            editable_headers=self._data.header,
+            delegates={
+                # "time": self._delegate_time,
+            },
+            data=self._data,
+            undo=self._undo_stack,
+            opt_data=self._study.time_system
+        )
+
+        table.setModel(self._table)
+        table.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table.setAlternatingRowColors(True)
+
+    def setup_plot(self):
+        self.canvas = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas.setObjectName("canvas")
+        self.toolbar = PamhyrPlotToolbar(
+            self.canvas, self
+        )
+        self.verticalLayout.addWidget(self.toolbar)
+        self.verticalLayout.addWidget(self.canvas)
+
+        self.plot = Plot(
+            canvas=self.canvas,
+            data=self._data,
+            mode=self._study.time_system,
+            trad=self._trad,
+            toolbar=self.toolbar,
+        )
+        self.plot.draw()
+
+    def setup_connections(self):
+        self.find(QAction, "action_add").triggered.connect(self.add)
+        self.find(QAction, "action_del").triggered.connect(self.delete)
+
+        self._table.dataChanged.connect(self.update)
+
+    def update(self):
+        self.plot.update()
+
+    def index_selected_row(self):
+        table = self.find(QTableView, "tableView")
+        return table.selectionModel()\
+                    .selectedRows()[0]\
+                    .row()
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, "tableView")
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._data) == 0 or len(rows) == 0:
+            self._table.add(0)
+        else:
+            self._table.add(rows[0])
+
+        self.plot.update()
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            return
+
+        self._table.delete(rows)
+        self.plot.update()
+
+    def sort(self):
+        self._table.sort(False)
+        self.plot.update()
+
+    def _copy(self):
+        rows = self.index_selected_rows()
+
+        table = []
+        table.append(self._data.header)
+
+        data = self._data.data
+        for row in rows:
+            table.append(list(data[row]))
+
+        self.copyTableIntoClipboard(table)
+
+    def _undo(self):
+        self._table.undo()
+        self.plot.update()
+
+    def _redo(self):
+        self._table.redo()
+        self.plot.update()
diff --git a/src/View/BoundaryConditionsAdisTS/Edit/translate.py b/src/View/BoundaryConditionsAdisTS/Edit/translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..0375adbb084a37fb2e90149382e13ae6650c0b3d
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/Edit/translate.py
@@ -0,0 +1,41 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+from View.BoundaryCondition.translate import BCTranslate
+
+_translate = QCoreApplication.translate
+
+
+class BCETranslate(BCTranslate):
+    def __init__(self):
+        super(BCETranslate, self).__init__()
+
+        self._dict["Edit Boundary Conditions AdisTS"] = _translate(
+            "BoundaryConditionAdisTS", "Edit boundary conditions AdisTS"
+        )
+
+        self._sub_dict["table_headers"] = {
+            "time": self._dict["time"],
+            "date": self._dict["date"],
+            "rate": _translate("BoundaryConditionAdisTS", "Rate"),
+            "concentration": _translate("BoundaryConditionAdisTS", "Concentration"),
+        }
diff --git a/src/View/BoundaryConditionsAdisTS/Table.py b/src/View/BoundaryConditionsAdisTS/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a71d3cf364dae7bb557a74d1ef449125e94177d
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/Table.py
@@ -0,0 +1,206 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from Model.BoundaryCondition.BoundaryConditionTypes import (
+    NotDefined, PonctualContribution,
+    TimeOverZ, TimeOverDischarge, ZOverDischarge
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.BoundaryConditionsAdisTS.UndoCommand import (
+    SetNodeCommand, SetTypeCommand,
+    AddCommand, DelCommand,
+)
+from View.BoundaryCondition.translate import BC_types
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class ComboBoxDelegate(QItemDelegate):
+    def __init__(self, data=None, mode="type", tab="",
+                 trad=None, parent=None):
+        super(ComboBoxDelegate, self).__init__(parent)
+
+        self._data = data
+        self._mode = mode
+        self._tab = tab
+        self._trad = trad
+
+        self._long_types = {}
+        if self._trad is not None:
+            self._long_types = self._trad.get_dict("long_types")
+
+    def createEditor(self, parent, option, index):
+        self.editor = QComboBox(parent)
+
+        if self._mode == "type":
+            lst = [self._trad["not_associated"], "Concentration", "Rate"]
+            self.editor.addItems(
+                lst
+            )
+        else:
+            self.editor.addItems(
+                [self._trad["not_associated"]] +
+                self._data.nodes_names()
+            )
+
+        self.editor.setCurrentText(index.data(Qt.DisplayRole))
+        return self.editor
+
+    def setEditorData(self, editor, index):
+        value = index.data(Qt.DisplayRole)
+        self.editor.currentTextChanged.connect(self.currentItemChanged)
+
+    def setModelData(self, editor, model, index):
+        text = str(editor.currentText())
+        model.setData(index, text)
+        editor.close()
+        editor.deleteLater()
+
+    def updateEditorGeometry(self, editor, option, index):
+        r = QRect(option.rect)
+        if self.editor.windowFlags() & Qt.Popup:
+            if editor.parent() is not None:
+                r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
+        editor.setGeometry(r)
+
+    @pyqtSlot()
+    def currentItemChanged(self):
+        self.commitData.emit(self.sender())
+
+
+class TableModel(PamhyrTableModel):
+    def __init__(self, pollutant=None, bc_list =None, trad=None, **kwargs):
+        self._trad = trad
+        self._bc_list = bc_list
+        self._pollutant = pollutant
+
+        super(TableModel, self).__init__(trad=trad, **kwargs)
+
+    def _setup_lst(self):
+        self._lst = self._bc_list.lst
+
+    def rowCount(self, parent):
+        return len(self._lst)
+
+    def data(self, index, role):
+        if len(self._lst) != 0:
+            data = list(filter(lambda x: x.pollutant == self._pollutant,
+                                     self._lst))
+        else:
+            data = []
+
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] == "type":
+            n = data[row].type
+            if n is None or n == "":
+                return self._trad["not_associated"]
+            return n
+        elif self._headers[column] == "node":
+            n = data[row].node
+            if n is None:
+                return self._trad["not_associated"]
+            return next(filter(lambda x: x.id == n, self._data._nodes)).name
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] == "type":
+                self._undo.push(
+                    SetTypeCommand(
+                        self._lst, row, value
+                    )
+                )
+            elif self._headers[column] == "node":
+                self._undo.push(
+                    SetNodeCommand(
+                        self._lst, row, self._data.node(value)
+                    )
+                )
+                print(value, self._data.node(value).id)
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._pollutant, self._bc_list, self._lst, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._lst, rows
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
diff --git a/src/View/BoundaryConditionsAdisTS/UndoCommand.py b/src/View/BoundaryConditionsAdisTS/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..dd39d42a6b339b42b4f3813cf4072742c61cad49
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/UndoCommand.py
@@ -0,0 +1,99 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.BoundaryConditionsAdisTS.BoundaryConditionAdisTS import BoundaryConditionAdisTS
+from Model.BoundaryConditionsAdisTS.BoundaryConditionsAdisTSList import BoundaryConditionsAdisTSList
+
+class SetNodeCommand(QUndoCommand):
+    def __init__(self, bcs, index, node):
+        QUndoCommand.__init__(self)
+
+        self._bcs = bcs
+        self._index = index
+        self._old = self._bcs[self._index].node
+        self._new = node.id
+
+    def undo(self):
+        self._bcs[self._index].node = self._old
+
+    def redo(self):
+        self._bcs[self._index].node = self._new
+
+class SetTypeCommand(QUndoCommand):
+    def __init__(self, bcs, index, _type):
+        QUndoCommand.__init__(self)
+
+        self._bcs = bcs
+        self._index = index
+        self._type = _type
+        self._old = self._bcs[self._index].type
+        self._new = self._type
+
+    def undo(self):
+        self._bcs[self._index].type = self._old
+
+    def redo(self):
+        self._bcs[self._index].type = self._new
+
+class AddCommand(QUndoCommand):
+    def __init__(self, pollutant, bcs_list, bcs, index):
+        QUndoCommand.__init__(self)
+
+        self._bcs = bcs
+        self._bc_list = bcs_list
+        self._pollutant = pollutant
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        del self._bcs[self._index]
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._bc_list.new(self._index, self._pollutant)
+        else:
+            self._bcs.insert(self._index, self._new)
+
+
+class DelCommand(QUndoCommand):
+    def __init__(self, bcs, rows):
+        QUndoCommand.__init__(self)
+
+        self._bcs = bcs
+        self._rows = rows
+
+        self._bc = []
+        for row in rows:
+            self._bc.append((row, self._bcs[row]))
+        self._bc.sort()
+
+    def undo(self):
+        for row, el in self._bc:
+            self._bcs.insert(row, el)
+
+    def redo(self):
+        for row in self._rows:
+            del self._bcs[row]
+
diff --git a/src/View/BoundaryConditionsAdisTS/Window.py b/src/View/BoundaryConditionsAdisTS/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..e055c58b5fb9e7336121a1aa02f3c23bb180bbd2
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/Window.py
@@ -0,0 +1,195 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+    QWidget,
+)
+
+from View.BoundaryConditionsAdisTS.Table import (
+    TableModel, ComboBoxDelegate
+)
+
+from View.Network.GraphWidget import GraphWidget
+from View.BoundaryConditionsAdisTS.translate import BCAdisTSTranslate
+from View.BoundaryConditionsAdisTS.Edit.Window import EditBoundaryConditionWindow
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class BoundaryConditionAdisTSWindow(PamhyrWindow):
+    _pamhyr_ui = "BoundaryConditionsAdisTS"
+    _pamhyr_name = "Boundary conditions AdisTS"
+
+    def __init__(self, pollutant=None, study=None, config=None, parent=None):
+        trad = BCAdisTSTranslate()
+        name = (
+            trad[self._pamhyr_name] +
+            " - " + study.name
+        )
+
+        self._pollutant = pollutant
+
+        super(BoundaryConditionAdisTSWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        self._bcs = self._study.river.boundary_conditions_adists
+
+        self.setup_table()
+        self.setup_graph()
+        self.setup_connections()
+
+        self.ui.setWindowTitle(self._title)
+
+    def setup_table(self):
+        self._delegate_type = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            mode="type",
+            parent=self
+        )
+        self._delegate_node = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            mode="node",
+            parent=self
+        )
+
+        table = self.find(QTableView, f"tableView")
+        self._table = TableModel(
+            table_view=table,
+            table_headers=self._trad.get_dict("table_headers"),
+            editable_headers=["type", "node"],
+            delegates={
+                "type": self._delegate_type,
+                "node": self._delegate_node,
+            },
+            trad=self._trad,
+            bc_list = self._study.river.boundary_conditions_adists,
+            undo=self._undo_stack,
+            pollutant=self._pollutant,
+            data=self._study.river
+        )
+        table.setModel(self._table)
+        table.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table.setAlternatingRowColors(True)
+
+    def setup_graph(self):
+        self.graph_widget = GraphWidget(
+            self._study.river,
+            min_size=None, size=(200, 200),
+            only_display=True,
+            parent=self
+        )
+        self.graph_layout = self.find(QVBoxLayout, "verticalLayout")
+        self.graph_layout.addWidget(self.graph_widget)
+
+    def setup_connections(self):
+        self.find(QAction, "action_add").triggered.connect(self.add)
+        self.find(QAction, "action_del").triggered.connect(self.delete)
+        self.find(QAction, "action_edit").triggered.connect(self.edit)
+
+    def index_selected_row(self):
+        tab = "liquid"
+        table = self.find(QTableView, f"tableView_{tab}")
+        return table.selectionModel()\
+                    .selectedRows()[0]\
+                    .row()
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, f"tableView")
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._bcs) == 0 or len(rows) == 0:
+            self._table.add(0)
+        else:
+            self._table.add(rows[0])
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            return
+
+        self._table.delete(rows)
+
+    def _copy(self):
+        logger.info("TODO: copy")
+
+    def _paste(self):
+        logger.info("TODO: paste")
+
+    def _undo(self):
+        self._table.undo()
+
+    def _redo(self):
+        self._table.redo()
+
+    def edit(self):
+        rows = self.index_selected_rows()
+        for row in rows:
+            data = self._bcs.lst[row]
+
+            if self.sub_window_exists(
+                EditBoundaryConditionWindow,
+                data=[self._study, None, data]
+            ):
+                continue
+
+            win = EditBoundaryConditionWindow(
+                data=data,
+                study=self._study,
+                parent=self
+            )
+            win.show()
diff --git a/src/View/BoundaryConditionsAdisTS/translate.py b/src/View/BoundaryConditionsAdisTS/translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0974318ace4179212b19ff9e477ad89454d1936
--- /dev/null
+++ b/src/View/BoundaryConditionsAdisTS/translate.py
@@ -0,0 +1,36 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+class BCAdisTSTranslate(MainTranslate):
+    def __init__(self):
+        super(BCAdisTSTranslate, self).__init__()
+
+        self._dict["Boundary conditions AdisTS"] = _translate(
+            "BoundaryConditionsAdisTS", "Boundary conditions AdisTS"
+        )
+
+        self._sub_dict["table_headers"] = {
+            "type": self._dict["type"],
+            "node": _translate("BoundaryCondition", "Node")
+        }
diff --git a/src/View/CheckList/WindowAdisTS.py b/src/View/CheckList/WindowAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..40bba4b4dcbb16983b6621a5a2991bd99befde69
--- /dev/null
+++ b/src/View/CheckList/WindowAdisTS.py
@@ -0,0 +1,200 @@
+# WindowAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from tools import trace, timer
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, QRect, QThread,
+    pyqtSlot, pyqtSignal,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+    QProgressBar, QLabel,
+)
+
+from View.CheckList.Table import TableModel
+from View.CheckList.Worker import Worker
+from View.CheckList.Translate import CheckListTranslate
+
+_translate = QCoreApplication.translate
+
+
+class CheckListWindowAdisTS(PamhyrWindow):
+    _pamhyr_ui = "CheckList"
+    _pamhyr_name = "Check list"
+
+    signalStatus = pyqtSignal(str)
+
+    def __init__(self, autorun: bool = True,
+                 study=None, config=None,
+                 solver=None, parent=None, mage_rep=None):
+        trad = CheckListTranslate()
+
+        self._autorun = autorun
+        self._solver = solver
+        self._mage_rep = mage_rep
+
+        name = trad[self._pamhyr_name] + " - " + study.name
+
+        super(CheckListWindowAdisTS, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            options=[],
+            parent=parent
+        )
+
+        # Add solver to hash computation data
+        self._hash_data.append(self._solver)
+
+        self._checker_list = (
+            self._study.checkers() +
+            self._solver.checkers()
+        )
+
+        self.setup_table()
+        self.setup_progress_bar()
+        self.setup_connections()
+        self.setup_thread()
+        self.setup_statusbar()
+
+    def setup_table(self):
+        table = self.find(QTableView, f"tableView")
+        self._table = TableModel(
+            table_view=table,
+            table_headers=self._trad.get_dict("table_headers"),
+            data=self._checker_list,
+        )
+
+    def setup_progress_bar(self):
+        self._progress = self.find(QProgressBar, f"progressBar")
+        self._p = 0             # Progress current step
+
+        self._progress.setRange(0, len(self._checker_list))
+        self._progress.setValue(self._p)
+
+    def setup_connections(self):
+        self.find(QPushButton, "pushButton_ok").clicked.connect(self.accept)
+        self.find(QPushButton, "pushButton_retry").clicked.connect(self.retry)
+        self.find(QPushButton, "pushButton_cancel")\
+            .clicked.connect(self.reject)
+
+    def setup_thread(self):
+        self._worker = Worker(self._study, self._checker_list)
+        self._worker_thread = QThread()
+        self._worker.moveToThread(self._worker_thread)
+
+        # Connect any worker signals
+        self._worker.signalStatus.connect(self.update)
+        self._worker_thread.started.connect(self._worker.process)
+
+        self._worker_thread.start()
+
+    def retry(self):
+        self._worker_thread.terminate()
+        self._worker_thread.wait()
+
+        self.find(QPushButton, "pushButton_retry").setEnabled(False)
+        self.find(QPushButton, "pushButton_ok").setEnabled(False)
+
+        self.setup_thread()
+
+    def _compute_status(self):
+        ok = len(list(filter(lambda c: c.is_ok(), self._checker_list)))
+        warning = len(
+            list(filter(lambda c: c.is_warning(), self._checker_list)))
+        error = len(list(filter(lambda c: c.is_error(), self._checker_list)))
+
+        return ok, warning, error
+
+    def _compute_status_label(self):
+        ok, warning, error = self._compute_status()
+        return (f"<font color=\"Green\">Ok: {ok} </font> |" +
+                f"<font color=\"Orange\">Warning: {warning} </font> |" +
+                f"<font color=\"Red\">Error: {error}</font>")
+
+    def setup_statusbar(self):
+        txt = self._compute_status_label()
+        self._status_label = QLabel(txt)
+        self.statusbar.addPermanentWidget(self._status_label)
+
+    def update_statusbar(self):
+        txt = self._compute_status_label()
+        self._status_label.setText(txt)
+
+    def progress(self):
+        self._p += 1
+        self._progress.setValue(self._p)
+        self._table.update()
+
+    def start_compute(self):
+        self._p = 0
+        self._progress.setValue(self._p)
+
+    def info_compute(self, str):
+        self.statusbar.showMessage(str, 3000)
+
+    def end_compute(self):
+        self._table.layoutChanged.emit()
+        self.find(QPushButton, "pushButton_retry").setEnabled(True)
+
+        errors = any(filter(lambda c: c.is_error(), self._checker_list))
+        if not errors:
+            self.find(QPushButton, "pushButton_ok").setEnabled(True)
+            if self._autorun:
+                self._parent.solver_log_adists(self._solver, self._mage_rep)
+                self.end()
+
+        self.update_statusbar()
+
+    def update(self, key: str):
+        if key == "start":
+            self.start_compute()
+            self.info_compute("Starting ...")
+        elif key == "end":
+            self.info_compute("Finish")
+            self.end_compute()
+        elif key == "progress":
+            self.progress()
+        else:
+            self.info_compute(key)
+
+        self.update_statusbar()
+
+    def end(self):
+        # self._worker.join()b
+        self.close()
+
+    def reject(self):
+        self.end()
+
+    def accept(self):
+        self._parent.solver_log_adists(self._solver, self._mage_rep)
+        # self.end()
diff --git a/src/View/D90AdisTS/Table.py b/src/View/D90AdisTS/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5bfbddbe56826c8fba10f97fd3bbfa6a7ba1829
--- /dev/null
+++ b/src/View/D90AdisTS/Table.py
@@ -0,0 +1,217 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.D90AdisTS.UndoCommand import (
+    SetCommand, AddCommand, SetCommandSpec,
+    DelCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class ComboBoxDelegate(QItemDelegate):
+    def __init__(self, data=None, ic_spec_lst=None, trad=None, parent=None, mode="reaches"):
+        super(ComboBoxDelegate, self).__init__(parent)
+
+        self._data = data
+        self._mode = mode
+        self._trad = trad
+        self._ic_spec_lst = ic_spec_lst
+
+    def createEditor(self, parent, option, index):
+        self.editor = QComboBox(parent)
+
+        val = []
+        if self._mode == "rk":
+            reach_id = self._ic_spec_lst[index.row()].reach
+
+            reach = next(filter(lambda edge: edge.id == reach_id, self._data.edges()))
+
+            if reach_id is not None:
+                val = list(
+                    map(
+                        lambda rk: str(rk), reach.reach.get_rk()
+                    )
+                )
+        else:
+            val = list(
+                map(
+                    lambda n: n.name, self._data.edges()
+                )
+            )
+
+        self.editor.addItems(
+            [self._trad['not_associated']] +
+            val
+        )
+
+        self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
+        return self.editor
+
+    def setEditorData(self, editor, index):
+        value = index.data(Qt.DisplayRole)
+        self.editor.currentTextChanged.connect(self.currentItemChanged)
+
+    def setModelData(self, editor, model, index):
+        text = str(editor.currentText())
+        model.setData(index, text)
+        editor.close()
+        editor.deleteLater()
+
+    def updateEditorGeometry(self, editor, option, index):
+        r = QRect(option.rect)
+        if self.editor.windowFlags() & Qt.Popup:
+            if editor.parent() is not None:
+                r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
+        editor.setGeometry(r)
+
+    @pyqtSlot()
+    def currentItemChanged(self):
+        self.commitData.emit(self.sender())
+
+
+class D90TableModel(PamhyrTableModel):
+    def __init__(self, river=None, data=None, **kwargs):
+        self._river = river
+
+        super(D90TableModel, self).__init__(data=data, **kwargs)
+
+        self._data = data
+
+    def _setup_lst(self):
+        self._lst = self._data._data
+
+    def rowCount(self, parent):
+        return len(self._lst)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] is "name":
+            n = self._lst[row].name
+            if n is None or n == "":
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "reach":
+            n = self._lst[row].reach
+            if n is None:
+                return self._trad['not_associated']
+            return next(filter(lambda edge: edge.id == n, self._river.edges())).name
+        elif self._headers[column] is "start_rk":
+            n = self._lst[row].start_rk
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "end_rk":
+            n = self._lst[row].end_rk
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "d90":
+            n = self._lst[row].d90
+            if n is None:
+                return self._trad['not_associated']
+            return n
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] != "reach":
+                self._undo.push(
+                    SetCommandSpec(
+                        self._lst, row, self._headers[column], value
+                    )
+                )
+            elif self._headers[column] == "reach":
+                print(self._river.edge(value).id)
+                self._undo.push(
+                    SetCommandSpec(
+                        self._lst, row, self._headers[column], self._river.edge(value).id
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._data, self._lst, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._data, self._lst, rows
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
+
diff --git a/src/View/D90AdisTS/TableDefault.py b/src/View/D90AdisTS/TableDefault.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3231d424916abef2ccb4c1790739737e44e5989
--- /dev/null
+++ b/src/View/D90AdisTS/TableDefault.py
@@ -0,0 +1,95 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.D90AdisTS.UndoCommand import (
+    SetCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+class D90TableDefaultModel(PamhyrTableModel):
+    def __init__(self, **kwargs):
+        super(D90TableDefaultModel, self).__init__(**kwargs)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] is "name":
+            return self._data[row].name
+        elif self._headers[column] is "d90":
+            n = self._data[row].d90
+            if n is None:
+                return self._trad['not_associated']
+            return n
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] is not None:
+                self._undo.push(
+                    SetCommand(
+                        self._data, row, self._headers[column], value
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
+
diff --git a/src/View/D90AdisTS/UndoCommand.py b/src/View/D90AdisTS/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..a59d69e74f0e879f20945dbd7890a3e9c154740a
--- /dev/null
+++ b/src/View/D90AdisTS/UndoCommand.py
@@ -0,0 +1,150 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.D90AdisTS.D90AdisTS import D90AdisTS
+from Model.D90AdisTS.D90AdisTSList import D90AdisTSList
+
+
+class SetCommand(QUndoCommand):
+    def __init__(self, data, row, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._row = row
+        self._column = column
+
+        if self._column == "name":
+            self._old = self._data[self._row].name
+        elif self._column == "d90":
+            self._old = self._data[self._row].d90
+
+        _type = float
+        if column == "name":
+            _type = str
+
+        self._new = _type(new_value)
+
+    def undo(self):
+        if self._column == "name":
+            self._data[self._row].name = self._old
+        elif self._column == "d90":
+            self._data[self._row].d90 = self._old
+
+    def redo(self):
+        if self._column == "name":
+            self._data[self._row].name = self._new
+        elif self._column == "d90":
+            self._data[self._row].d90 = self._new
+
+class SetCommandSpec(QUndoCommand):
+    def __init__(self, data, row, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._row = row
+        self._column = column
+
+        if self._column == "name":
+            self._old = self._data[self._row].name
+        elif self._column == "reach":
+            self._old = self._data[self._row].reach
+        elif self._column == "start_rk":
+            self._old = self._data[self._row].start_rk
+        elif self._column == "end_rk":
+            self._old = self._data[self._row].end_rk
+        elif self._column == "d90":
+            self._old = self._data[self._row].d90
+
+        _type = float
+        if column == "name":
+            _type = str
+        elif column == "reach":
+            _type = int
+
+        self._new = _type(new_value)
+
+    def undo(self):
+        if self._column == "name":
+            self._data[self._row].name = self._old
+        elif self._column == "reach":
+            self._data[self._row].reach = self._old
+        elif self._column == "start_rk":
+            self._data[self._row].start_rk = self._old
+        elif self._column == "end_rk":
+            self._data[self._row].end_rk = self._old
+        elif self._column == "d90":
+            self._data[self._row].d90 = self._old
+
+    def redo(self):
+        if self._column == "name":
+            self._data[self._row].name = self._new
+        elif self._column == "reach":
+            self._data[self._row].reach = self._new
+        elif self._column == "start_rk":
+            self._data[self._row].start_rk = self._new
+        elif self._column == "end_rk":
+            self._data[self._row].end_rk = self._new
+        elif self._column == "d90":
+            self._data[self._row].d90 = self._new
+
+class AddCommand(QUndoCommand):
+    def __init__(self, data, ics_spec, index):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._ics_spec = ics_spec
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        self._data.delete_i([self._index])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._data.new(self._index)
+        else:
+            self._data.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, data, ics_spec, rows):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._ics_spec = ics_spec
+        self._rows = rows
+        #self._data = data
+
+        self._ic = []
+        for row in rows:
+            self._ic.append((row, self._ics_spec[row]))
+        self._ic.sort()
+
+    def undo(self):
+        for row, el in self._ic:
+            self._data.insert(row, el)
+
+    def redo(self):
+        self._data.delete_i(self._rows)
+
diff --git a/src/View/D90AdisTS/Window.py b/src/View/D90AdisTS/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..b18895b8ef9231402098adc4bfe9fa64bbd6d311
--- /dev/null
+++ b/src/View/D90AdisTS/Window.py
@@ -0,0 +1,283 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import os
+import logging
+
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+    QKeySequence, QIcon,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect, QItemSelectionModel,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+    QVBoxLayout, QToolBar, QAction, QToolButton,
+)
+
+from Modules import Modules
+
+from View.InitialConditionsAdisTS.UndoCommand import (
+    SetCommand,
+)
+
+from View.D90AdisTS.TableDefault import (
+    D90TableDefaultModel,
+)
+
+from View.D90AdisTS.Table import (
+    D90TableModel, ComboBoxDelegate,
+)
+
+from View.D90AdisTS.translate import D90AdisTSTranslate
+
+from Solver.Mage import Mage8
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class D90AdisTSWindow(PamhyrWindow):
+    _pamhyr_ui = "D90AdisTS"
+    _pamhyr_name = "D90 AdisTS"
+
+    def __init__(self, data=None, study=None, config=None, parent=None):
+        self._data = []
+        self._data.append(data)
+        trad = D90AdisTSTranslate()
+
+        name = (
+            trad[self._pamhyr_name] +
+            " - " + study.name +
+            " - " + self._data[0].name
+        )
+
+        super(D90AdisTSWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        self._hash_data.append(data)
+
+        self._d90_adists_lst = study.river.d90_adists
+
+        self.setup_table()
+
+        self.ui.setWindowTitle(self._title)
+
+    def setup_table(self):
+
+        path_icons = os.path.join(self._get_ui_directory(), f"ressources")
+
+        table_default = self.find(QTableView, f"tableView")
+
+        self._table = D90TableDefaultModel(
+            table_view=table_default,
+            table_headers=self._trad.get_dict("table_headers"),
+            editable_headers=["name", "d90"],
+            delegates={},
+            data=self._data,
+            undo=self._undo_stack,
+            trad=self._trad
+        )
+
+        table_default.setModel(self._table)
+        table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table_default.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table_default.setAlternatingRowColors(True)
+
+        layout = self.find(QVBoxLayout, f"verticalLayout_1")
+        toolBar = QToolBar()
+        layout.addWidget(toolBar)
+
+        action_add = QAction(self)
+        action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
+        action_add.triggered.connect(self.add)
+        action_delete = QAction(self)
+        action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
+        action_delete.triggered.connect(self.delete)
+
+        toolBar.addAction(action_add)
+        toolBar.addAction(action_delete)
+
+        self.table_spec = QTableView()
+        layout.addWidget(self.table_spec)
+
+        self._delegate_reach = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            ic_spec_lst=self._data[0]._data,
+            parent=self,
+            mode="reaches"
+        )
+        self._delegate_rk = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            ic_spec_lst=self._data[0]._data,
+            parent=self,
+            mode="rk"
+        )
+
+        self._table_spec = D90TableModel(
+            table_view=self.table_spec,
+            table_headers=self._trad.get_dict("table_headers_spec"),
+            editable_headers=["name", "reach", "start_rk", "end_rk", "d90"],
+            delegates={
+                "reach": self._delegate_reach,
+                "start_rk": self._delegate_rk,
+                "end_rk": self._delegate_rk
+            },
+            data=self._data[0],
+            undo=self._undo_stack,
+            trad=self._trad,
+            river=self._study.river
+        )
+
+        self.table_spec.setModel(self._table_spec)
+        self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.table_spec.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        self.table_spec.setAlternatingRowColors(True)
+
+        selectionModel = self.table_spec.selectionModel()
+        index = self.table_spec.model().index(0, 0)
+
+        selectionModel.select(
+            index,
+            QItemSelectionModel.Rows |
+            QItemSelectionModel.ClearAndSelect |
+            QItemSelectionModel.Select
+        )
+        self.table_spec.scrollTo(index)
+
+    def index_selected_row(self):
+        #table = self.find(QTableView, f"tableView")
+        table = self.table_spec
+        rows = table.selectionModel()\
+                    .selectedRows()
+
+        if len(rows) == 0:
+            return 0
+
+        return rows[0].row()
+
+    def index_selected_rows(self):
+        #table = self.find(QTableView, f"tableView")
+        table = self.table_spec
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def move_up(self):
+        row = self.index_selected_row()
+        self._table.move_up(row)
+        self._update()
+
+    def move_down(self):
+        row = self.index_selected_row()
+        self._table.move_down(row)
+        self._update()
+
+    def _copy(self):
+        rows = list(
+            map(
+                lambda row: row.row(),
+                self.tableView.selectionModel().selectedRows()
+            )
+        )
+
+        table = list(
+            map(
+                lambda eic: list(
+                    map(
+                        lambda k: eic[1][k],
+                        ["rk", "discharge", "elevation"]
+                    )
+                ),
+                filter(
+                    lambda eic: eic[0] in rows,
+                    enumerate(self._ics.lst())
+                )
+            )
+        )
+
+        self.copyTableIntoClipboard(table)
+
+    def _paste(self):
+        header, data = self.parseClipboardTable()
+
+        if len(data) + len(header) == 0:
+            return
+
+        logger.debug(
+            "D90: Paste: " +
+            f"header = {header}, " +
+            f"data = {data}"
+        )
+
+        try:
+            row = self.index_selected_row()
+            # self._table.paste(row, header, data)
+            self._table.paste(row, [], data)
+        except Exception as e:
+            logger_exception(e)
+
+        self._update()
+
+    def _undo(self):
+        self._table.undo()
+        self._update()
+
+    def _redo(self):
+        self._table.redo()
+        self._update()
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._data[0]._data) == 0 or len(rows) == 0:
+            self._table_spec.add(0)
+        else:
+            self._table_spec.add(rows[0])
+
+    def delete(self):
+        print("del")
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            print("len 0")
+            return
+        self._table_spec.delete(rows)
diff --git a/src/View/D90AdisTS/translate.py b/src/View/D90AdisTS/translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..3203ee100205ac0cee402bf2a66046e91d5bea3d
--- /dev/null
+++ b/src/View/D90AdisTS/translate.py
@@ -0,0 +1,46 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+
+class D90AdisTSTranslate(MainTranslate):
+    def __init__(self):
+        super(D90AdisTSTranslate, self).__init__()
+
+        self._dict["D90 AdisTS"] = _translate(
+            "D90AdisTS", "D90 AdisTS")
+
+        self._dict["rk"] = self._dict["unit_rk"]
+
+        self._sub_dict["table_headers"] = {
+            "name": self._dict["name"],
+            "d90": _translate("Unit", "D90"),
+        }
+
+        self._sub_dict["table_headers_spec"] = {
+            "name": self._dict["name"],
+            "reach": self._dict["reach"],
+            "start_rk": _translate("Unit", "Start_RK (m)"),
+            "end_rk": _translate("Unit", "End_RK (m)"),
+            "d90": _translate("Unit", "D90"),
+        }
diff --git a/src/View/DIFAdisTS/Table.py b/src/View/DIFAdisTS/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..8d0dedbfb59a5a8eb65f3f40e94b3ed9f3c98c17
--- /dev/null
+++ b/src/View/DIFAdisTS/Table.py
@@ -0,0 +1,229 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.DIFAdisTS.UndoCommand import (
+    SetCommand, AddCommand, SetCommandSpec,
+    DelCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class ComboBoxDelegate(QItemDelegate):
+    def __init__(self, data=None, ic_spec_lst=None, trad=None, parent=None, mode="reaches"):
+        super(ComboBoxDelegate, self).__init__(parent)
+
+        self._data = data
+        self._mode = mode
+        self._trad = trad
+        self._ic_spec_lst = ic_spec_lst
+
+    def createEditor(self, parent, option, index):
+        self.editor = QComboBox(parent)
+
+        val = []
+        if self._mode == "rk":
+            reach_id = self._ic_spec_lst[index.row()].reach
+
+            reach = next(filter(lambda edge: edge.id == reach_id, self._data.edges()))
+
+            if reach_id is not None:
+                val = list(
+                    map(
+                        lambda rk: str(rk), reach.reach.get_rk()
+                    )
+                )
+        elif self._mode == "method":
+            val = self._data.dif_adists.lst[0].types
+        else:
+            val = list(
+                map(
+                    lambda n: n.name, self._data.edges()
+                )
+            )
+
+        self.editor.addItems(
+            [self._trad['not_associated']] +
+            val
+        )
+
+        self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
+        return self.editor
+
+    def setEditorData(self, editor, index):
+        value = index.data(Qt.DisplayRole)
+        self.editor.currentTextChanged.connect(self.currentItemChanged)
+
+    def setModelData(self, editor, model, index):
+        text = str(editor.currentText())
+        model.setData(index, text)
+        editor.close()
+        editor.deleteLater()
+
+    def updateEditorGeometry(self, editor, option, index):
+        r = QRect(option.rect)
+        if self.editor.windowFlags() & Qt.Popup:
+            if editor.parent() is not None:
+                r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
+        editor.setGeometry(r)
+
+    @pyqtSlot()
+    def currentItemChanged(self):
+        self.commitData.emit(self.sender())
+
+
+class DIFTableModel(PamhyrTableModel):
+    def __init__(self, river=None, data=None, **kwargs):
+        self._river = river
+
+        super(DIFTableModel, self).__init__(data=data, **kwargs)
+
+        self._data = data
+
+    def _setup_lst(self):
+        self._lst = self._data._data
+
+    def rowCount(self, parent):
+        return len(self._lst)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] is "method":
+            n = self._lst[row].method
+            if n is None or n == "":
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "reach":
+            n = self._lst[row].reach
+            if n is None:
+                return self._trad['not_associated']
+            return next(filter(lambda edge: edge.id == n, self._river.edges())).name
+        elif self._headers[column] is "start_rk":
+            n = self._lst[row].start_rk
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "end_rk":
+            n = self._lst[row].end_rk
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "dif":
+            n = self._lst[row].dif
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "b":
+            n = self._lst[row].b
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "c":
+            n = self._lst[row].c
+            if n is None:
+                return self._trad['not_associated']
+            return n
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] != "reach":
+                self._undo.push(
+                    SetCommandSpec(
+                        self._lst, row, self._headers[column], value
+                    )
+                )
+            elif self._headers[column] == "reach":
+                print(self._river.edge(value).id)
+                self._undo.push(
+                    SetCommandSpec(
+                        self._lst, row, self._headers[column], self._river.edge(value).id
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._data, self._lst, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._data, self._lst, rows
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
+
diff --git a/src/View/DIFAdisTS/TableDefault.py b/src/View/DIFAdisTS/TableDefault.py
new file mode 100644
index 0000000000000000000000000000000000000000..60d3f58784c2649350b724b1d560475778cb9473
--- /dev/null
+++ b/src/View/DIFAdisTS/TableDefault.py
@@ -0,0 +1,108 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.DIFAdisTS.UndoCommand import (
+    SetCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+class DIFTableDefaultModel(PamhyrTableModel):
+    def __init__(self, **kwargs):
+        super(DIFTableDefaultModel, self).__init__(**kwargs)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] is "method":
+            n = self._data[row].method
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "dif":
+            n = self._data[row].dif
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "b":
+            n = self._data[row].b
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "c":
+            n = self._data[row].c
+            if n is None:
+                return self._trad['not_associated']
+            return n
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] is not None:
+                self._undo.push(
+                    SetCommand(
+                        self._data, row, self._headers[column], value
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
+
diff --git a/src/View/DIFAdisTS/UndoCommand.py b/src/View/DIFAdisTS/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..08728b18bd185b8066e3af8f5f1b7eac94506ab6
--- /dev/null
+++ b/src/View/DIFAdisTS/UndoCommand.py
@@ -0,0 +1,174 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.DIFAdisTS.DIFAdisTS import DIFAdisTS
+from Model.DIFAdisTS.DIFAdisTSList import DIFAdisTSList
+
+
+class SetCommand(QUndoCommand):
+    def __init__(self, data, row, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._row = row
+        self._column = column
+
+        if self._column == "method":
+            self._old = self._data[self._row].method
+        elif self._column == "dif":
+            self._old = self._data[self._row].dif
+        elif self._column == "b":
+            self._old = self._data[self._row].b
+        elif self._column == "c":
+            self._old = self._data[self._row].c
+
+        _type = float
+        if column == "method":
+            _type = str
+
+        self._new = _type(new_value)
+
+    def undo(self):
+        if self._column == "method":
+            self._data[self._row].method = self._old
+        elif self._column == "dif":
+            self._data[self._row].dif = self._old
+        elif self._column == "b":
+            self._data[self._row].b = self._old
+        elif self._column == "c":
+            self._data[self._row].c = self._old
+
+    def redo(self):
+        if self._column == "method":
+            self._data[self._row].method = self._new
+        elif self._column == "dif":
+            self._data[self._row].dif = self._new
+        elif self._column == "b":
+            self._data[self._row].b = self._new
+        elif self._column == "c":
+            self._data[self._row].c = self._new
+
+class SetCommandSpec(QUndoCommand):
+    def __init__(self, data, row, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._row = row
+        self._column = column
+
+        if self._column == "method":
+            self._old = self._data[self._row].method
+        elif self._column == "reach":
+            self._old = self._data[self._row].reach
+        elif self._column == "start_rk":
+            self._old = self._data[self._row].start_rk
+        elif self._column == "end_rk":
+            self._old = self._data[self._row].end_rk
+        elif self._column == "dif":
+            self._old = self._data[self._row].dif
+        elif self._column == "b":
+            self._old = self._data[self._row].b
+        elif self._column == "c":
+            self._old = self._data[self._row].c
+
+        _type = float
+        if column == "method":
+            _type = str
+        elif column == "reach":
+            _type = int
+
+        self._new = _type(new_value)
+
+    def undo(self):
+        if self._column == "method":
+            self._data[self._row].method = self._old
+        elif self._column == "reach":
+            self._data[self._row].reach = self._old
+        elif self._column == "start_rk":
+            self._data[self._row].start_rk = self._old
+        elif self._column == "end_rk":
+            self._data[self._row].end_rk = self._old
+        elif self._column == "dif":
+            self._data[self._row].dif = self._old
+        elif self._column == "b":
+            self._data[self._row].b = self._old
+        elif self._column == "c":
+            self._data[self._row].c = self._old
+
+    def redo(self):
+        if self._column == "method":
+            self._data[self._row].method = self._new
+        elif self._column == "reach":
+            self._data[self._row].reach = self._new
+        elif self._column == "start_rk":
+            self._data[self._row].start_rk = self._new
+        elif self._column == "end_rk":
+            self._data[self._row].end_rk = self._new
+        elif self._column == "dif":
+            self._data[self._row].dif = self._new
+        elif self._column == "b":
+            self._data[self._row].b = self._new
+        elif self._column == "c":
+            self._data[self._row].c = self._new
+
+class AddCommand(QUndoCommand):
+    def __init__(self, data, ics_spec, index):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._ics_spec = ics_spec
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        self._data.delete_i([self._index])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._data.new(self._index)
+        else:
+            self._data.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, data, ics_spec, rows):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._ics_spec = ics_spec
+        self._rows = rows
+        #self._data = data
+
+        self._ic = []
+        for row in rows:
+            self._ic.append((row, self._ics_spec[row]))
+        self._ic.sort()
+
+    def undo(self):
+        for row, el in self._ic:
+            self._data.insert(row, el)
+
+    def redo(self):
+        self._data.delete_i(self._rows)
+
diff --git a/src/View/DIFAdisTS/Window.py b/src/View/DIFAdisTS/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..577c3a14341dcbf7cbcbb90aa619e82fd4f12cb8
--- /dev/null
+++ b/src/View/DIFAdisTS/Window.py
@@ -0,0 +1,290 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import os
+import logging
+
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+    QKeySequence, QIcon,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect, QItemSelectionModel,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+    QVBoxLayout, QToolBar, QAction, QToolButton,
+)
+
+from Modules import Modules
+
+from View.DIFAdisTS.TableDefault import (
+    DIFTableDefaultModel,
+)
+
+from View.DIFAdisTS.Table import (
+    DIFTableModel, ComboBoxDelegate,
+)
+
+from View.DIFAdisTS.translate import DIFAdisTSTranslate
+
+from Solver.Mage import Mage8
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class DIFAdisTSWindow(PamhyrWindow):
+    _pamhyr_ui = "DIFAdisTS"
+    _pamhyr_name = "DIF AdisTS"
+
+    def __init__(self, data=None, study=None, config=None, parent=None):
+        self._data = []
+        self._data.append(data)
+        trad = DIFAdisTSTranslate()
+
+        name = (
+            trad[self._pamhyr_name] +
+            " - " + study.name +
+            " - " + self._data[0].name
+        )
+
+        super(DIFAdisTSWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        self._hash_data.append(data)
+
+        self._dif_adists_lst = study.river.dif_adists
+
+        self.setup_table()
+
+        self.ui.setWindowTitle(self._title)
+
+    def setup_table(self):
+
+        path_icons = os.path.join(self._get_ui_directory(), f"ressources")
+
+        table_default = self.find(QTableView, f"tableView")
+
+        self._delegate_method = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            ic_spec_lst=self._data[0]._data,
+            parent=self,
+            mode="method"
+        )
+
+        self._table = DIFTableDefaultModel(
+            table_view=table_default,
+            table_headers=self._trad.get_dict("table_headers"),
+            editable_headers=["method", "dif", "b", "c"],
+            delegates={
+                "method": self._delegate_method
+            },
+            data=self._data,
+            undo=self._undo_stack,
+            trad=self._trad
+        )
+
+        table_default.setModel(self._table)
+        table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table_default.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table_default.setAlternatingRowColors(True)
+
+        layout = self.find(QVBoxLayout, f"verticalLayout_1")
+        toolBar = QToolBar()
+        layout.addWidget(toolBar)
+
+        action_add = QAction(self)
+        action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
+        action_add.triggered.connect(self.add)
+        action_delete = QAction(self)
+        action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
+        action_delete.triggered.connect(self.delete)
+
+        toolBar.addAction(action_add)
+        toolBar.addAction(action_delete)
+
+        self.table_spec = QTableView()
+        layout.addWidget(self.table_spec)
+
+        self._delegate_reach = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            ic_spec_lst=self._data[0]._data,
+            parent=self,
+            mode="reaches"
+        )
+        self._delegate_rk = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            ic_spec_lst=self._data[0]._data,
+            parent=self,
+            mode="rk"
+        )
+
+        self._table_spec = DIFTableModel(
+            table_view=self.table_spec,
+            table_headers=self._trad.get_dict("table_headers_spec"),
+            editable_headers=["method", "reach", "start_rk", "end_rk", "dif", "b", "c"],
+            delegates={
+                "method": self._delegate_method,
+                "reach": self._delegate_reach,
+                "start_rk": self._delegate_rk,
+                "end_rk": self._delegate_rk
+            },
+            data=self._data[0],
+            undo=self._undo_stack,
+            trad=self._trad,
+            river=self._study.river
+        )
+
+        self.table_spec.setModel(self._table_spec)
+        self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.table_spec.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        self.table_spec.setAlternatingRowColors(True)
+
+        selectionModel = self.table_spec.selectionModel()
+        index = self.table_spec.model().index(0, 0)
+
+        selectionModel.select(
+            index,
+            QItemSelectionModel.Rows |
+            QItemSelectionModel.ClearAndSelect |
+            QItemSelectionModel.Select
+        )
+        self.table_spec.scrollTo(index)
+
+    def index_selected_row(self):
+        #table = self.find(QTableView, f"tableView")
+        table = self.table_spec
+        rows = table.selectionModel()\
+                    .selectedRows()
+
+        if len(rows) == 0:
+            return 0
+
+        return rows[0].row()
+
+    def index_selected_rows(self):
+        #table = self.find(QTableView, f"tableView")
+        table = self.table_spec
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def move_up(self):
+        row = self.index_selected_row()
+        self._table.move_up(row)
+        self._update()
+
+    def move_down(self):
+        row = self.index_selected_row()
+        self._table.move_down(row)
+        self._update()
+
+    def _copy(self):
+        rows = list(
+            map(
+                lambda row: row.row(),
+                self.tableView.selectionModel().selectedRows()
+            )
+        )
+
+        table = list(
+            map(
+                lambda eic: list(
+                    map(
+                        lambda k: eic[1][k],
+                        ["rk", "discharge", "elevation"]
+                    )
+                ),
+                filter(
+                    lambda eic: eic[0] in rows,
+                    enumerate(self._ics.lst())
+                )
+            )
+        )
+
+        self.copyTableIntoClipboard(table)
+
+    def _paste(self):
+        header, data = self.parseClipboardTable()
+
+        if len(data) + len(header) == 0:
+            return
+
+        logger.debug(
+            "DIF: Paste: " +
+            f"header = {header}, " +
+            f"data = {data}"
+        )
+
+        try:
+            row = self.index_selected_row()
+            # self._table.paste(row, header, data)
+            self._table.paste(row, [], data)
+        except Exception as e:
+            logger_exception(e)
+
+        self._update()
+
+    def _undo(self):
+        self._table.undo()
+        self._update()
+
+    def _redo(self):
+        self._table.redo()
+        self._update()
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._data[0]._data) == 0 or len(rows) == 0:
+            self._table_spec.add(0)
+        else:
+            self._table_spec.add(rows[0])
+
+    def delete(self):
+        print("del")
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            print("len 0")
+            return
+        self._table_spec.delete(rows)
diff --git a/src/View/DIFAdisTS/translate.py b/src/View/DIFAdisTS/translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..373c0f5aba35b5dd3eb97b540a6957254fec69fe
--- /dev/null
+++ b/src/View/DIFAdisTS/translate.py
@@ -0,0 +1,50 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+
+class DIFAdisTSTranslate(MainTranslate):
+    def __init__(self):
+        super(DIFAdisTSTranslate, self).__init__()
+
+        self._dict["DIF AdisTS"] = _translate(
+            "DIFAdisTS", "DIF AdisTS")
+
+        self._dict["rk"] = self._dict["unit_rk"]
+
+        self._sub_dict["table_headers"] = {
+            "method": self._dict["method"],
+            "dif": _translate("Unit", "DIF"),
+            "b": _translate("Unit", "Coeff b"),
+            "c": _translate("Unit", "Coeff c"),
+        }
+
+        self._sub_dict["table_headers_spec"] = {
+            "method": self._dict["method"],
+            "reach": self._dict["reach"],
+            "start_rk": _translate("Unit", "Start_RK (m)"),
+            "end_rk": _translate("Unit", "End_RK (m)"),
+            "dif": _translate("Unit", "DIF"),
+            "b": _translate("Unit", "Coeff b"),
+            "c": _translate("Unit", "Coeff c"),
+        }
diff --git a/src/View/InitialConditions/UndoCommand.py b/src/View/InitialConditions/UndoCommand.py
index 8a226835185fc88aa98c8952199f0c9d35f6cbc8..a3f31a2917c51b1a3685f5af00529ba75a5ce0d7 100644
--- a/src/View/InitialConditions/UndoCommand.py
+++ b/src/View/InitialConditions/UndoCommand.py
@@ -138,7 +138,6 @@ class MoveCommand(QUndoCommand):
         else:
             self._ics.move_down(self._i)
 
-
 class InsertCommand(QUndoCommand):
     def __init__(self, ics, row, ic):
         QUndoCommand.__init__(self)
@@ -155,7 +154,6 @@ class InsertCommand(QUndoCommand):
         for ic in self._ic:
             self._ics.insert(self._row, ic)
 
-
 class DuplicateCommand(QUndoCommand):
     def __init__(self, ics, rows, ic):
         QUndoCommand.__init__(self)
diff --git a/src/View/InitialConditionsAdisTS/Table.py b/src/View/InitialConditionsAdisTS/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..7820a307c696440ea7de680a13adec5b92914486
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/Table.py
@@ -0,0 +1,237 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.InitialConditionsAdisTS.UndoCommand import (
+    SetCommand, AddCommand, SetCommandSpec,
+    DelCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class ComboBoxDelegate(QItemDelegate):
+    def __init__(self, data=None, ic_spec_lst=None, trad=None, parent=None, mode="reaches"):
+        super(ComboBoxDelegate, self).__init__(parent)
+
+        self._data = data
+        self._mode = mode
+        self._trad = trad
+        self._ic_spec_lst = ic_spec_lst
+
+    def createEditor(self, parent, option, index):
+        self.editor = QComboBox(parent)
+
+        val = []
+        if self._mode == "rk":
+            reach_id = self._ic_spec_lst[index.row()].reach
+
+            reach = next(filter(lambda edge: edge.id == reach_id, self._data.edges()))
+
+            if reach_id is not None:
+                val = list(
+                    map(
+                        lambda rk: str(rk), reach.reach.get_rk()
+                    )
+                )
+        else:
+            val = list(
+                map(
+                    lambda n: n.name, self._data.edges()
+                )
+            )
+
+        self.editor.addItems(
+            [self._trad['not_associated']] +
+            val
+        )
+
+        self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
+        return self.editor
+
+    def setEditorData(self, editor, index):
+        value = index.data(Qt.DisplayRole)
+        self.editor.currentTextChanged.connect(self.currentItemChanged)
+
+    def setModelData(self, editor, model, index):
+        text = str(editor.currentText())
+        model.setData(index, text)
+        editor.close()
+        editor.deleteLater()
+
+    def updateEditorGeometry(self, editor, option, index):
+        r = QRect(option.rect)
+        if self.editor.windowFlags() & Qt.Popup:
+            if editor.parent() is not None:
+                r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
+        editor.setGeometry(r)
+
+    @pyqtSlot()
+    def currentItemChanged(self):
+        self.commitData.emit(self.sender())
+
+
+class InitialConditionTableModel(PamhyrTableModel):
+    def __init__(self, river=None, data=None, **kwargs):
+        self._river = river
+
+        super(InitialConditionTableModel, self).__init__(data=data, **kwargs)
+
+        self._data = data
+
+    def _setup_lst(self):
+        self._lst = self._data._data
+
+    def rowCount(self, parent):
+        return len(self._lst)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] is "name":
+            n = self._lst[row].name
+            if n is None or n == "":
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "reach":
+            n = self._lst[row].reach
+            if n is None:
+                return self._trad['not_associated']
+            return next(filter(lambda edge: edge.id == n, self._river.edges())).name
+        elif self._headers[column] is "start_rk":
+            n = self._lst[row].start_rk
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "end_rk":
+            n = self._lst[row].end_rk
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "concentration":
+            n = self._lst[row].concentration
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "eg":
+            n = self._lst[row].eg
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "em":
+            n = self._lst[row].em
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "ed":
+            n = self._lst[row].ed
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "rate":
+            n = self._lst[row].rate
+            if n is None:
+                return self._trad['not_associated']
+            return n
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] != "reach":
+                self._undo.push(
+                    SetCommandSpec(
+                        self._lst, row, self._headers[column], value
+                    )
+                )
+            elif self._headers[column] == "reach":
+                print(self._river.edge(value).id)
+                self._undo.push(
+                    SetCommandSpec(
+                        self._lst, row, self._headers[column], self._river.edge(value).id
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._data, self._lst, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._data, self._lst, rows
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
+
diff --git a/src/View/InitialConditionsAdisTS/TableDefault.py b/src/View/InitialConditionsAdisTS/TableDefault.py
new file mode 100644
index 0000000000000000000000000000000000000000..67363647d1faceecf3e063c96f17bf1d857527c3
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/TableDefault.py
@@ -0,0 +1,110 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.InitialConditionsAdisTS.UndoCommand import (
+    SetCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+class InitialConditionTableDefaultModel(PamhyrTableModel):
+    def __init__(self, **kwargs):
+        super(InitialConditionTableDefaultModel, self).__init__(**kwargs)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] is "name":
+            return self._data[row].name
+        elif self._headers[column] is "concentration":
+            n = self._data[row].concentration
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "eg":
+            n = self._data[row].eg
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "em":
+            n = self._data[row].em
+            if n is None:
+                return self._trad['not_associated']
+            return n
+        elif self._headers[column] is "ed":
+            n = self._data[row].ed
+            if n is None:
+                return self._trad['not_associated']
+            return n
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] is not None:
+                self._undo.push(
+                    SetCommand(
+                        self._data, row, self._headers[column], value
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
+
diff --git a/src/View/InitialConditionsAdisTS/UndoCommand.py b/src/View/InitialConditionsAdisTS/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..6df1af9162a0b9d9566b8a709d87fd3556cbaf21
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/UndoCommand.py
@@ -0,0 +1,192 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.InitialConditionsAdisTS.InitialConditionsAdisTS import InitialConditionsAdisTS
+from Model.InitialConditionsAdisTS.InitialConditionsAdisTSList import InitialConditionsAdisTSList
+
+
+class SetCommand(QUndoCommand):
+    def __init__(self, data, row, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._row = row
+        self._column = column
+
+        if self._column == "name":
+            self._old = self._data[self._row].name
+        elif self._column == "concentration":
+            self._old = self._data[self._row].concentration
+        elif self._column == "eg":
+            self._old = self._data[self._row].eg
+        elif self._column == "em":
+            self._old = self._data[self._row].em
+        elif self._column == "ed":
+            self._old = self._data[self._row].ed
+
+        _type = float
+        if column == "name":
+            _type = str
+
+        self._new = _type(new_value)
+
+    def undo(self):
+        if self._column == "name":
+            self._data[self._row].name = self._old
+        elif self._column == "concentration":
+            self._data[self._row].concentration = self._old
+        elif self._column == "eg":
+            self._data[self._row].eg = self._old
+        elif self._column == "em":
+            self._data[self._row].em = self._old
+        elif self._column == "ed":
+            self._data[self._row].ed = self._old
+
+    def redo(self):
+        if self._column == "name":
+            self._data[self._row].name = self._new
+        elif self._column == "concentration":
+            self._data[self._row].concentration = self._new
+        elif self._column == "eg":
+            self._data[self._row].eg = self._new
+        elif self._column == "em":
+            self._data[self._row].em = self._new
+        elif self._column == "ed":
+            self._data[self._row].ed = self._new
+
+class SetCommandSpec(QUndoCommand):
+    def __init__(self, data, row, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._row = row
+        self._column = column
+
+        if self._column == "name":
+            self._old = self._data[self._row].name
+        elif self._column == "reach":
+            self._old = self._data[self._row].reach
+        elif self._column == "start_rk":
+            self._old = self._data[self._row].start_rk
+        elif self._column == "end_rk":
+            self._old = self._data[self._row].end_rk
+        elif self._column == "concentration":
+            self._old = self._data[self._row].concentration
+        elif self._column == "eg":
+            self._old = self._data[self._row].eg
+        elif self._column == "em":
+            self._old = self._data[self._row].em
+        elif self._column == "ed":
+            self._old = self._data[self._row].ed
+        elif self._column == "rate":
+            self._old = self._data[self._row].rate
+
+        _type = float
+        if column == "name":
+            _type = str
+        elif column == "reach":
+            _type = int
+
+        self._new = _type(new_value)
+
+    def undo(self):
+        if self._column == "name":
+            self._data[self._row].name = self._old
+        elif self._column == "reach":
+            self._data[self._row].reach = self._old
+        elif self._column == "start_rk":
+            self._data[self._row].start_rk = self._old
+        elif self._column == "end_rk":
+            self._data[self._row].end_rk = self._old
+        elif self._column == "concentration":
+            self._data[self._row].concentration = self._old
+        elif self._column == "eg":
+            self._data[self._row].eg = self._old
+        elif self._column == "em":
+            self._data[self._row].em = self._old
+        elif self._column == "ed":
+            self._data[self._row].ed = self._old
+        elif self._column == "rate":
+            self._data[self._row].rate = self._old
+
+    def redo(self):
+        if self._column == "name":
+            self._data[self._row].name = self._new
+        elif self._column == "reach":
+            self._data[self._row].reach = self._new
+        elif self._column == "start_rk":
+            self._data[self._row].start_rk = self._new
+        elif self._column == "end_rk":
+            self._data[self._row].end_rk = self._new
+        elif self._column == "concentration":
+            self._data[self._row].concentration = self._new
+        elif self._column == "eg":
+            self._data[self._row].eg = self._new
+        elif self._column == "em":
+            self._data[self._row].em = self._new
+        elif self._column == "ed":
+            self._data[self._row].ed = self._new
+        elif self._column == "rate":
+            self._data[self._row].rate = self._new
+
+class AddCommand(QUndoCommand):
+    def __init__(self, data, ics_spec, index):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._ics_spec = ics_spec
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        self._data.delete_i([self._index])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._data.new(self._index)
+        else:
+            self._data.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, data, ics_spec, rows):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._ics_spec = ics_spec
+        self._rows = rows
+        #self._data = data
+
+        self._ic = []
+        for row in rows:
+            self._ic.append((row, self._ics_spec[row]))
+        self._ic.sort()
+
+    def undo(self):
+        for row, el in self._ic:
+            self._data.insert(row, el)
+
+    def redo(self):
+        self._data.delete_i(self._rows)
+
diff --git a/src/View/InitialConditionsAdisTS/Window.py b/src/View/InitialConditionsAdisTS/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5105a6eb92928652d274e808077966adf2d774a
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/Window.py
@@ -0,0 +1,283 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import os
+import logging
+
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+    QKeySequence, QIcon,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect, QItemSelectionModel,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+    QVBoxLayout, QToolBar, QAction, QToolButton,
+)
+
+from Modules import Modules
+
+from View.InitialConditionsAdisTS.UndoCommand import (
+    SetCommand,
+)
+
+from View.InitialConditionsAdisTS.TableDefault import (
+    InitialConditionTableDefaultModel,
+)
+
+from View.InitialConditionsAdisTS.Table import (
+    InitialConditionTableModel, ComboBoxDelegate,
+)
+
+from View.InitialConditionsAdisTS.translate import IcAdisTSTranslate
+
+from Solver.Mage import Mage8
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class InitialConditionsAdisTSWindow(PamhyrWindow):
+    _pamhyr_ui = "InitialConditionsAdisTS"
+    _pamhyr_name = "Initial condition AdisTS"
+
+    def __init__(self, data=None, study=None, config=None, parent=None):
+        self._data = []
+        self._data.append(data)
+        trad = IcAdisTSTranslate()
+
+        name = (
+            trad[self._pamhyr_name] +
+            " - " + study.name +
+            " - " + self._data[0].name
+        )
+
+        super(InitialConditionsAdisTSWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        self._hash_data.append(data)
+
+        self._ics_adists_lst = study.river.initial_conditions_adists
+
+        self.setup_table()
+
+        self.ui.setWindowTitle(self._title)
+
+    def setup_table(self):
+
+        path_icons = os.path.join(self._get_ui_directory(), f"ressources")
+
+        table_default = self.find(QTableView, f"tableView")
+
+        self._table = InitialConditionTableDefaultModel(
+            table_view=table_default,
+            table_headers=self._trad.get_dict("table_headers"),
+            editable_headers=["name", "concentration", "eg", "em", "ed"],
+            delegates={},
+            data=self._data,
+            undo=self._undo_stack,
+            trad=self._trad
+        )
+
+        table_default.setModel(self._table)
+        table_default.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table_default.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table_default.setAlternatingRowColors(True)
+
+        layout = self.find(QVBoxLayout, f"verticalLayout_1")
+        toolBar = QToolBar()
+        layout.addWidget(toolBar)
+
+        action_add = QAction(self)
+        action_add.setIcon(QIcon(os.path.join(path_icons, f"add.png")))
+        action_add.triggered.connect(self.add)
+        action_delete = QAction(self)
+        action_delete.setIcon(QIcon(os.path.join(path_icons, f"del.png")))
+        action_delete.triggered.connect(self.delete)
+
+        toolBar.addAction(action_add)
+        toolBar.addAction(action_delete)
+
+        self.table_spec = QTableView()
+        layout.addWidget(self.table_spec)
+
+        self._delegate_reach = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            ic_spec_lst=self._data[0]._data,
+            parent=self,
+            mode="reaches"
+        )
+        self._delegate_rk = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            ic_spec_lst=self._data[0]._data,
+            parent=self,
+            mode="rk"
+        )
+
+        self._table_spec = InitialConditionTableModel(
+            table_view=self.table_spec,
+            table_headers=self._trad.get_dict("table_headers_spec"),
+            editable_headers=["name", "reach", "start_rk", "end_rk", "concentration", "eg", "em", "ed", "rate"],
+            delegates={
+                "reach": self._delegate_reach,
+                "start_rk": self._delegate_rk,
+                "end_rk": self._delegate_rk
+            },
+            data=self._data[0],
+            undo=self._undo_stack,
+            trad=self._trad,
+            river=self._study.river
+        )
+
+        self.table_spec.setModel(self._table_spec)
+        self.table_spec.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.table_spec.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        self.table_spec.setAlternatingRowColors(True)
+
+        selectionModel = self.table_spec.selectionModel()
+        index = self.table_spec.model().index(0, 0)
+
+        selectionModel.select(
+            index,
+            QItemSelectionModel.Rows |
+            QItemSelectionModel.ClearAndSelect |
+            QItemSelectionModel.Select
+        )
+        self.table_spec.scrollTo(index)
+
+    def index_selected_row(self):
+        #table = self.find(QTableView, f"tableView")
+        table = self.table_spec
+        rows = table.selectionModel()\
+                    .selectedRows()
+
+        if len(rows) == 0:
+            return 0
+
+        return rows[0].row()
+
+    def index_selected_rows(self):
+        #table = self.find(QTableView, f"tableView")
+        table = self.table_spec
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def move_up(self):
+        row = self.index_selected_row()
+        self._table.move_up(row)
+        self._update()
+
+    def move_down(self):
+        row = self.index_selected_row()
+        self._table.move_down(row)
+        self._update()
+
+    def _copy(self):
+        rows = list(
+            map(
+                lambda row: row.row(),
+                self.tableView.selectionModel().selectedRows()
+            )
+        )
+
+        table = list(
+            map(
+                lambda eic: list(
+                    map(
+                        lambda k: eic[1][k],
+                        ["rk", "discharge", "elevation"]
+                    )
+                ),
+                filter(
+                    lambda eic: eic[0] in rows,
+                    enumerate(self._ics.lst())
+                )
+            )
+        )
+
+        self.copyTableIntoClipboard(table)
+
+    def _paste(self):
+        header, data = self.parseClipboardTable()
+
+        if len(data) + len(header) == 0:
+            return
+
+        logger.debug(
+            "IC: Paste: " +
+            f"header = {header}, " +
+            f"data = {data}"
+        )
+
+        try:
+            row = self.index_selected_row()
+            # self._table.paste(row, header, data)
+            self._table.paste(row, [], data)
+        except Exception as e:
+            logger_exception(e)
+
+        self._update()
+
+    def _undo(self):
+        self._table.undo()
+        self._update()
+
+    def _redo(self):
+        self._table.redo()
+        self._update()
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._data[0]._data) == 0 or len(rows) == 0:
+            self._table_spec.add(0)
+        else:
+            self._table_spec.add(rows[0])
+
+    def delete(self):
+        print("del")
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            print("len 0")
+            return
+        self._table_spec.delete(rows)
diff --git a/src/View/InitialConditionsAdisTS/translate.py b/src/View/InitialConditionsAdisTS/translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..22ee8687fd341b177c271f2f5add4f7e027cda1d
--- /dev/null
+++ b/src/View/InitialConditionsAdisTS/translate.py
@@ -0,0 +1,53 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+
+class IcAdisTSTranslate(MainTranslate):
+    def __init__(self):
+        super(IcAdisTSTranslate, self).__init__()
+
+        self._dict["Initial condition AdisTS"] = _translate(
+            "InitialConditionAdisTS", "Initial condition AdisTS")
+
+        self._dict["rk"] = self._dict["unit_rk"]
+
+        self._sub_dict["table_headers"] = {
+            "name": self._dict["name"],
+            "concentration": self._dict["unit_concentration"],
+            "eg": _translate("Unit", "EG (m)"),
+            "em": _translate("Unit", "EM (m)"),
+            "ed": _translate("Unit", "ED (m)"),
+        }
+
+        self._sub_dict["table_headers_spec"] = {
+            "name": self._dict["name"],
+            "reach": self._dict["reach"],
+            "start_rk": _translate("Unit", "Start_RK (m)"),
+            "end_rk": _translate("Unit", "End_RK (m)"),
+            "concentration": self._dict["unit_concentration"],
+            "eg": _translate("Unit", "EG (m)"),
+            "em": _translate("Unit", "EM (m)"),
+            "ed": _translate("Unit", "ED (m)"),
+            "rate": _translate("Unit", "Rate"),
+        }
diff --git a/src/View/LateralContributionsAdisTS/Edit/Plot.py b/src/View/LateralContributionsAdisTS/Edit/Plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d10b5e0ea9cbe27275fe5a93448c65b96003833
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/Edit/Plot.py
@@ -0,0 +1,103 @@
+# Plot.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from datetime import datetime
+
+from tools import timer, trace
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+class Plot(PamhyrPlot):
+    def __init__(self, mode="time", data=None,
+                 trad=None, canvas=None, toolbar=None,
+                 parent=None):
+        super(Plot, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=data,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self._table_headers = self._trad.get_dict("table_headers")
+
+        header = self.data.header
+        self.label_x = self._table_headers[header[0]]
+        self.label_y = self._table_headers[header[1]]
+
+        self._mode = mode
+        self._isometric_axis = False
+
+        self._auto_relim_update = True
+        self._autoscale_update = True
+
+    def custom_ticks(self):
+        if self.data.header[0] != "time":
+            return
+
+        self.set_ticks_time_formater()
+
+    @timer
+    def draw(self):
+        self.init_axes()
+
+        if len(self.data) == 0:
+            self._init = False
+            return
+
+        self.draw_data()
+        self.custom_ticks()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self):
+        # Plot data
+        x = list(map(lambda v: v[0], self.data.data))
+        y = list(map(lambda v: v[1], self.data.data))
+
+        self._line, = self.canvas.axes.plot(
+            x, y,
+            color=self.color_plot,
+            **self.plot_default_kargs
+        )
+
+    @timer
+    def update(self, ind=None):
+        if not self._init:
+            self.draw()
+            return
+
+        self.update_data()
+
+        self.update_idle()
+
+    def update_data(self):
+        x = list(map(lambda v: v[0], self.data.data))
+        y = list(map(lambda v: v[1], self.data.data))
+
+        self._line.set_data(x, y)
diff --git a/src/View/LateralContributionsAdisTS/Edit/Table.py b/src/View/LateralContributionsAdisTS/Edit/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..4d08e5472f07421f55519b67bff911c851fa4753
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/Edit/Table.py
@@ -0,0 +1,123 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+from datetime import date, time, datetime, timedelta
+
+from tools import (
+    trace, timer,
+    timestamp_to_old_pamhyr_date,
+    old_pamhyr_date_to_timestamp,
+    timestamp_to_old_pamhyr_date_adists
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect, QTime, QDateTime,
+)
+
+from PyQt5.QtWidgets import (
+    QTableView, QAbstractItemView, QSpinBox,
+    QTimeEdit, QDateTimeEdit, QItemDelegate,
+)
+
+
+from View.LateralContributionsAdisTS.Edit.UndoCommand import (
+    SetDataCommand, AddCommand, DelCommand,
+)
+from View.LateralContributionsAdisTS.Edit.translate import *
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class TableModel(PamhyrTableModel):
+    def data(self, index, role):
+        if role == Qt.TextAlignmentRole:
+            return Qt.AlignHCenter | Qt.AlignVCenter
+
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        value = QVariant()
+
+        if 0 <= column < 2:
+            v = self._data._data[row][column]
+            if self._data._types[column] == float:
+                value = f"{v:.4f}"
+            elif self._data.header[column] == "time":
+                if self._opt_data == "time":
+                    value = timestamp_to_old_pamhyr_date_adists(int(v))
+                else:
+                    value = str(datetime.fromtimestamp(v))
+            else:
+                value = f"{v}"
+
+        return value
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            self._undo.push(
+                SetDataCommand(
+                    self._data, row, column, value
+                )
+            )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._data, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._data, rows
+            )
+        )
+
+        self.endRemoveRows()
+
diff --git a/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py b/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..d09f5809949807e988a2db001c72642843971044
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/Edit/UndoCommand.py
@@ -0,0 +1,88 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.LateralContributionsAdisTS.LateralContributionAdisTS import LateralContributionAdisTS
+
+
+class SetDataCommand(QUndoCommand):
+    def __init__(self, data, index, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._column = column
+        self._old = self._data._data[self._index][self._column]
+        _type = self._data._types[self._column]
+        self._new = _type(new_value)
+
+    def undo(self):
+        if self._column == 0:
+            self._data._data[self._index] = (self._old, self._data._data[self._index][1])
+        else:
+            self._data._data[self._index] = (self._data._data[self._index][0], self._old)
+
+    def redo(self):
+        if self._column == 0:
+            self._data._data[self._index] = (self._new, self._data._data[self._index][1])
+        else:
+            self._data._data[self._index] = (self._data._data[self._index][0], self._new)
+
+
+class AddCommand(QUndoCommand):
+    def __init__(self, data, index):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        del self._data._data[self._index]
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._data._data.insert(self._index, (self._data._types[0](0), self._data._types[1](0.0)))
+        else:
+            self._data._data.insert(self._index, self._new)
+
+class DelCommand(QUndoCommand):
+    def __init__(self, data, rows):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._rows = rows
+
+        self._lc = []
+        for row in rows:
+            self._lc.append((row, self._data._data[row]))
+        self._lc.sort()
+
+    def undo(self):
+        for row, el in self._lc:
+            self._data._data.insert(row, el)
+
+    def redo(self):
+        for row in self._rows:
+            del self._data._data[row]
diff --git a/src/View/LateralContributionsAdisTS/Edit/Window.py b/src/View/LateralContributionsAdisTS/Edit/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0ebea2295ecb08e2787a1c03b64b3333c66d578
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/Edit/Window.py
@@ -0,0 +1,186 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from tools import timer, trace
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+from View.Tools.PamhyrDelegate import PamhyrExTimeDelegate
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel, QCoreApplication,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QHeaderView,
+)
+
+from View.Tools.Plot.PamhyrCanvas import MplCanvas
+from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
+
+from View.LateralContributionsAdisTS.Edit.translate import LCETranslate
+from View.LateralContributionsAdisTS.Edit.Table import TableModel
+from View.LateralContributionsAdisTS.Edit.Plot import Plot
+
+_translate = QCoreApplication.translate
+
+
+class EditLateralContributionAdisTSWindow(PamhyrWindow):
+    _pamhyr_ui = "EditLateralContributionAdisTS"
+    _pamhyr_name = "Edit lateral contribution AdisTS"
+
+    def __init__(self, data=None,
+                 study=None, config=None,
+                 parent=None):
+        self._data = data
+
+        trad = LCETranslate()
+        name = trad[self._pamhyr_name]
+
+        super(EditLateralContributionAdisTSWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        if self._data is not None:
+            if self._data.edge is not None:
+                edge_name = next(filter(lambda edge: edge.id == self._data.edge, self._study.river.edges())).name
+            else:
+                edge_name = trad['not_associated']
+
+            name += (
+                f"{study.name} - " +
+                f"{edge_name})"
+            )
+
+        self._hash_data.append(data)
+
+        self.setup_table()
+        self.setup_plot()
+        self.setup_connections()
+
+    def setup_table(self):
+        self._delegate_time = PamhyrExTimeDelegate(
+            data=self._data,
+            mode=self._study.time_system,
+            parent=self
+        )
+
+        headers = {}
+        table_headers = self._trad.get_dict("table_headers")
+        for h in self._data.header:
+            headers[h] = table_headers[h]
+
+        table = self.find(QTableView, "tableView")
+        self._table = TableModel(
+            table_view=table,
+            table_headers=headers,
+            editable_headers=self._data.header,
+            delegates={
+                # "time": self._delegate_time,
+            },
+            data=self._data,
+            undo=self._undo_stack,
+            trad=self._trad,
+            opt_data=self._study.time_system
+        )
+
+        table.setModel(self._table)
+        table.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table.setAlternatingRowColors(True)
+
+    def setup_plot(self):
+        self.canvas = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas.setObjectName("canvas")
+        self.toolbar = PamhyrPlotToolbar(
+            self.canvas, self
+        )
+        self.verticalLayout.addWidget(self.toolbar)
+        self.verticalLayout.addWidget(self.canvas)
+
+        self.plot = Plot(
+            canvas=self.canvas,
+            data=self._data,
+            mode=self._study.time_system,
+            toolbar=self.toolbar,
+            trad=self._trad,
+            parent=self
+        )
+        self.plot.draw()
+
+    def setup_connections(self):
+        self.find(QAction, "action_add").triggered.connect(self.add)
+        self.find(QAction, "action_del").triggered.connect(self.delete)
+
+        self._table.dataChanged.connect(self.update)
+
+    def update(self):
+        self.plot.update()
+
+    def index_selected_row(self):
+        table = self.find(QTableView, "tableView")
+        return table.selectionModel()\
+                    .selectedRows()[0]\
+                    .row()
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, "tableView")
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._data) == 0 or len(rows) == 0:
+            self._table.add(0)
+        else:
+            self._table.add(rows[0])
+
+        self.plot.update()
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            return
+
+        self._table.delete(rows)
+        self.plot.update()
+
+    def _undo(self):
+        self._table.undo()
+        self.plot.update()
+
+    def _redo(self):
+        self._table.redo()
+        self.plot.update()
diff --git a/src/View/LateralContributionsAdisTS/Edit/translate.py b/src/View/LateralContributionsAdisTS/Edit/translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..2974577f7b815e66b1ccdffb2ce6e31fa7e082f2
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/Edit/translate.py
@@ -0,0 +1,38 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+from View.LateralContributionsAdisTS.translate import LCTranslate
+
+_translate = QCoreApplication.translate
+
+
+class LCETranslate(LCTranslate):
+    def __init__(self):
+        super(LCETranslate, self).__init__()
+        self._dict["Edit lateral contribution AdisTS"] = _translate(
+            "LateralContributionAdisTS", "Edit lateral contribution AdisTS"
+        )
+
+        self._sub_dict["table_headers"] = {
+            "time": self._dict["time"],
+            "date": self._dict["date"],
+            "rate": _translate("LateralContributionAdisTS", "Mass Flow"),
+        }
diff --git a/src/View/LateralContributionsAdisTS/Table.py b/src/View/LateralContributionsAdisTS/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..12ef745456ce30e7c8195282fd58ea5b9cd94f0e
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/Table.py
@@ -0,0 +1,206 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.LateralContributionsAdisTS.UndoCommand import (
+    SetEdgeCommand,
+    SetBeginCommand, SetEndCommand,
+    AddCommand, DelCommand,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class ComboBoxDelegate(QItemDelegate):
+    def __init__(self, data=None, mode="edge",
+                 trad=None, parent=None):
+        super(ComboBoxDelegate, self).__init__(parent)
+
+        self._data = data
+        self._mode = mode
+        self._trad = trad
+
+    @property
+    def data(self):
+        return self._data
+
+    @data.setter
+    def data(self, data):
+        self._data = data
+
+    def createEditor(self, parent, option, index):
+        self.editor = QComboBox(parent)
+
+        if self._mode == "rk":
+            if self._data is None:
+                self.editor.addItems(
+                    ["0"]
+                )
+            else:
+                self.editor.addItems(
+                    list(
+                        map(str, self._data.reach.get_rk())
+                    )
+                )
+        else:
+            self.editor.addItems(
+                [self._trad['not_associated']] +
+                self._data.edges_names()
+            )
+
+        self.editor.setCurrentText(index.data(Qt.DisplayRole))
+        return self.editor
+
+    def setEditorData(self, editor, index):
+        value = index.data(Qt.DisplayRole)
+        self.editor.currentTextChanged.connect(self.currentItemChanged)
+
+    def setModelData(self, editor, model, index):
+        text = str(editor.currentText())
+        model.setData(index, text)
+        editor.close()
+        editor.deleteLater()
+
+    def updateEditorGeometry(self, editor, option, index):
+        r = QRect(option.rect)
+        if self.editor.windowFlags() & Qt.Popup:
+            if editor.parent() is not None:
+                r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
+        editor.setGeometry(r)
+
+    @pyqtSlot()
+    def currentItemChanged(self):
+        self.commitData.emit(self.sender())
+
+
+class TableModel(PamhyrTableModel):
+    def __init__(self, pollutant=None, lcs_list=None, trad=None, **kwargs):
+        self._trad = trad
+        self._lcs_list = lcs_list
+        self._pollutant = pollutant
+
+        super(TableModel, self).__init__(trad=trad, **kwargs)
+
+    def _setup_lst(self):
+        self._lst = self._data.lateral_contributions_adists.lst
+        self._tab = self._opt_data
+        self._long_types = self._trad.get_dict("long_types")
+
+    def rowCount(self, parent):
+        return len(self._lst)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] == "edge":
+            n = self._lst[row].edge
+            if n is None:
+                return self._trad['not_associated']
+            return next(filter(lambda edge: edge.id == n, self._data.edges())).name
+        elif self._headers[column] == "begin_rk":
+            return str(self._lst[row].begin_rk)
+        elif self._headers[column] == "end_rk":
+            return str(self._lst[row].end_rk)
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] == "edge":
+                self._undo.push(
+                    SetEdgeCommand(
+                        self._lcs_list, self._lst, row, self._data.edge(value).id
+                    )
+                )
+            elif self._headers[column] == "begin_rk":
+                self._undo.push(
+                    SetBeginCommand(
+                        self._lcs_list, self._lst, row, value
+                    )
+                )
+            elif self._headers[column] == "end_rk":
+                self._undo.push(
+                    SetEndCommand(
+                        self._lcs_list, self._lst, row, value
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._pollutant, self._lcs_list, self._lst, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._lst, rows
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+
+
+
diff --git a/src/View/LateralContributionsAdisTS/UndoCommand.py b/src/View/LateralContributionsAdisTS/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..b8506ac3ef7eaff9a8aa2bb098764729db7cff70
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/UndoCommand.py
@@ -0,0 +1,120 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.LateralContributionsAdisTS.LateralContributionAdisTS import LateralContributionAdisTS
+from Model.LateralContributionsAdisTS.LateralContributionsAdisTSList import (
+    LateralContributionsAdisTSList
+)
+
+class SetBeginCommand(QUndoCommand):
+    def __init__(self, lcs, lcs_lst, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._lcs = lcs
+        self._lcs_lst = lcs_lst
+        self._index = index
+        self._old = self._lcs_lst[self._index].begin_rk
+        self._new = float(new_value)
+
+    def undo(self):
+        self._lcs_lst[self._index].begin_rk = float(self._old)
+
+    def redo(self):
+        self._lcs_lst[self._index].begin_rk = float(self._new)
+
+
+class SetEndCommand(QUndoCommand):
+    def __init__(self, lcs, lcs_lst, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._lcs = lcs
+        self._lcs_lst = lcs_lst
+        self._index = index
+        self._old = self._lcs_lst[self._index].end_rk
+        self._new = float(new_value)
+
+    def undo(self):
+        self._lcs_lst[self._index].end_rk = float(self._old)
+
+    def redo(self):
+        self._lcs_lst[self._index].end_rk = float(self._new)
+
+
+class SetEdgeCommand(QUndoCommand):
+    def __init__(self, lcs, lcs_lst, index, edge):
+        QUndoCommand.__init__(self)
+
+        self._lcs = lcs
+        self._lcs_lst = lcs_lst
+        self._index = index
+        self._old = self._lcs_lst[self._index].edge
+        self._new = edge
+
+    def undo(self):
+        self._lcs_lst[self._index].edge = self._old
+
+    def redo(self):
+        self._lcs_lst[self._index].edge = self._new
+
+class AddCommand(QUndoCommand):
+    def __init__(self, pollutant, lcs, lcs_lst, index):
+        QUndoCommand.__init__(self)
+
+        self._pollutant = pollutant
+        self._lcs = lcs
+        self._lcs_lst = lcs_lst
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        del self._lcs[self._index]
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._lcs.new(self._index, self._pollutant)
+        else:
+            self._lcs_lst.insert(self._index, self._new)
+
+
+class DelCommand(QUndoCommand):
+    def __init__(self, lcs, rows):
+        QUndoCommand.__init__(self)
+
+        self._lcs = lcs
+        self._rows = rows
+
+        self._bc = []
+        for row in rows:
+            self._bc.append((row, self._lcs[row]))
+        self._bc.sort()
+
+    def undo(self):
+        for row, el in self._bc:
+            self._lcs.insert(row, el)
+
+    def redo(self):
+        for row in self._rows:
+            del self._lcs[row]
+
diff --git a/src/View/LateralContributionsAdisTS/Window.py b/src/View/LateralContributionsAdisTS/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..4788adc0c68aed98da954c7657e8643498ba615f
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/Window.py
@@ -0,0 +1,250 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import trace, timer
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+)
+
+from View.LateralContributionsAdisTS.Table import (
+    TableModel, ComboBoxDelegate
+)
+
+from View.Tools.Plot.PamhyrCanvas import MplCanvas
+from View.Geometry.PlotXY import PlotXY
+from View.LateralContributionsAdisTS.translate import (
+    LCTranslate,
+)
+from View.LateralContributionsAdisTS.Edit.Window import EditLateralContributionAdisTSWindow
+
+logger = logging.getLogger()
+
+
+class LateralContributionAdisTSWindow(PamhyrWindow):
+    _pamhyr_ui = "LateralContributionsAdisTS"
+    _pamhyr_name = "Lateral contribution AdisTS"
+
+    def __init__(self, study=None, pollutant=None, config=None, parent=None):
+        trad = LCTranslate()
+        name = trad[self._pamhyr_name] + " - " + study.name
+
+        self._pollutant = pollutant
+
+        super(LateralContributionAdisTSWindow, self).__init__(
+            title=name,
+            study=study,
+            trad=trad,
+            config=config,
+            parent=parent
+        )
+
+        self._lcs = self._study.river.lateral_contributions_adists
+
+        self.setup_table()
+        self.setup_graph()
+        self.setup_connections()
+
+    def setup_table(self):
+        self._table = {}
+
+        self._delegate_rk = []
+
+        delegate_rk = ComboBoxDelegate(
+            data=None,
+            mode="rk",
+            trad=self._trad,
+            parent=self
+        )
+        self._delegate_rk.append(delegate_rk)
+
+        self._delegate_edge = ComboBoxDelegate(
+            data=self._study.river,
+            mode="edge",
+            trad=self._trad,
+            parent=self
+        )
+
+        table = self.find(QTableView, f"tableView")
+        self._table = TableModel(
+            table_view=table,
+            table_headers=self._trad.get_dict("table_headers"),
+            editable_headers=self._trad.get_dict("table_headers"),
+            delegates={
+                "edge": self._delegate_edge,
+                "begin_rk": delegate_rk,
+                "end_rk": delegate_rk,
+            },
+            data=self._study.river,
+            undo=self._undo_stack,
+            trad=self._trad,
+            opt_data="liquid",
+            pollutant=self._pollutant,
+            lcs_list=self._lcs,
+        )
+        table.setModel(self._table)
+        table.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table.setAlternatingRowColors(True)
+
+    def setup_graph(self):
+        self.canvas = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas.setObjectName("canvas")
+        self.plot_layout = self.find(QVBoxLayout, "verticalLayout_2")
+        self.plot_layout.addWidget(self.canvas)
+
+        self.plot = PlotXY(
+            canvas=self.canvas,
+            data=None,
+            trad=self._trad,
+            toolbar=None,
+        )
+
+    def setup_connections(self):
+        self.find(QAction, "action_add").triggered.connect(self.add)
+        self.find(QAction, "action_del").triggered.connect(self.delete)
+        self.find(QAction, "action_edit").triggered.connect(self.edit)
+
+        table = self.find(QTableView, f"tableView")
+        table.selectionModel()\
+             .selectionChanged\
+             .connect(self._set_current_reach)
+
+        self._table.dataChanged\
+                      .connect(self._set_current_reach)
+
+    def index_selected_row(self):
+        table = self.find(QTableView, f"tableView")
+        return table.selectionModel()\
+                    .selectedRows()[0]\
+                    .row()
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, f"tableView")
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def _set_current_reach(self):
+        rows = self.index_selected_rows()
+
+        data = None
+        highlight = None
+
+        tab = "liquid"
+
+        if len(rows) > 0:
+            edge_id = self._study.river\
+                       .lateral_contributions_adists.lst[rows[0]]\
+                       .edge
+
+            if edge_id:
+                edge = next(filter(lambda e: e.id == edge_id, self._study.river.edges()))
+                data = edge.reach
+                lc = self._lcs.lst[rows[0]]
+                highlight = (lc.begin_rk, lc.end_rk)
+
+                for delegate in self._delegate_rk:
+                    delegate.data = edge
+
+        self.plot = PlotXY(
+            canvas=self.canvas,
+            data=data,
+            trad=self._trad,
+            toolbar=None,
+        )
+        self.plot.highlight = highlight
+        self.plot.update()
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._lcs) == 0 or len(rows) == 0:
+            self._table.add(0)
+        else:
+            self._table.add(rows[0])
+
+        self._set_current_reach()
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            return
+
+        self._table.delete(rows)
+        self._set_current_reach()
+
+    def sort(self):
+        self._table.sort(False)
+        self._set_current_reach()
+
+    def _copy(self):
+        logger.info("TODO: copy")
+        self._set_current_reach()
+
+    def _paste(self):
+        logger.info("TODO: paste")
+        self._set_current_reach()
+
+    def _undo(self):
+        self._table.undo()
+        self._set_current_reach()
+
+    def _redo(self):
+        self._table.redo()
+        self._set_current_reach()
+
+    def edit(self):
+        rows = self.index_selected_rows()
+        for row in rows:
+            data = self._lcs.lst[row]
+
+            if self.sub_window_exists(
+                EditLateralContributionAdisTSWindow,
+                data=[self._study, None, data]
+            ):
+                continue
+
+            win = EditLateralContributionAdisTSWindow(
+                data=data,
+                study=self._study,
+                parent=self
+            )
+            win.show()
diff --git a/src/View/LateralContributionsAdisTS/translate.py b/src/View/LateralContributionsAdisTS/translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..15363ea9e9e19ec1e21f78c1df33066f020580db
--- /dev/null
+++ b/src/View/LateralContributionsAdisTS/translate.py
@@ -0,0 +1,48 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+class LCTranslate(MainTranslate):
+    def __init__(self):
+        super(LCTranslate, self).__init__()
+
+        self._dict["Lateral contribution AdisTS"] = _translate(
+            "LateralContribution1adisTS", "Lateral contribution AdisTS"
+        )
+
+        self._sub_dict["long_types"] = {
+            "ND": self._dict["not_defined"],
+            "LC": _translate("LateralContribution", "Lateral contribution"),
+            "RA": _translate("LateralContribution", "Rain"),
+            "EV": _translate("LateralContribution", "Evaporation"),
+        }
+
+        self._dict["x"] = _translate("Geometry", "X (m)")
+        self._dict["y"] = _translate("Geometry", "Y (m)")
+        self._dict["z"] = _translate("Geometry", "Z (m)")
+
+        self._sub_dict["table_headers"] = {
+            "edge": self._dict["reach"],
+            "begin_rk": _translate("LateralContribution", "Begin rk (m)"),
+            "end_rk": _translate("LateralContribution", "End rk (m)")
+        }
diff --git a/src/View/MainWindow.py b/src/View/MainWindow.py
index 63732472719adc086446e09aed2e87bdfe38b4ac..18d793551fef2f9d2786b14631f93c73384e714f 100644
--- a/src/View/MainWindow.py
+++ b/src/View/MainWindow.py
@@ -24,6 +24,8 @@ import subprocess
 from queue import Queue
 from functools import reduce
 from platformdirs import user_cache_dir
+
+from Solver.AdisTS import AdisTS
 from tools import logger_exception
 
 from PyQt5 import QtGui
@@ -71,10 +73,17 @@ from View.AdditionalFiles.Window import AddFileListWindow
 from View.REPLines.Window import REPLineListWindow
 from View.SolverParameters.Window import SolverParametersWindow
 from View.RunSolver.Window import SelectSolverWindow, SolverLogWindow
+from View.RunSolver.WindowAdisTS import SelectSolverWindowAdisTS, SolverLogWindowAdisTS
 from View.CheckList.Window import CheckListWindow
+from View.CheckList.WindowAdisTS import CheckListWindowAdisTS
 from View.Results.Window import ResultsWindow
+from View.Results.WindowAdisTS import ResultsWindowAdisTS
 from View.Results.ReadingResultsDialog import ReadingResultsDialog
 from View.Debug.Window import ReplWindow
+from View.OutputRKAdisTS.Window import OutputRKAdisTSWindow
+from View.Pollutants.Window import PollutantsWindow
+from View.D90AdisTS.Window import D90AdisTSWindow
+from View.DIFAdisTS.Window import DIFAdisTSWindow
 
 # Optional internal display of documentation for make the application
 # package lighter...
@@ -118,7 +127,9 @@ define_model_action = [
     "action_menu_edit_hydraulic_structures", "action_menu_additional_file",
     "action_menu_results_last", "action_open_results_from_file",
     "action_menu_boundary_conditions_sediment",
-    "action_menu_rep_additional_lines",
+    "action_menu_rep_additional_lines", "action_menu_output_rk",
+    "action_menu_run_adists", "action_menu_pollutants",
+    "action_menu_d90", "action_menu_dif",
 ]
 
 action = (
@@ -228,6 +239,11 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
         """
         actions = {
             # Menu action
+            "action_menu_dif": self.open_dif,
+            "action_menu_d90": self.open_d90,
+            "action_menu_pollutants": self.open_pollutants,
+            "action_menu_run_adists":self.select_run_solver_adists,
+            "action_menu_output_rk": self.open_output_rk_adists,
             "action_menu_config": self.open_configure,
             "action_menu_new": self.open_new_study,
             "action_menu_edit": self.open_edit_study,
@@ -879,6 +895,70 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
     # SUB WINDOWS #
     ###############
 
+    def open_d90(self):
+        if len(self._study.river.d90_adists.lst) != 0:
+            d90_default = self._study.river.d90_adists.lst[0]
+        else:
+            d90_default = self._study.river.d90_adists.new(0)
+
+        if self.sub_window_exists(
+            D90AdisTSWindow,
+            data=[self._study, None, d90_default]
+        ):
+            return
+
+        D90AdisTS = D90AdisTSWindow(
+            study=self._study,
+            parent=self,
+            data=d90_default
+        )
+        D90AdisTS.show()
+
+    def open_dif(self):
+        if len(self._study.river.dif_adists.lst) != 0:
+            dif_default = self._study.river.dif_adists.lst[0]
+        else:
+            dif_default = self._study.river.dif_adists.new(0)
+
+        if self.sub_window_exists(
+            DIFAdisTSWindow,
+            data=[self._study, None, dif_default]
+        ):
+            return
+
+        DIFAdisTS = DIFAdisTSWindow(
+            study=self._study,
+            parent=self,
+            data=dif_default
+        )
+        DIFAdisTS.show()
+
+    def open_pollutants(self):
+        if self.sub_window_exists(
+            PollutantsWindow,
+            data=[self._study, None]
+        ):
+            return
+
+        Pollutants = PollutantsWindow(
+            study=self._study,
+            parent=self
+        )
+        Pollutants.show()
+
+    def open_output_rk_adists(self):
+        if self.sub_window_exists(
+            OutputRKAdisTSWindow,
+            data=[self._study, None]
+        ):
+            return
+
+        Output_RK_AdisTS = OutputRKAdisTSWindow(
+            study=self._study,
+            parent=self
+        )
+        Output_RK_AdisTS.show()
+
     def open_configure(self):
         """Open configure window
 
@@ -1204,6 +1284,47 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
         if run.exec():
             self.run_solver(run.solver)
 
+    def select_run_solver_adists(self):
+        if self._study is None:
+            return
+
+        #solver = next(filter(lambda x: x._type == "adistslc", self.conf.solvers))
+        #solver = next(filter(lambda x: x.name == "AdisTS-LC", self.conf.solvers))
+        #print(solver._type)
+        #self.run_solver(solver)
+
+        run = SelectSolverWindowAdisTS(
+            study=self._study,
+            config=self.conf,
+            parent=self
+        )
+        if run.exec():
+            self.run_solver_adists(run.solver, run.mage_rep)
+
+    def run_solver_adists(self, solver, mage_rep):
+        if self._study is None:
+            return
+
+        if self.sub_window_exists(
+            CheckListWindowAdisTS,
+            data=[
+                self._study,
+                self.conf,
+                solver,
+                mage_rep
+            ]
+        ):
+            return
+
+        check = CheckListWindowAdisTS(
+            study=self._study,
+            config=self.conf,
+            solver=solver,
+            parent=self,
+            mage_rep=mage_rep,
+        )
+        check.show()
+
     def run_solver(self, solver):
         if self._study is None:
             return
@@ -1226,6 +1347,16 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
         )
         check.show()
 
+    def solver_log_adists(self, solver, mage_rep):
+        sol = SolverLogWindowAdisTS(
+            study=self._study,
+            config=self.conf,
+            solver=solver,
+            parent=self,
+            mage_rep=mage_rep,
+        )
+        sol.show()
+
     def solver_log(self, solver):
         sol = SolverLogWindow(
             study=self._study,
@@ -1292,6 +1423,63 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
         )
         res.show()
 
+    def open_solver_results_adists(self, solver, results=None):
+        def reading_fn():
+            self._tmp_results = results
+
+        # If no specific results, get last results
+        if results is None:
+            def reading_fn():
+                self._tmp_results = self._last_results
+
+            if self._last_results is None:
+                def reading_fn():
+                    self._tmp_results = solver.results(
+                        self._study,
+                        self._solver_workdir(solver),
+                    )
+
+        # Open from file
+        if type(results) is str:
+            logger.info(f"Open results from {os.path.dirname(results)}")
+
+            name = os.path.basename(results).replace(".BIN", "")
+
+            def reading_fn():
+                self._tmp_results = solver.results(
+                    self._study,
+                    os.path.dirname(results),
+                    name=name
+                )
+
+        dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
+        dlg.exec_()
+        results = self._tmp_results
+
+        # No results available
+        if results is None:
+            return
+
+        # Windows already opened
+        if self.sub_window_exists(
+                ResultsWindowAdisTS,
+                data=[
+                    self._study,
+                    None,       # No config
+                    solver,
+                    results
+                ]
+        ):
+            return
+
+        res = ResultsWindowAdisTS(
+            study=self._study,
+            solver=solver,
+            results=results,
+            parent=self
+        )
+        res.show()
+
     def _solver_workdir(self, solver):
         workdir = os.path.join(
             os.path.dirname(self._study.filename),
diff --git a/src/View/OutputRKAdisTS/Table.py b/src/View/OutputRKAdisTS/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..4282ae0fa69d3a508ce104a08982c5254b1fd7bb
--- /dev/null
+++ b/src/View/OutputRKAdisTS/Table.py
@@ -0,0 +1,220 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.OutputRKAdisTS.UndoCommand import (
+    SetTitleCommand, SetReachCommand, SetRKCommand,
+    SetEnabledCommand, AddCommand, DelCommand,
+)
+
+from functools import reduce
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class ComboBoxDelegate(QItemDelegate):
+    def __init__(self, data=None, trad=None, parent=None, mode="reaches"):
+        super(ComboBoxDelegate, self).__init__(parent)
+
+        self._data = data
+        self._trad = trad
+        self._mode = mode
+
+    def createEditor(self, parent, option, index):
+        self.editor = QComboBox(parent)
+
+        val = []
+        if self._mode == "rk":
+            reach_id = self._data.Output_rk_adists \
+                .get(index.row()) \
+                .reach
+
+            reach = next(filter(lambda edge: edge.id == reach_id, self._data.edges()))
+
+            if reach_id is not None:
+                val = list(
+                    map(
+                        lambda rk: str(rk), reach.reach.get_rk()
+                    )
+                )
+        else:
+            val = list(
+                map(
+                    lambda n: n.name, self._data.edges()
+                )
+            )
+
+        self.editor.addItems(
+            [self._trad['not_associated']] +
+            val
+        )
+
+        self.editor.setCurrentText(str(index.data(Qt.DisplayRole)))
+        return self.editor
+
+    def setEditorData(self, editor, index):
+        value = index.data(Qt.DisplayRole)
+        self.editor.currentTextChanged.connect(self.currentItemChanged)
+
+    def setModelData(self, editor, model, index):
+        text = str(editor.currentText())
+        model.setData(index, text)
+        editor.close()
+        editor.deleteLater()
+
+    def updateEditorGeometry(self, editor, option, index):
+        r = QRect(option.rect)
+        if self.editor.windowFlags() & Qt.Popup:
+            if editor.parent() is not None:
+                r.setTopLeft(self.editor.parent().mapToGlobal(r.topLeft()))
+        editor.setGeometry(r)
+
+    @pyqtSlot()
+    def currentItemChanged(self):
+        self.commitData.emit(self.sender())
+
+
+class TableModel(PamhyrTableModel):
+    def _setup_lst(self):
+        self._lst = self._data._Output_rk_adists
+
+    def rowCount(self, parent):
+        return len(self._lst)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] == "title":
+            return self._lst.get(row).title
+        elif self._headers[column] == "reach":
+            n = self._lst.get(row).reach
+            if n is None:
+                return self._trad['not_associated']
+            return next(filter(lambda edge: edge.id == n, self._data.edges())).name
+        elif self._headers[column] == "rk":
+            n = self._lst.get(row).rk
+            if n is None:
+                return self._trad['not_associated']
+            return n
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+        na = self._trad['not_associated']
+
+        try:
+            if self._headers[column] == "title":
+                self._undo.push(
+                    SetTitleCommand(
+                        self._lst, row, value
+                    )
+                )
+            elif self._headers[column] == "reach":
+                if value == na:
+                    value = None
+
+                self._undo.push(
+                    SetReachCommand(
+                        self._lst, row, self._data.edge(value)
+                    )
+                )
+            elif self._headers[column] == "rk":
+                if value == na:
+                    value = None
+
+                self._undo.push(
+                    SetRKCommand(
+                        self._lst, row, value
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._lst, row
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._lst, rows
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+    def enabled(self, row, enabled, parent=QModelIndex()):
+        self._undo.push(
+            SetEnabledCommand(
+                self._lst, row, enabled
+            )
+        )
+        self.layoutChanged.emit()
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
diff --git a/src/View/OutputRKAdisTS/Translate.py b/src/View/OutputRKAdisTS/Translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f29ed0f9883b941dce781905af16abe467f732d
--- /dev/null
+++ b/src/View/OutputRKAdisTS/Translate.py
@@ -0,0 +1,40 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+
+class OutputRKAdisTSTranslate(MainTranslate):
+    def __init__(self):
+        super(OutputRKAdisTSTranslate, self).__init__()
+
+        self._dict["Output RK"] = _translate(
+            "OutputRKAdisTS", "Output RK"
+        )
+
+        self._dict["x"] = _translate("OutputRKAdisTS", "X (m)")
+
+        self._sub_dict["table_headers"] = {
+            "title": self._dict["title"],
+            "reach": self._dict["reach"],
+            "rk": self._dict["unit_rk"],
+        }
diff --git a/src/View/OutputRKAdisTS/UndoCommand.py b/src/View/OutputRKAdisTS/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..4c7be5d707cf544e933570843476932e695916ae
--- /dev/null
+++ b/src/View/OutputRKAdisTS/UndoCommand.py
@@ -0,0 +1,155 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+logger = logging.getLogger()
+
+
+class SetTitleCommand(QUndoCommand):
+    def __init__(self, outputrk_lst, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._outputrk_lst = outputrk_lst
+        self._index = index
+        self._old = self._outputrk_lst.get(self._index).title
+        self._new = str(new_value)
+
+    def undo(self):
+        self._outputrk_lst.get(self._index).title = self._old
+
+    def redo(self):
+        self._outputrk_lst.get(self._index).title = self._new
+
+
+class SetReachCommand(QUndoCommand):
+    def __init__(self, outputrk_lst, index, reach):
+        QUndoCommand.__init__(self)
+
+        self._outputrk_lst = outputrk_lst
+        self._index = index
+        self._old = self._outputrk_lst.get(self._index).reach
+        self._new = reach.id
+
+    def undo(self):
+        i = self._outputrk_lst.get(self._index)
+        i.reach = self._old
+
+    def redo(self):
+        i = self._outputrk_lst.get(self._index)
+        i.reach = self._new
+
+
+class SetRKCommand(QUndoCommand):
+    def __init__(self, outputrk_lst, index, rk):
+        QUndoCommand.__init__(self)
+
+        self._outputrk_lst = outputrk_lst
+        self._index = index
+        self._old = self._outputrk_lst.get(self._index).rk
+        self._new = rk
+
+    def undo(self):
+        self._outputrk_lst.get(self._index).rk = self._old
+
+    def redo(self):
+        self._outputrk_lst.get(self._index).rk = self._new
+
+
+class SetEnabledCommand(QUndoCommand):
+    def __init__(self, outputrk_lst, index, enabled):
+        QUndoCommand.__init__(self)
+
+        self._outputrk_lst = outputrk_lst
+        self._index = index
+        self._old = not enabled
+        self._new = enabled
+
+    def undo(self):
+        self._outputrk_lst.get(self._index).enabled = self._old
+
+    def redo(self):
+        self._outputrk_lst.get(self._index).enabled = self._new
+
+
+class AddCommand(QUndoCommand):
+    def __init__(self, outputrk_lst, index):
+        QUndoCommand.__init__(self)
+
+        self._outputrk_lst = outputrk_lst
+
+        self._index = index
+        self._new = None
+
+    def undo(self):
+        self._outputrk_lst.delete_i([self._index])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._outputrk_lst.new(self._outputrk_lst, self._index)
+        else:
+            self._outputrk_lst.insert(self._index, self._new)
+
+
+class DelCommand(QUndoCommand):
+    def __init__(self, outputrk_lst, rows):
+        QUndoCommand.__init__(self)
+
+        self._outputrk_lst = outputrk_lst
+
+        self._rows = rows
+
+        self._outputrk = []
+        for row in rows:
+            self._outputrk.append((row, self._outputrk_lst.get(row)))
+        self._outputrk.sort()
+
+    def undo(self):
+        for row, el in self._outputrk:
+            self._outputrk_lst.insert(row, el)
+
+    def redo(self):
+        self._outputrk_lst.delete_i(self._rows)
+
+
+class PasteCommand(QUndoCommand):
+    def __init__(self, outputrk_lst, row, outputrk):
+        QUndoCommand.__init__(self)
+
+        self._outputrk_lst = outputrk_lst
+
+        self._row = row
+        self._outputrk = deepcopy(outputrk)
+        self._outputrk.reverse()
+
+    def undo(self):
+        self._outputrk_lst.delete_i(
+            self._tab,
+            range(self._row, self._row + len(self._outputrk))
+        )
+
+    def redo(self):
+        for r in self._outputrk:
+            self._outputrk_lst.insert(self._row, r)
diff --git a/src/View/OutputRKAdisTS/Window.py b/src/View/OutputRKAdisTS/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..a17e5544cc9d92edbfe5f063c941470c52e6e195
--- /dev/null
+++ b/src/View/OutputRKAdisTS/Window.py
@@ -0,0 +1,209 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import timer, trace
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5 import QtCore
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel, QCoreApplication,
+    pyqtSlot, pyqtSignal, QItemSelectionModel,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
+)
+
+from View.Tools.Plot.PamhyrCanvas import MplCanvas
+from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
+
+from View.OutputRKAdisTS.Table import (
+    TableModel, ComboBoxDelegate,
+)
+
+from View.Network.GraphWidget import GraphWidget
+from View.OutputRKAdisTS.Translate import OutputRKAdisTSTranslate
+
+logger = logging.getLogger()
+
+
+class OutputRKAdisTSWindow(PamhyrWindow):
+    _pamhyr_ui = "OutputRKAdisTS"
+    _pamhyr_name = "Output RK"
+
+    def __init__(self, study=None, config=None, parent=None):
+        trad = OutputRKAdisTSTranslate()
+        name = trad[self._pamhyr_name] + " - " + study.name
+
+        super(OutputRKAdisTSWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        self._outputrk_lst = self._study.river._Output_rk_adists
+
+        self.setup_table()
+        self.setup_checkbox()
+        self.setup_connections()
+
+        self.update()
+
+    def setup_table(self):
+        self._table = None
+
+        self._delegate_reach = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            parent=self,
+            mode="reaches"
+        )
+        self._delegate_rk = ComboBoxDelegate(
+            trad=self._trad,
+            data=self._study.river,
+            parent=self,
+            mode="rk"
+        )
+
+        table = self.find(QTableView, f"tableView")
+        self._table = TableModel(
+            table_view=table,
+            table_headers=self._trad.get_dict("table_headers"),
+            editable_headers=["title", "reach", "rk"],
+            delegates={
+                "reach": self._delegate_reach,
+                "rk": self._delegate_rk,
+            },
+            trad=self._trad,
+            data=self._study.river,
+            undo=self._undo_stack,
+        )
+
+        selectionModel = table.selectionModel()
+        index = table.model().index(0, 0)
+
+        selectionModel.select(
+            index,
+            QItemSelectionModel.Rows |
+            QItemSelectionModel.ClearAndSelect |
+            QItemSelectionModel.Select
+        )
+        table.scrollTo(index)
+
+    def setup_checkbox(self):
+        self._checkbox = self.find(QCheckBox, f"checkBox")
+        self._set_checkbox_state()
+
+    def setup_connections(self):
+        self.find(QAction, "action_add").triggered.connect(self.add)
+        self.find(QAction, "action_delete").triggered.connect(self.delete)
+        self._checkbox.clicked.connect(self._set_outputrk_state)
+
+        table = self.find(QTableView, "tableView")
+        table.selectionModel()\
+             .selectionChanged\
+             .connect(self.update)
+
+        self._table.dataChanged.connect(self.update)
+        self._table.layoutChanged.connect(self.update)
+
+    def index_selected(self):
+        table = self.find(QTableView, "tableView")
+        r = table.selectionModel().selectedRows()
+
+        if len(r) > 0:
+            return r[0]
+        else:
+            return None
+
+    def index_selected_row(self):
+        table = self.find(QTableView, "tableView")
+        r = table.selectionModel().selectedRows()
+
+        if len(r) > 0:
+            return r[0].row()
+        else:
+            return None
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, "tableView")
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._outputrk_lst) == 0 or len(rows) == 0:
+            self._table.add(0)
+        else:
+            self._table.add(rows[0])
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            return
+
+        self._table.delete(rows)
+
+    def _copy(self):
+        logger.info("TODO: copy")
+
+    def _paste(self):
+        logger.info("TODO: paste")
+
+    def _undo(self):
+        self._table.undo()
+
+    def _redo(self):
+        self._table.redo()
+
+    def _set_checkbox_state(self):
+        row = self.index_selected_row()
+        if row is None:
+            self._checkbox.setEnabled(False)
+            self._checkbox.setChecked(True)
+        else:
+            self._checkbox.setEnabled(True)
+            self._checkbox.setChecked(self._outputrk_lst.get(row).enabled)
+
+    def _set_outputrk_state(self):
+        rows = self.index_selected_rows()
+        if len(rows) != 0:
+            for row in rows:
+                if row is not None:
+                    self._table.enabled(
+                        row,
+                        self._checkbox.isChecked()
+                    )
+
+    def update(self):
+        self._set_checkbox_state()
diff --git a/src/View/Pollutants/Edit/Table.py b/src/View/Pollutants/Edit/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..aa3c919b0f0cd997b562efbfd8edec37d0319aa6
--- /dev/null
+++ b/src/View/Pollutants/Edit/Table.py
@@ -0,0 +1,106 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+
+from datetime import date, time, datetime, timedelta
+
+from tools import trace, timer
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect, QTime, QDateTime,
+)
+
+from PyQt5.QtWidgets import (
+    QTableView, QAbstractItemView, QSpinBox, QItemDelegate,
+)
+
+from View.Pollutants.Edit.UndoCommand import (
+    SetDataCommand, PasteCommand,
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class TableModel(PamhyrTableModel):
+    def data(self, index, role):
+        if role == Qt.TextAlignmentRole:
+            return Qt.AlignHCenter | Qt.AlignVCenter
+
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        return self._data.data[row][column]
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] == "type":
+                self._undo.push(
+                    SetDataCommand(
+                        self._data, row, column, int(value)
+                    )
+                )
+            else:
+                self._undo.push(
+                    SetDataCommand(
+                        self._data, row, column, float(value)
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def paste(self, row, data):
+        if len(data) == 0:
+            return
+
+        self.layoutAboutToBeChanged.emit()
+
+        self._undo.push(
+            PasteCommand(
+                self._data, row,
+                list(
+                    map(
+                        lambda d: self._data.new_from_data(d),
+                        data
+                    )
+                )
+            )
+        )
+
+        self.layoutAboutToBeChanged.emit()
+        self.layoutChanged.emit()
diff --git a/src/View/Pollutants/Edit/Translate.py b/src/View/Pollutants/Edit/Translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..acdcab31f3948eb3ee6fd7b847a235cfe0994482
--- /dev/null
+++ b/src/View/Pollutants/Edit/Translate.py
@@ -0,0 +1,46 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+from View.Pollutants.Translate import PollutantsTranslate
+
+_translate = QCoreApplication.translate
+
+
+class EditPollutantTranslate(PollutantsTranslate):
+    def __init__(self):
+        super(EditPollutantTranslate, self).__init__()
+
+        self._dict["Edit Pollutant"] = _translate(
+            "Pollutants", "Edit Pollutant"
+        )
+
+        self._sub_dict["table_headers"] = {
+            "type": self._dict["type"],
+            "diametre": self._dict["unit_area"],
+            "rho": self._dict["unit_rho"],
+            "porosity": self._dict["unit_porosity"],
+            "cdc_riv": self._dict["unit_cdc_riv"],
+            "cdc_cas": self._dict["unit_cdc_cas"],
+            "apd": self._dict["unit_apd"],
+            "ac": self._dict["unit_ac"],
+            "bc": self._dict["unit_bc"],
+        }
diff --git a/src/View/Pollutants/Edit/UndoCommand.py b/src/View/Pollutants/Edit/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..4a30340adfce2b5d88a5855d52c48ae5f5ae7e69
--- /dev/null
+++ b/src/View/Pollutants/Edit/UndoCommand.py
@@ -0,0 +1,65 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+from Model.Pollutants.Pollutants import Pollutants
+
+logger = logging.getLogger()
+
+
+class SetDataCommand(QUndoCommand):
+    def __init__(self, data, index, column, new_value):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._index = index
+        self._column = column
+        self._old = self._data.data[self._index][self._column]
+        self._new = new_value
+
+    def undo(self):
+        self._data.data[self._index][self._column] = self._old
+
+    def redo(self):
+        self._data.data[self._index][self._column] = self._new
+
+class PasteCommand(QUndoCommand):
+    def __init__(self, data, row, hs):
+        QUndoCommand.__init__(self)
+
+        self._data = data
+        self._row = row
+        self._h = hs
+        self._h.reverse()
+
+    def undo(self):
+        self._data.delete_i(
+            range(self._row, self._row + len(self._h))
+        )
+
+    def redo(self):
+        for h in self._h:
+            self._data.insert(self._row, h)
diff --git a/src/View/Pollutants/Edit/Window.py b/src/View/Pollutants/Edit/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f5351916bb73ef94bbc90b2e74b2cdfb0ec4c2c
--- /dev/null
+++ b/src/View/Pollutants/Edit/Window.py
@@ -0,0 +1,148 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import timer, trace
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+from View.Tools.PamhyrWidget import PamhyrWidget
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5 import QtCore
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel, QCoreApplication,
+    pyqtSlot, pyqtSignal,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QHeaderView, QDoubleSpinBox, QVBoxLayout,
+)
+
+from View.Pollutants.Edit.Translate import EditPollutantTranslate
+from View.Pollutants.Edit.Table import TableModel
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class EditPolluantWindow(PamhyrWindow):
+    _pamhyr_ui = "Pollutant"
+    _pamhyr_name = "Edit Pollutant"
+
+    def __init__(self, data=None, study=None, config=None, parent=None):
+        self._data = data
+        trad = EditPollutantTranslate()
+
+        name = trad[self._pamhyr_name]
+        if self._data is not None:
+            name += (
+                f" - {study.name} " +
+                f" - {self._data.name}"
+            )
+
+        super(EditPolluantWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        self._hash_data.append(data)
+
+        self.setup_table()
+
+    def setup_table(self):
+        headers = {}
+        table_headers = self._trad.get_dict("table_headers")
+
+        table = self.find(QTableView, "tableView")
+        self._table = TableModel(
+            table_view=table,
+            table_headers=table_headers,
+            editable_headers=table_headers,
+            delegates={},
+            data=self._data,
+            undo=self._undo_stack,
+            opt_data=self._study.time_system
+        )
+
+        table.setModel(self._table)
+        table.setSelectionBehavior(QAbstractItemView.SelectRows)
+        table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
+        table.setAlternatingRowColors(True)
+
+    def index_selected_row(self):
+        table = self.find(QTableView, "tableView")
+        return table.selectionModel()\
+                    .selectedRows()[0]\
+                    .row()
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, "tableView")
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def _copy(self):
+        rows = self.index_selected_rows()
+
+        table = []
+        # table.append(self._data.header)
+        table.append(self._trad.get_dict("table_headers"))
+
+        data = self._data.data
+        for row in rows:
+            table.append(list(data[row]))
+
+        self.copyTableIntoClipboard(table)
+
+    def _paste(self):
+        header, data = self.parseClipboardTable()
+
+        logger.debug(f"paste: h:{header}, d:{data}")
+
+        if len(data) == 0:
+            return
+
+        row = 0
+        rows = self.index_selected_rows()
+        if len(rows) != 0:
+            row = rows[0]
+
+        self._table.paste(row, data)
+
+    def _undo(self):
+        self._table.undo()
+
+    def _redo(self):
+        self._table.redo()
diff --git a/src/View/Pollutants/Table.py b/src/View/Pollutants/Table.py
new file mode 100644
index 0000000000000000000000000000000000000000..c3bbcd4dcd7916642c45545b65ffed93ad59c7fc
--- /dev/null
+++ b/src/View/Pollutants/Table.py
@@ -0,0 +1,126 @@
+# Table.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+
+from tools import trace, timer
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+
+from View.Pollutants.UndoCommand import (
+    SetNameCommand,
+    SetEnabledCommand, AddCommand, DelCommand,
+)
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+class TableModel(PamhyrTableModel):
+    def _setup_lst(self):
+        self._lst = self._data._Pollutants
+
+    def rowCount(self, parent):
+        return len(self._lst)
+
+    def data(self, index, role):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._headers[column] == "name":
+            return self._lst.get(row).name
+
+        return QVariant()
+
+    def setData(self, index, value, role=Qt.EditRole):
+        if not index.isValid() or role != Qt.EditRole:
+            return False
+
+        row = index.row()
+        column = index.column()
+
+        try:
+            if self._headers[column] == "name":
+                self._undo.push(
+                    SetNameCommand(
+                        self._lst, row, value
+                    )
+                )
+        except Exception as e:
+            logger.info(e)
+            logger.debug(traceback.format_exc())
+
+        self.dataChanged.emit(index, index)
+        return True
+
+    def add(self, row, parent=QModelIndex()):
+        self.beginInsertRows(parent, row, row - 1)
+
+        self._undo.push(
+            AddCommand(
+                self._lst, row, self._data.initial_conditions_adists
+            )
+        )
+
+        self.endInsertRows()
+        self.layoutChanged.emit()
+
+    def delete(self, rows, parent=QModelIndex()):
+        self.beginRemoveRows(parent, rows[0], rows[-1])
+
+        self._undo.push(
+            DelCommand(
+                self._lst, rows, self._data.initial_conditions_adists
+            )
+        )
+
+        self.endRemoveRows()
+        self.layoutChanged.emit()
+
+    def enabled(self, row, enabled, parent=QModelIndex()):
+        self._undo.push(
+            SetEnabledCommand(
+                self._lst, row, enabled
+            )
+        )
+        self.layoutChanged.emit()
+
+    def undo(self):
+        self._undo.undo()
+        self.layoutChanged.emit()
+
+    def redo(self):
+        self._undo.redo()
+        self.layoutChanged.emit()
diff --git a/src/View/Pollutants/Translate.py b/src/View/Pollutants/Translate.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed2c9fc30a02e23a193a7ad8fad2794e6d017c42
--- /dev/null
+++ b/src/View/Pollutants/Translate.py
@@ -0,0 +1,38 @@
+# translate.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+from PyQt5.QtCore import QCoreApplication
+
+from View.Translate import MainTranslate
+
+_translate = QCoreApplication.translate
+
+
+class PollutantsTranslate(MainTranslate):
+    def __init__(self):
+        super(PollutantsTranslate, self).__init__()
+
+        self._dict["Pollutants"] = _translate(
+            "Pollutants", "Pollutants"
+        )
+
+        self._dict["x"] = _translate("Pollutants", "X (m)")
+
+        self._sub_dict["table_headers"] = {
+            "name": self._dict["name"],
+        }
diff --git a/src/View/Pollutants/UndoCommand.py b/src/View/Pollutants/UndoCommand.py
new file mode 100644
index 0000000000000000000000000000000000000000..b60181aece260e2f5eaf6597e2f4ce946996e4b0
--- /dev/null
+++ b/src/View/Pollutants/UndoCommand.py
@@ -0,0 +1,135 @@
+# UndoCommand.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from copy import deepcopy
+from tools import trace, timer
+
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+)
+
+logger = logging.getLogger()
+
+
+class SetNameCommand(QUndoCommand):
+    def __init__(self, pollutants_lst, index, new_value):
+        QUndoCommand.__init__(self)
+
+        self._pollutants_lst = pollutants_lst
+        self._index = index
+        self._old = self._pollutants_lst.get(self._index).name
+        self._new = str(new_value)
+
+    def undo(self):
+        self._pollutants_lst.get(self._index).name = self._old
+
+    def redo(self):
+        self._pollutants_lst.get(self._index).name = self._new
+
+class SetEnabledCommand(QUndoCommand):
+    def __init__(self, pollutants_lst, index, enabled):
+        QUndoCommand.__init__(self)
+
+        self._pollutants_lst = pollutants_lst
+        self._index = index
+        self._old = not enabled
+        self._new = enabled
+
+    def undo(self):
+        self._pollutants_lst.get(self._index).enabled = self._old
+
+    def redo(self):
+        self._pollutants_lst.get(self._index).enabled = self._new
+
+
+class AddCommand(QUndoCommand):
+    def __init__(self, pollutants_lst, index, data):
+        QUndoCommand.__init__(self)
+
+        self._pollutants_lst = pollutants_lst
+
+        self._index = index
+        self._new = None
+        self._new_ic_adists = None
+        self._data = data
+
+    def undo(self):
+        self._pollutants_lst.delete_i([self._index])
+        self._data.delete_i([self._index])
+
+    def redo(self):
+        if self._new is None:
+            self._new = self._pollutants_lst.new(self._pollutants_lst, self._index)
+            self._new._data = [[0, 0., 0., 0., 0., 0., 0., 0., 0.]]
+            self._new_ic_adists = self._data.new(self._index, self._new.id)
+        else:
+            self._pollutants_lst.insert(self._index, self._new)
+            self._data.insert(self._index, self._new_ic_adists)
+
+
+class DelCommand(QUndoCommand):
+    def __init__(self, pollutants_lst, rows, data):
+        QUndoCommand.__init__(self)
+
+        self._pollutants_lst = pollutants_lst
+        self._inc_pollutants_lst = data
+
+        self._rows = rows
+
+        self._pollutants = []
+        self._inc_pollutants = []
+
+        for row in rows:
+            self._pollutants.append((row, self._pollutants_lst.get(row)))
+            self._inc_pollutants.append((row, self._inc_pollutants_lst))
+        self._pollutants.sort()
+        self._inc_pollutants.sort()
+
+    def undo(self):
+        for row, el in self._pollutants:
+            self._pollutants_lst.insert(row, el)
+
+        for row, el in self._inc_pollutants:
+            self._inc_pollutants_lst.insert(row, el)
+
+    def redo(self):
+        self._pollutants_lst.delete_i(self._rows)
+        self._inc_pollutants_lst.delete_i(self._rows)
+
+
+class PasteCommand(QUndoCommand):
+    def __init__(self, pollutants_lst, row, pollutant):
+        QUndoCommand.__init__(self)
+
+        self._pollutants_lst = pollutants_lst
+
+        self._row = row
+        self._pollutant = deepcopy(pollutant)
+        self._pollutant.reverse()
+
+    def undo(self):
+        self._pollutants_lst.delete_i(
+            self._tab,
+            range(self._row, self._row + len(self._pollutant))
+        )
+
+    def redo(self):
+        for r in self._pollutant:
+            self._pollutants_lst.insert(self._row, r)
diff --git a/src/View/Pollutants/Window.py b/src/View/Pollutants/Window.py
new file mode 100644
index 0000000000000000000000000000000000000000..a87c791fb1ac5f8a56c1173488ffe99f7a3c9558
--- /dev/null
+++ b/src/View/Pollutants/Window.py
@@ -0,0 +1,279 @@
+# Window.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from tools import timer, trace
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5 import QtCore
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel, QCoreApplication,
+    pyqtSlot, pyqtSignal, QItemSelectionModel,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QHeaderView, QDoubleSpinBox, QVBoxLayout, QCheckBox
+)
+
+from View.Pollutants.Table import (
+    TableModel
+)
+
+from View.Pollutants.Translate import PollutantsTranslate
+
+from View.Pollutants.Edit.Window import EditPolluantWindow
+
+from View.InitialConditionsAdisTS.Window import InitialConditionsAdisTSWindow
+from View.BoundaryConditionsAdisTS.Window import BoundaryConditionAdisTSWindow
+from View.LateralContributionsAdisTS.Window import LateralContributionAdisTSWindow
+
+logger = logging.getLogger()
+
+
+class PollutantsWindow(PamhyrWindow):
+    _pamhyr_ui = "Pollutants"
+    _pamhyr_name = "Pollutants"
+
+    def __init__(self, study=None, config=None, parent=None):
+        trad = PollutantsTranslate()
+        name = trad[self._pamhyr_name] + " - " + study.name
+
+        super(PollutantsWindow, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        self._pollutants_lst = self._study._river._Pollutants
+
+        self.setup_table()
+        self.setup_checkbox()
+        self.setup_connections()
+
+        self.update()
+
+    def setup_table(self):
+        self._table = None
+
+        table = self.find(QTableView, f"tableView")
+        self._table = TableModel(
+            table_view=table,
+            table_headers=self._trad.get_dict("table_headers"),
+            editable_headers=["name"],
+            trad=self._trad,
+            data=self._study.river,
+            undo=self._undo_stack,
+        )
+
+        selectionModel = table.selectionModel()
+        index = table.model().index(0, 0)
+
+        selectionModel.select(
+            index,
+            QItemSelectionModel.Rows |
+            QItemSelectionModel.ClearAndSelect |
+            QItemSelectionModel.Select
+        )
+        table.scrollTo(index)
+
+    def setup_checkbox(self):
+        self._checkbox = self.find(QCheckBox, f"checkBox")
+        self._set_checkbox_state()
+
+    def setup_connections(self):
+        self.find(QAction, "action_add").triggered.connect(self.add)
+        self.find(QAction, "action_delete").triggered.connect(self.delete)
+        self.find(QAction, "action_edit").triggered.connect(self.edit)
+        self.find(QAction, "action_initial_conditions").triggered.connect(self.initial_conditions)
+        self.find(QAction, "action_boundary_conditions").triggered.connect(self.boundary_conditions)
+        self.find(QAction, "action_lateral_contributions").triggered.connect(self.lateral_contrib)
+        self._checkbox.clicked.connect(self._set_structure_state)
+
+        table = self.find(QTableView, "tableView")
+        table.selectionModel()\
+             .selectionChanged\
+             .connect(self.update)
+
+        self._table.dataChanged.connect(self.update)
+        self._table.layoutChanged.connect(self.update)
+
+    def index_selected(self):
+        table = self.find(QTableView, "tableView")
+        r = table.selectionModel().selectedRows()
+
+        if len(r) > 0:
+            return r[0]
+        else:
+            return None
+
+    def index_selected_row(self):
+        table = self.find(QTableView, "tableView")
+        r = table.selectionModel().selectedRows()
+
+        if len(r) > 0:
+            return r[0].row()
+        else:
+            return None
+
+    def index_selected_rows(self):
+        table = self.find(QTableView, "tableView")
+        return list(
+            # Delete duplicate
+            set(
+                map(
+                    lambda i: i.row(),
+                    table.selectedIndexes()
+                )
+            )
+        )
+
+    def add(self):
+        rows = self.index_selected_rows()
+        if len(self._pollutants_lst) == 0 or len(rows) == 0:
+            self._table.add(0)
+        else:
+            self._table.add(rows[0])
+
+    def delete(self):
+        rows = self.index_selected_rows()
+        if len(rows) == 0:
+            return
+
+        self._table.delete(rows)
+
+    def _copy(self):
+        logger.info("TODO: copy")
+
+    def _paste(self):
+        logger.info("TODO: paste")
+
+    def _undo(self):
+        self._table.undo()
+
+    def _redo(self):
+        self._table.redo()
+
+    def edit(self):
+        rows = self.index_selected_rows()
+        for row in rows:
+            data = self._pollutants_lst.get(row)
+
+            if self.sub_window_exists(
+                EditPolluantWindow,
+                data=[self._study, None, data]
+            ):
+                continue
+
+            win = EditPolluantWindow(
+                data=data,
+                study=self._study,
+                parent=self
+            )
+            win.show()
+
+    def initial_conditions(self):
+        rows = self.index_selected_rows()
+
+        for row in rows:
+            pollutant_id = self._pollutants_lst.get(row).id
+
+            ics_adists = next(filter(lambda x: x.pollutant == pollutant_id,
+                                     self._study.river.initial_conditions_adists.lst))
+
+            if self.sub_window_exists(
+                InitialConditionsAdisTSWindow,
+                data=[self._study, None, ics_adists]
+            ):
+                return
+
+            initial = InitialConditionsAdisTSWindow(
+                study=self._study,
+                parent=self,
+                data=ics_adists
+            )
+            initial.show()
+
+    def boundary_conditions(self, tab=0):
+        rows = self.index_selected_rows()
+
+        for row in rows:
+            pollutant_id = self._pollutants_lst.get(row).id
+
+        if self.sub_window_exists(
+            BoundaryConditionAdisTSWindow,
+            data=[self._study, None, pollutant_id]
+        ):
+            bound = self.get_sub_window(
+                BoundaryConditionAdisTSWindow,
+                data=[self._study, None, pollutant_id]
+            )
+            return
+
+        bound = BoundaryConditionAdisTSWindow(study=self._study, pollutant=pollutant_id, parent=self)
+        bound.show()
+
+    def lateral_contrib(self):
+        rows = self.index_selected_rows()
+
+        for row in rows:
+            pollutant_id = self._pollutants_lst.get(row).id
+
+        if self.sub_window_exists(
+            LateralContributionAdisTSWindow,
+            data=[self._study, None, pollutant_id]
+        ):
+            return
+
+        lateral = LateralContributionAdisTSWindow(
+            study=self._study,
+            parent=self,
+            pollutant=pollutant_id
+        )
+        lateral.show()
+
+    def _set_checkbox_state(self):
+        row = self.index_selected_row()
+        if row is None:
+            self._checkbox.setEnabled(False)
+            self._checkbox.setChecked(True)
+        else:
+            self._checkbox.setEnabled(True)
+            self._checkbox.setChecked(self._pollutants_lst.get(row).enabled)
+
+    def _set_structure_state(self):
+        rows = self.index_selected_rows()
+        if len(rows) != 0:
+            for row in rows:
+                if row is not None:
+                    self._table.enabled(
+                        row,
+                        self._checkbox.isChecked()
+                    )
+
+    def update(self):
+        self._set_checkbox_state()
+
+
+
diff --git a/src/View/Results/PlotCAdisTS.py b/src/View/Results/PlotCAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..60c3ed684d8a56080df5db84bb4a6b61a8c27086
--- /dev/null
+++ b/src/View/Results/PlotCAdisTS.py
@@ -0,0 +1,162 @@
+# PlotCAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from functools import reduce
+from datetime import datetime
+
+from tools import timer, trace
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class PlotC(PamhyrPlot):
+    def __init__(self, canvas=None, trad=None, toolbar=None,
+                 results=None, reach_id=0, profile_id=0, pol_id=0,
+                 parent=None):
+        super(PlotC, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=results,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self._mode = "time"
+
+        self._current_timestamp = max(results.get("timestamps"))
+        self._current_reach_id = reach_id
+        self._current_profile_id = profile_id
+        self._current_pol_id = pol_id
+
+        self.label_x = _translate("Results", "Time (s)")
+        self.label_y = _translate("Results", "Discharge (m³/s)")
+
+        self.label_discharge = _translate("Results", "Cross-section discharge")
+        self.label_discharge_max = _translate("Results", "Max discharge")
+        self.label_timestamp = _translate("Results", "Current timestamp")
+
+        self._isometric_axis = False
+
+        self._auto_relim_update = False
+        self._autoscale_update = False
+
+    @property
+    def results(self):
+        return self.data
+
+    @results.setter
+    def results(self, results):
+        self.data = results
+        self._current_timestamp = max(results.get("timestamps"))
+
+    @timer
+    def draw(self, highlight=None):
+        self.init_axes()
+
+        if self.results is None:
+            return
+
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        if reach.geometry.number_profiles == 0:
+            self._init = False
+            return
+
+        self.draw_data(reach, profile, pollutant)
+
+        self.set_ticks_time_formater()
+
+        self.enable_legend()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self, reach, profile, pollutant):
+        self.ts = list(self.results.get("timestamps"))
+        self.ts.sort()
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        #First 0 for pol and second 0 for phys var
+        y = list(map(lambda data_el: data_el[pollutant][0], profile.get_key("pols")))
+
+        ###print("************//////////////////")
+        ###print("profile: ", self._current_profile_id)
+        ###print("reach: ", self._current_reach_id)
+        ###print("pollutant: ", pollutant)
+
+        ###print("*****************draw data: ", len(x),len(y))
+        ###print("x: ", x)
+        ###print("y: ", y)
+        ###print("reach: ", reach)
+        ###print("profile: ", profile)
+        ###print("pollutant: ", pollutant)
+
+        self._line, = self.canvas.axes.plot(
+            x, y,
+            label=self.label_discharge,
+            color=self.color_plot,
+            **self.plot_default_kargs
+        )
+
+    def set_reach(self, reach_id):
+        self._current_reach_id = reach_id
+        self._current_profile_id = 0
+        self.draw()
+
+    def set_profile(self, profile_id):
+        self._current_profile_id = profile_id
+        self.update()
+
+    def set_pollutant(self, pol_id):
+        self._current_pol_id = pol_id
+        self.update()
+
+    def set_timestamp(self, timestamp):
+        self._current_timestamp = timestamp
+        self.update()
+
+    def update(self):
+        if not self._init:
+            self.draw()
+
+        self.update_data()
+
+        self.update_idle()
+
+    def update_data(self):
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        y = list(map(lambda data_el: data_el[pollutant][0], profile.get_key("pols")))
+
+        self._line.set_data(x, y)
diff --git a/src/View/Results/PlotMAdisTS.py b/src/View/Results/PlotMAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b439cb6ebfde5c662e40ed648b6b38377022292
--- /dev/null
+++ b/src/View/Results/PlotMAdisTS.py
@@ -0,0 +1,173 @@
+# PlotMAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from functools import reduce
+from datetime import datetime
+
+import numpy as np
+
+from tools import timer, trace
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class PlotM(PamhyrPlot):
+    def __init__(self, canvas=None, trad=None, toolbar=None,
+                 results=None, reach_id=0, profile_id=0, pol_id=0,
+                 parent=None):
+        super(PlotM, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=results,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self._mode = "time"
+
+        self._current_timestamp = max(results.get("timestamps"))
+        self._current_reach_id = reach_id
+        self._current_profile_id = profile_id
+        self._current_pol_id = pol_id
+
+        self.label_x = _translate("Results", "Time (s)")
+        self.label_y = _translate("Results", "Discharge (m³/s)")
+
+        self.label_discharge = _translate("Results", "Cross-section discharge")
+        self.label_discharge_max = _translate("Results", "Max discharge")
+        self.label_timestamp = _translate("Results", "Current timestamp")
+
+        self._isometric_axis = False
+
+        self._auto_relim_update = False
+        self._autoscale_update = False
+
+    @property
+    def results(self):
+        return self.data
+
+    @results.setter
+    def results(self, results):
+        self.data = results
+        self._current_timestamp = max(results.get("timestamps"))
+
+    @timer
+    def draw(self, highlight=None):
+        self.init_axes()
+
+        if self.results is None:
+            return
+
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        if reach.geometry.number_profiles == 0:
+            self._init = False
+            return
+
+        self.draw_data(reach, profile, pollutant)
+
+        self.set_ticks_time_formater()
+
+        self.enable_legend()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self, reach, profile, pollutant):
+        self.ts = list(self.results.get("timestamps"))
+        self.ts.sort()
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        #First 0 for pol and second 0 for phys var
+        y1 = list(map(lambda data_el: data_el[pollutant][1], profile.get_key("pols")))
+        y2 = list(map(lambda data_el: data_el[pollutant][2], profile.get_key("pols")))
+        y3 = list(map(lambda data_el: data_el[pollutant][3], profile.get_key("pols")))
+
+        y = (np.array(y1) + np.array(y2) + np.array(y3)).tolist()
+
+        ###print("************//////////////////")
+        ###print("profile: ", self._current_profile_id)
+        ###print("reach: ", self._current_reach_id)
+        ###print("pollutant: ", pollutant)
+
+        ###print("*****************draw data: ", len(x),len(y))
+        ###print("x: ", x)
+        ###print("y: ", y)
+        ###print("reach: ", reach)
+        ###print("profile: ", profile)
+        ###print("pollutant: ", pollutant)
+
+        self._line, = self.canvas.axes.plot(
+            x, y,
+            label=self.label_discharge,
+            color=self.color_plot,
+            **self.plot_default_kargs
+        )
+
+    def set_reach(self, reach_id):
+        self._current_reach_id = reach_id
+        self._current_profile_id = 0
+        self.draw()
+
+    def set_profile(self, profile_id):
+        self._current_profile_id = profile_id
+        self.update()
+
+    def set_pollutant(self, pol_id):
+        self._current_pol_id = pol_id
+        self.update()
+
+    def set_timestamp(self, timestamp):
+        self._current_timestamp = timestamp
+        self.update()
+
+    def update(self):
+        if not self._init:
+            self.draw()
+
+        self.update_data()
+
+        self.update_idle()
+
+    def update_data(self):
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        x = self.ts
+        #y = profile.get_key("Q")
+
+        y1 = list(map(lambda data_el: data_el[pollutant][1], profile.get_key("pols")))
+        y2 = list(map(lambda data_el: data_el[pollutant][2], profile.get_key("pols")))
+        y3 = list(map(lambda data_el: data_el[pollutant][3], profile.get_key("pols")))
+
+        y = (np.array(y1) + np.array(y2) + np.array(y3)).tolist()
+
+        self._line.set_data(x, y)
diff --git a/src/View/Results/PlotTotSedCAdisTS.py b/src/View/Results/PlotTotSedCAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..bcf00b08ef5fb32c1f0d4958071da1b6fb50136e
--- /dev/null
+++ b/src/View/Results/PlotTotSedCAdisTS.py
@@ -0,0 +1,160 @@
+# PlotTotSedCAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from functools import reduce
+from datetime import datetime
+
+import numpy as np
+
+from tools import timer, trace
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class PlotTotSedC(PamhyrPlot):
+    def __init__(self, canvas=None, trad=None, toolbar=None,
+                 results=None, reach_id=0, profile_id=0, pol_id=0,
+                 parent=None):
+        super(PlotTotSedC, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=results,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self._mode = "time"
+
+        self._current_timestamp = max(results.get("timestamps"))
+        self._current_reach_id = reach_id
+        self._current_profile_id = profile_id
+        self._current_pol_id = pol_id
+
+        self.label_x = _translate("Results", "Time (s)")
+        self.label_y = _translate("Results", "Discharge (m³/s)")
+
+        self.label_discharge = _translate("Results", "Cross-section discharge")
+        self.label_discharge_max = _translate("Results", "Max discharge")
+        self.label_timestamp = _translate("Results", "Current timestamp")
+
+        self._isometric_axis = False
+
+        self._auto_relim_update = False
+        self._autoscale_update = False
+
+    @property
+    def results(self):
+        return self.data
+
+    @results.setter
+    def results(self, results):
+        self.data = results
+        self._current_timestamp = max(results.get("timestamps"))
+
+    @timer
+    def draw(self, highlight=None):
+        self.init_axes()
+
+        if self.results is None:
+            return
+
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        if reach.geometry.number_profiles == 0:
+            self._init = False
+            return
+
+        self.draw_data(reach, profile, pollutant)
+
+        self.set_ticks_time_formater()
+
+        self.enable_legend()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self, reach, profile, pollutant):
+        self.ts = list(self.results.get("timestamps"))
+        self.ts.sort()
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        #First 0 for pol and second 0 for phys var
+        y = list(map(lambda data_el: data_el[pollutant][0], profile.get_key("pols")))
+
+        print("Total Sed C************//////////////////")
+        print("profile: ", self._current_profile_id)
+        print("reach: ", self._current_reach_id)
+        print("pollutant: ", pollutant)
+
+        ###print("*****************draw data: ", len(x),len(y))
+        ###print("x: ", x)
+        ###print("y: ", y)
+        ###print("reach: ", reach)
+        ###print("profile: ", profile)
+        ###print("pollutant: ", pollutant)
+
+        self._line, = self.canvas.axes.plot(
+            x, y,
+            label=self.label_discharge,
+            color=self.color_plot,
+            **self.plot_default_kargs
+        )
+
+    def set_reach(self, reach_id):
+        self._current_reach_id = reach_id
+        self._current_profile_id = 0
+        self.draw()
+
+    def set_profile(self, profile_id):
+        self._current_profile_id = profile_id
+        self.update()
+
+    def set_timestamp(self, timestamp):
+        self._current_timestamp = timestamp
+        self.update()
+
+    def update(self):
+        if not self._init:
+            self.draw()
+
+        self.update_data()
+
+        self.update_idle()
+
+    def update_data(self):
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        y = list(map(lambda data_el: data_el[pollutant][0], profile.get_key("pols")))
+
+        self._line.set_data(x, y)
diff --git a/src/View/Results/PlotTotSedEDAdisTS.py b/src/View/Results/PlotTotSedEDAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..007b5038e649c63a2a6c42848d0456c3ea319338
--- /dev/null
+++ b/src/View/Results/PlotTotSedEDAdisTS.py
@@ -0,0 +1,160 @@
+# PlotTotSedEDAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from functools import reduce
+from datetime import datetime
+
+import numpy as np
+
+from tools import timer, trace
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class PlotTotSedED(PamhyrPlot):
+    def __init__(self, canvas=None, trad=None, toolbar=None,
+                 results=None, reach_id=0, profile_id=0, pol_id=0,
+                 parent=None):
+        super(PlotTotSedED, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=results,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self._mode = "time"
+
+        self._current_timestamp = max(results.get("timestamps"))
+        self._current_reach_id = reach_id
+        self._current_profile_id = profile_id
+        self._current_pol_id = pol_id
+
+        self.label_x = _translate("Results", "Time (s)")
+        self.label_y = _translate("Results", "Discharge (m³/s)")
+
+        self.label_discharge = _translate("Results", "Cross-section discharge")
+        self.label_discharge_max = _translate("Results", "Max discharge")
+        self.label_timestamp = _translate("Results", "Current timestamp")
+
+        self._isometric_axis = False
+
+        self._auto_relim_update = False
+        self._autoscale_update = False
+
+    @property
+    def results(self):
+        return self.data
+
+    @results.setter
+    def results(self, results):
+        self.data = results
+        self._current_timestamp = max(results.get("timestamps"))
+
+    @timer
+    def draw(self, highlight=None):
+        self.init_axes()
+
+        if self.results is None:
+            return
+
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        if reach.geometry.number_profiles == 0:
+            self._init = False
+            return
+
+        self.draw_data(reach, profile, pollutant)
+
+        self.set_ticks_time_formater()
+
+        self.enable_legend()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self, reach, profile, pollutant):
+        self.ts = list(self.results.get("timestamps"))
+        self.ts.sort()
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        #First 0 for pol and second 0 for phys var
+        y = list(map(lambda data_el: data_el[pollutant][3], profile.get_key("pols")))
+
+        print("Total Sed ED ************//////////////////")
+        print("profile: ", self._current_profile_id)
+        print("reach: ", self._current_reach_id)
+        print("pollutant: ", pollutant)
+
+        ###print("*****************draw data: ", len(x),len(y))
+        ###print("x: ", x)
+        ###print("y: ", y)
+        ###print("reach: ", reach)
+        ###print("profile: ", profile)
+        ###print("pollutant: ", pollutant)
+
+        self._line, = self.canvas.axes.plot(
+            x, y,
+            label=self.label_discharge,
+            color=self.color_plot,
+            **self.plot_default_kargs
+        )
+
+    def set_reach(self, reach_id):
+        self._current_reach_id = reach_id
+        self._current_profile_id = 0
+        self.draw()
+
+    def set_profile(self, profile_id):
+        self._current_profile_id = profile_id
+        self.update()
+
+    def set_timestamp(self, timestamp):
+        self._current_timestamp = timestamp
+        self.update()
+
+    def update(self):
+        if not self._init:
+            self.draw()
+
+        self.update_data()
+
+        self.update_idle()
+
+    def update_data(self):
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        y = list(map(lambda data_el: data_el[pollutant][3], profile.get_key("pols")))
+
+        self._line.set_data(x, y)
diff --git a/src/View/Results/PlotTotSedEGAdisTS.py b/src/View/Results/PlotTotSedEGAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba1f5a75e39f32dc2cf5d8b58caeb97dc8c0bbed
--- /dev/null
+++ b/src/View/Results/PlotTotSedEGAdisTS.py
@@ -0,0 +1,160 @@
+# PlotTotSedEGAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from functools import reduce
+from datetime import datetime
+
+import numpy as np
+
+from tools import timer, trace
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class PlotTotSedEG(PamhyrPlot):
+    def __init__(self, canvas=None, trad=None, toolbar=None,
+                 results=None, reach_id=0, profile_id=0, pol_id=0,
+                 parent=None):
+        super(PlotTotSedEG, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=results,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self._mode = "time"
+
+        self._current_timestamp = max(results.get("timestamps"))
+        self._current_reach_id = reach_id
+        self._current_profile_id = profile_id
+        self._current_pol_id = pol_id
+
+        self.label_x = _translate("Results", "Time (s)")
+        self.label_y = _translate("Results", "Discharge (m³/s)")
+
+        self.label_discharge = _translate("Results", "Cross-section discharge")
+        self.label_discharge_max = _translate("Results", "Max discharge")
+        self.label_timestamp = _translate("Results", "Current timestamp")
+
+        self._isometric_axis = False
+
+        self._auto_relim_update = False
+        self._autoscale_update = False
+
+    @property
+    def results(self):
+        return self.data
+
+    @results.setter
+    def results(self, results):
+        self.data = results
+        self._current_timestamp = max(results.get("timestamps"))
+
+    @timer
+    def draw(self, highlight=None):
+        self.init_axes()
+
+        if self.results is None:
+            return
+
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        if reach.geometry.number_profiles == 0:
+            self._init = False
+            return
+
+        self.draw_data(reach, profile, pollutant)
+
+        self.set_ticks_time_formater()
+
+        self.enable_legend()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self, reach, profile, pollutant):
+        self.ts = list(self.results.get("timestamps"))
+        self.ts.sort()
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        #First 0 for pol and second 0 for phys var
+        y = list(map(lambda data_el: data_el[pollutant][1], profile.get_key("pols")))
+
+        print("Total Sed EG ************//////////////////")
+        print("profile: ", self._current_profile_id)
+        print("reach: ", self._current_reach_id)
+        print("pollutant: ", pollutant)
+
+        ###print("*****************draw data: ", len(x),len(y))
+        ###print("x: ", x)
+        ###print("y: ", y)
+        ###print("reach: ", reach)
+        ###print("profile: ", profile)
+        ###print("pollutant: ", pollutant)
+
+        self._line, = self.canvas.axes.plot(
+            x, y,
+            label=self.label_discharge,
+            color=self.color_plot,
+            **self.plot_default_kargs
+        )
+
+    def set_reach(self, reach_id):
+        self._current_reach_id = reach_id
+        self._current_profile_id = 0
+        self.draw()
+
+    def set_profile(self, profile_id):
+        self._current_profile_id = profile_id
+        self.update()
+
+    def set_timestamp(self, timestamp):
+        self._current_timestamp = timestamp
+        self.update()
+
+    def update(self):
+        if not self._init:
+            self.draw()
+
+        self.update_data()
+
+        self.update_idle()
+
+    def update_data(self):
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        y = list(map(lambda data_el: data_el[pollutant][1], profile.get_key("pols")))
+
+        self._line.set_data(x, y)
diff --git a/src/View/Results/PlotTotSedEMAdisTS.py b/src/View/Results/PlotTotSedEMAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..36387d8343592007156a9d3dd1c49c2d30561732
--- /dev/null
+++ b/src/View/Results/PlotTotSedEMAdisTS.py
@@ -0,0 +1,160 @@
+# PlotTotSedEMAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+
+from functools import reduce
+from datetime import datetime
+
+import numpy as np
+
+from tools import timer, trace
+from View.Tools.PamhyrPlot import PamhyrPlot
+
+from PyQt5.QtCore import (
+    QCoreApplication
+)
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class PlotTotSedEM(PamhyrPlot):
+    def __init__(self, canvas=None, trad=None, toolbar=None,
+                 results=None, reach_id=0, profile_id=0, pol_id=0,
+                 parent=None):
+        super(PlotTotSedEM, self).__init__(
+            canvas=canvas,
+            trad=trad,
+            data=results,
+            toolbar=toolbar,
+            parent=parent
+        )
+
+        self._mode = "time"
+
+        self._current_timestamp = max(results.get("timestamps"))
+        self._current_reach_id = reach_id
+        self._current_profile_id = profile_id
+        self._current_pol_id = pol_id
+
+        self.label_x = _translate("Results", "Time (s)")
+        self.label_y = _translate("Results", "Discharge (m³/s)")
+
+        self.label_discharge = _translate("Results", "Cross-section discharge")
+        self.label_discharge_max = _translate("Results", "Max discharge")
+        self.label_timestamp = _translate("Results", "Current timestamp")
+
+        self._isometric_axis = False
+
+        self._auto_relim_update = False
+        self._autoscale_update = False
+
+    @property
+    def results(self):
+        return self.data
+
+    @results.setter
+    def results(self, results):
+        self.data = results
+        self._current_timestamp = max(results.get("timestamps"))
+
+    @timer
+    def draw(self, highlight=None):
+        self.init_axes()
+
+        if self.results is None:
+            return
+
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        if reach.geometry.number_profiles == 0:
+            self._init = False
+            return
+
+        self.draw_data(reach, profile, pollutant)
+
+        self.set_ticks_time_formater()
+
+        self.enable_legend()
+
+        self.idle()
+        self._init = True
+
+    def draw_data(self, reach, profile, pollutant):
+        self.ts = list(self.results.get("timestamps"))
+        self.ts.sort()
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        #First 0 for pol and second 0 for phys var
+        y = list(map(lambda data_el: data_el[pollutant][2], profile.get_key("pols")))
+
+        print("Total Sed EM ************//////////////////")
+        print("profile: ", self._current_profile_id)
+        print("reach: ", self._current_reach_id)
+        print("pollutant: ", pollutant)
+
+        ###print("*****************draw data: ", len(x),len(y))
+        ###print("x: ", x)
+        ###print("y: ", y)
+        ###print("reach: ", reach)
+        ###print("profile: ", profile)
+        ###print("pollutant: ", pollutant)
+
+        self._line, = self.canvas.axes.plot(
+            x, y,
+            label=self.label_discharge,
+            color=self.color_plot,
+            **self.plot_default_kargs
+        )
+
+    def set_reach(self, reach_id):
+        self._current_reach_id = reach_id
+        self._current_profile_id = 0
+        self.draw()
+
+    def set_profile(self, profile_id):
+        self._current_profile_id = profile_id
+        self.update()
+
+    def set_timestamp(self, timestamp):
+        self._current_timestamp = timestamp
+        self.update()
+
+    def update(self):
+        if not self._init:
+            self.draw()
+
+        self.update_data()
+
+        self.update_idle()
+
+    def update_data(self):
+        reach = self.results.river.reach(self._current_reach_id)
+        profile = reach.profile(self._current_profile_id)
+        pollutant = self._current_pol_id
+
+        x = self.ts
+        #y = profile.get_key("Q")
+        y = list(map(lambda data_el: data_el[pollutant][2], profile.get_key("pols")))
+
+        self._line.set_data(x, y)
diff --git a/src/View/Results/Table.py b/src/View/Results/Table.py
index c92e994e259e678858afa61a35e15fd9febe61f5..0a3dcb6c9ceae8a9e33767feba6f3b838e050760 100644
--- a/src/View/Results/Table.py
+++ b/src/View/Results/Table.py
@@ -47,8 +47,12 @@ _translate = QCoreApplication.translate
 class TableModel(PamhyrTableModel):
     def _setup_lst(self):
         _river = self._data.river
+        print("data: ", self._data)
+        print("river: ", _river)
+        print("reaches: ", _river.reachs)
         if self._opt_data == "reach":
             self._lst = _river.reachs
+            print("optreach: ", self._lst)
         elif self._opt_data == "profile":
             self._lst = _river.reach(0).profiles
         elif self._opt_data == "raw_data":
diff --git a/src/View/Results/TableAdisTS.py b/src/View/Results/TableAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..039956c045bd442e78644121dafa8ebba2029491
--- /dev/null
+++ b/src/View/Results/TableAdisTS.py
@@ -0,0 +1,141 @@
+# TableAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import logging
+import traceback
+
+from tools import timer, trace
+
+from PyQt5.QtGui import (
+    QKeySequence, QColor
+)
+from PyQt5.QtCore import (
+    Qt, QAbstractTableModel, QModelIndex,
+    QVariant, pyqtSlot, QCoreApplication,
+)
+from PyQt5.QtWidgets import (
+    QMessageBox, QUndoCommand, QUndoStack,
+    QStyledItemDelegate, QLineEdit, QAbstractItemView,
+    QComboBox,
+)
+
+from View.Tools.PamhyrTable import PamhyrTableModel
+from View.Results.translate import *
+
+logger = logging.getLogger()
+
+_translate = QCoreApplication.translate
+
+
+class TableModel(PamhyrTableModel):
+    def _setup_lst(self):
+        _river = self._data.river
+        ###print("//////////////////opt_data: ", self._opt_data)
+        ###print("data: ", self._data)
+        ###print("river: ", _river)
+        ###print("reaches: ", _river.reachs)
+        if self._opt_data == "reach":
+            self._lst = _river.reachs
+            ###print("optreach: ", self._lst)
+        elif self._opt_data == "profile":
+            self._lst = _river.reach(0).profiles
+        elif self._opt_data == "raw_data":
+            self._lst = _river.reach(0).profiles
+        elif self._opt_data == "pollutants":
+            tmp_list = self._data.pollutants_list.copy()
+            tmp_list.remove("total_sediment")
+            ###print(type(tmp_list))
+            #tmp_list.insert(len(tmp_list), "total_sediment")
+            self._lst = tmp_list
+            ###print("=====table pollutants: ", self._lst)
+
+    def __init__(self, **kwargs):
+        self._timestamp = 0.0
+        super(TableModel, self).__init__(**kwargs)
+
+    def data(self, index, role=Qt.DisplayRole):
+        if role != Qt.ItemDataRole.DisplayRole:
+            return QVariant()
+
+        row = index.row()
+        column = index.column()
+
+        if self._opt_data == "reach":
+            if self._headers[column] == "name":
+                v = self._lst[row].name
+                return str(v)
+        elif self._opt_data == "pollutants":
+            if self._headers[column] == "name":
+                v = self._lst[row]
+                return str(v)
+        elif self._opt_data == "profile":
+            if self._headers[column] == "name":
+                v = self._lst[row].name
+                return str(v)
+            elif self._headers[column] == "rk":
+                v = self._lst[row].rk
+                return f"{v:.4f}"
+        elif self._opt_data == "raw_data":
+            p = self._lst[row]
+            if self._headers[column] == "name":
+                if p.name == "":
+                    return f"{p.rk:.4f}"
+                return f"{p.name}"
+            tmp_list = self._data.pollutants_list.copy()
+            tmp_list.remove("total_sediment")
+            tmp_list2 = self._data.pollutants_list.copy()
+            print("=============================list polutants table raw: ", tmp_list)
+            print("=============================list polutants table raw: ", tmp_list2)
+            for pol in tmp_list:
+                pol_index = tmp_list2.index(pol)
+                print("=============================pol index: ", pol_index)
+                header_name = pol + " Concentration"
+                print("=============================Headers: ", self._headers[column])
+                print("=============================Headers: ", header_name)
+                if self._headers[column] == header_name:
+                    print("=============================Headers: ", self._headers[column])
+                    print("=============================Headers: ", header_name)
+                    v = self._lst[row].get_ts_key(self._timestamp, "pols")[pol_index][0]
+                    return f"{v:.4f}"
+                elif self._headers[column] == pol + " Mass":
+                    m1 = self._lst[row].get_ts_key(self._timestamp, "pols")[pol_index][1]
+                    m2 = self._lst[row].get_ts_key(self._timestamp, "pols")[pol_index][2]
+                    m3 = self._lst[row].get_ts_key(self._timestamp, "pols")[pol_index][3]
+                    v = m1 + m2 + m3
+                    return f"{v:.4f}"
+
+        return QVariant()
+
+    def update(self, reach):
+        _river = self._data.river
+
+        if self._opt_data == "reach":
+            self._lst = _river.reachs
+        elif self._opt_data == "profile" or self._opt_data == "raw_data":
+            self._lst = _river.reach(reach).profiles
+
+        self.layoutChanged.emit()
+
+    @property
+    def timestamp(self):
+        return self._timestamp
+
+    @timestamp.setter
+    def timestamp(self, timestamp):
+        self._timestamp = timestamp
+        self.layoutChanged.emit()
diff --git a/src/View/Results/WindowAdisTS.py b/src/View/Results/WindowAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae2c497f802d2f61078cc78a3ba8a8468bb43a53
--- /dev/null
+++ b/src/View/Results/WindowAdisTS.py
@@ -0,0 +1,643 @@
+# WindowAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import os
+import csv
+import logging
+
+from datetime import datetime
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrWindow
+
+from PyQt5.QtGui import (
+    QKeySequence, QIcon, QPixmap,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QItemSelectionModel, QTimer,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+    QSlider, QLabel, QWidget, QGridLayout,
+)
+
+from View.Tools.Plot.PamhyrCanvas import MplCanvas
+from View.Tools.Plot.PamhyrToolbar import PamhyrPlotToolbar
+
+from View.Results.PlotCAdisTS import PlotC
+from View.Results.PlotMAdisTS import PlotM
+from View.Results.PlotTotSedCAdisTS import PlotTotSedC
+from View.Results.PlotTotSedEGAdisTS import PlotTotSedEG
+from View.Results.PlotTotSedEMAdisTS import PlotTotSedEM
+from View.Results.PlotTotSedEDAdisTS import PlotTotSedED
+
+from View.Results.CustomPlot.Plot import CustomPlot
+from View.Results.CustomPlot.CustomPlotValuesSelectionDialog import (
+    CustomPlotValuesSelectionDialog,
+)
+
+from View.Results.TableAdisTS import TableModel
+from View.Results.translate import ResultsTranslate
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class ResultsWindowAdisTS(PamhyrWindow):
+    _pamhyr_ui = "ResultsAdisTS"
+    _pamhyr_name = "Results"
+
+    def _path_file(self, filename):
+        return os.path.abspath(
+            os.path.join(
+                os.path.dirname(__file__),
+                "..", "ui", "ressources", filename
+            )
+        )
+
+    def __init__(self, study=None, config=None,
+                 solver=None, results=None,
+                 parent=None):
+        self._solver = solver
+        self._results = results
+
+        pollutants_headers = self._results.pollutants_list.copy()
+        print("***///---+++///***---+++pollutants translate: ", pollutants_headers)
+
+        trad = ResultsTranslate(pollutants_headers)
+        name = (
+            trad[self._pamhyr_name] + " - "
+            + study.name + " - "
+            + self._solver.name
+        )
+
+        super(ResultsWindowAdisTS, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            trad=trad,
+            parent=parent
+        )
+
+        self._hash_data.append(self._solver)
+        self._hash_data.append(self._results)
+
+        self._additional_plot = {}
+
+        try:
+            #print("timestamps results: ", self._results)
+            self._timestamps = sorted(list(self._results.get("timestamps")))
+
+            print("setup table adists results")
+
+            self.setup_table()
+            self.setup_plots()
+            self.setup_slider()
+            print("///setup slider correct")
+            self.setup_statusbar()
+            print("///setup status bar correct")
+            self.setup_connections()
+            print("///setup connections correct")
+        except Exception as e:
+            logger_exception(e)
+            return
+
+    def setup_table(self):
+        print("setup table adists results")
+        print("results study name: ", self._results.study.name)
+        print("results study river: ", self._results.study.river)
+        print("*********files names resultats from Results Window: ", self._results.pollutants_list)
+        self._table = {}
+        for t in ["reach", "profile", "pollutants", "raw_data"]:
+            print("t: ", t)
+            table = self.find(QTableView, f"tableView_{t}")
+            self._table[t] = TableModel(
+                table_view=table,
+                table_headers=self._trad.get_dict(f"table_headers_{t}"),
+                data=self._results,
+                undo=self._undo_stack,
+                opt_data=t
+            )
+
+    def setup_slider(self):
+        self._timer = QTimer(self)
+
+    def setup_plots(self):
+        self.canvas_4 = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas_4.setObjectName("canvas_4")
+        self.toolbar_4 = PamhyrPlotToolbar(
+            self.canvas_4, self, items=[
+                "home", "move", "zoom", "save",
+                "iso", "back/forward"
+            ]
+        )
+        self.plot_layout_4 = self.find(
+            QVBoxLayout, "verticalLayout_concentration")
+        self.plot_layout_4.addWidget(self.toolbar_4)
+        self.plot_layout_4.addWidget(self.canvas_4)
+
+        self.plot_c = PlotC(
+            canvas=self.canvas_4,
+            results=self._results,
+            reach_id=0,
+            profile_id=0,
+            pol_id=0,
+            trad=self._trad,
+            toolbar=self.toolbar_4
+        )
+        self.plot_c.draw()
+
+        print("///plot c adists correct")
+
+        self.canvas_3 = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas_3.setObjectName("canvas_3")
+        self.toolbar_3 = PamhyrPlotToolbar(
+            self.canvas_3, self, items=[
+                "home", "move", "zoom", "save",
+                "iso", "back/forward"
+            ]
+        )
+        self.plot_layout_3 = self.find(
+            QVBoxLayout, "verticalLayout_mass")
+        self.plot_layout_3.addWidget(self.toolbar_3)
+        self.plot_layout_3.addWidget(self.canvas_3)
+
+        self.plot_m = PlotM(
+            canvas=self.canvas_3,
+            results=self._results,
+            reach_id=0,
+            profile_id=0,
+            pol_id=0,
+            trad=self._trad,
+            toolbar=self.toolbar_3
+        )
+        self.plot_m.draw()
+
+        print("///plot m adists correct")
+
+        self.canvas_2 = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas_2.setObjectName("canvas_2")
+        self.toolbar_2 = PamhyrPlotToolbar(
+            self.canvas_2, self, items=[
+                "home", "move", "zoom", "save",
+                "iso", "back/forward"
+            ]
+        )
+        self.plot_layout_2 = self.find(
+            QVBoxLayout, "verticalLayout_tot_c")
+        self.plot_layout_2.addWidget(self.toolbar_2)
+        self.plot_layout_2.addWidget(self.canvas_2)
+
+        self.plot_tot_c = PlotTotSedC(
+            canvas=self.canvas_2,
+            results=self._results,
+            reach_id=0,
+            profile_id=0,
+            pol_id=self._results.pollutants_list.index("total_sediment"),
+            trad=self._trad,
+            toolbar=self.toolbar_2
+        )
+
+        self.plot_tot_c.draw()
+
+        self.canvas_1 = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas_1.setObjectName("canvas_1")
+        self.toolbar_1 = PamhyrPlotToolbar(
+            self.canvas_1, self, items=[
+                "home", "move", "zoom", "save",
+                "iso", "back/forward"
+            ]
+        )
+        self.plot_layout_1 = self.find(
+            QVBoxLayout, "verticalLayout_tot_left_2")
+        self.plot_layout_1.addWidget(self.toolbar_1)
+        self.plot_layout_1.addWidget(self.canvas_1)
+
+        self.plot_tot_eg = PlotTotSedEG(
+            canvas=self.canvas_1,
+            results=self._results,
+            reach_id=0,
+            profile_id=0,
+            pol_id=self._results.pollutants_list.index("total_sediment"),
+            trad=self._trad,
+            toolbar=self.toolbar_1
+        )
+
+        self.plot_tot_eg.draw()
+
+        self.canvas_0 = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas_0.setObjectName("canvas_0")
+        self.toolbar_0 = PamhyrPlotToolbar(
+            self.canvas_0, self, items=[
+                "home", "move", "zoom", "save",
+                "iso", "back/forward"
+            ]
+        )
+        self.plot_layout_0 = self.find(
+            QVBoxLayout, "verticalLayout_tot_minor")
+        self.plot_layout_0.addWidget(self.toolbar_0)
+        self.plot_layout_0.addWidget(self.canvas_0)
+
+        self.plot_tot_em = PlotTotSedEM(
+            canvas=self.canvas_0,
+            results=self._results,
+            reach_id=0,
+            profile_id=0,
+            pol_id=self._results.pollutants_list.index("total_sediment"),
+            trad=self._trad,
+            toolbar=self.toolbar_0
+        )
+
+        self.plot_tot_em.draw()
+
+        self.canvas_5 = MplCanvas(width=5, height=4, dpi=100)
+        self.canvas_5.setObjectName("canvas_5")
+        self.toolbar_5 = PamhyrPlotToolbar(
+            self.canvas_5, self, items=[
+                "home", "move", "zoom", "save",
+                "iso", "back/forward"
+            ]
+        )
+        self.plot_layout_5 = self.find(
+            QVBoxLayout, "verticalLayout_tot_right")
+        self.plot_layout_5.addWidget(self.toolbar_5)
+        print("///***---+++///+++add toolbar correct")
+        self.plot_layout_5.addWidget(self.canvas_5)
+        print("///***---+++///+++add canvas correct")
+
+        self.plot_tot_ed = PlotTotSedED(
+            canvas=self.canvas_5,
+            results=self._results,
+            reach_id=0,
+            profile_id=0,
+            pol_id=self._results.pollutants_list.index("total_sediment"),
+            trad=self._trad,
+            toolbar=self.toolbar_5
+        )
+
+        self.plot_tot_ed.draw()
+
+    def closeEvent(self, event):
+        try:
+            self._timer.stop()
+        except Exception as e:
+            logger_exception(e)
+
+        super(ResultsWindowAdisTS, self).closeEvent(event)
+
+    def _compute_status_label(self):
+        # Reach
+        table = self.find(QTableView, f"tableView_reach")
+        indexes = table.selectedIndexes()
+        if len(indexes) == 0:
+            reach = self._study.river.edges()[0]
+        else:
+            reach = self._study.river.edges()[indexes[0].row()]
+
+        # Profile
+        table = self.find(QTableView, f"tableView_profile")
+        indexes = table.selectedIndexes()
+        if len(indexes) == 0:
+            profile = reach.reach.profile(0)
+        else:
+            profile = reach.reach.profile(indexes[0].row())
+
+        pname = profile.name if profile.name != "" else profile.rk
+
+        # Pollutant
+        table = self.find(QTableView, f"tableView_pollutants")
+        indexes = table.selectedIndexes()
+        if len(indexes) == 0:
+            pollutant = self._results.pollutants_list[0]
+        else:
+            pollutant = self._results.pollutants_list[indexes[0].row()]
+
+        return (f"Reach: {reach.name} | " +
+                f"Profile: {pname} | " +
+                f"Pollutant: {pollutant}")
+
+    def setup_statusbar(self):
+        txt = self._compute_status_label()
+        self._status_label = QLabel(txt)
+        self.statusbar.addPermanentWidget(self._status_label)
+
+    def update_statusbar(self):
+        txt = self._compute_status_label()
+        self._status_label.setText(txt)
+
+    def setup_connections(self):
+        # Action
+        actions = {
+            "action_reload": self._reload,
+            ###"action_add": self._add_custom_plot,
+            "action_export": self.export,
+        }
+
+        for action in actions:
+            self.find(QAction, action).triggered.connect(
+                actions[action]
+            )
+
+        # Table and Plot
+        fun = {
+            "reach": self._set_current_reach,
+            "profile": self._set_current_profile,
+            "pollutants": self._set_current_pol,
+            "raw_data": self._set_current_profile_raw_data,
+        }
+
+        for t in ["reach", "profile", "pollutants"]:###, "raw_data"]:
+            table = self.find(QTableView, f"tableView_{t}")
+
+            table.selectionModel()\
+                 .selectionChanged\
+                 .connect(fun[t])
+
+            self._table[t].dataChanged.connect(fun[t])
+
+        self._timer.timeout.connect(self._update_slider)
+
+    def update_table_selection_reach(self, ind):
+        table = self.find(QTableView, f"tableView_reach")
+        selectionModel = table.selectionModel()
+        index = table.model().index(ind, 0)
+
+        selectionModel.select(
+            index,
+            QItemSelectionModel.Rows |
+            QItemSelectionModel.ClearAndSelect |
+            QItemSelectionModel.Select
+        )
+        table.scrollTo(index)
+
+        self._table["profile"].update(ind)
+        ###self._table["raw_data"].update(ind)
+
+    def update_table_selection_profile(self, ind):
+        for t in ["profile"]:###, "raw_data"]:
+            table = self.find(QTableView, f"tableView_{t}")
+            selectionModel = table.selectionModel()
+            index = table.model().index(ind, 0)
+
+            selectionModel.select(
+                index,
+                QItemSelectionModel.Rows |
+                QItemSelectionModel.ClearAndSelect |
+                QItemSelectionModel.Select
+            )
+            table.scrollTo(index)
+
+    def update_table_selection_pol(self, ind):
+        for t in ["pollutants"]:###, "raw_data"]:
+            table = self.find(QTableView, f"tableView_{t}")
+            selectionModel = table.selectionModel()
+            index = table.model().index(ind, 0)
+
+            selectionModel.select(
+                index,
+                QItemSelectionModel.Rows |
+                QItemSelectionModel.ClearAndSelect |
+                QItemSelectionModel.Select
+            )
+            table.scrollTo(index)
+
+    def update(self, reach_id=None, profile_id=None, pol_id=None, timestamp=None):
+        if reach_id is not None:
+            self.plot_c.set_reach(reach_id)
+            self.plot_m.set_reach(reach_id)
+            self.plot_tot_c.set_reach(reach_id)
+            self.plot_tot_eg.set_reach(reach_id)
+            self.plot_tot_em.set_reach(reach_id)
+            self.plot_tot_ed.set_reach(reach_id)
+
+            self.update_table_selection_reach(reach_id)
+            self.update_table_selection_profile(0)
+            self.update_table_selection_pol(0)
+
+        if profile_id is not None:
+            self.plot_c.set_profile(profile_id)
+            self.plot_m.set_profile(profile_id)
+            self.plot_tot_c.set_profile(profile_id)
+            self.plot_tot_eg.set_profile(profile_id)
+            self.plot_tot_em.set_profile(profile_id)
+            self.plot_tot_ed.set_profile(profile_id)
+
+            self.update_table_selection_profile(profile_id)
+
+        print("--**//++update pol_id before None: ", pol_id)
+
+        if pol_id is not None:
+            print("--**//++//**//** update pol_id: ", pol_id)
+            self.plot_c.set_pollutant(pol_id)
+            self.plot_m.set_pollutant(pol_id)
+
+            self.update_table_selection_pol(pol_id)
+
+        if timestamp is not None:
+            self.plot_c.set_timestamp(timestamp)
+            self.plot_m.set_timestamp(timestamp)
+            self.plot_tot_c.set_timestamp(timestamp)
+            self.plot_tot_eg.set_timestamp(timestamp)
+            self.plot_tot_em.set_timestamp(timestamp)
+            self.plot_tot_ed.set_timestamp(timestamp)
+
+            self._table["raw_data"].timestamp = timestamp
+
+        self.update_statusbar()
+
+    def _set_current_reach(self):
+        table = self.find(QTableView, f"tableView_reach")
+        indexes = table.selectedIndexes()
+        if len(indexes) == 0:
+            return
+
+        self.update(reach_id=indexes[0].row())
+
+    def _set_current_profile(self):
+        table = self.find(QTableView, f"tableView_profile")
+        indexes = table.selectedIndexes()
+        if len(indexes) == 0:
+            return
+
+        ind = indexes[0].row()
+        self.update(profile_id=ind)
+        ###self._slider_profile.setValue(ind)
+
+    def _set_current_pol(self):
+        table = self.find(QTableView, f"tableView_pollutants")
+        indexes = table.selectedIndexes()
+        if len(indexes) == 0:
+            return
+
+        ind = indexes[0].row()
+        print("set pol id: ", ind)
+        self.update(pol_id=ind)
+
+    def _set_current_profile_raw_data(self):
+        table = self.find(QTableView, f"tableView_raw_data")
+        indexes = table.selectedIndexes()
+        if len(indexes) == 0:
+            return
+
+        ind = indexes[0].row()
+        self.update(profile_id=ind)
+        self._slider_profile.setValue(ind)
+
+    def _set_current_profile_slider(self):
+        pid = self._slider_profile.value()
+        self.update(profile_id=pid)
+
+    def _set_current_timestamp(self):
+        timestamp = self._timestamps[self._slider_time.value()]
+        self.update(timestamp=timestamp)
+
+    def _reload_plots(self):
+        self.plot_c.results = self._results
+        self.plot_m.results = self._results
+        self.plot_tot_c.results = self._results
+        self.plot_tot_eg.results = self._results
+        self.plot_tot_em.results = self._results
+        self.plot_tot_ed.results = self._results
+
+        self.plot_c.draw()
+        self.plot_m.draw()
+        self.plot_tot_c.draw()
+        self.plot_tot_eg.draw()
+        self.plot_tot_em.draw()
+        self.plot_tot_ed.draw()
+
+    def _reload_slider(self):
+        self._slider_time = self.find(QSlider, f"horizontalSlider_time")
+        self._slider_time.setMaximum(len(self._timestamps) - 1)
+        self._slider_time.setValue(len(self._timestamps) - 1)
+
+    def _reload(self):
+        logger.debug("Reload results...")
+        self._results = self._results.reload()
+
+        self._timestamps = sorted(list(self._results.get("timestamps")))
+
+        self._reload_plots()
+        ###self._reload_slider()
+
+    def _copy(self):
+        logger.info("TODO: copy")
+
+    def _paste(self):
+        logger.info("TODO: paste")
+
+    def _undo(self):
+        self._table.undo()
+
+    def _redo(self):
+        self._table.redo()
+
+    # play / pause buttons
+    def _update_slider(self):
+        if self._slider_time.value() == self._slider_time.maximum():
+            self._slider_time.setValue(self._slider_time.minimum())
+        else:
+            self._slider_time.setValue(self._slider_time.value()+1)
+
+    def _next(self):
+        self._slider_time.setValue(self._slider_time.value()+1)
+
+    def _back(self):
+        self._slider_time.setValue(self._slider_time.value()-1)
+
+    def _first(self):
+        self._slider_time.setValue(self._slider_time.minimum())
+
+    def _last(self):
+        self._slider_time.setValue(self._slider_time.maximum())
+
+    def _pause(self):
+        if self._button_play.isChecked():
+            self._button_next.setEnabled(False)
+            self._button_back.setEnabled(False)
+            self._button_first.setEnabled(False)
+            self._button_last.setEnabled(False)
+            self._timer.start(100)
+            self._button_play.setIcon(self._icon_pause)
+        else:
+            self._timer.stop()
+            self._button_next.setEnabled(True)
+            self._button_back.setEnabled(True)
+            self._button_first.setEnabled(True)
+            self._button_last.setEnabled(True)
+            self._button_play.setIcon(self._icon_start)
+
+    def export(self):
+        self.file_dialog(
+            select_file=False,
+            callback=lambda d: self.export_to(d[0])
+        )
+
+    def export_to(self, directory):
+        for reach in self._results.river.reachs:
+            self.export_reach(reach, directory)
+
+    def export_reach(self, reach, directory):
+        name = reach.name
+        name = name.replace(" ", "-")
+
+        file_name = os.path.join(
+            directory,
+            f"reach_{name}.csv"
+        )
+
+        with open(file_name, 'w', newline='') as csvfile:
+            writer = csv.writer(csvfile, delimiter=',',
+                                quotechar='|', quoting=csv.QUOTE_MINIMAL)
+            writer.writerow(["name", "rk", "data-file"])
+            for profile in reach.profiles:
+                p_file_name = os.path.join(
+                    directory,
+                    f"cs_{profile.geometry.id}.csv"
+                )
+
+                writer.writerow([
+                    profile.name,
+                    profile.rk,
+                    p_file_name
+                ])
+
+                self.export_profile(reach, profile, p_file_name)
+
+    def export_profile(self, reach, profile, file_name):
+        with open(file_name, 'w', newline='') as csvfile:
+            writer = csv.writer(csvfile, delimiter=',',
+                                quotechar='|', quoting=csv.QUOTE_MINIMAL)
+
+            writer.writerow(["timestamp", "z", "q"])
+            timestamps = sorted(self._results.get("timestamps"))
+
+            for ts in timestamps:
+                writer.writerow([
+                    ts,
+                    profile.get_ts_key(ts, "Z"),
+                    profile.get_ts_key(ts, "Q"),
+                ])
diff --git a/src/View/Results/translate.py b/src/View/Results/translate.py
index 8b66124edd6fffa1c09c807355dfc3a94e6ff999..243e618d3efcee05204f1b599111552c28db3d4b 100644
--- a/src/View/Results/translate.py
+++ b/src/View/Results/translate.py
@@ -24,7 +24,13 @@ _translate = QCoreApplication.translate
 
 
 class ResultsTranslate(MainTranslate):
-    def __init__(self):
+    def __init__(self, pollutants=None):
+        if pollutants is not None:
+            self.pollutants = pollutants
+            self.pollutants.remove("total_sediment")
+        else:
+            self.pollutants = pollutants
+
         super(ResultsTranslate, self).__init__()
 
         self._dict["Results"] = _translate("Results", "Results")
@@ -48,11 +54,19 @@ class ResultsTranslate(MainTranslate):
             "name": _translate("Results", "Reach name"),
         }
 
+        self._sub_dict["table_headers_phys_var"] = {
+            "name": _translate("Results", "Variables names"),
+        }
+
         self._sub_dict["table_headers_profile"] = {
             "name": self._dict["name"],
             "rk": self._dict["unit_rk"],
         }
 
+        self._sub_dict["table_headers_pollutants"] = {
+            "name": _translate("Results", "Pollutant name"),
+        }
+
         self._sub_dict["table_headers_raw_data"] = {
             "name": _translate("Results", "Profile"),
             "water_elevation": self._dict["unit_water_elevation"],
@@ -82,3 +96,19 @@ class ResultsTranslate(MainTranslate):
             "froude": self._dict["unit_froude"],
             "wet_area": self._dict["unit_wet_area"],
         }
+
+        if self.pollutants is not None:
+            self._sub_dict["table_headers_raw_data"] = {
+                "name": _translate("Results", "Profile"),
+            }
+            for pol in self.pollutants:
+                self._sub_dict["table_headers_raw_data"][pol + " Concentration"] = pol + "\n Concentration"
+                self._sub_dict["table_headers_raw_data"][pol + " Mass"] = pol + "\n Mass"
+        else:
+            self._sub_dict["table_headers_raw_data"] = {
+                "name": _translate("Results", "Profile"),
+                "water_elevation": self._dict["unit_water_elevation"],
+                "discharge": self._dict["unit_discharge"],
+                "speed": self._dict["unit_speed"],
+            }
+
diff --git a/src/View/RunSolver/Window.py b/src/View/RunSolver/Window.py
index 6f9bf5e6eec875e3531bf53a505e9f06e2145e1e..270d565f0a85bedf1283b1dbb66f4c669af4694c 100644
--- a/src/View/RunSolver/Window.py
+++ b/src/View/RunSolver/Window.py
@@ -79,7 +79,9 @@ class SelectSolverWindow(PamhyrDialog):
         self.select_last_solver()
 
     def setup_combobox(self):
-        solvers = self._config.solvers
+        #solvers = self._config.solvers
+        #solvers mage
+        solvers = list(filter(lambda x: "adists" not in x._type, self._config.solvers))
         solvers_name = list(
             map(
                 self._format_solver_name,
diff --git a/src/View/RunSolver/WindowAdisTS.py b/src/View/RunSolver/WindowAdisTS.py
new file mode 100644
index 0000000000000000000000000000000000000000..a048757a7e9b16d61285fbd0343d7b8aee9c0063
--- /dev/null
+++ b/src/View/RunSolver/WindowAdisTS.py
@@ -0,0 +1,451 @@
+# WindowAdisTS.py -- Pamhyr
+# Copyright (C) 2023-2024  INRAE
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+# -*- coding: utf-8 -*-
+
+import os
+import logging
+import tempfile
+
+from queue import Queue
+from tools import trace, timer, logger_exception
+
+from View.Tools.PamhyrWindow import PamhyrDialog, PamhyrWindow
+
+from PyQt5.QtGui import (
+    QKeySequence,
+)
+
+from PyQt5.QtCore import (
+    Qt, QVariant, QAbstractTableModel,
+    QCoreApplication, QModelIndex, pyqtSlot,
+    QRect, QTimer, QProcess,
+)
+
+from PyQt5.QtWidgets import (
+    QDialogButtonBox, QPushButton, QLineEdit,
+    QFileDialog, QTableView, QAbstractItemView,
+    QUndoStack, QShortcut, QAction, QItemDelegate,
+    QComboBox, QVBoxLayout, QHeaderView, QTabWidget,
+    QTextEdit,
+)
+
+from View.RunSolver.Log.Window import SolverLogFileWindow
+from View.Results.ReadingResultsDialog import ReadingResultsDialog
+
+try:
+    from signal import SIGTERM, SIGSTOP, SIGCONT
+    _signal = True
+except Exception:
+    _signal = False
+
+_translate = QCoreApplication.translate
+
+logger = logging.getLogger()
+
+
+class SelectSolverWindowAdisTS(PamhyrDialog):
+    _pamhyr_ui = "SelectSolverAdisTS"
+    _pamhyr_name = "Select solver"
+
+    def __init__(self, study=None, config=None,
+                 parent=None):
+        self._solver = None
+
+        name = _translate("Solver", "Select solver")
+        super(SelectSolverWindowAdisTS, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            options=[],
+            parent=parent
+        )
+
+        self.setup_combobox()
+        self.setup_connections()
+        self.select_last_solver()
+
+    def setup_combobox(self):
+        #solvers = self._config.solvers
+        #solvers mage
+        solvers = list(filter(lambda x: "mage" not in x._type, self._config.solvers))
+        solvers_name = list(
+            map(
+                self._format_solver_name,
+                solvers
+            )
+        )
+
+        solvers_mage = list(filter(lambda x: "mage" in x._type.lower(), self._config.solvers))
+        solvers_mage_names = list(map(lambda x: x._name, solvers_mage))
+
+        solvers_dir = os.path.join(
+            os.path.dirname(self._study.filename),
+            "_PAMHYR_",
+            self._study.name.replace(" ", "_"),
+        )
+
+        dir_solvers_List = os.listdir(solvers_dir)
+
+        display_mage_names = list(filter(lambda x: x in solvers_mage_names, dir_solvers_List))
+
+        self.combobox_add_items("comboBox", solvers_name)
+        self.combobox_add_items("comboBoxRepMage", display_mage_names)
+
+    def setup_connections(self):
+        self.find(QPushButton, "pushButton_run").clicked.connect(self.accept)
+        self.find(QPushButton, "pushButton_cancel")\
+            .clicked.connect(self.reject)
+
+    def select_last_solver(self):
+        solvers = self._config.solvers
+        last = self._config.last_solver_name
+
+        solver = list(
+            filter(
+                lambda s: s.name == last,
+                solvers
+            )
+        )
+
+        if len(solver) != 0:
+            self.set_combobox_text(
+                "comboBox",
+                self._format_solver_name(solver[0])
+            )
+
+    def _format_solver_name(self, solver):
+        return f"{solver.name} - ({solver._type})"
+
+    @property
+    def solver(self):
+        return self._solver
+
+    @property
+    def mage_rep(self):
+        return self._mage_result_rep
+
+    def accept(self):
+        solver_name = self.get_combobox_text("comboBox")
+        solver_name = solver_name.rsplit(" - ", 1)[0]
+
+        self._mage_result_rep = self.get_combobox_text("comboBoxRepMage")
+
+        self._config.update_last_solver_used(solver_name)
+
+        self._solver = next(
+            filter(
+                lambda s: s.name == solver_name,
+                self._config.solvers
+            )
+        )
+
+        super(SelectSolverWindowAdisTS, self).accept()
+
+class SolverLogWindowAdisTS(PamhyrWindow):
+    _pamhyr_ui = "SolverLogAdisTS"
+    _pamhyr_name = "Solver Log"
+
+    def __init__(self, study=None, config=None,
+                 solver=None, parent=None, mage_rep=None):
+        self._solver = solver
+        self._results = None
+        self._results_mage = None
+        self._mage_rep = mage_rep
+
+        name = _translate("Solver", "Select log")
+        super(SolverLogWindowAdisTS, self).__init__(
+            title=name,
+            study=study,
+            config=config,
+            options=[],
+            parent=parent
+        )
+
+        self.setup_action()
+        self.setup_alarm()
+        self.setup_connections()
+        self.setup_workdir()
+        self.setup_process()
+
+        ok = self.export()
+        if ok:
+            self.run()
+        else:
+            self._log(
+                f" *** Failed to export study to {self._solver._type}",
+                color="red"
+            )
+
+    def setup_action(self):
+        self.find(QAction, "action_start").setEnabled(False)
+        if _signal:
+            self.find(QAction, "action_pause").setEnabled(True)
+        else:
+            self.find(QAction, "action_pause").setEnabled(False)
+
+        self.find(QAction, "action_stop").setEnabled(True)
+        self.find(QAction, "action_log_file").setEnabled(False)
+        self.find(QAction, "action_results").setEnabled(False)
+
+    def setup_alarm(self):
+        self._alarm = QTimer()
+
+    def setup_connections(self):
+        self.find(QAction, "action_start").triggered.connect(self.start)
+        self.find(QAction, "action_pause").triggered.connect(self.pause)
+        self.find(QAction, "action_stop").triggered.connect(self.stop)
+        self.find(QAction, "action_log_file").triggered.connect(self.log_file)
+        self.find(QAction, "action_results").triggered.connect(self.results)
+        self.find(QAction, "action_results_Mage").triggered.connect(self.resultsMage)
+
+        self._alarm.timeout.connect(self.update)
+
+    def setup_workdir(self):
+        self._workdir = ""
+        if self._study.filename == "":
+            self._workdir = tempfile.TemporaryDirectory()
+        else:
+            self._workdir = os.path.join(
+                os.path.dirname(self._study.filename),
+                "_PAMHYR_",
+                self._study.name.replace(" ", "_"),
+                self._solver.name.replace(" ", "_"),
+            )
+            os.makedirs(self._workdir, exist_ok=True)
+
+    def setup_process(self):
+        self._alarm.start(100)
+        self._output = Queue()
+        self._process = self.new_process(self._parent)
+
+    def new_process(self, parent):
+        new = QProcess(parent)
+        new.setWorkingDirectory(self._workdir)
+        new.setProcessChannelMode(QProcess.MergedChannels)
+        return new
+
+    def export(self):
+        self._log(f" *** Export study {self._solver.name}", color="blue")
+        ok = self._solver.export(self._study, self._workdir, self._mage_rep, qlog=self._output)
+        self.update()
+
+        return ok
+
+    def closeEvent(self, event):
+        self._alarm.stop()
+        super(SolverLogWindowAdisTS, self).closeEvent(event)
+
+    def _copy(self):
+        self.find(QTextEdit, "textEdit").copy()
+
+    #######
+    # LOG #
+    #######
+
+    def _log(self, msg, color=None):
+        if type(msg) is str:
+            self._log_str(msg, color)
+        elif type(msg) is int:
+            self._log_int(msg, color)
+
+    def _log_str(self, msg, color=None):
+        if msg == "":
+            return
+
+        logger.info(f"solver: {msg}")
+        msg = msg.rsplit('\n', 1)[0]
+
+        if color is not None:
+            msg = f"<font color=\"{color}\">" + msg + "</font>"
+
+        self.find(QTextEdit, "textEdit").append(msg)
+
+    def _log_int(self, int_code, color=None):
+        logger.info(f"solver: Returns {int_code}")
+        color = "blue" if int_code == 0 else "red"
+
+        self.find(QTextEdit, "textEdit")\
+            .append(
+                f"<font color=\"{color}\">" +
+                f" *** Finished with code {int_code}" +
+                "</font>"
+            )
+
+        self.statusbar.showMessage(
+            "Done" if int_code == 0 else "Failed",
+            3000
+        )
+
+    ##########
+    # UPDATE #
+    ##########
+
+    def update(self):
+        if self._solver.is_stoped():
+            self.find(QAction, "action_start").setEnabled(True)
+            self.find(QAction, "action_pause").setEnabled(False)
+            self.find(QAction, "action_stop").setEnabled(False)
+            self.find(QAction, "action_results").setEnabled(True)
+
+            if self._solver.log_file() != "":
+                self.find(QAction, "action_log_file").setEnabled(True)
+
+            self._update_logs_all()
+            # self._update_get_results()
+
+        self._update_logs_all()
+
+    def _update_get_results(self):
+        if self._results is None:
+            def reading_fn():
+                try:
+                    self._results = self._solver.results(
+                        self._study, self._workdir, qlog=self._output
+                    )
+                    self._parent.set_results(self._solver, self._results)
+                except Exception as e:
+                    logger.error(f"Failed to open results")
+                    logger_exception(e)
+
+            dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
+            dlg.exec_()
+
+    def _update_logs_all(self):
+        while self._output.qsize() != 0:
+            s = self._output.get()
+
+            try:
+                if type(s) is str and "[ERROR]" in s:
+                    self._log(s.encode("utf-8"), color="red")
+                else:
+                    self._log(s)
+            except Exception as e:
+                logger_exception(e)
+
+    ####################
+    # Process controle #
+    ####################
+
+    def run(self):
+        self._log(f" *** Run solver {self._solver.name}", color="blue")
+        self._solver.run(
+            self._study,
+            process=self._process,
+            output_queue=self._output
+        )
+
+    def start(self):
+        if self._solver.is_stoped():
+            self._log(f" *** Export study {self._solver.name}", color="blue")
+
+            ok = self._solver.export(
+                self._study, self._workdir, self._mage_rep, qlog=self._output
+            )
+
+            if not ok:
+                self._log(f" *** Failed to export", color="red")
+                self.update()
+                return
+            else:
+                self.update()
+                self._process = self.new_process(self._parent)
+
+        self._log(" *** Start", color="blue")
+        self._results = None
+        self._solver.start(self._study, process=self._process)
+
+        self.find(QAction, "action_start").setEnabled(False)
+        if _signal:
+            self.find(QAction, "action_pause").setEnabled(True)
+        else:
+            self.find(QAction, "action_pause").setEnabled(False)
+        self.find(QAction, "action_stop").setEnabled(True)
+        self.find(QAction, "action_log_file").setEnabled(False)
+        self.find(QAction, "action_results").setEnabled(False)
+
+    def pause(self):
+        self._log(" *** Pause", color="blue")
+        self._solver.pause()
+
+        self.find(QAction, "action_start").setEnabled(True)
+        self.find(QAction, "action_pause").setEnabled(False)
+        self.find(QAction, "action_stop").setEnabled(True)
+        self.find(QAction, "action_results").setEnabled(False)
+
+    def stop(self):
+        self._log(" *** Stop", color="blue")
+        self._solver.kill()
+
+        self.find(QAction, "action_start").setEnabled(True)
+        self.find(QAction, "action_pause").setEnabled(False)
+        self.find(QAction, "action_stop").setEnabled(False)
+        self.find(QAction, "action_results").setEnabled(True)
+        if self._solver.log_file() != "":
+            self.find(QAction, "action_log_file").setEnabled(True)
+
+    ###########
+    # Results #
+    ###########
+
+    def results(self):
+        if self._results is None:
+            def reading_fn():
+                self._results = self._solver.results(
+                    self._study, self._workdir, qlog=self._output
+                )
+
+            dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
+            dlg.exec_()
+
+        self._parent.set_results(self._solver, self._results)
+        self._parent.open_solver_results_adists(self._solver, self._results)
+
+        self._solver.has_results_loaded()
+
+    def resultsMage(self):
+        if self._results_mage is None:
+            mage_solver  = next(filter(lambda x: x._name == self._mage_rep, self._config.solvers))
+            workdir_mage = os.path.join(
+                os.path.dirname(self._study.filename),
+                "_PAMHYR_",
+                self._study.name.replace(" ", "_"),
+                mage_solver.name.replace(" ", "_"),
+            )
+            def reading_fn():
+                self._results_mage = mage_solver.results(
+                    self._study, workdir_mage, qlog=self._output
+                )
+
+            dlg = ReadingResultsDialog(reading_fn=reading_fn, parent=self)
+            dlg.exec_()
+
+        self._parent.set_results(mage_solver, self._results_mage)
+        self._parent.open_solver_results(mage_solver, self._results_mage)
+
+        mage_solver.has_results_loaded()
+
+    def log_file(self):
+        file_name = os.path.join(self._workdir, self._solver.log_file())
+        log = SolverLogFileWindow(
+            file_name=file_name,
+            study=self._study,
+            config=self._config,
+            solver=self._solver,
+            parent=self,
+        )
+        log.show()
+
diff --git a/src/View/Translate.py b/src/View/Translate.py
index 99b3e542f97fcd7ef19d4a4bb123bbc69f10a2b5..3af3b28ea2db054353eb1c1a78d6e69d89bc6eee 100644
--- a/src/View/Translate.py
+++ b/src/View/Translate.py
@@ -28,6 +28,7 @@ class CommonWordTranslate(PamhyrTranslate):
         super(CommonWordTranslate, self).__init__()
 
         self._dict["name"] = _translate("CommonWord", "Name")
+        self._dict["title"] = _translate("CommonWord", "Title")
         self._dict["type"] = _translate("CommonWord", "Type")
         self._dict["value"] = _translate("CommonWord", "Value")
         self._dict["comment"] = _translate("CommonWord", "Comment")
@@ -47,6 +48,8 @@ class CommonWordTranslate(PamhyrTranslate):
             "CommonWord", "Not associated"
         )
 
+        self._dict["method"] = _translate("CommonWord", "Method")
+
 
 class UnitTranslate(CommonWordTranslate):
     def __init__(self):
@@ -74,7 +77,17 @@ class UnitTranslate(CommonWordTranslate):
         self._dict["unit_date_s"] = _translate("Unit", "Date (sec)")
         self._dict["unit_date_iso"] = _translate("Unit", "Date (ISO format)")
 
-        self._dict["unit_wet_area"] = _translate("Unit", "Wet Area (m^2)")
+        self._dict["unit_area"] = _translate("Unit", "Area")
+        self._dict["unit_rho"] = _translate("Unit", "Rho")
+        self._dict["unit_porosity"] = _translate("Unit", "Porosity")
+        self._dict["unit_cdc_riv"] = _translate("Unit", "CDC_RIV")
+        self._dict["unit_cdc_cas"] = _translate("Unit", "CDC_CAS")
+        self._dict["unit_apd"] = _translate("Unit", "APD")
+        self._dict["unit_ac"] = _translate("Unit", "AC")
+        self._dict["unit_bc"] = _translate("Unit", "BC")
+
+        self._dict["unit_concentration"] = _translate("Unit", "Concentration (g/l)")
+        self._dict["unit_wet_area"] = _translate("Unit", "Wet Area (m²)")
         self._dict["unit_wet_perimeter"] = _translate(
             "Unit", "Wet Perimeter (m)"
         )
diff --git a/src/View/ui/BoundaryConditionsAdisTS.ui b/src/View/ui/BoundaryConditionsAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..e9fce33d1d6985423e7943c667b1a1e395aa1751
--- /dev/null
+++ b/src/View/ui/BoundaryConditionsAdisTS.ui
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>861</width>
+    <height>498</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <property name="locale">
+   <locale language="English" country="Europe"/>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QWidget" name="verticalLayoutWidget_2">
+       <layout class="QVBoxLayout" name="verticalLayout_2">
+        <item>
+         <widget class="QTableView" name="tableView"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="verticalLayoutWidget">
+       <layout class="QVBoxLayout" name="verticalLayout"/>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>861</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_add"/>
+   <addaction name="action_del"/>
+   <addaction name="action_edit"/>
+  </widget>
+  <action name="action_add">
+   <property name="checkable">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add</string>
+   </property>
+   <property name="toolTip">
+    <string>Add a new boundary condition or punctual contribution</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+N</string>
+   </property>
+  </action>
+  <action name="action_del">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/del.png</normaloff>ressources/del.png</iconset>
+   </property>
+   <property name="text">
+    <string>Delete</string>
+   </property>
+   <property name="toolTip">
+    <string>Delete current selected rows</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+D</string>
+   </property>
+  </action>
+  <action name="action_edit">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset>
+   </property>
+   <property name="text">
+    <string>Edit</string>
+   </property>
+   <property name="toolTip">
+    <string>Edit boundary condition or punctual contribution</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+E</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/D90AdisTS.ui b/src/View/ui/D90AdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..b4fe3e2d6777785e701a5c7771e11d6ce77952af
--- /dev/null
+++ b/src/View/ui/D90AdisTS.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1024</width>
+    <height>576</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter_2">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QWidget" name="layoutWidget">
+       <layout class="QVBoxLayout" name="verticalLayout_3">
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout"/>
+        </item>
+        <item>
+         <widget class="QTableView" name="tableView"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QSplitter" name="splitter">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <widget class="QWidget" name="verticalLayoutWidget">
+        <layout class="QVBoxLayout" name="verticalLayout_1"/>
+       </widget>
+       <widget class="QWidget" name="verticalLayoutWidget_2">
+        <layout class="QVBoxLayout" name="verticalLayout_2"/>
+       </widget>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>1024</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/DIFAdisTS.ui b/src/View/ui/DIFAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..b4fe3e2d6777785e701a5c7771e11d6ce77952af
--- /dev/null
+++ b/src/View/ui/DIFAdisTS.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1024</width>
+    <height>576</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter_2">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QWidget" name="layoutWidget">
+       <layout class="QVBoxLayout" name="verticalLayout_3">
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout"/>
+        </item>
+        <item>
+         <widget class="QTableView" name="tableView"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QSplitter" name="splitter">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <widget class="QWidget" name="verticalLayoutWidget">
+        <layout class="QVBoxLayout" name="verticalLayout_1"/>
+       </widget>
+       <widget class="QWidget" name="verticalLayoutWidget_2">
+        <layout class="QVBoxLayout" name="verticalLayout_2"/>
+       </widget>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>1024</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/EditBoundaryConditionsAdisTS.ui b/src/View/ui/EditBoundaryConditionsAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..bd55a027cd27a91d93e5e69a8c14396a889e9c92
--- /dev/null
+++ b/src/View/ui/EditBoundaryConditionsAdisTS.ui
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>450</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <property name="locale">
+   <locale language="English" country="Europe"/>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QWidget" name="verticalLayoutWidget_2">
+       <layout class="QVBoxLayout" name="verticalLayout_table">
+        <item>
+         <widget class="QTableView" name="tableView">
+          <property name="minimumSize">
+           <size>
+            <width>0</width>
+            <height>200</height>
+           </size>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="verticalLayoutWidget">
+       <layout class="QVBoxLayout" name="verticalLayout"/>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_add"/>
+   <addaction name="action_del"/>
+  </widget>
+  <action name="action_add">
+   <property name="checkable">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add</string>
+   </property>
+   <property name="toolTip">
+    <string>Add a new point in boundary condition or punctual contribution</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+N</string>
+   </property>
+  </action>
+  <action name="action_del">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/del.png</normaloff>ressources/del.png</iconset>
+   </property>
+   <property name="text">
+    <string>Delete</string>
+   </property>
+   <property name="toolTip">
+    <string>Delete current selected rows</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+D</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/EditLateralContributionAdisTS.ui b/src/View/ui/EditLateralContributionAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..8b9f006b60513053d508240d409a0c69de9aa89d
--- /dev/null
+++ b/src/View/ui/EditLateralContributionAdisTS.ui
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>450</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <property name="locale">
+   <locale language="English" country="Europe"/>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QTableView" name="tableView"/>
+      <widget class="QWidget" name="verticalLayoutWidget">
+       <layout class="QVBoxLayout" name="verticalLayout"/>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_add"/>
+   <addaction name="action_del"/>
+  </widget>
+  <action name="action_add">
+   <property name="checkable">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add</string>
+   </property>
+   <property name="toolTip">
+    <string>Add a new point in boundary condition or lateral contribution</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+N</string>
+   </property>
+  </action>
+  <action name="action_del">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/del.png</normaloff>ressources/del.png</iconset>
+   </property>
+   <property name="text">
+    <string>Delete</string>
+   </property>
+   <property name="toolTip">
+    <string>Delete current selected rows</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+D</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/InitialConditionsAdisTS.ui b/src/View/ui/InitialConditionsAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..b4fe3e2d6777785e701a5c7771e11d6ce77952af
--- /dev/null
+++ b/src/View/ui/InitialConditionsAdisTS.ui
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1024</width>
+    <height>576</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter_2">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QWidget" name="layoutWidget">
+       <layout class="QVBoxLayout" name="verticalLayout_3">
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout"/>
+        </item>
+        <item>
+         <widget class="QTableView" name="tableView"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QSplitter" name="splitter">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <widget class="QWidget" name="verticalLayoutWidget">
+        <layout class="QVBoxLayout" name="verticalLayout_1"/>
+       </widget>
+       <widget class="QWidget" name="verticalLayoutWidget_2">
+        <layout class="QVBoxLayout" name="verticalLayout_2"/>
+       </widget>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>1024</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/LateralContributions.ui b/src/View/ui/LateralContributions.ui
index 20e1b4f139724c5b2444788c8db921539b091b55..32f4f60a3ddd5d3fc208041f17bb7bac26c4aa11 100644
--- a/src/View/ui/LateralContributions.ui
+++ b/src/View/ui/LateralContributions.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>800</width>
-    <height>450</height>
+    <width>936</width>
+    <height>594</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -82,7 +82,7 @@
     <rect>
      <x>0</x>
      <y>0</y>
-     <width>800</width>
+     <width>936</width>
      <height>22</height>
     </rect>
    </property>
diff --git a/src/View/ui/LateralContributionsAdisTS.ui b/src/View/ui/LateralContributionsAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..3ac8806c4d313c6bf54f1dda9408524c7640107a
--- /dev/null
+++ b/src/View/ui/LateralContributionsAdisTS.ui
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>990</width>
+    <height>582</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <property name="locale">
+   <locale language="English" country="Europe"/>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <property name="enabled">
+    <bool>true</bool>
+   </property>
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QWidget" name="verticalLayoutWidget">
+       <layout class="QVBoxLayout" name="verticalLayout">
+        <item>
+         <widget class="QTableView" name="tableView"/>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="verticalLayoutWidget_2">
+       <layout class="QVBoxLayout" name="verticalLayout_2"/>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>990</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_add"/>
+   <addaction name="action_del"/>
+   <addaction name="action_edit"/>
+  </widget>
+  <action name="action_add">
+   <property name="checkable">
+    <bool>false</bool>
+   </property>
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add</string>
+   </property>
+   <property name="toolTip">
+    <string>Add a new boundary condition or lateral contribution</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+N</string>
+   </property>
+  </action>
+  <action name="action_del">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/del.png</normaloff>ressources/del.png</iconset>
+   </property>
+   <property name="text">
+    <string>Delete</string>
+   </property>
+   <property name="toolTip">
+    <string>Delete current selected rows</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+D</string>
+   </property>
+  </action>
+  <action name="action_edit">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/edit.png</normaloff>ressources/edit.png</iconset>
+   </property>
+   <property name="text">
+    <string>Edit</string>
+   </property>
+   <property name="toolTip">
+    <string>Edit boundary condition or lateral contribution</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+E</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/MainWindow.ui b/src/View/ui/MainWindow.ui
index 1156fca9c6884130ee35902ca7e6a14b54edeed5..9dba82a2badbda5d48d290b761e0f81956598ea1 100644
--- a/src/View/ui/MainWindow.ui
+++ b/src/View/ui/MainWindow.ui
@@ -67,6 +67,7 @@
         <family>Ubuntu</family>
         <weight>50</weight>
         <bold>false</bold>
+        <kerning>false</kerning>
        </font>
       </property>
       <property name="currentIndex">
@@ -90,6 +91,7 @@
      <family>Sans Serif</family>
      <weight>50</weight>
      <bold>false</bold>
+     <kerning>false</kerning>
     </font>
    </property>
    <widget class="QMenu" name="menu_File">
@@ -137,6 +139,7 @@
     </property>
     <addaction name="action_menu_numerical_parameter"/>
     <addaction name="action_menu_run_solver"/>
+    <addaction name="action_menu_run_adists"/>
    </widget>
    <widget class="QMenu" name="menu_Hydraulics">
     <property name="locale">
@@ -210,12 +213,22 @@
     <addaction name="action_menu_additional_file"/>
     <addaction name="menuMage"/>
    </widget>
+   <widget class="QMenu" name="menuAdisTS">
+    <property name="title">
+     <string>AdisTS</string>
+    </property>
+    <addaction name="action_menu_output_rk"/>
+    <addaction name="action_menu_pollutants"/>
+    <addaction name="action_menu_d90"/>
+    <addaction name="action_menu_dif"/>
+   </widget>
    <addaction name="menu_File"/>
    <addaction name="menu_network"/>
    <addaction name="menu_geometry"/>
    <addaction name="menu_Hydraulics"/>
    <addaction name="menuSediment"/>
    <addaction name="menu_Avensed"/>
+   <addaction name="menuAdisTS"/>
    <addaction name="menu_run"/>
    <addaction name="menu_results"/>
    <addaction name="menu_windows"/>
@@ -297,6 +310,7 @@
      <family>Ubuntu</family>
      <weight>50</weight>
      <bold>false</bold>
+     <kerning>false</kerning>
     </font>
    </property>
    <property name="windowTitle">
@@ -730,6 +744,35 @@
     <string>REP additional lines</string>
    </property>
   </action>
+  <action name="action_menu_output_rk">
+   <property name="text">
+    <string>Output RK</string>
+   </property>
+  </action>
+  <action name="action_menu_run_adists">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/run.png</normaloff>ressources/run.png</iconset>
+   </property>
+   <property name="text">
+    <string>Run AdisTS</string>
+   </property>
+  </action>
+  <action name="action_menu_pollutants">
+   <property name="text">
+    <string>Pollutants</string>
+   </property>
+  </action>
+  <action name="action_menu_d90">
+   <property name="text">
+    <string>D90</string>
+   </property>
+  </action>
+  <action name="action_menu_dif">
+   <property name="text">
+    <string>DIF</string>
+   </property>
+  </action>
  </widget>
  <resources/>
  <connections>
diff --git a/src/View/ui/OutputKpAdisTS.ui b/src/View/ui/OutputKpAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..cb84fb2af52767366cedc33cdc94f2ba30493a7f
--- /dev/null
+++ b/src/View/ui/OutputKpAdisTS.ui
@@ -0,0 +1,126 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>450</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Hydraulic structures</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter_2">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QWidget" name="verticalLayoutWidget_3">
+       <layout class="QVBoxLayout" name="verticalLayout_3">
+        <item>
+         <widget class="QTableView" name="tableView">
+          <property name="minimumSize">
+           <size>
+            <width>300</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="baseSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout">
+          <property name="leftMargin">
+           <number>5</number>
+          </property>
+          <item>
+           <widget class="QCheckBox" name="checkBox">
+            <property name="text">
+             <string>Enable / Disable Output RK AdisTS</string>
+            </property>
+            <property name="checked">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QSplitter" name="splitter">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <widget class="QWidget" name="verticalLayoutWidget">
+        <layout class="QVBoxLayout" name="verticalLayout"/>
+       </widget>
+       <widget class="QWidget" name="verticalLayoutWidget_2">
+        <layout class="QVBoxLayout" name="verticalLayout_2"/>
+       </widget>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_add"/>
+   <addaction name="action_delete"/>
+  </widget>
+  <action name="action_add">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add</string>
+   </property>
+   <property name="toolTip">
+    <string>Add a new point</string>
+   </property>
+  </action>
+  <action name="action_delete">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/del.png</normaloff>ressources/del.png</iconset>
+   </property>
+   <property name="text">
+    <string>Delete</string>
+   </property>
+   <property name="toolTip">
+    <string>Delete points</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/Pollutant.ui b/src/View/ui/Pollutant.ui
new file mode 100644
index 0000000000000000000000000000000000000000..ebe58a6f1b6d7ec5b599aa238d01fb67c4342677
--- /dev/null
+++ b/src/View/ui/Pollutant.ui
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QTableView" name="tableView">
+       <property name="minimumSize">
+        <size>
+         <width>300</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="baseSize">
+        <size>
+         <width>0</width>
+         <height>0</height>
+        </size>
+       </property>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+  </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/Pollutants.ui b/src/View/ui/Pollutants.ui
new file mode 100644
index 0000000000000000000000000000000000000000..195cf1bbef6c0734f6a63adf2edacb48e21696d1
--- /dev/null
+++ b/src/View/ui/Pollutants.ui
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>450</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Hydraulic structures</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter_2">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QWidget" name="verticalLayoutWidget_3">
+       <layout class="QVBoxLayout" name="verticalLayout_3">
+        <item>
+         <widget class="QTableView" name="tableView">
+          <property name="minimumSize">
+           <size>
+            <width>300</width>
+            <height>0</height>
+           </size>
+          </property>
+          <property name="baseSize">
+           <size>
+            <width>0</width>
+            <height>0</height>
+           </size>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout">
+          <property name="leftMargin">
+           <number>5</number>
+          </property>
+          <item>
+           <widget class="QCheckBox" name="checkBox">
+            <property name="text">
+             <string>Enable / Disable Pollutant</string>
+            </property>
+            <property name="checked">
+             <bool>true</bool>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+       </layout>
+      </widget>
+      <widget class="QSplitter" name="splitter">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <widget class="QWidget" name="verticalLayoutWidget">
+        <layout class="QVBoxLayout" name="verticalLayout"/>
+       </widget>
+       <widget class="QWidget" name="verticalLayoutWidget_2">
+        <layout class="QVBoxLayout" name="verticalLayout_2"/>
+       </widget>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_add"/>
+   <addaction name="action_delete"/>
+   <addaction name="action_edit"/>
+   <addaction name="action_initial_conditions"/>
+   <addaction name="action_boundary_conditions"/>
+   <addaction name="action_lateral_contributions"/>
+  </widget>
+  <action name="action_add">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add</string>
+   </property>
+   <property name="toolTip">
+    <string>Add a new point</string>
+   </property>
+  </action>
+  <action name="action_delete">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/del.png</normaloff>ressources/del.png</iconset>
+   </property>
+   <property name="text">
+    <string>Delete</string>
+   </property>
+   <property name="toolTip">
+    <string>Delete points</string>
+   </property>
+  </action>
+  <action name="action_edit">
+   <property name="text">
+    <string>Characteristics</string>
+   </property>
+   <property name="toolTip">
+    <string>Edit selected hydraulic structure</string>
+   </property>
+  </action>
+  <action name="action_initial_conditions">
+   <property name="text">
+    <string>InitialConditions</string>
+   </property>
+  </action>
+  <action name="action_boundary_conditions">
+   <property name="text">
+    <string>BoundaryConditions</string>
+   </property>
+  </action>
+  <action name="action_lateral_contributions">
+   <property name="text">
+    <string>LateralContributions</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/ResultsAdisTS.ui b/src/View/ui/ResultsAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..8a9a8ad5330a20ef15466b1dd77f7e9880c0ec66
--- /dev/null
+++ b/src/View/ui/ResultsAdisTS.ui
@@ -0,0 +1,235 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1280</width>
+    <height>720</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <property name="locale">
+   <locale language="English" country="Europe"/>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <property name="minimumSize">
+    <size>
+     <width>874</width>
+     <height>0</height>
+    </size>
+   </property>
+   <layout class="QGridLayout" name="gridLayout_3">
+    <item row="0" column="0">
+     <widget class="QSplitter" name="splitter_4">
+      <property name="orientation">
+       <enum>Qt::Horizontal</enum>
+      </property>
+      <widget class="QSplitter" name="splitter_3">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+       <widget class="QTableView" name="tableView_reach"/>
+       <widget class="QWidget" name="layoutWidget">
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <item>
+          <widget class="QTableView" name="tableView_profile"/>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="horizontalLayoutWidget">
+        <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <item>
+          <widget class="QTableView" name="tableView_pollutants"/>
+         </item>
+         <item>
+          <layout class="QVBoxLayout" name="verticalLayout_tot_left"/>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+      <widget class="QWidget" name="layoutWidget">
+       <layout class="QGridLayout" name="gridLayout_2">
+        <item row="1" column="0">
+         <layout class="QHBoxLayout" name="horizontalLayout_2"/>
+        </item>
+        <item row="0" column="0">
+         <widget class="QTabWidget" name="tabWidget">
+          <property name="currentIndex">
+           <number>2</number>
+          </property>
+          <widget class="QWidget" name="tab_4">
+           <attribute name="title">
+            <string>Raw data</string>
+           </attribute>
+           <layout class="QGridLayout" name="gridLayout_5">
+            <item row="0" column="1">
+             <layout class="QVBoxLayout" name="verticalLayout_5">
+              <item>
+               <layout class="QHBoxLayout" name="horizontalLayout_4"/>
+              </item>
+              <item>
+               <widget class="QTableView" name="tableView_raw_data"/>
+              </item>
+             </layout>
+            </item>
+           </layout>
+          </widget>
+          <widget class="QWidget" name="tab_2">
+           <attribute name="title">
+            <string>Concentration</string>
+           </attribute>
+           <layout class="QGridLayout" name="gridLayout_4">
+            <item row="0" column="0">
+             <layout class="QVBoxLayout" name="verticalLayout_concentration"/>
+            </item>
+           </layout>
+          </widget>
+          <widget class="QWidget" name="tab_3">
+           <attribute name="title">
+            <string>Mass</string>
+           </attribute>
+           <layout class="QGridLayout" name="gridLayout">
+            <item row="0" column="0">
+             <layout class="QVBoxLayout" name="verticalLayout_mass"/>
+            </item>
+           </layout>
+          </widget>
+          <widget class="QWidget" name="tab_5">
+           <attribute name="title">
+            <string>Total Sediments</string>
+           </attribute>
+           <layout class="QGridLayout" name="gridLayout_6">
+            <item row="0" column="0">
+             <layout class="QVBoxLayout" name="verticalLayout_tot">
+              <item>
+               <widget class="QTabWidget" name="tabWidget_2">
+                <property name="currentIndex">
+                 <number>3</number>
+                </property>
+                <widget class="QWidget" name="tab">
+                 <attribute name="title">
+                  <string>Concentration</string>
+                 </attribute>
+                 <layout class="QGridLayout" name="gridLayout_7">
+                  <item row="0" column="0">
+                   <layout class="QVBoxLayout" name="verticalLayout_tot_c"/>
+                  </item>
+                 </layout>
+                </widget>
+                <widget class="QWidget" name="tab_6">
+                 <attribute name="title">
+                  <string>Left thickness</string>
+                 </attribute>
+                 <layout class="QGridLayout" name="gridLayout_8">
+                  <item row="0" column="0">
+                   <layout class="QVBoxLayout" name="verticalLayout_tot_left_2"/>
+                  </item>
+                 </layout>
+                </widget>
+                <widget class="QWidget" name="tab_7">
+                 <attribute name="title">
+                  <string>Minor thickness</string>
+                 </attribute>
+                 <layout class="QGridLayout" name="gridLayout_9">
+                  <item row="0" column="0">
+                   <layout class="QVBoxLayout" name="verticalLayout_tot_minor"/>
+                  </item>
+                 </layout>
+                </widget>
+                <widget class="QWidget" name="tab_8">
+                 <attribute name="title">
+                  <string>Right thickness</string>
+                 </attribute>
+                 <layout class="QGridLayout" name="gridLayout_10">
+                  <item row="0" column="0">
+                   <layout class="QVBoxLayout" name="verticalLayout_tot_right"/>
+                  </item>
+                 </layout>
+                </widget>
+               </widget>
+              </item>
+             </layout>
+            </item>
+           </layout>
+          </widget>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>1280</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="enabled">
+    <bool>true</bool>
+   </property>
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_add"/>
+   <addaction name="action_export"/>
+   <addaction name="action_reload"/>
+  </widget>
+  <action name="action_add">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/add.png</normaloff>ressources/add.png</iconset>
+   </property>
+   <property name="text">
+    <string>Add</string>
+   </property>
+   <property name="toolTip">
+    <string>Add custom visualization</string>
+   </property>
+  </action>
+  <action name="action_reload">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/reload.png</normaloff>ressources/reload.png</iconset>
+   </property>
+   <property name="text">
+    <string>Reload</string>
+   </property>
+  </action>
+  <action name="action_export">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/export.png</normaloff>ressources/export.png</iconset>
+   </property>
+   <property name="text">
+    <string>Export</string>
+   </property>
+   <property name="toolTip">
+    <string>Export raw data</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+E</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/SelectSolverAdisTS.ui b/src/View/ui/SelectSolverAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..2c8867e9f986b1d0d3ec1b97f73fba8d3458c2f5
--- /dev/null
+++ b/src/View/ui/SelectSolverAdisTS.ui
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>384</width>
+    <height>107</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Dialog</string>
+  </property>
+  <property name="locale">
+   <locale language="English" country="Europe"/>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="0" column="0">
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>AdisTS Solver:</string>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QComboBox" name="comboBox"/>
+   </item>
+   <item row="4" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="pushButton_run">
+       <property name="text">
+        <string>Run</string>
+       </property>
+       <property name="icon">
+        <iconset>
+         <normaloff>ressources/run.png</normaloff>ressources/run.png</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="pushButton_cancel">
+       <property name="text">
+        <string>Cancel</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="3" column="0">
+    <widget class="QComboBox" name="comboBoxRepMage"/>
+   </item>
+   <item row="2" column="0">
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>Mage Repertory:</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/View/ui/SolverLogAdisTS.ui b/src/View/ui/SolverLogAdisTS.ui
new file mode 100644
index 0000000000000000000000000000000000000000..3677152afb62d36ab184fc678069ca648d00f5f6
--- /dev/null
+++ b/src/View/ui/SolverLogAdisTS.ui
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>1152</width>
+    <height>648</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QGridLayout" name="gridLayout">
+    <item row="0" column="0">
+     <widget class="QTextEdit" name="textEdit">
+      <property name="font">
+       <font>
+        <family>Monospace</family>
+        <pointsize>10</pointsize>
+       </font>
+      </property>
+      <property name="locale">
+       <locale language="English" country="Europe"/>
+      </property>
+      <property name="documentTitle">
+       <string notr="true"/>
+      </property>
+      <property name="undoRedoEnabled">
+       <bool>false</bool>
+      </property>
+      <property name="readOnly">
+       <bool>true</bool>
+      </property>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>1152</width>
+     <height>22</height>
+    </rect>
+   </property>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QToolBar" name="toolBar">
+   <property name="enabled">
+    <bool>true</bool>
+   </property>
+   <property name="windowTitle">
+    <string>toolBar</string>
+   </property>
+   <attribute name="toolBarArea">
+    <enum>TopToolBarArea</enum>
+   </attribute>
+   <attribute name="toolBarBreak">
+    <bool>false</bool>
+   </attribute>
+   <addaction name="action_start"/>
+   <addaction name="action_pause"/>
+   <addaction name="action_stop"/>
+   <addaction name="action_log_file"/>
+   <addaction name="action_results"/>
+   <addaction name="action_results_Mage"/>
+  </widget>
+  <action name="action_stop">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/close.png</normaloff>ressources/close.png</iconset>
+   </property>
+   <property name="text">
+    <string>Stop</string>
+   </property>
+  </action>
+  <action name="action_start">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/run.png</normaloff>ressources/run.png</iconset>
+   </property>
+   <property name="text">
+    <string>Start</string>
+   </property>
+  </action>
+  <action name="action_pause">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/player_pause.png</normaloff>ressources/player_pause.png</iconset>
+   </property>
+   <property name="text">
+    <string>Pause</string>
+   </property>
+  </action>
+  <action name="action_log_file">
+   <property name="icon">
+    <iconset>
+     <normaloff>ressources/zoom.png</normaloff>ressources/zoom.png</iconset>
+   </property>
+   <property name="text">
+    <string>LogFile</string>
+   </property>
+  </action>
+  <action name="action_results">
+   <property name="text">
+    <string>results</string>
+   </property>
+  </action>
+  <action name="action_results_Mage">
+   <property name="text">
+    <string>results_Mage</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/config.py b/src/config.py
index 1209cc1686291210dc09661df1758809396446bc..71b85a979221e47c7125f38d89776432b2298cf6 100644
--- a/src/config.py
+++ b/src/config.py
@@ -41,6 +41,7 @@ class Config(SQL):
         self.filename = Config.filename()
         self.set_default_value()
 
+
         logging.info(f"Configuration file : {self.filename}")
 
         super(Config, self).__init__(filename=self.filename)
diff --git a/src/tools.py b/src/tools.py
index d7ba936d24f14f068a60295f516be52f084c80a5..12308a7b23afc1c27f0c4c48f7a9a21f84a8558b 100644
--- a/src/tools.py
+++ b/src/tools.py
@@ -299,6 +299,24 @@ def timestamp_to_old_pamhyr_date(time: int):
 
     return s
 
+def timestamp_to_old_pamhyr_date_adists(time: int):
+    t0 = datetime.fromtimestamp(0)
+
+    # HACK: Windows do not accept negative timestamps
+    if time < 0:
+        t = t0 + timedelta(seconds=int(time))
+    else:
+        t = datetime.fromtimestamp(int(time))
+
+    dt = t - t0
+    hours = dt.seconds // 3600
+    minutes = (dt.seconds % 3600) // 60
+    seconds = dt.seconds % 60
+
+    s = f"{dt.days:>3}:{hours:>2}:{minutes:>2}"
+    s = s.replace(" ", "0")
+
+    return s
 
 def get_user_name():
     if with_pwd:
diff --git a/tests_cases/Ardeche/Ardeche.pamhyr b/tests_cases/Ardeche/Ardeche.pamhyr
index 1dc13ad2416f94f848fa368d441eb3b20921c77b..1efb0ae8dde7817a8f93c275114fde846c18eaae 100644
Binary files a/tests_cases/Ardeche/Ardeche.pamhyr and b/tests_cases/Ardeche/Ardeche.pamhyr differ
diff --git a/tests_cases/Ardeche/Ardeche.pamhyr-journal b/tests_cases/Ardeche/Ardeche.pamhyr-journal
new file mode 100644
index 0000000000000000000000000000000000000000..624a5f81faf4cb9659a77c8af45c4107c7acc4a5
Binary files /dev/null and b/tests_cases/Ardeche/Ardeche.pamhyr-journal differ
diff --git a/tests_cases/Enlargement/Enlargement.pamhyr b/tests_cases/Enlargement/Enlargement.pamhyr
index 12b3751da32b437e218fa5932fe18c75df6d416f..92b931d2b11fa20988bcd7a1af3b8906e2293812 100644
Binary files a/tests_cases/Enlargement/Enlargement.pamhyr and b/tests_cases/Enlargement/Enlargement.pamhyr differ