Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve runner API #296

Merged
merged 16 commits into from
Aug 9, 2024
Merged
2 changes: 1 addition & 1 deletion extras/matching/check-matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ def collect_data():
data = {}
pdf = toy.mkPDF("", 0)
for id in th_updates.keys():
with eko.EKO.open(f"./eko_{id}.tar") as evolution_operator:
with eko.EKO.read(f"./eko_{id}.tar") as evolution_operator:
x = evolution_operator.metadata.rotations.targetgrid.raw
data[id] = {
mu2: el["pdfs"]
Expand Down
2 changes: 1 addition & 1 deletion src/eko/io/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ClosedOperator(RuntimeError, exceptions.OutputError):
class AccessConfigs:
"""Configurations specified during opening of an EKO."""

path: Path
path: Optional[Path]
"""The path to the permanent object."""
readonly: bool
"Read-only flag"
Expand Down
3 changes: 1 addition & 2 deletions src/eko/io/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import dataclasses
import io
import os
import pathlib
import tarfile
import tempfile
Expand Down Expand Up @@ -32,7 +31,7 @@
_MT = 172.5


def load_tar(source: os.PathLike, dest: os.PathLike, errors: bool = False):
def load_tar(source: pathlib.Path, dest: pathlib.Path, errors: bool = False):
"""Load tar representation from file.

Compliant with :meth:`dump_tar` output.
Expand Down
2 changes: 1 addition & 1 deletion src/eko/io/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class Metadata(DictLike):
"""Interpolation grid"""
# tagging information
_path: Optional[pathlib.Path] = None
"""Path to temporary dir."""
"""Path to the open dir."""
version: str = vmod.__version__
"""Library version used to create the corresponding file."""
data_version: int = vmod.__data_version__
Expand Down
132 changes: 59 additions & 73 deletions src/eko/io/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
import contextlib
import copy
import logging
import os
import pathlib
import shutil
import tarfile
import tempfile
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional

import numpy as np
Expand All @@ -30,7 +29,7 @@
TEMP_PREFIX = "eko-"


def inventories(path: pathlib.Path, access: AccessConfigs) -> dict:
def inventories(path: Path, access: AccessConfigs) -> dict:
"""Set up empty inventories for object initialization."""
paths = InternalPaths(path)
return dict(
Expand Down Expand Up @@ -290,7 +289,7 @@ def unload(self):
# operator management
# -------------------

def deepcopy(self, path: os.PathLike):
def deepcopy(self, path: Path):
"""Create a deep copy of current instance.

The managed on-disk object is copied as well, to the new ``path``
Expand Down Expand Up @@ -324,115 +323,101 @@ def deepcopy(self, path: os.PathLike):
self.unload()

new = copy.deepcopy(self)
new.access.path = pathlib.Path(path)
new.access.path = path
new.access.readonly = False
new.access.open = True

tmpdir = pathlib.Path(tempfile.mkdtemp(prefix=TEMP_PREFIX))
tmpdir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX))
new.metadata.path = tmpdir
# copy old dir to new dir
tmpdir.rmdir()
shutil.copytree(self.paths.root, new.paths.root)
new.close()

@staticmethod
def load(tarpath: os.PathLike, dest: os.PathLike):
"""Load the content of archive in a target directory.
@classmethod
def load(cls, path: Path):
"""Load the EKO from disk information.

Parameters
----------
tarpath: os.PathLike
the archive to extract
tmppath: os.PathLike
the destination directory
Note
----
No archive path is assigned to the :class:`EKO` object, setting its
:attr:`EKO.access.path` to `None`.
If you want to properly load from an archive, use the :meth:`read`
constructor.

"""
try:
with tarfile.open(tarpath) as tar:
raw.safe_extractall(tar, dest)
except tarfile.ReadError:
raise exceptions.OutputNotTar(f"Not a valid tar archive: '{tarpath}'")
access = AccessConfigs(None, readonly=True, open=True)

@classmethod
def open(cls, path: os.PathLike, mode="r"):
"""Open EKO object in the specified mode."""
path = pathlib.Path(path)
access = AccessConfigs(path, readonly=False, open=True)
load = False
if mode == "r":
load = True
access.readonly = True
elif mode in "w":
pass
elif mode in "a":
load = True
else:
raise ValueError(f"Unknown file mode: {mode}")

tmpdir = pathlib.Path(tempfile.mkdtemp(prefix=TEMP_PREFIX))
if not load:
return Builder(path=tmpdir, access=access)
# load existing instead
cls.load(path, tmpdir)
metadata = Metadata.load(tmpdir)
opened: EKO = cls(
**inventories(tmpdir, access),
metadata = Metadata.load(path)
loaded = cls(
**inventories(path, access),
metadata=metadata,
access=access,
)
opened.operators.sync()
loaded.operators.sync()

return opened
return loaded

@classmethod
def read(cls, path: os.PathLike):
"""Read the content of an EKO.
def read(
cls,
path: Path,
extract: bool = True,
dest: Optional[Path] = None,
readonly: bool = True,
):
felixhekhorn marked this conversation as resolved.
Show resolved Hide resolved
"""Load an existing EKO.

If the `extract` attribute is `True` the EKO is loaded from its archived
format. Otherwise, the `path` is interpreted as the location of an
already extracted folder.

Type-safe alias for::

EKO.open(... , "r")

"""
eko = cls.open(path, "r")
assert isinstance(eko, EKO)
return eko
if extract:
dir_ = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX)) if dest is None else dest
with tarfile.open(path) as tar:
raw.safe_extractall(tar, dir_)
else:
dir_ = path

@classmethod
def create(cls, path: os.PathLike):
"""Create a new EKO.
loaded = cls.load(dir_)

Type-safe alias for::
loaded.access.readonly = readonly
if extract:
loaded.access.path = path

EKO.open(... , "w")
return loaded

"""
builder = cls.open(path, "w")
assert isinstance(builder, Builder)
@classmethod
def create(cls, path: Path):
"""Create a new EKO."""
access = AccessConfigs(path, readonly=False, open=True)
builder = Builder(
path=Path(tempfile.mkdtemp(prefix=TEMP_PREFIX)), access=access
)
return builder

@classmethod
def edit(cls, path: os.PathLike):
def edit(cls, *args, **kwargs):
"""Read from and write on existing EKO.

Type-safe alias for::

EKO.open(... , "a")
Alias of `EKO.read(..., readonly=False)`, see :meth:`read`.

"""
eko = cls.open(path, "a")
assert isinstance(eko, EKO)
return eko
return cls.read(*args, readonly=False, **kwargs)

def __enter__(self):
"""Allow EKO to be used in :obj:`with` statements."""
return self

def dump(self, archive: Optional[os.PathLike] = None):
def dump(self, archive: Optional[Path] = None):
"""Dump the current content to archive.

Parameters
----------
archive: os.PathLike or None
archive: Path or None
path to archive, in general you should keep the default, that will
make use of the registered path (default: ``None``)

Expand Down Expand Up @@ -471,7 +456,8 @@ def __exit__(self, exc_type: type, _exc_value, _traceback):
if exc_type is not None:
return

self.close()
if self.access.path is not None:
self.close()

@property
def raw(self) -> dict:
Expand All @@ -491,8 +477,8 @@ def raw(self) -> dict:
class Builder:
"""Build EKO instances."""

path: pathlib.Path
"""Path on disk to ."""
path: Path
"""Path on disk to the EKO."""
access: AccessConfigs
"""Access related configurations."""

Expand Down
4 changes: 2 additions & 2 deletions src/eko/runner/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Manage steps to DGLAP solution, and operator creation."""

import os
from pathlib import Path
from typing import Union

from ..io.runcards import OperatorCard, TheoryCard
Expand All @@ -15,7 +15,7 @@
def solve(
theory_card: Union[RawCard, TheoryCard],
operators_card: Union[RawCard, OperatorCard],
path: os.PathLike,
path: Path,
):
r"""Solve DGLAP equations in terms of evolution kernel operators (EKO).

Expand Down
4 changes: 2 additions & 2 deletions src/eko/runner/legacy.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Main application class of eko."""

import logging
import os
from pathlib import Path
from typing import Union

from ekomark.data import update_runcards
Expand Down Expand Up @@ -32,7 +32,7 @@ def __init__(
self,
theory_card: Union[RawCard, runcards.TheoryCard],
operators_card: Union[RawCard, runcards.OperatorCard],
path: os.PathLike,
path: Path,
):
"""Initialize runner.

Expand Down
18 changes: 5 additions & 13 deletions src/eko/runner/managed.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,31 @@

from pathlib import Path

from ..io.items import Evolution, Matching, Target
from ..io.items import Target
from ..io.runcards import OperatorCard, TheoryCard
from ..io.struct import EKO
from . import commons, operators, parts, recipes
from . import operators, parts, recipes


def solve(theory: TheoryCard, operator: OperatorCard, path: Path):
"""Solve DGLAP equations in terms of evolution kernel operators (EKO)."""
with EKO.create(path) as builder:
eko = builder.load_cards(theory, operator).build() # pylint: disable=E1101

atlas = commons.atlas(eko.theory_card, eko.operator_card)

recs = recipes.create(eko.operator_card.evolgrid, atlas)
eko.load_recipes(recs)
recipes.create(eko)

for recipe in eko.recipes:
assert isinstance(recipe, Evolution)
eko.parts[recipe] = parts.evolve(eko, recipe)
# flush the memory
del eko.parts[recipe]
for recipe in eko.recipes_matching:
assert isinstance(recipe, Matching)
eko.parts_matching[recipe] = parts.match(eko, recipe)
# flush the memory
del eko.parts_matching[recipe]

for ep in operator.evolgrid:
headers = recipes.elements(ep, atlas)
parts_ = operators.retrieve(headers, eko.parts, eko.parts_matching)
components = operators.retrieve(ep, eko)
target = Target.from_ep(ep)
eko.operators[target] = operators.join(parts_)
eko.operators[target] = operators.join(components)
# flush the memory
del eko.parts
del eko.parts_matching
del eko.operators[target]
Loading
Loading