gammasimtools 0.20.0__py3-none-any.whl → 0.22.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 (315) hide show
  1. {gammasimtools-0.20.0.dist-info → gammasimtools-0.22.0.dist-info}/METADATA +2 -3
  2. {gammasimtools-0.20.0.dist-info → gammasimtools-0.22.0.dist-info}/RECORD +313 -296
  3. {gammasimtools-0.20.0.dist-info → gammasimtools-0.22.0.dist-info}/entry_points.txt +3 -2
  4. simtools/_version.py +2 -2
  5. simtools/applications/calculate_incident_angles.py +1 -4
  6. simtools/applications/convert_all_model_parameters_from_simtel.py +1 -2
  7. simtools/applications/convert_model_parameter_from_simtel.py +0 -1
  8. simtools/applications/db_generate_compound_indexes.py +4 -17
  9. simtools/applications/db_upload_model_repository.py +122 -0
  10. simtools/applications/derive_psf_parameters.py +71 -42
  11. simtools/applications/docs_produce_array_element_report.py +1 -1
  12. simtools/applications/docs_produce_calibration_reports.py +1 -1
  13. simtools/applications/docs_produce_model_parameter_reports.py +1 -1
  14. simtools/applications/docs_produce_simulation_configuration_report.py +1 -1
  15. simtools/applications/generate_corsika_histograms.py +8 -185
  16. simtools/applications/maintain_simulation_model_add_production.py +81 -0
  17. simtools/applications/merge_tables.py +1 -1
  18. simtools/applications/plot_array_layout.py +1 -2
  19. simtools/applications/plot_simtel_events.py +2 -228
  20. simtools/applications/print_version.py +8 -7
  21. simtools/applications/production_derive_statistics.py +1 -2
  22. simtools/applications/production_generate_grid.py +1 -1
  23. simtools/applications/simulate_flasher.py +74 -72
  24. simtools/applications/simulate_illuminator.py +52 -186
  25. simtools/applications/{simulate_calibration_events.py → simulate_pedestals.py} +9 -55
  26. simtools/applications/submit_model_parameter_from_external.py +0 -1
  27. simtools/applications/validate_camera_efficiency.py +0 -1
  28. simtools/applications/validate_camera_fov.py +1 -2
  29. simtools/applications/validate_cumulative_psf.py +2 -3
  30. simtools/applications/validate_file_using_schema.py +20 -12
  31. simtools/applications/validate_optics.py +2 -2
  32. simtools/camera/camera_efficiency.py +8 -11
  33. simtools/configuration/commandline_parser.py +1 -7
  34. simtools/configuration/configurator.py +0 -2
  35. simtools/corsika/corsika_config.py +9 -11
  36. simtools/corsika/corsika_histograms.py +82 -1
  37. simtools/data_model/model_data_writer.py +87 -25
  38. simtools/data_model/schema.py +61 -2
  39. simtools/data_model/validate_data.py +1 -1
  40. simtools/db/db_handler.py +103 -48
  41. simtools/db/db_model_upload.py +247 -16
  42. simtools/io/io_handler.py +31 -83
  43. simtools/job_execution/job_manager.py +45 -0
  44. simtools/layout/array_layout_utils.py +1 -5
  45. simtools/model/array_model.py +93 -42
  46. simtools/model/model_parameter.py +20 -9
  47. simtools/model/model_repository.py +197 -109
  48. simtools/model/model_utils.py +21 -6
  49. simtools/model/telescope_model.py +20 -0
  50. simtools/production_configuration/derive_corsika_limits.py +1 -1
  51. simtools/ray_tracing/incident_angles.py +7 -7
  52. simtools/ray_tracing/mirror_panel_psf.py +1 -1
  53. simtools/ray_tracing/psf_parameter_optimisation.py +1106 -565
  54. simtools/ray_tracing/ray_tracing.py +1 -3
  55. simtools/reporting/docs_read_parameters.py +171 -101
  56. simtools/resources/array_elements.yml +26 -0
  57. simtools/runners/corsika_simtel_runner.py +11 -17
  58. simtools/runners/runner_services.py +5 -6
  59. simtools/runners/simtools_runner.py +0 -2
  60. simtools/schemas/application_workflow.metaschema.yml +1 -1
  61. simtools/schemas/common_definitions.schema.yml +39 -0
  62. simtools/schemas/model_parameter.metaschema.yml +19 -13
  63. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +6 -12
  64. simtools/schemas/model_parameters/adjust_gain.schema.yml +0 -5
  65. simtools/schemas/model_parameters/altitude.schema.yml +0 -5
  66. simtools/schemas/model_parameters/array_coordinates.schema.yml +0 -5
  67. simtools/schemas/model_parameters/array_coordinates_UTM.schema.yml +0 -5
  68. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +0 -7
  69. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +0 -7
  70. simtools/schemas/model_parameters/array_layouts.schema.yml +0 -5
  71. simtools/schemas/model_parameters/array_triggers.schema.yml +0 -5
  72. simtools/schemas/model_parameters/array_window.schema.yml +0 -7
  73. simtools/schemas/model_parameters/asum_clipping.schema.yml +0 -3
  74. simtools/schemas/model_parameters/asum_offset.schema.yml +0 -7
  75. simtools/schemas/model_parameters/asum_shaping.schema.yml +0 -7
  76. simtools/schemas/model_parameters/asum_threshold.schema.yml +0 -7
  77. simtools/schemas/model_parameters/atmospheric_profile.schema.yml +0 -5
  78. simtools/schemas/model_parameters/atmospheric_transmission.schema.yml +0 -5
  79. simtools/schemas/model_parameters/axes_offsets.schema.yml +0 -7
  80. simtools/schemas/model_parameters/calibration_devices.schema.yml +30 -0
  81. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +0 -7
  82. simtools/schemas/model_parameters/camera_body_shape.schema.yml +0 -7
  83. simtools/schemas/model_parameters/camera_config_file.schema.yml +0 -7
  84. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +0 -7
  85. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +0 -7
  86. simtools/schemas/model_parameters/camera_degraded_map.schema.yml +0 -7
  87. simtools/schemas/model_parameters/camera_depth.schema.yml +0 -7
  88. simtools/schemas/model_parameters/camera_filter.schema.yml +0 -7
  89. simtools/schemas/model_parameters/camera_filter_incidence_angle.schema.yml +0 -3
  90. simtools/schemas/model_parameters/camera_pixels.schema.yml +0 -7
  91. simtools/schemas/model_parameters/camera_transmission.schema.yml +0 -7
  92. simtools/schemas/model_parameters/channels_per_chip.schema.yml +0 -7
  93. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +0 -7
  94. simtools/schemas/model_parameters/corsika_observation_level.schema.yml +0 -5
  95. simtools/schemas/model_parameters/dark_events.schema.yml +4 -3
  96. simtools/schemas/model_parameters/default_trigger.schema.yml +0 -7
  97. simtools/schemas/model_parameters/design_model.schema.yml +0 -7
  98. simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +0 -7
  99. simtools/schemas/model_parameters/disc_bins.schema.yml +0 -7
  100. simtools/schemas/model_parameters/disc_start.schema.yml +0 -7
  101. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +0 -7
  102. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +0 -7
  103. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +0 -7
  104. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +0 -7
  105. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +0 -7
  106. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +0 -7
  107. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +0 -7
  108. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +0 -7
  109. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +0 -7
  110. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +0 -7
  111. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +0 -7
  112. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +1 -9
  113. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +0 -7
  114. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +0 -7
  115. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +0 -7
  116. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +0 -7
  117. simtools/schemas/model_parameters/dish_shape_length.schema.yml +0 -5
  118. simtools/schemas/model_parameters/dsum_clipping.schema.yml +1 -5
  119. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +0 -3
  120. simtools/schemas/model_parameters/dsum_offset.schema.yml +0 -3
  121. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +0 -3
  122. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +0 -3
  123. simtools/schemas/model_parameters/dsum_prescale.schema.yml +0 -3
  124. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +0 -3
  125. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +0 -3
  126. simtools/schemas/model_parameters/dsum_shaping.schema.yml +0 -3
  127. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +0 -3
  128. simtools/schemas/model_parameters/dsum_threshold.schema.yml +2 -12
  129. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +0 -3
  130. simtools/schemas/model_parameters/effective_focal_length.schema.yml +0 -7
  131. simtools/schemas/model_parameters/epsg_code.schema.yml +0 -5
  132. simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +0 -7
  133. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +2 -9
  134. simtools/schemas/model_parameters/fadc_bins.schema.yml +0 -7
  135. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +0 -7
  136. simtools/schemas/model_parameters/fadc_dev_pedestal.schema.yml +0 -2
  137. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +0 -7
  138. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +0 -7
  139. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +2 -9
  140. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +0 -7
  141. simtools/schemas/model_parameters/fadc_lg_dev_pedestal.schema.yml +0 -2
  142. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +0 -7
  143. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +0 -7
  144. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +0 -7
  145. simtools/schemas/model_parameters/fadc_lg_max_sum.schema.yml +0 -2
  146. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +0 -7
  147. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +0 -7
  148. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +0 -7
  149. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +0 -7
  150. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +0 -7
  151. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +0 -7
  152. simtools/schemas/model_parameters/fadc_long_event_threshold.schema.yml +0 -3
  153. simtools/schemas/model_parameters/fadc_long_sum_bins.schema.yml +0 -3
  154. simtools/schemas/model_parameters/fadc_long_sum_offset.schema.yml +0 -3
  155. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +0 -7
  156. simtools/schemas/model_parameters/fadc_max_sum.schema.yml +0 -2
  157. simtools/schemas/model_parameters/fadc_mhz.schema.yml +0 -7
  158. simtools/schemas/model_parameters/fadc_noise.schema.yml +0 -7
  159. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +0 -7
  160. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +0 -7
  161. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +0 -7
  162. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +0 -7
  163. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +0 -7
  164. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +0 -7
  165. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +0 -7
  166. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +0 -7
  167. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +0 -3
  168. simtools/schemas/model_parameters/flasher_angular_distribution.schema.yml +32 -0
  169. simtools/schemas/model_parameters/flasher_angular_distribution_width.schema.yml +32 -0
  170. simtools/schemas/model_parameters/flasher_bunch_size.schema.yml +28 -0
  171. simtools/schemas/model_parameters/flasher_external_trigger.schema.yml +32 -0
  172. simtools/schemas/model_parameters/flasher_photons.schema.yml +34 -0
  173. simtools/schemas/model_parameters/flasher_position.schema.yml +43 -0
  174. simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +29 -0
  175. simtools/schemas/model_parameters/flasher_pulse_offset.schema.yml +35 -0
  176. simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +30 -0
  177. simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +32 -0
  178. simtools/schemas/model_parameters/flasher_type.schema.yml +28 -0
  179. simtools/schemas/model_parameters/flasher_var_photons.schema.yml +31 -0
  180. simtools/schemas/model_parameters/flasher_wavelength.schema.yml +33 -0
  181. simtools/schemas/model_parameters/flatfielding.schema.yml +0 -7
  182. simtools/schemas/model_parameters/focal_length.schema.yml +0 -7
  183. simtools/schemas/model_parameters/focal_surface_parameters.schema.yml +0 -3
  184. simtools/schemas/model_parameters/focal_surface_ref_radius.schema.yml +0 -3
  185. simtools/schemas/model_parameters/focus_offset.schema.yml +0 -7
  186. simtools/schemas/model_parameters/gain_variation.schema.yml +0 -7
  187. simtools/schemas/model_parameters/geomag_horizontal.schema.yml +2 -7
  188. simtools/schemas/model_parameters/geomag_rotation.schema.yml +2 -7
  189. simtools/schemas/model_parameters/geomag_vertical.schema.yml +2 -7
  190. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +0 -5
  191. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +0 -7
  192. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +0 -7
  193. simtools/schemas/model_parameters/laser_events.schema.yml +4 -3
  194. simtools/schemas/model_parameters/laser_external_trigger.schema.yml +4 -3
  195. simtools/schemas/model_parameters/laser_photons.schema.yml +4 -3
  196. simtools/schemas/model_parameters/laser_pulse_exptime.schema.yml +4 -3
  197. simtools/schemas/model_parameters/laser_pulse_offset.schema.yml +4 -3
  198. simtools/schemas/model_parameters/laser_pulse_sigtime.schema.yml +4 -3
  199. simtools/schemas/model_parameters/laser_pulse_twidth.schema.yml +4 -3
  200. simtools/schemas/model_parameters/laser_var_photons.schema.yml +4 -3
  201. simtools/schemas/model_parameters/laser_wavelength.schema.yml +4 -3
  202. simtools/schemas/model_parameters/led_events.schema.yml +4 -3
  203. simtools/schemas/model_parameters/led_photons.schema.yml +4 -3
  204. simtools/schemas/model_parameters/led_pulse_offset.schema.yml +4 -3
  205. simtools/schemas/model_parameters/led_pulse_sigtime.schema.yml +4 -3
  206. simtools/schemas/model_parameters/led_var_photons.schema.yml +4 -3
  207. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +0 -7
  208. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +0 -7
  209. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +0 -7
  210. simtools/schemas/model_parameters/min_photons.schema.yml +0 -7
  211. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +0 -5
  212. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +0 -7
  213. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +0 -7
  214. simtools/schemas/model_parameters/mirror_class.schema.yml +2 -9
  215. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +0 -7
  216. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +0 -5
  217. simtools/schemas/model_parameters/mirror_list.schema.yml +0 -7
  218. simtools/schemas/model_parameters/mirror_offset.schema.yml +0 -7
  219. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +0 -7
  220. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +0 -7
  221. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +0 -7
  222. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +0 -7
  223. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +0 -7
  224. simtools/schemas/model_parameters/nsb_gain_drop_scale.schema.yml +0 -3
  225. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +0 -7
  226. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +0 -7
  227. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +0 -5
  228. simtools/schemas/model_parameters/nsb_reference_value.schema.yml +0 -5
  229. simtools/schemas/model_parameters/nsb_scaling_factor.schema.yml +0 -5
  230. simtools/schemas/model_parameters/nsb_sky_map.schema.yml +0 -5
  231. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +0 -5
  232. simtools/schemas/model_parameters/num_gains.schema.yml +0 -7
  233. simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +0 -7
  234. simtools/schemas/model_parameters/optics_properties.schema.yml +0 -7
  235. simtools/schemas/model_parameters/parabolic_dish.schema.yml +0 -3
  236. simtools/schemas/model_parameters/pedestal_events.schema.yml +4 -7
  237. simtools/schemas/model_parameters/photon_delay.schema.yml +0 -7
  238. simtools/schemas/model_parameters/photons_per_run.schema.yml +4 -4
  239. simtools/schemas/model_parameters/pixel_cells.schema.yml +0 -3
  240. simtools/schemas/model_parameters/pixels_parallel.schema.yml +0 -3
  241. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +0 -7
  242. simtools/schemas/model_parameters/pm_average_gain.schema.yml +0 -5
  243. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +0 -5
  244. simtools/schemas/model_parameters/pm_gain_index.schema.yml +0 -5
  245. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +0 -7
  246. simtools/schemas/model_parameters/pm_transit_time.schema.yml +4 -9
  247. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +0 -5
  248. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +0 -7
  249. simtools/schemas/model_parameters/primary_mirror_diameter.schema.yml +0 -3
  250. simtools/schemas/model_parameters/primary_mirror_hole_diameter.schema.yml +0 -3
  251. simtools/schemas/model_parameters/primary_mirror_incidence_angle.schema.yml +0 -3
  252. simtools/schemas/model_parameters/primary_mirror_parameters.schema.yml +0 -3
  253. simtools/schemas/model_parameters/primary_mirror_ref_radius.schema.yml +0 -3
  254. simtools/schemas/model_parameters/primary_mirror_segmentation.schema.yml +0 -3
  255. simtools/schemas/model_parameters/qe_variation.schema.yml +0 -7
  256. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +0 -7
  257. simtools/schemas/model_parameters/random_focal_length.schema.yml +2 -7
  258. simtools/schemas/model_parameters/random_generator.schema.yml +0 -7
  259. simtools/schemas/model_parameters/random_mono_probability.schema.yml +0 -7
  260. simtools/schemas/model_parameters/reference_point_altitude.schema.yml +0 -5
  261. simtools/schemas/model_parameters/reference_point_latitude.schema.yml +0 -5
  262. simtools/schemas/model_parameters/reference_point_longitude.schema.yml +0 -5
  263. simtools/schemas/model_parameters/reference_point_utm_east.schema.yml +0 -5
  264. simtools/schemas/model_parameters/reference_point_utm_north.schema.yml +0 -5
  265. simtools/schemas/model_parameters/sampled_output.schema.yml +0 -7
  266. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +0 -7
  267. simtools/schemas/model_parameters/secondary_mirror_baffle.schema.yml +0 -3
  268. simtools/schemas/model_parameters/secondary_mirror_degraded_map.schema.yml +0 -3
  269. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +0 -3
  270. simtools/schemas/model_parameters/secondary_mirror_diameter.schema.yml +0 -3
  271. simtools/schemas/model_parameters/secondary_mirror_hole_diameter.schema.yml +0 -3
  272. simtools/schemas/model_parameters/secondary_mirror_incidence_angle.schema.yml +0 -3
  273. simtools/schemas/model_parameters/secondary_mirror_parameters.schema.yml +0 -3
  274. simtools/schemas/model_parameters/secondary_mirror_ref_radius.schema.yml +0 -3
  275. simtools/schemas/model_parameters/secondary_mirror_reflectivity.schema.yml +0 -3
  276. simtools/schemas/model_parameters/secondary_mirror_segmentation.schema.yml +0 -3
  277. simtools/schemas/model_parameters/secondary_mirror_shadow_diameter.schema.yml +0 -3
  278. simtools/schemas/model_parameters/secondary_mirror_shadow_offset.schema.yml +0 -3
  279. simtools/schemas/model_parameters/stars.schema.yml +0 -5
  280. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +0 -7
  281. simtools/schemas/model_parameters/tailcut_scale.schema.yml +0 -7
  282. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +0 -7
  283. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +0 -7
  284. simtools/schemas/model_parameters/telescope_random_error.schema.yml +0 -7
  285. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +0 -7
  286. simtools/schemas/model_parameters/telescope_transmission.schema.yml +0 -7
  287. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +0 -7
  288. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +0 -7
  289. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +0 -7
  290. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +0 -7
  291. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +0 -7
  292. simtools/schemas/model_parameters/transit_time_error.schema.yml +0 -7
  293. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +0 -7
  294. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +0 -7
  295. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +0 -7
  296. simtools/schemas/model_parameters/trigger_pixels.schema.yml +0 -7
  297. simtools/schemas/production_tables.schema.yml +8 -8
  298. simtools/schemas/simulation_models_info.schema.yml +78 -0
  299. simtools/simtel/simtel_config_writer.py +88 -14
  300. simtools/simtel/simulator_array.py +44 -74
  301. simtools/simtel/simulator_light_emission.py +336 -629
  302. simtools/simtel/simulator_ray_tracing.py +2 -2
  303. simtools/simulator.py +46 -18
  304. simtools/testing/configuration.py +4 -2
  305. simtools/testing/sim_telarray_metadata.py +4 -4
  306. simtools/utils/geometry.py +34 -0
  307. simtools/version.py +111 -0
  308. simtools/{corsika/corsika_histograms_visualize.py → visualization/plot_corsika_histograms.py} +109 -0
  309. simtools/visualization/plot_psf.py +775 -0
  310. simtools/visualization/plot_simtel_events.py +284 -87
  311. simtools/applications/maintain_simulation_model_add_production_table.py +0 -71
  312. simtools/model/flasher_model.py +0 -106
  313. {gammasimtools-0.20.0.dist-info → gammasimtools-0.22.0.dist-info}/WHEEL +0 -0
  314. {gammasimtools-0.20.0.dist-info → gammasimtools-0.22.0.dist-info}/licenses/LICENSE +0 -0
  315. {gammasimtools-0.20.0.dist-info → gammasimtools-0.22.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- """Simulation using the light emission package for calibration devices."""
1
+ """Light emission simulation (e.g. illuminators or flashers)."""
2
2
 
3
3
  import logging
4
4
  import shutil
@@ -10,261 +10,202 @@ import astropy.units as u
10
10
  import numpy as np
11
11
 
12
12
  from simtools.io import io_handler
13
+ from simtools.model.model_utils import initialize_simulation_models
13
14
  from simtools.runners.simtel_runner import SimtelRunner
14
15
  from simtools.utils.general import clear_default_sim_telarray_cfg_directories
16
+ from simtools.utils.geometry import fiducial_radius_from_shape
15
17
 
16
18
  __all__ = ["SimulatorLightEmission"]
17
19
 
18
20
 
19
21
  class SimulatorLightEmission(SimtelRunner):
20
22
  """
21
- Interface with sim_telarray to perform light emission package simulations.
23
+ Light emission simulation (e.g. illuminators or flashers).
22
24
 
23
- The light emission package is used to simulate an artificial light source, used for calibration.
24
- """
25
+ Uses the sim_telarray LightEmission package to simulate the light emission.
25
26
 
26
- def __init__(
27
- self,
28
- *,
29
- telescope_model,
30
- calibration_model=None,
31
- flasher_model=None,
32
- site_model=None,
33
- light_emission_config=None,
34
- light_source_setup=None,
35
- simtel_path=None,
36
- light_source_type=None,
37
- label=None,
38
- test=False,
39
- ):
40
- """Initialize SimtelRunner.
41
-
42
- Parameters
43
- ----------
44
- telescope_model : TelescopeModel
45
- Model of the telescope to be simulated
46
- calibration_model : CalibrationModel, optional
47
- Model of the calibration device to be simulated
48
- flasher_model : FlasherModel, optional
49
- Model of the flasher device to be simulated
50
- site_model : SiteModel, optional
51
- Model of the site
52
- light_emission_config : dict, optional
53
- Configuration for the light emission
54
- light_source_setup : str, optional
55
- Setup for light source positioning ("variable" or "layout")
56
- simtel_path : Path, optional
57
- Path to the sim_telarray installation
58
- light_source_type : str, optional
59
- Type of light source: 'illuminator', or 'flasher'
60
- label : str, optional
61
- Label for the simulation
62
- test : bool, optional
63
- Whether this is a test run
64
- """
65
- super().__init__(simtel_path=simtel_path, label=label, corsika_config=None)
27
+ Parameters
28
+ ----------
29
+ light_emission_config : dict, optional
30
+ Configuration for the light emission (e.g. number of events, model names)
31
+ label : str, optional
32
+ Label for the simulation
33
+ """
66
34
 
35
+ def __init__(self, light_emission_config, db_config=None, label=None):
36
+ """Initialize SimulatorLightEmission."""
67
37
  self._logger = logging.getLogger(__name__)
38
+ self.io_handler = io_handler.IOHandler()
68
39
 
69
- self._telescope_model = telescope_model
70
- self._calibration_model = calibration_model
71
- self._flasher_model = flasher_model
72
- self._site_model = site_model
73
- self.light_emission_config = light_emission_config or {}
74
- self.light_source_setup = light_source_setup
75
- self.light_source_type = light_source_type or "illuminator"
76
- self.test = test
40
+ super().__init__(
41
+ simtel_path=light_emission_config.get("simtel_path"), label=label, corsika_config=None
42
+ )
77
43
 
78
- # IO
79
- self.io_handler = io_handler.IOHandler()
80
- self.output_directory = self.io_handler.get_output_directory(self.label)
44
+ self.output_directory = self.io_handler.get_output_directory(label)
81
45
 
82
- self.number_events = self.light_emission_config["number_events"]
46
+ self.telescope_model, self.site_model, self.calibration_model = (
47
+ initialize_simulation_models(
48
+ label=label,
49
+ db_config=db_config,
50
+ site=light_emission_config.get("site"),
51
+ telescope_name=light_emission_config.get("telescope"),
52
+ calibration_device_name=light_emission_config.get("light_source"),
53
+ model_version=light_emission_config.get("model_version"),
54
+ )
55
+ )
56
+ self.telescope_model.write_sim_telarray_config_file(additional_models=self.site_model)
57
+
58
+ self.light_emission_config = self._initialize_light_emission_configuration(
59
+ light_emission_config
60
+ )
61
+
62
+ def _initialize_light_emission_configuration(self, config):
63
+ """Initialize light emission configuration."""
64
+ if self.calibration_model.get_parameter_value("flasher_type"):
65
+ config["light_source_type"] = self.calibration_model.get_parameter_value(
66
+ "flasher_type"
67
+ ).lower()
68
+
69
+ config["flasher_photons"] = (
70
+ self.calibration_model.get_parameter_value("flasher_photons")
71
+ if not config.get("test", False)
72
+ else 1e8
73
+ )
83
74
 
84
- # photons per run
85
- if self._calibration_model is not None:
86
- self.photons_per_run = (
87
- self._calibration_model.get_parameter_value("photons_per_run")
88
- if not self.test
89
- else 1e8
75
+ if config.get("light_source_position") is not None:
76
+ config["light_source_position"] = (
77
+ np.array(config["light_source_position"], dtype=float) * u.m
90
78
  )
91
- elif self._flasher_model is not None:
92
- self.photons_per_run = (
93
- self._flasher_model.get_parameter_value("photons_per_flasher")
94
- if not self.test
95
- else 1e8
79
+
80
+ return config
81
+
82
+ def simulate(self):
83
+ """
84
+ Simulate light emission.
85
+
86
+ Returns
87
+ -------
88
+ Path
89
+ The output simtel file path.
90
+ """
91
+ run_script = self.prepare_script()
92
+ log_path = Path(self.output_directory) / "logfile.log"
93
+ with open(log_path, "w", encoding="utf-8") as fh:
94
+ subprocess.run(
95
+ run_script,
96
+ shell=False,
97
+ check=False,
98
+ text=True,
99
+ stdout=fh,
100
+ stderr=fh,
101
+ )
102
+ out = Path(self._get_simulation_output_filename())
103
+ if not out.exists():
104
+ self._logger.warning(f"Expected sim_telarray output not found: {out}")
105
+ return out
106
+
107
+ def prepare_script(self):
108
+ """
109
+ Build and return bash run script containing the light-emission command.
110
+
111
+ Returns
112
+ -------
113
+ Path
114
+ Full path of the run script.
115
+ """
116
+ script_dir = self.output_directory.joinpath("scripts")
117
+ script_dir.mkdir(parents=True, exist_ok=True)
118
+
119
+ app_name = self._get_light_emission_application_name()
120
+ script_file = script_dir / f"{app_name}-light_emission.sh"
121
+ self._logger.debug(f"Run bash script - {script_file}")
122
+
123
+ target_out = Path(self._get_simulation_output_filename())
124
+ if target_out.exists():
125
+ raise FileExistsError(
126
+ f"sim_telarray output file exists, cancelling simulation: {target_out}"
96
127
  )
97
- else:
98
- self.photons_per_run = 1e8
99
128
 
100
- # Ensure sim_telarray config exists on disk
101
- if hasattr(self._telescope_model, "write_sim_telarray_config_file"):
102
- self._telescope_model.write_sim_telarray_config_file(additional_model=site_model)
129
+ lines = [
130
+ "#!/usr/bin/env bash\n",
131
+ f"{self._make_light_emission_script()}\n\n",
132
+ (
133
+ f"[ -s '{self.output_directory}/{app_name}.iact.gz' ] || "
134
+ f"{{ echo 'LightEmission did not produce IACT file' >&2; exit 1; }}\n\n"
135
+ ),
136
+ f"{self._make_simtel_script()}\n\n",
137
+ f"rm -f '{self.output_directory}/{app_name}.iact.gz'\n\n",
138
+ ]
103
139
 
104
- # Runtime variables
105
- self.distance = None
140
+ script_file.write_text("".join(lines), encoding="utf-8")
141
+ script_file.chmod(script_file.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP)
142
+ return script_file
106
143
 
107
- def _get_prefix(self) -> str:
144
+ def _get_prefix(self):
108
145
  prefix = self.light_emission_config.get("output_prefix", "")
109
146
  if prefix is not None:
110
147
  return f"{prefix}_"
111
148
  return ""
112
149
 
113
- def _infer_application(self) -> tuple[str, str]:
114
- """Infer the LightEmission application and mode from type/setup.
150
+ def _get_light_emission_application_name(self):
151
+ """
152
+ Return the LightEmission application and mode from type.
115
153
 
116
154
  Returns
117
155
  -------
118
- tuple[str, str]
119
- (app_name, mode)
156
+ str
157
+ app_name
120
158
  """
121
- if self.light_source_type == "flasher":
122
- return ("ff-1m", "flasher")
159
+ if self.light_emission_config["light_source_type"] == "flat_fielding":
160
+ return "ff-1m"
123
161
  # default to illuminator xyzls, mode from setup
124
- mode = self.light_source_setup or "layout"
125
- return ("xyzls", mode)
162
+ return "xyzls"
126
163
 
127
- @staticmethod
128
- def light_emission_default_configuration():
164
+ def _get_telescope_pointing(self):
129
165
  """
130
- Get default light emission configuration.
131
-
132
- Returns
133
- -------
134
- dict
135
- Default configuration light emission.
166
+ Return telescope pointing based on light source type.
136
167
 
137
- """
138
- return {
139
- "zenith_angle": {
140
- "len": 1,
141
- "unit": u.Unit("deg"),
142
- "default": 0.0 * u.deg,
143
- "names": ["zenith", "theta"],
144
- },
145
- "azimuth_angle": {
146
- "len": 1,
147
- "unit": u.Unit("deg"),
148
- "default": 0.0 * u.deg,
149
- "names": ["azimuth", "phi"],
150
- },
151
- "source_distance": {
152
- "len": 1,
153
- "unit": u.Unit("m"),
154
- "default": 1000 * u.m,
155
- "names": ["sourcedist", "srcdist"],
156
- },
157
- "off_axis_angle": {
158
- "len": 1,
159
- "unit": u.Unit("deg"),
160
- "default": 0 * u.deg,
161
- "names": ["off_axis"],
162
- },
163
- "fadc_bins": {
164
- "len": 1,
165
- "unit": u.dimensionless_unscaled,
166
- "default": 128,
167
- "names": ["fadc_bins"],
168
- },
169
- }
170
-
171
- @staticmethod
172
- def flasher_default_configuration():
173
- """
174
- Get default flasher configuration.
168
+ For flat_fielding sims, avoid calibration pointing entirely; default angles to (0,0).
175
169
 
176
170
  Returns
177
171
  -------
178
- dict
179
- Default configuration for flasher devices.
172
+ tuple
173
+ The telescope pointing angles (theta, phi).
174
+
180
175
  """
181
- return {
182
- "number_events": {
183
- "len": 1,
184
- "unit": None,
185
- "default": 1,
186
- "names": ["number_events"],
187
- },
188
- "photons_per_flasher": {
189
- "len": 1,
190
- "unit": None,
191
- "default": 2.5e6,
192
- "names": ["photons"],
193
- },
194
- "bunch_size": {
195
- "len": 1,
196
- "unit": None,
197
- "default": 1.0,
198
- "names": ["bunchsize"],
199
- },
200
- "flasher_position": {
201
- "len": 2,
202
- "unit": u.Unit("cm"),
203
- "default": [0.0, 0.0] * u.cm,
204
- "names": ["xy", "position"],
205
- },
206
- "flasher_depth": {
207
- "len": 1,
208
- "unit": u.Unit("cm"),
209
- "default": 60 * u.cm,
210
- "names": ["depth", "distance"],
211
- },
212
- "flasher_inclination": {
213
- "len": 1,
214
- "unit": u.Unit("deg"),
215
- "default": 0.0 * u.deg,
216
- "names": ["inclination"],
217
- },
218
- "spectrum": {
219
- "len": 1,
220
- "unit": u.Unit("nm"),
221
- "default": 400 * u.nm,
222
- "names": ["wavelength"],
223
- },
224
- "lightpulse": {
225
- "len": 1,
226
- "unit": None,
227
- "default": "Simple:0",
228
- "names": ["pulse"],
229
- },
230
- "angular_distribution": {
231
- "len": 1,
232
- "unit": None,
233
- "default": "isotropic",
234
- "names": ["angular"],
235
- },
236
- "flasher_pattern": {
237
- "len": 1,
238
- "unit": None,
239
- "default": "all",
240
- "names": ["fire", "pattern"],
241
- },
242
- }
243
-
244
- def calibration_pointing_direction(self):
176
+ if self.light_emission_config["light_source_type"] == "flat_fielding":
177
+ return 0.0, 0.0
178
+ if self.light_emission_config.get("light_source_position") is not None:
179
+ self._logger.info("Using fixed (vertical up) telescope pointing.")
180
+ return 0.0, 0.0
181
+ _, angles = self._calibration_pointing_direction()
182
+ return angles[0], angles[1]
183
+
184
+ def _calibration_pointing_direction(self, x_cal=None, y_cal=None, z_cal=None):
245
185
  """
246
186
  Calculate the pointing of the calibration device towards the telescope.
247
187
 
188
+ This is for calibration devices not installed on telescopes (e.g. illuminators).
189
+
248
190
  Returns
249
191
  -------
250
192
  list
251
193
  The pointing vector from the calibration device to the telescope.
252
194
  """
253
- x_cal, y_cal, z_cal = self._calibration_model.get_parameter_value_with_unit(
254
- "array_element_position_ground"
255
- )
195
+ if x_cal is None or y_cal is None or z_cal is None:
196
+ x_cal, y_cal, z_cal = self.calibration_model.get_parameter_value_with_unit(
197
+ "array_element_position_ground"
198
+ )
256
199
  x_cal, y_cal, z_cal = [coord.to(u.m).value for coord in (x_cal, y_cal, z_cal)]
257
200
  cal_vect = np.array([x_cal, y_cal, z_cal])
258
- x_tel, y_tel, z_tel = self._telescope_model.get_parameter_value_with_unit(
201
+ x_tel, y_tel, z_tel = self.telescope_model.get_parameter_value_with_unit(
259
202
  "array_element_position_ground"
260
203
  )
261
204
  x_tel, y_tel, z_tel = [coord.to(u.m).value for coord in (x_tel, y_tel, z_tel)]
262
-
263
205
  tel_vect = np.array([x_tel, y_tel, z_tel])
264
206
 
265
207
  direction_vector = tel_vect - cal_vect
266
208
  # pointing vector from calibration device to telescope
267
-
268
209
  pointing_vector = np.round(direction_vector / np.linalg.norm(direction_vector), 6)
269
210
 
270
211
  # Calculate telescope theta and phi angles
@@ -285,53 +226,52 @@ class SimulatorLightEmission(SimtelRunner):
285
226
  )
286
227
  return pointing_vector.tolist(), [tel_theta, tel_phi, source_theta, source_phi]
287
228
 
288
- def _write_telpos_file(self):
229
+ def _write_telescope_position_file(self):
289
230
  """
290
- Write the telescope positions to a telpos file.
231
+ Write the telescope positions to a telescope_position file.
291
232
 
292
233
  The file will contain lines in the format: x y z r in cm
293
234
 
294
235
  Returns
295
236
  -------
296
237
  Path
297
- The path to the generated telpos file.
238
+ The path to the generated telescope_position file.
298
239
  """
299
- telpos_file = self.output_directory.joinpath("telpos.dat")
300
- x_tel, y_tel, z_tel = self._telescope_model.get_parameter_value_with_unit(
240
+ x_tel, y_tel, z_tel = self.telescope_model.get_parameter_value_with_unit(
301
241
  "array_element_position_ground"
302
242
  )
303
243
  x_tel, y_tel, z_tel = [coord.to(u.cm).value for coord in (x_tel, y_tel, z_tel)]
304
244
 
305
- radius = self._telescope_model.get_parameter_value_with_unit("telescope_sphere_radius")
245
+ radius = self.telescope_model.get_parameter_value_with_unit("telescope_sphere_radius")
306
246
  radius = radius.to(u.cm).value # Convert radius to cm
307
- with telpos_file.open("w", encoding="utf-8") as file:
308
- file.write(f"{x_tel} {y_tel} {z_tel} {radius}\n")
309
247
 
310
- return telpos_file
248
+ telescope_position_file = self.output_directory.joinpath("telescope_position.dat")
249
+ telescope_position_file.write_text(f"{x_tel} {y_tel} {z_tel} {radius}\n", encoding="utf-8")
250
+ return telescope_position_file
311
251
 
312
- def _prepare_flasher_atmosphere_files(self, config_directory: Path) -> int:
313
- """Prepare canonical atmosphere aliases for ff-1m and return model id 1."""
314
- atmo_name = self._site_model.get_parameter_value("atmospheric_profile")
315
- self._logger.debug(f"Using atmosphere profile: {atmo_name}")
252
+ def _prepare_flasher_atmosphere_files(self, config_directory, model_id=1):
253
+ """
254
+ Prepare canonical atmosphere aliases for ff-1m and return model id.
316
255
 
317
- src_path = config_directory.joinpath(atmo_name)
318
- for canonical in ("atmprof1.dat", "atm_profile_model_1.dat"):
319
- dst = config_directory.joinpath(canonical)
320
- if dst.exists() or dst.is_symlink():
321
- try:
322
- dst.unlink()
323
- except OSError:
324
- pass
256
+ The ff-1m tool requires atmosphere files atmprof1.dat or atm_profile_model_1.dat and
257
+ as configuration parameter the atmosphere id ('--atmosphere id').
258
+
259
+ """
260
+ src_path = config_directory / self.site_model.get_parameter_value("atmospheric_profile")
261
+ self._logger.debug(f"Using atmosphere profile: {src_path}")
262
+
263
+ for name in (f"atmprof{model_id}.dat", f"atm_profile_model_{model_id}.dat"):
264
+ dst = config_directory / name
325
265
  try:
326
- dst.symlink_to(src_path)
327
- except OSError:
266
+ if dst.exists() or dst.is_symlink():
267
+ dst.unlink()
328
268
  try:
269
+ dst.symlink_to(src_path)
270
+ except OSError:
329
271
  shutil.copy2(src_path, dst)
330
- except OSError as copy_err:
331
- self._logger.warning(
332
- f"Failed to create atmosphere alias {dst.name}: {copy_err}"
333
- )
334
- return 1
272
+ except OSError as copy_err:
273
+ self._logger.warning(f"Failed to create atmosphere alias {dst.name}: {copy_err}")
274
+ return model_id
335
275
 
336
276
  def _make_light_emission_script(self):
337
277
  """
@@ -345,153 +285,109 @@ class SimulatorLightEmission(SimtelRunner):
345
285
  str
346
286
  The commands to run the Light Emission package
347
287
  """
348
- x_tel, y_tel, z_tel = self._telescope_model.get_parameter_value_with_unit(
349
- "array_element_position_ground"
350
- )
351
-
352
288
  config_directory = self.io_handler.get_model_configuration_directory(
353
- label=self.label, model_version=self._site_model.model_version
289
+ model_version=self.site_model.model_version
354
290
  )
355
- telpos_file = self._write_telpos_file()
356
-
357
- app_name, _ = self._infer_application()
358
-
359
- parts: list[str] = []
360
- # application path
361
- parts.append(str(self._simtel_path.joinpath("sim_telarray/LightEmission/")))
362
- parts.append(f"/{app_name}")
363
-
364
- corsika_observation_level = self._site_model.get_parameter_value_with_unit(
291
+ app_name = self._get_light_emission_application_name()
292
+ corsika_observation_level = self.site_model.get_parameter_value_with_unit(
365
293
  "corsika_observation_level"
366
294
  )
367
- parts.append(
368
- self._build_altitude_atmo_block(
369
- app_name, config_directory, corsika_observation_level, telpos_file
370
- )
371
- )
372
-
373
- parts.append(self._build_source_specific_block(x_tel, y_tel, z_tel, config_directory))
374
-
375
- if self.light_source_type == "illuminator":
376
- parts.append(f" -A {config_directory}/")
377
- parts.append(f"{self._telescope_model.get_parameter_value('atmospheric_profile')}")
378
-
379
- parts.append(f" -o {self.output_directory}/{app_name}.iact.gz")
380
- parts.append("\n")
381
-
382
- return "".join(parts)
383
295
 
384
- def _build_altitude_atmo_block(
385
- self, app_name, config_directory: Path, corsika_observation_level, telpos_file: Path
386
- ) -> str:
387
- """Return CLI segment for altitude/atmosphere and telpos handling."""
296
+ parts = [str(self._simtel_path / "sim_telarray/LightEmission") + f"/{app_name}"]
297
+ parts.extend(self._get_site_command(app_name, config_directory, corsika_observation_level))
298
+ parts.extend(self._get_light_source_command())
299
+ if self.light_emission_config["light_source_type"] == "illuminator":
300
+ parts += [
301
+ "-A",
302
+ (
303
+ f"{config_directory}/"
304
+ f"{self.telescope_model.get_parameter_value('atmospheric_profile')}"
305
+ ),
306
+ ]
307
+ parts += [f"-o {self.output_directory}/{app_name}.iact.gz", "\n"]
308
+ return " ".join(parts)
309
+
310
+ def _get_site_command(self, app_name, config_directory, corsika_observation_level):
311
+ """Return site command with altitude, atmosphere and telescope_position handling."""
388
312
  if app_name in ("ff-1m",):
389
- seg = []
390
- seg.append(" -I.")
391
- seg.append(f" -I{self._simtel_path.joinpath('sim_telarray/cfg')}")
392
- seg.append(f" -I{config_directory}")
393
- seg.append(f" --altitude {corsika_observation_level.to(u.m).value}")
394
313
  atmo_id = self._prepare_flasher_atmosphere_files(config_directory)
395
- seg.append(f" --atmosphere {atmo_id}")
396
- return "".join(seg)
314
+ return [
315
+ "-I.",
316
+ f"-I{self._simtel_path / 'sim_telarray/cfg'}",
317
+ f"-I{config_directory}",
318
+ f"--altitude {corsika_observation_level.to(u.m).value}",
319
+ f"--atmosphere {atmo_id}",
320
+ ]
397
321
  # default path (not used for flasher now, but kept for completeness)
398
- return f" -h {corsika_observation_level.to(u.m).value} --telpos-file {telpos_file}"
399
-
400
- def _build_source_specific_block(self, _x_tel, _y_tel, _z_tel, _config_directory: Path) -> str:
401
- """Return CLI segment for light-source specific flags."""
402
- if self.light_source_type == "flasher":
403
- return self._add_flasher_command_options("")
404
- if self.light_source_type == "illuminator":
405
- return self._add_illuminator_command_options("")
406
- self._logger.warning("Unknown light_source_type '%s'", self.light_source_type)
407
- return ""
408
-
409
- def _add_flasher_command_options(self, command):
410
- """Add flasher-specific options to the script (uniform ff-1m)."""
411
- return self._add_flasher_options(command)
322
+ return [
323
+ f"-h {corsika_observation_level.to(u.m).value} ",
324
+ f"--telpos-file {self._write_telescope_position_file()}",
325
+ ]
326
+
327
+ def _get_light_source_command(self):
328
+ """Return light-source specific command options."""
329
+ if self.light_emission_config["light_source_type"] == "flat_fielding":
330
+ return self._add_flasher_command_options()
331
+ if self.light_emission_config["light_source_type"] == "illuminator":
332
+ return self._add_illuminator_command_options()
333
+ raise ValueError(
334
+ f"Unknown light_source_type '{self.light_emission_config['light_source_type']}'"
335
+ )
412
336
 
413
- def _add_flasher_options(self, command):
337
+ def _add_flasher_command_options(self):
414
338
  """Add flasher options for all telescope types (ff-1m style)."""
415
- # For MST/LST we used to use ff-1m; now apply same for all telescopes
416
- flasher_xy = self._flasher_model.get_parameter_value_with_unit("flasher_position")
417
- flasher_distance = self._flasher_model.get_parameter_value_with_unit("flasher_depth")
418
- # Camera radius required for application, Radius of fiducial sphere enclosing camera
419
- camera_radius = (
420
- self._telescope_model.get_parameter_value_with_unit("camera_body_diameter")
339
+ flasher_xyz = self.calibration_model.get_parameter_value_with_unit("flasher_position")
340
+ camera_radius = fiducial_radius_from_shape(
341
+ self.telescope_model.get_parameter_value_with_unit("camera_body_diameter")
421
342
  .to(u.cm)
422
- .value
423
- / 2
343
+ .value,
344
+ self.telescope_model.get_parameter_value("camera_body_shape"),
424
345
  )
425
- spectrum = self._flasher_model.get_parameter_value_with_unit("spectrum")
426
- pulse = self._flasher_model.get_parameter_value("lightpulse")
427
- angular = self._flasher_model.get_parameter_value("angular_distribution")
428
- bunch_size = self._flasher_model.get_parameter_value("bunch_size")
429
-
430
- # Convert to plain numbers for CLI
431
- fx = flasher_xy[0].to(u.cm).value
432
- fy = flasher_xy[1].to(u.cm).value
433
- dist_cm = flasher_distance.to(u.cm).value
434
- spec_nm = int(spectrum.to(u.nm).value)
435
-
436
- command += f" --events {self.number_events}"
437
- command += f" --photons {self.photons_per_run}"
438
- command += f" --bunchsize {bunch_size}"
439
- command += f" --xy {fx},{fy}"
440
- command += f" --distance {dist_cm}"
441
- command += f" --camera-radius {camera_radius}"
442
- command += f" --spectrum {spec_nm}"
443
- command += f" --lightpulse {pulse}"
444
- command += f" --angular-distribution {angular}"
445
- return command
446
-
447
- def _add_illuminator_command_options(self, command):
448
- """
449
- Add illuminator-specific command options to the light emission script.
450
-
451
- Parameters
452
- ----------
453
- command : str
454
- The command string to add options to
455
-
456
- Returns
457
- -------
458
- str
459
- The updated command string
460
- """
461
- if self.light_source_setup == "variable":
462
- command += f" -x {self.light_emission_config['x_pos']['default'].to(u.cm).value}"
463
- command += f" -y {self.light_emission_config['y_pos']['default'].to(u.cm).value}"
464
- command += f" -z {self.light_emission_config['z_pos']['default'].to(u.cm).value}"
465
- command += (
466
- f" -d {','.join(map(str, self.light_emission_config['direction']['default']))}"
467
- )
468
- command += f" -n {self.photons_per_run}"
469
-
470
- elif self.light_source_setup == "layout":
471
- x_cal, y_cal, z_cal = self._calibration_model.get_parameter_value_with_unit(
346
+ flasher_wavelength = self.calibration_model.get_parameter_value_with_unit(
347
+ "flasher_wavelength"
348
+ )
349
+ dist_cm = self.calculate_distance_focal_plane_calibration_device().to(u.cm).value
350
+ angular_distribution = self._get_angular_distribution_string_for_sim_telarray()
351
+
352
+ return [
353
+ f"--events {self.light_emission_config['number_of_events']}",
354
+ f"--photons {self.light_emission_config['flasher_photons']}",
355
+ f"--bunchsize {self.calibration_model.get_parameter_value('flasher_bunch_size')}",
356
+ f"--xy {flasher_xyz[0].to(u.cm).value},{flasher_xyz[1].to(u.cm).value}",
357
+ f"--distance {dist_cm}",
358
+ f"--camera-radius {camera_radius}",
359
+ f"--spectrum {int(flasher_wavelength.to(u.nm).value)}",
360
+ f"--lightpulse {self._get_pulse_shape_string_for_sim_telarray()}",
361
+ f"--angular-distribution {angular_distribution}",
362
+ ]
363
+
364
+ def _add_illuminator_command_options(self):
365
+ """Get illuminator-specific command options for light emission script."""
366
+ pos = self.light_emission_config.get("light_source_position")
367
+ if pos is None:
368
+ pos = self.calibration_model.get_parameter_value_with_unit(
472
369
  "array_element_position_ground"
473
370
  )
474
- command += f" -x {x_cal.to(u.cm).value}"
475
- command += f" -y {y_cal.to(u.cm).value}"
476
- command += f" -z {z_cal.to(u.cm).value}"
477
- pointing_vector = self.calibration_pointing_direction()[0]
478
- command += f" -d {','.join(map(str, pointing_vector))}"
479
-
480
- command += f" -n {self.photons_per_run}"
481
- self._logger.info(f"Photons per run: {self.photons_per_run} ")
482
-
483
- laser_wavelength = self._calibration_model.get_parameter_value_with_unit(
484
- "laser_wavelength"
485
- )
486
- command += f" -s {int(laser_wavelength.to(u.nm).value)}"
487
-
488
- led_pulse_sigtime = self._calibration_model.get_parameter_value_with_unit(
489
- "led_pulse_sigtime"
490
- )
491
- command += f" -p Gauss:{led_pulse_sigtime.to(u.ns).value}"
492
- command += " -a isotropic"
493
-
494
- return command
371
+ x_cal, y_cal, z_cal = pos
372
+ if self.light_emission_config.get("light_source_pointing"):
373
+ pointing_vector = self.light_emission_config["light_source_pointing"]
374
+ else:
375
+ pointing_vector = self._calibration_pointing_direction(x_cal, y_cal, z_cal)[0]
376
+ flasher_wavelength = self.calibration_model.get_parameter_value_with_unit(
377
+ "flasher_wavelength"
378
+ )
379
+ angular_distribution = self._get_angular_distribution_string_for_sim_telarray()
380
+
381
+ return [
382
+ f"-x {x_cal.to(u.cm).value}",
383
+ f"-y {y_cal.to(u.cm).value}",
384
+ f"-z {z_cal.to(u.cm).value}",
385
+ f"-d {','.join(map(str, pointing_vector))}",
386
+ f"-n {self.light_emission_config['flasher_photons']}",
387
+ f"-s {int(flasher_wavelength.to(u.nm).value)}",
388
+ f"-p {self._get_pulse_shape_string_for_sim_telarray()}",
389
+ f"-a {angular_distribution}",
390
+ ]
495
391
 
496
392
  def _make_simtel_script(self):
497
393
  """
@@ -502,291 +398,102 @@ class SimulatorLightEmission(SimtelRunner):
502
398
  str
503
399
  The command to run sim_telarray
504
400
  """
505
- # For flasher sims, avoid calibration pointing entirely; default angles to (0,0)
506
- if self.light_source_type == "flasher":
507
- angles = [0, 0]
508
- else:
509
- _, angles = self.calibration_pointing_direction()
401
+ theta, phi = self._get_telescope_pointing()
510
402
 
511
403
  simtel_bin = self._simtel_path.joinpath("sim_telarray/bin/sim_telarray/")
512
- # Build command without prefix; caller will add SIM_TELARRAY_CONFIG_PATH once
513
- command = f"{simtel_bin} "
514
- command += f"-I{self._telescope_model.config_file_directory} "
515
- command += f"-I{simtel_bin} "
516
- command += f"-c {self._telescope_model.config_file_path} "
517
- self._remove_line_from_config(self._telescope_model.config_file_path, "array_triggers")
518
- self._remove_line_from_config(self._telescope_model.config_file_path, "axes_offsets")
519
-
520
- command += "-DNUM_TELESCOPES=1 "
521
-
522
- command += super().get_config_option(
523
- "altitude",
524
- self._site_model.get_parameter_value_with_unit("corsika_observation_level")
525
- .to(u.m)
526
- .value,
527
- )
528
- command += super().get_config_option(
529
- "atmospheric_transmission",
530
- self._site_model.get_parameter_value("atmospheric_transmission"),
531
- )
532
- command += super().get_config_option("TRIGGER_TELESCOPES", "1")
533
-
534
- command += super().get_config_option("TELTRIG_MIN_SIGSUM", "2")
535
- command += super().get_config_option("PULSE_ANALYSIS", "-30")
536
- command += super().get_config_option("MAXIMUM_TELESCOPES", 1)
537
-
538
- if self.light_source_type == "variable":
539
- command += super().get_config_option("telescope_theta", 0)
540
- command += super().get_config_option("telescope_phi", 0)
541
- else:
542
- command += super().get_config_option("telescope_theta", f"{angles[0]}")
543
- command += super().get_config_option("telescope_phi", f"{angles[1]}")
544
-
545
- # For flasher runs, bypass reflections on primary mirror
546
- if self.light_source_type == "flasher":
547
- command += super().get_config_option("Bypass_Optics", "1")
548
404
 
549
- command += super().get_config_option("power_law", "2.68")
550
- app_name, app_mode = self._infer_application()
405
+ parts = [
406
+ f"{simtel_bin}",
407
+ f"-I{self.telescope_model.config_file_directory}",
408
+ f"-I{simtel_bin}",
409
+ f"-c {self.telescope_model.config_file_path}",
410
+ "-DNUM_TELESCOPES=1",
411
+ super().get_config_option(
412
+ "altitude",
413
+ self.site_model.get_parameter_value_with_unit("corsika_observation_level")
414
+ .to(u.m)
415
+ .value,
416
+ ),
417
+ super().get_config_option(
418
+ "atmospheric_transmission",
419
+ self.site_model.get_parameter_value("atmospheric_transmission"),
420
+ ),
421
+ super().get_config_option("TRIGGER_TELESCOPES", "1"),
422
+ super().get_config_option("TELTRIG_MIN_SIGSUM", "2"),
423
+ super().get_config_option("PULSE_ANALYSIS", "-30"),
424
+ super().get_config_option("MAXIMUM_TELESCOPES", 1),
425
+ super().get_config_option("telescope_theta", f"{theta}"),
426
+ super().get_config_option("telescope_phi", f"{phi}"),
427
+ ]
428
+
429
+ if self.light_emission_config["light_source_type"] == "flat_fielding":
430
+ parts.append(super().get_config_option("Bypass_Optics", "1"))
431
+
432
+ app_name = self._get_light_emission_application_name()
551
433
  pref = self._get_prefix()
552
- command += super().get_config_option(
553
- "input_file", f"{self.output_directory}/{app_name}.iact.gz"
554
- )
555
- dist_suffix = ""
556
- if self.light_source_setup == "variable":
557
- try:
558
- dist_val = int(self._get_distance_for_plotting().to_value(u.m))
559
- dist_suffix = f"_d_{dist_val}"
560
- except Exception: # pylint:disable=broad-except
561
- dist_suffix = ""
562
-
563
- command += super().get_config_option(
564
- "output_file",
565
- f"{self.output_directory}/{pref}{app_name}_{app_mode}{dist_suffix}.simtel.zst",
566
- )
567
- command += super().get_config_option(
568
- "histogram_file",
569
- f"{self.output_directory}/{pref}{app_name}_{app_mode}{dist_suffix}.ctsim.hdata\n",
570
- )
571
-
572
- # Remove the default sim_telarray configuration directories
573
- return clear_default_sim_telarray_cfg_directories(command)
574
-
575
- def _remove_line_from_config(self, file_path, line_prefix):
576
- """
577
- Remove lines starting with a specific prefix from the config.
578
-
579
- Parameters
580
- ----------
581
- file_path : Path
582
- The path to the configuration file.
583
- line_prefix : str
584
- The prefix of lines to be removed.
585
- """
586
- file_path = Path(file_path)
587
- with file_path.open("r", encoding="utf-8") as file:
588
- lines = file.readlines()
589
-
590
- with file_path.open("w", encoding="utf-8") as file:
591
- for line in lines:
592
- if not line.startswith(line_prefix):
593
- file.write(line)
594
-
595
- def prepare_script(self):
596
- """
597
- Build and return bash run script containing the light-emission command.
598
-
599
- Returns
600
- -------
601
- Path
602
- Full path of the run script.
603
- """
604
- self._logger.debug("Creating run bash script")
605
-
606
- _script_dir = self.output_directory.joinpath("scripts")
607
- _script_dir.mkdir(parents=True, exist_ok=True)
608
- _script_file = _script_dir.joinpath(f"{self._infer_application()[0]}-lightemission.sh")
609
- self._logger.debug(f"Run bash script - {_script_file}")
610
-
611
- target_out = Path(self._get_simulation_output_filename())
612
- if target_out.exists():
613
- msg = f"Simtel output file exists already, cancelling simulation: {target_out}"
614
- self._logger.error(msg)
615
- raise FileExistsError(msg)
616
-
617
- command_le = self._make_light_emission_script()
618
- command_simtel = self._make_simtel_script()
619
-
620
- with _script_file.open("w", encoding="utf-8") as file:
621
- file.write("#!/usr/bin/env bash\n")
622
-
623
- file.write(f"{command_le}\n\n")
624
- app_name, _ = self._infer_application()
625
- file.write(
626
- f"[ -s '{self.output_directory}/{app_name}.iact.gz' ] || "
627
- f"{{ echo 'LightEmission did not produce IACT file' >&2; exit 1; }}\n\n"
628
- )
629
- file.write(f"{command_simtel}\n\n")
630
-
631
- # Cleanup intermediate IACT file at the end of the run
632
- file.write(f"rm -f '{self.output_directory}/{app_name}.iact.gz'\n\n")
633
-
634
- _script_file.chmod(_script_file.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP)
635
- return _script_file
434
+ parts += [
435
+ super().get_config_option("power_law", "2.68"),
436
+ super().get_config_option("input_file", f"{self.output_directory}/{app_name}.iact.gz"),
437
+ super().get_config_option(
438
+ "output_file", f"{self.output_directory}/{pref}{app_name}.simtel.zst"
439
+ ),
440
+ super().get_config_option(
441
+ "histogram_file", f"{self.output_directory}/{pref}{app_name}.ctsim.hdata\n"
442
+ ),
443
+ ]
444
+
445
+ return clear_default_sim_telarray_cfg_directories(" ".join(parts))
636
446
 
637
447
  def _get_simulation_output_filename(self):
638
448
  """Get the filename of the simulation output."""
639
- dist_suffix = ""
640
- if self.light_source_setup == "variable":
641
- try:
642
- dist_val = int(self._get_distance_for_plotting().to_value(u.m))
643
- dist_suffix = f"_d_{dist_val}"
644
- except Exception: # pylint:disable=broad-except
645
- dist_suffix = ""
646
- app_name, app_mode = self._infer_application()
449
+ app_name = self._get_light_emission_application_name()
647
450
  pref = self._get_prefix()
648
- return f"{self.output_directory}/{pref}{app_name}_{app_mode}{dist_suffix}.simtel.zst"
649
-
650
- def _get_distance_for_plotting(self):
651
- """Get the distance to be used for plotting as an astropy Quantity.
451
+ return f"{self.output_directory}/{pref}{app_name}.simtel.zst"
652
452
 
653
- For flasher runs, use the flasher_depth (cm) from the flasher model.
654
- For illuminator runs, use the configured z_pos quantity.
655
- Otherwise, fall back to self.distance if set, or 0 m.
453
+ def calculate_distance_focal_plane_calibration_device(self):
656
454
  """
657
- if self.light_source_type == "flasher" and self._flasher_model is not None:
658
- return self._flasher_model.get_parameter_value_with_unit("flasher_depth").to(u.m)
455
+ Calculate distance between focal plane and calibration device.
659
456
 
660
- def _as_meters(val):
661
- if isinstance(val, u.Quantity):
662
- return val.to(u.m)
663
- try:
664
- return float(val) * u.m
665
- except (TypeError, ValueError):
666
- return None
667
-
668
- cfg = self.light_emission_config or {}
669
- z = cfg.get("z_pos")
670
- if isinstance(z, dict):
671
- z_def = z.get("default")
672
- z_val = z_def[0] if isinstance(z_def, list | tuple) and z_def else z_def
673
- z_q = _as_meters(z_val)
674
- if z_q is not None:
675
- return z_q
676
-
677
- d_q = _as_meters(getattr(self, "distance", None))
678
- if d_q is not None:
679
- return d_q
680
-
681
- return 0 * u.m
682
-
683
- def run_simulation(self) -> Path:
684
- """Run the light emission simulation and return the output simtel file path."""
685
- run_script = self.prepare_script()
686
- log_path = Path(self.output_directory) / "logfile.log"
687
- with open(log_path, "w", encoding="utf-8") as fh:
688
- subprocess.run(
689
- run_script,
690
- shell=False,
691
- check=False,
692
- text=True,
693
- stdout=fh,
694
- stderr=fh,
695
- )
696
- out = Path(self._get_simulation_output_filename())
697
- if not out.exists():
698
- self._logger.warning(f"Expected simtel output not found: {out}")
699
- return out
457
+ For flasher-type light sources. Flasher position is given in mirror coordinates,
458
+ with positive z pointing towards the camera, so the distance is focal_length - flasher_z.
700
459
 
701
- def distance_list(self, arg):
460
+ Returns
461
+ -------
462
+ astropy.units.Quantity
463
+ Distance between calibration device and focal plane.
702
464
  """
703
- Convert distance list to astropy quantities.
465
+ focal_length = self.telescope_model.get_parameter_value_with_unit("focal_length").to(u.m)
466
+ flasher_z = self.calibration_model.get_parameter_value_with_unit("flasher_position")[2].to(
467
+ u.m
468
+ )
469
+ return focal_length - flasher_z
704
470
 
705
- Parameters
706
- ----------
707
- arg: list
708
- List of distances.
471
+ def _get_angular_distribution_string_for_sim_telarray(self):
472
+ """
473
+ Get the angular distribution string for sim_telarray.
709
474
 
710
475
  Returns
711
476
  -------
712
- values: list
713
- List of distances as astropy quantities.
477
+ str
478
+ The angular distribution string.
714
479
  """
715
- try:
716
- return [float(x) * u.m for x in arg]
717
- except ValueError as exc:
718
- raise ValueError("Distances must be numeric values") from exc
480
+ option_string = self.calibration_model.get_parameter_value(
481
+ "flasher_angular_distribution"
482
+ ).lower()
483
+ width = self.calibration_model.get_parameter_value_with_unit(
484
+ "flasher_angular_distribution_width"
485
+ )
486
+ return f"{option_string}:{width.to(u.deg).value}" if width is not None else option_string
719
487
 
720
- def update_light_emission_config(self, key: str, value):
721
- """
722
- Update the light emission configuration.
723
-
724
- Parameters
725
- ----------
726
- key : str
727
- The key in the configuration to update.
728
- value : Any
729
- The new value to set for the key.
488
+ def _get_pulse_shape_string_for_sim_telarray(self):
730
489
  """
731
- if key in self.light_emission_config:
732
- self.light_emission_config[key]["default"] = value
733
- else:
734
- raise KeyError(f"Key '{key}' not found in light emission configuration.")
490
+ Get the pulse shape string for sim_telarray.
735
491
 
736
- def calculate_distance_telescope_calibration_device(self):
737
- """Calculate distance(s) between telescope and calibration device."""
738
- if self.light_source_setup == "layout":
739
- # Layout positions: Use DB coordinates
740
- x_cal, y_cal, z_cal = self._calibration_model.get_parameter_value_with_unit(
741
- "array_element_position_ground"
742
- )
743
- x_cal, y_cal, z_cal = [coord.to(u.m).value for coord in (x_cal, y_cal, z_cal)]
744
- x_tel, y_tel, z_tel = self._telescope_model.get_parameter_value_with_unit(
745
- "array_element_position_ground"
746
- )
747
- x_tel, y_tel, z_tel = [coord.to(u.m).value for coord in (x_tel, y_tel, z_tel)]
748
- tel_vect = np.array([x_tel, y_tel, z_tel])
749
- cal_vect = np.array([x_cal, y_cal, z_cal])
750
- distance = np.linalg.norm(cal_vect - tel_vect)
751
- self._logger.info(f"Distance between telescope and calibration device: {distance} m")
752
- return [distance * u.m]
753
-
754
- # Variable positions: Calculate distances for all positions
755
- x_tel = self.light_emission_config["x_pos"]["default"].to(u.m).value
756
- y_tel = self.light_emission_config["y_pos"]["default"].to(u.m).value
757
- z_positions = self.light_emission_config["z_pos"]["default"]
758
-
759
- distances = []
760
- for z in z_positions:
761
- tel_vect = np.array([x_tel, y_tel, z.to(u.m).value])
762
- cal_vect = np.array([0, 0, 0]) # Calibration device at origin
763
- distances.append(np.linalg.norm(cal_vect - tel_vect) * u.m)
764
- return distances
765
-
766
- def simulate_variable_distances(self, args_dict):
767
- """Simulate light emission for variable distances and return output files list."""
768
- if args_dict["distances_ls"] is not None:
769
- self.update_light_emission_config(
770
- "z_pos", self.distance_list(args_dict["distances_ls"])
771
- )
772
- self._logger.info(
773
- f"Simulating for distances: {self.light_emission_config['z_pos']['default']}"
774
- )
775
- outputs: list[Path] = []
776
- distances = self.calculate_distance_telescope_calibration_device()
777
-
778
- for current_distance, z_pos in zip(
779
- distances, self.light_emission_config["z_pos"]["default"]
780
- ):
781
- self.update_light_emission_config("z_pos", z_pos)
782
- self.distance = current_distance
783
- outputs.append(self.run_simulation())
784
- return outputs
785
-
786
- def simulate_layout_positions(self, args_dict): # pylint: disable=unused-argument
787
- """Simulate light emission for layout positions and return output files list."""
788
- # args_dict kept for API symmetry; explicitly mark as unused
789
- del args_dict
790
- self.distance = self.calculate_distance_telescope_calibration_device()[0]
791
- # Single distance for layout
792
- return [self.run_simulation()]
492
+ Returns
493
+ -------
494
+ str
495
+ The pulse shape string.
496
+ """
497
+ option_string = self.calibration_model.get_parameter_value("flasher_pulse_shape").lower()
498
+ width = self.calibration_model.get_parameter_value_with_unit("flasher_pulse_width")
499
+ return f"{option_string}:{width.to(u.ns).value}" if width is not None else option_string