Source code for lightwin.config.full_specs

"""Gather in a single object all the parameters for LW to run."""

import logging
from pathlib import Path
from typing import Any, Literal

from lightwin.beam_calculation.specs import (
    BEAM_CALCULATOR_MONKEY_PATCHES,
    BEAM_CALCULATORS_CONFIGS,
)
from lightwin.config.table_spec import TableConfSpec
from lightwin.core.beam_specs import BEAM_CONFIG, BeamTableConfSpec
from lightwin.core.files_specs import FILES_CONFIG, FilesTableConfSpec
from lightwin.evaluator.specs import EVALUATORS_CONFIG
from lightwin.optimisation.design_space_specs import DESIGN_SPACE_CONFIGS
from lightwin.optimisation.wtf_specs import WTF_CONFIGS, WTF_MONKEY_PATCHES
from lightwin.visualization.specs import PLOTS_CONFIG


[docs] class ConfSpec: """Define structure of a configuration object. Parameters ---------- MANDATORY_CONFIG_ENTRIES : Entries that you must declare for this :class:`ConfSpec` to work. """ MANDATORY_CONFIG_ENTRIES: tuple[str, ...] = ()
[docs] def __init__( self, files: str = "", beam: str = "", beam_calculator: str = "", beam_calculator_post: str = "", plots: str = "", evaluators: str = "", design_space: str = "", wtf: str = "", **kwargs, ) -> None: """Declare the attributes.""" table_of_specs = [] if files: table_of_specs.append( FilesTableConfSpec("files", files, FILES_CONFIG) ) if beam: table_of_specs.append(BeamTableConfSpec("beam", beam, BEAM_CONFIG)) if beam_calculator: table_of_specs.append( TableConfSpec( "beam_calculator", beam_calculator, BEAM_CALCULATORS_CONFIGS, selectkey_n_default=("tool", "Envelope1D"), monkey_patches=BEAM_CALCULATOR_MONKEY_PATCHES, ) ) if beam_calculator_post: table_of_specs.append( TableConfSpec( "beam_calculator_post", beam_calculator_post, BEAM_CALCULATORS_CONFIGS, selectkey_n_default=("tool", "Envelope1D"), monkey_patches=BEAM_CALCULATOR_MONKEY_PATCHES, ) ) if plots: table_of_specs.append(TableConfSpec("plots", plots, PLOTS_CONFIG)) if evaluators: table_of_specs.append( TableConfSpec("evaluators", evaluators, EVALUATORS_CONFIG) ) if design_space: table_of_specs.append( TableConfSpec( "design_space", design_space, DESIGN_SPACE_CONFIGS, selectkey_n_default=("from_file", True), ) ) if wtf: table_of_specs.append( TableConfSpec( "wtf", wtf, WTF_CONFIGS, selectkey_n_default=("strategy", "k out of n"), monkey_patches=WTF_MONKEY_PATCHES, ) ) self.tables_of_specs = tuple(table_of_specs)
def __repr__(self) -> str: """Print info on how object was instantiated.""" tables_info = ( [f"{self.__class__.__name__}("] + ["\t" + table.__repr__() for table in self.tables_of_specs] + [")"] ) return "\n".join(tables_info)
[docs] def _get_proper_table( self, table_id: str, id_type: Literal[ "configured_object", "table_entry" ] = "configured_object", ) -> TableConfSpec: """Get the :class:`.TableConfSpec` named ``table_id``. Parameters ---------- table_id : Name of the desired table. id_type : If ``table_id`` is the name of the object (eg ``'beam'``) or of the table entry in the ``TOML`` (eg ``'my_proton_beam'``, without brackets). Returns ------- TableConfSpec The desired object. """ for table in self.tables_of_specs: if table_id != getattr(table, id_type): continue return table raise ValueError( f"No table with {id_type} attribute = {table_id} found in " f"{self.__repr__()}." )
[docs] def to_toml_strings( self, toml_fulldict: dict[str, dict[str, Any]], id_type: Literal[ "configured_object", "table_entry" ] = "configured_object", original_toml_folder: Path | None = None, **kwargs, ) -> list[str]: """Convert the given dict in string that can be put in a ``TOML``. Parameters ---------- toml_fulldict : Holds the full configuration. id_type : If ``toml_fulldict`` keys are name of the object (eg ``'beam'``) or of the table entry in the ``TOML`` (eg ``'my_proton_beam'``, without brackets). original_toml_folder : Where the original ``TOML`` was; this is used to resolve paths relative to this location. Returns ------- list[str] The ``TOML`` content that can be directly written to a ``TOML`` file. """ strings = [] for key, val in toml_fulldict.items(): spec = self._get_proper_table(key, id_type=id_type) strings += spec.to_toml_strings( val, original_toml_folder=original_toml_folder, **kwargs ) return strings
[docs] def prepare( self, toml_fulldict: dict[str, dict[str, Any]], toml_folder: Path, id_type: Literal[ "configured_object", "table_entry" ] = "configured_object", **kwargs, ) -> bool: """Check that all the tables in ``toml_fulldict`` are valid. Also edit some values if necessary. Parameters ---------- toml_fulldict : Holds the full configuration. id_type : If ``toml_fulldict`` keys are name of the object (eg ``'beam'``) or of the table entry in the ``TOML`` (eg ``'my_proton_beam'``). Do not put the brackets present in the ``TOML`` file. Returns ------- all_is_validated : bool If the dict is valid or not. """ validations = [self._mandatory_keys_are_present] for table_name, toml_subdict in toml_fulldict.items(): spec = self._get_proper_table(table_name, id_type=id_type) validations.append( spec.prepare(toml_subdict, toml_folder=toml_folder, **kwargs) ) all_is_validated = all(validations) if not all_is_validated: logging.error( "At least one error was raised treating configuration" ) return all_is_validated
@property def _mandatory_keys_are_present(self) -> bool: """Ensure that all the mandatory parameters are defined.""" they_are_all_present = True for table_id in self.MANDATORY_CONFIG_ENTRIES: try: _ = self._get_proper_table( table_id, id_type="configured_object" ) except ValueError: logging.error( f"The table entry {table_id} should be given but was not " "found." ) they_are_all_present = False return they_are_all_present
[docs] def generate_dummy_dict( self, only_mandatory: bool = True ) -> dict[str, dict[str, Any]]: """Generate a default dummy dict that should let LightWin work.""" dummy_conf = { spec.table_entry: spec.generate_dummy_dict( only_mandatory=only_mandatory ) for spec in self.tables_of_specs if spec.is_mandatory or not only_mandatory } return dummy_conf
[docs] class SimplestConfSpec(ConfSpec): """Hold all the LightWin inputs, their types, allowed values, etc. Defined for a run without optimization. """ MANDATORY_CONFIG_ENTRIES = ( "beam", "files", "beam_calculator", ) #:
[docs] def __init__( self, *, beam: str = "beam", files: str = "files", beam_calculator: str = "beam_calculator", ) -> None: """Define static specifications. In the future, may add different mandatory specs, for example if failures are to be fixed or not. """ super().__init__( beam=beam, files=files, beam_calculator=beam_calculator, )