From 4d9fb9b161d82da9d98c1fa6df82d97bc41546fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Thu, 21 Mar 2024 21:03:06 +0100 Subject: [PATCH 01/11] add ogs monitor --- ogs-monitor.py | 201 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- 2 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 ogs-monitor.py diff --git a/ogs-monitor.py b/ogs-monitor.py new file mode 100644 index 0000000..28463d2 --- /dev/null +++ b/ogs-monitor.py @@ -0,0 +1,201 @@ +import sys +import warnings + +from ogs6py.ogs import OGS +import matplotlib.pyplot as plt +import matplotlib.animation as animation +from matplotlib import style +import matplotlib.ticker as mticker +import pandas as pd + +warnings.simplefilter(action='ignore', category=FutureWarning) + +class OGSMONITOR(object): + def __init__(self, logfile, reffile, update_interval, window_length, maximum_lines, convergence_metric, convergence_component): + self.f = OGS() + self.log_file = logfile + self.ref_file = reffile + self.crit = convergence_metric + self.crit_comp = convergence_component + self.window_length = window_length + self.interval = update_interval + self.maximum_lines = maximum_lines + self.filters = ["time_step_vs_iterations","by_time_step", "convergence_newton_iteration"] + self.plot_init() + self.plot() + def plot_init(self): + style.use('fast') + self.fig, self.axs = plt.subplots(2,2) + def plot(self): + def animate(_): + logfile_df = {} + logfile_ref_df = {} + for j, filt in enumerate(self.filters): + try: + logfile_df[j] = self.f.parse_out(logfile=self.log_file, filter = filt, maximum_lines=self.maximum_lines) + if j == 2: + logfile_df[j] = logfile_df[j][logfile_df[j]["time_step"] >= logfile_df[j].time_step.max()-self.window_length] + else: + logfile_df[j] = logfile_df[j][-self.window_length:] + except: + logfile_df[j] = None + if self.ref_file is not None: + logfile_ref_df[j] = self.f.parse_out(logfile=self.ref_file, filter = filt) + if j == 2: + logfile_ref_df[j] = logfile_ref_df[j][logfile_ref_df[j]["time_step"] >= logfile_ref_df[j].time_step.max()-self.window_length] + else: + logfile_ref_df[j] = logfile_ref_df[j][-self.window_length:] + else: + logfile_ref_df[j] = None + if logfile_df[0] is None: + return + if logfile_df[1] is None: + return + self.axs[0,0].clear() + self.axs[0,0].plot(logfile_df[0]["time_step"],logfile_df[0]["iteration_number"], 'b', label="actual log") + axs12 = self.axs[0,0].twiny() + if not logfile_ref_df[0] is None: + axs12.plot(logfile_ref_df[0]["time_step"],logfile_ref_df[0]["iteration_number"], 'k', label="reference data") + #axs12.set(xlabel="time step", ylabel="iterations") + self.axs[0,0].set(xlabel="time step", ylabel="iterations") + lines, labels = axs12.get_legend_handles_labels() + lines2, labels2 = self.axs[0,0].get_legend_handles_labels() + self.axs[0,0].legend(lines + lines2, labels + labels2, loc=0) + #self.axs[0,0].legend() + self.axs[0,0].set_title("iterations per time step") + + self.axs[0,1].clear() + self.axs[0,1].plot(logfile_df[1]["time_step"],logfile_df[1]["step_size"], 'b', label="actual log") + axs22 = self.axs[0,1].twiny() + if not logfile_ref_df[1] is None: + axs22.plot(logfile_ref_df[1]["time_step"],logfile_ref_df[1]["step_size"], 'k', label="reference data") + #axs22.set(xlabel="time step", ylabel="step size") + lines, labels = axs22.get_legend_handles_labels() + lines2, labels2 = self.axs[0,1].get_legend_handles_labels() + self.axs[0,1].legend(lines + lines2, labels + labels2, loc=0) + self.axs[0,1].set(xlabel="time step", ylabel="step size") + #self.axs[0,1].legend() + self.axs[0,1].set_title("time step sizes") + + self.axs[1,0].clear() + self.axs[1,0].plot(logfile_df[1]["time_step"],logfile_df[1]["assembly_time"],"b-", label="assembly time (actual)") + self.axs[1,0].plot(logfile_df[1]["time_step"],logfile_df[1]["linear_solver_time"], "g-", label="linear solver time (actual)") + axs32 = self.axs[1,0].twiny() + if not logfile_ref_df[1] is None: + axs32.plot(logfile_ref_df[1]["time_step"],logfile_ref_df[1]["assembly_time"], "b--", label="assembly time (ref)") + axs32.plot(logfile_ref_df[1]["time_step"],logfile_ref_df[1]["linear_solver_time"], "g--", label="linear solver time (ref)") + #axs32.set(xlabel="time step", ylabel="time / s") + lines, labels = axs32.get_legend_handles_labels() + lines2, labels2 = self.axs[1,0].get_legend_handles_labels() + self.axs[1,0].legend(lines + lines2, labels + labels2, loc=0) + self.axs[1,0].set(xlabel="time step", ylabel="time / s") + #self.axs[1,0].legend() + self.axs[1,0].set_title("time step sizes") + + newindex = [] + for i in logfile_df[2][logfile_df[2]["component"]==self.crit_comp].index: + newindex.append(f"{i}") + + def update_ticks(x, pos): + if pos is None: + return "" + else: + it_num = logfile_df[2][logfile_df[2]["component"]==self.crit_comp]["iteration_number"].iloc[pos] + return f"{it_num}" + def update_ticks_ref(x, pos): + if pos is None: + return "" + else: + it_num = logfile_ref_df[2][logfile_ref_df[2]["component"]==self.crit_comp]["iteration_number"].iloc[pos] + return f"{it_num}\n" + + newindex2 = [i for i in range(len(newindex))] + time_data = [] + tmp = "" + for i in logfile_df[2][logfile_df[2]["component"]==self.crit_comp]["time_step"]: + if i == tmp: + time_data.append("") + else: + time_data.append(f"\n\n{i}") + tmp = i + + if not logfile_ref_df[2] is None: + newindex_ref = [] + for i in logfile_ref_df[2][logfile_ref_df[2]["component"]==self.crit_comp].index: + newindex_ref.append(f"{i}") + + newindex2_ref = [i for i in range(len(newindex_ref))] + time_data_ref = [] + tmp = "" + for i in logfile_ref_df[2][logfile_ref_df[2]["component"]==self.crit_comp]["time_step"]: + if i == tmp: + time_data_ref.append("") + else: + time_data_ref.append(f"{i}") + tmp = i + + self.axs[1,1].clear() + self.axs[1,1].plot(newindex, logfile_df[2][logfile_df[2]["component"]==self.crit_comp][self.crit], 'b', label="actual") + axs42 = self.axs[1,1].twiny() + if not logfile_ref_df[2] is None: + axs42.plot(newindex_ref, logfile_ref_df[2][logfile_ref_df[2]["component"]==self.crit_comp][self.crit], 'k', label="reference") + #axs42.set(xlabel="\n\ntime step", ylabel=self.crit) + axs42.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks_ref)) + sec_ref = axs42.secondary_xaxis(location=1) + sec_ref.set_xticks(newindex2_ref, labels=time_data_ref) + sec_ref.tick_params('x', length=0) + lines, labels = axs42.get_legend_handles_labels() + lines2, labels2 = self.axs[1,1].get_legend_handles_labels() + self.axs[1,1].legend(lines + lines2, labels + labels2, loc=0) + self.axs[1,1].set(xlabel="\n\ntime step", ylabel=self.crit) + #self.axs[1,1].legend() + self.axs[1,1].set_title("criterion") + self.axs[1,1].xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks)) + self.axs[1,1].set_yscale('log') + sec = self.axs[1,1].secondary_xaxis(location=0) + sec.set_xticks(newindex2, labels=time_data) + sec.tick_params('x', length=0) + ani = animation.FuncAnimation(self.fig, animate, interval=self.interval) + #plt.title("OGS Monitor") + plt.tight_layout() + plt.show() + +#ani = animation.FuncAnimation(fig, animate, interval=1000) +#plt.show() + +if __name__ == '__main__': + input_file = sys.argv[1] + ref_file = None + update_interval = 3000 + window_length = 10 + maximum_lines = None + convergence_metric = "dx" + convergence_component = 0 + norun = False + for i, arg in enumerate(sys.argv): + if arg == "-r": + ref_file = sys.argv[i+1] + elif arg == "-wl": + window_length = sys.argv[i+1] + elif arg == "-ml": + maximum_lines = sys.argv[i+1] + elif arg == "-cm": + convergence_metric = sys.argv[i+1] + elif arg == "-cc": + convergence_component = sys.argv[i+1] + elif arg == "-ui": + update_interval = sys.argv[i+1] + elif arg == "-h": + norun = True + print("OGS monitor help\n") + print("default usage: ogs-monitor.py logfile.log") + print("args:") + print(" -r [file] : provide reference data") + print(" -wl [window length] : provide length of displayed data") + print(" -ml [max lines] : provide maximum lines to read in") + print(" -cm [convergence metric] : provide convergence metric, could be dx, r, dx/x") + print(" -cc [convergence component] : provide convergence component, default: 0") + print(" -ui [update interval] : provide an update interval in ms") + print(" -h : display this help") + if norun is False: + ogs_monitor = OGSMONITOR(input_file, ref_file, update_interval, window_length, maximum_lines, convergence_metric, convergence_component) diff --git a/setup.py b/setup.py index 1ea70ae..5300af3 100644 --- a/setup.py +++ b/setup.py @@ -55,5 +55,5 @@ def find_version(*file_paths): include_package_data=True, python_requires='>=3.8', install_requires=["lxml","pandas"], - py_modules=["ogs6py/ogs","ogs6py/log_parser/log_parser", "ogs6py/log_parser/common_ogs_analyses", "ogs6py/ogs_regexes/ogs_regexes"], + py_modules=["ogs6py/ogs","ogs6py/log_parser/log_parser", "ogs6py/log_parser/common_ogs_analyses", "ogs6py/ogs_regexes/ogs_regexes", "ogs-monitor"], packages=["ogs6py/classes","ogs6py/log_parser","ogs6py/ogs_regexes"]) From c9df0f15aa0b598579627ca9d66abd189b1cef1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Fri, 22 Mar 2024 00:35:54 +0100 Subject: [PATCH 02/11] increase performance by parsing the log file only once --- ogs-monitor.py | 328 ++++++++++++++++++++++++++++++------------------- 1 file changed, 202 insertions(+), 126 deletions(-) diff --git a/ogs-monitor.py b/ogs-monitor.py index 28463d2..eb7789a 100644 --- a/ogs-monitor.py +++ b/ogs-monitor.py @@ -1,176 +1,249 @@ import sys import warnings -from ogs6py.ogs import OGS +import time import matplotlib.pyplot as plt import matplotlib.animation as animation from matplotlib import style import matplotlib.ticker as mticker import pandas as pd +import ogs6py.log_parser.log_parser as parser +import ogs6py.log_parser.common_ogs_analyses as parse_fcts warnings.simplefilter(action='ignore', category=FutureWarning) class OGSMONITOR(object): - def __init__(self, logfile, reffile, update_interval, window_length, maximum_lines, convergence_metric, convergence_component): - self.f = OGS() + def __init__(self, logfile, reffile, update_interval, window_length, maximum_lines, convergence_metric, convergence_component, show_quadrant): self.log_file = logfile self.ref_file = reffile self.crit = convergence_metric self.crit_comp = convergence_component - self.window_length = window_length - self.interval = update_interval - self.maximum_lines = maximum_lines - self.filters = ["time_step_vs_iterations","by_time_step", "convergence_newton_iteration"] + self.window_length = int(window_length) + self.interval = float(update_interval) + if maximum_lines is None: + self.maximum_lines = maximum_lines + else: + self.maximum_lines = int(maximum_lines) + self.running = True + self.show_quadrant = int(show_quadrant) + self.filters = ["time_step_vs_iterations","by_time_step", "convergence_newton_iteration","analysis_simulation"] + self.filterdict = {"by_time_step":parse_fcts.analysis_time_step, + "convergence_newton_iteration":parse_fcts.analysis_convergence_newton_iteration, + "convergence_coupling_iteration": parse_fcts.analysis_convergence_coupling_iteration, + "time_step_vs_iterations": parse_fcts.time_step_vs_iterations, + "analysis_simulation": parse_fcts.analysis_simulation, + "fill_ogs_context": parse_fcts.fill_ogs_context + } + self.logfile_ref_df = {} + if self.ref_file is not None: + records_ref = parser.parse_file(self.ref_file, maximum_lines=self.maximum_lines, force_parallel=False) + df_ref = pd.DataFrame(records_ref) + df_ref = parse_fcts.fill_ogs_context(df_ref) + for j, filt in enumerate(self.filters): + self.logfile_ref_df[j] = self.filterdict[filt](df_ref) + self.logfile_ref_df[j].reset_index(inplace=True) + else: + for j, filt in enumerate(self.filters): + self.logfile_ref_df[j] = None self.plot_init() self.plot() def plot_init(self): style.use('fast') - self.fig, self.axs = plt.subplots(2,2) + if self.show_quadrant == -1: + self.fig, self.axs = plt.subplots(2,2) + else: + self.fig, self.axs = plt.subplots(1,1) + def map_quadrant_rqfilter(self, j): + if j == 3: + return True + elif self.show_quadrant == -1: + return True + elif j == 1 and self.show_quadrant == 0: + return True + elif j == 0 and self.show_quadrant == 1: + return True + elif j == 1 and self.show_quadrant == 2: + return True + elif j == 2 and self.show_quadrant == 3: + return True + else: + return False def plot(self): def animate(_): logfile_df = {} - logfile_ref_df = {} + start = time.time() + records = parser.parse_file(self.log_file, maximum_lines=self.maximum_lines, force_parallel=False) + stop = time.time() + print(f"parse {stop-start} s") + start = time.time() + df = pd.DataFrame(records) + df = parse_fcts.fill_ogs_context(df) for j, filt in enumerate(self.filters): - try: - logfile_df[j] = self.f.parse_out(logfile=self.log_file, filter = filt, maximum_lines=self.maximum_lines) - if j == 2: - logfile_df[j] = logfile_df[j][logfile_df[j]["time_step"] >= logfile_df[j].time_step.max()-self.window_length] - else: - logfile_df[j] = logfile_df[j][-self.window_length:] - except: - logfile_df[j] = None - if self.ref_file is not None: - logfile_ref_df[j] = self.f.parse_out(logfile=self.ref_file, filter = filt) - if j == 2: - logfile_ref_df[j] = logfile_ref_df[j][logfile_ref_df[j]["time_step"] >= logfile_ref_df[j].time_step.max()-self.window_length] - else: - logfile_ref_df[j] = logfile_ref_df[j][-self.window_length:] + if self.map_quadrant_rqfilter(j): + try: + logfile_df[j] = self.filterdict[filt](df) + logfile_df[j].reset_index(inplace=True) + if j == 3: + self.running = False + elif j == 2: + logfile_df[j] = logfile_df[j][logfile_df[j]["time_step"] >= logfile_df[j].time_step.max()-self.window_length] + if self.ref_file is not None: + self.logfile_ref_df[j] = self.logfile_ref_df[j][self.logfile_ref_df[j]["time_step"] >= self.logfile_ref_df[j].time_step.max()-self.window_length] + else: + logfile_df[j] = logfile_df[j][-self.window_length:] + if self.ref_file is not None: + self.logfile_ref_df[j] = self.logfile_ref_df[j][-self.window_length:] + except: + if j ==3: + self.running = True + logfile_df[j] = None + if ((self.show_quadrant == 0) or (self.show_quadrant == -1)): + if logfile_df[1] is None: + return + if ((self.show_quadrant == 1) or (self.show_quadrant == -1)): + if logfile_df[0] is None: + return + stop = time.time() + print(f"transform/filter df {stop-start} s") + start = time.time() + if ((self.show_quadrant == -1) or (self.show_quadrant == 1)): + if self.show_quadrant == -1: + ax = self.axs[0,0] else: - logfile_ref_df[j] = None - if logfile_df[0] is None: - return - if logfile_df[1] is None: - return - self.axs[0,0].clear() - self.axs[0,0].plot(logfile_df[0]["time_step"],logfile_df[0]["iteration_number"], 'b', label="actual log") - axs12 = self.axs[0,0].twiny() - if not logfile_ref_df[0] is None: - axs12.plot(logfile_ref_df[0]["time_step"],logfile_ref_df[0]["iteration_number"], 'k', label="reference data") - #axs12.set(xlabel="time step", ylabel="iterations") - self.axs[0,0].set(xlabel="time step", ylabel="iterations") - lines, labels = axs12.get_legend_handles_labels() - lines2, labels2 = self.axs[0,0].get_legend_handles_labels() - self.axs[0,0].legend(lines + lines2, labels + labels2, loc=0) - #self.axs[0,0].legend() - self.axs[0,0].set_title("iterations per time step") - - self.axs[0,1].clear() - self.axs[0,1].plot(logfile_df[1]["time_step"],logfile_df[1]["step_size"], 'b', label="actual log") - axs22 = self.axs[0,1].twiny() - if not logfile_ref_df[1] is None: - axs22.plot(logfile_ref_df[1]["time_step"],logfile_ref_df[1]["step_size"], 'k', label="reference data") - #axs22.set(xlabel="time step", ylabel="step size") - lines, labels = axs22.get_legend_handles_labels() - lines2, labels2 = self.axs[0,1].get_legend_handles_labels() - self.axs[0,1].legend(lines + lines2, labels + labels2, loc=0) - self.axs[0,1].set(xlabel="time step", ylabel="step size") - #self.axs[0,1].legend() - self.axs[0,1].set_title("time step sizes") - - self.axs[1,0].clear() - self.axs[1,0].plot(logfile_df[1]["time_step"],logfile_df[1]["assembly_time"],"b-", label="assembly time (actual)") - self.axs[1,0].plot(logfile_df[1]["time_step"],logfile_df[1]["linear_solver_time"], "g-", label="linear solver time (actual)") - axs32 = self.axs[1,0].twiny() - if not logfile_ref_df[1] is None: - axs32.plot(logfile_ref_df[1]["time_step"],logfile_ref_df[1]["assembly_time"], "b--", label="assembly time (ref)") - axs32.plot(logfile_ref_df[1]["time_step"],logfile_ref_df[1]["linear_solver_time"], "g--", label="linear solver time (ref)") - #axs32.set(xlabel="time step", ylabel="time / s") - lines, labels = axs32.get_legend_handles_labels() - lines2, labels2 = self.axs[1,0].get_legend_handles_labels() - self.axs[1,0].legend(lines + lines2, labels + labels2, loc=0) - self.axs[1,0].set(xlabel="time step", ylabel="time / s") - #self.axs[1,0].legend() - self.axs[1,0].set_title("time step sizes") - - newindex = [] - for i in logfile_df[2][logfile_df[2]["component"]==self.crit_comp].index: - newindex.append(f"{i}") + ax = self.axs + ax.clear() + ax.plot(logfile_df[0]["time_step"], logfile_df[0]["iteration_number"], 'b', label="actual log") + axs12 = ax.twiny() + if not self.logfile_ref_df[0] is None: + axs12.plot(self.logfile_ref_df[0]["time_step"], self.logfile_ref_df[0]["iteration_number"], 'k', label="reference data") + ax.set(xlabel="time step", ylabel="iterations") + lines, labels = axs12.get_legend_handles_labels() + lines2, labels2 = ax.get_legend_handles_labels() + ax.legend(lines + lines2, labels + labels2, loc=0) + ax.set_title("iterations per time step") - def update_ticks(x, pos): - if pos is None: - return "" + if ((self.show_quadrant == -1) or (self.show_quadrant == 0)): + if self.show_quadrant == -1: + ax = self.axs[0,1] else: - it_num = logfile_df[2][logfile_df[2]["component"]==self.crit_comp]["iteration_number"].iloc[pos] - return f"{it_num}" - def update_ticks_ref(x, pos): - if pos is None: - return "" - else: - it_num = logfile_ref_df[2][logfile_ref_df[2]["component"]==self.crit_comp]["iteration_number"].iloc[pos] - return f"{it_num}\n" + ax = self.axs + ax.clear() + ax.plot(logfile_df[1]["time_step"], logfile_df[1]["step_size"], 'b', label="actual log") + axs22 = ax.twiny() + if not self.logfile_ref_df[1] is None: + axs22.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["step_size"], 'k', label="reference data") + lines, labels = axs22.get_legend_handles_labels() + lines2, labels2 = ax.get_legend_handles_labels() + ax.legend(lines + lines2, labels + labels2, loc=0) + ax.set(xlabel="time step", ylabel="step size") + ax.set_title("time step sizes") - newindex2 = [i for i in range(len(newindex))] - time_data = [] - tmp = "" - for i in logfile_df[2][logfile_df[2]["component"]==self.crit_comp]["time_step"]: - if i == tmp: - time_data.append("") + if ((self.show_quadrant == -1) or (self.show_quadrant == 2)): + if self.show_quadrant == -1: + ax = self.axs[1,0] else: - time_data.append(f"\n\n{i}") - tmp = i + ax = self.axs + ax.clear() + ax.plot(logfile_df[1]["time_step"],logfile_df[1]["assembly_time"],"b-", label="assembly time (actual)") + ax.plot(logfile_df[1]["time_step"],logfile_df[1]["linear_solver_time"], "g-", label="linear solver time (actual)") + axs32 = ax.twiny() + if not self.logfile_ref_df[1] is None: + axs32.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["assembly_time"], "b--", label="assembly time (ref)") + axs32.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["linear_solver_time"], "g--", label="linear solver time (ref)") + lines, labels = axs32.get_legend_handles_labels() + lines2, labels2 = ax.get_legend_handles_labels() + ax.legend(lines + lines2, labels + labels2, loc=0) + ax.set(xlabel="time step", ylabel="time / s") + ax.set_title("time step sizes") + + if ((self.show_quadrant == -1) or (self.show_quadrant == 3)): + start2 = time.time() + newindex = [] + for i in logfile_df[2][logfile_df[2]["component"]==self.crit_comp].index: + newindex.append(f"{i}") - if not logfile_ref_df[2] is None: - newindex_ref = [] - for i in logfile_ref_df[2][logfile_ref_df[2]["component"]==self.crit_comp].index: - newindex_ref.append(f"{i}") + def update_ticks(x, pos): + if pos is None: + return "" + else: + it_num = logfile_df[2][logfile_df[2]["component"]==self.crit_comp]["iteration_number"].iloc[pos] + return f"{it_num}" + def update_ticks_ref(x, pos): + if pos is None: + return "" + else: + it_num = self.logfile_ref_df[2][self.logfile_ref_df[2]["component"]==self.crit_comp]["iteration_number"].iloc[pos] + return f"{it_num}\n" - newindex2_ref = [i for i in range(len(newindex_ref))] - time_data_ref = [] + newindex2 = [i for i in range(len(newindex))] + time_data = [] tmp = "" - for i in logfile_ref_df[2][logfile_ref_df[2]["component"]==self.crit_comp]["time_step"]: + for i in logfile_df[2][logfile_df[2]["component"]==self.crit_comp]["time_step"]: if i == tmp: - time_data_ref.append("") + time_data.append("") else: - time_data_ref.append(f"{i}") + time_data.append(f"\n\n{i}") tmp = i - self.axs[1,1].clear() - self.axs[1,1].plot(newindex, logfile_df[2][logfile_df[2]["component"]==self.crit_comp][self.crit], 'b', label="actual") - axs42 = self.axs[1,1].twiny() - if not logfile_ref_df[2] is None: - axs42.plot(newindex_ref, logfile_ref_df[2][logfile_ref_df[2]["component"]==self.crit_comp][self.crit], 'k', label="reference") - #axs42.set(xlabel="\n\ntime step", ylabel=self.crit) - axs42.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks_ref)) - sec_ref = axs42.secondary_xaxis(location=1) - sec_ref.set_xticks(newindex2_ref, labels=time_data_ref) - sec_ref.tick_params('x', length=0) - lines, labels = axs42.get_legend_handles_labels() - lines2, labels2 = self.axs[1,1].get_legend_handles_labels() - self.axs[1,1].legend(lines + lines2, labels + labels2, loc=0) - self.axs[1,1].set(xlabel="\n\ntime step", ylabel=self.crit) - #self.axs[1,1].legend() - self.axs[1,1].set_title("criterion") - self.axs[1,1].xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks)) - self.axs[1,1].set_yscale('log') - sec = self.axs[1,1].secondary_xaxis(location=0) - sec.set_xticks(newindex2, labels=time_data) - sec.tick_params('x', length=0) + if not self.logfile_ref_df[2] is None: + newindex_ref = [] + for i in self.logfile_ref_df[2][self.logfile_ref_df[2]["component"]==self.crit_comp].index: + newindex_ref.append(f"{i}") + + newindex2_ref = [i for i in range(len(newindex_ref))] + time_data_ref = [] + tmp = "" + for i in self.logfile_ref_df[2][self.logfile_ref_df[2]["component"]==self.crit_comp]["time_step"]: + if i == tmp: + time_data_ref.append("") + else: + time_data_ref.append(f"{i}") + tmp = i + stop2 = time.time() + print(f"index trafo {stop2-start2} s") + if self.show_quadrant == -1: + ax = self.axs[1,1] + else: + ax = self.axs + ax.clear() + ax.plot(newindex, logfile_df[2][logfile_df[2]["component"]==self.crit_comp][self.crit], 'b', label="actual") + axs42 = ax.twiny() + if not self.logfile_ref_df[2] is None: + axs42.plot(newindex_ref, self.logfile_ref_df[2][self.logfile_ref_df[2]["component"]==self.crit_comp][self.crit], 'k', label="reference") + axs42.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks_ref)) + sec_ref = axs42.secondary_xaxis(location=1) + sec_ref.set_xticks(newindex2_ref, labels=time_data_ref) + sec_ref.tick_params('x', length=0) + lines, labels = axs42.get_legend_handles_labels() + lines2, labels2 = ax.get_legend_handles_labels() + ax.legend(lines + lines2, labels + labels2, loc=0) + ax.set(xlabel="\n\ntime step", ylabel=self.crit) + ax.set_title("criterion") + ax.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks)) + ax.set_yscale('log') + sec = ax.secondary_xaxis(location=0) + sec.set_xticks(newindex2, labels=time_data) + sec.tick_params('x', length=0) + if self.running is True: + self.fig.suptitle("OGS Monitor\n(OGS running)") + else: + self.fig.suptitle(f"OGS Monitor\n(OGS excec time: {logfile_df[3]['execution_time'].iloc[0]} s)") + stop = time.time() + print(f"plot {stop-start} s") ani = animation.FuncAnimation(self.fig, animate, interval=self.interval) - #plt.title("OGS Monitor") plt.tight_layout() plt.show() -#ani = animation.FuncAnimation(fig, animate, interval=1000) -#plt.show() if __name__ == '__main__': input_file = sys.argv[1] ref_file = None - update_interval = 3000 + update_interval = 4000 window_length = 10 maximum_lines = None convergence_metric = "dx" convergence_component = 0 + show_quadrant = -1 norun = False for i, arg in enumerate(sys.argv): if arg == "-r": @@ -185,6 +258,8 @@ def update_ticks_ref(x, pos): convergence_component = sys.argv[i+1] elif arg == "-ui": update_interval = sys.argv[i+1] + elif arg == "-q": + show_quadrant = sys.argv[i+1] elif arg == "-h": norun = True print("OGS monitor help\n") @@ -196,6 +271,7 @@ def update_ticks_ref(x, pos): print(" -cm [convergence metric] : provide convergence metric, could be dx, r, dx/x") print(" -cc [convergence component] : provide convergence component, default: 0") print(" -ui [update interval] : provide an update interval in ms") + print(" -q [X] : show only quadrant X, default: all four are shown") print(" -h : display this help") if norun is False: - ogs_monitor = OGSMONITOR(input_file, ref_file, update_interval, window_length, maximum_lines, convergence_metric, convergence_component) + ogs_monitor = OGSMONITOR(input_file, ref_file, update_interval, window_length, maximum_lines, convergence_metric, convergence_component, show_quadrant) From 85006996a32a44137150f76b486fa845d4902b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Sat, 23 Mar 2024 00:55:59 +0100 Subject: [PATCH 03/11] improve appearance --- ogs-monitor.py | 99 +++++++++++++++++++++++++++++++++++++------------ ogs.png | Bin 0 -> 24471 bytes 2 files changed, 76 insertions(+), 23 deletions(-) create mode 100644 ogs.png diff --git a/ogs-monitor.py b/ogs-monitor.py index eb7789a..b3ed051 100644 --- a/ogs-monitor.py +++ b/ogs-monitor.py @@ -1,7 +1,14 @@ +import os import sys import warnings import time +try: + from PIL import Image + imagemodule = True +except ModuleNotFoundError: + print("module Image not installed") + imagemodule = False import matplotlib.pyplot as plt import matplotlib.animation as animation from matplotlib import style @@ -45,14 +52,31 @@ def __init__(self, logfile, reffile, update_interval, window_length, maximum_lin else: for j, filt in enumerate(self.filters): self.logfile_ref_df[j] = None + self.q0_xb = None + self.q0_yb = None + self.q1_xb = None + self.q1_yb = None + self.q2_xb = None + self.q2_yb = None + self.q3_xb = None + self.q3_yb = None self.plot_init() self.plot() + def plot_init(self): style.use('fast') if self.show_quadrant == -1: self.fig, self.axs = plt.subplots(2,2) else: self.fig, self.axs = plt.subplots(1,1) + if imagemodule is True: + dirname = os.path.dirname(__file__) + im = Image.open(os.path.join(dirname, "ogs.png")) + height = im.size[1] + width = im.size[0] + newsize = (int(width/2), int(height/2)) + im = im.resize(newsize) + self.fig.figimage(im, 0, 0, alpha=0.5) def map_quadrant_rqfilter(self, j): if j == 3: return True @@ -69,7 +93,8 @@ def map_quadrant_rqfilter(self, j): else: return False def plot(self): - def animate(_): + def animate(m): + print(m) logfile_df = {} start = time.time() records = parser.parse_file(self.log_file, maximum_lines=self.maximum_lines, force_parallel=False) @@ -112,12 +137,18 @@ def animate(_): else: ax = self.axs ax.clear() + #ax.spines['top'].set_visible(False) not working + #ax.spines['right'].set_visible(False) ax.plot(logfile_df[0]["time_step"], logfile_df[0]["iteration_number"], 'b', label="actual log") - axs12 = ax.twiny() + if self.q1_yb is None: + self.q1_yb = ax.twiny() + self.q1_yb.get_legend_handles_labels() + #self.q1_yb.get_yaxis().set_visible(False) if not self.logfile_ref_df[0] is None: - axs12.plot(self.logfile_ref_df[0]["time_step"], self.logfile_ref_df[0]["iteration_number"], 'k', label="reference data") + #self.q1_yb.get_yaxis().set_visible(False) + self.q1_yb.plot(self.logfile_ref_df[0]["time_step"], self.logfile_ref_df[0]["iteration_number"], 'k', label="reference data") ax.set(xlabel="time step", ylabel="iterations") - lines, labels = axs12.get_legend_handles_labels() + lines, labels = self.q1_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() ax.legend(lines + lines2, labels + labels2, loc=0) ax.set_title("iterations per time step") @@ -127,15 +158,27 @@ def animate(_): ax = self.axs[0,1] else: ax = self.axs - ax.clear() - ax.plot(logfile_df[1]["time_step"], logfile_df[1]["step_size"], 'b', label="actual log") - axs22 = ax.twiny() + #ax.spines['top'].set_visible(False) + for entry in ax.get_shared_x_axes().get_siblings(ax): + entry.clear() + ax.plot(logfile_df[1]["time_step"], logfile_df[1]["step_size"], 'b-', label="time step (actual)") + if self.q0_yb is None: + self.q0_yb = ax.twiny() + #self.q0_yb.get_yaxis().set_visible(False) if not self.logfile_ref_df[1] is None: - axs22.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["step_size"], 'k', label="reference data") - lines, labels = axs22.get_legend_handles_labels() + #self.q0_yb.get_yaxis().set_visible(True) + self.q0_yb.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["step_size"], 'k-', label="time step (reference)") + if self.q0_xb is None: + self.q0_xb = ax.twinx() + self.q0_xb.plot(logfile_df[1]["time_step"], logfile_df[1]["step_start_time"], 'r-', label="start time (actual)") + self.q0_xb.set_ylabel("time / s", color="r") + self.q0_xb.yaxis.set_label_position("right") + lines, labels = self.q0_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() - ax.legend(lines + lines2, labels + labels2, loc=0) - ax.set(xlabel="time step", ylabel="step size") + lines3, labels3 = self.q0_xb.get_legend_handles_labels() + ax.legend(lines + lines2 + lines3, labels + labels2 + labels3, loc=0) + ax.set_xlabel("time step") + ax.set_ylabel("step size", color='b') ax.set_title("time step sizes") if ((self.show_quadrant == -1) or (self.show_quadrant == 2)): @@ -146,11 +189,14 @@ def animate(_): ax.clear() ax.plot(logfile_df[1]["time_step"],logfile_df[1]["assembly_time"],"b-", label="assembly time (actual)") ax.plot(logfile_df[1]["time_step"],logfile_df[1]["linear_solver_time"], "g-", label="linear solver time (actual)") - axs32 = ax.twiny() + if self.q2_yb is None: + self.q2_yb = ax.twiny() + #self.q2_yb.get_yaxis().set_visible(False) if not self.logfile_ref_df[1] is None: - axs32.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["assembly_time"], "b--", label="assembly time (ref)") - axs32.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["linear_solver_time"], "g--", label="linear solver time (ref)") - lines, labels = axs32.get_legend_handles_labels() + #self.q2_yb.get_yaxis().set_visible(True) + self.q2_yb.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["assembly_time"], "b--", label="assembly time (ref)") + self.q2_yb.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["linear_solver_time"], "g--", label="linear solver time (ref)") + lines, labels = self.q2_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() ax.legend(lines + lines2, labels + labels2, loc=0) ax.set(xlabel="time step", ylabel="time / s") @@ -207,14 +253,17 @@ def update_ticks_ref(x, pos): ax = self.axs ax.clear() ax.plot(newindex, logfile_df[2][logfile_df[2]["component"]==self.crit_comp][self.crit], 'b', label="actual") - axs42 = ax.twiny() + if self.q3_yb is None: + self.q3_yb = ax.twiny() + #self.q3_yb.get_yaxis().set_visible(False) if not self.logfile_ref_df[2] is None: - axs42.plot(newindex_ref, self.logfile_ref_df[2][self.logfile_ref_df[2]["component"]==self.crit_comp][self.crit], 'k', label="reference") - axs42.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks_ref)) - sec_ref = axs42.secondary_xaxis(location=1) + #self.q3_yb.get_yaxis().set_visible(True) + self.q3_yb.plot(newindex_ref, self.logfile_ref_df[2][self.logfile_ref_df[2]["component"]==self.crit_comp][self.crit], 'k', label="reference") + self.q3_yb.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks_ref)) + sec_ref = self.q3_yb.secondary_xaxis(location=1) sec_ref.set_xticks(newindex2_ref, labels=time_data_ref) sec_ref.tick_params('x', length=0) - lines, labels = axs42.get_legend_handles_labels() + lines, labels = self.q3_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() ax.legend(lines + lines2, labels + labels2, loc=0) ax.set(xlabel="\n\ntime step", ylabel=self.crit) @@ -225,13 +274,17 @@ def update_ticks_ref(x, pos): sec.set_xticks(newindex2, labels=time_data) sec.tick_params('x', length=0) if self.running is True: - self.fig.suptitle("OGS Monitor\n(OGS running)") + try: + self.fig.suptitle(f"OGS running\n({logfile_df[1]['time_step_solution_time'].sum()} s)") + except: + self.fig.suptitle("OGS running") else: - self.fig.suptitle(f"OGS Monitor\n(OGS excec time: {logfile_df[3]['execution_time'].iloc[0]} s)") + self.fig.suptitle(f"OGS finished\n({logfile_df[3]['execution_time'].iloc[0]} s)") stop = time.time() print(f"plot {stop-start} s") ani = animation.FuncAnimation(self.fig, animate, interval=self.interval) - plt.tight_layout() + #äplt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0) + plt.subplots_adjust(plt.subplots_adjust(top=0.9, bottom=0.1, left=0.1, right=0.9, hspace=0.3, wspace=0.2)) plt.show() diff --git a/ogs.png b/ogs.png new file mode 100644 index 0000000000000000000000000000000000000000..92cc0721b3aa049325805bae6678c923f35a8252 GIT binary patch literal 24471 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 Date: Sat, 23 Mar 2024 01:00:11 +0100 Subject: [PATCH 04/11] log parser: add start time to analyses --- ogs6py/log_parser/common_ogs_analyses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ogs6py/log_parser/common_ogs_analyses.py b/ogs6py/log_parser/common_ogs_analyses.py index ae9c7e6..bd5df40 100644 --- a/ogs6py/log_parser/common_ogs_analyses.py +++ b/ogs6py/log_parser/common_ogs_analyses.py @@ -49,7 +49,7 @@ def wrapped_f(df): def analysis_time_step(df): - interest1 = ['output_time', 'time_step_solution_time', 'step_size'] + interest1 = ['output_time', 'step_start_time', 'time_step_solution_time', 'step_size'] interest2 = ['assembly_time', 'linear_solver_time', 'dirichlet_time'] interest = [*interest1, *interest2] context = ['mpi_process', 'time_step'] From 8a2455e880c4f2fdb3d519dcaf71719a4b4b5cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Mon, 25 Mar 2024 23:55:55 +0100 Subject: [PATCH 05/11] change record parsing --- ogs-monitor.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/ogs-monitor.py b/ogs-monitor.py index b3ed051..f9e034b 100644 --- a/ogs-monitor.py +++ b/ogs-monitor.py @@ -3,6 +3,7 @@ import warnings import time +import numpy as np try: from PIL import Image imagemodule = True @@ -14,6 +15,7 @@ from matplotlib import style import matplotlib.ticker as mticker import pandas as pd +import ogs6py import ogs6py.log_parser.log_parser as parser import ogs6py.log_parser.common_ogs_analyses as parse_fcts @@ -43,7 +45,7 @@ def __init__(self, logfile, reffile, update_interval, window_length, maximum_lin } self.logfile_ref_df = {} if self.ref_file is not None: - records_ref = parser.parse_file(self.ref_file, maximum_lines=self.maximum_lines, force_parallel=False) + records_ref, _ = parser.parse_file(self.ref_file, maximum_lines=self.maximum_lines, force_parallel=False) df_ref = pd.DataFrame(records_ref) df_ref = parse_fcts.fill_ogs_context(df_ref) for j, filt in enumerate(self.filters): @@ -60,9 +62,42 @@ def __init__(self, logfile, reffile, update_interval, window_length, maximum_lin self.q2_yb = None self.q3_xb = None self.q3_yb = None + self.last_record = 0 + self.last_line = 0 + #TimeStepStartTime record + self.timesteps = [] + self.ts_simtime = [] + self.iterations_per_ts = [] + self.assembly_solver_time = [] + self.criterion = [] self.plot_init() self.plot() + def parse_records(self, records): + len_records = len(records) + for record in records[self.last_record:len_records]: + if isinstance(record, ogs6py.ogs_regexes.ogs_regexes.TimeStepStartTime): + self.timesteps.append(record.time_step) + self.ts_simtime.append((record.step_start_time, record.step_size)) + self.iterations_per_ts.append(0) + self.assembly_solver_time.append([0, 0]) + + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.IterationTime): + self.iterations_per_ts[-1] = record.iteration_number + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.AssemblyTime): + self.assembly_solver_time[-1][0] = (self.assembly_solver_time[-1][0] + + record.assembly_time) / (1. + self.iterations_per_ts[-1]) + print(self.assembly_solver_time[-1][0]) + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.LinearSolverTime): + self.assembly_solver_time[-1][1] = (self.assembly_solver_time[-1][1] + + record.linear_solver_time) / (1. + self.iterations_per_ts[-1]) + print(self.assembly_solver_time[-1][1]) + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.ComponentConvergenceCriterion): + if record.component == int(self.crit_comp): + self.criterion.append((self.timesteps[-1], self.iterations_per_ts, getattr(record, self.crit))) + self.last_record = len_records + + def plot_init(self): style.use('fast') if self.show_quadrant == -1: @@ -97,10 +132,12 @@ def animate(m): print(m) logfile_df = {} start = time.time() - records = parser.parse_file(self.log_file, maximum_lines=self.maximum_lines, force_parallel=False) + records, self.last_line = parser.parse_file(self.log_file, maximum_lines=self.maximum_lines, force_parallel=False, start_line=self.last_line) stop = time.time() print(f"parse {stop-start} s") start = time.time() + self.parse_records(records) + """ df = pd.DataFrame(records) df = parse_fcts.fill_ogs_context(df) for j, filt in enumerate(self.filters): @@ -128,6 +165,7 @@ def animate(m): if ((self.show_quadrant == 1) or (self.show_quadrant == -1)): if logfile_df[0] is None: return + """ stop = time.time() print(f"transform/filter df {stop-start} s") start = time.time() @@ -139,7 +177,10 @@ def animate(m): ax.clear() #ax.spines['top'].set_visible(False) not working #ax.spines['right'].set_visible(False) + """ ax.plot(logfile_df[0]["time_step"], logfile_df[0]["iteration_number"], 'b', label="actual log") + """ + ax.plot(np.array(self.timesteps), np.array(self.iterations_per_ts), 'b', label="actual log") if self.q1_yb is None: self.q1_yb = ax.twiny() self.q1_yb.get_legend_handles_labels() @@ -161,7 +202,10 @@ def animate(m): #ax.spines['top'].set_visible(False) for entry in ax.get_shared_x_axes().get_siblings(ax): entry.clear() + """ ax.plot(logfile_df[1]["time_step"], logfile_df[1]["step_size"], 'b-', label="time step (actual)") + """ + ax.plot(np.array(self.timesteps), np.array(self.ts_simtime)[:,1], 'b-', label="time step (actual)") if self.q0_yb is None: self.q0_yb = ax.twiny() #self.q0_yb.get_yaxis().set_visible(False) @@ -170,7 +214,10 @@ def animate(m): self.q0_yb.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["step_size"], 'k-', label="time step (reference)") if self.q0_xb is None: self.q0_xb = ax.twinx() + """ self.q0_xb.plot(logfile_df[1]["time_step"], logfile_df[1]["step_start_time"], 'r-', label="start time (actual)") + """ + self.q0_xb.plot(np.array(self.timesteps), np.array(self.ts_simtime)[:,0], 'r-', label="start time (actual)") self.q0_xb.set_ylabel("time / s", color="r") self.q0_xb.yaxis.set_label_position("right") lines, labels = self.q0_yb.get_legend_handles_labels() @@ -187,8 +234,12 @@ def animate(m): else: ax = self.axs ax.clear() + """ ax.plot(logfile_df[1]["time_step"],logfile_df[1]["assembly_time"],"b-", label="assembly time (actual)") ax.plot(logfile_df[1]["time_step"],logfile_df[1]["linear_solver_time"], "g-", label="linear solver time (actual)") + """ + ax.plot(np.array(self.timesteps), np.array(self.assembly_solver_time)[:,0],"b-", label="assembly time (actual)") + ax.plot(np.array(self.timesteps), np.array(self.assembly_solver_time)[:,1], "g-", label="linear solver time (actual)") if self.q2_yb is None: self.q2_yb = ax.twiny() #self.q2_yb.get_yaxis().set_visible(False) From d672d6538e09740a7e26e99b5386b7d8c1b88765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Thu, 28 Mar 2024 17:25:20 +0100 Subject: [PATCH 06/11] use records instead of df --- ogs-monitor.py | 156 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 104 insertions(+), 52 deletions(-) diff --git a/ogs-monitor.py b/ogs-monitor.py index f9e034b..fd18b34 100644 --- a/ogs-monitor.py +++ b/ogs-monitor.py @@ -36,6 +36,7 @@ def __init__(self, logfile, reffile, update_interval, window_length, maximum_lin self.running = True self.show_quadrant = int(show_quadrant) self.filters = ["time_step_vs_iterations","by_time_step", "convergence_newton_iteration","analysis_simulation"] + """ self.filterdict = {"by_time_step":parse_fcts.analysis_time_step, "convergence_newton_iteration":parse_fcts.analysis_convergence_newton_iteration, "convergence_coupling_iteration": parse_fcts.analysis_convergence_coupling_iteration, @@ -43,18 +44,30 @@ def __init__(self, logfile, reffile, update_interval, window_length, maximum_lin "analysis_simulation": parse_fcts.analysis_simulation, "fill_ogs_context": parse_fcts.fill_ogs_context } - self.logfile_ref_df = {} + """ + # plot data from records actual + self.last_record = {"actual": 0, "ref": 0} + self.last_line = {"actual": 0, "ref": 0} + self.timesteps = {"actual": [], "ref": []} + self.ts_simtime = {"actual": [], "ref": []} + self.iterations_per_ts = {"actual": [], "ref": []} + self.assembly_solver_time = {"actual": [], "ref": []} + self.criterion_x = {"actual": [], "ref": []} + self.criterion_label = {"actual": [], "ref": []} + self.criterion_y = {"actual": [], "ref": []} + self.crit_timesteppointer = {"actual": [], "ref": []} + if self.ref_file is not None: records_ref, _ = parser.parse_file(self.ref_file, maximum_lines=self.maximum_lines, force_parallel=False) + self.parse_records(records_ref, dataset="ref") + """ df_ref = pd.DataFrame(records_ref) df_ref = parse_fcts.fill_ogs_context(df_ref) for j, filt in enumerate(self.filters): self.logfile_ref_df[j] = self.filterdict[filt](df_ref) self.logfile_ref_df[j].reset_index(inplace=True) - else: - for j, filt in enumerate(self.filters): - self.logfile_ref_df[j] = None - self.q0_xb = None + """ + self.q0_xb = None self.q0_yb = None self.q1_xb = None self.q1_yb = None @@ -62,40 +75,40 @@ def __init__(self, logfile, reffile, update_interval, window_length, maximum_lin self.q2_yb = None self.q3_xb = None self.q3_yb = None - self.last_record = 0 - self.last_line = 0 - #TimeStepStartTime record - self.timesteps = [] - self.ts_simtime = [] - self.iterations_per_ts = [] - self.assembly_solver_time = [] - self.criterion = [] + # plot data from records actual self.plot_init() self.plot() - def parse_records(self, records): - len_records = len(records) - for record in records[self.last_record:len_records]: + def parse_records(self, records, dataset="actual"): + #len_records = len(records) + for record in records: #[self.last_record[dataset]:len_records]: if isinstance(record, ogs6py.ogs_regexes.ogs_regexes.TimeStepStartTime): - self.timesteps.append(record.time_step) - self.ts_simtime.append((record.step_start_time, record.step_size)) - self.iterations_per_ts.append(0) - self.assembly_solver_time.append([0, 0]) + self.timesteps[dataset].append(int(record.time_step)) + self.ts_simtime[dataset].append((float(record.step_start_time), float(record.step_size))) + self.iterations_per_ts[dataset].append(1) + self.assembly_solver_time[dataset].append([0, 0]) elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.IterationTime): - self.iterations_per_ts[-1] = record.iteration_number + self.iterations_per_ts[dataset][-1] = int(record.iteration_number)+1 elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.AssemblyTime): - self.assembly_solver_time[-1][0] = (self.assembly_solver_time[-1][0] + - record.assembly_time) / (1. + self.iterations_per_ts[-1]) - print(self.assembly_solver_time[-1][0]) + self.assembly_solver_time[dataset][-1][0] = (self.assembly_solver_time[dataset][-1][0] + + float(record.assembly_time)) / (self.iterations_per_ts[dataset][-1]) elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.LinearSolverTime): - self.assembly_solver_time[-1][1] = (self.assembly_solver_time[-1][1] + - record.linear_solver_time) / (1. + self.iterations_per_ts[-1]) - print(self.assembly_solver_time[-1][1]) + self.assembly_solver_time[dataset][-1][1] = (self.assembly_solver_time[dataset][-1][1] + + float(record.linear_solver_time)) / (self.iterations_per_ts[dataset][-1]) elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.ComponentConvergenceCriterion): if record.component == int(self.crit_comp): - self.criterion.append((self.timesteps[-1], self.iterations_per_ts, getattr(record, self.crit))) - self.last_record = len_records + if int(self.iterations_per_ts[dataset][-1]) == 1: + self.criterion_x[dataset].append(len(self.criterion_x[dataset])+1) + self.criterion_label[dataset].append(f"{self.iterations_per_ts[dataset][-1]} \n{self.timesteps[dataset][-1]}") + self.criterion_y[dataset].append(float(getattr(record, self.crit))) + self.crit_timesteppointer[dataset].append(len(self.criterion_x[dataset])-1) + else: + self.criterion_x[dataset].append(len(self.criterion_x[dataset])+1) + self.criterion_label[dataset].append(f"{self.iterations_per_ts[dataset][-1]} \n") + self.criterion_y[dataset].append(float(getattr(record, self.crit))) + #self.criterion[dataset].append((self.timesteps[dataset][-1], self.iterations_per_ts[dataset], getattr(record, self.crit))) + #self.last_record[dataset] = len_records def plot_init(self): @@ -132,7 +145,11 @@ def animate(m): print(m) logfile_df = {} start = time.time() - records, self.last_line = parser.parse_file(self.log_file, maximum_lines=self.maximum_lines, force_parallel=False, start_line=self.last_line) + records, self.last_line["actual"] = parser.parse_file( + self.log_file, + maximum_lines=self.maximum_lines, + force_parallel=False, + start_line=self.last_line["actual"]) stop = time.time() print(f"parse {stop-start} s") start = time.time() @@ -169,6 +186,11 @@ def animate(m): stop = time.time() print(f"transform/filter df {stop-start} s") start = time.time() + window_length = self.window_length + if len(self.timesteps["actual"]) < self.window_length: + window_length = len(self.timesteps["actual"]) + ts0 = int(np.array(self.timesteps["actual"])[-window_length])-1 + ts1 = int(np.array(self.timesteps["actual"])[-1]) if ((self.show_quadrant == -1) or (self.show_quadrant == 1)): if self.show_quadrant == -1: ax = self.axs[0,0] @@ -180,14 +202,15 @@ def animate(m): """ ax.plot(logfile_df[0]["time_step"], logfile_df[0]["iteration_number"], 'b', label="actual log") """ - ax.plot(np.array(self.timesteps), np.array(self.iterations_per_ts), 'b', label="actual log") + ax.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.iterations_per_ts["actual"])[-window_length:], 'b', label="actual log") if self.q1_yb is None: self.q1_yb = ax.twiny() self.q1_yb.get_legend_handles_labels() #self.q1_yb.get_yaxis().set_visible(False) - if not self.logfile_ref_df[0] is None: + if not self.ref_file is None: #self.q1_yb.get_yaxis().set_visible(False) - self.q1_yb.plot(self.logfile_ref_df[0]["time_step"], self.logfile_ref_df[0]["iteration_number"], 'k', label="reference data") + self.q1_yb.clear() + self.q1_yb.plot(np.array(self.timesteps["ref"])[ts0:ts1], np.array(self.iterations_per_ts["ref"])[ts0:ts1], 'k', label="reference data") ax.set(xlabel="time step", ylabel="iterations") lines, labels = self.q1_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() @@ -205,19 +228,20 @@ def animate(m): """ ax.plot(logfile_df[1]["time_step"], logfile_df[1]["step_size"], 'b-', label="time step (actual)") """ - ax.plot(np.array(self.timesteps), np.array(self.ts_simtime)[:,1], 'b-', label="time step (actual)") + ax.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.ts_simtime["actual"])[:,1][-window_length:], 'b-', label="time step (actual)") if self.q0_yb is None: self.q0_yb = ax.twiny() #self.q0_yb.get_yaxis().set_visible(False) - if not self.logfile_ref_df[1] is None: + if not self.ref_file is None: #self.q0_yb.get_yaxis().set_visible(True) - self.q0_yb.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["step_size"], 'k-', label="time step (reference)") + self.q0_yb.clear() + self.q0_yb.plot(np.array(self.timesteps["ref"])[ts0:ts1], np.array(self.ts_simtime["ref"])[:,1][ts0:ts1], 'k-', label="time step (reference)") if self.q0_xb is None: self.q0_xb = ax.twinx() """ self.q0_xb.plot(logfile_df[1]["time_step"], logfile_df[1]["step_start_time"], 'r-', label="start time (actual)") """ - self.q0_xb.plot(np.array(self.timesteps), np.array(self.ts_simtime)[:,0], 'r-', label="start time (actual)") + self.q0_xb.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.ts_simtime["actual"])[:,0][-window_length:], 'r-', label="start time (actual)") self.q0_xb.set_ylabel("time / s", color="r") self.q0_xb.yaxis.set_label_position("right") lines, labels = self.q0_yb.get_legend_handles_labels() @@ -238,15 +262,16 @@ def animate(m): ax.plot(logfile_df[1]["time_step"],logfile_df[1]["assembly_time"],"b-", label="assembly time (actual)") ax.plot(logfile_df[1]["time_step"],logfile_df[1]["linear_solver_time"], "g-", label="linear solver time (actual)") """ - ax.plot(np.array(self.timesteps), np.array(self.assembly_solver_time)[:,0],"b-", label="assembly time (actual)") - ax.plot(np.array(self.timesteps), np.array(self.assembly_solver_time)[:,1], "g-", label="linear solver time (actual)") + ax.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.assembly_solver_time["actual"])[:,0][-window_length:],"b-", label="assembly time (actual)") + ax.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.assembly_solver_time["actual"])[:,1][-window_length:], "g-", label="linear solver time (actual)") if self.q2_yb is None: self.q2_yb = ax.twiny() #self.q2_yb.get_yaxis().set_visible(False) - if not self.logfile_ref_df[1] is None: + if not self.ref_file is None: #self.q2_yb.get_yaxis().set_visible(True) - self.q2_yb.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["assembly_time"], "b--", label="assembly time (ref)") - self.q2_yb.plot(self.logfile_ref_df[1]["time_step"], self.logfile_ref_df[1]["linear_solver_time"], "g--", label="linear solver time (ref)") + self.q2_yb.clear() + self.q2_yb.plot(self.timesteps["ref"][ts0:ts1], np.array(self.assembly_solver_time["ref"])[:,0][ts0:ts1], "b--", label="assembly time (ref)") + self.q2_yb.plot(self.timesteps["ref"][ts0:ts1], np.array(self.assembly_solver_time["ref"])[:,1][ts0:ts1], "g--", label="linear solver time (ref)") lines, labels = self.q2_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() ax.legend(lines + lines2, labels + labels2, loc=0) @@ -254,6 +279,7 @@ def animate(m): ax.set_title("time step sizes") if ((self.show_quadrant == -1) or (self.show_quadrant == 3)): + """ start2 = time.time() newindex = [] for i in logfile_df[2][logfile_df[2]["component"]==self.crit_comp].index: @@ -298,32 +324,58 @@ def update_ticks_ref(x, pos): tmp = i stop2 = time.time() print(f"index trafo {stop2-start2} s") + """ + line_begin = self.crit_timesteppointer['actual'][ts0] + line_begin_ref = self.crit_timesteppointer['ref'][ts0] + line_stop_ref = line_begin_ref + if len(self.crit_timesteppointer['ref']) <= ts1: + ts1 = len(self.crit_timesteppointer['ref']) - 1 + line_stop_ref = len(self.criterion_x['ref']) + else: + line_stop_ref = self.crit_timesteppointer['ref'][ts1-1] + def update_ticks(x, pos): + if pos is None: + return "" + else: + it_num = self.criterion_label["actual"][line_begin:][pos] + return f"{it_num}" + def update_ticks_ref(x, pos): + if pos is None: + return "" + else: + it_num = self.criterion_label["ref"][line_begin_ref:line_stop_ref][pos] + return f"{it_num}" if self.show_quadrant == -1: ax = self.axs[1,1] else: ax = self.axs ax.clear() - ax.plot(newindex, logfile_df[2][logfile_df[2]["component"]==self.crit_comp][self.crit], 'b', label="actual") + #ax.plot(newindex, logfile_df[2][logfile_df[2]["component"]==self.crit_comp][self.crit], 'b', label="actual") + ax.plot(np.array(self.criterion_x["actual"])[line_begin:], np.array(self.criterion_y["actual"])[line_begin:],"b-", label="criterion (actual)") if self.q3_yb is None: self.q3_yb = ax.twiny() #self.q3_yb.get_yaxis().set_visible(False) - if not self.logfile_ref_df[2] is None: + if not self.ref_file is None: + self.q3_yb.clear() #self.q3_yb.get_yaxis().set_visible(True) - self.q3_yb.plot(newindex_ref, self.logfile_ref_df[2][self.logfile_ref_df[2]["component"]==self.crit_comp][self.crit], 'k', label="reference") + self.q3_yb.plot(np.array(self.criterion_x["ref"])[line_begin_ref:line_stop_ref], np.array(self.criterion_y["ref"])[line_begin_ref:line_stop_ref], 'k', label="reference") + self.q3_yb.set_xticks(np.array(self.criterion_x["actual"][line_begin_ref:line_stop_ref])) self.q3_yb.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks_ref)) - sec_ref = self.q3_yb.secondary_xaxis(location=1) - sec_ref.set_xticks(newindex2_ref, labels=time_data_ref) - sec_ref.tick_params('x', length=0) + #sec_ref = self.q3_yb.secondary_xaxis(location=1) + #sec_ref.set_xticks(newindex2_ref, labels=time_data_ref) + #sec_ref.tick_params('x', length=0) lines, labels = self.q3_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() ax.legend(lines + lines2, labels + labels2, loc=0) - ax.set(xlabel="\n\ntime step", ylabel=self.crit) + ax.set(xlabel="\niterations, time step", ylabel=self.crit) ax.set_title("criterion") + ax.tick_params('x', which='both', length=0) + ax.set_xticks(np.array(self.criterion_x["actual"][line_begin:])) ax.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks)) ax.set_yscale('log') - sec = ax.secondary_xaxis(location=0) - sec.set_xticks(newindex2, labels=time_data) - sec.tick_params('x', length=0) + #sec = ax.secondary_xaxis(location=0) + #sec.set_xticks(newindex2, labels=time_data) + #sec.tick_params('x', length=0) if self.running is True: try: self.fig.suptitle(f"OGS running\n({logfile_df[1]['time_step_solution_time'].sum()} s)") From 52b6f436001aa5d804f54599d63cb7a020ff22c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Thu, 28 Mar 2024 17:26:59 +0100 Subject: [PATCH 07/11] workaround to prevent redundant parsing --- ogs6py/log_parser/log_parser.py | 21 ++++++++++++--------- ogs6py/ogs.py | 7 +++++-- tests/test_ogs6py.py | 16 ++++++++-------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/ogs6py/log_parser/log_parser.py b/ogs6py/log_parser/log_parser.py index debb3f5..170e309 100644 --- a/ogs6py/log_parser/log_parser.py +++ b/ogs6py/log_parser/log_parser.py @@ -40,7 +40,7 @@ def mpi_processes(file_name): return processes -def parse_file(file_name, maximum_lines=None, force_parallel=False): +def parse_file(file_name, maximum_lines=None, force_parallel=False, start_line=0): ogs_res = ogs_regexes() parallel_log = force_parallel or mpi_processes(file_name) > 1 @@ -61,16 +61,19 @@ def compile_re_fn(mpi_process_regex): with open(file_name) as file: lines = iter(file) records = list() + last_line = 0 for line in lines: - number_of_lines_read += 1 + last_line = last_line + 1 + if last_line > start_line: + number_of_lines_read += 1 - if (maximum_lines is not None) and (maximum_lines > number_of_lines_read): - break - - for key, value in patterns: - if r := try_match(line, number_of_lines_read, key, value): - records.append(value(*r)) + if (maximum_lines is not None) and (maximum_lines > number_of_lines_read): break - return records + for key, value in patterns: + if r := try_match(line, number_of_lines_read, key, value): + records.append(value(*r)) + break + + return records, last_line diff --git a/ogs6py/ogs.py b/ogs6py/ogs.py index 38cd10e..9b75cef 100644 --- a/ogs6py/ogs.py +++ b/ogs6py/ogs.py @@ -1018,7 +1018,8 @@ def write_input(self, keep_includes: bool = False) -> None: def parse_out(self, logfile: str | None = None, filter: str | None = None, maximum_lines: int | None = None, - reset_index: bool = True) -> pd.DataFrame: + reset_index: bool = True, + return_records: bool = False) -> pd.DataFrame: """Parses the logfile Parameters @@ -1035,7 +1036,9 @@ def parse_out(self, logfile: str | None = None, """ if logfile is None: logfile = self.logfile - records = parser.parse_file(logfile, maximum_lines=maximum_lines, force_parallel=False) + records, _ = parser.parse_file(logfile, maximum_lines=maximum_lines, force_parallel=False) + if return_records is True: + return records df = pd.DataFrame(records) df = parse_fcts.fill_ogs_context(df) diff --git a/tests/test_ogs6py.py b/tests/test_ogs6py.py index ae1e2ad..89547b4 100644 --- a/tests/test_ogs6py.py +++ b/tests/test_ogs6py.py @@ -683,11 +683,11 @@ def test_model_run(self): def test_parallel_1_compare_serial_info(self): filename_p = 'tests/parser/parallel_1_info.txt' # Only for MPI execution with 1 process we need to tell the log parser by force_parallel=True! - records_p = parse_file(filename_p, force_parallel=True) + records_p, _ = parse_file(filename_p, force_parallel=True) num_of_record_type_p = [len(i) for i in log_types(records_p).values()] filename_s = 'tests/parser/serial_info.txt' - records_s = parse_file(filename_s) + records_s, _ = parse_file(filename_s) num_of_record_type_s = [len(i) for i in log_types(records_s).values()] self.assertSequenceEqual(num_of_record_type_s, num_of_record_type_p, @@ -695,7 +695,7 @@ def test_parallel_1_compare_serial_info(self): def test_parallel_3_debug(self): filename = 'tests/parser/parallel_3_debug.txt' - records = parse_file(filename) + records, _ = parse_file(filename) mpi_processes = 3 self.assertEqual(len(records) % mpi_processes, 0, @@ -723,7 +723,7 @@ def test_parallel_3_debug(self): def test_serial_convergence_newton_iteration_long(self): filename = 'tests/parser/serial_convergence_long.txt' - records = parse_file(filename) + records, _ = parse_file(filename) df = pd.DataFrame(records) df = fill_ogs_context(df) dfe = analysis_convergence_newton_iteration(df) @@ -740,7 +740,7 @@ def test_serial_convergence_newton_iteration_long(self): def test_serial_convergence_coupling_iteration_long(self): filename = 'tests/parser/serial_convergence_long.txt' - records = parse_file(filename) + records, _ = parse_file(filename) df = pd.DataFrame(records) dfe = analysis_simulation_termination(df) status = dfe.empty # No errors assumed @@ -763,7 +763,7 @@ def test_serial_convergence_coupling_iteration_long(self): def test_serial_critical(self): filename = 'tests/parser/serial_critical.txt' - records = parse_file(filename) + records, _ = parse_file(filename) self.assertEqual(len(records),4) df = pd.DataFrame(records) self.assertEqual(len(df), 4) @@ -776,7 +776,7 @@ def test_serial_critical(self): def test_serial_warning_only(self): filename = 'tests/parser/serial_warning_only.txt' - records = parse_file(filename) + records, _ = parse_file(filename) self.assertEqual(len(records),1) df = pd.DataFrame(records) self.assertEqual(len(df), 1) @@ -789,7 +789,7 @@ def test_serial_warning_only(self): def test_serial_time_vs_iterations(self): filename = 'tests/parser/serial_convergence_long.txt' - records = parse_file(filename) + records, _ = parse_file(filename) df = pd.DataFrame(records) df = fill_ogs_context(df) dfe = time_step_vs_iterations(df) From 9e2d894e37dfecfef8aba8eba0a4661de59b9dcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Fri, 29 Mar 2024 20:06:58 +0100 Subject: [PATCH 08/11] add criteria --- ogs-monitor.py | 82 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/ogs-monitor.py b/ogs-monitor.py index fd18b34..24d0637 100644 --- a/ogs-monitor.py +++ b/ogs-monitor.py @@ -22,7 +22,16 @@ warnings.simplefilter(action='ignore', category=FutureWarning) class OGSMONITOR(object): - def __init__(self, logfile, reffile, update_interval, window_length, maximum_lines, convergence_metric, convergence_component, show_quadrant): + def __init__(self, + logfile, + reffile, + update_interval, + window_length, + maximum_lines, + convergence_metric, + convergence_component, + convergence_type, + show_quadrant): self.log_file = logfile self.ref_file = reffile self.crit = convergence_metric @@ -35,6 +44,12 @@ def __init__(self, logfile, reffile, update_interval, window_length, maximum_lin self.maximum_lines = int(maximum_lines) self.running = True self.show_quadrant = int(show_quadrant) + self.convergence_type = convergence_type + self.criteria_types = {"PerComponentDeltaX": ogs6py.ogs_regexes.ogs_regexes.ComponentConvergenceCriterion, + "DeltaX": ogs6py.ogs_regexes.ogs_regexes.TimeStepConvergenceCriterion, + "Residual": ogs6py.ogs_regexes.ogs_regexes.ConvergenceCriterionResidual, + "Res": ogs6py.ogs_regexes.ogs_regexes.Residual, + "PerComponentResidual": ogs6py.ogs_regexes.ogs_regexes.ComponentConvergenceCriterionResidual} self.filters = ["time_step_vs_iterations","by_time_step", "convergence_newton_iteration","analysis_simulation"] """ self.filterdict = {"by_time_step":parse_fcts.analysis_time_step, @@ -56,6 +71,8 @@ def __init__(self, logfile, reffile, update_interval, window_length, maximum_lin self.criterion_label = {"actual": [], "ref": []} self.criterion_y = {"actual": [], "ref": []} self.crit_timesteppointer = {"actual": [], "ref": []} + self.time_step_solution_time = {"actual": [], "ref": []} + self.walltime = {"actual": 0.0, "ref": 0.0} if self.ref_file is not None: records_ref, _ = parser.parse_file(self.ref_file, maximum_lines=self.maximum_lines, force_parallel=False) @@ -96,8 +113,12 @@ def parse_records(self, records, dataset="actual"): elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.LinearSolverTime): self.assembly_solver_time[dataset][-1][1] = (self.assembly_solver_time[dataset][-1][1] + float(record.linear_solver_time)) / (self.iterations_per_ts[dataset][-1]) - elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.ComponentConvergenceCriterion): - if record.component == int(self.crit_comp): + elif isinstance(record, self.criteria_types[self.convergence_type]): + if self.convergence_type in ["PerComponentResidual", "PerComponentDeltaX"]: + comp = record.component + else: + comp = int(self.crit_comp) + if comp == int(self.crit_comp): if int(self.iterations_per_ts[dataset][-1]) == 1: self.criterion_x[dataset].append(len(self.criterion_x[dataset])+1) self.criterion_label[dataset].append(f"{self.iterations_per_ts[dataset][-1]} \n{self.timesteps[dataset][-1]}") @@ -107,8 +128,10 @@ def parse_records(self, records, dataset="actual"): self.criterion_x[dataset].append(len(self.criterion_x[dataset])+1) self.criterion_label[dataset].append(f"{self.iterations_per_ts[dataset][-1]} \n") self.criterion_y[dataset].append(float(getattr(record, self.crit))) - #self.criterion[dataset].append((self.timesteps[dataset][-1], self.iterations_per_ts[dataset], getattr(record, self.crit))) - #self.last_record[dataset] = len_records + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.TimeStepSolutionTime): + self.time_step_solution_time[dataset].append(float(record.time_step_solution_time)) + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.SimulationExecutionTime): + self.walltime[dataset] = float(record.execution_time) def plot_init(self): @@ -142,7 +165,7 @@ def map_quadrant_rqfilter(self, j): return False def plot(self): def animate(m): - print(m) + print(f"reloads: {m}") logfile_df = {} start = time.time() records, self.last_line["actual"] = parser.parse_file( @@ -151,7 +174,7 @@ def animate(m): force_parallel=False, start_line=self.last_line["actual"]) stop = time.time() - print(f"parse {stop-start} s") + print(f"time to parse: {stop-start} s") start = time.time() self.parse_records(records) """ @@ -184,10 +207,10 @@ def animate(m): return """ stop = time.time() - print(f"transform/filter df {stop-start} s") + print(f"time to extract records: {stop-start} s") start = time.time() window_length = self.window_length - if len(self.timesteps["actual"]) < self.window_length: + if ((len(self.timesteps["actual"]) < self.window_length) or (self.window_length == -1)): window_length = len(self.timesteps["actual"]) ts0 = int(np.array(self.timesteps["actual"])[-window_length])-1 ts1 = int(np.array(self.timesteps["actual"])[-1]) @@ -202,7 +225,7 @@ def animate(m): """ ax.plot(logfile_df[0]["time_step"], logfile_df[0]["iteration_number"], 'b', label="actual log") """ - ax.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.iterations_per_ts["actual"])[-window_length:], 'b', label="actual log") + ax.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.iterations_per_ts["actual"])[-window_length:], 'b', label="actual") if self.q1_yb is None: self.q1_yb = ax.twiny() self.q1_yb.get_legend_handles_labels() @@ -210,7 +233,7 @@ def animate(m): if not self.ref_file is None: #self.q1_yb.get_yaxis().set_visible(False) self.q1_yb.clear() - self.q1_yb.plot(np.array(self.timesteps["ref"])[ts0:ts1], np.array(self.iterations_per_ts["ref"])[ts0:ts1], 'k', label="reference data") + self.q1_yb.plot(np.array(self.timesteps["ref"])[ts0:ts1], np.array(self.iterations_per_ts["ref"])[ts0:ts1], 'k', label="reference") ax.set(xlabel="time step", ylabel="iterations") lines, labels = self.q1_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() @@ -226,7 +249,7 @@ def animate(m): for entry in ax.get_shared_x_axes().get_siblings(ax): entry.clear() """ - ax.plot(logfile_df[1]["time_step"], logfile_df[1]["step_size"], 'b-', label="time step (actual)") + ax.plot(logfile_df[1]["time_step"], logfile_df[1]["step_size"], 'b-', label="actual") """ ax.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.ts_simtime["actual"])[:,1][-window_length:], 'b-', label="time step (actual)") if self.q0_yb is None: @@ -241,7 +264,7 @@ def animate(m): """ self.q0_xb.plot(logfile_df[1]["time_step"], logfile_df[1]["step_start_time"], 'r-', label="start time (actual)") """ - self.q0_xb.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.ts_simtime["actual"])[:,0][-window_length:], 'r-', label="start time (actual)") + self.q0_xb.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.ts_simtime["actual"])[:,0][-window_length:], 'r-', label="ts start time (actual)") self.q0_xb.set_ylabel("time / s", color="r") self.q0_xb.yaxis.set_label_position("right") lines, labels = self.q0_yb.get_legend_handles_labels() @@ -270,13 +293,13 @@ def animate(m): if not self.ref_file is None: #self.q2_yb.get_yaxis().set_visible(True) self.q2_yb.clear() - self.q2_yb.plot(self.timesteps["ref"][ts0:ts1], np.array(self.assembly_solver_time["ref"])[:,0][ts0:ts1], "b--", label="assembly time (ref)") - self.q2_yb.plot(self.timesteps["ref"][ts0:ts1], np.array(self.assembly_solver_time["ref"])[:,1][ts0:ts1], "g--", label="linear solver time (ref)") + self.q2_yb.plot(self.timesteps["ref"][ts0:ts1], np.array(self.assembly_solver_time["ref"])[:,0][ts0:ts1], "b--", label="assembly time (reference)") + self.q2_yb.plot(self.timesteps["ref"][ts0:ts1], np.array(self.assembly_solver_time["ref"])[:,1][ts0:ts1], "g--", label="linear solver time (reference)") lines, labels = self.q2_yb.get_legend_handles_labels() lines2, labels2 = ax.get_legend_handles_labels() ax.legend(lines + lines2, labels + labels2, loc=0) ax.set(xlabel="time step", ylabel="time / s") - ax.set_title("time step sizes") + ax.set_title("assembly and linear solver times") if ((self.show_quadrant == -1) or (self.show_quadrant == 3)): """ @@ -326,8 +349,11 @@ def update_ticks_ref(x, pos): print(f"index trafo {stop2-start2} s") """ line_begin = self.crit_timesteppointer['actual'][ts0] - line_begin_ref = self.crit_timesteppointer['ref'][ts0] - line_stop_ref = line_begin_ref + line_begin_ref = 0 + line_stop_ref = 0 + if not self.ref_file is None: + line_begin_ref = self.crit_timesteppointer['ref'][ts0] + line_stop_ref = line_begin_ref if len(self.crit_timesteppointer['ref']) <= ts1: ts1 = len(self.crit_timesteppointer['ref']) - 1 line_stop_ref = len(self.criterion_x['ref']) @@ -351,7 +377,7 @@ def update_ticks_ref(x, pos): ax = self.axs ax.clear() #ax.plot(newindex, logfile_df[2][logfile_df[2]["component"]==self.crit_comp][self.crit], 'b', label="actual") - ax.plot(np.array(self.criterion_x["actual"])[line_begin:], np.array(self.criterion_y["actual"])[line_begin:],"b-", label="criterion (actual)") + ax.plot(np.array(self.criterion_x["actual"])[line_begin:], np.array(self.criterion_y["actual"])[line_begin:],"b-", label="actual") if self.q3_yb is None: self.q3_yb = ax.twiny() #self.q3_yb.get_yaxis().set_visible(False) @@ -359,7 +385,7 @@ def update_ticks_ref(x, pos): self.q3_yb.clear() #self.q3_yb.get_yaxis().set_visible(True) self.q3_yb.plot(np.array(self.criterion_x["ref"])[line_begin_ref:line_stop_ref], np.array(self.criterion_y["ref"])[line_begin_ref:line_stop_ref], 'k', label="reference") - self.q3_yb.set_xticks(np.array(self.criterion_x["actual"][line_begin_ref:line_stop_ref])) + self.q3_yb.set_xticks(np.array(self.criterion_x["ref"][line_begin_ref:line_stop_ref])) self.q3_yb.xaxis.set_major_formatter(mticker.FuncFormatter(update_ticks_ref)) #sec_ref = self.q3_yb.secondary_xaxis(location=1) #sec_ref.set_xticks(newindex2_ref, labels=time_data_ref) @@ -378,15 +404,15 @@ def update_ticks_ref(x, pos): #sec.tick_params('x', length=0) if self.running is True: try: - self.fig.suptitle(f"OGS running\n({logfile_df[1]['time_step_solution_time'].sum()} s)") + self.fig.suptitle(f"OGS running\n({np.sum(self.time_step_solution_time['actual'])} s)") except: self.fig.suptitle("OGS running") else: - self.fig.suptitle(f"OGS finished\n({logfile_df[3]['execution_time'].iloc[0]} s)") + self.fig.suptitle(f"OGS finished\n({self.walltime['actual']} s)") stop = time.time() - print(f"plot {stop-start} s") + print(f"plotting time: {stop-start} s") ani = animation.FuncAnimation(self.fig, animate, interval=self.interval) - #äplt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0) + #plt.tight_layout(pad=0.4, w_pad=0.5, h_pad=1.0) plt.subplots_adjust(plt.subplots_adjust(top=0.9, bottom=0.1, left=0.1, right=0.9, hspace=0.3, wspace=0.2)) plt.show() @@ -399,6 +425,7 @@ def update_ticks_ref(x, pos): maximum_lines = None convergence_metric = "dx" convergence_component = 0 + convergence_type = "PerComponentDeltaX" show_quadrant = -1 norun = False for i, arg in enumerate(sys.argv): @@ -412,6 +439,8 @@ def update_ticks_ref(x, pos): convergence_metric = sys.argv[i+1] elif arg == "-cc": convergence_component = sys.argv[i+1] + elif arg == "-ct": + convergence_type = sys.argv[i+1] elif arg == "-ui": update_interval = sys.argv[i+1] elif arg == "-q": @@ -422,12 +451,13 @@ def update_ticks_ref(x, pos): print("default usage: ogs-monitor.py logfile.log") print("args:") print(" -r [file] : provide reference data") - print(" -wl [window length] : provide length of displayed data") + print(" -wl [window length] : provide length of displayed data, default: 10 (time steps), -1: display all") print(" -ml [max lines] : provide maximum lines to read in") print(" -cm [convergence metric] : provide convergence metric, could be dx, r, dx/x") print(" -cc [convergence component] : provide convergence component, default: 0") + print(" -ct [convergence type] : provide a type could be DeltaX, PerComponentDeltaX (default), PerComponentResidual, Residual") print(" -ui [update interval] : provide an update interval in ms") print(" -q [X] : show only quadrant X, default: all four are shown") print(" -h : display this help") if norun is False: - ogs_monitor = OGSMONITOR(input_file, ref_file, update_interval, window_length, maximum_lines, convergence_metric, convergence_component, show_quadrant) + ogs_monitor = OGSMONITOR(input_file, ref_file, update_interval, window_length, maximum_lines, convergence_metric, convergence_component, convergence_type, show_quadrant) From 7fb71710bbb75a7c2d93d4aeb27fdf26fdecf36a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Fri, 29 Mar 2024 20:07:25 +0100 Subject: [PATCH 09/11] add residual criteria --- ogs6py/log_parser/log_parser.py | 2 +- ogs6py/ogs_regexes/ogs_regexes.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/ogs6py/log_parser/log_parser.py b/ogs6py/log_parser/log_parser.py index 170e309..ff8b3b2 100644 --- a/ogs6py/log_parser/log_parser.py +++ b/ogs6py/log_parser/log_parser.py @@ -71,7 +71,7 @@ def compile_re_fn(mpi_process_regex): break for key, value in patterns: - if r := try_match(line, number_of_lines_read, key, value): + if r := try_match(line.replace("-nan","nan"), number_of_lines_read, key, value): records.append(value(*r)) break diff --git a/ogs6py/ogs_regexes/ogs_regexes.py b/ogs6py/ogs_regexes/ogs_regexes.py index 21e5644..b3af211 100644 --- a/ogs6py/ogs_regexes/ogs_regexes.py +++ b/ogs6py/ogs_regexes/ogs_regexes.py @@ -125,6 +125,12 @@ class ComponentConvergenceCriterion(MPIProcess,Info): x: float dx_x: float +@dataclass +class ComponentConvergenceCriterionResidual(MPIProcess,Info): + component: int + dr: float + r0: float + dr_r0: float @dataclass class TimeStepConvergenceCriterion(MPIProcess,Info): @@ -132,6 +138,15 @@ class TimeStepConvergenceCriterion(MPIProcess,Info): x: float dx_x: float +@dataclass +class ConvergenceCriterionResidual(MPIProcess,Info): + dr: float + r0: float + dr_r0: float + +@dataclass +class Residual(MPIProcess,Info): + r: float @dataclass class CouplingIterationConvergence(MPIProcess,Info): @@ -186,6 +201,12 @@ def ogs_regexes(): ( "info: Convergence criterion, component (\d+): \|dx\|=([\d\.e+-]+), \|x\|=([\d\.e+-]+), \|dx\|/\|x\|=([\d\.e+-]+|nan|inf)$", ComponentConvergenceCriterion), + ( + "info: Convergence criterion, component (\d+): \|dr\|=([\d\.e+-]+), \|r0\|=([\d\.e+-]+), \|dr\|/\|r0\|=([\d\.e+-]+|nan|inf)$", + ComponentConvergenceCriterionResidual), + ("info: Convergence criterion: \|dr\|=([\d\.e+-]+) \|r0\|=([\d\.e+-]+) \|dr\|/\|r0\|=([\d\.e+-]+|nan|inf)", + ConvergenceCriterionResidual), + ("info: \[time\] Residual norm: ([\d\.e+-]+|nan|inf)", Residual), ("critical: (.*)", CriticalMessage), ("error: (.*)", ErrorMessage), ("warning: (.*)", WarningMessage) From 896ae42d171132b3b12c321e931ad1a5f8c3026c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Wed, 11 Dec 2024 17:36:19 +0100 Subject: [PATCH 10/11] add reference --- ogs-monitor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ogs-monitor.py b/ogs-monitor.py index 24d0637..0b1c94f 100644 --- a/ogs-monitor.py +++ b/ogs-monitor.py @@ -265,6 +265,8 @@ def animate(m): self.q0_xb.plot(logfile_df[1]["time_step"], logfile_df[1]["step_start_time"], 'r-', label="start time (actual)") """ self.q0_xb.plot(np.array(self.timesteps["actual"])[-window_length:], np.array(self.ts_simtime["actual"])[:,0][-window_length:], 'r-', label="ts start time (actual)") + if not self.ref_file is None: + self.q0_xb.plot(np.array(self.timesteps["ref"])[ts0:ts1], np.array(self.ts_simtime["ref"])[:,0][ts0:ts1], 'g-', label="ts start time (reference)") self.q0_xb.set_ylabel("time / s", color="r") self.q0_xb.yaxis.set_label_position("right") lines, labels = self.q0_yb.get_legend_handles_labels() From 3431fb155d9edb5bf731a0b63499d0c997c1813e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Buchwald?= Date: Wed, 11 Dec 2024 17:36:35 +0100 Subject: [PATCH 11/11] add static log analysis --- static_log_vis.py | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 static_log_vis.py diff --git a/static_log_vis.py b/static_log_vis.py new file mode 100644 index 0000000..a6082d8 --- /dev/null +++ b/static_log_vis.py @@ -0,0 +1,163 @@ +# pylint: disable=C0301 +import os +import sys + +import numpy as np +import matplotlib.pyplot as plt + +import ogs6py +import ogs6py.log_parser.log_parser as parser +import ogs6py.log_parser.common_ogs_analyses as parse_fcts + +class PLOTLOG(object): + def __init__(self, + logfilelist, + maximum_lines, + convergence_metric, + convergence_component, + convergence_type, + show_quadrant): + self.log_file = logfilelist + self.crit = convergence_metric + self.crit_comp = convergence_component + if maximum_lines is None: + self.maximum_lines = maximum_lines + else: + self.maximum_lines = int(maximum_lines) + self.running = True + self.show_quadrant = int(show_quadrant) + self.convergence_type = convergence_type + self.criteria_types = {"PerComponentDeltaX": ogs6py.ogs_regexes.ogs_regexes.ComponentConvergenceCriterion, + "DeltaX": ogs6py.ogs_regexes.ogs_regexes.TimeStepConvergenceCriterion, + "Residual": ogs6py.ogs_regexes.ogs_regexes.ConvergenceCriterionResidual, + "Res": ogs6py.ogs_regexes.ogs_regexes.Residual, + "PerComponentResidual": ogs6py.ogs_regexes.ogs_regexes.ComponentConvergenceCriterionResidual} + self.filters = ["time_step_vs_iterations","by_time_step", "convergence_newton_iteration","analysis_simulation"] + # plot data from records actual + self.last_record = {file:0 for file in self.log_file} + self.last_line = {file:0 for file in self.log_file} + self.timesteps = {file:[] for file in self.log_file} + self.ts_simtime = {file:[] for file in self.log_file} + self.iterations_per_ts = {file:[] for file in self.log_file} + self.assembly_solver_time = {file:[] for file in self.log_file} + self.criterion_x = {file:[] for file in self.log_file} + self.criterion_label = {file:[] for file in self.log_file} + self.criterion_y = {file:[] for file in self.log_file} + self.crit_timesteppointer = {file:[] for file in self.log_file} + self.time_step_solution_time = {file:[] for file in self.log_file} + self.walltime = {file:0 for file in self.log_file} + self.records = {} + for file in logfilelist: + records_ref, _ = parser.parse_file(file, maximum_lines=self.maximum_lines, force_parallel=False) + self.records[file] = records_ref + self.parse_records(self.records[file], dataset=file) + self.plot() + + def plot(self): + if (self.show_quadrant == 1): + for file in self.log_file: + plt.plot(np.array(self.timesteps[file]), np.array(self.iterations_per_ts[file]), label=file) + plt.xlabel("time step") + plt.ylabel("iterations") + plt.title("iterations per timestep") + plt.legend() + plt.show() + elif self.show_quadrant == 0: + for file in self.log_file: + plt.plot(np.array(self.timesteps[file]), np.array(self.ts_simtime[file])[:,1], label=file) + plt.xlabel("time step") + plt.ylabel("step size") + plt.title("step size per timestep") + plt.legend() + plt.show() + elif self.show_quadrant == 2: + for file in self.log_file: + plt.plot(np.array(self.timesteps[file]), np.array(self.ts_simtime[file])[:,0], label=file) + plt.xlabel("time step") + plt.ylabel("time step start time") + plt.title("start time vs timestep") + plt.legend() + plt.show() + + + + def parse_records(self, records, dataset="actual"): + #len_records = len(records) + for record in records: #[self.last_record[dataset]:len_records]: + if isinstance(record, ogs6py.ogs_regexes.ogs_regexes.TimeStepStartTime): + self.timesteps[dataset].append(int(record.time_step)) + self.ts_simtime[dataset].append((float(record.step_start_time), float(record.step_size))) + self.iterations_per_ts[dataset].append(1) + self.assembly_solver_time[dataset].append([0, 0]) + + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.IterationTime): + self.iterations_per_ts[dataset][-1] = int(record.iteration_number)+1 + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.AssemblyTime): + self.assembly_solver_time[dataset][-1][0] = (self.assembly_solver_time[dataset][-1][0] + + float(record.assembly_time)) / (self.iterations_per_ts[dataset][-1]) + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.LinearSolverTime): + self.assembly_solver_time[dataset][-1][1] = (self.assembly_solver_time[dataset][-1][1] + + float(record.linear_solver_time)) / (self.iterations_per_ts[dataset][-1]) + elif isinstance(record, self.criteria_types[self.convergence_type]): + if self.convergence_type in ["PerComponentResidual", "PerComponentDeltaX"]: + comp = record.component + else: + comp = int(self.crit_comp) + if comp == int(self.crit_comp): + if int(self.iterations_per_ts[dataset][-1]) == 1: + self.criterion_x[dataset].append(len(self.criterion_x[dataset])+1) + self.criterion_label[dataset].append(f"{self.iterations_per_ts[dataset][-1]} \n{self.timesteps[dataset][-1]}") + self.criterion_y[dataset].append(float(getattr(record, self.crit))) + self.crit_timesteppointer[dataset].append(len(self.criterion_x[dataset])-1) + else: + self.criterion_x[dataset].append(len(self.criterion_x[dataset])+1) + self.criterion_label[dataset].append(f"{self.iterations_per_ts[dataset][-1]} \n") + self.criterion_y[dataset].append(float(getattr(record, self.crit))) + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.TimeStepSolutionTime): + self.time_step_solution_time[dataset].append(float(record.time_step_solution_time)) + elif isinstance(record, ogs6py.ogs_regexes.ogs_regexes.SimulationExecutionTime): + self.walltime[dataset] = float(record.execution_time) + +if __name__ == '__main__': + input_files = [] + for entry in sys.argv: + if ".log" in entry: + input_files.append(entry) + window_length = 10 + maximum_lines = None + convergence_metric = "dx" + convergence_component = 0 + convergence_type = "PerComponentDeltaX" + show_quadrant = 0 + norun = False + for i, arg in enumerate(sys.argv): + if arg == "-r": + ref_file = sys.argv[i+1] + elif arg == "-wl": + window_length = sys.argv[i+1] + elif arg == "-ml": + maximum_lines = sys.argv[i+1] + elif arg == "-cm": + convergence_metric = sys.argv[i+1] + elif arg == "-cc": + convergence_component = sys.argv[i+1] + elif arg == "-ct": + convergence_type = sys.argv[i+1] + elif arg == "-ui": + update_interval = sys.argv[i+1] + elif arg == "-q": + show_quadrant = sys.argv[i+1] + elif arg == "-h": + norun = True + print("OGS monitor help\n") + print("default usage: ogs-monitor.py [logfile.log ...] args") + print("args:") + print(" -ml [max lines] : provide maximum lines to read in") + print(" -cm [convergence metric] : provide convergence metric, could be dx, r, dx/x") + print(" -cc [convergence component] : provide convergence component, default: 0") + print(" -ct [convergence type] : provide a type could be DeltaX, PerComponentDeltaX (default), PerComponentResidual, Residual") + print(" -q [X] : show only quadrant X, default: all four are shown") + print(" -h : display this help") + if norun is False: + ogs_monitor = PLOTLOG(input_files, maximum_lines, convergence_metric, convergence_component, convergence_type, show_quadrant) +