Repository: snek5000/snek5000 (language: python)

Issue:
Saving (and plot) of an estimation of remaining_clock_time
From my experience, the figure obtained in fluidsim with `sim.output.print_stdout.plot_clock_times()` is VERY useful.

The related code is in https://foss.heptapod.net/fluiddyn/fluidsim/-/blob/branch/default/fluidsim/base/output/print_stdout.py (methods `_evaluate_duration_left` and of course `plot_clock_times`).

The logic of these methods should also applies for Nek5000.

So we could move some code in `fluidsim_core` and use it for snek.

Note that we don't need to write that for each time step and we don't need to write that in stdout (it can be another file with for some time steps `it, time, dt, remaining_clock_time`).

Related to #81 and #121


Discussion / hints:
Also this detail while deciding end of sim: https://github.com/snek5000/snek5000/pull/121#issuecomment-995789318
@ashwinvis it would be great if you could work on the Fortran/Nek5000 part of this one (because I would struggle to do it :-)). Then, I will be able to work on the Python/fluidsim-core part.

It seems to me that 

- It can first be implemented in our tgv solver.
- We can use some `remaining-clock-time.csv` files in the session directories.
- A reasonable condition to print a new line in these files could be based on the real clock time, for example every 10 s, and I tend to think that we don't really need a user parameter for this (?).
I'm going to work on this one so that @ashwinvis you can focus on #179!

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c2b1b241..58253126 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,7 +33,8 @@ Security in case of vulnerabilities.
 ### Added
 
 - {func}`snek5000.output.base.Output.post_init_create_additional_source_files`,
-  {func}`snek5000.resources.get_base_templates` and {func}`snek5000.resources.get_base_template`.
+  {func}`snek5000.resources.get_base_templates` and
+  {func}`snek5000.resources.get_base_template`.
 
 ## [0.9.0rc0]
 
diff --git a/docs/examples/snek5000-tgv/src/snek5000_tgv/tgv.usr.f b/docs/examples/snek5000-tgv/src/snek5000_tgv/tgv.usr.f
index fcdeb657..b3eb9120 100644
--- a/docs/examples/snek5000-tgv/src/snek5000_tgv/tgv.usr.f
+++ b/docs/examples/snek5000-tgv/src/snek5000_tgv/tgv.usr.f
@@ -110,7 +110,7 @@ subroutine userchk()
       ! for remaining_clock_time
       character*28 rct_file_name
       real rct_seconds_between_save
-      real remaining_clock_time
+      real remaining_clock_time, remaining_eq_time, delta_clock_time
 
       period_save = real(UPARAM(11))
 
@@ -132,7 +132,9 @@ subroutine userchk()
          if (.not. exist) then
             if (nid.eq.0) then
                open(11, File=rct_file_name)
-               write(11,'(a)') 'it,time,dt,remaining_clock_time'
+               write(11,'(a,a)')
+     &           'it,equation_times,dt,delta_clock_times,',
+     &           'remaining_equation_times,remaining_clock_times'
                close(11)
             endif
          endif
@@ -149,13 +151,22 @@ subroutine userchk()
 c      iostep_full = iostep
 c      call full_restart_save(iostep_full)
 
-      if ((nid .eq. 0) .and. (istep > 0)) then
+      if (nid .eq. 0) then
          call compute_remaining_clock_time(
-     &       rct_seconds_between_save, remaining_clock_time)
-         if (remaining_clock_time >= 0.0) then
+     &       rct_seconds_between_save, remaining_clock_time,
+     &       remaining_eq_time, delta_clock_time)
+         if (istep == 0) then
             open(10, File=rct_file_name, position='append')
-            write(10,'(I12,A,g14.8,A,g14.8,A,g14.8)')
-     &          istep,',',time,',',dt,',',remaining_clock_time
+            write(10,'(I12,A,g14.8,A,g14.8,A,g14.8,A)')
+     &          istep,',',time,',',dt,',',delta_clock_time,
+     &          ',nan,nan'
+            close(10)
+         elseif (remaining_clock_time >= 0.0) then
+            open(10, File=rct_file_name, position='append')
+            write(10,'(I12,A,g14.8,A,g14.8,A,g14.8,A,g14.8,A,g14.8)')
+     &          istep,',',time,',',dt,',',delta_clock_time,
+     &          ',',remaining_eq_time,
+     &          ',',remaining_clock_time
             close(10)
          endif
       endif
@@ -241,15 +252,15 @@ subroutine compute_energy_enstrophy(e1,e2,u,v,w)
       end
 c-----------------------------------------------------------------------
       subroutine compute_remaining_clock_time(
-     &    seconds_between_save, remaining_clock_time)
+     &    seconds_between_save, remaining_clock_time, remaining_eq_time,
+     &    delta_clock_time)
       implicit none
 
       include 'SIZE'
       include 'TOTAL'
 
       real seconds_between_save
-      real remaining_clock_time
-      real remaining_eq_time
+      real remaining_clock_time, remaining_eq_time
 
       real clock_time, delta_clock_time, delta_eq_time
 
@@ -260,11 +271,10 @@ subroutine compute_remaining_clock_time(
       save istep_next
 
       remaining_clock_time = -1.0
-      if (istep .eq. 1) then
-        call cpu_time(clock_time_last)
+      delta_clock_time = 0.0
+      if (istep .eq. 0) then
         eq_time_last = time
-        istep_last = 1
-        istep_next = 2
+        istep_next = 1
       elseif ((istep .eq. istep_next) .or. (lastep .eq. 1)) then
         call cpu_time(clock_time)
         delta_clock_time = clock_time - clock_time_last
@@ -278,10 +288,14 @@ subroutine compute_remaining_clock_time(
         else
           remaining_eq_time = (nsteps - istep) * dt
         endif
-        istep_next = istep + int((istep - istep_last) *
+        if (istep .eq. 1) then
+          istep_next = 2
+        else
+          istep_next = istep + int((istep - istep_last) *
      &      seconds_between_save / delta_clock_time)
-        if (istep_next .eq. istep) then
-          istep_next = istep_next + 1
+          if (istep_next .eq. istep) then
+            istep_next = istep_next + 1
+          endif
         endif
         istep_last = istep
         remaining_clock_time = remaining_eq_time / delta_eq_time *
diff --git a/docs/tuto_tgv.myst.md b/docs/tuto_tgv.myst.md
index 9768414c..cc97cbb5 100644
--- a/docs/tuto_tgv.myst.md
+++ b/docs/tuto_tgv.myst.md
@@ -139,6 +139,24 @@ The command `snek-ipy-load` can be used to start a IPython session and load the
 simulation saved in the current directory.
 ```
 
++++
+
+## How long was the run?
+
+`snek5000-tgv` saves a file `remaining_clock_time.csv` during the simulation. It can be
+read manually,
+
+```{code-cell} ipython3
+!cat {path_run}/remaining_clock_time.csv
+```
+
+and there is also an object `sim.output.remaining_clock_time` to load and plot these
+data (which is especially useful during the simulation!):
+
+```{code-cell} ipython3
+sim.output.remaining_clock_time.plot()
+```
+
 ## Parse, load and plot information contained in the Nek5000 log
 
 The object `sim.output.print_stdout` (see
@@ -273,3 +291,9 @@ ax.figure
 
 Note that we use for this tutorial very small and coarse simulations, which explain the
 differences between our results and the reference!
+
+Let us see what gives the `remaining_clock_time` plot after the second simulation:
+
+```{code-cell} ipython3
+sim.output.remaining_clock_time.plot()
+```
diff --git a/requirements/dev.txt b/requirements/dev.txt
index 219bd650..983f7684 100644
--- a/requirements/dev.txt
+++ b/requirements/dev.txt
@@ -1,5 +1,5 @@
 #
-# This file is autogenerated by pip-compile with Python 3.9
+# This file is autogenerated by pip-compile with Python 3.10
 # by the following command:
 #
 #    pip-compile --extra=dev --output-file=requirements/dev.txt --resolver=backtracking requirements/vcs_packages.in setup.cfg
@@ -127,8 +127,9 @@ fluiddyn==0.5.1
     #   fluidsim-core
     #   snek5000
     #   snek5000 (setup.cfg)
-fluidsim-core==0.7.1
+fluidsim-core @ hg+https://foss.heptapod.net/fluiddyn/fluidsim#subdirectory=lib
     # via
+    #   -r requirements/vcs_packages.in
     #   snek5000
     #   snek5000 (setup.cfg)
 fonttools==4.38.0
@@ -161,12 +162,9 @@ imagesize==1.4.1
     # via sphinx
 importlib-metadata==5.1.0
     # via
-    #   fluidsim-core
     #   jupyter-cache
     #   keyring
     #   myst-nb
-    #   nbconvert
-    #   sphinx
     #   twine
 inflection==0.5.1
     # via
diff --git a/requirements/docs.txt b/requirements/docs.txt
index eb9d8cb5..f42c1d7a 100644
--- a/requirements/docs.txt
+++ b/requirements/docs.txt
@@ -1,5 +1,5 @@
 #
-# This file is autogenerated by pip-compile with Python 3.9
+# This file is autogenerated by pip-compile with Python 3.10
 # by the following command:
 #
 #    pip-compile --extra=docs --output-file=requirements/docs.txt --resolver=backtracking requirements/vcs_packages.in setup.cfg
@@ -94,8 +94,9 @@ fluiddyn==0.5.1
     #   fluidsim-core
     #   snek5000
     #   snek5000 (setup.cfg)
-fluidsim-core==0.7.1
+fluidsim-core @ hg+https://foss.heptapod.net/fluiddyn/fluidsim#subdirectory=lib
     # via
+    #   -r requirements/vcs_packages.in
     #   snek5000
     #   snek5000 (setup.cfg)
 fonttools==4.38.0
@@ -124,11 +125,8 @@ imagesize==1.4.1
     # via sphinx
 importlib-metadata==5.1.0
     # via
-    #   fluidsim-core
     #   jupyter-cache
     #   myst-nb
-    #   nbconvert
-    #   sphinx
 inflection==0.5.1
     # via
     #   snek5000
diff --git a/requirements/main.txt b/requirements/main.txt
index d3b95f58..26fcc17c 100644
--- a/requirements/main.txt
+++ b/requirements/main.txt
@@ -1,5 +1,5 @@
 #
-# This file is autogenerated by pip-compile with Python 3.9
+# This file is autogenerated by pip-compile with Python 3.10
 # by the following command:
 #
 #    pip-compile --output-file=requirements/main.txt --resolver=backtracking setup.cfg
@@ -54,8 +54,6 @@ h5py==3.7.0
     #   h5netcdf
 idna==3.4
     # via requests
-importlib-metadata==5.1.0
-    # via fluidsim-core
 inflection==0.5.1
     # via snek5000 (setup.cfg)
 jinja2==3.1.2
@@ -160,5 +158,3 @@ xarray==2022.12.0
     # via pymech
 yte==1.5.1
     # via snakemake
-zipp==3.11.0
-    # via importlib-metadata
diff --git a/requirements/vcs_packages.in b/requirements/vcs_packages.in
index 6f6f5703..55211ebf 100644
--- a/requirements/vcs_packages.in
+++ b/requirements/vcs_packages.in
@@ -4,5 +4,5 @@
 snek5000-phill @ git+https://github.com/snek5000/snek5000-phill.git
 snek5000-cbox @ git+https://github.com/snek5000/snek5000-cbox.git
 # TODO before releases: comment out these 2 lines
-# fluidsim-core @ hg+https://foss.heptapod.net/fluiddyn/fluidsim#subdirectory=lib
+fluidsim-core @ hg+https://foss.heptapod.net/fluiddyn/fluidsim#subdirectory=lib
 # pymech @ git+https://github.com/eX-Mech/pymech.git
diff --git a/src/snek5000/output/remaining_clock_time.py b/src/snek5000/output/remaining_clock_time.py
index 8e44c6e6..c4af30b3 100644
--- a/src/snek5000/output/remaining_clock_time.py
+++ b/src/snek5000/output/remaining_clock_time.py
@@ -2,11 +2,38 @@
 
 """
 
+import numpy as np
+
+from fluidsim_core.output.remaining_clock_time import RemainingClockTime as Base
+
 from .simple_csv import OutputWithCsvFileAndParam
 
 
-class RemainingClockTime(OutputWithCsvFileAndParam):
+class RemainingClockTime(OutputWithCsvFileAndParam, Base):
     INDEX_USERPARAM = 12
     _tag = "remaining_clock_time"
     _param_name = "period_save_in_seconds"
     _param_default_value = 5.0
+
+    def _load_times(self):
+        df = self.load()
+        data = {key: df[key].values for key in df.keys()}
+        data["full_clock_time"] = np.nansum(data["delta_clock_times"])
+        data["equation_time_start"] = df.loc[0, "equation_times"]
+        return data
+
+    def load(self):
+        df = super().load()
+        df["delta_equation_times"] = df.equation_times.diff()
+        delta_time_inds = df.it.diff()
+        df["clock_times_per_timestep"] = df["delta_clock_times"] / delta_time_inds
+        keys = (
+            "delta_clock_times",
+            "delta_equation_times",
+            "clock_times_per_timestep",
+        )
+        df.loc[df["it"] == 0, keys] = np.nan
+        return df
+
+    def plot(self):
+        self.plot_clock_times()


Test patch:
diff --git a/requirements/tests-editable.txt b/requirements/tests-editable.txt
index ff36d879..47a447e4 100644
--- a/requirements/tests-editable.txt
+++ b/requirements/tests-editable.txt
@@ -1,5 +1,5 @@
 #
-# This file is autogenerated by pip-compile with Python 3.9
+# This file is autogenerated by pip-compile with Python 3.10
 # by the following command:
 #
 #    pip-compile --extra=tests --output-file=requirements/tests.txt --resolver=backtracking requirements/vcs_packages.in setup.cfg
@@ -70,8 +70,9 @@ fluiddyn==0.5.1
     #   fluidsim-core
     #   snek5000
     #   snek5000 (setup.cfg)
-fluidsim-core==0.7.1
+fluidsim-core @ hg+https://foss.heptapod.net/fluiddyn/fluidsim#subdirectory=lib
     # via
+    #   -r requirements/vcs_packages.in
     #   snek5000
     #   snek5000 (setup.cfg)
 fonttools==4.38.0
@@ -88,8 +89,6 @@ h5py==3.7.0
     #   h5netcdf
 idna==3.4
     # via requests
-importlib-metadata==5.1.0
-    # via fluidsim-core
 inflection==0.5.1
     # via
     #   snek5000
@@ -267,5 +266,3 @@ xarray==2022.12.0
     # via pymech
 yte==1.5.1
     # via snakemake
-zipp==3.11.0
-    # via importlib-metadata
diff --git a/requirements/tests.txt b/requirements/tests.txt
index ab5a68c9..eebe8b09 100644
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -1,5 +1,5 @@
 #
-# This file is autogenerated by pip-compile with Python 3.9
+# This file is autogenerated by pip-compile with Python 3.10
 # by the following command:
 #
 #    pip-compile --extra=tests --output-file=requirements/tests.txt --resolver=backtracking requirements/vcs_packages.in setup.cfg
@@ -70,8 +70,9 @@ fluiddyn==0.5.1
     #   fluidsim-core
     #   snek5000
     #   snek5000 (setup.cfg)
-fluidsim-core==0.7.1
+fluidsim-core @ hg+https://foss.heptapod.net/fluiddyn/fluidsim#subdirectory=lib
     # via
+    #   -r requirements/vcs_packages.in
     #   snek5000
     #   snek5000 (setup.cfg)
 fonttools==4.38.0
@@ -88,8 +89,6 @@ h5py==3.7.0
     #   h5netcdf
 idna==3.4
     # via requests
-importlib-metadata==5.1.0
-    # via fluidsim-core
 inflection==0.5.1
     # via
     #   snek5000
@@ -267,5 +266,3 @@ xarray==2022.12.0
     # via pymech
 yte==1.5.1
     # via snakemake
-zipp==3.11.0
-    # via importlib-metadata
diff --git a/tests/test_tgv.py b/tests/test_tgv.py
index d0808c42..7b7ecadf 100644
--- a/tests/test_tgv.py
+++ b/tests/test_tgv.py
@@ -14,3 +14,5 @@ def test_tgv(sim_tgv_executed):
     df = sim.output.remaining_clock_time.load()
     assert isinstance(df, pd.DataFrame)
     assert len(df) >= 2
+
+    sim.output.remaining_clock_time.plot()