Source code for odatse._info

# SPDX-License-Identifier: MPL-2.0
#
# ODAT-SE -- an open framework for data analysis
# Copyright (C) 2020- The University of Tokyo
#
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.

from collections.abc import MutableMapping
from typing import Optional
from pathlib import Path
from fnmatch import fnmatch

from .util import toml
from . import mpi
from . import exception


[docs] class Info: """ A class to represent the information structure for the data-analysis software. """ base: dict algorithm: dict solver: dict runner: dict # perf_counter() timestamp recorded by odatse.initialize(), used as the # start of the "init" phase in time.log. None when the Info object is # constructed directly without going through initialize(). _start_time: Optional[float] = None
[docs] def __init__(self, d: Optional[MutableMapping] = None): """ Initialize the Info object. Parameters ---------- d : MutableMapping (optional) A dictionary to initialize the Info object. """ if d is not None: self.from_dict(d) else: self._cleanup()
[docs] def from_dict(self, d: MutableMapping) -> None: """ Initialize the Info object from a dictionary. Parameters ---------- d : MutableMapping A dictionary containing the information to initialize the Info object. Raises ------ exception.InputError If any required section is missing in the input dictionary. """ for section in ["base", "algorithm", "solver"]: if section not in d: raise exception.InputError( f"section {section} does not appear in input" ) self._cleanup() self.base = d["base"] self.algorithm = d["algorithm"] self.solver = d["solver"] self.runner = d.get("runner", {}) self.base["root_dir"] = ( Path(self.base.get("root_dir", ".")).expanduser().absolute() ) self.base["output_dir"] = ( self.base["root_dir"] / Path(self.base.get("output_dir", ".")).expanduser() )
[docs] def _cleanup(self) -> None: """ Reset the Info object to its default state. """ self.base = {} self.base["root_dir"] = Path(".").absolute() self.base["output_dir"] = self.base["root_dir"] self.algorithm = {} self.solver = {} self.runner = {}
[docs] @classmethod def from_file(cls, file_name, **kwargs): """ Create an Info object from a file. Parameters ---------- file_name : str The name of the file to load the information from. **kwargs Additional keyword arguments. Returns ------- Info An Info object initialized with the data from the file. Raises ------ TOMLDecodeError If the file is an invalid TOML document (raised on rank 0). exception.InputError On the other ranks, if the load failed on rank 0. Notes ----- Only rank 0 reads the file. The load status is broadcast *before* the parsed data so that a failure on rank 0 does not leave the other ranks blocked forever on the data broadcast. """ inp = {} if mpi.size() > 1: comm = mpi.comm() rank = mpi.rank() # Phase 1: rank 0 attempts the load; share the outcome with all # ranks. ``error_message`` is a plain (picklable) string so the # status broadcast itself can never fail and deadlock. error = None # original exception, rank 0 only error_message = None # status shared with every rank if rank == 0: try: inp = toml.load(file_name) except Exception as e: error = e error_message = f"{type(e).__name__}: {e}" error_message = comm.bcast(error_message, root=0) if error_message is not None: if rank == 0: raise error raise exception.InputError( f"failed to load '{file_name}' on rank 0: {error_message}" ) # Phase 2: broadcast the parsed data only when the load succeeded. inp = comm.bcast(inp, root=0) else: inp = toml.load(file_name) return cls(inp)