gammasimtools 0.25.0__py3-none-any.whl → 0.26.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
  2. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +122 -121
  3. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +2 -1
  4. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
  5. simtools/_version.py +2 -2
  6. simtools/application_control.py +35 -7
  7. simtools/applications/calculate_incident_angles.py +0 -2
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -2
  9. simtools/applications/db_add_file_to_db.py +1 -1
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -1
  12. simtools/applications/db_generate_compound_indexes.py +1 -1
  13. simtools/applications/db_get_array_layouts_from_db.py +2 -6
  14. simtools/applications/db_get_file_from_db.py +1 -1
  15. simtools/applications/db_get_parameter_from_db.py +1 -1
  16. simtools/applications/db_inspect_databases.py +1 -1
  17. simtools/applications/db_upload_model_repository.py +1 -1
  18. simtools/applications/derive_ctao_array_layouts.py +1 -2
  19. simtools/applications/derive_mirror_rnda.py +1 -3
  20. simtools/applications/derive_psf_parameters.py +0 -1
  21. simtools/applications/derive_pulse_shape_parameters.py +0 -1
  22. simtools/applications/derive_trigger_rates.py +1 -1
  23. simtools/applications/docs_produce_array_element_report.py +2 -8
  24. simtools/applications/docs_produce_calibration_reports.py +1 -3
  25. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  26. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  27. simtools/applications/generate_array_config.py +0 -1
  28. simtools/applications/generate_corsika_histograms.py +48 -235
  29. simtools/applications/generate_regular_arrays.py +5 -35
  30. simtools/applications/generate_simtel_event_data.py +2 -2
  31. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  32. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  33. simtools/applications/plot_array_layout.py +5 -111
  34. simtools/applications/plot_simulated_event_distributions.py +57 -0
  35. simtools/applications/plot_tabular_data.py +0 -1
  36. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  37. simtools/applications/production_derive_corsika_limits.py +1 -1
  38. simtools/applications/production_generate_grid.py +0 -1
  39. simtools/applications/run_application.py +1 -1
  40. simtools/applications/simulate_flasher.py +0 -2
  41. simtools/applications/simulate_illuminator.py +0 -1
  42. simtools/applications/simulate_pedestals.py +1 -5
  43. simtools/applications/simulate_prod.py +1 -5
  44. simtools/applications/simulate_prod_htcondor_generator.py +1 -1
  45. simtools/applications/submit_array_layouts.py +2 -4
  46. simtools/applications/submit_model_parameter_from_external.py +1 -3
  47. simtools/applications/validate_camera_efficiency.py +0 -1
  48. simtools/applications/validate_camera_fov.py +0 -1
  49. simtools/applications/validate_cumulative_psf.py +0 -2
  50. simtools/applications/validate_optics.py +0 -13
  51. simtools/camera/camera_efficiency.py +1 -6
  52. simtools/camera/single_photon_electron_spectrum.py +2 -1
  53. simtools/configuration/commandline_parser.py +35 -2
  54. simtools/configuration/configurator.py +6 -11
  55. simtools/corsika/corsika_config.py +16 -21
  56. simtools/corsika/corsika_histograms.py +411 -1735
  57. simtools/corsika/primary_particle.py +1 -1
  58. simtools/data_model/metadata_collector.py +5 -2
  59. simtools/data_model/metadata_model.py +0 -4
  60. simtools/data_model/model_data_writer.py +13 -15
  61. simtools/data_model/validate_data.py +1 -3
  62. simtools/db/db_handler.py +19 -8
  63. simtools/dependencies.py +81 -38
  64. simtools/io/ascii_handler.py +4 -2
  65. simtools/io/table_handler.py +1 -1
  66. simtools/layout/array_layout.py +4 -12
  67. simtools/layout/array_layout_utils.py +226 -57
  68. simtools/model/array_model.py +1 -13
  69. simtools/model/calibration_model.py +0 -4
  70. simtools/model/legacy_model_parameter.py +134 -0
  71. simtools/model/model_parameter.py +24 -13
  72. simtools/model/model_utils.py +1 -6
  73. simtools/model/site_model.py +0 -4
  74. simtools/model/telescope_model.py +6 -11
  75. simtools/production_configuration/derive_corsika_limits.py +6 -11
  76. simtools/production_configuration/interpolation_handler.py +16 -16
  77. simtools/ray_tracing/incident_angles.py +5 -11
  78. simtools/ray_tracing/mirror_panel_psf.py +3 -7
  79. simtools/ray_tracing/psf_analysis.py +18 -19
  80. simtools/ray_tracing/psf_parameter_optimisation.py +0 -1
  81. simtools/ray_tracing/ray_tracing.py +6 -15
  82. simtools/reporting/docs_auto_report_generator.py +8 -13
  83. simtools/reporting/docs_read_parameters.py +2 -8
  84. simtools/runners/corsika_runner.py +5 -9
  85. simtools/runners/corsika_simtel_runner.py +3 -8
  86. simtools/runners/simtel_runner.py +0 -5
  87. simtools/runners/simtools_runner.py +2 -4
  88. simtools/settings.py +154 -0
  89. simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
  90. simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
  91. simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
  92. simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
  93. simtools/simtel/pulse_shapes.py +7 -2
  94. simtools/simtel/simtel_config_writer.py +79 -36
  95. simtools/simtel/simtel_table_reader.py +6 -4
  96. simtools/simtel/simulator_array.py +4 -11
  97. simtools/simtel/simulator_camera_efficiency.py +4 -6
  98. simtools/simtel/simulator_light_emission.py +69 -24
  99. simtools/simtel/simulator_ray_tracing.py +4 -10
  100. simtools/simulator.py +7 -14
  101. simtools/telescope_trigger_rates.py +3 -4
  102. simtools/testing/assertions.py +84 -33
  103. simtools/testing/configuration.py +1 -2
  104. simtools/testing/helpers.py +2 -3
  105. simtools/testing/log_inspector.py +1 -0
  106. simtools/testing/sim_telarray_metadata.py +1 -1
  107. simtools/testing/validate_output.py +34 -23
  108. simtools/utils/general.py +37 -0
  109. simtools/utils/geometry.py +0 -77
  110. simtools/utils/names.py +5 -5
  111. simtools/visualization/legend_handlers.py +7 -6
  112. simtools/visualization/plot_array_layout.py +91 -16
  113. simtools/visualization/plot_corsika_histograms.py +143 -605
  114. simtools/visualization/plot_mirrors.py +1 -4
  115. simtools/visualization/plot_pixels.py +2 -4
  116. simtools/visualization/plot_psf.py +0 -1
  117. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  118. simtools/visualization/plot_simtel_events.py +6 -11
  119. simtools/visualization/plot_tables.py +8 -19
  120. simtools/visualization/visualize.py +22 -2
  121. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  122. simtools/applications/print_version.py +0 -53
  123. simtools/io/hdf5_handler.py +0 -139
  124. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
  125. {gammasimtools-0.25.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ from pathlib import Path
9
9
  import astropy.units as u
10
10
  import numpy as np
11
11
 
12
+ from simtools import settings
12
13
  from simtools.io import io_handler
13
14
  from simtools.model.model_utils import initialize_simulation_models
14
15
  from simtools.runners.simtel_runner import SimtelRunner
@@ -31,21 +32,18 @@ class SimulatorLightEmission(SimtelRunner):
31
32
  Label for the simulation
32
33
  """
33
34
 
34
- def __init__(self, light_emission_config, db_config=None, label=None):
35
+ def __init__(self, light_emission_config, label=None):
35
36
  """Initialize SimulatorLightEmission."""
36
37
  self._logger = logging.getLogger(__name__)
37
38
  self.io_handler = io_handler.IOHandler()
38
39
 
39
- super().__init__(
40
- simtel_path=light_emission_config.get("simtel_path"), label=label, corsika_config=None
41
- )
40
+ super().__init__(label=label, corsika_config=None)
42
41
 
43
42
  self.output_directory = self.io_handler.get_output_directory()
44
43
 
45
44
  self.telescope_model, self.site_model, self.calibration_model = (
46
45
  initialize_simulation_models(
47
46
  label=label,
48
- db_config=db_config,
49
47
  site=light_emission_config.get("site"),
50
48
  telescope_name=light_emission_config.get("telescope"),
51
49
  calibration_device_name=light_emission_config.get("light_source"),
@@ -68,7 +66,7 @@ class SimulatorLightEmission(SimtelRunner):
68
66
  config["flasher_photons"] = (
69
67
  self.calibration_model.get_parameter_value("flasher_photons")
70
68
  if not config.get("test", False)
71
- else 1e8
69
+ else 1e5
72
70
  )
73
71
 
74
72
  if config.get("light_source_position") is not None:
@@ -292,7 +290,7 @@ class SimulatorLightEmission(SimtelRunner):
292
290
  "corsika_observation_level"
293
291
  )
294
292
 
295
- parts = [str(self._simtel_path / "sim_telarray/LightEmission") + f"/{app_name}"]
293
+ parts = [str(settings.config.sim_telarray_path / "LightEmission") + f"/{app_name}"]
296
294
  parts.extend(self._get_site_command(app_name, config_directory, corsika_observation_level))
297
295
  parts.extend(self._get_light_source_command())
298
296
  if self.light_emission_config["light_source_type"] == "illuminator":
@@ -312,7 +310,7 @@ class SimulatorLightEmission(SimtelRunner):
312
310
  atmo_id = self._prepare_flasher_atmosphere_files(config_directory)
313
311
  return [
314
312
  "-I.",
315
- f"-I{self._simtel_path / 'sim_telarray/cfg'}",
313
+ f"-I{settings.config.sim_telarray_path / 'cfg'}",
316
314
  f"-I{config_directory}",
317
315
  f"--altitude {corsika_observation_level.to(u.m).value}",
318
316
  f"--atmosphere {atmo_id}",
@@ -349,16 +347,19 @@ class SimulatorLightEmission(SimtelRunner):
349
347
  dist_cm = self.calculate_distance_focal_plane_calibration_device().to(u.cm).value
350
348
  angular_distribution = self._get_angular_distribution_string_for_sim_telarray()
351
349
 
352
- # Build pulse table for ff-1m using model width/exp parameters; else use token.
350
+ # Build pulse table for ff-1m using unified list parameter [shape, width, exp]
351
+ pulse_shape_value = self.calibration_model.get_parameter_value("flasher_pulse_shape")
352
+ shape_name = pulse_shape_value[0]
353
+ width_ns = pulse_shape_value[1]
354
+ exp_ns = pulse_shape_value[2]
353
355
  pulse_arg = self._get_pulse_shape_string_for_sim_telarray()
354
- pulse_shape = self.calibration_model.get_parameter_value("flasher_pulse_shape")
355
- width_q = self.calibration_model.get_parameter_value_with_unit("flasher_pulse_width")
356
- exp_q = self.calibration_model.get_parameter_value_with_unit("flasher_pulse_exp_decay")
357
- if (
358
- isinstance(exp_q, u.Quantity)
359
- and isinstance(width_q, u.Quantity)
360
- and pulse_shape == "Gauss-Exponential"
361
- ):
356
+
357
+ if shape_name == "Gauss-Exponential":
358
+ if width_ns <= 0 or exp_ns <= 0:
359
+ raise ValueError(
360
+ "Gauss-Exponential pulse shape requires positive width"
361
+ " and exponential decay values"
362
+ )
362
363
  try:
363
364
  base_dir = self.io_handler.get_output_directory("pulse_shapes")
364
365
 
@@ -373,10 +374,10 @@ class SimulatorLightEmission(SimtelRunner):
373
374
  table_path = base_dir / fname
374
375
  fadc_bins = self.telescope_model.get_parameter_value("fadc_sum_bins")
375
376
 
376
- SimtelConfigWriter.write_lightpulse_table_gauss_expconv(
377
+ SimtelConfigWriter.write_light_pulse_table_gauss_exp_conv(
377
378
  file_path=table_path,
378
- width_ns=width_q.to(u.ns).value,
379
- exp_decay_ns=exp_q.to(u.ns).value,
379
+ width_ns=width_ns,
380
+ exp_decay_ns=exp_ns,
380
381
  fadc_sum_bins=fadc_bins,
381
382
  time_margin_ns=5.0,
382
383
  )
@@ -435,7 +436,7 @@ class SimulatorLightEmission(SimtelRunner):
435
436
  """
436
437
  theta, phi = self._get_telescope_pointing()
437
438
 
438
- simtel_bin = self._simtel_path.joinpath("sim_telarray/bin/sim_telarray/")
439
+ simtel_bin = str(settings.config.sim_telarray_exe)
439
440
 
440
441
  parts = [
441
442
  f"{simtel_bin}",
@@ -503,6 +504,27 @@ class SimulatorLightEmission(SimtelRunner):
503
504
  )
504
505
  return focal_length - flasher_z
505
506
 
507
+ def _generate_lambertian_angular_distribution_table(self):
508
+ """Generate Lambertian angular distribution table via config writer and return path.
509
+
510
+ Uses a pure cosine profile normalized to 1 at 0 deg and spans 0..90 deg by default.
511
+ """
512
+ base_dir = self.io_handler.get_output_directory("angular_distributions")
513
+
514
+ def _sanitize_name(value):
515
+ return "".join(ch if (ch.isalnum() or ch in ("-", "_")) else "_" for ch in str(value))
516
+
517
+ tel = self.light_emission_config.get("telescope") or "telescope"
518
+ cal = self.light_emission_config.get("light_source") or "calibration"
519
+ fname = f"flasher_angular_distribution_{_sanitize_name(tel)}_{_sanitize_name(cal)}.dat"
520
+ table_path = base_dir / fname
521
+ SimtelConfigWriter.write_angular_distribution_table_lambertian(
522
+ file_path=table_path,
523
+ max_angle_deg=90.0,
524
+ n_samples=100,
525
+ )
526
+ return str(table_path)
527
+
506
528
  def _get_angular_distribution_string_for_sim_telarray(self):
507
529
  """
508
530
  Get the angular distribution string for sim_telarray.
@@ -514,6 +536,16 @@ class SimulatorLightEmission(SimtelRunner):
514
536
  """
515
537
  opt = self.calibration_model.get_parameter_value("flasher_angular_distribution")
516
538
  option_string = str(opt).lower() if opt is not None else ""
539
+ if option_string == "lambertian":
540
+ try:
541
+ return self._generate_lambertian_angular_distribution_table()
542
+ except (OSError, ValueError) as err:
543
+ self._logger.warning(
544
+ f"Failed to write Lambertian angular distribution table: {err};"
545
+ f" using token instead."
546
+ )
547
+ return option_string
548
+
517
549
  width = self.calibration_model.get_parameter_value_with_unit(
518
550
  "flasher_angular_distribution_width"
519
551
  )
@@ -529,6 +561,19 @@ class SimulatorLightEmission(SimtelRunner):
529
561
  The pulse shape string.
530
562
  """
531
563
  opt = self.calibration_model.get_parameter_value("flasher_pulse_shape")
532
- option_string = str(opt).lower() if opt is not None else ""
533
- width = self.calibration_model.get_parameter_value_with_unit("flasher_pulse_width")
534
- return f"{option_string}:{width.to(u.ns).value}" if width is not None else option_string
564
+ shape = opt[0].lower()
565
+ # Map internal shapes to sim_telarray expected tokens
566
+ # 'tophat' corresponds to a simple (flat) pulse in sim_telarray.
567
+ shape_token_map = {
568
+ "tophat": "simple",
569
+ }
570
+ shape_out = shape_token_map.get(shape, shape)
571
+ width = opt[1]
572
+ expv = opt[2]
573
+ if shape_out == "gauss-exponential" and width is not None and expv is not None:
574
+ return f"{shape_out}:{float(width)}:{float(expv)}"
575
+ if shape_out in ("gauss", "simple") and width is not None:
576
+ return f"{shape_out}:{float(width)}"
577
+ if shape_out == "exponential" and expv is not None:
578
+ return f"{shape_out}:{float(expv)}"
579
+ return shape_out
@@ -5,6 +5,7 @@ from collections import namedtuple
5
5
 
6
6
  import astropy.units as u
7
7
 
8
+ from simtools import settings
8
9
  from simtools.io import io_handler
9
10
  from simtools.runners.simtel_runner import SimtelRunner
10
11
  from simtools.utils import names
@@ -27,8 +28,6 @@ class SimulatorRayTracing(SimtelRunner):
27
28
  site model
28
29
  label: str
29
30
  label used for output file naming.
30
- simtel_path: str or Path
31
- Location of sim_telarray installation.
32
31
  config_data: namedtuple
33
32
  namedtuple containing the configurable parameters as values (expected units in
34
33
  brackets): zenith_angle (deg), off_axis_angle (deg), source_distance (km),
@@ -43,7 +42,6 @@ class SimulatorRayTracing(SimtelRunner):
43
42
  telescope_model,
44
43
  site_model,
45
44
  label=None,
46
- simtel_path=None,
47
45
  config_data=None,
48
46
  force_simulate=False,
49
47
  test=False,
@@ -52,7 +50,7 @@ class SimulatorRayTracing(SimtelRunner):
52
50
  self._logger = logging.getLogger(__name__)
53
51
  self._logger.debug("Init SimulatorRayTracing")
54
52
 
55
- super().__init__(label=label, simtel_path=simtel_path)
53
+ super().__init__(label=label)
56
54
 
57
55
  self.telescope_model = telescope_model
58
56
  self.site_model = site_model
@@ -83,10 +81,6 @@ class SimulatorRayTracing(SimtelRunner):
83
81
  force_simulate: bool
84
82
  Remove existing files and force re-running of the ray-tracing simulation.
85
83
  """
86
- # This file is not actually needed and does not exist in simtools.
87
- # It is required as CORSIKA input file to sim_telarray
88
- self._corsika_file = self._simtel_path.joinpath("run9991.corsika.gz")
89
-
90
84
  # Loop to define and remove existing files.
91
85
  # Files will be named _base_file = self.__dict__['_' + base + 'File']
92
86
  for base_name in ["stars", "photons", "log"]:
@@ -153,7 +147,7 @@ class SimulatorRayTracing(SimtelRunner):
153
147
  )
154
148
 
155
149
  # RayTracing
156
- command = str(self._simtel_path.joinpath("sim_telarray/bin/sim_telarray"))
150
+ command = str(settings.config.sim_telarray_exe)
157
151
  command += f" -c {self.telescope_model.config_file_path}"
158
152
  command += f" -I{self.telescope_model.config_file_directory}"
159
153
  command += super().get_config_option("random_state", "none")
@@ -197,7 +191,7 @@ class SimulatorRayTracing(SimtelRunner):
197
191
  command += super().get_config_option("parabolic_dish", "0")
198
192
  command += super().get_config_option("mirror_align_random_distance", "0.")
199
193
  command += super().get_config_option("mirror_align_random_vertical", "0.,28.,0.,0.")
200
- command += " " + str(self._corsika_file)
194
+ command += " " + str(settings.config.corsika_dummy_file)
201
195
 
202
196
  return clear_default_sim_telarray_cfg_directories(command), self._log_file, self._log_file
203
197
 
simtools/simulator.py CHANGED
@@ -11,12 +11,13 @@ import numpy as np
11
11
  from astropy import units as u
12
12
 
13
13
  from simtools.corsika.corsika_config import CorsikaConfig
14
- from simtools.io import eventio_handler, io_handler, table_handler
14
+ from simtools.io import io_handler, table_handler
15
15
  from simtools.job_execution.job_manager import JobManager
16
16
  from simtools.model.array_model import ArrayModel
17
17
  from simtools.runners.corsika_runner import CorsikaRunner
18
18
  from simtools.runners.corsika_simtel_runner import CorsikaSimtelRunner
19
- from simtools.simtel.simtel_io_event_writer import SimtelIOEventDataWriter
19
+ from simtools.sim_events import file_info
20
+ from simtools.sim_events.writer import EventDataWriter
20
21
  from simtools.simtel.simulator_array import SimulatorArray
21
22
  from simtools.testing.sim_telarray_metadata import assert_sim_telarray_metadata
22
23
  from simtools.utils import general, names
@@ -42,8 +43,6 @@ class Simulator:
42
43
  Instance label.
43
44
  extra_commands: str or list of str
44
45
  Extra commands to be added to the run script before the run command.
45
- db_config: dict
46
- Database configuration.
47
46
  """
48
47
 
49
48
  def __init__(
@@ -51,14 +50,12 @@ class Simulator:
51
50
  args_dict,
52
51
  label=None,
53
52
  extra_commands=None,
54
- db_config=None,
55
53
  ):
56
54
  """Initialize Simulator class."""
57
55
  self.logger = logging.getLogger(__name__)
58
56
  self.label = label
59
57
 
60
58
  self.args_dict = args_dict
61
- self.db_config = db_config
62
59
  self.site = self.args_dict.get("site", None)
63
60
  self.model_version = self.args_dict.get("model_version", None)
64
61
 
@@ -114,7 +111,7 @@ class Simulator:
114
111
  }
115
112
 
116
113
  if self.args_dict.get("corsika_file"):
117
- self.run_number = eventio_handler.get_corsika_run_number(self.args_dict["corsika_file"])
114
+ self.run_number = file_info.get_corsika_run_number(self.args_dict["corsika_file"])
118
115
  else:
119
116
  self.run_number = self.args_dict.get("run_number_offset", 0) + self.args_dict.get(
120
117
  "run_number", 1
@@ -140,11 +137,9 @@ class Simulator:
140
137
  label=self.label,
141
138
  site=self.site,
142
139
  layout_name=self.args_dict.get("array_layout_name"),
143
- db_config=self.db_config,
144
140
  model_version=version,
145
141
  calibration_device_types=self._get_calibration_device_types(self.run_mode),
146
142
  overwrite_model_parameters=self.args_dict.get("overwrite_model_parameters"),
147
- simtel_path=self.args_dict.get("simtel_path"),
148
143
  )
149
144
  )
150
145
  corsika_configurations.append(
@@ -152,7 +147,6 @@ class Simulator:
152
147
  array_model=array_model[-1],
153
148
  label=self.label,
154
149
  args_dict=self.args_dict,
155
- db_config=self.db_config,
156
150
  dummy_simulations=self._is_calibration_run(self.run_mode),
157
151
  )
158
152
  )
@@ -232,7 +226,6 @@ class Simulator:
232
226
  runner_args = {
233
227
  "label": self.label,
234
228
  "corsika_config": self.corsika_configurations,
235
- "simtel_path": self.args_dict.get("simtel_path"),
236
229
  "use_multipipe": runner_class is CorsikaSimtelRunner,
237
230
  }
238
231
 
@@ -445,7 +438,7 @@ class Simulator:
445
438
  input_files = self.get_file_list(file_type="simtel_output")
446
439
  output_files = self.get_file_list(file_type="event_data")
447
440
  for input_file, output_file in zip(input_files, output_files):
448
- generator = SimtelIOEventDataWriter([input_file])
441
+ generator = EventDataWriter([input_file])
449
442
  table_handler.write_tables(
450
443
  tables=generator.process_files(),
451
444
  output_file=Path(output_file),
@@ -691,7 +684,7 @@ class Simulator:
691
684
 
692
685
  event_errors = []
693
686
  for file in self.get_file_list(file_type="corsika_output"):
694
- shower_events, _ = eventio_handler.get_simulated_events(file)
687
+ shower_events, _ = file_info.get_simulated_events(file)
695
688
 
696
689
  if shower_events != expected_mc_events:
697
690
  if consistent(shower_events, expected_mc_events, tol=tolerance):
@@ -736,7 +729,7 @@ class Simulator:
736
729
  """
737
730
  event_errors = []
738
731
  for file in self.get_file_list(file_type="simtel_output"):
739
- shower_events, mc_events = eventio_handler.get_simulated_events(file)
732
+ shower_events, mc_events = file_info.get_simulated_events(file)
740
733
 
741
734
  if (shower_events, mc_events) != (expected_shower_events, expected_mc_events):
742
735
  event_errors.append(
@@ -8,13 +8,13 @@ from ctao_cr_spectra.definitions import IRFDOC_PROTON_SPECTRUM
8
8
 
9
9
  from simtools.io import ascii_handler, io_handler
10
10
  from simtools.layout.array_layout_utils import get_array_elements_from_db_for_layouts
11
- from simtools.simtel.simtel_io_event_histograms import SimtelIOEventHistograms
11
+ from simtools.sim_events.histograms import EventDataHistograms
12
12
  from simtools.visualization import plot_simtel_event_histograms
13
13
 
14
14
  _logger = logging.getLogger(__name__)
15
15
 
16
16
 
17
- def telescope_trigger_rates(args_dict, db_config):
17
+ def telescope_trigger_rates(args_dict):
18
18
  """
19
19
  Calculate trigger rates for single telescopes or arrays of telescopes.
20
20
 
@@ -27,7 +27,6 @@ def telescope_trigger_rates(args_dict, db_config):
27
27
  args_dict["array_layout_name"],
28
28
  args_dict.get("site"),
29
29
  args_dict.get("model_version"),
30
- db_config,
31
30
  )
32
31
  else:
33
32
  telescope_configs = ascii_handler.collect_data_from_file(args_dict["telescope_ids"])[
@@ -38,7 +37,7 @@ def telescope_trigger_rates(args_dict, db_config):
38
37
  _logger.info(
39
38
  f"Processing file: {args_dict['event_data_file']} with telescope config: {array_name}"
40
39
  )
41
- histograms = SimtelIOEventHistograms(
40
+ histograms = EventDataHistograms(
42
41
  args_dict["event_data_file"], array_name=array_name, telescope_list=telescope_ids
43
42
  )
44
43
  histograms.fill()
@@ -68,8 +68,7 @@ def assert_n_showers_and_energy_range(file):
68
68
  simulation_config = {}
69
69
  with SimTelFile(file, skip_non_triggered=False) as f:
70
70
  simulation_config = f.mc_run_headers[0]
71
- for event in f:
72
- simulated_energies.append(event["mc_shower"]["energy"])
71
+ simulated_energies.extend(event["mc_shower"]["energy"] for event in f)
73
72
 
74
73
  # The relative tolerance is set to 1% because ~0.5% shower simulations do not
75
74
  # succeed, without resulting in an error. This tolerance therefore is not an issue.
@@ -191,54 +190,94 @@ def check_output_from_sim_telarray(file, file_test):
191
190
  return assert_n_showers_and_energy_range(file=file) and assert_output and assert_metadata
192
191
 
193
192
 
194
- def _find_patterns(text, patterns):
195
- """Find patterns in text."""
196
- return {p for p in patterns if p in text}
197
-
198
-
199
- def _read_log(member, tar):
200
- """Read and decode a gzipped log file from a tar archive."""
201
- with tar.extractfile(member) as gz, gzip.open(gz, "rb") as f:
202
- return f.read().decode("utf-8", "ignore")
203
-
204
-
205
193
  def check_simulation_logs(tar_file, file_test):
206
194
  """
207
- Check log files of CORSIKA and sim_telarray for expected output.
195
+ Check simulation logs for wanted and forbidden patterns.
208
196
 
209
197
  Parameters
210
198
  ----------
211
- tar_file: Path
212
- Path to a log file tar package.
213
- file_test: dict
214
- File test description including expected log output.
215
-
216
- Raises
217
- ------
218
- ValueError
219
- If the file is not a tar file.
199
+ tar_file : str
200
+ Path to the tar file.
201
+ file_test : dict
202
+ Dictionary with the test configuration.
203
+
204
+ Returns
205
+ -------
206
+ bool
207
+ True if the logs are correct.
220
208
  """
221
- expected_log = file_test.get("expected_log_output", {})
222
- wanted = expected_log.get("pattern", [])
223
- forbidden = expected_log.get("forbidden_pattern", [])
224
-
225
- if not (wanted or forbidden):
226
- _logger.debug(f"No expected log output provided, skipping checks {file_test}")
209
+ wanted, forbidden = _get_expected_patterns(file_test)
210
+ if wanted is None:
227
211
  return True
228
212
 
229
213
  if not tarfile.is_tarfile(tar_file):
230
- raise ValueError(f"File {tar_file} is not a tar file.")
214
+ raise ValueError(f"{tar_file} is not a tar file")
231
215
 
232
- found, bad = set(), set()
216
+ found_wanted = set()
217
+ found_forbidden = set()
233
218
  with tarfile.open(tar_file, "r:*") as tar:
234
219
  for member in tar.getmembers():
235
220
  if not member.name.endswith(".log.gz"):
236
221
  continue
237
222
  _logger.info(f"Scanning {member.name}")
238
223
  text = _read_log(member, tar)
239
- found |= _find_patterns(text, wanted)
240
- bad |= _find_patterns(text, forbidden)
224
+ found_wanted |= _find_patterns(text, wanted)
225
+ found_forbidden |= _find_patterns(text, forbidden)
226
+
227
+ return _validate_patterns(found_wanted, found_forbidden, wanted)
228
+
229
+
230
+ def check_plain_log(log_file, file_test):
231
+ """
232
+ Check plain log file for wanted and forbidden patterns.
233
+
234
+ Parameters
235
+ ----------
236
+ log_file : str
237
+ Path to the log file.
238
+ file_test : dict
239
+ Dictionary with the test configuration.
240
+
241
+ Returns
242
+ -------
243
+ bool
244
+ True if the logs are correct.
245
+ """
246
+ wanted, forbidden = _get_expected_patterns(file_test)
247
+ if wanted is None:
248
+ return True
249
+
250
+ try:
251
+ with open(log_file, encoding="utf-8") as f:
252
+ text = f.read()
253
+ except FileNotFoundError:
254
+ _logger.error(f"Log file {log_file} not found")
255
+ return False
256
+
257
+ found = _find_patterns(text, wanted)
258
+ bad = _find_patterns(text, forbidden)
259
+
260
+ return _validate_patterns(found, bad, wanted)
261
+
262
+
263
+ def _get_expected_patterns(file_test):
264
+ """Get wanted and forbidden patterns from file test configuration."""
265
+ expected_log = file_test.get("expected_log_output")
266
+ if isinstance(expected_log, dict):
267
+ wanted = expected_log.get("pattern", [])
268
+ forbidden = expected_log.get("forbidden_pattern", [])
269
+ else:
270
+ wanted = file_test.get("pattern", [])
271
+ forbidden = file_test.get("forbidden_pattern", [])
272
+ if not (wanted or forbidden):
273
+ _logger.debug(f"No expected log output provided, skipping checks {file_test}")
274
+ return None, None
241
275
 
276
+ return wanted, forbidden
277
+
278
+
279
+ def _validate_patterns(found, bad, wanted):
280
+ """Validate found patterns against wanted and forbidden ones."""
242
281
  if bad:
243
282
  _logger.error(f"Forbidden patterns found: {list(bad)}")
244
283
  return False
@@ -249,3 +288,15 @@ def check_simulation_logs(tar_file, file_test):
249
288
 
250
289
  _logger.debug(f"All expected patterns found: {wanted}")
251
290
  return True
291
+
292
+
293
+ def _find_patterns(text, patterns):
294
+ """Find patterns in text (case insensitive)."""
295
+ text_lower = text.lower()
296
+ return {p for p in patterns if p.lower() in text_lower}
297
+
298
+
299
+ def _read_log(member, tar):
300
+ """Read and decode a gzipped log file from a tar archive."""
301
+ with tar.extractfile(member) as gz, gzip.open(gz, "rb") as f:
302
+ return f.read().decode("utf-8", "ignore")
@@ -76,8 +76,7 @@ def _read_configs_from_files(config_files):
76
76
  _dict = gen.remove_substring_recursively_from_dict(
77
77
  ascii_handler.collect_data_from_file(file_name=config_file), substring="\n"
78
78
  )
79
- for application in _dict.get("applications", []):
80
- configs.append(application)
79
+ configs.extend(_dict.get("applications", []))
81
80
  return configs
82
81
 
83
82
 
@@ -1,7 +1,6 @@
1
1
  """Helper functions for integration testing."""
2
2
 
3
- import os
4
- from pathlib import Path
3
+ from simtools import settings
5
4
 
6
5
 
7
6
  def skip_camera_efficiency(config):
@@ -21,7 +20,7 @@ def _new_testeff_version():
21
20
 
22
21
  This test checks if the new version is used.
23
22
  """
24
- testeff_path = Path(os.getenv("SIMTOOLS_SIMTEL_PATH")) / "sim_telarray/testeff.c"
23
+ testeff_path = settings.config.sim_telarray_path / "testeff.c"
25
24
  try:
26
25
  with open(testeff_path, encoding="utf-8") as file:
27
26
  file_content = file.read()
@@ -18,6 +18,7 @@ ERROR_PATTERNS = [
18
18
  IGNORE_PATTERNS = [
19
19
  re.compile(r"Falling back to 'utf-8' with errors='ignore'", re.IGNORECASE),
20
20
  re.compile(r"Failed to get user name[^\n]*setting it to UNKNOWN_USER", re.IGNORECASE),
21
+ re.compile(r"adjust_text::Error", re.IGNORECASE),
21
22
  ]
22
23
 
23
24
 
@@ -4,7 +4,7 @@ import logging
4
4
 
5
5
  import numpy as np
6
6
 
7
- from simtools.io.eventio_handler import get_corsika_run_number
7
+ from simtools.sim_events.file_info import get_corsika_run_number
8
8
  from simtools.simtel.simtel_config_reader import SimtelConfigReader
9
9
  from simtools.simtel.simtel_config_writer import sim_telarray_random_seeds
10
10
  from simtools.simtel.simtel_io_metadata import (