From f8eb45607422edf275451b383f783dc77dedf5ec Mon Sep 17 00:00:00 2001 From: Felix Hekhorn Date: Fri, 29 Jul 2022 16:44:20 +0200 Subject: [PATCH] Complete most tests --- src/eko/compatibility.py | 7 +- src/eko/output/struct.py | 33 +++------ src/eko/runner.py | 4 +- tests/eko/test_compatibility.py | 7 +- tests/eko/test_output_struct.py | 124 +++++++++++++++++++++++++++++++- tests/eko/test_runner.py | 1 + 6 files changed, 140 insertions(+), 36 deletions(-) diff --git a/src/eko/compatibility.py b/src/eko/compatibility.py index baa76fbff..86a89f9ca 100644 --- a/src/eko/compatibility.py +++ b/src/eko/compatibility.py @@ -32,7 +32,7 @@ def update(theory: dict, operators: Optional[dict]): if "alphaqed" in new_theory: new_theory["alphaem"] = new_theory.pop("alphaqed") if "QED" in new_theory: - new_theory["order"] = (new_theory.pop("PTO") + 1, new_theory.pop("QED")) + new_theory["order"] = [new_theory.pop("PTO") + 1, new_theory.pop("QED")] if operators is not None and "configs" not in operators: new_operators["configs"] = {} @@ -51,14 +51,15 @@ def update(theory: dict, operators: Optional[dict]): max_order = operators["ev_op_max_order"] if isinstance(max_order, int): - new_operators["configs"]["ev_op_max_order"] = ( + new_operators["configs"]["ev_op_max_order"] = [ max_order, new_theory["order"][1], - ) + ] new_operators["rotations"]["xgrid"] = operators["interpolation_xgrid"] for basis in ("inputgrid", "targetgrid", "inputpids", "targetpids"): new_operators["rotations"][f"{basis}"] = operators[basis] + new_operators["Q0"] = new_theory["Q0"] return new_theory, new_operators diff --git a/src/eko/output/struct.py b/src/eko/output/struct.py index eb9d20fa8..239336f3e 100644 --- a/src/eko/output/struct.py +++ b/src/eko/output/struct.py @@ -363,13 +363,11 @@ def __post_init__(self): """Validate class members.""" if self.path.suffix != ".tar": raise ValueError("Not a valid path for an EKO") - if not tarfile.is_tarfile(self.path): - raise ValueError("EKO: the corresponding file is not a valid tar archive") @staticmethod def opname(q2: float) -> str: """Operator file name from :math:`Q^2` value.""" - return f"operators/{q2:8.2f}" + return f"{OPERATORSDIR}/{q2:8.2f}" def __getitem__(self, q2: float) -> Operator: """Retrieve operator for given :math:`Q^2`. @@ -379,7 +377,7 @@ def __getitem__(self, q2: float) -> Operator: Parameters ---------- - q2: float + q2 : float :math:`Q^2` value labeling the operator to be retrieved Returns @@ -388,9 +386,10 @@ def __getitem__(self, q2: float) -> Operator: the retrieved operator """ - op = self._operators[q2] - if op is not None: - return op + if q2 in self._operators: + op = self._operators[q2] + if op is not None: + return op with tarfile.open(self.path) as tar: names = list( @@ -399,18 +398,11 @@ def __getitem__(self, q2: float) -> Operator: if len(names) == 0: raise ValueError(f"Q2 value '{q2}' not available in '{self.path}'") - if len(names) > 1: - raise ValueError( - f"Q2 value '{q2}' occurs multiple times in '{self.path}'" - ) name = names[0] compressed = name.endswith(".lz4") stream = tar.extractfile(name) - if stream is None: - raise ValueError - op = Operator.load(stream, compressed=compressed) self._operators[q2] = op @@ -686,10 +678,6 @@ def extract(path: os.PathLike, filename: str) -> str: with tarfile.open(path, "r") as tar: fd = tar.extractfile(filename) - if fd is None: - raise ValueError( - f"The member '{filename}' is not a readable file inside EKO tar" - ) content = fd.read().decode() return content @@ -763,6 +751,9 @@ def detached(cls, theory: dict, operator: dict, path: pathlib.Path): """ bases = operator["rotations"] + for basis in ("inputgrid", "targetgrid", "inputpids", "targetpids"): + bases[f"_{basis}"] = bases[basis] + del bases[basis] bases["pids"] = np.array(br.flavor_basis_pids) for k in ("xgrid", "_inputgrid", "_targetgrid"): if operator["rotations"][k] is None: @@ -834,10 +825,6 @@ def new(cls, theory: dict, operator: dict, path: Optional[os.PathLike] = None): shutil.rmtree(td) - for basis in ("inputgrid", "targetgrid", "inputpids", "targetpids"): - operator["rotations"][f"_{basis}"] = operator["rotations"][basis] - del operator["rotations"][basis] - eko = cls.detached(theory, operator, path=path) logger.info(f"New operator created at path '{path}'") return eko @@ -882,7 +869,7 @@ def raw(self) -> dict: return dict( path=str(self.path), Q0=float(np.sqrt(self.Q02)), - Q2grid=self.Q2grid, + Q2grid=self.Q2grid.tolist(), configs=self.configs.raw, rotations=self.rotations.raw, debug=self.debug.raw, diff --git a/src/eko/runner.py b/src/eko/runner.py index 8c3a364d5..37f37a3e6 100644 --- a/src/eko/runner.py +++ b/src/eko/runner.py @@ -82,9 +82,7 @@ def __init__(self, theory_card: dict, operators_card: dict): self.post_process[key] = rot.get(key, None) new_operators["rotations"][key] = None - self.out = EKO.new( - theory=theory_card, operator=dict(Q0=np.sqrt(tc.q2_ref), **new_operators) - ) + self.out = EKO.new(theory=theory_card, operator=new_operators) def get_output(self) -> EKO: """Run evolution and generate output operator. diff --git a/tests/eko/test_compatibility.py b/tests/eko/test_compatibility.py index 8bc46d3f0..1a9de4d0b 100644 --- a/tests/eko/test_compatibility.py +++ b/tests/eko/test_compatibility.py @@ -1,12 +1,7 @@ # -*- coding: utf-8 -*- from eko import compatibility -theory1 = { - "alphas": 0.1180, - "alphaqed": 0.007496, - "PTO": 2, - "QED": 0, -} +theory1 = {"alphas": 0.1180, "alphaqed": 0.007496, "PTO": 2, "QED": 0, "Q0": 1.0} def test_compatibility(): diff --git a/tests/eko/test_output_struct.py b/tests/eko/test_output_struct.py index 9738fecb8..ea8324c2d 100644 --- a/tests/eko/test_output_struct.py +++ b/tests/eko/test_output_struct.py @@ -7,8 +7,10 @@ import pytest import yaml -from eko import interpolation, output +from eko import compatibility, interpolation, output from eko.output import struct +from ekobox import operators_card as oc +from ekobox import theory_card as tc @dataclass @@ -125,6 +127,126 @@ def test_init(self): assert r.inputgrid == interpolation.XGrid.load(ixg) +class TestEKO: + def _default_cards(self): + t = tc.generate(0, 1.0) + o = oc.generate([10.0]) + return compatibility.update(t, o) + + def test_new_error(self, tmp_path): + nt, no = self._default_cards() + # try to write to a file different from bla + no_tar_path = tmp_path / "Blub.bla" + with pytest.raises(ValueError): + struct.EKO.new(nt, no, no_tar_path) + # try to overwrite an existing file + exists_path = tmp_path / "Blub.tar" + exists_path.write_text("Blub", encoding="utf-8") + with pytest.raises(FileExistsError): + struct.EKO.new(nt, no, exists_path) + + def test_load_error(self, tmp_path): + # try to read from a non-tar path + no_tar_path = tmp_path / "Blub.tar" + no_tar_path.write_text("Blub", encoding="utf-8") + with pytest.raises(ValueError): + struct.EKO.load(no_tar_path) + + def test_properties(self): + eko = struct.EKO.new(*self._default_cards()) + assert "mc" in eko.theory_card + assert "debug" in eko.operator_card + np.testing.assert_allclose(eko.Q2grid, np.array([10.0])) + assert 10.0 in eko + default_grid = interpolation.XGrid(eko.operator_card["rotations"]["xgrid"]) + assert eko.xgrid == default_grid + for use_target in (True, False): + assert eko.interpolator(False, use_target).xgrid == default_grid + xg = interpolation.XGrid([0.1, 1.0]) + eko.xgrid = xg + assert eko.xgrid == xg + assert "debug" in eko.raw + # check we can dump and reload + stream = io.StringIO() + yaml.safe_dump(eko.raw, stream) + stream.seek(0) + raw_eko = yaml.safe_load(stream) + assert "debug" in raw_eko + + def test_ops(self): + v = np.random.rand(2, 2) + opv = struct.Operator(operator=v) + eko = struct.EKO.new(*self._default_cards()) + # try setting not an operator + with pytest.raises(ValueError): + eko[10.0] = "bla" + # approx + eko[10.0] = opv + assert eko.approx(20.0) is None + assert eko.approx(11.0, atol=2) == 10.0 + eko[11.0] = opv + with pytest.raises(ValueError): + eko.approx(10.5, atol=2) + # iterate + for q2, q2eko in zip((10.0, 11.0), eko): + assert q2 == q2eko + np.testing.assert_allclose(v, eko[q2].operator) + for q2, (q2eko, op) in zip((10.0, 11.0), eko.items()): + assert q2 == q2eko + np.testing.assert_allclose(v, op.operator) + # getter + with pytest.raises(ValueError): + eko[12.0] + with eko.operator(10.0) as op: + np.testing.assert_allclose(v, op.operator) + # overwrite + vv = np.random.rand(2, 2) + opvv = struct.Operator(operator=vv) + eko[11.0] = opvv + np.testing.assert_allclose(vv, eko[11.0].operator) + + def test_interpolator(self): + nt, no = self._default_cards() + txg = np.geomspace(0.1, 1.0, 5) + ixg = np.geomspace(0.01, 1.0, 5) + no["rotations"]["targetgrid"] = txg + no["rotations"]["inputgrid"] = ixg + eko = struct.EKO.new(nt, no) + assert eko.interpolator(False, True).xgrid == interpolation.XGrid(txg) + assert eko.interpolator(False, False).xgrid == interpolation.XGrid(ixg) + + def test_copy(self, tmp_path): + v = np.random.rand(2, 2) + opv = struct.Operator(operator=v) + eko1 = struct.EKO.new(*self._default_cards()) + eko1[10.0] = opv + np.testing.assert_allclose(eko1[10.0].operator, v) + p = tmp_path / "eko2.tar" + eko2 = eko1.deepcopy(p) + np.testing.assert_allclose(eko1[10.0].operator, v) + np.testing.assert_allclose(eko2[10.0].operator, v) + vv = np.random.rand(2, 2) + opvv = struct.Operator(operator=vv) + eko2[10.0] = opvv + np.testing.assert_allclose(eko1[10.0].operator, v) + np.testing.assert_allclose(eko2[10.0].operator, vv) + # try loading again + eko2_ = struct.EKO.load(p) + assert eko2.raw == eko2_.raw + + def test_extract(self, tmp_path): + p = tmp_path / "test.tar" + eko = struct.EKO.new(*self._default_cards(), p) + # check theory file + t = struct.EKO.extract(p, struct.THEORYFILE) + assert isinstance(t, str) + tt = yaml.safe_load(io.StringIO(t)) + assert tt == eko.theory_card + # try a wrong file + with pytest.raises(KeyError): + t = struct.EKO.extract(p, "Blub.bla") + + class TestLegacy: def test_items(self, fake_output): """Test autodump, autoload, and manual unload.""" diff --git a/tests/eko/test_runner.py b/tests/eko/test_runner.py index 796556b95..c1d491022 100644 --- a/tests/eko/test_runner.py +++ b/tests/eko/test_runner.py @@ -38,6 +38,7 @@ } operators_card = { "Q2grid": [10, 100], + "Q0": np.sqrt(2), "configs": { "interpolation_polynomial_degree": 1, "interpolation_is_log": True,