Merge branch 'develop' of ssh://kuhl-mann.de/jkuhl/corrlib into develop
All checks were successful
Mypy / mypy (push) Successful in 1m16s
Pytest / pytest (3.12) (push) Successful in 1m21s
Pytest / pytest (3.13) (push) Successful in 1m15s
Pytest / pytest (3.14) (push) Successful in 1m17s
Ruff / ruff (push) Successful in 1m3s

This commit is contained in:
Justus Kuhlmann 2026-05-11 22:54:37 +02:00
commit 05d4f904ae
19 changed files with 1452 additions and 171 deletions

View file

@ -1,15 +1,16 @@
from typing import Optional from typing import Optional
import typer import typer
from corrlib import __app_name__ from corrlib import __app_name__
from .initialization import create from .initialization import create
from .toml import import_tomls, update_project, reimport_project from .toml import import_tomls, update_project, reimport_project
from .find import find_record, list_projects from .find import find_record, list_projects, list_ensembles, get_stat
from .tools import str2list from .tools import str2list
from .main import update_aliases from .main import update_aliases
from .meas_io import drop_cache as mio_drop_cache from .meas_io import drop_cache as mio_drop_cache
from .meas_io import load_record as mio_load_record from .integrity import full_integrity_check
import os import os
from pyerrors import Corr
from importlib.metadata import version from importlib.metadata import version
from pathlib import Path from pathlib import Path
@ -26,7 +27,7 @@ def _version_callback(value: bool) -> None:
@app.command() @app.command()
def update( def update(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),
@ -42,7 +43,7 @@ def update(
@app.command() @app.command()
def lister( def lister(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),
@ -53,15 +54,15 @@ def lister(
""" """
if entities in ['ensembles', 'Ensembles','ENSEMBLES']: if entities in ['ensembles', 'Ensembles','ENSEMBLES']:
print("Ensembles:") print("Ensembles:")
for item in os.listdir(path / "archive"): ensemble_results = list_ensembles(path)
if os.path.isdir(path / "archive" / item): for e in ensemble_results:
print(item) print(e)
elif entities == 'projects': elif entities == 'projects':
results = list_projects(path) project_results = list_projects(path)
print("Projects:") print("Projects:")
header = "UUID".ljust(37) + "| Aliases" header = "UUID".ljust(37) + "| Aliases"
print(header) print(header)
for project in results: for project in project_results:
if project[1] is not None: if project[1] is not None:
aliases = " | ".join(str2list(project[1])) aliases = " | ".join(str2list(project[1]))
else: else:
@ -73,7 +74,7 @@ def lister(
@app.command() @app.command()
def alias_add( def alias_add(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),
@ -91,7 +92,7 @@ def alias_add(
@app.command() @app.command()
def find( def find(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),
@ -105,12 +106,19 @@ def find(
), ),
) -> None: ) -> None:
""" """
Find a record in the backlog at hand. Through specifying it's ensemble and the measured correlator. Find a record in the given backlog.
""" """
results = find_record(path, ensemble, corr, code) results = find_record(path, ensemble, corr, code)
if results.empty:
return
if arg == 'all': if arg == 'all':
print(results) print(results)
else: else:
if arg == 'stat':
for r in results['path'].values:
stat = get_stat(path, r)
print(stat)
return
for r in results[arg].values: for r in results[arg].values:
print(r) print(r)
@ -118,7 +126,7 @@ def find(
@app.command() @app.command()
def stat( def stat(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),
@ -127,18 +135,28 @@ def stat(
""" """
Show the statistics of a given record. Show the statistics of a given record.
""" """
record = mio_load_record(path, record_id) statistics = get_stat(path, record_id)
if isinstance(record, (list, Corr)):
record = record[0]
statistics = record.idl
print(statistics) print(statistics)
return return
@app.command()
def check(path: Path = typer.Option(
Path('.'),
"--dataset",
"-d",
),
) -> None:
"""
Check the integrity of the repository.
"""
full_integrity_check(path)
@app.command() @app.command()
def importer( def importer(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),
@ -155,13 +173,14 @@ def importer(
""" """
file_list = files.split(",") file_list = files.split(",")
import_tomls(path, file_list, copy_file) import_tomls(path, file_list, copy_file)
mio_drop_cache(path)
return return
@app.command() @app.command()
def reimporter( def reimporter(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),
@ -179,13 +198,14 @@ def reimporter(
raise Exception("This file is not known for this project.") raise Exception("This file is not known for this project.")
else: else:
reimport_project(path, uuid) reimport_project(path, uuid)
mio_drop_cache(path)
return return
@app.command() @app.command()
def init( def init(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),
@ -205,7 +225,7 @@ def init(
@app.command() @app.command()
def drop_cache( def drop_cache(
path: Path = typer.Option( path: Path = typer.Option(
Path('./corrlib'), Path('.'),
"--dataset", "--dataset",
"-d", "-d",
), ),

View file

@ -6,11 +6,18 @@ import numpy as np
from .input.implementations import codes from .input.implementations import codes
from .tools import k2m, get_db_file from .tools import k2m, get_db_file
from .tracker import get from .tracker import get
from .integrity import has_valid_times
from .sql import thin_sql_wrapper
from typing import Any, Optional from typing import Any, Optional
from pathlib import Path from pathlib import Path
import datetime as dt
from collections.abc import Callable
import warnings
from .meas_io import load_record
from pyerrors import Corr, Obs
def _project_lookup_by_alias(db: Path, alias: str) -> str: def _project_lookup_by_alias(path: Path, alias: str) -> str:
""" """
Lookup a projects UUID by its (human-readable) alias. Lookup a projects UUID by its (human-readable) alias.
@ -26,11 +33,8 @@ def _project_lookup_by_alias(db: Path, alias: str) -> str:
uuid: str uuid: str
The UUID of the project with the given alias. The UUID of the project with the given alias.
""" """
conn = sqlite3.connect(db) stmt = f"SELECT * FROM 'projects' WHERE aliases = '{alias}'"
c = conn.cursor() results = thin_sql_wrapper(path, stmt)
c.execute(f"SELECT * FROM 'projects' WHERE aliases = '{alias}'")
results = c.fetchall()
conn.close()
if len(results)>1: if len(results)>1:
print("Error: multiple projects found with alias " + alias) print("Error: multiple projects found with alias " + alias)
elif len(results) == 0: elif len(results) == 0:
@ -38,7 +42,7 @@ def _project_lookup_by_alias(db: Path, alias: str) -> str:
return str(results[0][0]) return str(results[0][0])
def _project_lookup_by_id(db: Path, uuid: str) -> list[tuple[str, str]]: def _project_lookup_by_id(path: Path, uuid: str) -> list[tuple[str, ...]]:
""" """
Return the project information available in the database by UUID. Return the project information available in the database by UUID.
@ -54,16 +58,61 @@ def _project_lookup_by_id(db: Path, uuid: str) -> list[tuple[str, str]]:
results: list results: list
The row of the project in the database. The row of the project in the database.
""" """
conn = sqlite3.connect(db) stmt = f"SELECT * FROM 'projects' WHERE id = '{uuid}'"
c = conn.cursor() results = thin_sql_wrapper(path, stmt)
c.execute(f"SELECT * FROM 'projects' WHERE id = '{uuid}'")
results = c.fetchall()
conn.close()
return results return results
def _db_lookup(db: Path, ensemble: str, correlator_name: str, code: str, project: Optional[str]=None, parameters: Optional[str]=None, def _time_filter(results: pd.DataFrame, created_before: Optional[str]=None, created_after: Optional[Any]=None, updated_before: Optional[Any]=None, updated_after: Optional[Any]=None) -> pd.DataFrame:
created_before: Optional[str]=None, created_after: Optional[Any]=None, updated_before: Optional[Any]=None, updated_after: Optional[Any]=None) -> pd.DataFrame: """
Filter the results from the database in terms of the creation and update times.
Parameters
----------
results: pd.DataFrame
The dataframe holding the unfilteres results from the database.
created_before: str
Contraint on the creation date in datetime.datetime.isoformat. Note that this is exclusive. The creation date has to be truly before the date and time given.
created_after: str
Contraint on the creation date in datetime.datetime.isoformat. Note that this is exclusive. The creation date has to be truly after the date and time given.
updated_before: str
Contraint on the creation date in datetime.datetime.isoformat. Note that this is exclusive. The date of the last update has to be truly before the date and time given.
updated_after: str
Contraint on the creation date in datetime.datetime.isoformat. Note that this is exclusive. The date of the last update has to be truly after the date and time given.
"""
drops = []
for ind in range(len(results)):
result = results.iloc[ind]
created_at = dt.datetime.fromisoformat(result['created_at'])
updated_at = dt.datetime.fromisoformat(result['updated_at'])
db_times_valid = has_valid_times(result)
if not db_times_valid:
raise ValueError('Time stamps not valid for result with path', result["path"])
if created_before is not None:
date_created_before = dt.datetime.fromisoformat(created_before)
if date_created_before < created_at:
drops.append(ind)
continue
if created_after is not None:
date_created_after = dt.datetime.fromisoformat(created_after)
if date_created_after > created_at:
drops.append(ind)
continue
if updated_before is not None:
date_updated_before = dt.datetime.fromisoformat(updated_before)
if date_updated_before < updated_at:
drops.append(ind)
continue
if updated_after is not None:
date_updated_after = dt.datetime.fromisoformat(updated_after)
if date_updated_after > updated_at:
drops.append(ind)
continue
return results.drop(drops)
def _db_lookup(db: Path, ensemble: str, correlator_name: str, code: str, project: Optional[str]=None, parameters: Optional[str]=None) -> pd.DataFrame:
""" """
Look up a correlator record in the database by the data given to the method. Look up a correlator record in the database by the data given to the method.
@ -105,20 +154,84 @@ def _db_lookup(db: Path, ensemble: str, correlator_name: str, code: str, project
search_expr += f" AND code = '{code}'" search_expr += f" AND code = '{code}'"
if parameters: if parameters:
search_expr += f" AND parameters = '{parameters}'" search_expr += f" AND parameters = '{parameters}'"
if created_before:
search_expr += f" AND created_at < '{created_before}'"
if created_after:
search_expr += f" AND created_at > '{created_after}'"
if updated_before:
search_expr += f" AND updated_at < '{updated_before}'"
if updated_after:
search_expr += f" AND updated_at > '{updated_after}'"
conn = sqlite3.connect(db) conn = sqlite3.connect(db)
results = pd.read_sql(search_expr, conn) results = pd.read_sql(search_expr, conn)
conn.close() conn.close()
return results return results
def _sfcf_drop(param: dict[str, Any], **kwargs: Any) -> bool:
if 'offset' in kwargs:
if kwargs.get('offset') != param['offset']:
return True
if 'quark_kappas' in kwargs:
kappas = kwargs['quark_kappas']
if (not np.isclose(kappas[0], param['quarks'][0]['mass']) or not np.isclose(kappas[1], param['quarks'][1]['mass'])):
return True
if 'quark_masses' in kwargs:
masses = kwargs['quark_masses']
if (not np.isclose(masses[0], k2m(param['quarks'][0]['mass'])) or not np.isclose(masses[1], k2m(param['quarks'][1]['mass']))):
return True
if 'qk1' in kwargs:
quark_kappa1 = kwargs['qk1']
if not isinstance(quark_kappa1, list):
if (not np.isclose(quark_kappa1, param['quarks'][0]['mass'])):
return True
else:
if len(quark_kappa1) == 2:
if (quark_kappa1[0] > param['quarks'][0]['mass']) or (quark_kappa1[1] < param['quarks'][0]['mass']):
return True
else:
raise ValueError("quark_kappa1 has to have length 2")
if 'qk2' in kwargs:
quark_kappa2 = kwargs['qk2']
if not isinstance(quark_kappa2, list):
if (not np.isclose(quark_kappa2, param['quarks'][1]['mass'])):
return True
else:
if len(quark_kappa2) == 2:
if (quark_kappa2[0] > param['quarks'][1]['mass']) or (quark_kappa2[1] < param['quarks'][1]['mass']):
return True
else:
raise ValueError("quark_kappa2 has to have length 2")
if 'qm1' in kwargs:
quark_mass1 = kwargs['qm1']
if not isinstance(quark_mass1, list):
if (not np.isclose(quark_mass1, k2m(param['quarks'][0]['mass']))):
return True
else:
if len(quark_mass1) == 2:
if (quark_mass1[0] > k2m(param['quarks'][0]['mass'])) or (quark_mass1[1] < k2m(param['quarks'][0]['mass'])):
return True
else:
raise ValueError("quark_mass1 has to have length 2")
if 'qm2' in kwargs:
quark_mass2 = kwargs['qm2']
if not isinstance(quark_mass2, list):
if (not np.isclose(quark_mass2, k2m(param['quarks'][1]['mass']))):
return True
else:
if len(quark_mass2) == 2:
if (quark_mass2[0] > k2m(param['quarks'][1]['mass'])) or (quark_mass2[1] < k2m(param['quarks'][1]['mass'])):
return True
else:
raise ValueError("quark_mass2 has to have length 2")
if 'quark_thetas' in kwargs:
quark_thetas = kwargs['quark_thetas']
if (quark_thetas[0] != param['quarks'][0]['thetas'] and quark_thetas[1] != param['quarks'][1]['thetas']) or (quark_thetas[0] != param['quarks'][1]['thetas'] and quark_thetas[1] != param['quarks'][0]['thetas']):
return True
# careful, this is not save, when multiple contributions are present!
if 'wf1' in kwargs:
wf1 = kwargs['wf1']
if not (np.isclose(wf1[0][0], param['wf1'][0][0], 1e-8) and np.isclose(wf1[0][1][0], param['wf1'][0][1][0], 1e-8) and np.isclose(wf1[0][1][1], param['wf1'][0][1][1], 1e-8)):
return True
if 'wf2' in kwargs:
wf2 = kwargs['wf2']
if not (np.isclose(wf2[0][0], param['wf2'][0][0], 1e-8) and np.isclose(wf2[0][1][0], param['wf2'][0][1][0], 1e-8) and np.isclose(wf2[0][1][1], param['wf2'][0][1][1], 1e-8)):
return True
return False
def sfcf_filter(results: pd.DataFrame, **kwargs: Any) -> pd.DataFrame: def sfcf_filter(results: pd.DataFrame, **kwargs: Any) -> pd.DataFrame:
r""" r"""
Filter method for the Database entries holding SFCF calculations. Filter method for the Database entries holding SFCF calculations.
@ -136,9 +249,9 @@ def sfcf_filter(results: pd.DataFrame, **kwargs: Any) -> pd.DataFrame:
qk2: float, optional qk2: float, optional
Mass parameter $\kappa_2$ of the first quark. Mass parameter $\kappa_2$ of the first quark.
qm1: float, optional qm1: float, optional
Bare quak mass $m_1$ of the first quark. Bare quark mass $m_1$ of the first quark.
qm2: float, optional qm2: float, optional
Bare quak mass $m_1$ of the first quark. Bare quark mass $m_2$ of the first quark.
quarks_thetas: list[list[float]], optional quarks_thetas: list[list[float]], optional
wf1: optional wf1: optional
wf2: optional wf2: optional
@ -148,101 +261,81 @@ def sfcf_filter(results: pd.DataFrame, **kwargs: Any) -> pd.DataFrame:
results: pd.DataFrame results: pd.DataFrame
The filtered DataFrame, only holding the records that fit to the parameters given. The filtered DataFrame, only holding the records that fit to the parameters given.
""" """
drops = [] drops = []
for ind in range(len(results)): for ind in range(len(results)):
result = results.iloc[ind] result = results.iloc[ind]
param = json.loads(result['parameters']) param = json.loads(result['parameters'])
if 'offset' in kwargs: if _sfcf_drop(param, **kwargs):
if kwargs.get('offset') != param['offset']:
drops.append(ind) drops.append(ind)
continue
if 'quark_kappas' in kwargs:
kappas = kwargs['quark_kappas']
if (not np.isclose(kappas[0], param['quarks'][0]['mass']) or not np.isclose(kappas[1], param['quarks'][1]['mass'])):
drops.append(ind)
continue
if 'quark_masses' in kwargs:
masses = kwargs['quark_masses']
if (not np.isclose(masses[0], k2m(param['quarks'][0]['mass'])) or not np.isclose(masses[1], k2m(param['quarks'][1]['mass']))):
drops.append(ind)
continue
if 'qk1' in kwargs:
quark_kappa1 = kwargs['qk1']
if not isinstance(quark_kappa1, list):
if (not np.isclose(quark_kappa1, param['quarks'][0]['mass'])):
drops.append(ind)
continue
else:
if len(quark_kappa1) == 2:
if (quark_kappa1[0] > param['quarks'][0]['mass']) or (quark_kappa1[1] < param['quarks'][0]['mass']):
drops.append(ind)
continue
if 'qk2' in kwargs:
quark_kappa2 = kwargs['qk2']
if not isinstance(quark_kappa2, list):
if (not np.isclose(quark_kappa2, param['quarks'][1]['mass'])):
drops.append(ind)
continue
else:
if len(quark_kappa2) == 2:
if (quark_kappa2[0] > param['quarks'][1]['mass']) or (quark_kappa2[1] < param['quarks'][1]['mass']):
drops.append(ind)
continue
if 'qm1' in kwargs:
quark_mass1 = kwargs['qm1']
if not isinstance(quark_mass1, list):
if (not np.isclose(quark_mass1, k2m(param['quarks'][0]['mass']))):
drops.append(ind)
continue
else:
if len(quark_mass1) == 2:
if (quark_mass1[0] > k2m(param['quarks'][0]['mass'])) or (quark_mass1[1] < k2m(param['quarks'][0]['mass'])):
drops.append(ind)
continue
if 'qm2' in kwargs:
quark_mass2 = kwargs['qm2']
if not isinstance(quark_mass2, list):
if (not np.isclose(quark_mass2, k2m(param['quarks'][1]['mass']))):
drops.append(ind)
continue
else:
if len(quark_mass2) == 2:
if (quark_mass2[0] > k2m(param['quarks'][1]['mass'])) or (quark_mass2[1] < k2m(param['quarks'][1]['mass'])):
drops.append(ind)
continue
if 'quark_thetas' in kwargs:
quark_thetas = kwargs['quark_thetas']
if (quark_thetas[0] != param['quarks'][0]['thetas'] and quark_thetas[1] != param['quarks'][1]['thetas']) or (quark_thetas[0] != param['quarks'][1]['thetas'] and quark_thetas[1] != param['quarks'][0]['thetas']):
drops.append(ind)
continue
# careful, this is not save, when multiple contributions are present!
if 'wf1' in kwargs:
wf1 = kwargs['wf1']
if not (np.isclose(wf1[0][0], param['wf1'][0][0], 1e-8) and np.isclose(wf1[0][1][0], param['wf1'][0][1][0], 1e-8) and np.isclose(wf1[0][1][1], param['wf1'][0][1][1], 1e-8)):
drops.append(ind)
continue
if 'wf2' in kwargs:
wf2 = kwargs['wf2']
if not (np.isclose(wf2[0][0], param['wf2'][0][0], 1e-8) and np.isclose(wf2[0][1][0], param['wf2'][0][1][0], 1e-8) and np.isclose(wf2[0][1][1], param['wf2'][0][1][1], 1e-8)):
drops.append(ind)
continue
return results.drop(drops) return results.drop(drops)
def openQCD_filter(results:pd.DataFrame, **kwargs: Any) -> pd.DataFrame:
"""
Filter for parameters of openQCD.
Parameters
----------
results: pd.DataFrame
The unfiltered list of results from the database.
Returns
-------
results: pd.DataFrame
The filtered results.
"""
warnings.warn("A filter for openQCD parameters is no implemented yet.", Warning)
return results
def _code_filter(results: pd.DataFrame, code: str, **kwargs: Any) -> pd.DataFrame:
"""
Abstraction of the filters for the different codes that are available.
At the moment, only openQCD and SFCF are known.
The possible key words for the parameters can be seen in the descriptionso f the code-specific filters.
Parameters
----------
results: pd.DataFrame
The unfiltered list of results from the database.
code: str
The name of the code that produced the record at hand.
kwargs:
The keyworkd args that are handed over to the code-specific filters.
Returns
-------
results: pd.DataFrame
The filtered results.
"""
if code == "sfcf":
return sfcf_filter(results, **kwargs)
elif code == "openQCD":
return openQCD_filter(results, **kwargs)
else:
raise ValueError(f"Code {code} is not known.")
def find_record(path: Path, ensemble: str, correlator_name: str, code: str, project: Optional[str]=None, parameters: Optional[str]=None, def find_record(path: Path, ensemble: str, correlator_name: str, code: str, project: Optional[str]=None, parameters: Optional[str]=None,
created_before: Optional[str]=None, created_after: Optional[str]=None, updated_before: Optional[str]=None, updated_after: Optional[str]=None, revision: Optional[str]=None, **kwargs: Any) -> pd.DataFrame: created_before: Optional[str]=None, created_after: Optional[str]=None, updated_before: Optional[str]=None, updated_after: Optional[str]=None,
revision: Optional[str]=None,
customFilter: Optional[Callable[[pd.DataFrame], pd.DataFrame]] = None,
**kwargs: Any) -> pd.DataFrame:
path = Path(path)
db_file = get_db_file(path) db_file = get_db_file(path)
db = path / db_file db = path / db_file
if code not in codes: if code not in codes:
raise ValueError("Code " + code + "unknown, take one of the following:" + ", ".join(codes)) raise ValueError("Code " + code + "unknown, take one of the following:" + ", ".join(codes))
get(path, db_file) get(path, db_file)
results = _db_lookup(db, ensemble, correlator_name,code, project, parameters=parameters, created_before=created_before, created_after=created_after, updated_before=updated_before, updated_after=updated_after) results = _db_lookup(db, ensemble, correlator_name,code, project, parameters=parameters)
if code == "sfcf": if any([arg is not None for arg in [created_before, created_after, updated_before, updated_after]]):
results = sfcf_filter(results, **kwargs) results = _time_filter(results, created_before, created_after, updated_before, updated_after)
elif code == "openQCD": results = _code_filter(results, code, **kwargs)
pass if customFilter is not None:
else: results = customFilter(results)
raise Exception
print("Found " + str(len(results)) + " result" + ("s" if len(results)>1 else "")) print("Found " + str(len(results)) + " result" + ("s" if len(results)>1 else ""))
return results.reset_index() return results.reset_index()
@ -265,7 +358,7 @@ def find_project(path: Path, name: str) -> str:
""" """
db_file = get_db_file(path) db_file = get_db_file(path)
get(path, db_file) get(path, db_file)
return _project_lookup_by_alias(path / db_file, name) return _project_lookup_by_alias(path, name)
def list_projects(path: Path) -> list[tuple[str, str]]: def list_projects(path: Path) -> list[tuple[str, str]]:
@ -291,3 +384,19 @@ def list_projects(path: Path) -> list[tuple[str, str]]:
conn.close() conn.close()
return results return results
def list_ensembles(path: Path) -> list[str]:
res = []
for item in os.listdir(path / "archive"):
if os.path.isdir(path / "archive" / item):
res.append(item)
return res
def get_stat(path: Path, record_id: str) -> Any:
loaded_record: Obs = load_record(path, record_id)
if isinstance(loaded_record, (list, Corr)):
record: Obs = loaded_record[0]
else:
record = loaded_record
return record.idl

View file

@ -3,6 +3,7 @@ import sqlite3
import os import os
from .tracker import save, init from .tracker import save, init
from pathlib import Path from pathlib import Path
from .tools import CONFIG_FILENAME
def _create_db(db: Path) -> None: def _create_db(db: Path) -> None:
@ -87,7 +88,7 @@ def _write_config(path: Path, config: ConfigParser) -> None:
config: ConfigParser config: ConfigParser
The configuration to be used as a ConfigParser, e.g. generated by _create_config. The configuration to be used as a ConfigParser, e.g. generated by _create_config.
""" """
with open(os.path.join(path, '.corrlib'), 'w') as configfile: with open(os.path.join(path, CONFIG_FILENAME), 'w') as configfile:
config.write(configfile) config.write(configfile)
return return

View file

@ -4,9 +4,12 @@ import os
import fnmatch import fnmatch
from typing import Any, Optional from typing import Any, Optional
from pathlib import Path from pathlib import Path
from ..pars.openQCD import ms1
from ..pars.openQCD import qcd2
def read_ms1_param(path: Path, project: str, file_in_project: str) -> dict[str, Any]:
def load_ms1_infile(path: Path, project: str, file_in_project: str) -> dict[str, Any]:
""" """
Read the parameters for ms1 measurements from a parameter file in the project. Read the parameters for ms1 measurements from a parameter file in the project.
@ -70,7 +73,7 @@ def read_ms1_param(path: Path, project: str, file_in_project: str) -> dict[str,
return param return param
def read_ms3_param(path: Path, project: str, file_in_project: str) -> dict[str, Any]: def load_ms3_infile(path: Path, project: str, file_in_project: str) -> dict[str, Any]:
""" """
Read the parameters for ms3 measurements from a parameter file in the project. Read the parameters for ms3 measurements from a parameter file in the project.
@ -161,7 +164,8 @@ def read_rwms(path: Path, project: str, dir_in_project: str, param: dict[str, An
return rw_dict 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) -> dict[str, Any]: 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,
r_start: list[int]=[], r_stop: list[int]=[], r_step:int=1) -> dict[str, Any]:
""" """
Extract t0 measurements from the project. Extract t0 measurements from the project.
@ -215,6 +219,11 @@ def extract_t0(path: Path, project: str, dir_in_project: str, param: dict[str, A
if postfix is not None: if postfix is not None:
kwargs['postfix'] = postfix kwargs['postfix'] = postfix
kwargs['plot_fit'] = False kwargs['plot_fit'] = False
if not r_start == []:
kwargs['r_start'] = r_start
if not r_stop == []:
kwargs['r_stop'] = r_stop
kwargs['r_step'] = r_step
t0 = input.extract_t0(directory, t0 = input.extract_t0(directory,
prefix, prefix,
@ -235,7 +244,8 @@ def extract_t0(path: Path, project: str, dir_in_project: str, param: dict[str, A
return t0_dict 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) -> dict[str, Any]: 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,
r_start: list[int]=[], r_stop: list[int]=[], r_step:int=1) -> dict[str, Any]:
""" """
Extract t1 measurements from the project. Extract t1 measurements from the project.
@ -287,6 +297,11 @@ def extract_t1(path: Path, project: str, dir_in_project: str, param: dict[str, A
if postfix is not None: if postfix is not None:
kwargs['postfix'] = postfix kwargs['postfix'] = postfix
kwargs['plot_fit'] = False kwargs['plot_fit'] = False
if not r_start == []:
kwargs['r_start'] = r_start
if not r_stop == []:
kwargs['r_stop'] = r_stop
kwargs['r_step'] = r_step
t0 = input.extract_t0(directory, t0 = input.extract_t0(directory,
prefix, prefix,
dtr_read, dtr_read,
@ -304,3 +319,51 @@ def extract_t1(path: Path, project: str, dir_in_project: str, param: dict[str, A
t1_dict[param["type"]] = {} t1_dict[param["type"]] = {}
t1_dict[param["type"]][pars] = t0 t1_dict[param["type"]][pars] = t0
return t1_dict return t1_dict
def load_qcd2_pars(path: Path, project: str, file_in_project: str) -> dict[str, Any]:
"""
Thin wrapper around read_qcd2_par_file, getting the file before reading.
Parameters
----------
path: Path
Path of the corrlib repository.
project: str
UUID of the project of the parameter-file.
file_in_project: str
The loaction of the file in the project directory.
Returns
-------
par_dict: dict
The dict with the parameters read from the .par-file.
"""
fname = path / "projects" / project / file_in_project
ds = os.path.join(path, "projects", project)
dl.get(fname, dataset=ds)
return qcd2.read_qcd2_par_file(fname)
def load_ms1_parfile(path: Path, project: str, file_in_project: str) -> dict[str, Any]:
"""
Thin wrapper around read_qcd2_ms1_par_file, getting the file before reading.
Parameters
----------
path: Path
Path of the corrlib repository.
project: str
UUID of the project of the parameter-file.
file_in_project: str
The loaction of the file in the project directory.
Returns
-------
par_dict: dict
The dict with the parameters read from the .par-file.
"""
fname = path / "projects" / project / file_in_project
ds = os.path.join(path, "projects", project)
dl.get(fname, dataset=ds)
return ms1.read_qcd2_ms1_par_file(fname)

295
corrlib/integrity.py Normal file
View file

@ -0,0 +1,295 @@
import datetime as dt
from pathlib import Path
from .tools import get_db_file, CONFIG_FILENAME
import pandas as pd
import sqlite3
from .tracker import get
import pyerrors.input.json as pj
import os
from configparser import ConfigParser
from typing import Any
path_opts = ['db', 'projects_path', 'archive_path', 'toml_imports_path', 'import_scripts_path']
def has_valid_times(result: pd.Series) -> bool:
"""
Check, whether the result at hand has time-stamps that are sensible:
A recored is created first, then updated, with both times laying in the past.
Parameters
----------
result: pd.Series
The result to check
Returns
-------
b: bool
True, if the timestamps make sense.
"""
# we expect created_at <= updated_at <= now
created_at = dt.datetime.fromisoformat(result['created_at'])
updated_at = dt.datetime.fromisoformat(result['updated_at'])
if created_at > updated_at:
return False
if updated_at > dt.datetime.now():
return False
return True
def are_keys_unique(db: Path, table: str, col: str) -> bool:
"""
Check whether the strings listed in a column of a given table are unique.
Parameters
----------
db: Path
The database to check.
table: str
The table to check.
col: str
The column to be checked for uniqueness.
Returns
-------
b: bool
True, if the strings are unique.
"""
conn = sqlite3.connect(db)
c = conn.cursor()
c.execute(f"SELECT COUNT( DISTINCT CAST({col} AS nvarchar(4000))), COUNT({col}) FROM {table};")
results = c.fetchall()[0]
conn.close()
res = bool(results[0] == results[1])
if not res:
print("Unique:", results[0], "All:", results[1])
return res
def _list_projects(path: Path) -> list[tuple[str, str]]:
"""
List all projects known to the library.
Parameters
----------
path: str
The path of the library.
Returns
-------
results: list[Any]
The projects known to the library.
"""
db_file = get_db_file(path)
get(path, db_file)
conn = sqlite3.connect(os.path.join(path, db_file))
c = conn.cursor()
c.execute("SELECT id,aliases FROM projects")
results = c.fetchall()
conn.close()
return results
def _list_ensembles(path: Path) -> list[str]:
res = []
for item in os.listdir(path / "archive"):
if os.path.isdir(path / "archive" / item):
res.append(item)
return res
def check_path_format(result: pd.Series, ensembles: list[str], projects: list[str]) -> None:
"""
Check whether the path of the given result has the right format.
Parameters
----------
result: pd.Series
The result to be checked.
"""
p = result['path']
if not p.startswith('archive'):
raise ValueError(f'The path {p} does not start correctly')
meas_key = p.split('::')[1]
ensemble = p.split('/')[1]
project = p.split('/')[3].split('.')[0]
if not len(meas_key) == 64:
raise ValueError(f'meas_key of {p} is scrambled')
if ensemble not in ensembles:
raise ValueError(f'meas_key of {p} points to an unknown ensemble')
if project not in projects:
raise ValueError(f'meas_key of {p} points to an unknown project id ({project})')
if not ensemble == result['ensemble']:
raise ValueError(f'Ensemble in database and file does not match for path {p}.')
def check_db_integrity(path: Path) -> None:
"""
Check intergrity of the database by checking the uniqueness of the record keys used to load the records
and ensuring that the timestamps of each record is sensible. Throws an error, if issues are detected.
Parameters
----------
path: Path
Path to the backlog-library to check.
"""
db = get_db_file(path)
if not are_keys_unique(path / db, 'backlogs', 'path'):
raise Exception("The paths the backlog table of the database links are not unique.")
search_expr = "SELECT * FROM 'backlogs'"
conn = sqlite3.connect(path / db)
results = pd.read_sql(search_expr, conn)
ensembles = _list_ensembles(path)
projects = [p[0] for p in _list_projects(path)]
for _, result in results.iterrows():
if not has_valid_times(result):
raise ValueError(f"Result with id {result[id]} has wrong time signatures.")
check_path_format(result, ensembles, projects)
return
def _check_db2paths(path: Path, meas_paths: list[str]) -> None:
"""
Check whether for each record in the given by meas_paths, we can find the data in the file as we expect.
Also check, whether there are unreachable records in the files. If either of the issues arise, throws an error.
Parameters
----------
path: Path
Path to the backlog-library to check.
meas_paths: list[str]
List of measurement paths to check.
"""
needed_data: dict[str, list[str]] = {}
for mpath in meas_paths:
file = mpath.split("::")[0]
if file not in needed_data.keys():
needed_data[file] = []
key = mpath.split("::")[1]
needed_data[file].append(key)
totf = len(needed_data.keys())
for i, file in enumerate(needed_data.keys()):
print(f"Check against file {i}/{totf}: {file}")
get(path, Path(file))
filedict: dict[str, Any] = pj.load_json_dict(str(path / file))
if not set(filedict.keys()).issubset(needed_data[file]):
for key in filedict.keys():
if key not in needed_data[file]:
raise ValueError(f"Found unintended key {key} in file {file}.")
if not set(needed_data[file]).issubset(filedict.keys()):
for key in needed_data[file]:
if key not in filedict.keys():
raise ValueError(f"Did not find data for key {key} that should be in file {file}.")
return
def check_db_file_links(path: Path) -> None:
"""
Check whether for each record in the given correlator library, we can find the data in the file as we expect.
Also check, whether there are unreachable records in the files. If either of the issues arise, throws an error.
Parameters
----------
path: Path
Path to the backlog-library to check.
"""
db = get_db_file(path)
search_expr = "SELECT path FROM 'backlogs'"
conn = sqlite3.connect(path / db)
results = pd.read_sql(search_expr, conn)['path'].values
_check_db2paths(path, list(results))
def check_path_and_config(path: Path) -> None:
"""
Check whether the given path exists and the cinfigureation file can be found.
Parameters
----------
path: Path
Path to the backlog-library to check.
"""
if not os.path.exists(path):
raise FileNotFoundError(f"Corrlib path {path} does not exist.")
config_path = path / CONFIG_FILENAME
if not os.path.exists(config_path):
raise FileNotFoundError(f"Configuration file {config_path} not found.")
def check_config_validity(path: Path) -> None:
"""
Check whether the configuration file of the given corrlib-dataset path is valid.
Parameters
----------
path: Path
Path to the backlog-library to check.
"""
config = ConfigParser()
config_path = path / CONFIG_FILENAME
if os.path.exists(config_path):
config.read(config_path)
else:
raise FileNotFoundError("Configuration file not found.")
if config.has_section('core'):
core_opts = ['version', 'tracker', 'cached']
has_core_opts = [config.has_option('core', opt) for opt in core_opts]
if not all(has_core_opts):
raise ValueError("One of the options in the 'core' section ('version', 'tracker', 'cached') is missing.")
if config.has_section('paths'):
has_path_opts = [config.has_option('paths', opt) for opt in path_opts]
if not all(has_path_opts):
raise ValueError("One of the options in the 'path' section ('db', 'projects_path', 'archive_path', 'toml_imports_path', 'import_scripts_path') is missing.")
def check_paths(path: Path) -> None:
"""
Check whether all paths demanded by the 'paths' section of the configuration-file exist.
Parameters
----------
path: Path
Path to the backlog-library to check.
"""
config = ConfigParser()
config_path = path / CONFIG_FILENAME
if os.path.exists(config_path):
config.read(config_path)
else:
raise FileNotFoundError("Configuration file not found.")
has_paths = [os.path.exists(path / config.get('paths', opt)) for opt in path_opts]
if not all(has_paths):
raise FileNotFoundError("One of the paths specified in the configuration file is not present.")
def full_integrity_check(path: Path) -> None:
"""
Aggregate all checks for easy validation of the backlog-library.
Parameters
----------
path: Path
Path to the backlog-library to check.
"""
print("Run full integrity check...")
check_path_and_config(path)
print("(1/5) Path and config-file exist: ✅")
check_config_validity(path)
print("(2/5) Configuration is valid: ✅")
check_paths(path)
print("(3/5) Needed paths exist: ✅")
check_db_integrity(path)
print("(4/5) Database is sane: ✅")
check_db_file_links(path)
print("(5/5) DB2File and File2DB-links are sound: ✅")
print("Full integrity check: ✅")

View file

@ -27,7 +27,7 @@ def create_project(path: Path, uuid: str, owner: Union[str, None]=None, tags: Un
The code that was used to create the measurements. The code that was used to create the measurements.
""" """
db_file = get_db_file(path) db_file = get_db_file(path)
db = os.path.join(path, db_file) db = path / db_file
get(path, db_file) get(path, db_file)
conn = sqlite3.connect(db) conn = sqlite3.connect(db)
c = conn.cursor() c = conn.cursor()
@ -67,7 +67,7 @@ def update_project_data(path: Path, uuid: str, prop: str, value: Union[str, None
""" """
db_file = get_db_file(path) db_file = get_db_file(path)
get(path, db_file) get(path, db_file)
conn = sqlite3.connect(os.path.join(path, db_file)) conn = sqlite3.connect(path / db_file)
c = conn.cursor() c = conn.cursor()
c.execute(f"UPDATE projects SET '{prop}' = '{value}' WHERE id == '{uuid}'") c.execute(f"UPDATE projects SET '{prop}' = '{value}' WHERE id == '{uuid}'")
conn.commit() conn.commit()
@ -77,9 +77,8 @@ def update_project_data(path: Path, uuid: str, prop: str, value: Union[str, None
def update_aliases(path: Path, uuid: str, aliases: list[str]) -> None: def update_aliases(path: Path, uuid: str, aliases: list[str]) -> None:
db_file = get_db_file(path) db_file = get_db_file(path)
db = path / db_file
get(path, db_file) get(path, db_file)
known_data = _project_lookup_by_id(db, uuid)[0] known_data = _project_lookup_by_id(path, uuid)[0]
known_aliases = known_data[1] known_aliases = known_data[1]
if aliases is None: if aliases is None:

View file

@ -11,6 +11,7 @@ from .tracker import get, save, unlock
import shutil import shutil
from typing import Any from typing import Any
from pathlib import Path from pathlib import Path
from .integrity import _check_db2paths
CACHE_DIR = ".cache" CACHE_DIR = ".cache"
@ -36,6 +37,7 @@ def write_measurement(path: Path, ensemble: str, measurement: dict[str, dict[str
parameter_file: str parameter_file: str
The parameter file used for the measurement. The parameter file used for the measurement.
""" """
path = Path(path)
db_file = get_db_file(path) db_file = get_db_file(path)
db = path / db_file db = path / db_file
@ -49,7 +51,7 @@ def write_measurement(path: Path, ensemble: str, measurement: dict[str, dict[str
c = conn.cursor() c = conn.cursor()
for corr in measurement.keys(): for corr in measurement.keys():
file_in_archive = Path('.') / 'archive' / ensemble / corr / str(uuid + '.json.gz') file_in_archive = Path('.') / 'archive' / ensemble / corr / str(uuid + '.json.gz')
file = path / file_in_archive file = Path(path) / file_in_archive
known_meas = {} known_meas = {}
if not os.path.exists(path / 'archive' / ensemble / corr): if not os.path.exists(path / 'archive' / ensemble / corr):
os.makedirs(path / 'archive' / ensemble / corr) os.makedirs(path / 'archive' / ensemble / corr)
@ -59,7 +61,7 @@ def write_measurement(path: Path, ensemble: str, measurement: dict[str, dict[str
if file not in files_to_save: if file not in files_to_save:
unlock(path, file_in_archive) unlock(path, file_in_archive)
files_to_save.append(file_in_archive) files_to_save.append(file_in_archive)
known_meas = pj.load_json_dict(file, verbose=False) known_meas = pj.load_json_dict(str(file), verbose=False)
if code == "sfcf": if code == "sfcf":
if parameter_file is not None: if parameter_file is not None:
parameters = sfcf.read_param(path, uuid, parameter_file) parameters = sfcf.read_param(path, uuid, parameter_file)
@ -74,9 +76,24 @@ def write_measurement(path: Path, ensemble: str, measurement: dict[str, dict[str
ms_type = list(measurement.keys())[0] ms_type = list(measurement.keys())[0]
if ms_type == 'ms1': if ms_type == 'ms1':
if parameter_file is not None: if parameter_file is not None:
parameters = openQCD.read_ms1_param(path, uuid, parameter_file) if parameter_file.endswith(".ms1.in"):
parameters = openQCD.load_ms1_infile(path, uuid, parameter_file)
elif parameter_file.endswith(".ms1.par"):
parameters = openQCD.load_ms1_parfile(path, uuid, parameter_file)
else: else:
raise Exception("Need parameter file for this code!") # Temporary solution
parameters = {}
parameters["rand"] = {}
parameters["rw_fcts"] = [{}]
for nrw in range(1):
if "nsrc" not in parameters["rw_fcts"][nrw]:
parameters["rw_fcts"][nrw]["nsrc"] = 1
if "mu" not in parameters["rw_fcts"][nrw]:
parameters["rw_fcts"][nrw]["mu"] = "None"
if "np" not in parameters["rw_fcts"][nrw]:
parameters["rw_fcts"][nrw]["np"] = "None"
if "irp" not in parameters["rw_fcts"][nrw]:
parameters["rw_fcts"][nrw]["irp"] = "None"
pars = {} pars = {}
subkeys = [] subkeys = []
for i in range(len(parameters["rw_fcts"])): for i in range(len(parameters["rw_fcts"])):
@ -88,7 +105,7 @@ def write_measurement(path: Path, ensemble: str, measurement: dict[str, dict[str
pars[subkey] = json.dumps(parameters["rw_fcts"][i]) pars[subkey] = json.dumps(parameters["rw_fcts"][i])
elif ms_type in ['t0', 't1']: elif ms_type in ['t0', 't1']:
if parameter_file is not None: if parameter_file is not None:
parameters = openQCD.read_ms3_param(path, uuid, parameter_file) parameters = openQCD.load_ms3_infile(path, uuid, parameter_file)
else: else:
parameters = {} parameters = {}
for rwp in ["integrator", "eps", "ntot", "dnms"]: for rwp in ["integrator", "eps", "ntot", "dnms"]:
@ -113,7 +130,7 @@ def write_measurement(path: Path, ensemble: str, measurement: dict[str, dict[str
c.execute("INSERT INTO backlogs (name, ensemble, code, path, project, parameters, parameter_file, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))", c.execute("INSERT INTO backlogs (name, ensemble, code, path, project, parameters, parameter_file, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(corr, ensemble, code, meas_path, uuid, pars[subkey], parameter_file)) (corr, ensemble, code, meas_path, uuid, pars[subkey], parameter_file))
conn.commit() conn.commit()
pj.dump_dict_to_json(known_meas, file) pj.dump_dict_to_json(known_meas, str(file))
conn.close() conn.close()
save(path, message="Add measurements to database", files=files_to_save) save(path, message="Add measurements to database", files=files_to_save)
return return
@ -138,7 +155,7 @@ def load_record(path: Path, meas_path: str) -> Union[Corr, Obs]:
return load_records(path, [meas_path])[0] return load_records(path, [meas_path])[0]
def load_records(path: Path, meas_paths: list[str], preloaded: dict[str, Any] = {}) -> list[Union[Corr, Obs]]: def load_records(path: Path, meas_paths: list[str], preloaded: dict[str, Any] = {}, dry_run: bool = False) -> list[Union[Corr, Obs]]:
""" """
Load a list of records by their paths. Load a list of records by their paths.
@ -148,14 +165,20 @@ def load_records(path: Path, meas_paths: list[str], preloaded: dict[str, Any] =
Path of the correlator library. Path of the correlator library.
meas_paths: list[str] meas_paths: list[str]
A list of the paths to the correlator in the backlog system. A list of the paths to the correlator in the backlog system.
perloaded: dict[str, Any] preloaded: dict[str, Any]
The data that is already prelaoded. Of interest if data has alread been loaded in the same script. The data that is already preloaded. Of interest if data has alread been loaded in the same script.
dry_run: bool
Do not load datda, just check whether we can reach the data we are interested in.
Returns Returns
------- -------
retruned_data: list returned_data: list
The loaded records. The loaded records.
""" """
path = Path(path)
if dry_run:
_check_db2paths(path, meas_paths)
return []
needed_data: dict[str, list[str]] = {} needed_data: dict[str, list[str]] = {}
for mpath in meas_paths: for mpath in meas_paths:
file = mpath.split("::")[0] file = mpath.split("::")[0]
@ -175,7 +198,7 @@ def load_records(path: Path, meas_paths: list[str], preloaded: dict[str, Any] =
if cache_enabled(path): if cache_enabled(path):
if not os.path.exists(cache_dir(path, file)): if not os.path.exists(cache_dir(path, file)):
os.makedirs(cache_dir(path, file)) os.makedirs(cache_dir(path, file))
dump_object(preloaded[file][key], cache_path(path, file, key)) dump_object(preloaded[file][key], str(cache_path(path, file, key)))
return returned_data return returned_data
@ -195,7 +218,7 @@ def cache_dir(path: Path, file: str) -> Path:
The path holding the cached data for the given file. The path holding the cached data for the given file.
""" """
cache_path_list = file.split("/")[1:] cache_path_list = file.split("/")[1:]
cache_path = path / CACHE_DIR cache_path = Path(path) / CACHE_DIR
for directory in cache_path_list: for directory in cache_path_list:
cache_path /= directory cache_path /= directory
return cache_path return cache_path
@ -217,6 +240,7 @@ def cache_path(path: Path, file: str, key: str) -> Path:
cache_path: str cache_path: str
The path at which the measurement of the given file and key is cached. The path at which the measurement of the given file and key is cached.
""" """
path = Path(path)
cache_path = cache_dir(path, file) / key cache_path = cache_dir(path, file) / key
return cache_path return cache_path
@ -237,8 +261,9 @@ def preload(path: Path, file: Path) -> dict[str, Any]:
filedict: dict[str, Any] filedict: dict[str, Any]
The data read from the file. The data read from the file.
""" """
path = Path(path)
get(path, file) get(path, file)
filedict: dict[str, Any] = pj.load_json_dict(path / file) filedict: dict[str, Any] = pj.load_json_dict(str(path / file))
print("> read file") print("> read file")
return filedict return filedict
@ -255,7 +280,7 @@ def drop_record(path: Path, meas_path: str) -> None:
The measurement path as noted in the database. The measurement path as noted in the database.
""" """
file_in_archive = meas_path.split("::")[0] file_in_archive = meas_path.split("::")[0]
file = path / file_in_archive file = Path(path) / file_in_archive
db_file = get_db_file(path) db_file = get_db_file(path)
db = path / db_file db = path / db_file
get(path, db_file) get(path, db_file)
@ -269,11 +294,11 @@ def drop_record(path: Path, meas_path: str) -> None:
raise ValueError("This measurement does not exist as an entry!") raise ValueError("This measurement does not exist as an entry!")
conn.commit() conn.commit()
known_meas = pj.load_json_dict(file) known_meas = pj.load_json_dict(str(file))
if sub_key in known_meas: if sub_key in known_meas:
del known_meas[sub_key] del known_meas[sub_key]
unlock(path, Path(file_in_archive)) unlock(path, Path(file_in_archive))
pj.dump_dict_to_json(known_meas, file) pj.dump_dict_to_json(known_meas, str(file))
save(path, message="Drop measurements to database", files=[db, file]) save(path, message="Drop measurements to database", files=[db, file])
return return
else: else:
@ -289,6 +314,7 @@ def drop_cache(path: Path) -> None:
path: str path: str
The path of the library. The path of the library.
""" """
path = Path(path)
cache_dir = path / ".cache" cache_dir = path / ".cache"
for f in os.listdir(cache_dir): for f in os.listdir(cache_dir):
shutil.rmtree(cache_dir / f) shutil.rmtree(cache_dir / f)

View file

@ -0,0 +1,3 @@
from . import ms1 as ms1
from . import qcd2 as qcd2

View file

@ -0,0 +1,59 @@
"""
Reconstruct the outputs of flags.
"""
import struct
from typing import Any, BinaryIO
# lat_parms.c
def lat_parms_write_lat_parms(fp: BinaryIO) -> dict[str, Any]:
"""
NOTE: This is a duplcation from qcd2.
Unpack the lattice parameters written by write_lat_parms.
"""
lat_pars = {}
t = fp.read(16)
lat_pars["N"] = list(struct.unpack('iiii', t)) # lattice extends
t = fp.read(8)
nk, isw = struct.unpack('ii', t) # number of kappas and isw parameter
lat_pars["nk"] = nk
lat_pars["isw"] = isw
t = fp.read(8)
lat_pars["beta"] = struct.unpack('d', t)[0] # beta
t = fp.read(8)
lat_pars["c0"] = struct.unpack('d', t)[0]
t = fp.read(8)
lat_pars["c1"] = struct.unpack('d', t)[0]
t = fp.read(8)
lat_pars["csw"] = struct.unpack('d', t)[0] # csw factor
kappas = []
m0s = []
# read kappas
for ik in range(nk):
t = fp.read(8)
kappas.append(struct.unpack('d', t)[0])
t = fp.read(8)
m0s.append(struct.unpack('d', t)[0])
lat_pars["kappas"] = kappas
lat_pars["m0s"] = m0s
return lat_pars
def lat_parms_write_bc_parms(fp: BinaryIO) -> dict[str, Any]:
"""
NOTE: This is a duplcation from qcd2.
Unpack the boundary parameters written by write_bc_parms.
"""
bc_pars: dict[str, Any] = {}
t = fp.read(4)
bc_pars["type"] = struct.unpack('i', t)[0] # type of hte boundaries
t = fp.read(104)
bc_parms = struct.unpack('d'*13, t)
bc_pars["cG"] = list(bc_parms[:2]) # boundary gauge field improvement
bc_pars["cF"] = list(bc_parms[2:4]) # boundary fermion field improvement
phi: list[list[float]] = [[], []]
phi[0] = list(bc_parms[4:7])
phi[1] = list(bc_parms[7:10])
bc_pars["phi"] = phi
bc_pars["theta"] = list(bc_parms[10:])
return bc_pars

View file

@ -0,0 +1,30 @@
from . import flags
from typing import Any
from pathlib import Path
def read_qcd2_ms1_par_file(fname: Path) -> dict[str, dict[str, Any]]:
"""
The subroutines written here have names according to the openQCD programs and functions that write out the data.
Parameters
----------
fname: Path
Location of the parameter file.
Returns
-------
par_dict: dict
Dictionary holding the parameters specified in the given file.
"""
with open(fname, "rb") as fp:
lat_par_dict = flags.lat_parms_write_lat_parms(fp)
bc_par_dict = flags.lat_parms_write_bc_parms(fp)
fp.close()
par_dict = {}
par_dict["lat"] = lat_par_dict
par_dict["bc"] = bc_par_dict
return par_dict

View file

@ -0,0 +1,29 @@
from . import flags
from pathlib import Path
from typing import Any
def read_qcd2_par_file(fname: Path) -> dict[str, dict[str, Any]]:
"""
The subroutines written here have names according to the openQCD programs and functions that write out the data.
Parameters
----------
fname: Path
Location of the parameter file.
Returns
-------
par_dict: dict
Dictionary holding the parameters specified in the given file.
"""
with open(fname, "rb") as fp:
lat_par_dict = flags.lat_parms_write_lat_parms(fp)
bc_par_dict = flags.lat_parms_write_bc_parms(fp)
fp.close()
par_dict = {}
par_dict["lat"] = lat_par_dict
par_dict["bc"] = bc_par_dict
return par_dict

17
corrlib/sql.py Normal file
View file

@ -0,0 +1,17 @@
import sqlite3
from .tools import get_db_file
from pathlib import Path
from typing import Any
def thin_sql_wrapper(path: Path, stmt: str) -> list[Any]:
db_file = get_db_file(path)
db = path / db_file
conn = sqlite3.connect(db)
c = conn.cursor()
c.execute(stmt)
results = c.fetchall()
conn.commit()
conn.close()
return results

View file

@ -158,6 +158,10 @@ def import_toml(path: Path, file: str, copy_file: bool=True) -> None:
copy_file: bool, optional copy_file: bool, optional
Whether the toml-files will be copied into the library. Default is True. Whether the toml-files will be copied into the library. Default is True.
""" """
if not os.path.exists(path):
raise FileNotFoundError(f"Corrlib path {path} does not exist.")
if not os.path.exists(file):
raise FileNotFoundError(f".toml-file {file} does not exist.")
print("Import project as decribed in " + file) print("Import project as decribed in " + file)
with open(file, 'rb') as fp: with open(file, 'rb') as fp:
toml_dict = toml.load(fp) toml_dict = toml.load(fp)
@ -178,8 +182,10 @@ def import_toml(path: Path, file: str, copy_file: bool=True) -> None:
update_aliases(path, uuid, aliases) update_aliases(path, uuid, aliases)
else: else:
uuid = import_project(path, project['url'], aliases=aliases) uuid = import_project(path, project['url'], aliases=aliases)
imeas = 1
nmeas = len(measurements.keys())
for mname, md in measurements.items(): for mname, md in measurements.items():
print("Import measurement: " + mname) print(f"Import measurement {imeas}/{nmeas}: {mname}")
ensemble = md['ensemble'] ensemble = md['ensemble']
if project['code'] == 'sfcf': if project['code'] == 'sfcf':
param = sfcf.read_param(path, uuid, md['param_file']) param = sfcf.read_param(path, uuid, md['param_file'])
@ -192,26 +198,49 @@ def import_toml(path: Path, file: str, copy_file: bool=True) -> None:
elif project['code'] == 'openQCD': elif project['code'] == 'openQCD':
if md['measurement'] == 'ms1': if md['measurement'] == 'ms1':
param = openQCD.read_ms1_param(path, uuid, md['param_file']) if 'param_file' in md.keys():
parameter_file = md['param_file']
if parameter_file.endswith(".ms1.in"):
param = openQCD.load_ms1_infile(path, uuid, parameter_file)
elif parameter_file.endswith(".ms1.par"):
param = openQCD.load_ms1_parfile(path, uuid, parameter_file)
else:
# Temporary solution
parameters: dict[str, Any] = {}
parameters["rand"] = {}
parameters["rw_fcts"] = [{}]
for nrw in range(1):
if "nsrc" not in parameters["rw_fcts"][nrw]:
parameters["rw_fcts"][nrw]["nsrc"] = 1
if "mu" not in parameters["rw_fcts"][nrw]:
parameters["rw_fcts"][nrw]["mu"] = "None"
if "np" not in parameters["rw_fcts"][nrw]:
parameters["rw_fcts"][nrw]["np"] = "None"
if "irp" not in parameters["rw_fcts"][nrw]:
parameters["rw_fcts"][nrw]["irp"] = "None"
param = parameters
param['type'] = 'ms1' param['type'] = 'ms1'
measurement = openQCD.read_rwms(path, uuid, md['path'], param, md["prefix"], version=md["version"], names=md['names'], files=md['files']) measurement = openQCD.read_rwms(path, uuid, md['path'], param, md["prefix"], version=md["version"], names=md['names'], files=md['files'])
elif md['measurement'] == 't0': elif md['measurement'] == 't0':
if 'param_file' in md: if 'param_file' in md:
param = openQCD.read_ms3_param(path, uuid, md['param_file']) param = openQCD.load_ms3_infile(path, uuid, md['param_file'])
else: else:
param = {} param = {}
for rwp in ["integrator", "eps", "ntot", "dnms"]: for rwp in ["integrator", "eps", "ntot", "dnms"]:
param[rwp] = "Unknown" param[rwp] = "Unknown"
param['type'] = 't0' 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'], 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', [])) 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': elif md['measurement'] == 't1':
if 'param_file' in md: if 'param_file' in md:
param = openQCD.read_ms3_param(path, uuid, md['param_file']) param = openQCD.load_ms3_infile(path, uuid, md['param_file'])
param['type'] = 't1' 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'], 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', [])) 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)) write_measurement(path, ensemble, measurement, uuid, project['code'], (md['param_file'] if 'param_file' in md else None))
imeas += 1
print(mname + " imported.") print(mname + " imported.")
if not os.path.exists(path / "toml_imports" / uuid): if not os.path.exists(path / "toml_imports" / uuid):

View file

@ -89,7 +89,8 @@ def set_config(path: Path, section: str, option: str, value: Any) -> None:
value: Any value: Any
The value we set the option to. The value we set the option to.
""" """
config_path = os.path.join(path, CONFIG_FILENAME) path = Path(path)
config_path = path / CONFIG_FILENAME
config = ConfigParser() config = ConfigParser()
if os.path.exists(config_path): if os.path.exists(config_path):
config.read(config_path) config.read(config_path)
@ -115,7 +116,10 @@ def get_db_file(path: Path) -> Path:
db_file: str db_file: str
The file holding the database. The file holding the database.
""" """
config_path = os.path.join(path, CONFIG_FILENAME) 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() config = ConfigParser()
if os.path.exists(config_path): if os.path.exists(config_path):
config.read(config_path) config.read(config_path)
@ -140,7 +144,8 @@ def cache_enabled(path: Path) -> bool:
cached_bool: bool cached_bool: bool
Whether the given library is cached. Whether the given library is cached.
""" """
config_path = os.path.join(path, CONFIG_FILENAME) path = Path(path)
config_path = path / CONFIG_FILENAME
config = ConfigParser() config = ConfigParser()
if os.path.exists(config_path): if os.path.exists(config_path):
config.read(config_path) config.read(config_path)

View file

@ -3,7 +3,7 @@ from configparser import ConfigParser
import datalad.api as dl import datalad.api as dl
from typing import Optional from typing import Optional
import shutil import shutil
from .tools import get_db_file from .tools import get_db_file, CONFIG_FILENAME
from pathlib import Path from pathlib import Path
@ -21,7 +21,8 @@ def get_tracker(path: Path) -> str:
tracker: str tracker: str
The tracker used in the dataset. The tracker used in the dataset.
""" """
config_path = os.path.join(path, '.corrlib') path = Path(path)
config_path = path / CONFIG_FILENAME
config = ConfigParser() config = ConfigParser()
if os.path.exists(config_path): if os.path.exists(config_path):
config.read(config_path) config.read(config_path)
@ -42,6 +43,7 @@ def get(path: Path, file: Path) -> None:
file: str file: str
The file to get. The file to get.
""" """
path = Path(path)
tracker = get_tracker(path) tracker = get_tracker(path)
if tracker == 'datalad': if tracker == 'datalad':
if file == get_db_file(path): if file == get_db_file(path):
@ -70,6 +72,7 @@ def save(path: Path, message: str, files: Optional[list[Path]]=None) -> None:
files: list[str], optional files: list[str], optional
The files to save. If None, all changes are saved. The files to save. If None, all changes are saved.
""" """
path = Path(path)
tracker = get_tracker(path) tracker = get_tracker(path)
if tracker == 'datalad': if tracker == 'datalad':
if files is not None: if files is not None:
@ -93,6 +96,7 @@ def init(path: Path, tracker: str='datalad') -> None:
tracker: str tracker: str
The tracker to use. Currently only 'datalad' and 'None' are supported. The tracker to use. Currently only 'datalad' and 'None' are supported.
""" """
path = Path(path)
if tracker == 'datalad': if tracker == 'datalad':
dl.create(path) dl.create(path)
elif tracker == 'None': elif tracker == 'None':
@ -113,6 +117,7 @@ def unlock(path: Path, file: Path) -> None:
file : str file : str
The file to unlock. The file to unlock.
""" """
path = Path(path)
tracker = get_tracker(path) tracker = get_tracker(path)
if tracker == 'datalad': if tracker == 'datalad':
dl.unlock(os.path.join(path, file), dataset=path) dl.unlock(os.path.join(path, file), dataset=path)
@ -136,6 +141,7 @@ def clone(path: Path, source: str, target: str) -> None:
target: str target: str
The target path to clone the dataset to. The target path to clone the dataset to.
""" """
path = Path(path)
tracker = get_tracker(path) tracker = get_tracker(path)
if tracker == 'datalad': if tracker == 'datalad':
dl.clone(target=target, source=source, dataset=path) dl.clone(target=target, source=source, dataset=path)
@ -159,6 +165,7 @@ def drop(path: Path, reckless: Optional[str]=None) -> None:
reckless: Optional[str] reckless: Optional[str]
The datalad's reckless option for dropping data. The datalad's reckless option for dropping data.
""" """
path = Path(path)
tracker = get_tracker(path) tracker = get_tracker(path)
if tracker == 'datalad': if tracker == 'datalad':
dl.drop(path, reckless=reckless) dl.drop(path, reckless=reckless)

View file

@ -1,5 +1,6 @@
# file generated by setuptools-scm # file generated by vcs-versioning
# don't change, don't track in version control # don't change, don't track in version control
from __future__ import annotations
__all__ = [ __all__ = [
"__version__", "__version__",
@ -10,25 +11,14 @@ __all__ = [
"commit_id", "commit_id",
] ]
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Tuple
from typing import Union
VERSION_TUPLE = Tuple[Union[int, str], ...]
COMMIT_ID = Union[str, None]
else:
VERSION_TUPLE = object
COMMIT_ID = object
version: str version: str
__version__: str __version__: str
__version_tuple__: VERSION_TUPLE __version_tuple__: tuple[int | str, ...]
version_tuple: VERSION_TUPLE version_tuple: tuple[int | str, ...]
commit_id: COMMIT_ID commit_id: str | None
__commit_id__: COMMIT_ID __commit_id__: str | None
__version__ = version = '0.2.4.dev71+g5e712b64c.d20260213' __version__ = version = '0.3.1.dev0+g08de17e6b.d20260507'
__version_tuple__ = version_tuple = (0, 2, 4, 'dev71', 'g5e712b64c.d20260213') __version_tuple__ = version_tuple = (0, 3, 1, 'dev0', 'g08de17e6b.d20260507')
__commit_id__ = commit_id = 'g5e712b64c' __commit_id__ = commit_id = 'g08de17e6b'

View file

@ -3,14 +3,23 @@ import sqlite3
from pathlib import Path from pathlib import Path
import corrlib.initialization as cinit import corrlib.initialization as cinit
import pytest import pytest
import pandas as pd
import datalad.api as dl
import datetime as dt
def make_sql(path: Path) -> Path: def make_sql(path: Path) -> Path:
db = path / "test.db" db = path / "backlogger.db"
cinit._create_db(db) cinit._create_db(db)
return db return db
def make_config(path: Path) -> None:
cinit._write_config(path, cinit._create_config(path, "datalad", False))
def test_find_lookup_by_one_alias(tmp_path: Path) -> None: def test_find_lookup_by_one_alias(tmp_path: Path) -> None:
make_config(tmp_path)
db = make_sql(tmp_path) db = make_sql(tmp_path)
conn = sqlite3.connect(db) conn = sqlite3.connect(db)
c = conn.cursor() c = conn.cursor()
@ -22,7 +31,7 @@ def test_find_lookup_by_one_alias(tmp_path: Path) -> None:
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))", c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(uuid, alias_str, tag_str, owner, code)) (uuid, alias_str, tag_str, owner, code))
conn.commit() conn.commit()
assert uuid == find._project_lookup_by_alias(db, "fun_project") assert uuid == find._project_lookup_by_alias(tmp_path, "fun_project")
uuid = "test_uuid2" uuid = "test_uuid2"
alias_str = "fun_project" alias_str = "fun_project"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))", c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
@ -31,3 +40,400 @@ def test_find_lookup_by_one_alias(tmp_path: Path) -> None:
with pytest.raises(Exception): with pytest.raises(Exception):
assert uuid == find._project_lookup_by_alias(db, "fun_project") assert uuid == find._project_lookup_by_alias(db, "fun_project")
conn.close() conn.close()
def test_find_lookup_by_id(tmp_path: Path) -> None:
make_config(tmp_path)
db = make_sql(tmp_path)
conn = sqlite3.connect(db)
c = conn.cursor()
uuid = "test_uuid"
alias_str = "fun_project"
tag_str = "tt"
owner = "tester"
code = "test_code"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(uuid, alias_str, tag_str, owner, code))
conn.commit()
conn.close()
result = find._project_lookup_by_id(tmp_path, uuid)[0]
assert uuid == result[0]
assert alias_str == result[1]
assert tag_str == result[2]
assert owner == result[3]
assert code == result[4]
def test_time_filter() -> None:
record_A = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf0", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966'] # only created
record_B = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf1", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-04-26 12:55:18.229966'] # created and updated
record_C = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf2", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2026-04-14 12:55:18.229966'] # created and updated later
record_D = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf3", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2026-03-27 12:55:18.229966']
record_E = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf4", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2024-03-26 12:55:18.229966', '2024-03-26 12:55:18.229966'] # only created, earlier
record_F = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf5", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2024-03-26 12:55:18.229966'] # this is invalid...
record_G = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf2", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', str(dt.datetime.now() + dt.timedelta(days=2, hours=3, minutes=5, seconds=30))] # created and updated later
data = [record_A, record_B, record_C, record_D, record_E]
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
df = pd.DataFrame(data,columns=cols)
results = find._time_filter(df, created_before='2023-03-26 12:55:18.229966')
assert results.empty
results = find._time_filter(df, created_before='2027-03-26 12:55:18.229966')
assert len(results) == 5
results = find._time_filter(df, created_before='2026-03-25 12:55:18.229966')
assert len(results) == 3
results = find._time_filter(df, created_before='2026-03-26 12:55:18.229965')
assert len(results) == 3
results = find._time_filter(df, created_before='2025-03-04 12:55:18.229965')
assert len(results) == 1
results = find._time_filter(df, created_after='2023-03-26 12:55:18.229966')
assert len(results) == 5
results = find._time_filter(df, created_after='2027-03-26 12:55:18.229966')
assert results.empty
results = find._time_filter(df, created_after='2026-03-25 12:55:18.229966')
assert len(results) == 2
results = find._time_filter(df, created_after='2026-03-26 12:55:18.229965')
assert len(results) == 2
results = find._time_filter(df, created_after='2025-03-04 12:55:18.229965')
assert len(results) == 4
results = find._time_filter(df, updated_before='2023-03-26 12:55:18.229966')
assert results.empty
results = find._time_filter(df, updated_before='2027-03-26 12:55:18.229966')
assert len(results) == 5
results = find._time_filter(df, updated_before='2026-03-25 12:55:18.229966')
assert len(results) == 3
results = find._time_filter(df, updated_before='2026-03-26 12:55:18.229965')
assert len(results) == 3
results = find._time_filter(df, updated_before='2025-03-04 12:55:18.229965')
assert len(results) == 1
results = find._time_filter(df, updated_after='2023-03-26 12:55:18.229966')
assert len(results) == 5
results = find._time_filter(df, updated_after='2027-03-26 12:55:18.229966')
assert results.empty
results = find._time_filter(df, updated_after='2026-03-25 12:55:18.229966')
assert len(results) == 2
results = find._time_filter(df, updated_after='2026-03-26 12:55:18.229965')
assert len(results) == 2
results = find._time_filter(df, updated_after='2025-03-04 12:55:18.229965')
assert len(results) == 4
data = [record_A, record_B, record_C, record_D, record_F]
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
df = pd.DataFrame(data,columns=cols)
with pytest.raises(ValueError):
results = find._time_filter(df, created_before='2023-03-26 12:55:18.229966')
data = [record_A, record_B, record_C, record_D, record_G]
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
df = pd.DataFrame(data,columns=cols)
with pytest.raises(ValueError):
results = find._time_filter(df, created_before='2023-03-26 12:55:18.229966')
def test_db_lookup(tmp_path: Path) -> None:
db = make_sql(tmp_path)
conn = sqlite3.connect(db)
c = conn.cursor()
corr = "f_A"
ensemble = "SF_A"
code = "openQCD"
meas_path = "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf"
uuid = "Project_A"
pars = "{par_A: 3.0, par_B: 5.0}"
parameter_file = "projects/Project_A/myinput.in"
c.execute("INSERT INTO backlogs (name, ensemble, code, path, project, parameters, parameter_file, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(corr, ensemble, code, meas_path, uuid, pars, parameter_file))
conn.commit()
results = find._db_lookup(db, ensemble, corr, code)
assert len(results) == 1
results = find._db_lookup(db, "SF_B", corr, code)
assert results.empty
results = find._db_lookup(db, ensemble, "g_A", code)
assert results.empty
results = find._db_lookup(db, ensemble, corr, "sfcf")
assert results.empty
results = find._db_lookup(db, ensemble, corr, code, project = "Project_A")
assert len(results) == 1
results = find._db_lookup(db, ensemble, corr, code, project = "Project_B")
assert results.empty
results = find._db_lookup(db, ensemble, corr, code, parameters = pars)
assert len(results) == 1
results = find._db_lookup(db, ensemble, corr, code, parameters = '{"par_A": 3.0, "par_B": 4.0}')
assert results.empty
corr = "g_A"
ensemble = "SF_A"
code = "openQCD"
meas_path = "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf"
uuid = "Project_A"
pars = '{"par_A": 3.0, "par_B": 4.0}'
parameter_file = "projects/Project_A/myinput.in"
c.execute("INSERT INTO backlogs (name, ensemble, code, path, project, parameters, parameter_file, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(corr, ensemble, code, meas_path, uuid, pars, parameter_file))
conn.commit()
corr = "f_A"
results = find._db_lookup(db, ensemble, corr, code)
assert len(results) == 1
results = find._db_lookup(db, "SF_B", corr, code)
assert results.empty
results = find._db_lookup(db, ensemble, "g_A", code)
assert len(results) == 1
results = find._db_lookup(db, ensemble, corr, "sfcf")
assert results.empty
results = find._db_lookup(db, ensemble, corr, code, project = "Project_A")
assert len(results) == 1
results = find._db_lookup(db, ensemble, "g_A", code, project = "Project_A")
assert len(results) == 1
results = find._db_lookup(db, ensemble, corr, code, project = "Project_B")
assert results.empty
results = find._db_lookup(db, ensemble, "g_A", code, project = "Project_B")
assert results.empty
results = find._db_lookup(db, ensemble, corr, code, parameters = pars)
assert results.empty
results = find._db_lookup(db, ensemble, "g_A", code, parameters = '{"par_A": 3.0, "par_B": 4.0}')
assert len(results) == 1
conn.close()
def test_sfcf_drop() -> None:
parameters0 = {
'offset': [0,0,0],
'quarks': [{'mass': 1, 'thetas': [0,0,0]}, {'mass': 2, 'thetas': [0,0,1]}], # m0s = -3.5, -3.75
'wf1': [[1, [0, 0]], [0.5, [1, 0]], [.75, [.5, .5]]],
'wf2': [[1, [2, 1]], [2, [0.5, -0.5]], [.5, [.75, .72]]],
}
assert not find._sfcf_drop(parameters0, offset=[0,0,0])
assert find._sfcf_drop(parameters0, offset=[1,0,0])
assert not find._sfcf_drop(parameters0, quark_kappas = [1, 2])
assert find._sfcf_drop(parameters0, quark_kappas = [-3.1, -3.72])
assert not find._sfcf_drop(parameters0, quark_masses = [-3.5, -3.75])
assert find._sfcf_drop(parameters0, quark_masses = [-3.1, -3.72])
assert not find._sfcf_drop(parameters0, qk1 = 1)
assert not find._sfcf_drop(parameters0, qk2 = 2)
assert find._sfcf_drop(parameters0, qk1 = 2)
assert find._sfcf_drop(parameters0, qk2 = 1)
assert not find._sfcf_drop(parameters0, qk1 = [0.5,1.5])
assert not find._sfcf_drop(parameters0, qk2 = [1.5,2.5])
assert find._sfcf_drop(parameters0, qk1 = 2)
assert find._sfcf_drop(parameters0, qk2 = 1)
with pytest.raises(ValueError):
assert not find._sfcf_drop(parameters0, qk1 = [0.5,1,5])
with pytest.raises(ValueError):
assert not find._sfcf_drop(parameters0, qk2 = [1,5,2.5])
assert find._sfcf_drop(parameters0, qm1 = 1.2)
assert find._sfcf_drop(parameters0, qm2 = 2.2)
assert not find._sfcf_drop(parameters0, qm1 = -3.5)
assert not find._sfcf_drop(parameters0, qm2 = -3.75)
assert find._sfcf_drop(parameters0, qm2 = 1.2)
assert find._sfcf_drop(parameters0, qm1 = 2.2)
with pytest.raises(ValueError):
assert not find._sfcf_drop(parameters0, qm1 = [0.5,1,5])
with pytest.raises(ValueError):
assert not find._sfcf_drop(parameters0, qm2 = [1,5,2.5])
def test_openQCD_filter() -> None:
record_0 = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_1 = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_2 = ["f_P", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_3 = ["f_P", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
data = [
record_0,
record_1,
record_2,
record_3,
]
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
df = pd.DataFrame(data,columns=cols)
with pytest.warns(Warning):
find.openQCD_filter(df, a = "asdf")
def test_code_filter() -> None:
record_0 = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_1 = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_2 = ["f_P", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_3 = ["f_P", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_4 = ["f_A", "ensA", "openQCD", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_5 = ["f_A", "ensA", "openQCD", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_6 = ["f_P", "ensA", "openQCD", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_7 = ["f_P", "ensA", "openQCD", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
record_8 = ["f_P", "ensA", "openQCD", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966']
data = [
record_0,
record_1,
record_2,
record_3,
]
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
df = pd.DataFrame(data,columns=cols)
res = find._code_filter(df, "sfcf")
assert len(res) == 4
data = [
record_4,
record_5,
record_6,
record_7,
record_8,
]
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
df = pd.DataFrame(data,columns=cols)
res = find._code_filter(df, "openQCD")
assert len(res) == 5
with pytest.raises(ValueError):
res = find._code_filter(df, "asdf")
def test_find_record() -> None:
assert True
def test_find_project(tmp_path: Path) -> None:
cinit.create(tmp_path)
db = tmp_path / "backlogger.db"
dl.unlock(str(db), dataset=str(tmp_path))
conn = sqlite3.connect(db)
c = conn.cursor()
uuid = "test_uuid"
alias_str = "fun_project"
tag_str = "tt"
owner = "tester"
code = "test_code"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(uuid, alias_str, tag_str, owner, code))
conn.commit()
assert uuid == find.find_project(tmp_path, "fun_project")
uuid = "test_uuid2"
alias_str = "fun_project"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(uuid, alias_str, tag_str, owner, code))
conn.commit()
with pytest.raises(Exception):
assert uuid == find._project_lookup_by_alias(tmp_path, "fun_project")
conn.close()
def test_list_projects(tmp_path: Path) -> None:
cinit.create(tmp_path)
db = tmp_path / "backlogger.db"
dl.unlock(str(db), dataset=str(tmp_path))
conn = sqlite3.connect(db)
c = conn.cursor()
uuid = "test_uuid"
alias_str = "fun_project"
tag_str = "tt"
owner = "tester"
code = "test_code"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(uuid, alias_str, tag_str, owner, code))
uuid = "test_uuid2"
alias_str = "fun_project2"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(uuid, alias_str, tag_str, owner, code))
uuid = "test_uuid3"
alias_str = "fun_project3"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(uuid, alias_str, tag_str, owner, code))
uuid = "test_uuid4"
alias_str = "fun_project4"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))",
(uuid, alias_str, tag_str, owner, code))
conn.commit()
conn.close()
results = find.list_projects(tmp_path)
assert len(results) == 4
for i in range(4):
assert len(results[i]) == 2

189
tests/integrity_test.py Normal file
View file

@ -0,0 +1,189 @@
import corrlib.integrity as integ
import corrlib.find as find
import datalad.api as dl
import corrlib.initialization as cinit
import sqlite3
from pathlib import Path
import os
import pandas as pd
import datetime as dt
import pytest
def test_list_ensembles(tmp_path: Path) -> None:
"""
Check against the implementation in find to check if they are the same.
"""
os.mkdir(tmp_path / 'archive')
os.mkdir(tmp_path / 'archive' / 'A')
os.mkdir(tmp_path / 'archive' / 'B')
os.mkdir(tmp_path / 'archive' / 'C')
integ_results = integ._list_ensembles(tmp_path)
assert len(integ_results) == 3
find_results = find.list_ensembles(tmp_path)
assert len(find_results) == 3
for f,i in zip(find_results, integ_results):
assert f == i
def test_list_projects(tmp_path: Path) -> None:
cinit.create(tmp_path)
db = tmp_path / "backlogger.db"
dl.unlock(str(db), dataset=str(tmp_path))
conn = sqlite3.connect(db)
c = conn.cursor()
customTags = ""
owner = "owner"
code = "sfcf"
created_at = "today"
updated_at = "today"
id = "asdf1"
aliases = "a1,s1,d1,f1"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?,?,?,?,?,?,?)", (id, aliases, customTags, owner, code , created_at, updated_at))
id = "asdf2"
aliases = "a2,s2,d2,f2"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?,?,?,?,?,?,?)", (id, aliases, customTags, owner, code , created_at, updated_at))
id = "asdf3"
aliases = "a3,s3,d3,f3"
c.execute("INSERT INTO projects (id, aliases, customTags, owner, code, created_at, updated_at) VALUES (?,?,?,?,?,?,?)", (id, aliases, customTags, owner, code , created_at, updated_at))
conn.commit()
conn.close
integ_results = integ._list_projects(tmp_path)
assert len(integ_results) == 3
find_results = find.list_projects(tmp_path)
assert len(find_results) == 3
for f,i in zip(find_results, integ_results):
assert f == i
def test_has_valid_time() -> None:
record_A = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf0", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966'] # only created
record_B = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf1", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-04-26 12:55:18.229966'] # created and updated
record_C = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf2", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2026-04-14 12:55:18.229966'] # created and updated later
record_D = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf3", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2026-03-27 12:55:18.229966']
record_E = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf4", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2024-03-26 12:55:18.229966', '2024-03-26 12:55:18.229966'] # only created, earlier
record_F = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf5", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2024-03-26 12:55:18.229966'] # this is invalid...
record_G = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf2", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', str(dt.datetime.now() + dt.timedelta(days=2, hours=3, minutes=5, seconds=30))] # created and updated later
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
data = [record_A, record_B, record_C, record_D, record_E]
df = pd.DataFrame(data,columns=cols)
for _, result in df.iterrows():
assert integ.has_valid_times(result)
data = [record_F, record_G]
df = pd.DataFrame(data,columns=cols)
for _, result in df.iterrows():
assert not integ.has_valid_times(result)
def test_are_keys_unique(tmp_path: Path) -> None:
db = tmp_path / 'test_success.db'
record_A = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf0", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966'] # only created
record_B = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf1", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-04-26 12:55:18.229966'] # created and updated
record_C = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf2", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2026-04-14 12:55:18.229966'] # created and updated later
record_D = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf3", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2026-03-27 12:55:18.229966']
record_E = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf4", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2024-03-26 12:55:18.229966', '2024-03-26 12:55:18.229966'] # only created, earlier
record_F = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf5", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2024-03-26 12:55:18.229966'] # this is invalid...
record_G = ["f_A", "ensA", "sfcf", "archive/SF_A/f_A/Project_A.json.gz::asdfasdfasdf2", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', str(dt.datetime.now() + dt.timedelta(days=2, hours=3, minutes=5, seconds=30))] # created and updated later
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
data = [record_A, record_B, record_C, record_D, record_E, record_F]
df = pd.DataFrame(data,columns=cols)
conn = sqlite3.connect(db)
df.to_sql('backlogs', conn)
conn.close()
assert integ.are_keys_unique(db, 'backlogs', 'path')
db = tmp_path / 'test_fail.db'
data = [record_A, record_B, record_C, record_D, record_E, record_F, record_G]
df = pd.DataFrame(data,columns=cols)
conn = sqlite3.connect(db)
df.to_sql('backlogs', conn)
conn.close()
assert not integ.are_keys_unique(db, 'backlogs', 'path')
def test_check_path_format() -> None:
record_A = ["f_A", "ensA", "sfcf", "archive/ensA/f_A/Project_A.json.gz::asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-03-26 12:55:18.229966'] # only created
record_B = ["f_A", "ensA", "sfcf", "archive/ensA/f_A/Project_B.json.gz::asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2025-03-26 12:55:18.229966', '2025-04-26 12:55:18.229966'] # created and updated
record_C = ["f_A", "ensA", "sfcf", "archive/ensA/f_A/Project_A.json.gz::asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2026-04-14 12:55:18.229966'] # created and updated later
record_D = ["f_A", "ensA", "sfcf", "archive/ensA/f_A/Project_B.json.gz::asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2026-03-27 12:55:18.229966']
record_E = ["f_A", "ensA", "sfcf", "archive/ensA/f_A/Project_A.json.gz::asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2024-03-26 12:55:18.229966', '2024-03-26 12:55:18.229966'] # only created, earlier
record_F = ["f_A", "ensA", "sfcf", "archive/ensA/f_A/Project_B.json.gz::asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', '2024-03-26 12:55:18.229966'] # this is invalid...
record_G = ["f_A", "ensA", "sfcf", "archive/ensA/f_A/Project_A.json.gz::asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfas", "SF_A", '{"par_A": 5.0, "par_B": 5.0}', "projects/SF_A/input.in",
'2026-03-26 12:55:18.229966', str(dt.datetime.now() + dt.timedelta(days=2, hours=3, minutes=5, seconds=30))] # created and updated later
projects = ['Project_A', 'Project_B']
ensembles = ['ensA']
cols = ["name",
"ensemble",
"code",
"path",
"project",
"parameters",
"parameter_file",
"created_at",
"updated_at"]
data = [record_A, record_B, record_C, record_D, record_E, record_F]
df = pd.DataFrame(data,columns=cols)
for _, result in df.iterrows():
integ.check_path_format(result, ensembles, projects)
projects = ['Project_A', 'Project_B']
ensembles = ['ensB']
for _, result in df.iterrows():
with pytest.raises(ValueError):
integ.check_path_format(result, ensembles, projects)
projects = ['Project_A', 'Project_B']
ensembles = ['ensA', 'ensB']
for _, result in df.iterrows():
integ.check_path_format(result, ensembles, projects)
data = [record_G]
df = pd.DataFrame(data,columns=cols)
for _, result in df.iterrows():
with pytest.raises(ValueError):
integ.check_path_format(result, ensembles, projects)

View file

@ -69,6 +69,8 @@ def test_get_db_file(tmp_path: Path) -> None:
# config is not yet available # config is not yet available
tl.set_config(tmp_path, section, option, value) tl.set_config(tmp_path, section, option, value)
assert tl.get_db_file(tmp_path) == Path("test_value") assert tl.get_db_file(tmp_path) == Path("test_value")
with pytest.raises(FileNotFoundError):
tl.get_db_file(tmp_path / "doesnotexist")
def test_cache_enabled(tmp_path: Path) -> None: def test_cache_enabled(tmp_path: Path) -> None:
@ -82,3 +84,5 @@ def test_cache_enabled(tmp_path: Path) -> None:
tl.set_config(tmp_path, section, option, "lalala") tl.set_config(tmp_path, section, option, "lalala")
with pytest.raises(ValueError): with pytest.raises(ValueError):
tl.cache_enabled(tmp_path) tl.cache_enabled(tmp_path)
with pytest.raises(FileNotFoundError):
tl.cache_enabled(tmp_path / "doesnotexist")