gammasimtools 0.8.2__py3-none-any.whl → 0.10.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 (122) hide show
  1. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/METADATA +4 -4
  2. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/RECORD +119 -105
  3. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/entry_points.txt +4 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/calculate_trigger_rate.py +15 -38
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +9 -28
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +54 -53
  9. simtools/applications/convert_model_parameter_from_simtel.py +2 -2
  10. simtools/applications/db_add_file_to_db.py +1 -2
  11. simtools/applications/db_add_simulation_model_from_repository_to_db.py +110 -0
  12. simtools/applications/db_add_value_from_json_to_db.py +2 -11
  13. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +6 -6
  14. simtools/applications/db_get_array_layouts_from_db.py +3 -1
  15. simtools/applications/db_get_file_from_db.py +11 -12
  16. simtools/applications/db_get_parameter_from_db.py +44 -32
  17. simtools/applications/derive_mirror_rnda.py +10 -1
  18. simtools/applications/derive_photon_electron_spectrum.py +99 -0
  19. simtools/applications/derive_psf_parameters.py +1 -1
  20. simtools/applications/generate_array_config.py +18 -22
  21. simtools/applications/generate_regular_arrays.py +24 -21
  22. simtools/applications/generate_simtel_array_histograms.py +11 -48
  23. simtools/applications/plot_array_layout.py +3 -1
  24. simtools/applications/plot_tabular_data.py +84 -0
  25. simtools/applications/production_generate_simulation_config.py +25 -7
  26. simtools/applications/production_scale_events.py +3 -4
  27. simtools/applications/simulate_light_emission.py +2 -2
  28. simtools/applications/simulate_prod.py +25 -60
  29. simtools/applications/simulate_prod_htcondor_generator.py +95 -0
  30. simtools/applications/submit_data_from_external.py +12 -4
  31. simtools/applications/submit_model_parameter_from_external.py +8 -6
  32. simtools/applications/validate_camera_efficiency.py +3 -3
  33. simtools/applications/validate_camera_fov.py +3 -7
  34. simtools/applications/validate_cumulative_psf.py +3 -7
  35. simtools/applications/validate_file_using_schema.py +38 -24
  36. simtools/applications/validate_optics.py +3 -4
  37. simtools/{camera_efficiency.py → camera/camera_efficiency.py} +1 -4
  38. simtools/camera/single_photon_electron_spectrum.py +168 -0
  39. simtools/configuration/commandline_parser.py +14 -13
  40. simtools/configuration/configurator.py +6 -19
  41. simtools/constants.py +10 -3
  42. simtools/corsika/corsika_config.py +8 -7
  43. simtools/corsika/corsika_histograms.py +1 -1
  44. simtools/data_model/data_reader.py +0 -3
  45. simtools/data_model/metadata_collector.py +21 -4
  46. simtools/data_model/metadata_model.py +8 -111
  47. simtools/data_model/model_data_writer.py +18 -64
  48. simtools/data_model/schema.py +213 -0
  49. simtools/data_model/validate_data.py +73 -51
  50. simtools/db/db_handler.py +395 -790
  51. simtools/db/db_model_upload.py +139 -0
  52. simtools/io_operations/hdf5_handler.py +54 -24
  53. simtools/io_operations/legacy_data_handler.py +61 -0
  54. simtools/job_execution/htcondor_script_generator.py +133 -0
  55. simtools/job_execution/job_manager.py +77 -50
  56. simtools/layout/array_layout.py +33 -28
  57. simtools/model/array_model.py +13 -7
  58. simtools/model/camera.py +4 -2
  59. simtools/model/model_parameter.py +61 -63
  60. simtools/model/site_model.py +3 -3
  61. simtools/production_configuration/calculate_statistical_errors_grid_point.py +119 -144
  62. simtools/production_configuration/event_scaler.py +7 -17
  63. simtools/production_configuration/generate_simulation_config.py +5 -32
  64. simtools/production_configuration/interpolation_handler.py +8 -11
  65. simtools/ray_tracing/mirror_panel_psf.py +47 -27
  66. simtools/runners/corsika_runner.py +14 -3
  67. simtools/runners/corsika_simtel_runner.py +3 -1
  68. simtools/runners/runner_services.py +3 -3
  69. simtools/runners/simtel_runner.py +27 -8
  70. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +39 -0
  71. simtools/schemas/input/single_pe_spectrum.schema.yml +38 -0
  72. simtools/schemas/integration_tests_config.metaschema.yml +23 -3
  73. simtools/schemas/model_parameter.metaschema.yml +95 -2
  74. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +2 -0
  75. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  76. simtools/schemas/model_parameters/array_window.schema.yml +37 -0
  77. simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -4
  78. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  79. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +2 -2
  80. simtools/schemas/model_parameters/dsum_clipping.schema.yml +0 -2
  81. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -2
  82. simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -2
  83. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -2
  84. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -2
  85. simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -2
  86. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -2
  87. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -2
  88. simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -2
  89. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -2
  90. simtools/schemas/model_parameters/dsum_threshold.schema.yml +0 -2
  91. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -2
  92. simtools/schemas/model_parameters/effective_focal_length.schema.yml +31 -1
  93. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  94. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  95. simtools/schemas/model_parameters/fadc_noise.schema.yml +3 -3
  96. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +33 -0
  97. simtools/schemas/model_parameters/laser_photons.schema.yml +2 -2
  98. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  99. simtools/schemas/production_configuration_metrics.schema.yml +68 -0
  100. simtools/schemas/production_tables.schema.yml +41 -0
  101. simtools/simtel/simtel_config_writer.py +5 -6
  102. simtools/simtel/simtel_io_histogram.py +32 -67
  103. simtools/simtel/simtel_io_histograms.py +15 -30
  104. simtools/simtel/simtel_table_reader.py +410 -0
  105. simtools/simtel/simulator_array.py +2 -1
  106. simtools/simtel/simulator_camera_efficiency.py +11 -4
  107. simtools/simtel/simulator_light_emission.py +5 -3
  108. simtools/simtel/simulator_ray_tracing.py +2 -2
  109. simtools/simulator.py +80 -33
  110. simtools/testing/configuration.py +12 -8
  111. simtools/testing/helpers.py +9 -16
  112. simtools/testing/validate_output.py +152 -68
  113. simtools/utils/general.py +149 -12
  114. simtools/utils/names.py +25 -21
  115. simtools/utils/value_conversion.py +9 -1
  116. simtools/visualization/plot_tables.py +106 -0
  117. simtools/visualization/visualize.py +43 -5
  118. simtools/applications/db_add_model_parameters_from_repository_to_db.py +0 -184
  119. simtools/db/db_array_elements.py +0 -130
  120. simtools/db/db_from_repo_handler.py +0 -106
  121. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/LICENSE +0 -0
  122. {gammasimtools-0.8.2.dist-info → gammasimtools-0.10.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,410 @@
1
+ #!/usr/bin/python3
2
+ """Read tabular data in sim_telarray format and return as astropy table."""
3
+
4
+ import logging
5
+ import re
6
+
7
+ import astropy.units as u
8
+ from astropy.table import Table
9
+
10
+ from simtools.utils import general as gen
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def _data_columns(parameter_name, n_columns, n_dim):
16
+ """
17
+ Get column information for a given parameter.
18
+
19
+ Individual functions are adapted to the specific format of the sim_telarray tables.
20
+
21
+ Parameters
22
+ ----------
23
+ parameter_name: str
24
+ Model parameter name.
25
+ n_columns: int
26
+ Number of columns in the table.
27
+ n_dim: list
28
+ List of columns for n-dimensional tables defined by RPOL lines.
29
+
30
+ Returns
31
+ -------
32
+ list, str
33
+ List of columns for n-dimensional tables, description.
34
+ """
35
+ if parameter_name == "mirror_reflectivity":
36
+ return _data_columns_mirror_reflectivity(n_columns, n_dim)
37
+ if parameter_name in ("discriminator_pulse_shape", "fadc_pulse_shape"):
38
+ return _data_columns_pulse_shape(n_columns)
39
+ try:
40
+ return globals()[f"_data_columns_{parameter_name}"]()
41
+ except KeyError as exc:
42
+ raise ValueError(
43
+ f"Unsupported parameter for sim_telarray table reading: {parameter_name}"
44
+ ) from exc
45
+
46
+
47
+ def _data_columns_atmospheric_profile():
48
+ """Column representation for parameter atmospheric_profile."""
49
+ return (
50
+ [
51
+ {"name": "altitude", "description": "Altitude", "unit": "km"},
52
+ {"name": "density", "description": "Density", "unit": "g/cm^3"},
53
+ {"name": "thickness", "description": "Thickness", "unit": "g/cm^2"},
54
+ {
55
+ "name": "refractive_index",
56
+ "description": "Refractive index (n-1)",
57
+ "unit": None,
58
+ },
59
+ {
60
+ "name": "temperature",
61
+ "description": "Temperature",
62
+ "unit": "K",
63
+ },
64
+ {
65
+ "name": "pressure",
66
+ "description": "Pressure",
67
+ "unit": "mbar",
68
+ },
69
+ {
70
+ "name": "pw/w",
71
+ "description": "Partial pressure of water vapor",
72
+ "unit": None,
73
+ },
74
+ ],
75
+ "Atmospheric profile",
76
+ )
77
+
78
+
79
+ def _data_columns_pm_photoelectron_spectrum():
80
+ """Column description for parameter pm_photoelectron_spectrum."""
81
+ return (
82
+ [
83
+ {"name": "amplitude", "description": "Signal amplitude", "unit": None},
84
+ {
85
+ "name": "response",
86
+ "description": "response without afterpulsing component",
87
+ "unit": None,
88
+ },
89
+ {
90
+ "name": "response_with_ap",
91
+ "description": "response including afterpulsing component",
92
+ "unit": None,
93
+ },
94
+ ],
95
+ "Photoelectron spectrum",
96
+ )
97
+
98
+
99
+ def _data_columns_quantum_efficiency():
100
+ """Column description for parameter quantum_efficiency."""
101
+ return (
102
+ [
103
+ {"name": "wavelength", "description": "Wavelength", "unit": "nm"},
104
+ {
105
+ "name": "efficiency",
106
+ "description": "Quantum efficiency",
107
+ "unit": None,
108
+ },
109
+ {
110
+ "name": "efficiency_rms",
111
+ "description": "Quantum efficiency (standard deviation)",
112
+ "unit": None,
113
+ },
114
+ ],
115
+ "Quantum efficiency",
116
+ )
117
+
118
+
119
+ def _data_columns_camera_filter():
120
+ """Column description for parameter camera_filter."""
121
+ return (
122
+ [
123
+ {"name": "wavelength", "description": "Wavelength", "unit": "nm"},
124
+ {
125
+ "name": "transmission",
126
+ "description": "Average transmission",
127
+ "unit": None,
128
+ },
129
+ ],
130
+ "Camera window transmission",
131
+ )
132
+
133
+
134
+ def _data_columns_lightguide_efficiency_vs_wavelength():
135
+ """Column description for parameter lightguide_efficiency_vs_wavelength."""
136
+ return _data_columns_lightguide_efficiency_vs_incidence_angle()
137
+
138
+
139
+ def _data_columns_lightguide_efficiency_vs_incidence_angle():
140
+ """Column description for (parameter lightguide_efficiency_vs_incidence_angle."""
141
+ return (
142
+ [
143
+ {"name": "angle", "description": "Incidence angle", "unit": "deg"},
144
+ {
145
+ "name": "efficiency",
146
+ "description": "Light guide efficiency",
147
+ "unit": None,
148
+ },
149
+ ],
150
+ "Light guide efficiency vs incidence angle",
151
+ )
152
+
153
+
154
+ def _data_columns_mirror_reflectivity(n_columns, n_dim):
155
+ """Column description for parameter mirror_reflectivity."""
156
+ _columns = [
157
+ {"name": "wavelength", "description": "Wavelength", "unit": "nm"},
158
+ ]
159
+ if n_dim:
160
+ for angle in n_dim:
161
+ _columns.append(
162
+ {
163
+ "name": f"reflectivity_{angle}deg",
164
+ "description": f"Mirror reflectivity at {angle} deg",
165
+ "unit": None,
166
+ },
167
+ )
168
+ else:
169
+ _columns.append(
170
+ {
171
+ "name": "reflectivity",
172
+ "description": "Mirror reflectivity",
173
+ "unit": None,
174
+ },
175
+ )
176
+ if n_columns == 3:
177
+ _columns.append(
178
+ {
179
+ "name": "reflectivity_rms",
180
+ "description": "Mirror reflectivity (standard deviation)",
181
+ "unit": None,
182
+ },
183
+ )
184
+ if n_columns == 4:
185
+ _columns.append(
186
+ {
187
+ "name": "reflectivity_min",
188
+ "description": "Mirror reflectivity (min)",
189
+ "unit": None,
190
+ },
191
+ )
192
+ _columns.append(
193
+ {
194
+ "name": "reflectivity_max",
195
+ "description": "Mirror reflectivity (max)",
196
+ "unit": None,
197
+ },
198
+ )
199
+
200
+ return _columns, "Mirror reflectivity"
201
+
202
+
203
+ def _data_columns_pulse_shape(n_columns):
204
+ """Column description for parameters discriminator_pulse_shape, fadc_pulse_shape."""
205
+ _columns = [
206
+ {"name": "time", "description": "Time", "unit": "ns"},
207
+ {
208
+ "name": "amplitude",
209
+ "description": "Amplitude",
210
+ "unit": None,
211
+ },
212
+ ]
213
+ if n_columns == 3:
214
+ _columns.append(
215
+ {
216
+ "name": "amplitude (low gain)",
217
+ "description": "Amplitude (low gain)",
218
+ "unit": None,
219
+ },
220
+ )
221
+
222
+ return _columns, "Pulse shape"
223
+
224
+
225
+ def _data_columns_nsb_reference_spectrum():
226
+ """Column description for parameter nsb_reference_spectrum."""
227
+ return (
228
+ [
229
+ {"name": "wavelength", "description": "Wavelength", "unit": "nm"},
230
+ {
231
+ "name": "differential photon rate",
232
+ "description": "Differential photon rate",
233
+ "unit": "1.e9 / (nm s m^2 sr)",
234
+ },
235
+ ],
236
+ "NSB reference spectrum",
237
+ )
238
+
239
+
240
+ def read_simtel_table(parameter_name, file_path):
241
+ """
242
+ Read sim_telarray table file for a given parameter.
243
+
244
+ Parameters
245
+ ----------
246
+ parameter_name: str
247
+ Model parameter name.
248
+ file_path: Path
249
+ Name (full path) of the sim_telarray table file.
250
+
251
+ Returns
252
+ -------
253
+ Table
254
+ Astropy table.
255
+ """
256
+ if parameter_name == "atmospheric_transmission":
257
+ return _read_simtel_data_for_atmospheric_transmission(file_path)
258
+
259
+ rows, meta_from_simtel, n_columns, n_dim = _read_simtel_data(file_path)
260
+ columns_info, description = _data_columns(parameter_name, n_columns, n_dim)
261
+
262
+ rows = _adjust_columns_length(rows, len(columns_info))
263
+
264
+ metadata = {
265
+ "Name": parameter_name,
266
+ "File": str(file_path),
267
+ "Description:": description,
268
+ "Context_from_sim_telarray": meta_from_simtel,
269
+ }
270
+
271
+ table = Table(rows=rows, names=[col["name"] for col in columns_info])
272
+ for col, info in zip(table.colnames, columns_info):
273
+ table[col].unit = info.get("unit")
274
+ table[col].description = info.get("description")
275
+ table.meta.update(metadata)
276
+
277
+ return table
278
+
279
+
280
+ def _adjust_columns_length(rows, n_columns):
281
+ """
282
+ Adjust row lengths to match the specified column count.
283
+
284
+ - Truncate rows with extra columns beyond the specified count 'n_columns'.
285
+ - Pad shorter rows with zeros.
286
+ """
287
+ return [row[:n_columns] + [0.0] * max(0, n_columns - len(row)) for row in rows]
288
+
289
+
290
+ def _read_simtel_data(file_path):
291
+ """
292
+ Read data, comments, and (if available) axis definition from sim_telarray table.
293
+
294
+ Parameters
295
+ ----------
296
+ file_path: Path
297
+ Path to the sim_telarray table file.
298
+
299
+ Returns
300
+ -------
301
+ str, str, int, str
302
+ data, metadata (comments), number of columns (max value), n-dimensional axis description.
303
+ """
304
+ logger.debug(f"Reading sim_telarray table from {file_path}")
305
+ meta_lines = []
306
+ data_lines = []
307
+ n_dim_axis = None
308
+ r_pol_axis = None
309
+
310
+ lines = gen.read_file_encoded_in_utf_or_latin(file_path)
311
+
312
+ for line in lines:
313
+ stripped = line.strip()
314
+ if "@RPOL@" in stripped: # RPOL description for N-dimensional tables
315
+ match = re.search(r"#@RPOL@\[(\w+)=\]", stripped)
316
+ if match:
317
+ r_pol_axis = match.group(1)
318
+ elif r_pol_axis and r_pol_axis in stripped: # N-dimensional axis description
319
+ n_dim_axis = stripped.split("=")[1].split()
320
+ elif stripped.startswith("#"): # Metadata
321
+ meta_lines.append(stripped.lstrip("#").strip())
322
+ elif stripped: # Data
323
+ data_lines.append(stripped.split("%%%")[0].split("#")[0].strip()) # Remove comments
324
+
325
+ rows = [[float(part) for part in line.split()] for line in data_lines]
326
+ n_columns = max(len(row) for row in rows) if rows else 0
327
+
328
+ return rows, "\n".join(meta_lines), n_columns, n_dim_axis
329
+
330
+
331
+ def _read_simtel_data_for_atmospheric_transmission(file_path):
332
+ """
333
+ Read data and comments from sim_telarray table for atmospheric_transmission.
334
+
335
+ Parameters
336
+ ----------
337
+ file_path: Path
338
+ Path to the sim_telarray table file.
339
+
340
+ Returns
341
+ -------
342
+ astropy table
343
+ Table with atmospheric transmission.
344
+ """
345
+ lines = lines = gen.read_file_encoded_in_utf_or_latin(file_path)
346
+
347
+ observatory_level, height_bins = _read_header_line_for_atmospheric_transmission(
348
+ lines, file_path
349
+ )
350
+
351
+ wavelengths = []
352
+ heights = []
353
+ extinctions = []
354
+ meta_lines = []
355
+
356
+ for line in lines:
357
+ if line.startswith("#") or not line.strip():
358
+ meta_lines.append(line.lstrip("#").strip())
359
+ continue
360
+ parts = line.split()
361
+ try:
362
+ wl = float(parts[0])
363
+ for i, height in enumerate(height_bins):
364
+ extinction_value = float(parts[i + 1])
365
+ if extinction_value == 99999.0:
366
+ continue
367
+ wavelengths.append(wl)
368
+ heights.append(height)
369
+ extinctions.append(extinction_value)
370
+ except (ValueError, IndexError):
371
+ logger.debug(f"Skipping malformed line: {line.strip()}")
372
+
373
+ table = Table()
374
+ table["wavelength"] = wavelengths
375
+ table["altitude"] = heights
376
+ table["extinction"] = extinctions
377
+
378
+ table.meta.update(
379
+ {
380
+ "Name": "atmospheric_transmission",
381
+ "File": str(file_path),
382
+ "Description": "Atmospheric transmission",
383
+ "Context_from_sim_telarray": "\n".join(meta_lines),
384
+ "observatory_level": observatory_level,
385
+ }
386
+ )
387
+
388
+ return table
389
+
390
+
391
+ def _read_header_line_for_atmospheric_transmission(lines, file_path):
392
+ """Reader observatory level and height bins from header line for atmospheric transmission."""
393
+ header_line = None
394
+ observatory_level = None
395
+ for line in lines:
396
+ if "H2=" in line and "H1=" in line:
397
+ match_h2 = re.search(r"H2=\s*([\d.]+)", line)
398
+ if match_h2:
399
+ observatory_level = float(match_h2.group(1)) * u.km
400
+
401
+ if "H1=" in line:
402
+ header_line = line.split("H1=")[-1].strip()
403
+ break
404
+
405
+ if header_line is None:
406
+ raise ValueError(f"Header with 'H1=' not found file {file_path}")
407
+
408
+ height_bins = [float(x.replace(",", "")) for x in header_line.split()]
409
+
410
+ return observatory_level, height_bins
@@ -4,6 +4,7 @@ import logging
4
4
 
5
5
  from simtools.io_operations import io_handler
6
6
  from simtools.runners.simtel_runner import InvalidOutputFileError, SimtelRunner
7
+ from simtools.utils.general import clear_default_sim_telarray_cfg_directories
7
8
 
8
9
  __all__ = ["SimulatorArray"]
9
10
 
@@ -88,7 +89,7 @@ class SimulatorArray(SimtelRunner):
88
89
  command += f" {input_file}"
89
90
  command += f" > {self._log_file} 2>&1 || exit"
90
91
 
91
- return command
92
+ return clear_default_sim_telarray_cfg_directories(command)
92
93
 
93
94
  def _check_run_result(self, run_number=None):
94
95
  """
@@ -135,6 +135,8 @@ class SimulatorCameraEfficiency(SimtelRunner):
135
135
  command += f" {pixel_shape_cmd} {pixel_diameter}"
136
136
  if mirror_class == 0:
137
137
  command += f" -fmir {self._telescope_model.get_parameter_value('mirror_list')}"
138
+ if mirror_class == 2:
139
+ command += f" -fmir {self._telescope_model.get_parameter_value('fake_mirror_list')}"
138
140
  command += f" -fref {mirror_reflectivity}"
139
141
  if mirror_class == 2:
140
142
  command += " -m2"
@@ -154,10 +156,15 @@ class SimulatorCameraEfficiency(SimtelRunner):
154
156
  command += " 300" # Xmax
155
157
  command += f" {self._telescope_model.get_parameter_value('atmospheric_profile')}"
156
158
  command += f" {self.zenith_angle}"
157
- command += f" 2>{self._file_log}"
158
- command += f" >{self._file_simtel}"
159
159
 
160
- return f"cd {self._simtel_path.joinpath('sim_telarray')} && {command}"
160
+ # Remove the default sim_telarray configuration directories
161
+ command = general.clear_default_sim_telarray_cfg_directories(command)
162
+
163
+ return (
164
+ f"cd {self._simtel_path.joinpath('sim_telarray')} && {command}",
165
+ self._file_simtel,
166
+ self._file_log,
167
+ )
161
168
 
162
169
  def _check_run_result(self, run_number=None): # pylint: disable=unused-argument
163
170
  """Check run results.
@@ -169,7 +176,7 @@ class SimulatorCameraEfficiency(SimtelRunner):
169
176
  """
170
177
  # Checking run
171
178
  if not self._file_simtel.exists():
172
- msg = "Camera efficiency simulation results file does not exist"
179
+ msg = f"Camera efficiency simulation results file does not exist ({self._file_simtel})."
173
180
  self._logger.error(msg)
174
181
  raise RuntimeError(msg)
175
182
 
@@ -1,7 +1,7 @@
1
1
  """Simulation using the light emission package for calibration."""
2
2
 
3
3
  import logging
4
- import os
4
+ import stat
5
5
  from pathlib import Path
6
6
 
7
7
  import astropy.units as u
@@ -9,6 +9,7 @@ import numpy as np
9
9
 
10
10
  from simtools.io_operations import io_handler
11
11
  from simtools.runners.simtel_runner import SimtelRunner
12
+ from simtools.utils.general import clear_default_sim_telarray_cfg_directories
12
13
 
13
14
  __all__ = ["SimulatorLightEmission"]
14
15
 
@@ -360,7 +361,8 @@ class SimulatorLightEmission(SimtelRunner):
360
361
  f"{self.le_application[0]}_{self.le_application[1]}.ctsim.hdata\n",
361
362
  )
362
363
 
363
- return command
364
+ # Remove the default sim_telarray configuration directories
365
+ return clear_default_sim_telarray_cfg_directories(command)
364
366
 
365
367
  def _remove_line_from_config(self, file_path, line_prefix):
366
368
  """
@@ -469,5 +471,5 @@ class SimulatorLightEmission(SimtelRunner):
469
471
  file.write(f"{command_plot}\n\n")
470
472
  file.write("# End\n\n")
471
473
 
472
- os.system(f"chmod ug+x {_script_file}")
474
+ _script_file.chmod(_script_file.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP)
473
475
  return _script_file
@@ -8,6 +8,7 @@ import astropy.units as u
8
8
  from simtools.io_operations import io_handler
9
9
  from simtools.runners.simtel_runner import SimtelRunner
10
10
  from simtools.utils import names
11
+ from simtools.utils.general import clear_default_sim_telarray_cfg_directories
11
12
 
12
13
  __all__ = ["SimulatorRayTracing"]
13
14
 
@@ -191,9 +192,8 @@ class SimulatorRayTracing(SimtelRunner):
191
192
  command += super().get_config_option("mirror_align_random_distance", "0.")
192
193
  command += super().get_config_option("mirror_align_random_vertical", "0.,28.,0.,0.")
193
194
  command += " " + str(self._corsika_file)
194
- command += f" 2>&1 > {self._log_file} 2>&1"
195
195
 
196
- return command
196
+ return clear_default_sim_telarray_cfg_directories(command), self._log_file, self._log_file
197
197
 
198
198
  def _check_run_result(self, run_number=None): # pylint: disable=unused-argument
199
199
  """
simtools/simulator.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  import logging
4
4
  import re
5
+ import shutil
6
+ import tarfile
5
7
  from collections import defaultdict
6
8
  from pathlib import Path
7
9
 
@@ -68,7 +70,7 @@ class Simulator:
68
70
  self.runs = self._initialize_run_list()
69
71
  self._results = defaultdict(list)
70
72
  self._test = self.args_dict.get("test", False)
71
- self._submit_engine = self.args_dict.get("submit_engine", "local")
73
+ self.submit_engine = self.args_dict.get("submit_engine", "local")
72
74
  self._submit_options = self.args_dict.get("submit_options", None)
73
75
  self._extra_commands = extra_commands
74
76
 
@@ -265,7 +267,7 @@ class Simulator:
265
267
  input_file_list: str or list of str
266
268
  Single file or list of files of shower simulations.
267
269
  """
268
- self._logger.info(f"Submission command: {self._submit_engine}")
270
+ self._logger.info(f"Submission command: {self.submit_engine}")
269
271
 
270
272
  runs_and_files_to_submit = self._get_runs_and_files_to_submit(
271
273
  input_file_list=input_file_list
@@ -281,7 +283,7 @@ class Simulator:
281
283
  )
282
284
 
283
285
  job_manager = JobManager(
284
- submit_engine=self._submit_engine,
286
+ submit_engine=self.submit_engine,
285
287
  submit_options=self._submit_options,
286
288
  test=self._test,
287
289
  )
@@ -389,50 +391,46 @@ class Simulator:
389
391
  run number
390
392
 
391
393
  """
392
- self._results["output"].append(
393
- str(self._simulation_runner.get_file_name(file_type="output", run_number=run_number))
394
+ keys = ["output", "sub_out", "log", "input", "hist", "corsika_log"]
395
+ defaults = {key: None for key in keys}
396
+ results = {key: defaults[key] for key in keys}
397
+ results["output"] = str(
398
+ self._simulation_runner.get_file_name(file_type="output", run_number=run_number)
394
399
  )
395
- self._results["sub_out"].append(
396
- str(
397
- self._simulation_runner.get_file_name(
398
- file_type="sub_log", run_number=run_number, mode="out"
399
- )
400
+ results["sub_out"] = str(
401
+ self._simulation_runner.get_file_name(
402
+ file_type="sub_log", mode="out", run_number=run_number
400
403
  )
401
404
  )
402
405
 
403
- if self.simulation_software in ["simtel", "corsika_simtel"]:
404
- self._results["log"].append(
405
- str(
406
- self._simulation_runner.get_file_name(
407
- simulation_software="simtel", file_type="log", run_number=run_number
408
- )
406
+ if "simtel" in self.simulation_software:
407
+ results["log"] = str(
408
+ self._simulation_runner.get_file_name(
409
+ file_type="log", simulation_software="simtel", run_number=run_number
409
410
  )
410
411
  )
411
- self._results["input"].append(str(file))
412
- self._results["hist"].append(
413
- str(
414
- self._simulation_runner.get_file_name(
415
- simulation_software="simtel", file_type="histogram", run_number=run_number
416
- )
412
+ results["input"] = str(file)
413
+ results["hist"] = str(
414
+ self._simulation_runner.get_file_name(
415
+ file_type="histogram", simulation_software="simtel", run_number=run_number
417
416
  )
418
417
  )
419
- else:
420
- self._results["corsika_autoinputs_log"].append(
421
- str(
422
- self._simulation_runner.get_file_name(
423
- simulation_software="corsika", file_type="log", run_number=run_number
424
- )
418
+
419
+ if "corsika" in self.simulation_software:
420
+ results["corsika_log"] = str(
421
+ self._simulation_runner.get_file_name(
422
+ file_type="corsika_log", simulation_software="corsika", run_number=run_number
425
423
  )
426
424
  )
427
- self._results["input"].append(None)
428
- self._results["hist"].append(None)
429
- self._results["log"].append(None)
425
+
426
+ for key in keys:
427
+ self._results[key].append(results[key])
430
428
 
431
429
  def get_file_list(self, file_type="output"):
432
430
  """
433
431
  Get list of files generated by simulations.
434
432
 
435
- Options are "input", "output", "hist", "log".
433
+ Options are "input", "output", "hist", "log", "corsika_log".
436
434
  Not all file types are available for all simulation types.
437
435
  Returns an empty list for an unknown file type.
438
436
 
@@ -548,7 +546,7 @@ class Simulator:
548
546
 
549
547
  def save_file_lists(self):
550
548
  """Save files lists for output and log files."""
551
- for file_type in ["output", "log", "hist"]:
549
+ for file_type in ["output", "log", "corsika_log", "hist"]:
552
550
  file_name = self.io_handler.get_output_directory(label=self.label).joinpath(
553
551
  f"{file_type}_files.txt"
554
552
  )
@@ -560,3 +558,52 @@ class Simulator:
560
558
  f.write(f"{line}\n")
561
559
  else:
562
560
  self._logger.debug(f"No files to save for {file_type} files.")
561
+
562
+ def pack_for_register(self, directory_for_grid_upload=None):
563
+ """
564
+ Pack simulation output files for registering on the grid.
565
+
566
+ Parameters
567
+ ----------
568
+ directory_for_grid_upload: str
569
+ Directory for the tarball with output files.
570
+
571
+ """
572
+ self._logger.info(
573
+ f"Packing the output files for registering on the grid ({directory_for_grid_upload})"
574
+ )
575
+ output_files = self.get_file_list(file_type="output")
576
+ log_files = self.get_file_list(file_type="log")
577
+ corsika_log_files = self.get_file_list(file_type="corsika_log")
578
+ histogram_files = self.get_file_list(file_type="hist")
579
+ tar_file_name = Path(log_files[0]).name.replace("log.gz", "log_hist.tar.gz")
580
+ directory_for_grid_upload = (
581
+ Path(directory_for_grid_upload)
582
+ if directory_for_grid_upload
583
+ else self.io_handler.get_output_directory(label=self.label).joinpath(
584
+ "directory_for_grid_upload"
585
+ )
586
+ )
587
+ directory_for_grid_upload.mkdir(parents=True, exist_ok=True)
588
+
589
+ tar_file_name = directory_for_grid_upload.joinpath(tar_file_name)
590
+
591
+ with tarfile.open(tar_file_name, "w:gz") as tar:
592
+ files_to_tar = (
593
+ (log_files[:1] if log_files else [])
594
+ + (histogram_files[:1] if histogram_files else [])
595
+ + (corsika_log_files[:1] if corsika_log_files else [])
596
+ )
597
+ for file_to_tar in files_to_tar:
598
+ tar.add(file_to_tar, arcname=Path(file_to_tar).name)
599
+
600
+ for file_to_move in [*output_files]:
601
+ source_file = Path(file_to_move)
602
+ destination_file = directory_for_grid_upload / source_file.name
603
+ if destination_file.exists():
604
+ self._logger.warning(f"Overwriting existing file: {destination_file}")
605
+ # Note that this will overwrite previous files which exist in the directory
606
+ # It should be fine for normal production since each run is on a separate node
607
+ # so no files are expected there.
608
+ shutil.move(source_file, destination_file)
609
+ self._logger.info(f"Output files for the grid placed in {directory_for_grid_upload!s}")