gammasimtools 0.14.0__py3-none-any.whl → 0.16.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 (223) hide show
  1. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/METADATA +2 -32
  2. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/RECORD +222 -214
  3. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/entry_points.txt +5 -2
  5. simtools/_version.py +2 -2
  6. simtools/applications/calculate_trigger_rate.py +10 -10
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +16 -16
  8. simtools/applications/convert_model_parameter_from_simtel.py +1 -1
  9. simtools/applications/derive_psf_parameters.py +12 -9
  10. simtools/applications/docs_produce_array_element_report.py +3 -3
  11. simtools/applications/docs_produce_calibration_reports.py +49 -0
  12. simtools/applications/docs_produce_simulation_configuration_report.py +50 -0
  13. simtools/applications/{generate_simtel_array_histograms.py → generate_sim_telarray_histograms.py} +2 -2
  14. simtools/applications/production_derive_corsika_limits.py +63 -10
  15. simtools/applications/production_derive_statistics.py +125 -0
  16. simtools/applications/production_generate_grid.py +197 -0
  17. simtools/applications/production_generate_simulation_config.py +0 -10
  18. simtools/applications/simulate_light_emission.py +5 -13
  19. simtools/applications/simulate_prod.py +16 -4
  20. simtools/applications/validate_cumulative_psf.py +6 -4
  21. simtools/applications/validate_file_using_schema.py +7 -3
  22. simtools/applications/validate_optics.py +5 -4
  23. simtools/camera/camera_efficiency.py +14 -39
  24. simtools/configuration/commandline_parser.py +4 -3
  25. simtools/configuration/configurator.py +10 -0
  26. simtools/corsika/corsika_config.py +103 -5
  27. simtools/data_model/format_checkers.py +9 -0
  28. simtools/data_model/model_data_writer.py +3 -0
  29. simtools/data_model/schema.py +27 -16
  30. simtools/data_model/validate_data.py +27 -7
  31. simtools/db/db_handler.py +10 -4
  32. simtools/layout/array_layout.py +1 -0
  33. simtools/model/array_model.py +63 -29
  34. simtools/model/model_parameter.py +76 -51
  35. simtools/model/model_utils.py +43 -1
  36. simtools/model/site_model.py +3 -2
  37. simtools/model/telescope_model.py +4 -4
  38. simtools/production_configuration/calculate_statistical_errors_grid_point.py +0 -4
  39. simtools/production_configuration/{event_scaler.py → derive_production_statistics.py} +24 -20
  40. simtools/production_configuration/derive_production_statistics_handler.py +119 -0
  41. simtools/production_configuration/generate_production_grid.py +364 -0
  42. simtools/production_configuration/generate_simulation_config.py +9 -9
  43. simtools/production_configuration/interpolation_handler.py +16 -11
  44. simtools/ray_tracing/mirror_panel_psf.py +16 -20
  45. simtools/ray_tracing/psf_analysis.py +2 -2
  46. simtools/ray_tracing/ray_tracing.py +5 -1
  47. simtools/reporting/docs_read_parameters.py +361 -58
  48. simtools/runners/corsika_runner.py +11 -1
  49. simtools/runners/corsika_simtel_runner.py +80 -89
  50. simtools/runners/runner_services.py +17 -4
  51. simtools/runners/simtel_runner.py +27 -10
  52. simtools/schemas/model_parameter.metaschema.yml +4 -0
  53. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +1 -0
  54. simtools/schemas/model_parameters/adjust_gain.schema.yml +2 -2
  55. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +2 -2
  56. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +2 -2
  57. simtools/schemas/model_parameters/array_window.schema.yml +2 -2
  58. simtools/schemas/model_parameters/asum_offset.schema.yml +2 -2
  59. simtools/schemas/model_parameters/asum_shaping.schema.yml +2 -2
  60. simtools/schemas/model_parameters/asum_threshold.schema.yml +2 -2
  61. simtools/schemas/model_parameters/axes_offsets.schema.yml +2 -2
  62. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +2 -2
  63. simtools/schemas/model_parameters/camera_body_shape.schema.yml +2 -2
  64. simtools/schemas/model_parameters/camera_config_file.schema.yml +2 -2
  65. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +2 -2
  66. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +2 -2
  67. simtools/schemas/model_parameters/camera_degraded_map.schema.yml +2 -2
  68. simtools/schemas/model_parameters/camera_depth.schema.yml +2 -2
  69. simtools/schemas/model_parameters/camera_filter.schema.yml +2 -2
  70. simtools/schemas/model_parameters/camera_pixels.schema.yml +2 -2
  71. simtools/schemas/model_parameters/camera_transmission.schema.yml +2 -2
  72. simtools/schemas/model_parameters/channels_per_chip.schema.yml +2 -2
  73. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +2 -2
  74. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +90 -1
  75. simtools/schemas/model_parameters/default_trigger.schema.yml +2 -2
  76. simtools/schemas/model_parameters/design_model.schema.yml +2 -2
  77. simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +2 -2
  78. simtools/schemas/model_parameters/disc_bins.schema.yml +2 -2
  79. simtools/schemas/model_parameters/disc_start.schema.yml +2 -2
  80. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +2 -2
  81. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +2 -2
  82. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +2 -2
  83. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +2 -2
  84. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +2 -2
  85. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +2 -2
  86. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +2 -2
  87. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +2 -2
  88. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +2 -2
  89. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +2 -2
  90. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +2 -2
  91. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +2 -2
  92. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +2 -2
  93. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +2 -2
  94. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +2 -2
  95. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +2 -2
  96. simtools/schemas/model_parameters/dish_shape_length.schema.yml +2 -2
  97. simtools/schemas/model_parameters/dsum_clipping.schema.yml +2 -2
  98. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +2 -2
  99. simtools/schemas/model_parameters/dsum_offset.schema.yml +2 -2
  100. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +2 -2
  101. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +2 -2
  102. simtools/schemas/model_parameters/dsum_prescale.schema.yml +2 -2
  103. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +2 -2
  104. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +2 -2
  105. simtools/schemas/model_parameters/dsum_shaping.schema.yml +2 -2
  106. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +2 -2
  107. simtools/schemas/model_parameters/dsum_threshold.schema.yml +3 -3
  108. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +2 -2
  109. simtools/schemas/model_parameters/effective_focal_length.schema.yml +2 -2
  110. simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +2 -2
  111. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +2 -2
  112. simtools/schemas/model_parameters/fadc_bins.schema.yml +2 -2
  113. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +2 -2
  114. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +2 -2
  115. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +2 -2
  116. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +2 -2
  117. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +2 -2
  118. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +2 -2
  119. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +2 -2
  120. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +2 -2
  121. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +2 -2
  122. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +2 -2
  123. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +2 -2
  124. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +2 -2
  125. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +2 -2
  126. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +2 -2
  127. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +2 -2
  128. simtools/schemas/model_parameters/fadc_mhz.schema.yml +2 -2
  129. simtools/schemas/model_parameters/fadc_noise.schema.yml +2 -2
  130. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +2 -2
  131. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +2 -2
  132. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +2 -2
  133. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +2 -2
  134. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +2 -2
  135. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +2 -2
  136. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +2 -2
  137. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +2 -2
  138. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +1 -1
  139. simtools/schemas/model_parameters/flatfielding.schema.yml +2 -2
  140. simtools/schemas/model_parameters/focal_length.schema.yml +2 -2
  141. simtools/schemas/model_parameters/focus_offset.schema.yml +2 -2
  142. simtools/schemas/model_parameters/gain_variation.schema.yml +2 -2
  143. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +2 -2
  144. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +2 -2
  145. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +2 -2
  146. simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
  147. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +2 -2
  148. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +2 -2
  149. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +2 -2
  150. simtools/schemas/model_parameters/min_photons.schema.yml +2 -2
  151. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +2 -2
  152. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +2 -2
  153. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +2 -2
  154. simtools/schemas/model_parameters/mirror_class.schema.yml +2 -2
  155. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +2 -2
  156. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +2 -2
  157. simtools/schemas/model_parameters/mirror_list.schema.yml +2 -2
  158. simtools/schemas/model_parameters/mirror_offset.schema.yml +2 -2
  159. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +2 -2
  160. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +2 -2
  161. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +2 -2
  162. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +2 -2
  163. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +2 -2
  164. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +2 -2
  165. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +2 -2
  166. simtools/schemas/model_parameters/num_gains.schema.yml +2 -2
  167. simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +2 -2
  168. simtools/schemas/model_parameters/optics_properties.schema.yml +2 -2
  169. simtools/schemas/model_parameters/pedestal_events.schema.yml +7 -3
  170. simtools/schemas/model_parameters/photon_delay.schema.yml +2 -2
  171. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +2 -2
  172. simtools/schemas/model_parameters/pm_average_gain.schema.yml +2 -2
  173. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +2 -2
  174. simtools/schemas/model_parameters/pm_gain_index.schema.yml +2 -2
  175. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +2 -2
  176. simtools/schemas/model_parameters/pm_transit_time.schema.yml +2 -2
  177. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +2 -2
  178. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +2 -2
  179. simtools/schemas/model_parameters/qe_variation.schema.yml +2 -2
  180. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +2 -2
  181. simtools/schemas/model_parameters/random_focal_length.schema.yml +2 -2
  182. simtools/schemas/model_parameters/random_generator.schema.yml +2 -2
  183. simtools/schemas/model_parameters/random_mono_probability.schema.yml +2 -2
  184. simtools/schemas/model_parameters/sampled_output.schema.yml +2 -2
  185. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +2 -2
  186. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +2 -2
  187. simtools/schemas/model_parameters/tailcut_scale.schema.yml +2 -2
  188. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +2 -2
  189. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +2 -2
  190. simtools/schemas/model_parameters/telescope_random_error.schema.yml +2 -2
  191. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +2 -2
  192. simtools/schemas/model_parameters/telescope_transmission.schema.yml +2 -2
  193. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +2 -2
  194. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +2 -2
  195. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +2 -2
  196. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +2 -2
  197. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +2 -2
  198. simtools/schemas/model_parameters/transit_time_error.schema.yml +2 -2
  199. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +2 -2
  200. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +2 -2
  201. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +2 -2
  202. simtools/schemas/model_parameters/trigger_pixels.schema.yml +2 -2
  203. simtools/simtel/simtel_config_reader.py +21 -17
  204. simtools/simtel/simtel_config_writer.py +237 -65
  205. simtools/simtel/simtel_io_file_info.py +57 -0
  206. simtools/simtel/simtel_io_histogram.py +10 -14
  207. simtools/simtel/simtel_io_histograms.py +2 -2
  208. simtools/simtel/simtel_io_metadata.py +91 -0
  209. simtools/simtel/simulator_array.py +26 -12
  210. simtools/simtel/simulator_camera_efficiency.py +12 -6
  211. simtools/simtel/simulator_light_emission.py +6 -11
  212. simtools/simtel/simulator_ray_tracing.py +14 -4
  213. simtools/simulator.py +230 -66
  214. simtools/testing/configuration.py +12 -6
  215. simtools/testing/helpers.py +18 -0
  216. simtools/testing/sim_telarray_metadata.py +212 -0
  217. simtools/testing/validate_output.py +12 -5
  218. simtools/utils/general.py +18 -27
  219. simtools/utils/names.py +27 -5
  220. simtools/visualization/visualize.py +2 -2
  221. simtools/applications/production_scale_events.py +0 -185
  222. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/licenses/LICENSE +0 -0
  223. {gammasimtools-0.14.0.dist-info → gammasimtools-0.16.0.dist-info}/top_level.txt +0 -0
@@ -3,6 +3,7 @@
3
3
  r"""Class to read and manage relevant model parameters for a given telescope model."""
4
4
 
5
5
  import logging
6
+ import re
6
7
  import textwrap
7
8
  from collections import defaultdict
8
9
  from itertools import groupby
@@ -15,6 +16,7 @@ from simtools.model.telescope_model import TelescopeModel
15
16
  from simtools.utils import names
16
17
 
17
18
  logger = logging.getLogger()
19
+ IMAGE_PATH = "../../_images"
18
20
 
19
21
 
20
22
  class ReadParameters:
@@ -25,13 +27,42 @@ class ReadParameters:
25
27
  self._logger = logging.getLogger(__name__)
26
28
  self.db = db_handler.DatabaseHandler(mongo_db_config=db_config)
27
29
  self.db_config = db_config
28
- self.array_element = args.get("telescope")
29
- self.site = args.get("site")
30
+ self.array_element = args.get("telescope", None)
31
+ self.site = args.get("site", None)
30
32
  self.model_version = args.get("model_version", None)
31
33
  self.output_path = output_path
32
34
  self.observatory = args.get("observatory")
35
+ self.software = args.get("simulation_software", None)
33
36
 
34
- def _convert_to_md(self, input_file):
37
+ @property
38
+ def model_version(self):
39
+ """Model version."""
40
+ return self._model_version
41
+
42
+ @model_version.setter
43
+ def model_version(self, model_version):
44
+ """
45
+ Set model version.
46
+
47
+ Parameters
48
+ ----------
49
+ _model_version: str or list
50
+ Model version (e.g., "6.0.0").
51
+ If a list is passed, it must contain exactly one element,
52
+ and only that element will be used.
53
+
54
+ Raises
55
+ ------
56
+ ValueError
57
+ If more than one model version is passed.
58
+ """
59
+ if isinstance(model_version, list):
60
+ raise ValueError(
61
+ f"Only one model version can be passed to {self.__class__.__name__}, not a list."
62
+ )
63
+ self._model_version = model_version
64
+
65
+ def _convert_to_md(self, parameter, input_file):
35
66
  """Convert a file to a Markdown file, preserving formatting."""
36
67
  input_file = Path(input_file)
37
68
  output_data_path = Path(self.output_path / "_data_files")
@@ -49,11 +80,25 @@ class ReadParameters:
49
80
  with input_file.open("r", encoding="latin-1") as infile:
50
81
  file_contents = infile.read()
51
82
 
52
- with output_file.open("w", encoding="utf-8") as outfile:
53
- outfile.write(f"# {input_file.stem}\n")
54
- outfile.write("```\n")
55
- outfile.write(file_contents)
56
- outfile.write("\n```")
83
+ if self.model_version is not None:
84
+ with output_file.open("w", encoding="utf-8") as outfile:
85
+ outfile.write(f"# {input_file.stem}\n")
86
+ outfile.write(
87
+ "The full file can be found in the Simulation Model repository [here]"
88
+ "(https://gitlab.cta-observatory.org/cta-science/simulations/"
89
+ "simulation-model/simulation-models/-/blob/main/simulation-models/"
90
+ f"model_parameters/Files/{input_file.name}).\n\n"
91
+ )
92
+ outfile.write(
93
+ f"![Parameter plot.](../{IMAGE_PATH}/{self.array_element}_"
94
+ f"{parameter}_{self.model_version.replace('.', '-')}.png)\n"
95
+ )
96
+ outfile.write("\n\n")
97
+ outfile.write("The first 30 lines of the file are:\n")
98
+ outfile.write("```\n")
99
+ first_30_lines = "\n".join(file_contents.splitlines()[:30])
100
+ outfile.write(first_30_lines)
101
+ outfile.write("\n```")
57
102
 
58
103
  except FileNotFoundError as exc:
59
104
  logger.exception(f"Data file not found: {input_file}.")
@@ -61,11 +106,11 @@ class ReadParameters:
61
106
 
62
107
  return f"_data_files/{output_file_name}"
63
108
 
64
- def _format_parameter_value(self, value_data, unit, file_flag):
109
+ def _format_parameter_value(self, parameter, value_data, unit, file_flag):
65
110
  """Format parameter value based on type."""
66
111
  if file_flag:
67
112
  input_file_name = f"{self.output_path}/model/{value_data}"
68
- output_file_name = self._convert_to_md(input_file_name)
113
+ output_file_name = self._convert_to_md(parameter, input_file_name)
69
114
  return f"[{Path(value_data).name}]({output_file_name})".strip()
70
115
  if isinstance(value_data, (str | int | float)):
71
116
  return f"{value_data} {unit}".strip()
@@ -77,6 +122,26 @@ class ReadParameters:
77
122
  else ", ".join(f"{v} {unit}" for v in value_data)
78
123
  ).strip()
79
124
 
125
+ def _wrap_at_underscores(self, text, max_width):
126
+ """Wrap text at underscores to fit within a specified width."""
127
+ parts = text.split("_")
128
+ lines = []
129
+ current = []
130
+
131
+ for part in parts:
132
+ # Predict the new length if we add this part
133
+ next_line = "_".join([*current, part])
134
+ if len(next_line) <= max_width:
135
+ current.append(part)
136
+ else:
137
+ lines.append("_".join(current))
138
+ current = [part]
139
+
140
+ if current:
141
+ lines.append("_".join(current))
142
+
143
+ return " ".join(lines)
144
+
80
145
  def _group_model_versions_by_parameter_version(self, grouped_data):
81
146
  """Group model versions by parameter version and track the parameter values."""
82
147
  result = {}
@@ -152,7 +217,7 @@ class ReadParameters:
152
217
  continue
153
218
 
154
219
  file_flag = parameter_data.get("file", False)
155
- value = self._format_parameter_value(value_data, unit, file_flag)
220
+ value = self._format_parameter_value(parameter_name, value_data, unit, file_flag)
156
221
  parameter_version = parameter_data.get("parameter_version")
157
222
  model_version = version
158
223
 
@@ -168,7 +233,7 @@ class ReadParameters:
168
233
 
169
234
  return self._group_model_versions_by_parameter_version(grouped_data)
170
235
 
171
- def get_all_parameter_descriptions(self):
236
+ def get_all_parameter_descriptions(self, collection="telescopes"):
172
237
  """
173
238
  Get descriptions for all model parameters.
174
239
 
@@ -181,7 +246,7 @@ class ReadParameters:
181
246
  """
182
247
  parameter_description, short_description, inst_class = {}, {}, {}
183
248
 
184
- for instrument_class in names.db_collection_to_instrument_class_key("telescopes"):
249
+ for instrument_class in names.db_collection_to_instrument_class_key(collection):
185
250
  for parameter, details in names.model_parameters(instrument_class).items():
186
251
  parameter_description[parameter] = details.get("description")
187
252
  short_description[parameter] = details.get("short_description")
@@ -219,8 +284,6 @@ class ReadParameters:
219
284
 
220
285
  for parameter in filter(all_parameter_data.__contains__, names.model_parameters().keys()):
221
286
  parameter_data = all_parameter_data.get(parameter)
222
- if parameter_data["instrument"] != telescope_model.name:
223
- continue
224
287
  parameter_version = telescope_model.get_parameter_version(parameter)
225
288
  unit = parameter_data.get("unit") or " "
226
289
  value_data = parameter_data.get("value")
@@ -229,11 +292,23 @@ class ReadParameters:
229
292
  continue
230
293
 
231
294
  file_flag = parameter_data.get("file", False)
232
- value = self._format_parameter_value(value_data, unit, file_flag)
295
+ value = self._format_parameter_value(parameter, value_data, unit, file_flag)
233
296
 
234
297
  description = parameter_descriptions[0].get(parameter)
235
- short_description = parameter_descriptions[1].get(parameter, description)
298
+ short_description = parameter_descriptions[1].get(parameter)
299
+ if short_description is None:
300
+ short_description = description
236
301
  inst_class = parameter_descriptions[2].get(parameter)
302
+
303
+ matching_instrument = parameter_data["instrument"] == telescope_model.name
304
+ if not names.is_design_type(telescope_model.name) and matching_instrument:
305
+ parameter = f"***{parameter}***"
306
+ parameter_version = f"***{parameter_version}***"
307
+ if not re.match(r"^\[.*\]\(.*\)$", value.strip()):
308
+ value = f"***{value}***"
309
+ description = f"***{description}***"
310
+ short_description = f"***{short_description}***"
311
+
237
312
  data.append(
238
313
  [
239
314
  inst_class,
@@ -247,6 +322,116 @@ class ReadParameters:
247
322
 
248
323
  return data
249
324
 
325
+ def _write_to_file(self, data, file):
326
+ # Write table header and separator row
327
+ file.write(
328
+ "| Parameter Name | Parameter Version "
329
+ "| Values | Short Description |\n"
330
+ "|---------------------|------------------------"
331
+ "|-------------|-----------------------------|\n"
332
+ )
333
+
334
+ # Write table rows
335
+ column_widths = [10, 10, 20, 60]
336
+ for (
337
+ _,
338
+ parameter_name,
339
+ parameter_version,
340
+ value,
341
+ description,
342
+ short_description,
343
+ ) in data:
344
+ text = short_description if short_description else description
345
+ wrapped_text = textwrap.fill(str(text), column_widths[3]).split("\n")
346
+ wrapped_text = " ".join(wrapped_text)
347
+ parameter_name = self._wrap_at_underscores(parameter_name, column_widths[0])
348
+
349
+ file.write(
350
+ f"| {parameter_name:{column_widths[0]}} |"
351
+ f" {parameter_version:{column_widths[1]}} |"
352
+ f" {value:{column_widths[2]}} |"
353
+ f" {wrapped_text:{column_widths[3]}} |\n"
354
+ )
355
+ file.write("\n\n")
356
+
357
+ def get_simulation_configuration_data(self):
358
+ """Get data and descriptions for simulation configuration parameters."""
359
+
360
+ def get_param_data(telescope, site):
361
+ """Retrieve and format parameter data for one telescope-site combo."""
362
+ param_dict = self.db.get_simulation_configuration_parameters(
363
+ simulation_software=self.software,
364
+ site=site,
365
+ array_element_name=telescope,
366
+ model_version=self.model_version,
367
+ )
368
+
369
+ parameter_descriptions = self.get_all_parameter_descriptions(
370
+ collection=f"configuration_{self.software}"
371
+ )
372
+
373
+ model_output_path = Path(f"{self.output_path}/model")
374
+ model_output_path.mkdir(parents=True, exist_ok=True)
375
+ self.db.export_model_files(parameters=param_dict, dest=str(model_output_path))
376
+
377
+ data = []
378
+ for parameter, parameter_data in param_dict.items():
379
+ description = parameter_descriptions[0].get(parameter)
380
+ short_description = parameter_descriptions[1].get(parameter, description)
381
+ value_data = parameter_data.get("value")
382
+
383
+ if value_data is None:
384
+ continue
385
+
386
+ unit = parameter_data.get("unit") or " "
387
+ file_flag = parameter_data.get("file", False)
388
+ parameter_version = parameter_data.get("parameter_version")
389
+ value = self._format_parameter_value(parameter, value_data, unit, file_flag)
390
+
391
+ data.append(
392
+ [
393
+ telescope,
394
+ parameter,
395
+ parameter_version,
396
+ value,
397
+ description,
398
+ short_description,
399
+ ]
400
+ )
401
+ return data
402
+
403
+ if self.software == "corsika":
404
+ return get_param_data(self.array_element, self.site)
405
+
406
+ results = []
407
+ telescopes = self.db.get_array_elements(self.model_version)
408
+ for telescope in telescopes:
409
+ valid_site = names.get_site_from_array_element_name(telescope)
410
+ if not isinstance(valid_site, list):
411
+ results.extend(get_param_data(telescope, valid_site))
412
+ else:
413
+ for site in valid_site:
414
+ results.extend(get_param_data(telescope, site))
415
+ return results
416
+
417
+ def produce_simulation_configuration_report(self):
418
+ """Write simulation configuration report."""
419
+ output_filename = Path(self.output_path / (f"configuration_{self.software}.md"))
420
+ output_filename.parent.mkdir(parents=True, exist_ok=True)
421
+ data = self.get_simulation_configuration_data()
422
+
423
+ with output_filename.open("w", encoding="utf-8") as file:
424
+ file.write(f"# configuration_{self.software}\n")
425
+ file.write("\n\n")
426
+ if self.software == "sim_telarray":
427
+ data.sort(key=lambda x: (x[0], x[1]))
428
+ for telescope, group in groupby(data, key=lambda x: x[0]):
429
+ file.write(f"## [{telescope}]({telescope}.md)\n")
430
+ file.write("\n\n")
431
+ self._write_to_file(group, file)
432
+ else:
433
+ self._write_to_file(data, file)
434
+
250
435
  def produce_array_element_report(self):
251
436
  """
252
437
  Produce a markdown report of all model parameters per array element.
@@ -281,44 +466,20 @@ class ReadParameters:
281
466
  file.write(
282
467
  "The design model can be found here: "
283
468
  f"[{telescope_model.design_model}]"
284
- f"({telescope_model.design_model}.md).\n"
469
+ f"({telescope_model.design_model}.md).\n\n"
470
+ )
471
+ file.write(
472
+ "Parameters shown in ***bold and italics*** are specific to each telescope.\n"
473
+ "Parameters without emphasis are inherited from the design model.\n"
285
474
  )
286
475
  file.write("\n\n")
287
476
 
288
477
  for class_name, group in groupby(data, key=lambda x: x[0]):
289
478
  group = sorted(group, key=lambda x: x[1])
290
479
  file.write(f"## {class_name}\n\n")
480
+ self._write_to_file(group, file)
291
481
 
292
- # Write table header and separator row
293
- file.write(
294
- "| Parameter Name | Parameter Version "
295
- "| Values | Short Description |\n"
296
- "|---------------------|------------------------"
297
- "|-------------|-----------------------------|\n"
298
- )
299
-
300
- # Write table rows
301
- column_widths = [20, 20, 20, 70]
302
- for (
303
- _,
304
- parameter_name,
305
- parameter_version,
306
- value,
307
- description,
308
- short_description,
309
- ) in group:
310
- text = short_description if short_description else description
311
- wrapped_text = textwrap.fill(str(text), column_widths[3]).split("\n")
312
- wrapped_text = " ".join(wrapped_text)
313
- file.write(
314
- f"| {parameter_name:{column_widths[0]}} |"
315
- f" {parameter_version:{column_widths[1]}} |"
316
- f" {value:{column_widths[2]}} |"
317
- f" {wrapped_text} |\n"
318
- )
319
- file.write("\n\n")
320
-
321
- def produce_model_parameter_reports(self):
482
+ def produce_model_parameter_reports(self, collection="telescopes"):
322
483
  """
323
484
  Produce a markdown report per parameter for a given array element.
324
485
 
@@ -334,7 +495,7 @@ class ReadParameters:
334
495
 
335
496
  all_parameter_names = names.model_parameters(None).keys()
336
497
  all_parameter_data = self.db.get_model_parameters_for_all_model_versions(
337
- site=self.site, array_element_name=self.array_element, collection="telescopes"
498
+ site=self.site, array_element_name=self.array_element, collection=collection
338
499
  )
339
500
 
340
501
  comparison_data = self._compare_parameter_across_versions(
@@ -347,7 +508,10 @@ class ReadParameters:
347
508
  continue
348
509
 
349
510
  output_filename = output_path / f"{parameter}.md"
350
- description = self.get_all_parameter_descriptions()[0].get(parameter)
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
+ )
351
515
  with output_filename.open("w", encoding="utf-8") as file:
352
516
  # Write header
353
517
  file.write(
@@ -375,11 +539,13 @@ class ReadParameters:
375
539
 
376
540
  file.write("\n")
377
541
  if comparison_data.get(parameter)[0]["file_flag"]:
378
- file.write(f"![Parameter plot.](_images/{self.array_element}_{parameter}.png)")
542
+ file.write(
543
+ f"![Parameter plot.]({IMAGE_PATH}/{self.array_element}_{parameter}.png)"
544
+ )
379
545
 
380
546
  def _write_array_layouts_section(self, file, layouts):
381
547
  """Write the array layouts section of the report."""
382
- file.write("\n## Array Layouts {#array-layouts-details}\n\n")
548
+ file.write("\n## Array Layouts\n\n")
383
549
  for layout in layouts:
384
550
  layout_name = layout["name"]
385
551
  elements = layout["elements"]
@@ -388,10 +554,15 @@ class ReadParameters:
388
554
  for element in sorted(elements):
389
555
  file.write(f"| [{element}]({element}.md) |\n")
390
556
  file.write("\n")
557
+ version = self.model_version.replace(".", "-")
558
+ filename = f"OBS-{self.site}_{layout_name}_{version}.png"
559
+ image_path = f"{IMAGE_PATH}/{filename}"
560
+ file.write(f"![{layout_name} Layout]({image_path})\n\n")
561
+ file.write("\n")
391
562
 
392
563
  def _write_array_triggers_section(self, file, trigger_configs):
393
564
  """Write the array triggers section of the report."""
394
- file.write("\n## Array Trigger Configurations {#array-triggers-details}\n\n")
565
+ file.write("\n## Array Trigger Configurations\n\n")
395
566
  file.write(
396
567
  "| Trigger Name | Multiplicity | Width | Hard Stereo | Min Separation |\n"
397
568
  "|--------------|--------------|--------|-------------|----------------|\n"
@@ -411,24 +582,32 @@ class ReadParameters:
411
582
 
412
583
  def _write_parameters_table(self, file, all_parameter_data):
413
584
  """Write the main parameters table of the report."""
414
- file.write("| Parameter | Value |\n|-----------|--------|\n")
585
+ file.write(
586
+ "| Parameter | Value | Parameter Version |\n"
587
+ "|-----------|--------|-------------------|\n"
588
+ )
415
589
  for param_name, param_data in sorted(all_parameter_data.items()):
416
590
  value = param_data.get("value")
417
591
  unit = param_data.get("unit") or " "
418
592
  file_flag = param_data.get("file", False)
593
+ parameter_version = param_data.get("parameter_version")
419
594
 
420
595
  if value is None:
421
596
  continue
422
597
 
423
598
  if param_name == "array_layouts":
424
- file.write("| array_layouts | [View Array Layouts](#array-layouts-details) |\n")
599
+ file.write(
600
+ "| array_layouts | [View Array Layouts](#array-layouts)"
601
+ f" | {parameter_version} |\n"
602
+ )
425
603
  elif param_name == "array_triggers":
426
604
  file.write(
427
- "| array_triggers | [View Trigger Configurations](#array-triggers-details) |\n"
605
+ "| array_triggers | [View Trigger Configurations]"
606
+ f"(#array-trigger-configurations) | {parameter_version} |\n"
428
607
  )
429
608
  else:
430
- formatted_value = self._format_parameter_value(value, unit, file_flag)
431
- file.write(f"| {param_name} | {formatted_value} |\n")
609
+ formatted_value = self._format_parameter_value(param_name, value, unit, file_flag)
610
+ file.write(f"| {param_name} | {formatted_value} | {parameter_version} |\n")
432
611
  file.write("\n")
433
612
 
434
613
  def produce_observatory_report(self):
@@ -463,3 +642,127 @@ class ReadParameters:
463
642
  self._write_array_triggers_section(
464
643
  file, all_parameter_data["array_triggers"]["value"]
465
644
  )
645
+
646
+ def get_calibration_data(self, all_parameter_data, array_element):
647
+ """Get calibration data and descriptions for a given array element."""
648
+ parameter_descriptions = self.get_all_parameter_descriptions(
649
+ collection="calibration_devices"
650
+ )
651
+ # get descriptions of array element positions from the telescope collection
652
+ telescope_descriptions = self.get_all_parameter_descriptions(collection="telescopes")
653
+ data = []
654
+ class_grouped_data = {}
655
+
656
+ for parameter in all_parameter_data.keys():
657
+ parameter_data = all_parameter_data.get(parameter)
658
+ parameter_version = parameter_data.get("parameter_version")
659
+ unit = parameter_data.get("unit") or " "
660
+ value_data = parameter_data.get("value")
661
+
662
+ if value_data is None:
663
+ continue
664
+
665
+ 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)
677
+ )
678
+
679
+ matching_instrument = parameter_data["instrument"] == array_element
680
+ if not names.is_design_type(array_element) and matching_instrument:
681
+ parameter = f"***{parameter}***"
682
+ parameter_version = f"***{parameter_version}***"
683
+ if not re.match(r"^\[.*\]\(.*\)$", value.strip()):
684
+ value = f"***{value}***"
685
+ description = f"***{description}***"
686
+ short_description = f"***{short_description}***"
687
+
688
+ # Group by class name
689
+ if inst_class not in class_grouped_data:
690
+ class_grouped_data[inst_class] = []
691
+
692
+ class_grouped_data[inst_class].append(
693
+ [
694
+ inst_class,
695
+ parameter,
696
+ parameter_version,
697
+ value,
698
+ description,
699
+ short_description,
700
+ ]
701
+ )
702
+
703
+ data = []
704
+ for class_name in sorted(class_grouped_data.keys(), reverse=True):
705
+ sorted_class_data = sorted(class_grouped_data[class_name], key=lambda x: x[1])
706
+ data.extend(sorted_class_data)
707
+
708
+ return data
709
+
710
+ def produce_calibration_reports(self):
711
+ """Write calibration reports."""
712
+ calibration_array_elements = self.db.get_array_elements(
713
+ self.model_version, collection="calibration_devices"
714
+ )
715
+ array_elements = calibration_array_elements.copy()
716
+ for element in calibration_array_elements:
717
+ design_model = self.db.get_design_model(
718
+ self.model_version, element, "calibration_devices"
719
+ )
720
+ if design_model and design_model not in array_elements:
721
+ array_elements.append(design_model)
722
+
723
+ for calibration_device in array_elements:
724
+ all_parameter_data = self.db.get_model_parameters(
725
+ site=names.get_site_from_array_element_name(calibration_device),
726
+ array_element_name=calibration_device,
727
+ collection="calibration_devices",
728
+ model_version=self.model_version,
729
+ )
730
+
731
+ output_filename = Path(self.output_path / (f"{calibration_device}.md"))
732
+ output_filename.parent.mkdir(parents=True, exist_ok=True)
733
+ data = self.get_calibration_data(all_parameter_data, calibration_device)
734
+
735
+ design_model = self.db.get_design_model(
736
+ self.model_version, calibration_device, "calibration_devices"
737
+ )
738
+
739
+ with output_filename.open("w", encoding="utf-8") as file:
740
+ file.write(f"# {calibration_device}\n")
741
+ file.write("\n\n")
742
+
743
+ if not names.is_design_type(calibration_device):
744
+ file.write(
745
+ "The design model can be found here: "
746
+ f"[{design_model}]"
747
+ f"({design_model}.md).\n\n"
748
+ )
749
+ file.write(
750
+ "Parameters shown in ***bold and italics*** are specific"
751
+ " to each array element.\n"
752
+ "Parameters without emphasis are inherited from the design model.\n"
753
+ )
754
+ file.write("\n\n")
755
+
756
+ for class_name, group in groupby(data, key=lambda x: x[0]):
757
+ group = sorted(group, key=lambda x: x[1])
758
+ file.write(f"## {class_name}\n\n")
759
+ self._write_to_file(group, file)
760
+
761
+ new_output_path = Path(self.output_path).parent.parent / "parameters"
762
+ new_output_path.mkdir(parents=True, exist_ok=True)
763
+ self.output_path = new_output_path
764
+ for calibration_device in array_elements:
765
+ self.site = names.get_site_from_array_element_name(calibration_device)
766
+ self.array_element = calibration_device
767
+ print("cal: ", calibration_device)
768
+ self.produce_model_parameter_reports(collection="calibration_devices")
@@ -221,7 +221,12 @@ class CorsikaRunner:
221
221
  return cmd
222
222
 
223
223
  def get_file_name(
224
- self, simulation_software="corsika", file_type=None, run_number=None, mode=""
224
+ self,
225
+ simulation_software="corsika",
226
+ file_type=None,
227
+ run_number=None,
228
+ mode="",
229
+ model_version_index=0,
225
230
  ):
226
231
  """
227
232
  Get the full path of a file for a given run number.
@@ -234,6 +239,10 @@ class CorsikaRunner:
234
239
  File type.
235
240
  run_number: int
236
241
  Run number.
242
+ model_version_index: int
243
+ Index of the model version.
244
+ This is used to select the correct simulator_array instance in case
245
+ multiple array models are simulated.
237
246
 
238
247
  Returns
239
248
  -------
@@ -248,4 +257,5 @@ class CorsikaRunner:
248
257
  file_type=file_type,
249
258
  run_number=run_number,
250
259
  mode=mode,
260
+ _model_version_index=model_version_index,
251
261
  )