gammasimtools 0.16.0__py3-none-any.whl → 0.17.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 (63) hide show
  1. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/METADATA +4 -2
  2. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/RECORD +60 -54
  3. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/entry_points.txt +3 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/derive_ctao_array_layouts.py +5 -5
  7. simtools/applications/generate_simtel_event_data.py +36 -46
  8. simtools/applications/merge_tables.py +104 -0
  9. simtools/applications/plot_array_layout.py +145 -258
  10. simtools/applications/production_derive_corsika_limits.py +35 -220
  11. simtools/applications/production_derive_statistics.py +77 -43
  12. simtools/applications/simulate_light_emission.py +1 -0
  13. simtools/applications/simulate_prod.py +30 -18
  14. simtools/applications/simulate_prod_htcondor_generator.py +0 -1
  15. simtools/applications/submit_array_layouts.py +93 -0
  16. simtools/applications/verify_simulation_model_production_tables.py +52 -0
  17. simtools/camera/camera_efficiency.py +3 -3
  18. simtools/configuration/commandline_parser.py +28 -34
  19. simtools/configuration/configurator.py +0 -4
  20. simtools/corsika/corsika_config.py +17 -12
  21. simtools/corsika/primary_particle.py +46 -13
  22. simtools/data_model/metadata_collector.py +7 -3
  23. simtools/db/db_handler.py +11 -11
  24. simtools/db/db_model_upload.py +2 -2
  25. simtools/io_operations/io_handler.py +2 -2
  26. simtools/io_operations/io_table_handler.py +345 -0
  27. simtools/job_execution/htcondor_script_generator.py +2 -2
  28. simtools/job_execution/job_manager.py +7 -121
  29. simtools/layout/array_layout_utils.py +385 -0
  30. simtools/model/array_model.py +5 -0
  31. simtools/model/model_repository.py +134 -0
  32. simtools/production_configuration/{calculate_statistical_errors_grid_point.py → calculate_statistical_uncertainties_grid_point.py} +101 -112
  33. simtools/production_configuration/derive_corsika_limits.py +239 -111
  34. simtools/production_configuration/derive_corsika_limits_grid.py +189 -0
  35. simtools/production_configuration/derive_production_statistics.py +57 -26
  36. simtools/production_configuration/derive_production_statistics_handler.py +70 -37
  37. simtools/production_configuration/interpolation_handler.py +296 -94
  38. simtools/ray_tracing/ray_tracing.py +7 -6
  39. simtools/reporting/docs_read_parameters.py +104 -62
  40. simtools/runners/corsika_simtel_runner.py +4 -1
  41. simtools/runners/runner_services.py +5 -4
  42. simtools/schemas/model_parameters/dsum_threshold.schema.yml +41 -0
  43. simtools/schemas/production_configuration_metrics.schema.yml +2 -2
  44. simtools/simtel/simtel_config_writer.py +34 -14
  45. simtools/simtel/simtel_io_event_reader.py +301 -194
  46. simtools/simtel/simtel_io_event_writer.py +207 -227
  47. simtools/simtel/simtel_io_file_info.py +9 -4
  48. simtools/simtel/simtel_io_metadata.py +20 -5
  49. simtools/simtel/simulator_array.py +2 -2
  50. simtools/simtel/simulator_light_emission.py +79 -34
  51. simtools/simtel/simulator_ray_tracing.py +2 -2
  52. simtools/simulator.py +101 -68
  53. simtools/testing/validate_output.py +4 -1
  54. simtools/utils/general.py +1 -1
  55. simtools/utils/names.py +5 -5
  56. simtools/visualization/plot_array_layout.py +242 -0
  57. simtools/visualization/plot_pixels.py +681 -0
  58. simtools/visualization/visualize.py +3 -219
  59. simtools/applications/production_generate_simulation_config.py +0 -152
  60. simtools/layout/ctao_array_layouts.py +0 -172
  61. simtools/production_configuration/generate_simulation_config.py +0 -158
  62. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/licenses/LICENSE +0 -0
  63. {gammasimtools-0.16.0.dist-info → gammasimtools-0.17.0.dist-info}/top_level.txt +0 -0
@@ -12,11 +12,13 @@ from pathlib import Path
12
12
  import numpy as np
13
13
 
14
14
  from simtools.db import db_handler
15
+ from simtools.io_operations import io_handler
15
16
  from simtools.model.telescope_model import TelescopeModel
17
+ from simtools.utils import general as gen
16
18
  from simtools.utils import names
19
+ from simtools.visualization import plot_pixels
17
20
 
18
21
  logger = logging.getLogger()
19
- IMAGE_PATH = "../../_images"
20
22
 
21
23
 
22
24
  class ReadParameters:
@@ -62,41 +64,61 @@ class ReadParameters:
62
64
  )
63
65
  self._model_version = model_version
64
66
 
65
- def _convert_to_md(self, parameter, input_file):
67
+ def _convert_to_md(self, parameter, parameter_version, input_file):
66
68
  """Convert a file to a Markdown file, preserving formatting."""
67
69
  input_file = Path(input_file)
70
+
68
71
  output_data_path = Path(self.output_path / "_data_files")
69
72
  output_data_path.mkdir(parents=True, exist_ok=True)
70
73
  output_file_name = Path(input_file.stem + ".md")
71
74
  output_file = output_data_path / output_file_name
75
+ image_name = f"{self.array_element}_{parameter}_{self.model_version.replace('.', '-')}"
76
+ outpath = Path(io_handler.IOHandler().get_output_directory().parent / "_images")
77
+ outpath.mkdir(parents=True, exist_ok=True)
78
+ image_path = Path(f"{outpath}/{image_name}")
79
+
80
+ if parameter == "camera_config_file" and parameter_version:
81
+ image_path = Path(f"{outpath}/{input_file.stem.replace('.', '-')}")
82
+ if not (image_path.with_suffix(".png")).exists():
83
+ logger.info("Plotting camera configuration file: %s", input_file.name)
84
+ plot_config = {
85
+ "file_name": input_file.name,
86
+ "telescope": self.array_element,
87
+ "parameter_version": parameter_version,
88
+ "site": self.site,
89
+ "model_version": self.model_version,
90
+ "parameter": parameter,
91
+ }
92
+
93
+ plot_pixels.plot(
94
+ config=plot_config,
95
+ output_file=image_path,
96
+ db_config=self.db_config,
97
+ )
98
+ else:
99
+ logger.info(
100
+ "Camera configuration file plot already exists: %s",
101
+ image_path.with_suffix(".png"),
102
+ )
72
103
 
73
104
  try:
74
- # First try with utf-8
75
- try:
76
- with input_file.open("r", encoding="utf-8") as infile:
77
- file_contents = infile.read()
78
- except UnicodeDecodeError:
79
- # If utf-8 fails, try with latin-1 (which can read any byte sequence)
80
- with input_file.open("r", encoding="latin-1") as infile:
81
- file_contents = infile.read()
105
+ # with input_file.open("r", encoding="utf-8") as infile:
106
+ file_contents = gen.read_file_encoded_in_utf_or_latin(input_file)
82
107
 
83
108
  if self.model_version is not None:
84
109
  with output_file.open("w", encoding="utf-8") as outfile:
85
110
  outfile.write(f"# {input_file.stem}\n")
111
+ outfile.write(f"![Parameter plot.]({image_path}.png)\n\n")
86
112
  outfile.write(
87
- "The full file can be found in the Simulation Model repository [here]"
113
+ "\n\nThe full file can be found in the Simulation Model repository [here]"
88
114
  "(https://gitlab.cta-observatory.org/cta-science/simulations/"
89
115
  "simulation-model/simulation-models/-/blob/main/simulation-models/"
90
116
  f"model_parameters/Files/{input_file.name}).\n\n"
91
117
  )
92
- outfile.write(
93
- f"![Parameter plot.](../{IMAGE_PATH}/{self.array_element}_"
94
- f"{parameter}_{self.model_version.replace('.', '-')}.png)\n"
95
- )
96
118
  outfile.write("\n\n")
97
119
  outfile.write("The first 30 lines of the file are:\n")
98
120
  outfile.write("```\n")
99
- first_30_lines = "\n".join(file_contents.splitlines()[:30])
121
+ first_30_lines = "".join(file_contents[:30])
100
122
  outfile.write(first_30_lines)
101
123
  outfile.write("\n```")
102
124
 
@@ -106,11 +128,19 @@ class ReadParameters:
106
128
 
107
129
  return f"_data_files/{output_file_name}"
108
130
 
109
- def _format_parameter_value(self, parameter, value_data, unit, file_flag):
131
+ def _format_parameter_value(
132
+ self, parameter, value_data, unit, file_flag, parameter_version=None
133
+ ):
110
134
  """Format parameter value based on type."""
111
135
  if file_flag:
112
136
  input_file_name = f"{self.output_path}/model/{value_data}"
113
- output_file_name = self._convert_to_md(parameter, input_file_name)
137
+ if parameter_version is None:
138
+ return (
139
+ f"[{Path(value_data).name}](https://gitlab.cta-observatory.org/"
140
+ "cta-science/simulations/simulation-model/simulation-models/-/blob/main/"
141
+ f"simulation-models/model_parameters/Files/{value_data})"
142
+ ).strip()
143
+ output_file_name = self._convert_to_md(parameter, parameter_version, input_file_name)
114
144
  return f"[{Path(value_data).name}]({output_file_name})".strip()
115
145
  if isinstance(value_data, (str | int | float)):
116
146
  return f"{value_data} {unit}".strip()
@@ -217,8 +247,10 @@ class ReadParameters:
217
247
  continue
218
248
 
219
249
  file_flag = parameter_data.get("file", False)
220
- value = self._format_parameter_value(parameter_name, value_data, unit, file_flag)
221
250
  parameter_version = parameter_data.get("parameter_version")
251
+ value = self._format_parameter_value(
252
+ parameter_name, value_data, unit, file_flag, parameter_version=None
253
+ )
222
254
  model_version = version
223
255
 
224
256
  # Group the data by parameter version and store model versions as a list
@@ -234,25 +266,28 @@ class ReadParameters:
234
266
  return self._group_model_versions_by_parameter_version(grouped_data)
235
267
 
236
268
  def get_all_parameter_descriptions(self, collection="telescopes"):
237
- """
238
- Get descriptions for all model parameters.
269
+ """Get descriptions for all model parameters.
239
270
 
240
271
  Returns
241
272
  -------
242
- tuple: A tuple containing two dictionaries:
243
- - parameter_description: Maps parameter names to their descriptions.
244
- - short_description: Maps parameter names to their short descriptions.
245
- - inst_class: Maps parameter names to their respective class.
273
+ dict
274
+ Nested dictionaries with first key as the parameter name and
275
+ the following dictionary as the value:
276
+ - key: description, value: description of the parameter.
277
+ - key: short_description, value: short description of the parameter.
278
+ - key: inst_class, value: class, for eg. Structure, Camera, etc.
246
279
  """
247
- parameter_description, short_description, inst_class = {}, {}, {}
280
+ parameter_dict = {}
248
281
 
249
282
  for instrument_class in names.db_collection_to_instrument_class_key(collection):
250
283
  for parameter, details in names.model_parameters(instrument_class).items():
251
- parameter_description[parameter] = details.get("description")
252
- short_description[parameter] = details.get("short_description")
253
- inst_class[parameter] = instrument_class
284
+ parameter_dict[parameter] = {
285
+ "description": details.get("description"),
286
+ "short_description": details.get("short_description"),
287
+ "inst_class": instrument_class,
288
+ }
254
289
 
255
- return parameter_description, short_description, inst_class
290
+ return parameter_dict
256
291
 
257
292
  def get_array_element_parameter_data(self, telescope_model, collection="telescopes"):
258
293
  """
@@ -292,13 +327,15 @@ class ReadParameters:
292
327
  continue
293
328
 
294
329
  file_flag = parameter_data.get("file", False)
295
- value = self._format_parameter_value(parameter, value_data, unit, file_flag)
330
+ value = self._format_parameter_value(
331
+ parameter, value_data, unit, file_flag, parameter_version
332
+ )
296
333
 
297
- description = parameter_descriptions[0].get(parameter)
298
- short_description = parameter_descriptions[1].get(parameter)
299
- if short_description is None:
300
- short_description = description
301
- inst_class = parameter_descriptions[2].get(parameter)
334
+ description = parameter_descriptions.get(parameter).get("description")
335
+ short_description = (
336
+ parameter_descriptions.get(parameter).get("short_description") or description
337
+ )
338
+ inst_class = parameter_descriptions.get(parameter).get("inst_class")
302
339
 
303
340
  matching_instrument = parameter_data["instrument"] == telescope_model.name
304
341
  if not names.is_design_type(telescope_model.name) and matching_instrument:
@@ -376,8 +413,10 @@ class ReadParameters:
376
413
 
377
414
  data = []
378
415
  for parameter, parameter_data in param_dict.items():
379
- description = parameter_descriptions[0].get(parameter)
380
- short_description = parameter_descriptions[1].get(parameter, description)
416
+ description = parameter_descriptions.get(parameter).get("description")
417
+ short_description = parameter_descriptions.get(parameter).get(
418
+ "short_description", description
419
+ )
381
420
  value_data = parameter_data.get("value")
382
421
 
383
422
  if value_data is None:
@@ -386,7 +425,9 @@ class ReadParameters:
386
425
  unit = parameter_data.get("unit") or " "
387
426
  file_flag = parameter_data.get("file", False)
388
427
  parameter_version = parameter_data.get("parameter_version")
389
- value = self._format_parameter_value(parameter, value_data, unit, file_flag)
428
+ value = self._format_parameter_value(
429
+ parameter, value_data, unit, file_flag, parameter_version
430
+ )
390
431
 
391
432
  data.append(
392
433
  [
@@ -508,10 +549,12 @@ class ReadParameters:
508
549
  continue
509
550
 
510
551
  output_filename = output_path / f"{parameter}.md"
511
- description = self.get_all_parameter_descriptions(collection=collection)[0].get(
512
- parameter,
513
- self.get_all_parameter_descriptions(collection="telescopes")[0].get(parameter),
514
- )
552
+
553
+ parameter_descriptions = self.get_all_parameter_descriptions(collection=collection).get(
554
+ parameter
555
+ ) or self.get_all_parameter_descriptions(collection="telescopes").get(parameter)
556
+
557
+ description = parameter_descriptions.get("description")
515
558
  with output_filename.open("w", encoding="utf-8") as file:
516
559
  # Write header
517
560
  file.write(
@@ -534,14 +577,12 @@ class ReadParameters:
534
577
  file.write(
535
578
  f"| {item['parameter_version']} |"
536
579
  f" {item['model_version']} |"
537
- f"{item['value'].replace('](', '](../')} |\n"
580
+ f"{item['value']} |\n"
538
581
  )
539
582
 
540
583
  file.write("\n")
541
584
  if comparison_data.get(parameter)[0]["file_flag"]:
542
- file.write(
543
- f"![Parameter plot.]({IMAGE_PATH}/{self.array_element}_{parameter}.png)"
544
- )
585
+ file.write(f"![Parameter plot.](/_images/{self.array_element}_{parameter}.png)")
545
586
 
546
587
  def _write_array_layouts_section(self, file, layouts):
547
588
  """Write the array layouts section of the report."""
@@ -556,7 +597,7 @@ class ReadParameters:
556
597
  file.write("\n")
557
598
  version = self.model_version.replace(".", "-")
558
599
  filename = f"OBS-{self.site}_{layout_name}_{version}.png"
559
- image_path = f"{IMAGE_PATH}/{filename}"
600
+ image_path = f"/_images/{filename}"
560
601
  file.write(f"![{layout_name} Layout]({image_path})\n\n")
561
602
  file.write("\n")
562
603
 
@@ -606,7 +647,9 @@ class ReadParameters:
606
647
  f"(#array-trigger-configurations) | {parameter_version} |\n"
607
648
  )
608
649
  else:
609
- formatted_value = self._format_parameter_value(param_name, value, unit, file_flag)
650
+ formatted_value = self._format_parameter_value(
651
+ param_name, value, unit, file_flag, parameter_version
652
+ )
610
653
  file.write(f"| {param_name} | {formatted_value} | {parameter_version} |\n")
611
654
  file.write("\n")
612
655
 
@@ -645,7 +688,7 @@ class ReadParameters:
645
688
 
646
689
  def get_calibration_data(self, all_parameter_data, array_element):
647
690
  """Get calibration data and descriptions for a given array element."""
648
- parameter_descriptions = self.get_all_parameter_descriptions(
691
+ calibration_descriptions = self.get_all_parameter_descriptions(
649
692
  collection="calibration_devices"
650
693
  )
651
694
  # get descriptions of array element positions from the telescope collection
@@ -654,6 +697,10 @@ class ReadParameters:
654
697
  class_grouped_data = {}
655
698
 
656
699
  for parameter in all_parameter_data.keys():
700
+ parameter_descriptions = calibration_descriptions.get(
701
+ parameter
702
+ ) or telescope_descriptions.get(parameter)
703
+
657
704
  parameter_data = all_parameter_data.get(parameter)
658
705
  parameter_version = parameter_data.get("parameter_version")
659
706
  unit = parameter_data.get("unit") or " "
@@ -663,19 +710,15 @@ class ReadParameters:
663
710
  continue
664
711
 
665
712
  file_flag = parameter_data.get("file", False)
666
- value = self._format_parameter_value(parameter, value_data, unit, file_flag)
667
-
668
- description = parameter_descriptions[0].get(
669
- parameter, telescope_descriptions[0].get(parameter)
670
- )
671
- short_description = (
672
- parameter_descriptions[1].get(parameter, telescope_descriptions[1].get(parameter))
673
- or description
674
- )
675
- inst_class = parameter_descriptions[2].get(
676
- parameter, telescope_descriptions[2].get(parameter)
713
+ value = self._format_parameter_value(
714
+ parameter, value_data, unit, file_flag, parameter_version
677
715
  )
678
716
 
717
+ description = parameter_descriptions.get("description")
718
+ short_description = parameter_descriptions.get("short_description") or description
719
+
720
+ inst_class = parameter_descriptions.get("inst_class")
721
+
679
722
  matching_instrument = parameter_data["instrument"] == array_element
680
723
  if not names.is_design_type(array_element) and matching_instrument:
681
724
  parameter = f"***{parameter}***"
@@ -764,5 +807,4 @@ class ReadParameters:
764
807
  for calibration_device in array_elements:
765
808
  self.site = names.get_site_from_array_element_name(calibration_device)
766
809
  self.array_element = calibration_device
767
- print("cal: ", calibration_device)
768
810
  self.produce_model_parameter_reports(collection="calibration_devices")
@@ -42,6 +42,7 @@ class CorsikaSimtelRunner:
42
42
  keep_seeds=False,
43
43
  use_multipipe=False,
44
44
  sim_telarray_seeds=None,
45
+ sequential=False,
45
46
  ):
46
47
  self._logger = logging.getLogger(__name__)
47
48
  self.corsika_config = (
@@ -53,6 +54,7 @@ class CorsikaSimtelRunner:
53
54
  self._simtel_path = simtel_path
54
55
  self.sim_telarray_seeds = sim_telarray_seeds
55
56
  self.label = label
57
+ self.sequential = "--sequential" if sequential else ""
56
58
 
57
59
  self.base_corsika_config.set_output_file_and_directory(use_multipipe)
58
60
  self.corsika_runner = CorsikaRunner(
@@ -167,7 +169,8 @@ class CorsikaSimtelRunner:
167
169
  )
168
170
  with open(multipipe_script, "w", encoding="utf-8") as file:
169
171
  multipipe_command = Path(self._simtel_path).joinpath(
170
- f"sim_telarray/bin/multipipe_corsika -c {multipipe_file} || echo 'Fan-out failed'"
172
+ f"sim_telarray/bin/multipipe_corsika -c {multipipe_file} {self.sequential} "
173
+ "|| echo 'Fan-out failed'"
171
174
  )
172
175
  file.write(f"{multipipe_command}")
173
176
 
@@ -138,9 +138,9 @@ class RunnerServices:
138
138
  file_label = f"_{info_for_file_name['label']}" if info_for_file_name.get("label") else ""
139
139
  zenith = self.corsika_config.get_config_parameter("THETAP")[0]
140
140
  azimuth = self.corsika_config.azimuth_angle
141
- run_dir = self._get_run_number_string(info_for_file_name["run_number"])
141
+ run_number_string = self._get_run_number_string(info_for_file_name["run_number"])
142
142
  return (
143
- f"{run_dir}_{info_for_file_name['primary']}_"
143
+ f"{info_for_file_name['primary']}_{run_number_string}_"
144
144
  f"za{round(zenith):02}deg_azm{azimuth:03}deg_"
145
145
  f"{info_for_file_name['site']}_{info_for_file_name['array_name']}_"
146
146
  f"{info_for_file_name['model_version']}{file_label}"
@@ -189,8 +189,9 @@ class RunnerServices:
189
189
  """
190
190
  data_suffixes = {
191
191
  "output": ".zst",
192
- "corsika_output": ".zst",
192
+ "corsika_output": ".corsika.zst",
193
193
  "simtel_output": ".simtel.zst",
194
+ "event_data": ".reduced_event_data.hdf5",
194
195
  }
195
196
  run_dir = self._get_run_number_string(run_number)
196
197
  data_run_dir = self.directory["data"].joinpath(run_dir)
@@ -261,7 +262,7 @@ class RunnerServices:
261
262
  if file_type in ["log", "histogram", "corsika_log"]:
262
263
  return self._get_log_file_path(file_type, file_name)
263
264
 
264
- if file_type in ["output", "corsika_output", "simtel_output"]:
265
+ if file_type in ["output", "corsika_output", "simtel_output", "event_data"]:
265
266
  return self._get_data_file_path(file_type, file_name, run_number)
266
267
 
267
268
  if file_type in ("sub_log", "sub_script"):
@@ -1,6 +1,47 @@
1
1
  %YAML 1.2
2
2
  ---
3
3
  title: Schema for dsum_threshold model parameter
4
+ version: 0.2.0
5
+ meta_schema: simpipe-schema
6
+ meta_schema_url: https://raw.githubusercontent.com/gammasim/simtools/main/src/simtools/schemas/model_parameter_and_data_schema.metaschema.yml
7
+ meta_schema_version: 0.1.0
8
+ name: dsum_threshold
9
+ description: |-
10
+ The amplitude level above pedestal sum above which a
11
+ digital sum leads to a telescope trigger.
12
+ Note that, like for discriminator/comparator and analog sum, the signal
13
+ must exceed (\'>\') the threshold here before we declare the telescope
14
+ triggered. The assigned threshold value would have to be one count lower
15
+ than in a camera-internal trigger implementation (like MSTx-FlashCam) where
16
+ reaching (\'>=\') the threshold is enough.
17
+ short_description: Amplitude level above which a digital sum leads to a telescope
18
+ trigger.
19
+ data:
20
+ - type: int64
21
+ unit: count
22
+ default: 0
23
+ allowed_range:
24
+ min: 0
25
+ condition: default_trigger==DigitalSum
26
+ instrument:
27
+ class: Camera
28
+ type:
29
+ - MSTx-NectarCam
30
+ - MSTx-FlashCam
31
+ activity:
32
+ setting:
33
+ - SetParameterFromExternal
34
+ - SetTriggerThresholdsFromRateScan
35
+ validation:
36
+ - ValidateParameterByExpert
37
+ - ValidateTriggerPerformance
38
+ source:
39
+ - Observation execution
40
+ simulation_software:
41
+ - name: sim_telarray
42
+ ...
43
+ ---
44
+ title: Schema for dsum_threshold model parameter
4
45
  version: 0.1.0
5
46
  meta_schema: simpipe-schema
6
47
  meta_schema_url: https://raw.githubusercontent.com/gammasim/simtools/main/src/simtools/schemas/model_parameter_and_data_schema.metaschema.yml
@@ -29,12 +29,12 @@ definitions:
29
29
  description:
30
30
  type: string
31
31
  description: Description of the error metric.
32
- target_error:
32
+ target_uncertainty:
33
33
  $ref: '#/definitions/TargetError'
34
34
  energy_range:
35
35
  $ref: '#/definitions/EnergyRange'
36
36
  required:
37
- - target_error
37
+ - target_uncertainty
38
38
  - energy_range
39
39
 
40
40
  TargetError:
@@ -54,12 +54,20 @@ class SimtelConfigWriter:
54
54
  Layout name.
55
55
  label: str
56
56
  Instance label. Important for output file naming.
57
+ simtel_path: str or Path
58
+ Path to the sim_telarray installation directory.
57
59
  """
58
60
 
59
61
  TAB = " " * 3
60
62
 
61
63
  def __init__(
62
- self, site, model_version, layout_name=None, telescope_model_name=None, label=None
64
+ self,
65
+ site,
66
+ model_version,
67
+ layout_name=None,
68
+ telescope_model_name=None,
69
+ label=None,
70
+ simtel_path=None,
63
71
  ):
64
72
  """Initialize SimtelConfigWriter."""
65
73
  self._logger = logging.getLogger(__name__)
@@ -70,10 +78,9 @@ class SimtelConfigWriter:
70
78
  self._label = label
71
79
  self._layout_name = layout_name
72
80
  self._telescope_model_name = telescope_model_name
81
+ self._simtel_path = simtel_path
73
82
 
74
- def write_telescope_config_file(
75
- self, config_file_path, parameters, telescope_name=None, write_dummy_config=False
76
- ):
83
+ def write_telescope_config_file(self, config_file_path, parameters, telescope_name=None):
77
84
  """
78
85
  Write the sim_telarray config file for a single telescope.
79
86
 
@@ -85,8 +92,6 @@ class SimtelConfigWriter:
85
92
  Model parameters
86
93
  telescope_name: str
87
94
  Name of the telescope (use self._telescope_model_name if None)
88
- write_dummy_config: bool
89
- Flag to write a dummy telescope configuration file.
90
95
  """
91
96
  self._logger.debug(f"Writing telescope config file {config_file_path}")
92
97
 
@@ -97,8 +102,6 @@ class SimtelConfigWriter:
97
102
  file.write("#ifdef TELESCOPE\n")
98
103
  file.write(f" echo Configuration for {telescope_name} - TELESCOPE $(TELESCOPE)\n")
99
104
  file.write("#endif\n\n")
100
- if write_dummy_config:
101
- file.write("#define DUMMY_CONFIG 1\n")
102
105
 
103
106
  for par, value in parameters.items():
104
107
  simtel_name, value = self._convert_model_parameters_to_simtel_format(
@@ -252,6 +255,8 @@ class SimtelConfigWriter:
252
255
  file.write(self.TAB + f"echo ModelVersion: {self._model_version}\n")
253
256
  file.write(self.TAB + "echo *****************************\n\n")
254
257
 
258
+ self._write_simtools_parameters(file)
259
+
255
260
  self._write_site_parameters(
256
261
  file,
257
262
  site_model.parameters,
@@ -392,6 +397,23 @@ class SimtelConfigWriter:
392
397
  header += f"{comment_char}\n"
393
398
  file.write(header)
394
399
 
400
+ def _write_simtools_parameters(self, file):
401
+ """Write simtools-specific parameters."""
402
+ meta_items = {
403
+ "simtools_version": simtools.version.__version__,
404
+ "simtools_model_production_version": self._model_version,
405
+ }
406
+ try:
407
+ build_opts = gen.collect_data_from_file(Path(self._simtel_path) / "build_opts.yml")
408
+ for key, value in build_opts.items():
409
+ meta_items[f"simtools_{key}"] = value
410
+ except (FileNotFoundError, TypeError):
411
+ pass # don't expect build_opts.yml to be present on all systems
412
+
413
+ file.write(f"{self.TAB}% Simtools parameters\n")
414
+ for key, value in meta_items.items():
415
+ file.write(f"{self.TAB}metaparam global set {key} = {value}\n")
416
+
395
417
  def _write_site_parameters(
396
418
  self, file, site_parameters, model_path, telescope_model, sim_telarray_seeds=None
397
419
  ):
@@ -556,9 +578,9 @@ class SimtelConfigWriter:
556
578
  "disc_bins": 10,
557
579
  "fadc_sum_bins": 10,
558
580
  "fadc_sum_offset": 0,
559
- "asum_threshold": 0,
560
- "dsum_threshold": 0,
561
- "discriminator_threshold": 1,
581
+ "asum_threshold": 9999,
582
+ "dsum_threshold": 9999,
583
+ "discriminator_threshold": 9999,
562
584
  "fadc_amplitude": 1.0,
563
585
  "discriminator_amplitude": 1.0,
564
586
  }
@@ -567,9 +589,7 @@ class SimtelConfigWriter:
567
589
  if key in parameters:
568
590
  parameters[key]["value"] = val
569
591
 
570
- self.write_telescope_config_file(
571
- config_file_path, parameters, telescope_name, write_dummy_config=True
572
- )
592
+ self.write_telescope_config_file(config_file_path, parameters, telescope_name)
573
593
 
574
594
  config_file_directory = Path(config_file_path).parent
575
595
  self._write_dummy_mirror_list_files(config_file_directory, telescope_name)