gammasimtools 0.13.0__py3-none-any.whl → 0.15.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 (226) hide show
  1. {gammasimtools-0.13.0.dist-info → gammasimtools-0.15.0.dist-info}/METADATA +3 -3
  2. {gammasimtools-0.13.0.dist-info → gammasimtools-0.15.0.dist-info}/RECORD +224 -217
  3. {gammasimtools-0.13.0.dist-info → gammasimtools-0.15.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.13.0.dist-info → gammasimtools-0.15.0.dist-info}/entry_points.txt +3 -1
  5. simtools/_version.py +2 -2
  6. simtools/applications/db_add_file_to_db.py +15 -0
  7. simtools/applications/db_add_value_from_json_to_db.py +18 -1
  8. simtools/applications/derive_ctao_array_layouts.py +120 -0
  9. simtools/applications/derive_photon_electron_spectrum.py +29 -1
  10. simtools/applications/docs_produce_array_element_report.py +41 -16
  11. simtools/applications/docs_produce_model_parameter_reports.py +28 -8
  12. simtools/applications/{production_extract_mc_event_data.py → generate_simtel_event_data.py} +12 -20
  13. simtools/applications/print_version.py +81 -0
  14. simtools/applications/production_derive_corsika_limits.py +172 -39
  15. simtools/applications/production_scale_events.py +59 -36
  16. simtools/applications/run_application.py +41 -11
  17. simtools/applications/simulate_light_emission.py +115 -247
  18. simtools/applications/simulate_prod_htcondor_generator.py +2 -2
  19. simtools/camera/single_photon_electron_spectrum.py +163 -15
  20. simtools/data_model/model_data_writer.py +7 -6
  21. simtools/db/db_handler.py +14 -8
  22. simtools/dependencies.py +38 -3
  23. simtools/layout/array_layout.py +1 -0
  24. simtools/layout/ctao_array_layouts.py +172 -0
  25. simtools/model/array_model.py +3 -4
  26. simtools/model/model_parameter.py +30 -87
  27. simtools/production_configuration/derive_corsika_limits.py +222 -154
  28. simtools/production_configuration/event_scaler.py +2 -2
  29. simtools/reporting/docs_auto_report_generator.py +217 -0
  30. simtools/reporting/docs_read_parameters.py +192 -110
  31. simtools/schemas/application_workflow.metaschema.yml +3 -0
  32. simtools/schemas/model_parameter.metaschema.yml +13 -0
  33. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +6 -0
  34. simtools/schemas/model_parameters/adjust_gain.schema.yml +1 -1
  35. simtools/schemas/model_parameters/altitude.schema.yml +1 -1
  36. simtools/schemas/model_parameters/array_coordinates_UTM.schema.yml +3 -3
  37. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +3 -3
  38. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +3 -3
  39. simtools/schemas/model_parameters/array_window.schema.yml +1 -1
  40. simtools/schemas/model_parameters/asum_clipping.schema.yml +1 -1
  41. simtools/schemas/model_parameters/asum_offset.schema.yml +1 -1
  42. simtools/schemas/model_parameters/asum_threshold.schema.yml +1 -1
  43. simtools/schemas/model_parameters/axes_offsets.schema.yml +2 -2
  44. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +1 -1
  45. simtools/schemas/model_parameters/camera_body_shape.schema.yml +1 -1
  46. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +1 -1
  47. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +1 -1
  48. simtools/schemas/model_parameters/camera_depth.schema.yml +1 -1
  49. simtools/schemas/model_parameters/camera_pixels.schema.yml +1 -1
  50. simtools/schemas/model_parameters/camera_transmission.schema.yml +1 -1
  51. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  52. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +1 -1
  53. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +1 -1
  54. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +1 -1
  55. simtools/schemas/model_parameters/corsika_observation_level.schema.yml +1 -1
  56. simtools/schemas/model_parameters/dark_events.schema.yml +1 -1
  57. simtools/schemas/model_parameters/disc_bins.schema.yml +1 -1
  58. simtools/schemas/model_parameters/disc_start.schema.yml +1 -1
  59. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +1 -1
  60. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +1 -1
  61. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +1 -1
  62. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +1 -1
  63. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +1 -1
  64. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +1 -1
  65. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +1 -1
  66. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +1 -1
  67. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +1 -1
  68. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +1 -1
  69. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +1 -1
  70. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +1 -1
  71. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +1 -1
  72. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +1 -1
  73. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +1 -1
  74. simtools/schemas/model_parameters/dish_shape_length.schema.yml +1 -1
  75. simtools/schemas/model_parameters/dsum_clipping.schema.yml +1 -1
  76. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +1 -1
  77. simtools/schemas/model_parameters/dsum_offset.schema.yml +1 -1
  78. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +1 -1
  79. simtools/schemas/model_parameters/dsum_prescale.schema.yml +2 -2
  80. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +1 -1
  81. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +1 -1
  82. simtools/schemas/model_parameters/dsum_threshold.schema.yml +1 -1
  83. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +1 -1
  84. simtools/schemas/model_parameters/effective_focal_length.schema.yml +5 -5
  85. simtools/schemas/model_parameters/epsg_code.schema.yml +1 -1
  86. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +1 -1
  87. simtools/schemas/model_parameters/fadc_bins.schema.yml +1 -1
  88. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  89. simtools/schemas/model_parameters/fadc_dev_pedestal.schema.yml +1 -1
  90. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +1 -1
  91. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +1 -1
  92. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +1 -1
  93. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  94. simtools/schemas/model_parameters/fadc_lg_dev_pedestal.schema.yml +1 -1
  95. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +1 -1
  96. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +1 -1
  97. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +1 -1
  98. simtools/schemas/model_parameters/fadc_lg_max_sum.schema.yml +1 -1
  99. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +1 -1
  100. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +1 -1
  101. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +1 -1
  102. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +1 -1
  103. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +1 -1
  104. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +1 -1
  105. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +1 -1
  106. simtools/schemas/model_parameters/fadc_max_sum.schema.yml +1 -1
  107. simtools/schemas/model_parameters/fadc_mhz.schema.yml +1 -1
  108. simtools/schemas/model_parameters/fadc_noise.schema.yml +1 -1
  109. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +1 -1
  110. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +1 -1
  111. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +1 -1
  112. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +1 -1
  113. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +1 -1
  114. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +1 -1
  115. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +1 -1
  116. simtools/schemas/model_parameters/focal_length.schema.yml +1 -1
  117. simtools/schemas/model_parameters/focal_surface_parameters.schema.yml +20 -20
  118. simtools/schemas/model_parameters/focal_surface_ref_radius.schema.yml +1 -1
  119. simtools/schemas/model_parameters/focus_offset.schema.yml +4 -4
  120. simtools/schemas/model_parameters/gain_variation.schema.yml +1 -1
  121. simtools/schemas/model_parameters/geomag_horizontal.schema.yml +1 -1
  122. simtools/schemas/model_parameters/geomag_rotation.schema.yml +1 -1
  123. simtools/schemas/model_parameters/geomag_vertical.schema.yml +1 -1
  124. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +1 -1
  125. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  126. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  127. simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
  128. simtools/schemas/model_parameters/laser_external_trigger.schema.yml +1 -1
  129. simtools/schemas/model_parameters/laser_photons.schema.yml +1 -1
  130. simtools/schemas/model_parameters/laser_pulse_exptime.schema.yml +1 -1
  131. simtools/schemas/model_parameters/laser_pulse_offset.schema.yml +1 -1
  132. simtools/schemas/model_parameters/laser_pulse_sigtime.schema.yml +1 -1
  133. simtools/schemas/model_parameters/laser_pulse_twidth.schema.yml +1 -1
  134. simtools/schemas/model_parameters/laser_var_photons.schema.yml +1 -1
  135. simtools/schemas/model_parameters/laser_wavelength.schema.yml +1 -1
  136. simtools/schemas/model_parameters/led_events.schema.yml +1 -1
  137. simtools/schemas/model_parameters/led_photons.schema.yml +1 -1
  138. simtools/schemas/model_parameters/led_pulse_offset.schema.yml +1 -1
  139. simtools/schemas/model_parameters/led_pulse_sigtime.schema.yml +1 -1
  140. simtools/schemas/model_parameters/led_var_photons.schema.yml +1 -1
  141. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  142. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  143. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +1 -1
  144. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +4 -4
  145. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +4 -4
  146. simtools/schemas/model_parameters/mirror_class.schema.yml +1 -1
  147. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +1 -1
  148. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +1 -1
  149. simtools/schemas/model_parameters/mirror_offset.schema.yml +1 -1
  150. simtools/schemas/model_parameters/mirror_panel_2f_measurements.schema.yml +3 -3
  151. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +3 -3
  152. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +1 -1
  153. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +45 -0
  154. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +2 -2
  155. simtools/schemas/model_parameters/nsb_gain_drop_scale.schema.yml +1 -1
  156. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +5 -5
  157. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +1 -1
  158. simtools/schemas/model_parameters/nsb_reference_value.schema.yml +1 -1
  159. simtools/schemas/model_parameters/nsb_scaling_factor.schema.yml +1 -1
  160. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +2 -2
  161. simtools/schemas/model_parameters/num_gains.schema.yml +1 -1
  162. simtools/schemas/model_parameters/pedestal_events.schema.yml +1 -1
  163. simtools/schemas/model_parameters/photon_delay.schema.yml +1 -1
  164. simtools/schemas/model_parameters/photons_per_run.schema.yml +1 -1
  165. simtools/schemas/model_parameters/pixel_cells.schema.yml +1 -1
  166. simtools/schemas/model_parameters/pixels_parallel.schema.yml +1 -1
  167. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +1 -1
  168. simtools/schemas/model_parameters/pm_average_gain.schema.yml +1 -1
  169. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +1 -1
  170. simtools/schemas/model_parameters/pm_gain_index.schema.yml +1 -1
  171. simtools/schemas/model_parameters/pm_transit_time.schema.yml +4 -4
  172. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +1 -1
  173. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +7 -3
  174. simtools/schemas/model_parameters/primary_mirror_diameter.schema.yml +1 -1
  175. simtools/schemas/model_parameters/primary_mirror_hole_diameter.schema.yml +1 -1
  176. simtools/schemas/model_parameters/primary_mirror_parameters.schema.yml +20 -20
  177. simtools/schemas/model_parameters/primary_mirror_ref_radius.schema.yml +1 -1
  178. simtools/schemas/model_parameters/qe_variation.schema.yml +1 -1
  179. simtools/schemas/model_parameters/random_focal_length.schema.yml +2 -2
  180. simtools/schemas/model_parameters/random_mono_probability.schema.yml +38 -0
  181. simtools/schemas/model_parameters/reference_point_altitude.schema.yml +1 -1
  182. simtools/schemas/model_parameters/reference_point_latitude.schema.yml +4 -1
  183. simtools/schemas/model_parameters/reference_point_longitude.schema.yml +4 -1
  184. simtools/schemas/model_parameters/reference_point_utm_east.schema.yml +1 -1
  185. simtools/schemas/model_parameters/reference_point_utm_north.schema.yml +1 -1
  186. simtools/schemas/model_parameters/secondary_mirror_baffle.schema.yml +5 -5
  187. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  188. simtools/schemas/model_parameters/secondary_mirror_diameter.schema.yml +1 -1
  189. simtools/schemas/model_parameters/secondary_mirror_hole_diameter.schema.yml +1 -1
  190. simtools/schemas/model_parameters/secondary_mirror_parameters.schema.yml +20 -20
  191. simtools/schemas/model_parameters/secondary_mirror_ref_radius.schema.yml +1 -1
  192. simtools/schemas/model_parameters/secondary_mirror_shadow_diameter.schema.yml +1 -1
  193. simtools/schemas/model_parameters/secondary_mirror_shadow_offset.schema.yml +1 -1
  194. simtools/schemas/model_parameters/stars.schema.yml +36 -0
  195. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  196. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  197. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +1 -1
  198. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +1 -1
  199. simtools/schemas/model_parameters/telescope_random_error.schema.yml +1 -1
  200. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +1 -1
  201. simtools/schemas/model_parameters/telescope_transmission.schema.yml +6 -6
  202. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +1 -1
  203. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +1 -1
  204. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +1 -1
  205. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +1 -1
  206. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +1 -1
  207. simtools/schemas/model_parameters/transit_time_error.schema.yml +1 -1
  208. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +1 -1
  209. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +1 -1
  210. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +4 -4
  211. simtools/schemas/model_parameters/trigger_pixels.schema.yml +1 -1
  212. simtools/simtel/simtel_config_writer.py +70 -38
  213. simtools/simtel/simtel_io_event_reader.py +278 -0
  214. simtools/simtel/simtel_io_event_writer.py +317 -0
  215. simtools/simtel/simulator_light_emission.py +181 -67
  216. simtools/simulator.py +2 -2
  217. simtools/testing/configuration.py +18 -0
  218. simtools/utils/general.py +33 -7
  219. simtools/utils/geometry.py +19 -0
  220. simtools/utils/names.py +7 -1
  221. simtools/visualization/visualize.py +2 -2
  222. simtools/production_configuration/extract_mc_event_data.py +0 -253
  223. simtools/simtel/simtel_io_events.py +0 -265
  224. {gammasimtools-0.13.0.dist-info → gammasimtools-0.15.0.dist-info/licenses}/LICENSE +0 -0
  225. {gammasimtools-0.13.0.dist-info → gammasimtools-0.15.0.dist-info}/top_level.txt +0 -0
  226. /simtools/schemas/model_parameters/{nsb_skymap.schema.yml → nsb_sky_map.schema.yml} +0 -0
@@ -7,7 +7,9 @@ import tempfile
7
7
  from io import BytesIO
8
8
  from pathlib import Path
9
9
 
10
+ import numpy as np
10
11
  from astropy.table import Table
12
+ from scipy.optimize import curve_fit
11
13
 
12
14
  import simtools.data_model.model_data_writer as writer
13
15
  from simtools.constants import MODEL_PARAMETER_SCHEMA_URL, SCHEMA_PATH
@@ -52,8 +54,16 @@ class SinglePhotonElectronSpectrum:
52
54
 
53
55
  def derive_single_pe_spectrum(self):
54
56
  """Derive single photon electron spectrum."""
57
+ afterpulse_fitted_spectrum = (
58
+ self.fit_afterpulse_spectrum() if self.args_dict.get("fit_afterpulse") else None
59
+ )
60
+
55
61
  if self.args_dict.get("use_norm_spe"):
56
- return self._derive_spectrum_norm_spe()
62
+ return self._derive_spectrum_norm_spe(
63
+ input_spectrum=self.args_dict["input_spectrum"],
64
+ afterpulse_spectrum=self.args_dict.get("afterpulse_spectrum"),
65
+ afterpulse_fitted_spectrum=afterpulse_fitted_spectrum,
66
+ )
57
67
 
58
68
  raise NotImplementedError(
59
69
  "Derivation of single photon electron spectrum using a simtool is not yet implemented."
@@ -92,10 +102,22 @@ class SinglePhotonElectronSpectrum:
92
102
  validate_schema_file=None,
93
103
  )
94
104
 
95
- def _derive_spectrum_norm_spe(self):
105
+ def _derive_spectrum_norm_spe(
106
+ self, input_spectrum, afterpulse_spectrum, afterpulse_fitted_spectrum
107
+ ):
96
108
  """
97
109
  Derive single photon electron spectrum using sim_telarray tool 'norm_spe'.
98
110
 
111
+ Parameters
112
+ ----------
113
+ input_spectrum : str
114
+ Input file with amplitude spectrum
115
+ (prompt spectrum only if afterpulse spectrum is given).
116
+ afterpulse_spectrum : str
117
+ Input file with afterpulse spectrum.
118
+ afterpulse_fitted_spectrum : astro.Table
119
+ Fitted afterpulse spectrum data.
120
+
99
121
  Returns
100
122
  -------
101
123
  int
@@ -107,11 +129,13 @@ class SinglePhotonElectronSpectrum:
107
129
  If the command execution fails.
108
130
  """
109
131
  tmp_input_file = self._get_input_data(
110
- input_file=self.args_dict["input_spectrum"],
132
+ input_file=input_spectrum,
133
+ input_table=None,
111
134
  frequency_column=self.prompt_column,
112
135
  )
113
136
  tmp_ap_file = self._get_input_data(
114
- input_file=self.args_dict.get("afterpulse_spectrum"),
137
+ input_file=afterpulse_spectrum,
138
+ input_table=afterpulse_fitted_spectrum,
115
139
  frequency_column=self.afterpulse_column,
116
140
  )
117
141
 
@@ -119,13 +143,14 @@ class SinglePhotonElectronSpectrum:
119
143
  f"{self.args_dict['simtel_path']}/sim_telarray/bin/norm_spe",
120
144
  "-r",
121
145
  f"{self.args_dict['step_size']},{self.args_dict['max_amplitude']}",
122
- tmp_input_file.name,
123
146
  ]
124
147
  if tmp_ap_file:
125
- command.insert(1, "-a")
126
- command.insert(2, f"{tmp_ap_file.name}")
148
+ command.extend(["-a", f"{tmp_ap_file.name}"])
149
+ command.extend(["-s", f"{self.args_dict['scale_afterpulse_spectrum']}"])
150
+ command.extend(["-t", f"{self.args_dict['afterpulse_amplitude_range'][0]}"])
151
+ command.append(tmp_input_file.name)
127
152
 
128
- self._logger.debug(f"Running norm_spe command: {' '.join(command)}")
153
+ self._logger.info(f"Running norm_spe command: {' '.join(command)}")
129
154
  try:
130
155
  result = subprocess.run(command, capture_output=True, text=True, check=True)
131
156
  except subprocess.CalledProcessError as exc:
@@ -142,24 +167,37 @@ class SinglePhotonElectronSpectrum:
142
167
  self.data = result.stdout
143
168
  return result.returncode
144
169
 
145
- def _get_input_data(self, input_file, frequency_column):
170
+ def _get_input_data(self, input_file, input_table, frequency_column):
146
171
  """
147
- Return input data for norm_spe command.
172
+ Return input data in the format required by the norm_spe tool as temporary file.
173
+
174
+ The norm_spe tool requires the data to be space separated values of the amplitude spectrum,
175
+ with two columns: amplitude and frequency.
176
+ Input is validated using the single_pe_spectrum schema (legacy input is not validated).
148
177
 
149
- Input data need to be space separated values of the amplitude spectrum.
178
+ Parameters
179
+ ----------
180
+ input_file : str
181
+ Input file with amplitude spectrum.
182
+ input_table : astro.Table
183
+ Input table with amplitude spectrum.
184
+ frequency_column : str
185
+ Column name of the frequency data.
150
186
  """
151
- input_data = ""
152
187
  if not input_file:
153
188
  return None
154
189
  input_file = Path(input_file)
155
190
 
156
- if input_file.suffix == ".ecsv":
191
+ input_data = ""
192
+ if input_file.suffix == ".ecsv" or input_table:
157
193
  data_validator = validate_data.DataValidator(
158
- schema_file=self.input_schema, data_file=input_file
194
+ schema_file=self.input_schema,
195
+ data_table=input_table,
196
+ data_file=input_file if input_table is None else None,
159
197
  )
160
198
  table = data_validator.validate_and_transform()
161
199
  input_data = "\n".join(f"{row['amplitude']} {row[frequency_column]}" for row in table)
162
- else:
200
+ else: # legacy format
163
201
  with open(input_file, encoding="utf-8") as f:
164
202
  input_data = (
165
203
  f.read().replace(",", " ")
@@ -170,3 +208,113 @@ class SinglePhotonElectronSpectrum:
170
208
  with tempfile.NamedTemporaryFile(delete=False, mode="w", encoding="utf-8") as tmpfile:
171
209
  tmpfile.write(input_data)
172
210
  return tmpfile
211
+
212
+ def fit_afterpulse_spectrum(self):
213
+ """
214
+ Fit afterpulse spectrum with a exponential decay function.
215
+
216
+ Assume input to be in ecsv format with columns 'amplitude', 'frequency (afterpulsing)',
217
+ and 'frequency stdev (afterpulsing)'.
218
+
219
+ Returns
220
+ -------
221
+ astro.Table
222
+ Table with fitted afterpulse spectrum data.
223
+ """
224
+ ap_min = self.args_dict["afterpulse_amplitude_range"][0]
225
+ fix_k = self.args_dict.get("afterpulse_decay_factor_fixed_value")
226
+
227
+ x, y, y_err = self._read_afterpulse_spectrum_for_fit(
228
+ self.args_dict.get("afterpulse_spectrum"), ap_min
229
+ )
230
+ fit_func, p0, bounds = self.afterpulse_fit_function(fix_k=fix_k)
231
+
232
+ result = curve_fit(fit_func, x, y, sigma=y_err, p0=p0, bounds=bounds, absolute_sigma=True)
233
+ params, covariance = result[0], result[1]
234
+ param_errors = np.sqrt(np.diag(covariance))
235
+ predicted = fit_func(x, *params)
236
+ self._afterpulse_fit_statistics(x, y, y_err, params, param_errors, predicted, fix_k)
237
+
238
+ # table with fitted afterpulse spectrum
239
+ x_fit = np.arange(
240
+ ap_min, self.args_dict["afterpulse_amplitude_range"][1], self.args_dict["step_size"]
241
+ )
242
+ y_fit = fit_func(x_fit, *params)
243
+ return Table([x_fit, y_fit], names=["amplitude", self.afterpulse_column])
244
+
245
+ def afterpulse_fit_function(self, fix_k):
246
+ """
247
+ Afterpulse fit function: exponential decay with linear term in the exponent.
248
+
249
+ Starting values and bounds are set for the other parameters using values typical
250
+ for LSTN-design. Allows to fix the K parameter.
251
+
252
+ Parameters
253
+ ----------
254
+ fix_K : float
255
+ Fixed value for K parameter.
256
+
257
+ Returns
258
+ -------
259
+ function
260
+ Exponential decay function with linear term in the exponent.
261
+ """
262
+
263
+ def exp_decay(x, a, b, k):
264
+ return a * np.exp(-1.0 / (b * (k / (x + k))) * x)
265
+
266
+ p0 = [1e-5, 8.0] # Initial guess for [A, B] typical LSTN values
267
+ bounds_lower = [0, 0]
268
+ bounds_upper = [1.0, 20.0]
269
+
270
+ if fix_k is None:
271
+ p0.append(25.0)
272
+ bounds_lower.append(5.0)
273
+ bounds_upper.append(35.0)
274
+ return exp_decay, p0, (bounds_lower, bounds_upper)
275
+
276
+ def exp_decay_fixed_k(x, a, b):
277
+ return exp_decay(x, a, b, k=fix_k)
278
+
279
+ return exp_decay_fixed_k, p0, (bounds_lower, bounds_upper)
280
+
281
+ def _afterpulse_fit_statistics(self, x, y, y_err, params, param_errors, predicted, fix_k):
282
+ """Print and return afterpulse fit statistics."""
283
+ chi2 = np.sum(((y - predicted) / y_err) ** 2)
284
+ ndf = len(x) - len(params)
285
+
286
+ result = {
287
+ "params": params.tolist(),
288
+ "errors": param_errors.tolist(),
289
+ "chi2_ndf": chi2 / ndf if ndf > 0 else np.nan,
290
+ }
291
+ if fix_k is not None:
292
+ result["params"].append(fix_k)
293
+ result["errors"].append(0.0)
294
+
295
+ self._logger.info(f"Fit results: {result}")
296
+ return result
297
+
298
+ def _read_afterpulse_spectrum_for_fit(self, afterpulse_spectrum, fit_min_pe):
299
+ """
300
+ Read afterpulse spectrum data for fitting.
301
+
302
+ Parameters
303
+ ----------
304
+ afterpulse_spectrum : str
305
+ Afterpulse spectrum data file.
306
+ fit_min_pe : float
307
+ Minimum amplitude for fitting.
308
+
309
+ Returns
310
+ -------
311
+ tuple
312
+ Tuple with x, y, y_err data for fitting.
313
+ """
314
+ table = Table.read(afterpulse_spectrum, format="ascii.ecsv")
315
+ x = table["amplitude"]
316
+ y = table[self.afterpulse_column]
317
+ y_err = table["frequency stdev (afterpulsing)"]
318
+ mask = (x >= fit_min_pe) & (y > 0)
319
+ x_fit, y_fit, y_err_fit = x[mask], y[mask], y_err[mask]
320
+ return x_fit, y_fit, y_err_fit
@@ -269,6 +269,7 @@ class ModelDataWriter:
269
269
  "unit": unit,
270
270
  "type": self._get_parameter_type(),
271
271
  "file": self._parameter_is_a_file(),
272
+ "meta_parameter": False,
272
273
  }
273
274
  return self.validate_and_transform(
274
275
  product_data_dict=data_dict,
@@ -426,7 +427,7 @@ class ModelDataWriter:
426
427
  """
427
428
  Prepare data dictionary for writing to json file.
428
429
 
429
- Ensure sim_telarray style lists as strings.
430
+ Ensure sim_telarray style lists as strings 'type' and 'unit' entries.
430
431
  Replace "None" with "null" for unit field.
431
432
 
432
433
  Parameters
@@ -441,13 +442,13 @@ class ModelDataWriter:
441
442
 
442
443
  """
443
444
  try:
444
- data_dict["value"] = gen.convert_list_to_string(data_dict["value"])
445
- data_dict["unit"] = gen.convert_list_to_string(data_dict["unit"], comma_separated=True)
446
- data_dict["type"] = gen.convert_list_to_string(
447
- data_dict["type"], comma_separated=True, collapse_list=True
448
- )
449
445
  if isinstance(data_dict["unit"], str):
450
446
  data_dict["unit"] = data_dict["unit"].replace("None", "null")
447
+ elif isinstance(data_dict["unit"], list):
448
+ data_dict["unit"] = [
449
+ unit.replace("None", "null") if isinstance(unit, str) else unit
450
+ for unit in data_dict["unit"]
451
+ ]
451
452
  except KeyError:
452
453
  pass
453
454
  return data_dict
simtools/db/db_handler.py CHANGED
@@ -15,7 +15,7 @@ from pymongo import MongoClient
15
15
  from simtools.data_model import validate_data
16
16
  from simtools.io_operations import io_handler
17
17
  from simtools.simtel import simtel_table_reader
18
- from simtools.utils import names, value_conversion
18
+ from simtools.utils import general, names, value_conversion
19
19
 
20
20
  __all__ = ["DatabaseHandler"]
21
21
 
@@ -273,8 +273,6 @@ class DatabaseHandler:
273
273
  """
274
274
  Get model parameters for all model versions.
275
275
 
276
- Queries parameters for design and for the specified array element (if necessary).
277
-
278
276
  Parameters
279
277
  ----------
280
278
  site: str
@@ -290,10 +288,16 @@ class DatabaseHandler:
290
288
  """
291
289
  pars = defaultdict(dict)
292
290
  for _model_version in self.get_model_versions(collection):
293
- parameter_data = self.get_model_parameters(
294
- site, array_element_name, collection, _model_version
295
- )
296
- pars[_model_version].update(parameter_data)
291
+ try:
292
+ parameter_data = self.get_model_parameters(
293
+ site, array_element_name, collection, _model_version
294
+ )
295
+ pars[_model_version].update(parameter_data)
296
+ except KeyError:
297
+ self._logger.debug(
298
+ f"Skipping model version {_model_version} - {array_element_name} not found"
299
+ )
300
+ continue
297
301
  return pars
298
302
 
299
303
  def _get_parameter_for_model_version(
@@ -795,10 +799,12 @@ class DatabaseHandler:
795
799
  f"corresponding to the {par_dict['parameter']} parameter, must be provided."
796
800
  )
797
801
  file_path = Path(file_prefix).joinpath(par_dict["value"])
802
+ if not general.is_utf8_file(file_path):
803
+ raise ValueError(f"File is not UTF-8 encoded: {file_path}")
798
804
  files_to_add_to_db.add(f"{file_path}")
799
805
 
800
806
  self._logger.info(
801
- f"Adding a new entry to DB {db_name} and collection {db_name}:\n{par_dict}"
807
+ f"Adding a new entry to DB {db_name} and collection {collection_name}:\n{par_dict}"
802
808
  )
803
809
  collection.insert_one(par_dict)
804
810
 
simtools/dependencies.py CHANGED
@@ -81,18 +81,53 @@ def get_sim_telarray_version():
81
81
 
82
82
  def get_corsika_version():
83
83
  """
84
- Get the version of the corsika package.
84
+ Get the version of the CORSIKA package.
85
85
 
86
86
  Returns
87
87
  -------
88
88
  str
89
- Version of the corsika package.
89
+ Version of the CORSIKA package.
90
90
  """
91
+ version = None
92
+ sim_telarray_path = os.getenv("SIMTOOLS_SIMTEL_PATH")
93
+ if sim_telarray_path is None:
94
+ _logger.warning("Environment variable SIMTOOLS_SIMTEL_PATH is not set.")
95
+ return None
96
+ corsika_command = Path(sim_telarray_path) / "corsika-run" / "corsika"
97
+
98
+ # Below I do not use the standard context manager because
99
+ # it makes mocking in the tests significantly more difficult
100
+ process = subprocess.Popen( # pylint: disable=consider-using-with
101
+ corsika_command,
102
+ stdout=subprocess.PIPE,
103
+ stderr=subprocess.PIPE,
104
+ stdin=subprocess.PIPE,
105
+ text=True,
106
+ )
107
+
108
+ # Capture output until it waits for input
109
+ while True:
110
+ line = process.stdout.readline()
111
+ if not line:
112
+ break
113
+ # Extract the version from the line "NUMBER OF VERSION : 7.7550"
114
+ if "NUMBER OF VERSION" in line:
115
+ version = line.split(":")[1].strip()
116
+ break
117
+ # Check for a specific prompt or indication that the program is waiting for input
118
+ if "DATA CARDS FOR RUN STEERING ARE EXPECTED FROM STANDARD INPUT" in line:
119
+ break
120
+
121
+ process.terminate()
122
+ # Check it's a valid version string
123
+ if version and re.match(r"\d+\.\d+", version):
124
+ return version
91
125
  try:
92
126
  build_opts = get_build_options()
93
127
  except (FileNotFoundError, TypeError):
94
- _logger.warning("CORSIKA version not implemented yet.")
128
+ _logger.warning("Could not get CORSIKA version.")
95
129
  return None
130
+ _logger.debug("Getting the CORSIKA version from the build options.")
96
131
  return build_opts.get("corsika_version")
97
132
 
98
133
 
@@ -641,6 +641,7 @@ class ArrayLayout:
641
641
  "unit": "m",
642
642
  "type": "float64",
643
643
  "file": False,
644
+ "meta_parameter": False,
644
645
  }
645
646
 
646
647
  def get_number_of_telescopes(self):
@@ -0,0 +1,172 @@
1
+ """Retrieve, merge, and write layouts from CTAO common identifiers repository."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ import simtools.utils.general as gen
7
+ from simtools.data_model.metadata_collector import MetadataCollector
8
+ from simtools.data_model.model_data_writer import ModelDataWriter
9
+ from simtools.io_operations import io_handler
10
+ from simtools.utils import names
11
+
12
+ _logger = logging.getLogger(__name__)
13
+
14
+
15
+ def retrieve_array_layouts(site, repository_url, branch_name="main"):
16
+ """
17
+ Retrieve array layouts from CTAO common identifiers repository.
18
+
19
+ Parameters
20
+ ----------
21
+ site : str
22
+ Site identifier.
23
+ repository_url : str
24
+ URL or path to CTAO common identifiers
25
+ branch_name : str
26
+ Repository branch to use for CTAO common identifiers.
27
+
28
+ Returns
29
+ -------
30
+ dict
31
+ Array layouts for all CTAO sites.
32
+ """
33
+ _logger.info(f"Retrieving array layouts from {repository_url} on branch {branch_name}.")
34
+
35
+ if gen.is_url(repository_url):
36
+ array_element_ids = gen.collect_data_from_http(
37
+ url=f"{repository_url}/{branch_name}/array-element-ids.json"
38
+ )
39
+ sub_arrays = gen.collect_data_from_http(
40
+ url=f"{repository_url}/{branch_name}/subarray-ids.json"
41
+ )
42
+ else:
43
+ array_element_ids = gen.collect_data_from_file(
44
+ Path(repository_url) / "array-element-ids.json"
45
+ )
46
+ sub_arrays = gen.collect_data_from_file(Path(repository_url) / "subarray-ids.json")
47
+
48
+ return _get_layouts_per_site(site, sub_arrays, array_element_ids)
49
+
50
+
51
+ def _get_layouts_per_site(site, sub_arrays, array_element_ids):
52
+ """
53
+ Get array layouts for CTAO sites.
54
+
55
+ Parameters
56
+ ----------
57
+ site : str
58
+ Site identifier.
59
+ sub_arrays : dict
60
+ Sub-array definitions.
61
+ array_element_ids : dict
62
+ Array element definitions.
63
+
64
+ Returns
65
+ -------
66
+ dict
67
+ Array layouts for CTAO sites.
68
+ """
69
+ layouts_per_site = []
70
+
71
+ for array in sub_arrays.get("subarrays", []):
72
+ elements = []
73
+ for ids in array.get("array_element_ids", []):
74
+ element_name = _get_array_element_name(ids, array_element_ids)
75
+ if names.get_site_from_array_element_name(element_name) != site:
76
+ break
77
+ elements.append(element_name)
78
+ if len(elements) > 0:
79
+ array_layout = {
80
+ "name": array.get("name"),
81
+ "elements": elements,
82
+ }
83
+ layouts_per_site.append(array_layout)
84
+
85
+ _logger.info(f"CTAO array layout definition: {layouts_per_site}")
86
+ return layouts_per_site
87
+
88
+
89
+ def _get_array_element_name(ids, array_element_ids):
90
+ """Return array element name for common identifier."""
91
+ for element in array_element_ids.get("array_elements", []):
92
+ if element.get("id") == ids:
93
+ return element.get("name")
94
+ return None
95
+
96
+
97
+ def merge_array_layouts(layouts_1, layouts_2):
98
+ """
99
+ Compare array layout dictionaries and merge them.
100
+
101
+ Parameters
102
+ ----------
103
+ layouts_1 : dict
104
+ Array layout dictionary 1.
105
+ layouts_2 : dict
106
+ Array layout dictionary 2.
107
+
108
+ Returns
109
+ -------
110
+ dict
111
+ Merged array layout dictionary based on layout_1.
112
+ """
113
+ merged_layout = layouts_1
114
+ for layout_2 in layouts_2:
115
+ layout_found = False
116
+ for layout_1 in layouts_1.get("value", {}):
117
+ if sorted(layout_1["elements"]) == sorted(layout_2["elements"]):
118
+ print(
119
+ f"Equal telescope list: simtools '{layout_1['name']}' "
120
+ f"and CTAO '{layout_2['name']}'"
121
+ )
122
+ layout_1["name"] = layout_2["name"]
123
+ layout_found = True
124
+ if not layout_found:
125
+ merged_layout["value"].append(
126
+ {
127
+ "name": layout_2["name"],
128
+ "elements": layout_2["elements"],
129
+ }
130
+ )
131
+ _logger.info(f"Adding {layout_2['name']} with {layout_2['elements']}")
132
+ return merged_layout
133
+
134
+
135
+ def write_array_layouts(array_layouts, args_dict, db_config):
136
+ """
137
+ Write array layouts as model parameter.
138
+
139
+ Parameters
140
+ ----------
141
+ args_dict : dict
142
+ Command line arguments.
143
+ array_layouts : dict
144
+ Array layouts to be written.
145
+ db_config : dict
146
+ Database configuration.
147
+ """
148
+ _logger.info(f"Writing updated array layouts to the database for site {args_dict['site']}.")
149
+
150
+ io_handler_instance = io_handler.IOHandler()
151
+ io_handler_instance.set_paths(
152
+ output_path=args_dict["output_path"],
153
+ use_plain_output_path=args_dict["use_plain_output_path"],
154
+ )
155
+ output_file = io_handler_instance.get_output_file(
156
+ f"array-layouts-{args_dict['updated_parameter_version']}.json"
157
+ )
158
+
159
+ ModelDataWriter.dump_model_parameter(
160
+ parameter_name="array_layouts",
161
+ value=array_layouts["value"],
162
+ instrument=args_dict["site"],
163
+ parameter_version=args_dict.get("updated_parameter_version"),
164
+ output_file=output_file,
165
+ use_plain_output_path=args_dict["use_plain_output_path"],
166
+ db_config=db_config,
167
+ )
168
+ MetadataCollector.dump(
169
+ args_dict,
170
+ output_file,
171
+ add_activity_name=True,
172
+ )
@@ -12,7 +12,7 @@ from simtools.io_operations import io_handler
12
12
  from simtools.model.site_model import SiteModel
13
13
  from simtools.model.telescope_model import TelescopeModel
14
14
  from simtools.simtel.simtel_config_writer import SimtelConfigWriter
15
- from simtools.utils import general, names
15
+ from simtools.utils import names
16
16
 
17
17
  __all__ = ["ArrayModel"]
18
18
 
@@ -326,12 +326,11 @@ class ArrayModel:
326
326
  "site": site,
327
327
  "parameter_version": parameter_version,
328
328
  "unique_id": None,
329
- "value": general.convert_list_to_string(
330
- [x.to("m").value, y.to("m").value, z.to("m").value]
331
- ),
329
+ "value": [x.to("m").value, y.to("m").value, z.to("m").value],
332
330
  "unit": "m",
333
331
  "type": "float64",
334
332
  "file": False,
333
+ "meta_parameter": False,
335
334
  }
336
335
 
337
336
  def _get_array_elements_from_list(self, array_elements_list: list[str]) -> dict: