From bbf94e4457c19000b8061fd1c9f630cec74fb045 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Mon, 11 May 2026 22:53:43 +0200 Subject: [PATCH 1/8] update version --- corrlib/version.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/corrlib/version.py b/corrlib/version.py index 68d1c45..60637af 100644 --- a/corrlib/version.py +++ b/corrlib/version.py @@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE commit_id: COMMIT_ID __commit_id__: COMMIT_ID -__version__ = version = '0.2.4.dev14+g602324f84.d20251202' -__version_tuple__ = version_tuple = (0, 2, 4, 'dev14', 'g602324f84.d20251202') +__version__ = version = '0.2.4.dev71+g5e712b64c.d20260213' +__version_tuple__ = version_tuple = (0, 2, 4, 'dev71', 'g5e712b64c.d20260213') -__commit_id__ = commit_id = 'g602324f84' +__commit_id__ = commit_id = 'g5e712b64c' From 0798ab9f10f41af1a53e73c907b0732b19272605 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 19 May 2026 17:16:40 +0200 Subject: [PATCH 2/8] add LICENSE --- LICENSE | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1889fd3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2026 Justus Kuhlmann + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From c6ad3f900346a0817621bfe4dca4fbd80f1e35bd Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 26 May 2026 11:16:18 +0200 Subject: [PATCH 3/8] add automatic saving of plots for t0 and t1 --- corrlib/initialization.py | 2 ++ corrlib/input/openQCD.py | 17 ++++++++++++++--- corrlib/meas_io.py | 4 +++- corrlib/toml.py | 4 ++-- corrlib/tools.py | 27 +++++++++++++++++++++++++++ pyproject.toml | 1 + 6 files changed, 49 insertions(+), 6 deletions(-) diff --git a/corrlib/initialization.py b/corrlib/initialization.py index bdf9cee..99f90d1 100644 --- a/corrlib/initialization.py +++ b/corrlib/initialization.py @@ -71,6 +71,7 @@ def _create_config(path: Path, tracker: str, cached: bool) -> ConfigParser: 'db': 'backlogger.db', 'projects_path': 'projects', 'archive_path': 'archive', + 'plot_path': 'plots', 'toml_imports_path': 'toml_imports', 'import_scripts_path': 'import_scripts', } @@ -113,6 +114,7 @@ def create(path: Path, tracker: str = 'datalad', cached: bool = True) -> None: os.chmod(path / config['paths']['db'], 0o666) os.makedirs(path / config['paths']['projects_path']) os.makedirs(path / config['paths']['archive_path']) + os.makedirs(path / config['paths']['plot_path']) os.makedirs(path / config['paths']['toml_imports_path']) os.makedirs(path / config['paths']['import_scripts_path'] / 'template.py') with open(path / ".gitignore", "w") as fp: diff --git a/corrlib/input/openQCD.py b/corrlib/input/openQCD.py index c8eef72..9dfe85f 100644 --- a/corrlib/input/openQCD.py +++ b/corrlib/input/openQCD.py @@ -4,8 +4,10 @@ import os import fnmatch from typing import Any, Optional from pathlib import Path +import matplotlib.pyplot as plt from ..pars.openQCD import ms1 from ..pars.openQCD import qcd2 +from ..tools import get_plot_dir @@ -164,7 +166,7 @@ def read_rwms(path: Path, project: str, dir_in_project: str, param: dict[str, An return rw_dict -def extract_t0(path: Path, project: str, dir_in_project: str, param: dict[str, Any], prefix: str, dtr_read: int, xmin: int, spatial_extent: int, fit_range: int = 5, postfix: str="", names: Optional[list[str]]=None, files: Optional[list[str]]=None, +def extract_t0(path: Path, project: str, dir_in_project: str, ensemble: str, param: dict[str, Any], prefix: str, dtr_read: int, xmin: int, spatial_extent: int, fit_range: int = 5, postfix: str="", names: Optional[list[str]]=None, files: Optional[list[str]]=None, r_start: list[int]=[], r_stop: list[int]=[], r_step:int=1) -> dict[str, Any]: """ Extract t0 measurements from the project. @@ -224,7 +226,7 @@ def extract_t0(path: Path, project: str, dir_in_project: str, param: dict[str, A if not r_stop == []: kwargs['r_stop'] = r_stop kwargs['r_step'] = r_step - + kwargs['plot_fit'] = True t0 = input.extract_t0(directory, prefix, dtr_read, @@ -234,6 +236,10 @@ def extract_t0(path: Path, project: str, dir_in_project: str, param: dict[str, A c=0.3, **kwargs ) + plot_dir = path / get_plot_dir(path) / ensemble / project + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) + plt.savefig(plot_dir / "t0.pdf") par_list= [] for k in ["integrator", "eps", "ntot", "dnms"]: par_list.append(str(param[k])) @@ -244,7 +250,7 @@ def extract_t0(path: Path, project: str, dir_in_project: str, param: dict[str, A return t0_dict -def extract_t1(path: Path, project: str, dir_in_project: str, param: dict[str, Any], prefix: str, dtr_read: int, xmin: int, spatial_extent: int, fit_range: int = 5, postfix: str = "", names: Optional[list[str]]=None, files: Optional[list[str]]=None, +def extract_t1(path: Path, project: str, dir_in_project: str, ensemble: str, param: dict[str, Any], prefix: str, dtr_read: int, xmin: int, spatial_extent: int, fit_range: int = 5, postfix: str = "", names: Optional[list[str]]=None, files: Optional[list[str]]=None, r_start: list[int]=[], r_stop: list[int]=[], r_step:int=1) -> dict[str, Any]: """ Extract t1 measurements from the project. @@ -302,6 +308,7 @@ def extract_t1(path: Path, project: str, dir_in_project: str, param: dict[str, A if not r_stop == []: kwargs['r_stop'] = r_stop kwargs['r_step'] = r_step + kwargs['plot_fit'] = True t0 = input.extract_t0(directory, prefix, dtr_read, @@ -311,6 +318,10 @@ def extract_t1(path: Path, project: str, dir_in_project: str, param: dict[str, A c=2./3, **kwargs ) + plot_dir = path / get_plot_dir(path) / ensemble / project + if not os.path.exists(plot_dir): + os.makedirs(plot_dir) + plt.savefig(plot_dir / "t1.pdf") par_list= [] for k in ["integrator", "eps", "ntot", "dnms"]: par_list.append(str(param[k])) diff --git a/corrlib/meas_io.py b/corrlib/meas_io.py index 6b6e5f1..52e307e 100644 --- a/corrlib/meas_io.py +++ b/corrlib/meas_io.py @@ -6,7 +6,7 @@ import json from typing import Union from pyerrors import Obs, Corr, dump_object, load_object from hashlib import sha256 -from .tools import get_db_file, cache_enabled +from .tools import get_db_file, cache_enabled, get_plot_dir from .tracker import get, save, unlock import shutil from typing import Any @@ -104,6 +104,8 @@ def write_measurement(path: Path, ensemble: str, measurement: dict[str, dict[str subkeys.append(subkey) pars[subkey] = json.dumps(parameters["rw_fcts"][i]) elif ms_type in ['t0', 't1']: + plot_file = path / get_plot_dir(path) / ensemble / uuid / (ms_type + ".pdf") + files_to_save.append(plot_file) if parameter_file is not None: parameters = openQCD.load_ms3_infile(path, uuid, parameter_file) else: diff --git a/corrlib/toml.py b/corrlib/toml.py index 1f4e300..76533f4 100644 --- a/corrlib/toml.py +++ b/corrlib/toml.py @@ -229,14 +229,14 @@ def import_toml(path: Path, file: str, copy_file: bool=True) -> None: for rwp in ["integrator", "eps", "ntot", "dnms"]: param[rwp] = "Unknown" param['type'] = 't0' - measurement = openQCD.extract_t0(path, uuid, md['path'], param, str(md["prefix"]), int(md["dtr_read"]), int(md["xmin"]), int(md["spatial_extent"]), + measurement = openQCD.extract_t0(path, uuid, md['path'], ensemble, param, str(md["prefix"]), int(md["dtr_read"]), int(md["xmin"]), int(md["spatial_extent"]), fit_range=int(md.get('fit_range', 5)), postfix=str(md.get('postfix', '')), names=md.get('names', []), files=md.get('files', []), r_start=md.get('r_start', []), r_stop=md.get('r_stop', []), r_step=md.get('r_step', 1)) elif md['measurement'] == 't1': if 'param_file' in md: param = openQCD.load_ms3_infile(path, uuid, md['param_file']) param['type'] = 't1' - measurement = openQCD.extract_t1(path, uuid, md['path'], param, str(md["prefix"]), int(md["dtr_read"]), int(md["xmin"]), int(md["spatial_extent"]), + measurement = openQCD.extract_t1(path, uuid, md['path'], ensemble, param, str(md["prefix"]), int(md["dtr_read"]), int(md["xmin"]), int(md["spatial_extent"]), fit_range=int(md.get('fit_range', 5)), postfix=str(md.get('postfix', '')), names=md.get('names', []), files=md.get('files', []), r_start=md.get('r_start', []), r_stop=md.get('r_stop', []), r_step=md.get('r_step', 1)) write_measurement(path, ensemble, measurement, uuid, project['code'], (md['param_file'] if 'param_file' in md else None)) diff --git a/corrlib/tools.py b/corrlib/tools.py index 9ce194b..8dec61a 100644 --- a/corrlib/tools.py +++ b/corrlib/tools.py @@ -129,6 +129,33 @@ def get_db_file(path: Path) -> Path: return db_file +def get_plot_dir(path: Path) -> Path: + """ + Get the plots directory associated with the library at the given path. + + Parameters + ---------- + path: str + The path of the library. + + Returns + ------- + db_file: str + The file holding the database. + """ + path = Path(path) + if not os.path.exists(path): + raise FileNotFoundError(f"Corrlib path {path} does not exist.") + config_path = path / CONFIG_FILENAME + config = ConfigParser() + if os.path.exists(config_path): + config.read(config_path) + else: + raise FileNotFoundError("Configuration file not found.") + plot_dir = Path(config.get('paths', 'plot_dir', fallback='plots')) + return plot_dir + + def cache_enabled(path: Path) -> bool: """ Check, whether the library is cached. diff --git a/pyproject.toml b/pyproject.toml index faf7e6c..68abbe8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ 'pyerrors>=2.11.1', "datalad>=1.1.0", 'typer>=0.12.5', + "matplotlib>=3.10.7", ] description = "Python correlation library" authors = [ From 354cd96407f4d4fa8f2d2e4d2d4cf07b2fde0f9e Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 26 May 2026 11:40:18 +0200 Subject: [PATCH 4/8] add matplitlib --- uv.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uv.lock b/uv.lock index f76ee81..6e246e6 100644 --- a/uv.lock +++ b/uv.lock @@ -409,6 +409,7 @@ source = { editable = "." } dependencies = [ { name = "datalad" }, { name = "gitpython" }, + { name = "matplotlib" }, { name = "pyerrors" }, { name = "typer" }, ] @@ -427,6 +428,7 @@ dev = [ requires-dist = [ { name = "datalad", specifier = ">=1.1.0" }, { name = "gitpython", specifier = ">=3.1.45" }, + { name = "matplotlib", specifier = ">=3.10.7" }, { name = "pyerrors", specifier = ">=2.11.1" }, { name = "typer", specifier = ">=0.12.5" }, ] From f14fe6f2e3e41d3d00b6a15c94cb3b5015c8557e Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Wed, 1 Jul 2026 10:16:56 +0200 Subject: [PATCH 5/8] add build --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f97ff98..cff0601 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ test.ipynb .vscode .venv .pytest_cache -.coverage \ No newline at end of file +.coverage +build From 25124025fbf27fcaa322ee34fffde87f9286b50f Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Wed, 1 Jul 2026 10:18:18 +0200 Subject: [PATCH 6/8] Fix: rename clone target to path --- corrlib/tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corrlib/tracker.py b/corrlib/tracker.py index 6f4ae3d..1581fba 100644 --- a/corrlib/tracker.py +++ b/corrlib/tracker.py @@ -144,7 +144,7 @@ def clone(path: Path, source: str, target: str) -> None: path = Path(path) tracker = get_tracker(path) if tracker == 'datalad': - dl.clone(target=target, source=source, dataset=path) + dl.clone(path=target, source=source, dataset=path) elif tracker == 'None': os.makedirs(path, exist_ok=True) # Implement a simple clone by copying files From 4b1c2130906a0455cdc20f40f1dff2eb9b485e1c Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Wed, 1 Jul 2026 14:15:37 +0200 Subject: [PATCH 7/8] Fix: variable typo --- corrlib/input/sfcf.py | 8 ++++---- corrlib/toml.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/corrlib/input/sfcf.py b/corrlib/input/sfcf.py index acd8261..1af366f 100644 --- a/corrlib/input/sfcf.py +++ b/corrlib/input/sfcf.py @@ -258,7 +258,7 @@ def get_specs(key: str, parameters: dict[str, Any], sep: str = '/') -> str: return s -def read_data(path: Path, project: str, dir_in_project: str, prefix: str, param: dict[str, Any], version: str = '1.0c', cfg_seperator: str = 'n', sep: str = '/', **kwargs: Any) -> dict[str, Any]: +def read_data(path: Path, project: str, dir_in_project: str, prefix: str, param: dict[str, Any], version: str = '1.0c', cfg_separator: str = 'n', sep: str = '/', **kwargs: Any) -> dict[str, Any]: """ Extract the data from the sfcf file. @@ -274,7 +274,7 @@ def read_data(path: Path, project: str, dir_in_project: str, prefix: str, param: The parameter dictionary, as given by read_param. version: str Version of sfcf. - cfg_seperator: str + cfg_separator: str Separator of the configuration number. Needed for reading. default: "n" sep: str Seperator for the key in return dict. (default: "/) @@ -321,10 +321,10 @@ def read_data(path: Path, project: str, dir_in_project: str, prefix: str, param: if not param['crr'] == []: if names is not None: data_crr = pe.input.sfcf.read_sfcf_multi(directory, prefix, param['crr'], param['mrr'], corr_type_list, range(len(param['wf_offsets'])), - range(len(param['wf_basis'])), range(len(param['wf_basis'])), version, cfg_seperator, keyed_out=True, silent=True, names=names) + range(len(param['wf_basis'])), range(len(param['wf_basis'])), version, cfg_separator, keyed_out=True, silent=True, names=names) else: data_crr = pe.input.sfcf.read_sfcf_multi(directory, prefix, param['crr'], param['mrr'], corr_type_list, range(len(param['wf_offsets'])), - range(len(param['wf_basis'])), range(len(param['wf_basis'])), version, cfg_seperator, keyed_out=True, silent=True) + range(len(param['wf_basis'])), range(len(param['wf_basis'])), version, cfg_separator, keyed_out=True, silent=True) for key in data_crr.keys(): data[key] = data_crr[key] diff --git a/corrlib/toml.py b/corrlib/toml.py index 76533f4..c452dd8 100644 --- a/corrlib/toml.py +++ b/corrlib/toml.py @@ -116,7 +116,7 @@ def check_measurement_data(measurements: dict[str, dict[str, str]], code: str) - """ var_names: list[str] = [] if code == "sfcf": - var_names = ["path", "ensemble", "param_file", "version", "prefix", "cfg_seperator", "names"] + var_names = ["path", "ensemble", "param_file", "version", "prefix", "cfg_separator", "names"] elif code == "openQCD": var_names = ["path", "ensemble", "measurement", "prefix"] # , "param_file" for mname, md in measurements.items(): @@ -191,10 +191,10 @@ def import_toml(path: Path, file: str, copy_file: bool=True) -> None: param = sfcf.read_param(path, uuid, md['param_file']) if 'names' in md.keys(): measurement = sfcf.read_data(path, uuid, md['path'], md['prefix'], param, - version=md['version'], cfg_seperator=md['cfg_seperator'], sep='/', names=md['names']) + version=md['version'], cfg_separator=md['cfg_separator'], sep='/', names=md['names']) else: measurement = sfcf.read_data(path, uuid, md['path'], md['prefix'], param, - version=md['version'], cfg_seperator=md['cfg_seperator'], sep='/') + version=md['version'], cfg_separator=md['cfg_separator'], sep='/') elif project['code'] == 'openQCD': if md['measurement'] == 'ms1': From 07fdc1ba6af9faa1dd597da02b79e4d38b71ec60 Mon Sep 17 00:00:00 2001 From: Justus Kuhlmann Date: Tue, 7 Jul 2026 08:52:01 +0200 Subject: [PATCH 8/8] check if param file exists --- corrlib/input/openQCD.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/corrlib/input/openQCD.py b/corrlib/input/openQCD.py index 9dfe85f..8f5a8a1 100644 --- a/corrlib/input/openQCD.py +++ b/corrlib/input/openQCD.py @@ -31,6 +31,8 @@ def load_ms1_infile(path: Path, project: str, file_in_project: str) -> dict[str, """ file = os.path.join(path, "projects", project, file_in_project) + if not os.path.exists(file): + raise IOError(f"File {file} does not exist.") ds = os.path.join(path, "projects", project) dl.get(file, dataset=ds) with open(file, 'r') as fp: