Source code for runners.simtel_runner
"""Base class for running sim_telarray simulations."""
import logging
import os
from pathlib import Path
import simtools.utils.general as gen
from simtools.runners.runner_services import RunnerServices
__all__ = ["InvalidOutputFileError", "SimtelExecutionError", "SimtelRunner"]
[docs]
class SimtelExecutionError(Exception):
"""Exception for simtel_array execution error."""
[docs]
class InvalidOutputFileError(Exception):
"""Exception for invalid output file."""
[docs]
class SimtelRunner:
"""
Base class for running sim_telarray simulations.
Parameters
----------
simtel_path: str or Path
Location of sim_telarray installation.
label: str
Instance label. Important for output file naming.
"""
def __init__(self, simtel_path, label=None, corsika_config=None, use_multipipe=False):
"""Initialize SimtelRunner."""
self._logger = logging.getLogger(__name__)
self._simtel_path = Path(simtel_path)
self.label = label
self._base_directory = None
self.runs_per_set = 1
self.runner_service = RunnerServices(corsika_config, label)
self._directory = self.runner_service.load_data_directories(
"corsika_simtel" if use_multipipe else "simtel"
)
def __repr__(self):
"""Return a string representation of the SimtelRunner object."""
return f"SimtelRunner(label={self.label})\n"
[docs]
def prepare_run_script(self, test=False, input_file=None, run_number=None, extra_commands=None):
"""
Build and return the full path of the bash run script containing the sim_telarray command.
Parameters
----------
test: bool
Test flag for faster execution.
input_file: str or Path
Full path of the input CORSIKA file.
run_number: int
Run number.
extra_commands: str
Additional commands for running simulations given in config.yml.
Returns
-------
Path
Full path of the run script.
"""
self._logger.debug("Creating run bash script")
script_file_path = self.get_file_name(file_type="sub_script", run_number=run_number)
self._logger.debug(f"Run bash script - {script_file_path}")
self._logger.debug(f"Extra commands to be added to the run script {extra_commands}")
command = self._make_run_command(run_number=run_number, input_file=input_file)
with script_file_path.open("w", encoding="utf-8") as file:
file.write("#!/usr/bin/env bash\n\n")
file.write("set -e\n")
file.write("set -o pipefail\n")
file.write("\nSECONDS=0\n")
if extra_commands is not None:
file.write("# Writing extras\n")
for line in extra_commands:
file.write(f"{line}\n")
file.write("# End of extras\n\n")
n = 1 if test else self.runs_per_set
for _ in range(n):
file.write(f"{command}\n\n")
file.write('\necho "RUNTIME: $SECONDS"\n')
os.system(f"chmod ug+x {script_file_path}")
return script_file_path
[docs]
def run(self, test=False, input_file=None, run_number=None):
"""
Make run command and run sim_telarray.
Parameters
----------
test: bool
If True, make simulations faster.
input_file: str or Path
Full path of the input CORSIKA file.
run_number: int
Run number.
"""
self._logger.debug("Running sim_telarray")
command = self._make_run_command(run_number=run_number, input_file=input_file)
if test:
self._logger.info(f"Running (test) with command: {command}")
self._run_simtel_and_check_output(command)
else:
self._logger.debug(f"Running ({self.runs_per_set}x) with command: {command}")
for _ in range(self.runs_per_set):
self._run_simtel_and_check_output(command)
self._check_run_result(run_number=run_number)
def _check_run_result(self, run_number=None): # pylint: disable=all
"""Check if simtel output file exists."""
pass
def _raise_simtel_error(self):
"""
Raise sim_telarray execution error.
Final 30 lines from the log file are collected and printed.
Raises
------
SimtelExecutionError
"""
if hasattr(self, "_log_file"):
msg = gen.get_log_excerpt(self._log_file)
else:
msg = "Simtel log file does not exist."
self._logger.error(msg)
raise SimtelExecutionError(msg)
def _run_simtel_and_check_output(self, command):
"""
Run the sim_telarray command and check the exit code.
Raises
------
SimtelExecutionError
if run was not successful.
"""
if os.system(command) != 0:
self._raise_simtel_error()
def _make_run_command(self, run_number=None, input_file=None):
self._logger.debug(
"make_run_command is being called from the base class - "
"it should be implemented in the sub class"
)
input_file = input_file if input_file else "nofile"
run_number = run_number if run_number else 1
return f"{input_file}-{run_number}"
[docs]
@staticmethod
def get_config_option(par, value=None, weak_option=False):
"""
Build sim_telarray command.
Parameters
----------
par: str
Parameter name.
value: str
Parameter value.
weak_option: bool
If True, use -W option instead of -C.
Returns
-------
str
Command for sim_telarray.
"""
option_syntax = "-W" if weak_option else "-C"
c = f" {option_syntax} {par}"
c += f"={value}" if value is not None else ""
return c
[docs]
def get_resources(self, run_number=None):
"""Return computing resources used."""
return self.runner_service.get_resources(run_number)
[docs]
def get_file_name(self, simulation_software="simtel", file_type=None, run_number=None, mode=""):
"""
Get the full path of a file for a given run number.
Parameters
----------
simulation_software: str
Simulation software.
file_type: str
File type.
run_number: int
Run number.
Returns
-------
str
File name with full path.
"""
if simulation_software.lower() != "simtel":
raise ValueError(
f"simulation_software ({simulation_software}) is not supported in SimulatorArray"
)
return self.runner_service.get_file_name(
file_type=file_type, run_number=run_number, mode=mode
)