#
# Copyright 2024 John Harwell, All rights reserved.
#
# SPDX-License-Identifier: MIT
#
"""Functionality for generating root directory paths for a batch experiment.
See :ref:`concepts/run-time-tree` for details about the defined root
directories in SIERRA.
"""
# Core packages
import typing as tp
import logging
import pathlib
import argparse
from dataclasses import dataclass
# 3rd party packages
# Project packages
[docs]
class ExpRootLeaf:
"""
Representation of the "name" in pathlib parlance in :class:`ExpRoot`.
"""
[docs]
@staticmethod
def from_name(leaf: str) -> "ExpRootLeaf":
"""Parse the the directory name to extract leaf components.
"Name" here is pathlib parlance. Expected to be of the form::
<template stem>-[<criteria0>+<criteria1>]...
This function is the inverse of :func:`to_path()`.
"""
template_stem, bc_str = leaf.split("-")
bc = bc_str.split("+")
if not isinstance(bc, list): # Univariate batch criteria
bc = [bc]
return ExpRootLeaf(bc, template_stem)
[docs]
@staticmethod
def from_components(bc: list[str], template_stem: str) -> "ExpRootLeaf":
"""Create the leaf representation directory from the components."""
return ExpRootLeaf(bc, template_stem)
def __init__(self, bc: list[str], template_stem: str) -> None:
self.bc = bc
self.template_stem = template_stem
[docs]
def to_path(self) -> pathlib.Path:
"""Get the leaf as a ``pathlib.Path`` object."""
return pathlib.Path("{}-{}".format(self.template_stem, "+".join(self.bc)))
def to_str(self) -> str:
return str(self.to_path())
[docs]
class ExpRoot:
"""
Representation of the filesystem path containing all per-experiment data.
"""
def __init__(
self,
sierra_root: str,
project: str,
controller: str,
scenario: str,
leaf: ExpRootLeaf,
) -> None:
"""Generate the directory path for the rootbatch root directory.
The directory path depends on all of the input arguments to this
class, and if ANY of the arguments change, so will the generated
path. Root is::
<sierra_root>/<project>/<controller>/<scenario>/<criterias>/<template_basename>+<criterias>
Args:
root: The path to the root directory where SIERRA should store
everything.
project: The name of the project plugin used.
controller: The name of the controller used.
scenario: The name of the scenario used.
leaf: The batch criteria + ``--expdef-template`` stem.
"""
self.leaf = leaf
self.project = project
self.controller = controller
self.scenario = scenario
if self.controller is None:
raise RuntimeError("--controller must be specified")
if self.scenario is None:
raise RuntimeError("--scenario must be specified")
# Don't reslove() the path--that makes symlinked dirs under $HOME throw
# errors which are fatal from pathlib's POV, but actually harmless.
self.sierra_root = pathlib.Path(sierra_root)
def to_path(self) -> pathlib.Path:
return (
self.sierra_root
/ self.project
/ self.controller
/ self.scenario
/ self.leaf.to_path()
)
def to_str(self) -> str:
return str(self.to_path())
[docs]
@dataclass
class PathSet:
"""
The set of filesystem paths under ``--sierra-root`` that SIERRA uses.
Collected here in the interest of DRY.
"""
input_root: pathlib.Path
output_root: pathlib.Path
graph_root: pathlib.Path
model_root: pathlib.Path
model_interexp_root: pathlib.Path
stat_root: pathlib.Path
stat_exec_root: pathlib.Path
imagize_root: pathlib.Path
video_root: pathlib.Path
stat_interexp_root: pathlib.Path
graph_interexp_root: pathlib.Path
scratch_root: pathlib.Path
root: pathlib.Path
[docs]
@classmethod
def from_root(cls, root: ExpRoot) -> "PathSet":
"""Create PathSet from an ExpRoot."""
root_path = root.to_path()
model_root = root_path / "models"
stat_root = root_path / "statistics"
graph_root = root_path / "graphs"
return cls(
input_root=root_path / "exp-inputs",
output_root=root_path / "exp-outputs",
graph_root=graph_root,
model_root=model_root,
model_interexp_root=model_root / "inter-exp",
stat_root=stat_root,
stat_exec_root=stat_root / "exec",
imagize_root=root_path / "imagize",
video_root=root_path / "videos",
stat_interexp_root=stat_root / "inter-exp",
graph_interexp_root=graph_root / "inter-exp",
scratch_root=root_path / "scratch",
root=root_path,
)
def __str__(self) -> str:
"""
Convert the batch eperiment pathset to a GNU ``tree``-like format string.
No recursion. Everything is shown in a one-level breakdown relative to
the batch root. Could be improved by using recursion, but this is
sufficient for now.
"""
# pointers:
tee = "├── "
last = "└── "
contents = [
self.input_root,
self.output_root,
self.graph_root,
self.model_root,
self.stat_root,
self.stat_exec_root,
self.imagize_root,
self.video_root,
self.stat_interexp_root,
self.graph_interexp_root,
self.scratch_root,
]
contents.sort()
pointers = [tee] * (len(contents) - 1) + [last]
dirs = ""
for pointer, path in zip(pointers, contents):
dirs += f"\n{pointer}{path.relative_to(self.root)}"
return str(self.root.resolve()) + dirs
[docs]
def from_cmdline(args: argparse.Namespace) -> PathSet:
"""Generate directory paths directly from cmdline arguments."""
template = pathlib.Path(args.expdef_template)
# Remove all '-' from the template input file stem so we know the only '-'
# that are in it are ones that we put there.
template_stem = template.stem.replace("-", "")
batch_leaf = ExpRootLeaf(args.batch_criteria, str(template_stem))
return from_exp(
args.sierra_root, args.project, batch_leaf, args.controller, args.scenario
)
[docs]
def from_exp(
sierra_root: str,
project: str,
batch_leaf: ExpRootLeaf,
controller: str,
scenario: str,
) -> PathSet:
"""Regenerate directory pathroots from a batch experiment.
Args:
sierra_root: The value of ``--sierra-root``.
project: The value of ``--project``.
batch_leaf: The name of the directory that will be the root of the batch
experiment (not including its parent).
controller: The value of ``--controller``.
scenario: The value of ``--scenario``.
"""
root = ExpRoot(sierra_root, project, controller, scenario, batch_leaf)
logging.info("Generated batchroot %s", root.to_path())
return PathSet.from_root(root)
__all__ = [
"ExpRoot",
"ExpRootLeaf",
"PathSet",
"from_cmdline",
"from_exp",
]