Source code for runners.runner_services

"""Base service methods for simulation runners."""

import logging
from pathlib import Path

from simtools.io_operations import io_handler

_logger = logging.getLogger(__name__)


[docs] class RunnerServices: """ Base services for simulation runners. Parameters ---------- corsika_config : CorsikaConfig Configuration parameters for CORSIKA. label : str Label. """ def __init__(self, corsika_config, label=None): """Initialize RunnerServices.""" self._logger = logging.getLogger(__name__) self._logger.debug("Init RunnerServices") self.label = label self.corsika_config = corsika_config self.directory = {} def _get_info_for_file_name(self, run_number): """ Return dictionary for building names for simulation output files. Parameters ---------- run_number : int Run number. Returns ------- dict Dictionary with the keys or building the file names for simulation output files. """ _vc_high = self.corsika_config.get_config_parameter("VIEWCONE")[1] primary_name = self.corsika_config.primary if primary_name == "gamma" and _vc_high > 0: primary_name = "gamma_diffuse" return { "run_number": self.corsika_config.validate_run_number(run_number), "primary": primary_name, "array_name": self.corsika_config.array_model.layout_name, "site": self.corsika_config.array_model.site, "label": self.label, "zenith": self.corsika_config.zenith_angle, "azimuth": self.corsika_config.azimuth_angle, } @staticmethod def _get_simulation_software_list(simulation_software): """ Return a list of simulation software based on the input string. Args: simulation_software: String representing the desired software. Returns ------- List of simulation software names. """ software_map = { "corsika": ["corsika"], "simtel": ["simtel"], "corsika_simtel": ["corsika", "simtel"], } return software_map.get(simulation_software.lower(), [])
[docs] def load_data_directories(self, simulation_software): """ Create and return directories for output, data, log and input. Parameters ---------- simulation_software : str Simulation software to be used. Returns ------- dict Dictionary containing paths requires for simulation configuration. """ self.directory["output"] = io_handler.IOHandler().get_output_directory(self.label) _logger.debug(f"Creating output dir {self.directory['output']}") for dir_name in ["sub_scripts", "sub_logs"]: self.directory[dir_name] = self.directory["output"].joinpath(dir_name) self.directory[dir_name].mkdir(parents=True, exist_ok=True) for _simulation_software in self._get_simulation_software_list(simulation_software): for dir_name in ["data", "inputs", "logs"]: self.directory[dir_name] = self.directory["output"].joinpath( _simulation_software, dir_name ) self.directory[dir_name].mkdir(parents=True, exist_ok=True) self._logger.debug(f"Data directories for {simulation_software}: {self.directory}") return self.directory
[docs] def has_file(self, file_type, run_number=None, mode="out"): """ Check that the file of file_type for the specified run number exists. Parameters ---------- file_type: str File type to check. run_number: int Run number. """ run_sub_file = self.get_file_name(file_type, run_number=run_number, mode=mode) _logger.debug(f"Checking if {run_sub_file} exists") return Path(run_sub_file).is_file()
def _get_file_basename(self, run_number): """ Get the base name for the simulation files. Parameters ---------- run_number: int Run number. Returns ------- str Base name for the simulation files. """ info_for_file_name = self._get_info_for_file_name(run_number) file_label = f"_{info_for_file_name['label']}" if info_for_file_name.get("label") else "" zenith = self.corsika_config.get_config_parameter("THETAP")[0] azimuth = self.corsika_config.azimuth_angle run_dir = self._get_run_number_string(info_for_file_name["run_number"]) return ( f"{run_dir}_{info_for_file_name['primary']}_" f"za{round(zenith):02}deg_azm{azimuth:03}deg_" f"{info_for_file_name['site']}_{info_for_file_name['array_name']}{file_label}" ) def _get_log_file_path(self, file_type, file_name): """ Return path for log files. Parameters ---------- file_type : str File type. file_name : str File name. Returns ------- Path Path for log files. """ log_suffixes = { "log": ".log.gz", "histogram": ".hdata.zst", } return self.directory["logs"].joinpath(f"{file_name}{log_suffixes[file_type]}") def _get_data_file_path(self, file_type, file_name, run_number): """ Return path for data files. Parameters ---------- file_type : str File type. file_name : str File name. run_number : int Run number. Returns ------- Path Path for data files. """ data_suffixes = { "output": ".zst", "corsika_output": ".zst", "corsika_log": ".log", "simtel_output": ".simtel.zst", } run_dir = self._get_run_number_string(run_number) data_run_dir = self.directory["data"].joinpath(run_dir) data_run_dir.mkdir(parents=True, exist_ok=True) return data_run_dir.joinpath(f"{file_name}{data_suffixes[file_type]}") def _get_sub_file_path(self, file_type, file_name, mode): """ Return path for submission files. Parameters ---------- file_type : str File type. file_name : str File name. mode : str Mode (out or err). Returns ------- Path Path for submission files. """ suffix = ".log" if file_type == "sub_log" else ".sh" if mode and mode != "": suffix = f".{mode}" sub_log_file_dir = self.directory["output"].joinpath(f"{file_type}s") sub_log_file_dir.mkdir(parents=True, exist_ok=True) return sub_log_file_dir.joinpath(f"sub_{file_name}{suffix}")
[docs] def get_file_name(self, file_type, run_number=None, mode=None): """ Get a CORSIKA/sim_telarray style file name for various log and data file types. Parameters ---------- file_type : str The type of file (determines the file suffix). run_number : int Run number. mode: str out or err (optional, relevant only for sub_log). Returns ------- str File name with full path. Raises ------ ValueError If file_type is unknown. """ file_name = self._get_file_basename(run_number) if file_type in ["log", "histogram"]: return self._get_log_file_path(file_type, file_name) if file_type in ["output", "corsika_output", "corsika_log", "simtel_output"]: return self._get_data_file_path(file_type, file_name, run_number) if file_type in ("sub_log", "sub_script"): return self._get_sub_file_path(file_type, file_name, mode) raise ValueError(f"The requested file type ({file_type}) is unknown")
@staticmethod def _get_run_number_string(run_number): """ Get run number string as used for the simulation file names(ex. run000014). Parameters ---------- run_number: int Run number. Returns ------- str Run number string. """ nn = str(run_number) if len(nn) > 6: raise ValueError("Run number cannot have more than 6 digits") return "run" + nn.zfill(6)
[docs] def get_resources(self, run_number=None): """ Read run time of job from last line of submission log file. Parameters ---------- run_number: int Run number. Returns ------- dict run time and number of simulated events """ sub_log_file = self.get_file_name(file_type="sub_log", run_number=run_number, mode="out") _logger.debug(f"Reading resources from {sub_log_file}") _resources = {} _resources["runtime"] = None with open(sub_log_file, encoding="utf-8") as file: for line in reversed(list(file)): if "RUNTIME" in line: _resources["runtime"] = int(line.split()[1]) break if _resources["runtime"] is None: _logger.debug("RUNTIME was not found in run log file") _resources["n_events"] = int(self.corsika_config.get_config_parameter("NSHOW")) return _resources