Source code for snek5000.output.print_stdout
"""Load and parse stdout from Nek5000.
"""
import re
from pathlib import Path
import matplotlib.pyplot as plt
import pandas as pd
from fluiddyn.util.util import modification_date
from snek5000 import mpi
# Uses awkupy
# %load_ext iawk
[docs]class PrintStdOut:
"""Parse standard output log files."""
_tag = "print_stdout"
def __init__(self, output=None, path_file=None):
self.output = output
self._path_file = path_file
self.data = None
self._data_modif_time = None
@property
def path_file(self):
output = self.output
if output and not self._path_file:
path_run = Path(output.path_run)
logfiles = sorted(path_run.glob("*.log"))
if logfiles:
try:
self._path_file = next(
file
for file in logfiles
if file.name == f"{output.name_solver}.log"
)
except StopIteration:
self._path_file = logfiles[-1]
else:
self._path_file = path_run / f"{output.name_solver}.log"
return self._path_file.resolve()
@path_file.setter
def path_file(self, path_log_file):
self._path_file = path_log_file
@property
def text(self):
with open(self.path_file) as fp:
return fp.read()
[docs] def load(self, pressure_solver="gmres"):
"""Load time data from the log file"""
# Parse text starting with Step
# https://regex101.com/r/enFOAg/1
path_file_time = modification_date(self.path_file)
if self.data is not None and path_file_time <= self._data_modif_time:
return self.data
pattern_step = r"""^Step # For all lines starting with Step
\s* # Followed by some whitespaces
(?P<it>\d+) # 0: Capture timestep which are integers
.* # Followed by some characters until
t=\s* # t=spaces
(?P<t>\S+) # 1: Capture t: any non-whitespace char
,\s* # comma and spaces
DT=\s* # DT=spaces
(?P<dt>\S+) # 2: Capture DT: any non-whitespace char
,\s* # comma and spaces
C=\s* # C=spaces
(?P<CFL>\S+) # 3: Capture C: any non-whitespace char
"""
# TODO: add undocumented params.nek.pressure.solver parameter
# See Line 445 in Nek5000/core/reader_par.f
pattern_pressure = rf"""^\ +
(?P<it2>\d+)
.*
PRES\ {pressure_solver} # For all lines containing a string like PRES gmres
\s+
(?P<pres_it>\d+)
\s+
(?P<pres_div>\S+)
\s+
(?P<pres_div0>\S+)
\s+
(?P<pres_tol>\S+)
\s+
(?P<pres_etime>\S+)
\s+
(?P<pres_etime1>\S+)
"""
expr = re.compile(
f"({pattern_step})|({pattern_pressure})",
re.VERBOSE | re.MULTILINE,
)
keys_all = tuple(expr.groupindex)
# Divide into two tuples at "it2"
idx = keys_all.index("it2")
keys_step, keys_pressure = keys_all[:idx], keys_all[idx:]
matches = tuple(expr.finditer(self.text))
def make_dict(keys):
return {key: [m[key] for m in matches] for key in keys}
def make_df(keys):
return (
pd.DataFrame.from_dict(make_dict(keys))
.dropna()
.astype({key: int if key == "it" else float for key in keys})
)
df_step = make_df(keys_step).set_index("it")
df_pressure = (
make_df(keys_pressure).rename(columns={"it2": "it"}).set_index("it")
)
self.data = df_step.join(df_pressure)
self._data_modif_time = path_file_time
return self.data
def __call__(self, *args):
"""Print to stdout and log file simultaneously."""
mpi.printby0(*args)
if mpi.rank == 0 and self.output._has_to_save:
with self.path_file.open("a") as f:
f.write(" ".join(str(a) for a in args) + "\n")
[docs] def plot_dt_cfl(self):
"""Plot the evolution of the time step and the CFL number"""
df = self.load()
fig, (ax_top, ax_bot) = plt.subplots(nrows=2, sharex=True)
ax_bot.plot(df.t, df.dt)
ax_bot.set_title("time step")
ax_bot.set_xlabel("time")
ax_top.plot(df.t, df.CFL)
ax_top.set_title("CFL number")
params = self.output.sim.params
if params.nek.general.variable_dt:
ax_top.axhline(params.nek.general.target_cfl, color="r", label="Target CFL")
ax_top.legend(loc="lower right")
fig.tight_layout()
[docs] def plot_nb_iterations(self):
"""Plot the evolution of the number of iterations for the Poisson solver"""
df = self.load()
fig, ax = plt.subplots()
ax.plot(df.t, df.pres_it)
ax.set_ylabel("number of iterations")
ax.set_xlabel("time")
fig.tight_layout()