From b7bac5f8ab51a6626538778b24152035992b0237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 29 May 2026 14:08:11 +0200 Subject: [PATCH 1/8] ENH: use inifix to properly parse inifiles --- idefix2python/context.py | 7 ++++--- idefix2python/tools.py | 33 --------------------------------- pyproject.toml | 3 ++- 3 files changed, 6 insertions(+), 37 deletions(-) diff --git a/idefix2python/context.py b/idefix2python/context.py index ac39d82..5687ca3 100644 --- a/idefix2python/context.py +++ b/idefix2python/context.py @@ -4,6 +4,7 @@ import argparse from . import tools from .vtk_io import readVTK +import inifix import numpy as np CARTESIAN_DIMENSION_NAMES = { @@ -207,12 +208,12 @@ def __init__(self, runName, projectPath=".", **kwargs): ) self.format_inputs_text = "" if self.debug: - if self.iniPath.is_file(): - self.format_inputs_text = tools.formatInputs(self.iniPath) - else: + if not self.iniPath.exists(): raise FileNotFoundError( f"debug requested but {self.iniPath} doesn't exist" ) + with self.iniPath.open("rb") as fh: + self.format_inputs_text = inifix.load(fh, sections="require") self.partFolder = kwargs.get("partFolder", None) diff --git a/idefix2python/tools.py b/idefix2python/tools.py index 9656961..a0b29bb 100644 --- a/idefix2python/tools.py +++ b/idefix2python/tools.py @@ -37,39 +37,6 @@ def fmt(x, pos): return r"${} \times 10^{{{}}}$".format(a, b) -def formatInputs(iniPath): - """ - Formats the .ini file into a decent dict - """ - with open(iniPath) as ini: - content = ini.read() - - sections = {} - current_section = None - - MAX_VAL_LEN = 22 - - for line in content.splitlines(): - line = line.strip() - if not line or line.startswith("#"): - continue - - if line.startswith("[") and line.endswith("]"): - current_section = line[1:-1] - sections[current_section] = [] - elif current_section: - parts = line.split() - if len(parts) >= 2: - key = parts[0] - val = " ".join(parts[1:]) - val = "".join(val.split("#")[0]) - if len(val) > MAX_VAL_LEN: - val = "..." + val[MAX_VAL_LEN - 3 :] - sections[current_section].append(f"{key:<14} {val}") - - return {k: "\n".join(v) for k, v in sections.items() if v} - - def annotateInputs(fig, ini_dict, padding_top=0.0): """ Writes text on the `fig` with distinctive sections. diff --git a/pyproject.toml b/pyproject.toml index b56ad03..acffe8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,8 @@ version = "0.1.0" dependencies = [ "numpy", "matplotlib", + "inifix>=5.1.0", ] [tool.setuptools.packages.find] -where = ["."] \ No newline at end of file +where = ["."] From 94b1149ab1090c0f67880d3a28b05ae2e6f07583 Mon Sep 17 00:00:00 2001 From: David Fang Date: Mon, 1 Jun 2026 09:59:47 +0100 Subject: [PATCH 2/8] reintroduce debug --- idefix2python/axes.py | 15 ++++++--- idefix2python/context.py | 5 +-- idefix2python/renderer.py | 1 + idefix2python/tools.py | 68 ++++++++++++++++++++++----------------- 4 files changed, 52 insertions(+), 37 deletions(-) diff --git a/idefix2python/axes.py b/idefix2python/axes.py index d43e24a..d8ea5ab 100644 --- a/idefix2python/axes.py +++ b/idefix2python/axes.py @@ -53,6 +53,7 @@ def __init__(self, quantities, suptitle=None): self.columns = qtyInfo.plot_coords[1] + 1 self.axes = np.empty((self.rows, self.columns), dtype="object") + self.initxt = None def init(self): """ @@ -87,18 +88,22 @@ def generate_figure(self, custom_suptitle=None): fig.suptitle(suptitle) # TODO move to renderer? Later PR - # if len(self.context.format_inputs_text) > 0: - # padding_top = 0.1 - # tools.annotateInputs( - # fig, self.context.format_inputs_text, padding_top=padding_top - # ) + self.used_coords = [list(qtyInfo.plot_coords) for qtyInfo in self.quantities] for i in range(self.rows): for j in range(self.columns): self.axes[i, j].generate_ax(self.fig, axs[i, j]) + def set_initxt(self, initxt): + self.initxt = initxt + def save_and_close(self, path): + print(self.initxt) + if self.initxt: + padding_top = 0.1 + tools.annotateInputs(self.fig, self.initxt, padding_top=padding_top) + for ax in self.axes.flat: ax.last_pimp() self.fig.savefig(path, dpi=DPI, bbox_inches="tight") diff --git a/idefix2python/context.py b/idefix2python/context.py index 5687ca3..7853838 100644 --- a/idefix2python/context.py +++ b/idefix2python/context.py @@ -206,14 +206,15 @@ def __init__(self, runName, projectPath=".", **kwargs): self.iniPath = Path( kwargs.get("iniPath", self.projectPath / "inputs" / f"{runName}.ini") ) - self.format_inputs_text = "" + self.inidata = None if self.debug: if not self.iniPath.exists(): raise FileNotFoundError( f"debug requested but {self.iniPath} doesn't exist" ) with self.iniPath.open("rb") as fh: - self.format_inputs_text = inifix.load(fh, sections="require") + self.inidata = inifix.load(fh, sections="require") + self.initxt = inifix.format_string(self.iniPath.read_text(encoding="utf-8")) self.partFolder = kwargs.get("partFolder", None) diff --git a/idefix2python/renderer.py b/idefix2python/renderer.py index 63ad2b2..187b223 100644 --- a/idefix2python/renderer.py +++ b/idefix2python/renderer.py @@ -131,6 +131,7 @@ def _pre_render(self): self.figsMovie.append(fig) else: self.figsTimeline.append(fig) + fig.set_initxt(self.context.initxt) for qtyInfo in fig.quantities: if isinstance(qtyInfo, MapMovie2D): diff --git a/idefix2python/tools.py b/idefix2python/tools.py index a0b29bb..be15778 100644 --- a/idefix2python/tools.py +++ b/idefix2python/tools.py @@ -1,6 +1,7 @@ import numpy as np import json from itertools import zip_longest +import inifix def LOG(*args): @@ -37,56 +38,63 @@ def fmt(x, pos): return r"${} \times 10^{{{}}}$".format(a, b) -def annotateInputs(fig, ini_dict, padding_top=0.0): +def annotateInputs(fig, initxt, padding_top=0.0): """ Writes text on the `fig` with distinctive sections. """ - if ini_dict == {}: - return - COL_WIDTH = 42 - COLS_NB = 3 + # COL_WIDTH = 42 + # COLS_NB = 3 - all_panels = [] - keys = list(ini_dict.keys()) - table = [keys[i : i + COLS_NB] for i in range(0, len(keys), COLS_NB)] + # txt = "" + # for section_name, section in inidata.values(): + # txt += section_name + "\n" + # for param_name, params in section.values(): + # if isinstance(params, list): - for section_group in table: - formatted_columns = [ - [f"[{name}]", *ini_dict[name].split("\n")] - for name in section_group - if name in ini_dict - ] + # all_panels = [] + # keys = list(ini_dict.keys()) + # # table_section_names = + # table_param_names = [keys[i : i + COLS_NB] for i in range(0, len(keys), COLS_NB)] - if not formatted_columns: - continue + # for section_group in table: + # formatted_columns = [ + # [f"[{name}]", *ini_dict[name].split("\n")] + # for name in section_group + # if name in ini_dict + # ] - printable_rows = [ - "".join(f"{section_line:<{COL_WIDTH}}" for section_line in horizontal_slice) - for horizontal_slice in zip_longest(*formatted_columns, fillvalue="") - ] + # if not formatted_columns: + # continue - text_panel = "\n".join(printable_rows) - all_panels.append(text_panel) + # printable_rows = [ + # "".join(f"{section_line:<{COL_WIDTH}}" for section_line in horizontal_slice) + # for horizontal_slice in zip_longest(*formatted_columns, fillvalue="") + # ] - final_display_string = "\n\n\n".join(all_panels) + # text_panel = "\n".join(printable_rows) + # all_panels.append(text_panel) - total_lines = final_display_string.count("\n") + 1 - header_space = total_lines * 0.014 - margin_top = header_space + padding_top + # final_display_string = "\n\n\n".join(all_panels) + # total_lines = final_display_string.count("\n") + 1 + # header_space = total_lines * 0.014 + # margin_top = header_space + padding_top + + # final_display_string = inifix.format_string(initxt) + final_display_string = initxt fig.text( - 0.55, - 1 - margin_top, + 0.1, + 1, final_display_string, family="monospace", fontsize=7, va="bottom", - ha="center", + ha="left", usetex=False, ) - fig.subplots_adjust(top=1.0 - margin_top - 0.05) + # fig.subplots_adjust(top=1.0 - margin_top - 0.05) def divide_discardingNullDenominator(a, b): From 7f921fe1b354ca3ba0ba2cdf43976d3a7bf73fd7 Mon Sep 17 00:00:00 2001 From: David Fang Date: Mon, 1 Jun 2026 15:20:23 +0100 Subject: [PATCH 3/8] clean up and rename `debug` to `show_ini` --- idefix2python/axes.py | 13 +++++++-- idefix2python/context.py | 8 +++--- idefix2python/tools.py | 59 ---------------------------------------- 3 files changed, 14 insertions(+), 66 deletions(-) diff --git a/idefix2python/axes.py b/idefix2python/axes.py index d8ea5ab..f221893 100644 --- a/idefix2python/axes.py +++ b/idefix2python/axes.py @@ -99,10 +99,17 @@ def set_initxt(self, initxt): self.initxt = initxt def save_and_close(self, path): - print(self.initxt) if self.initxt: - padding_top = 0.1 - tools.annotateInputs(self.fig, self.initxt, padding_top=padding_top) + self.fig.text( + 0.1, + 1, + self.initxt, + family="monospace", + fontsize=7, + va="bottom", + ha="left", + usetex=False, + ) for ax in self.axes.flat: ax.last_pimp() diff --git a/idefix2python/context.py b/idefix2python/context.py index 7853838..82663c1 100644 --- a/idefix2python/context.py +++ b/idefix2python/context.py @@ -177,7 +177,7 @@ class RunContext: * partFolder (str): Folder path containing the particles data. * frameFolder (str): Folder name where the rendered frames will be stored. * active_directions (list): List of active coordinate directions. - * debug (bool): debug mode will show the .ini file. + * show_ini (bool): show_ini mode will show the .ini file. Defaults to False. * iniPath (Path): Custom path to the .ini input file. Defaults to `projectPath/inputs/{runName}.ini`. @@ -192,7 +192,7 @@ def __init__(self, runName, projectPath=".", **kwargs): self.projectPath = Path(projectPath) self.projectPath.resolve(strict=True) - self.debug = kwargs.get("debug", False) + self.show_ini = kwargs.get("show_ini", False) self.userArgs = kwargs.get("args", _get_args()) @@ -207,10 +207,10 @@ def __init__(self, runName, projectPath=".", **kwargs): kwargs.get("iniPath", self.projectPath / "inputs" / f"{runName}.ini") ) self.inidata = None - if self.debug: + if self.show_ini: if not self.iniPath.exists(): raise FileNotFoundError( - f"debug requested but {self.iniPath} doesn't exist" + f"show_ini requested but {self.iniPath} doesn't exist" ) with self.iniPath.open("rb") as fh: self.inidata = inifix.load(fh, sections="require") diff --git a/idefix2python/tools.py b/idefix2python/tools.py index be15778..779f171 100644 --- a/idefix2python/tools.py +++ b/idefix2python/tools.py @@ -38,65 +38,6 @@ def fmt(x, pos): return r"${} \times 10^{{{}}}$".format(a, b) -def annotateInputs(fig, initxt, padding_top=0.0): - """ - Writes text on the `fig` with distinctive sections. - """ - - # COL_WIDTH = 42 - # COLS_NB = 3 - - # txt = "" - # for section_name, section in inidata.values(): - # txt += section_name + "\n" - # for param_name, params in section.values(): - # if isinstance(params, list): - - # all_panels = [] - # keys = list(ini_dict.keys()) - # # table_section_names = - # table_param_names = [keys[i : i + COLS_NB] for i in range(0, len(keys), COLS_NB)] - - # for section_group in table: - # formatted_columns = [ - # [f"[{name}]", *ini_dict[name].split("\n")] - # for name in section_group - # if name in ini_dict - # ] - - # if not formatted_columns: - # continue - - # printable_rows = [ - # "".join(f"{section_line:<{COL_WIDTH}}" for section_line in horizontal_slice) - # for horizontal_slice in zip_longest(*formatted_columns, fillvalue="") - # ] - - # text_panel = "\n".join(printable_rows) - # all_panels.append(text_panel) - - # final_display_string = "\n\n\n".join(all_panels) - - # total_lines = final_display_string.count("\n") + 1 - # header_space = total_lines * 0.014 - # margin_top = header_space + padding_top - - # final_display_string = inifix.format_string(initxt) - final_display_string = initxt - fig.text( - 0.1, - 1, - final_display_string, - family="monospace", - fontsize=7, - va="bottom", - ha="left", - usetex=False, - ) - - # fig.subplots_adjust(top=1.0 - margin_top - 0.05) - - def divide_discardingNullDenominator(a, b): """ Returns a/b but with None wherever b=0 From b0445d8db1b18c493b00187edf7481fce535768f Mon Sep 17 00:00:00 2001 From: David Fang Date: Mon, 1 Jun 2026 16:42:56 +0100 Subject: [PATCH 4/8] fix --- idefix2python/context.py | 1 + 1 file changed, 1 insertion(+) diff --git a/idefix2python/context.py b/idefix2python/context.py index 82663c1..4f76248 100644 --- a/idefix2python/context.py +++ b/idefix2python/context.py @@ -207,6 +207,7 @@ def __init__(self, runName, projectPath=".", **kwargs): kwargs.get("iniPath", self.projectPath / "inputs" / f"{runName}.ini") ) self.inidata = None + self.initxt = None if self.show_ini: if not self.iniPath.exists(): raise FileNotFoundError( From 3b45f671a130148f77e698d19c54883e766d724d Mon Sep 17 00:00:00 2001 From: David Fang Date: Mon, 1 Jun 2026 16:43:02 +0100 Subject: [PATCH 5/8] to revert later --- examples/run_particles_over.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/run_particles_over.py b/examples/run_particles_over.py index 1887478..a27023a 100644 --- a/examples/run_particles_over.py +++ b/examples/run_particles_over.py @@ -37,6 +37,7 @@ def analytical_trajectory(t): runContext = RunContext( task, projectPath, + show_ini=True ) if __name__ == "__main__": From b01fa861e6ecec85ff72acdfcc09693e5b9fd00a Mon Sep 17 00:00:00 2001 From: David Fang Date: Fri, 5 Jun 2026 18:32:24 +0100 Subject: [PATCH 6/8] reverting small test --- examples/run_particles_over.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/run_particles_over.py b/examples/run_particles_over.py index a27023a..1887478 100644 --- a/examples/run_particles_over.py +++ b/examples/run_particles_over.py @@ -37,7 +37,6 @@ def analytical_trajectory(t): runContext = RunContext( task, projectPath, - show_ini=True ) if __name__ == "__main__": From 89a5c4def2b5cc7ed87a53e1cfe25a3dec2fcfa2 Mon Sep 17 00:00:00 2001 From: David Fang Date: Fri, 5 Jun 2026 18:35:15 +0100 Subject: [PATCH 7/8] doc --- idefix2python/context.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/idefix2python/context.py b/idefix2python/context.py index 8e49f5a..30353b0 100644 --- a/idefix2python/context.py +++ b/idefix2python/context.py @@ -185,9 +185,9 @@ class RunContext: * partFolder (str): Folder path containing the particles data. * frameFolder (str): Folder name where the rendered frames will be stored. * active_directions (list): List of active coordinate directions. - * show_ini (bool): show_ini mode will show the .ini file. + * show_ini (bool): show_ini mode will show the .ini file content at the top of the frame. Defaults to False. - * iniPath (Path): Custom path to the .ini input file. Defaults to + * iniPath (Path): Custom path to the .ini input file. The .ini content is accessible as a dict through context.inidata. Defaults to `projectPath/inputs/{runName}.ini`. Note: From d87f4710792d2b71c06e92255d04333ca5e54283 Mon Sep 17 00:00:00 2001 From: David Fang Date: Fri, 5 Jun 2026 18:40:02 +0100 Subject: [PATCH 8/8] cleanup --- idefix2python/axes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/idefix2python/axes.py b/idefix2python/axes.py index a0565c1..cd68392 100644 --- a/idefix2python/axes.py +++ b/idefix2python/axes.py @@ -77,8 +77,6 @@ def generate_figure(self, custom_suptitle=None): if suptitle is not None: fig.suptitle(suptitle) - # TODO move to renderer? Later PR - self.used_coords = [list(qtyInfo.plot_coords) for qtyInfo in self.quantities] for i in range(self.rows):