Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/eko/io/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,27 @@ def load(cls, path: os.PathLike):
# patch if necessary
if data_version == 1:
if version.major == 0 and version.minor == 13:
raw = v1.update_metadata(paths, raw)
raw = v1.update_metadata(raw)
elif version.major == 0 and version.minor == 14:
raw = v2.update_metadata(paths, raw)
raw = v2.update_metadata(raw)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to reuse the new function of course


# now we are ready
content = cls.from_dict(raw)
content._path = path
return content

@classmethod
def from_raw(cls, raw: dict) -> "Metadata":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata legacy upgrade

yeah, seems that the update_metadata s take paths - but if you look to the function body they don't do anything with it so I'd say we can just remove them from there, because we definitely need patches here

"""Build metadata from raw yaml, applying legacy patches."""
version = parse(raw["version"])
data_version = int(raw["data_version"])
if data_version == 1:
if version.major == 0 and version.minor == 13:
raw = v1.update_metadata(raw)
elif version.major == 0 and version.minor == 14:
raw = v2.update_metadata(raw)
return cls.from_dict(raw)

def update(self):
"""Update the disk copy of metadata."""
if self._path is None:
Expand Down
58 changes: 58 additions & 0 deletions src/eko/io/runcards.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
squared value, for consistency. Squares are consistently taken inside.
"""

import pathlib
import tarfile
from dataclasses import dataclass
from math import nan
from typing import List, Optional, Union

import numpy as np
import numpy.typing as npt
import yaml

from .. import basis_rotation as br
from .. import interpolation, msbar_masses
Expand All @@ -19,7 +22,10 @@
from ..quantities import heavy_quarks as hq
from ..quantities.couplings import CouplingsInfo
from ..quantities.heavy_quarks import HeavyInfo, QuarkMassScheme
from . import v1, v2
from .dictlike import DictLike
from .metadata import Metadata
from .paths import METADATAFILE, OPERATORFILE, THEORYFILE
from .types import (
EvolutionMethod,
InversionMethod,
Expand Down Expand Up @@ -78,6 +84,15 @@ def __post_init__(self):
if self.matching_order is None:
self.matching_order = (self.order[0] - 1, 0)

@classmethod
def from_raw(cls, raw: dict, data_version: int) -> "TheoryCard":
"""Build a theory card from a raw dictionary."""
if data_version == 1:
raw = v1.update_theory(raw)
if data_version == 2:
raw = v2.update_theory(raw)
return cls.from_dict(raw)


@dataclass
class Debug(DictLike):
Expand Down Expand Up @@ -170,6 +185,15 @@ def pids(self):
"""Internal flavor basis, used for computation."""
return np.array(br.flavor_basis_pids)

@classmethod
def from_raw(cls, raw: dict, data_version: int, theory_raw) -> "OperatorCard":
"""Build an operator card from a raw dictionary."""
if data_version == 1:
raw = v1.update_operator(raw, theory_raw)
if data_version == 2:
raw = v2.update_operator(raw, theory_raw)
return cls.from_dict(raw)


Card = Union[TheoryCard, OperatorCard]

Expand Down Expand Up @@ -343,3 +367,37 @@ def masses(theory: TheoryCard, evmeth: EvolutionMethod) -> List[SquaredScale]:
return [mq.value**2 for mq in theory.heavy.masses]

raise ValueError(f"Unknown mass scheme '{theory.heavy.masses_scheme}'")


def _read_yaml_members(tar, filenames):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the last commit you removed types, e.g. -> dict, but please keep them (and consider adding more)

"""Extract and parse several yaml files in a single pass."""
wanted = set(filenames)
found = {}
for member in tar.getmembers():

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this "costless". If it is, then this is fine.

Otherwise, this function could take a list of filenames so that one can extract all of the files we need in one go.

name = pathlib.Path(member.name).name
if name in wanted:
extracted = tar.extractfile(member)
if extracted is not None:
found[name] = yaml.safe_load(extracted.read())
missing = wanted - found.keys()
if missing:
raise KeyError(f"{missing} not found in {tar.name}")
return found


def read_eko_cards(eko_path):
"""Read metadata, theory and operator cards from an EKO archive.

The (large) operators are never extracted, only the small yaml files.
"""
with tarfile.open(eko_path) as tar:
raw_files = _read_yaml_members(tar, [METADATAFILE, THEORYFILE, OPERATORFILE])

raw_meta = raw_files[METADATAFILE]
raw_theory = raw_files[THEORYFILE]
raw_operator = raw_files[OPERATORFILE]
metadata = Metadata.from_raw(raw_meta)
data_version = metadata.data_version
operator = OperatorCard.from_raw(raw_operator, data_version, raw_theory)
theory = TheoryCard.from_raw(raw_theory, data_version)
return metadata, theory, operator
19 changes: 4 additions & 15 deletions src/eko/io/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import yaml

from .. import interpolation
from . import exceptions, raw, v1, v2
from . import exceptions, raw
from .access import AccessConfigs
from .inventory import Inventory
from .items import Evolution, Matching, Operator, Recipe, Target
Expand Down Expand Up @@ -133,25 +133,14 @@ def evolgrid(self) -> List[EPoint]:
def theory_card(self):
"""Provide theory card, retrieving from the dump."""
raw_th = yaml.safe_load(self.paths.theory_card.read_text(encoding="utf-8"))
if self.metadata.data_version in [1]:
raw_th = v1.update_theory(raw_th)
if self.metadata.data_version in [2]:
raw_th = v2.update_theory(raw_th)
return TheoryCard.from_dict(raw_th)
return TheoryCard.from_raw(raw_th, self.metadata.data_version)

@property
def operator_card(self):
"""Provide operator card, retrieving from the dump."""
raw_op = yaml.safe_load(self.paths.operator_card.read_text(encoding="utf-8"))
if self.metadata.data_version in [1]:
# here we need to read also the theory card
raw_th = yaml.safe_load(self.paths.theory_card.read_text(encoding="utf-8"))
raw_op = v1.update_operator(raw_op, raw_th)
if self.metadata.data_version in [2]:
# here we need to read also the theory card
raw_th = yaml.safe_load(self.paths.theory_card.read_text(encoding="utf-8"))
raw_op = v2.update_operator(raw_op, raw_th)
return OperatorCard.from_dict(raw_op)
raw_th = yaml.safe_load(self.paths.theory_card.read_text(encoding="utf-8"))
return OperatorCard.from_raw(raw_op, self.metadata.data_version, raw_th)

# persistency control
# -------------------
Expand Down
4 changes: 1 addition & 3 deletions src/eko/io/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
that API version.
"""

from .paths import InternalPaths


def update_metadata(paths: InternalPaths, raw: dict) -> dict:
def update_metadata(raw: dict) -> dict:
"""Modify the raw metadata to the new format.

Parameters
Expand Down
4 changes: 1 addition & 3 deletions src/eko/io/v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
exactly that API version.
"""

from .paths import InternalPaths


def update_metadata(paths: InternalPaths, raw: dict) -> dict:
def update_metadata(raw: dict) -> dict:
"""Modify the raw metadata to the new format.

Parameters
Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def operator_card():
return card


@pytest.fixture
def legacy_eko_filenames():
return ["v1-0.13.tar", "v1-0.14.tar", "v3.tar"]
Comment on lines +72 to +73

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The "legacy" in the name is not correct since it contains both legacy and current
  2. rather indicate (some way) that they are actual real objects (and not mocked)
  3. of course the path should be here (otherwise it is repeated)



class EKOFactory:
def __init__(self, theory: TheoryCard, operator: OperatorCard, path: os.PathLike):
self.path = path
Expand Down
4 changes: 2 additions & 2 deletions tests/eko/io/test_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
EP = (10000.0, 4)


def test_read_legacy():
for name in ["v1-0.13.tar", "v1-0.14.tar", "v3.tar"]:
def test_read_legacy(legacy_eko_filenames):
for name in legacy_eko_filenames:
with eko.EKO.read(TEST_DATA_DIR / name) as evolution_operator:
# Check the cards
assert isinstance(
Expand Down
14 changes: 14 additions & 0 deletions tests/eko/io/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from eko import EKO, interpolation
from eko.io import struct
from eko.io.items import Target
from eko.io.metadata import Metadata
from eko.io.runcards import OperatorCard, TheoryCard, read_eko_cards
from tests.conftest import EKOFactory


Expand Down Expand Up @@ -197,6 +199,18 @@ def test_load_opened(self, tmp_path: pathlib.Path, eko_factory: EKOFactory):

assert read_closed.metadata == read_opened.metadata

def test_read_eko_cards(self, legacy_eko_filenames):
"""Load metadata and both YAML cards from legacy EKO archives."""
data_dir = pathlib.Path(__file__).parents[2] / "data"
for filename in legacy_eko_filenames:
metadata, theory, operator = read_eko_cards(
data_dir / filename
)

assert isinstance(metadata, Metadata)
assert isinstance(theory, TheoryCard)
assert isinstance(operator, OperatorCard)

def test_version(self, tmp_path: pathlib.Path, eko_factory: EKOFactory):
"""Test asserted version. Should either be supported version, or have a postrelease addition"""
eko = eko_factory.get()
Expand Down