gammasimtools 0.17.0__py3-none-any.whl → 0.19.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 (363) hide show
  1. {gammasimtools-0.17.0.dist-info → gammasimtools-0.19.0.dist-info}/METADATA +27 -69
  2. gammasimtools-0.19.0.dist-info/RECORD +393 -0
  3. {gammasimtools-0.17.0.dist-info → gammasimtools-0.19.0.dist-info}/entry_points.txt +10 -2
  4. {gammasimtools-0.17.0.dist-info → gammasimtools-0.19.0.dist-info}/licenses/LICENSE +1 -1
  5. simtools/_version.py +16 -3
  6. simtools/applications/calculate_trigger_rate.py +1 -1
  7. simtools/applications/convert_all_model_parameters_from_simtel.py +4 -3
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
  9. simtools/applications/db_add_simulation_model_from_repository_to_db.py +10 -1
  10. simtools/applications/db_add_value_from_json_to_db.py +2 -1
  11. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +8 -13
  12. simtools/applications/db_generate_compound_indexes.py +61 -0
  13. simtools/applications/db_get_file_from_db.py +1 -1
  14. simtools/applications/db_get_parameter_from_db.py +4 -4
  15. simtools/applications/db_inspect_databases.py +20 -10
  16. simtools/applications/derive_mirror_rnda.py +18 -12
  17. simtools/applications/derive_psf_parameters.py +59 -309
  18. simtools/applications/docs_produce_array_element_report.py +1 -1
  19. simtools/applications/docs_produce_calibration_reports.py +1 -1
  20. simtools/applications/docs_produce_model_parameter_reports.py +1 -1
  21. simtools/applications/docs_produce_simulation_configuration_report.py +1 -1
  22. simtools/applications/generate_corsika_histograms.py +1 -1
  23. simtools/applications/generate_default_metadata.py +8 -24
  24. simtools/applications/generate_sim_telarray_histograms.py +1 -1
  25. simtools/applications/generate_simtel_event_data.py +97 -5
  26. simtools/applications/maintain_simulation_model_add_production_table.py +71 -0
  27. simtools/applications/maintain_simulation_model_compare_productions.py +98 -0
  28. simtools/applications/{verify_simulation_model_production_tables.py → maintain_simulation_model_verify_production_tables.py} +9 -1
  29. simtools/applications/merge_tables.py +16 -18
  30. simtools/applications/plot_array_layout.py +3 -3
  31. simtools/applications/plot_simtel_events.py +379 -0
  32. simtools/applications/plot_tabular_data.py +21 -3
  33. simtools/applications/plot_tabular_data_for_model_parameter.py +104 -0
  34. simtools/applications/print_version.py +8 -9
  35. simtools/applications/production_derive_corsika_limits.py +64 -27
  36. simtools/applications/production_derive_statistics.py +1 -1
  37. simtools/applications/production_generate_grid.py +2 -2
  38. simtools/applications/production_merge_corsika_limits.py +214 -0
  39. simtools/applications/run_application.py +47 -113
  40. simtools/applications/simulate_calibration_events.py +166 -0
  41. simtools/applications/simulate_flasher.py +141 -0
  42. simtools/applications/{simulate_light_emission.py → simulate_illuminator.py} +35 -99
  43. simtools/applications/simulate_prod.py +6 -24
  44. simtools/applications/simulate_prod_htcondor_generator.py +7 -0
  45. simtools/applications/submit_array_layouts.py +2 -1
  46. simtools/applications/submit_model_parameter_from_external.py +1 -1
  47. simtools/applications/validate_camera_efficiency.py +30 -12
  48. simtools/applications/validate_camera_fov.py +1 -1
  49. simtools/applications/validate_cumulative_psf.py +1 -1
  50. simtools/applications/validate_file_using_schema.py +2 -1
  51. simtools/applications/validate_optics.py +1 -1
  52. simtools/camera/camera_efficiency.py +61 -45
  53. simtools/camera/single_photon_electron_spectrum.py +1 -1
  54. simtools/configuration/commandline_parser.py +31 -1
  55. simtools/configuration/configurator.py +4 -4
  56. simtools/constants.py +2 -0
  57. simtools/corsika/corsika_config.py +45 -25
  58. simtools/corsika/corsika_histograms.py +6 -5
  59. simtools/data_model/data_reader.py +2 -3
  60. simtools/data_model/metadata_collector.py +32 -36
  61. simtools/data_model/metadata_model.py +15 -12
  62. simtools/data_model/model_data_writer.py +13 -32
  63. simtools/data_model/schema.py +88 -24
  64. simtools/data_model/validate_data.py +34 -9
  65. simtools/db/db_handler.py +48 -37
  66. simtools/db/db_model_upload.py +3 -3
  67. simtools/dependencies.py +88 -25
  68. simtools/io/ascii_handler.py +279 -0
  69. simtools/{io_operations → io}/io_handler.py +25 -3
  70. simtools/{io_operations/io_table_handler.py → io/table_handler.py} +1 -1
  71. simtools/job_execution/htcondor_script_generator.py +15 -4
  72. simtools/layout/array_layout.py +1 -1
  73. simtools/layout/array_layout_utils.py +19 -8
  74. simtools/model/array_model.py +28 -5
  75. simtools/model/flasher_model.py +106 -0
  76. simtools/model/model_parameter.py +4 -4
  77. simtools/model/model_repository.py +197 -2
  78. simtools/model/telescope_model.py +3 -1
  79. simtools/production_configuration/derive_corsika_limits.py +361 -427
  80. simtools/production_configuration/derive_production_statistics_handler.py +7 -6
  81. simtools/production_configuration/generate_production_grid.py +9 -11
  82. simtools/production_configuration/merge_corsika_limits.py +528 -0
  83. simtools/ray_tracing/mirror_panel_psf.py +1 -0
  84. simtools/ray_tracing/psf_parameter_optimisation.py +792 -0
  85. simtools/ray_tracing/ray_tracing.py +6 -2
  86. simtools/reporting/docs_read_parameters.py +150 -62
  87. simtools/resources/array-element-ids.json +126 -0
  88. simtools/runners/corsika_runner.py +1 -1
  89. simtools/runners/corsika_simtel_runner.py +14 -5
  90. simtools/runners/runner_services.py +10 -5
  91. simtools/runners/simtools_runner.py +267 -0
  92. simtools/schemas/application_workflow.metaschema.yml +101 -68
  93. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +1 -1
  94. simtools/schemas/input/single_pe_spectrum.schema.yml +1 -1
  95. simtools/schemas/metadata.metaschema.yml +577 -3
  96. simtools/schemas/model_parameter.metaschema.yml +6 -6
  97. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +7 -3
  98. simtools/schemas/model_parameters/adjust_gain.schema.yml +1 -1
  99. simtools/schemas/model_parameters/altitude.schema.yml +1 -1
  100. simtools/schemas/model_parameters/array_coordinates.schema.yml +1 -1
  101. simtools/schemas/model_parameters/array_coordinates_UTM.schema.yml +1 -1
  102. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +1 -1
  103. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  104. simtools/schemas/model_parameters/array_layouts.schema.yml +1 -1
  105. simtools/schemas/model_parameters/array_triggers.schema.yml +1 -1
  106. simtools/schemas/model_parameters/array_window.schema.yml +1 -1
  107. simtools/schemas/model_parameters/asum_clipping.schema.yml +1 -1
  108. simtools/schemas/model_parameters/asum_offset.schema.yml +1 -1
  109. simtools/schemas/model_parameters/asum_shaping.schema.yml +1 -1
  110. simtools/schemas/model_parameters/asum_threshold.schema.yml +1 -1
  111. simtools/schemas/model_parameters/atmospheric_profile.schema.yml +42 -1
  112. simtools/schemas/model_parameters/atmospheric_transmission.schema.yml +44 -1
  113. simtools/schemas/model_parameters/axes_offsets.schema.yml +1 -1
  114. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +1 -1
  115. simtools/schemas/model_parameters/camera_body_shape.schema.yml +1 -1
  116. simtools/schemas/model_parameters/camera_config_file.schema.yml +1 -1
  117. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +1 -1
  118. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +1 -1
  119. simtools/schemas/model_parameters/camera_degraded_map.schema.yml +1 -1
  120. simtools/schemas/model_parameters/camera_depth.schema.yml +1 -1
  121. simtools/schemas/model_parameters/camera_filter.schema.yml +11 -1
  122. simtools/schemas/model_parameters/camera_filter_incidence_angle.schema.yml +11 -1
  123. simtools/schemas/model_parameters/camera_pixels.schema.yml +1 -1
  124. simtools/schemas/model_parameters/camera_transmission.schema.yml +1 -1
  125. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  126. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
  127. simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +1 -1
  128. simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +1 -1
  129. simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +1 -1
  130. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +1 -1
  131. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +1 -1
  132. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +1 -1
  133. simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +1 -1
  134. simtools/schemas/model_parameters/corsika_observation_level.schema.yml +1 -1
  135. simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +1 -1
  136. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +3 -3
  137. simtools/schemas/model_parameters/dark_events.schema.yml +1 -1
  138. simtools/schemas/model_parameters/default_trigger.schema.yml +1 -1
  139. simtools/schemas/model_parameters/design_model.schema.yml +1 -1
  140. simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +1 -1
  141. simtools/schemas/model_parameters/disc_bins.schema.yml +1 -1
  142. simtools/schemas/model_parameters/disc_start.schema.yml +1 -1
  143. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +1 -1
  144. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +1 -1
  145. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +1 -1
  146. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +1 -1
  147. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +1 -1
  148. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +1 -1
  149. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +32 -1
  150. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +1 -1
  151. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +1 -1
  152. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +1 -1
  153. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +1 -1
  154. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +1 -1
  155. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +1 -1
  156. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +1 -1
  157. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +1 -1
  158. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +1 -1
  159. simtools/schemas/model_parameters/dish_shape_length.schema.yml +1 -1
  160. simtools/schemas/model_parameters/dsum_clipping.schema.yml +1 -1
  161. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +1 -1
  162. simtools/schemas/model_parameters/dsum_offset.schema.yml +1 -1
  163. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +1 -1
  164. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +1 -1
  165. simtools/schemas/model_parameters/dsum_prescale.schema.yml +1 -1
  166. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +1 -1
  167. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +1 -1
  168. simtools/schemas/model_parameters/dsum_shaping.schema.yml +1 -1
  169. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +1 -1
  170. simtools/schemas/model_parameters/dsum_threshold.schema.yml +2 -2
  171. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +1 -1
  172. simtools/schemas/model_parameters/effective_focal_length.schema.yml +1 -1
  173. simtools/schemas/model_parameters/epsg_code.schema.yml +1 -1
  174. simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +1 -1
  175. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +1 -1
  176. simtools/schemas/model_parameters/fadc_bins.schema.yml +1 -1
  177. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  178. simtools/schemas/model_parameters/fadc_dev_pedestal.schema.yml +1 -1
  179. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +1 -1
  180. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +1 -1
  181. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +1 -1
  182. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  183. simtools/schemas/model_parameters/fadc_lg_dev_pedestal.schema.yml +1 -1
  184. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +1 -1
  185. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +1 -1
  186. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +1 -1
  187. simtools/schemas/model_parameters/fadc_lg_max_sum.schema.yml +1 -1
  188. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +1 -1
  189. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +1 -1
  190. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +1 -1
  191. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +1 -1
  192. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +1 -1
  193. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +1 -1
  194. simtools/schemas/model_parameters/fadc_long_event_threshold.schema.yml +35 -0
  195. simtools/schemas/model_parameters/fadc_long_sum_bins.schema.yml +41 -0
  196. simtools/schemas/model_parameters/fadc_long_sum_offset.schema.yml +38 -0
  197. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +1 -1
  198. simtools/schemas/model_parameters/fadc_max_sum.schema.yml +1 -1
  199. simtools/schemas/model_parameters/fadc_mhz.schema.yml +1 -1
  200. simtools/schemas/model_parameters/fadc_noise.schema.yml +1 -1
  201. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +1 -1
  202. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +13 -1
  203. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +1 -1
  204. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +1 -1
  205. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +1 -1
  206. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +1 -1
  207. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +1 -1
  208. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +1 -1
  209. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +1 -1
  210. simtools/schemas/model_parameters/flatfielding.schema.yml +1 -1
  211. simtools/schemas/model_parameters/focal_length.schema.yml +1 -1
  212. simtools/schemas/model_parameters/focal_surface_parameters.schema.yml +1 -1
  213. simtools/schemas/model_parameters/focal_surface_ref_radius.schema.yml +1 -1
  214. simtools/schemas/model_parameters/focus_offset.schema.yml +1 -1
  215. simtools/schemas/model_parameters/gain_variation.schema.yml +1 -1
  216. simtools/schemas/model_parameters/geomag_horizontal.schema.yml +1 -1
  217. simtools/schemas/model_parameters/geomag_rotation.schema.yml +1 -1
  218. simtools/schemas/model_parameters/geomag_vertical.schema.yml +1 -1
  219. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +1 -1
  220. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  221. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  222. simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
  223. simtools/schemas/model_parameters/laser_external_trigger.schema.yml +1 -1
  224. simtools/schemas/model_parameters/laser_photons.schema.yml +1 -1
  225. simtools/schemas/model_parameters/laser_pulse_exptime.schema.yml +1 -1
  226. simtools/schemas/model_parameters/laser_pulse_offset.schema.yml +1 -1
  227. simtools/schemas/model_parameters/laser_pulse_sigtime.schema.yml +1 -1
  228. simtools/schemas/model_parameters/laser_pulse_twidth.schema.yml +1 -1
  229. simtools/schemas/model_parameters/laser_var_photons.schema.yml +1 -1
  230. simtools/schemas/model_parameters/laser_wavelength.schema.yml +1 -1
  231. simtools/schemas/model_parameters/led_events.schema.yml +1 -1
  232. simtools/schemas/model_parameters/led_photons.schema.yml +1 -1
  233. simtools/schemas/model_parameters/led_pulse_offset.schema.yml +1 -1
  234. simtools/schemas/model_parameters/led_pulse_sigtime.schema.yml +1 -1
  235. simtools/schemas/model_parameters/led_var_photons.schema.yml +1 -1
  236. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +11 -1
  237. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +50 -1
  238. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  239. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  240. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +1 -1
  241. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +1 -1
  242. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +1 -1
  243. simtools/schemas/model_parameters/mirror_class.schema.yml +1 -1
  244. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +1 -1
  245. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +1 -1
  246. simtools/schemas/model_parameters/mirror_list.schema.yml +1 -1
  247. simtools/schemas/model_parameters/mirror_offset.schema.yml +1 -1
  248. simtools/schemas/model_parameters/mirror_panel_2f_measurements.schema.yml +1 -1
  249. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +1 -1
  250. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +11 -1
  251. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +1 -1
  252. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +1 -1
  253. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +1 -1
  254. simtools/schemas/model_parameters/nsb_gain_drop_scale.schema.yml +1 -1
  255. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +1 -1
  256. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +1 -1
  257. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +13 -1
  258. simtools/schemas/model_parameters/nsb_reference_value.schema.yml +1 -1
  259. simtools/schemas/model_parameters/nsb_scaling_factor.schema.yml +1 -1
  260. simtools/schemas/model_parameters/nsb_sky_map.schema.yml +1 -1
  261. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +1 -1
  262. simtools/schemas/model_parameters/num_gains.schema.yml +1 -1
  263. simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +1 -1
  264. simtools/schemas/model_parameters/optics_properties.schema.yml +1 -1
  265. simtools/schemas/model_parameters/parabolic_dish.schema.yml +1 -1
  266. simtools/schemas/model_parameters/pedestal_events.schema.yml +1 -1
  267. simtools/schemas/model_parameters/photon_delay.schema.yml +1 -1
  268. simtools/schemas/model_parameters/photons_per_run.schema.yml +1 -1
  269. simtools/schemas/model_parameters/pixel_cells.schema.yml +1 -1
  270. simtools/schemas/model_parameters/pixels_parallel.schema.yml +1 -1
  271. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +1 -1
  272. simtools/schemas/model_parameters/pm_average_gain.schema.yml +1 -1
  273. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +1 -1
  274. simtools/schemas/model_parameters/pm_gain_index.schema.yml +1 -1
  275. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +20 -1
  276. simtools/schemas/model_parameters/pm_transit_time.schema.yml +1 -1
  277. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +1 -1
  278. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +1 -1
  279. simtools/schemas/model_parameters/primary_mirror_diameter.schema.yml +1 -1
  280. simtools/schemas/model_parameters/primary_mirror_hole_diameter.schema.yml +1 -1
  281. simtools/schemas/model_parameters/primary_mirror_incidence_angle.schema.yml +11 -1
  282. simtools/schemas/model_parameters/primary_mirror_parameters.schema.yml +1 -1
  283. simtools/schemas/model_parameters/primary_mirror_ref_radius.schema.yml +1 -1
  284. simtools/schemas/model_parameters/primary_mirror_segmentation.schema.yml +1 -1
  285. simtools/schemas/model_parameters/qe_variation.schema.yml +1 -1
  286. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +11 -1
  287. simtools/schemas/model_parameters/random_focal_length.schema.yml +1 -1
  288. simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
  289. simtools/schemas/model_parameters/random_mono_probability.schema.yml +1 -1
  290. simtools/schemas/model_parameters/reference_point_altitude.schema.yml +1 -1
  291. simtools/schemas/model_parameters/reference_point_latitude.schema.yml +1 -1
  292. simtools/schemas/model_parameters/reference_point_longitude.schema.yml +1 -1
  293. simtools/schemas/model_parameters/reference_point_utm_east.schema.yml +1 -1
  294. simtools/schemas/model_parameters/reference_point_utm_north.schema.yml +1 -1
  295. simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
  296. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
  297. simtools/schemas/model_parameters/secondary_mirror_baffle.schema.yml +1 -1
  298. simtools/schemas/model_parameters/secondary_mirror_degraded_map.schema.yml +1 -1
  299. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  300. simtools/schemas/model_parameters/secondary_mirror_diameter.schema.yml +1 -1
  301. simtools/schemas/model_parameters/secondary_mirror_hole_diameter.schema.yml +1 -1
  302. simtools/schemas/model_parameters/secondary_mirror_incidence_angle.schema.yml +11 -1
  303. simtools/schemas/model_parameters/secondary_mirror_parameters.schema.yml +1 -1
  304. simtools/schemas/model_parameters/secondary_mirror_ref_radius.schema.yml +1 -1
  305. simtools/schemas/model_parameters/secondary_mirror_reflectivity.schema.yml +11 -1
  306. simtools/schemas/model_parameters/secondary_mirror_segmentation.schema.yml +1 -1
  307. simtools/schemas/model_parameters/secondary_mirror_shadow_diameter.schema.yml +1 -1
  308. simtools/schemas/model_parameters/secondary_mirror_shadow_offset.schema.yml +1 -1
  309. simtools/schemas/model_parameters/stars.schema.yml +1 -1
  310. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  311. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  312. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +1 -1
  313. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +1 -1
  314. simtools/schemas/model_parameters/telescope_random_error.schema.yml +1 -1
  315. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +1 -1
  316. simtools/schemas/model_parameters/telescope_transmission.schema.yml +1 -1
  317. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +1 -1
  318. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +1 -1
  319. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +1 -1
  320. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +1 -1
  321. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +1 -1
  322. simtools/schemas/model_parameters/transit_time_error.schema.yml +1 -1
  323. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +1 -1
  324. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +1 -1
  325. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +1 -1
  326. simtools/schemas/model_parameters/trigger_pixels.schema.yml +1 -1
  327. simtools/schemas/plot_configuration.metaschema.yml +51 -59
  328. simtools/schemas/production_configuration_metrics.schema.yml +12 -2
  329. simtools/schemas/production_tables.schema.yml +2 -2
  330. simtools/simtel/simtel_config_reader.py +2 -2
  331. simtools/simtel/simtel_config_writer.py +16 -4
  332. simtools/simtel/simtel_io_event_histograms.py +746 -0
  333. simtools/simtel/simtel_io_event_reader.py +16 -43
  334. simtools/simtel/simtel_io_event_writer.py +46 -10
  335. simtools/simtel/simtel_io_histogram.py +3 -1
  336. simtools/simtel/simtel_io_histograms.py +7 -3
  337. simtools/simtel/simtel_io_metadata.py +99 -3
  338. simtools/simtel/simtel_table_reader.py +92 -10
  339. simtools/simtel/simulator_array.py +138 -10
  340. simtools/simtel/simulator_camera_efficiency.py +32 -23
  341. simtools/simtel/simulator_light_emission.py +437 -271
  342. simtools/simtel/simulator_ray_tracing.py +1 -1
  343. simtools/simulator.py +105 -147
  344. simtools/testing/configuration.py +24 -26
  345. simtools/testing/helpers.py +2 -2
  346. simtools/testing/log_inspector.py +50 -0
  347. simtools/testing/validate_output.py +87 -37
  348. simtools/utils/general.py +125 -257
  349. simtools/utils/geometry.py +36 -0
  350. simtools/utils/names.py +72 -3
  351. simtools/visualization/legend_handlers.py +180 -264
  352. simtools/visualization/plot_array_layout.py +20 -8
  353. simtools/visualization/plot_pixels.py +1 -2
  354. simtools/visualization/plot_tables.py +202 -27
  355. simtools/visualization/simtel_event_plots.py +816 -0
  356. simtools/visualization/visualize.py +4 -101
  357. gammasimtools-0.17.0.dist-info/RECORD +0 -374
  358. simtools/production_configuration/derive_corsika_limits_grid.py +0 -189
  359. {gammasimtools-0.17.0.dist-info → gammasimtools-0.19.0.dist-info}/WHEEL +0 -0
  360. {gammasimtools-0.17.0.dist-info → gammasimtools-0.19.0.dist-info}/top_level.txt +0 -0
  361. /simtools/{io_operations → io}/hdf5_handler.py +0 -0
  362. /simtools/{io_operations → io}/legacy_data_handler.py +0 -0
  363. /simtools/{schemas → resources}/array_elements.yml +0 -0
@@ -0,0 +1,792 @@
1
+ """
2
+ PSF parameter optimisation and fitting routines for mirror alignment and reflection parameters.
3
+
4
+ This module provides functions for loading PSF data, generating random parameter sets,
5
+ running PSF simulations, calculating RMSD, and finding the best-fit parameters for a given
6
+ telescope model.
7
+
8
+ PSF (Point Spread Function) describes how a point source of light is spread out by the
9
+ optical system, and RMSD (Root Mean Squared Deviation) is used as the optimization metric
10
+ to quantify the difference between measured and simulated PSF curves.
11
+ """
12
+
13
+ import logging
14
+ from collections import OrderedDict
15
+
16
+ import astropy.units as u
17
+ import matplotlib.pyplot as plt
18
+ import numpy as np
19
+ from matplotlib.backends.backend_pdf import PdfPages
20
+
21
+ from simtools.data_model import model_data_writer as writer
22
+ from simtools.model import model_utils
23
+ from simtools.ray_tracing.ray_tracing import RayTracing
24
+ from simtools.utils import general as gen
25
+ from simtools.visualization import visualize
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ # Constants
30
+ RADIUS_CM = "Radius [cm]"
31
+ CUMULATIVE_PSF = "Cumulative PSF"
32
+
33
+ MRRA_RANGE_DEFAULT = 0.004 # Mirror reflection random angle range
34
+ MRF_RANGE_DEFAULT = 0.1 # Mirror reflection fraction range
35
+ MRRA2_RANGE_DEFAULT = 0.03 # Second mirror reflection random angle range
36
+ MAR_RANGE_DEFAULT = 0.005 # Mirror alignment random range
37
+ MAX_OFFSET_DEFAULT = 4.5 # Maximum off-axis angle in degrees
38
+ OFFSET_STEPS_DEFAULT = 0.1 # Step size for off-axis angle sampling
39
+
40
+
41
+ def load_psf_data(data_file):
42
+ """
43
+ Load data from a text file containing cumulative PSF measurements.
44
+
45
+ Parameters
46
+ ----------
47
+ data_file : str
48
+ Name of the data file with the measured cumulative PSF.
49
+ Expected format:
50
+ Column 0: radial distance in mm
51
+ Column 2: cumulative PSF values
52
+
53
+ Returns
54
+ -------
55
+ numpy.ndarray
56
+ Loaded and processed data with radius in cm and normalized cumulative PSF.
57
+ """
58
+ d_type = {"names": (RADIUS_CM, CUMULATIVE_PSF), "formats": ("f8", "f8")}
59
+ data = np.loadtxt(data_file, dtype=d_type, usecols=(0, 2))
60
+ data[RADIUS_CM] *= 0.1 # Convert from mm to cm
61
+ data[CUMULATIVE_PSF] /= np.max(np.abs(data[CUMULATIVE_PSF])) # Normalize to max = 1.0
62
+ return data
63
+
64
+
65
+ def calculate_rmsd(data, sim):
66
+ """Calculate Root Mean Squared Deviation to be used as metric to find the best parameters."""
67
+ return np.sqrt(np.mean((data - sim) ** 2))
68
+
69
+
70
+ def add_parameters(
71
+ all_parameters,
72
+ mirror_reflection,
73
+ mirror_align,
74
+ mirror_reflection_fraction=0.15,
75
+ mirror_reflection_2=0.035,
76
+ ):
77
+ """
78
+ Transform and add parameters to the all_parameters list.
79
+
80
+ Parameters
81
+ ----------
82
+ mirror_reflection : float
83
+ The random angle of mirror reflection.
84
+ mirror_align : float
85
+ The random angle for mirror alignment (both horizontal and vertical).
86
+ mirror_reflection_fraction : float, optional
87
+ The fraction of the mirror reflection. Default is 0.15.
88
+ mirror_reflection_2 : float, optional
89
+ A secondary random angle for mirror reflection. Default is 0.035.
90
+
91
+ Returns
92
+ -------
93
+ None
94
+ Updates the all_parameters list in place.
95
+ """
96
+ pars = {
97
+ "mirror_reflection_random_angle": [
98
+ mirror_reflection,
99
+ mirror_reflection_fraction,
100
+ mirror_reflection_2,
101
+ ],
102
+ "mirror_align_random_horizontal": [mirror_align, 28.0, 0.0, 0.0],
103
+ "mirror_align_random_vertical": [mirror_align, 28.0, 0.0, 0.0],
104
+ }
105
+ all_parameters.append(pars)
106
+
107
+
108
+ def get_previous_values(tel_model):
109
+ """
110
+ Retrieve previous parameter values from the telescope model.
111
+
112
+ Parameters
113
+ ----------
114
+ tel_model : TelescopeModel
115
+ Telescope model object.
116
+
117
+ Returns
118
+ -------
119
+ tuple
120
+ Tuple containing the previous values of mirror_reflection_random_angle (first entry),
121
+ mirror_reflection_fraction, second entry), mirror_reflection_random_angle (third entry),
122
+ and mirror_align_random_horizontal/vertical.
123
+ """
124
+ split_par = tel_model.get_parameter_value("mirror_reflection_random_angle")
125
+ mrra_0, mfr_0, mrra2_0 = split_par[0], split_par[1], split_par[2]
126
+ mar_0 = tel_model.get_parameter_value("mirror_align_random_horizontal")[0]
127
+ logger.debug(
128
+ "Previous parameter values:\n"
129
+ f"MRRA = {mrra_0!s}\n"
130
+ f"MRF = {mfr_0!s}\n"
131
+ f"MRRA2 = {mrra2_0!s}\n"
132
+ f"MAR = {mar_0!s}\n"
133
+ )
134
+ return mrra_0, mfr_0, mrra2_0, mar_0
135
+
136
+
137
+ def generate_random_parameters(
138
+ all_parameters, n_runs, args_dict, mrra_0, mfr_0, mrra2_0, mar_0, tel_model
139
+ ):
140
+ """
141
+ Generate random parameters for tuning.
142
+
143
+ The parameter ranges around the previous values are configurable via module constants.
144
+
145
+ Parameters
146
+ ----------
147
+ all_parameters : list
148
+ List to store all parameter sets.
149
+ n_runs : int
150
+ Number of random parameter combinations to test.
151
+ args_dict : dict
152
+ Dictionary containing parsed command-line arguments.
153
+ mrra_0 : float
154
+ Initial value of mirror_reflection_random_angle.
155
+ mfr_0 : float
156
+ Initial value of mirror_reflection_fraction.
157
+ mrra2_0 : float
158
+ Initial value of the second mirror_reflection_random_angle.
159
+ mar_0 : float
160
+ Initial value of mirror_align_random_horizontal/vertical.
161
+ tel_model : TelescopeModel
162
+ Telescope model object to check if it's a dual mirror telescope.
163
+ """
164
+ if args_dict["fixed"]:
165
+ logger.debug("fixed=True - First entry of mirror_reflection_random_angle is kept fixed.")
166
+
167
+ is_dual_mirror = model_utils.is_two_mirror_telescope(tel_model.name)
168
+ if is_dual_mirror:
169
+ mar_fixed_value = 0.0
170
+ else:
171
+ mar_fixed_value = None
172
+
173
+ for _ in range(n_runs):
174
+ mrra_range = MRRA_RANGE_DEFAULT if not args_dict["fixed"] else 0
175
+ mrf_range = MRF_RANGE_DEFAULT
176
+ mrra2_range = MRRA2_RANGE_DEFAULT
177
+ mar_range = MAR_RANGE_DEFAULT
178
+ rng = np.random.default_rng(seed=args_dict.get("random_seed"))
179
+ mrra = rng.uniform(max(mrra_0 - mrra_range, 0), mrra_0 + mrra_range)
180
+ mrf = rng.uniform(max(mfr_0 - mrf_range, 0), mfr_0 + mrf_range)
181
+ mrra2 = rng.uniform(max(mrra2_0 - mrra2_range, 0), mrra2_0 + mrra2_range)
182
+
183
+ # Set mar to 0 for dual mirror telescopes, otherwise use random value
184
+ if mar_fixed_value is not None:
185
+ mar = mar_fixed_value
186
+ else:
187
+ mar = rng.uniform(max(mar_0 - mar_range, 0), mar_0 + mar_range)
188
+
189
+ add_parameters(all_parameters, mrra, mar, mrf, mrra2)
190
+
191
+
192
+ def _run_ray_tracing_simulation(tel_model, site_model, args_dict, pars):
193
+ """
194
+ Run a ray tracing simulation with the given telescope parameters.
195
+
196
+ Parameters
197
+ ----------
198
+ tel_model : TelescopeModel
199
+ Telescope model object.
200
+ site_model : SiteModel
201
+ Site model object.
202
+ args_dict : dict
203
+ Dictionary containing parsed command-line arguments.
204
+ pars : dict
205
+ Parameter set dictionary.
206
+
207
+ Returns
208
+ -------
209
+ tuple
210
+ (d80, simulated_data) - D80 value and simulated data from ray tracing.
211
+ """
212
+ if pars is not None:
213
+ tel_model.change_multiple_parameters(**pars)
214
+ else:
215
+ raise ValueError("No best parameters found")
216
+
217
+ ray = RayTracing(
218
+ telescope_model=tel_model,
219
+ site_model=site_model,
220
+ simtel_path=args_dict["simtel_path"],
221
+ zenith_angle=args_dict["zenith"] * u.deg,
222
+ source_distance=args_dict["src_distance"] * u.km,
223
+ off_axis_angle=[0.0] * u.deg,
224
+ )
225
+ ray.simulate(test=args_dict.get("test", False), force=True)
226
+ ray.analyze(force=True, use_rx=False)
227
+ im = ray.images()[0]
228
+ d80 = im.get_psf()
229
+
230
+ return d80, im
231
+
232
+
233
+ def _create_psf_simulation_plot(data_to_plot, pars, d80, rmsd, is_best, pdf_pages):
234
+ """
235
+ Create a plot for PSF simulation results.
236
+
237
+ Parameters
238
+ ----------
239
+ data_to_plot : dict
240
+ Data dictionary for plotting.
241
+ pars : dict
242
+ Parameter set dictionary.
243
+ d80 : float
244
+ D80 value.
245
+ rmsd : float
246
+ RMSD value.
247
+ is_best : bool
248
+ Whether this is the best parameter set.
249
+ pdf_pages : PdfPages
250
+ PDF pages object for saving plots.
251
+ """
252
+ fig = visualize.plot_1d(
253
+ data_to_plot,
254
+ plot_difference=True,
255
+ no_markers=True,
256
+ )
257
+ ax = fig.get_axes()[0]
258
+ ax.set_ylim(0, 1.05)
259
+ ax.set_ylabel(CUMULATIVE_PSF)
260
+
261
+ title_prefix = "* " if is_best else ""
262
+ ax.set_title(
263
+ f"{title_prefix}refl_rnd = "
264
+ f"{pars['mirror_reflection_random_angle'][0]:.5f}, "
265
+ f"{pars['mirror_reflection_random_angle'][1]:.5f}, "
266
+ f"{pars['mirror_reflection_random_angle'][2]:.5f}\n"
267
+ f"align_rnd = {pars['mirror_align_random_vertical'][0]:.5f}, "
268
+ f"{pars['mirror_align_random_vertical'][1]:.5f}, "
269
+ f"{pars['mirror_align_random_vertical'][2]:.5f}, "
270
+ f"{pars['mirror_align_random_vertical'][3]:.5f}"
271
+ )
272
+
273
+ d80_color = "red" if is_best else "black"
274
+ d80_weight = "bold" if is_best else "normal"
275
+ d80_text = f"D80 = {d80:.5f} cm"
276
+
277
+ ax.text(
278
+ 0.5,
279
+ 0.3,
280
+ f"{d80_text}\nRMSD = {rmsd:.4f}",
281
+ verticalalignment="center",
282
+ horizontalalignment="left",
283
+ transform=ax.transAxes,
284
+ color=d80_color,
285
+ weight=d80_weight,
286
+ bbox={"boxstyle": "round,pad=0.3", "facecolor": "yellow", "alpha": 0.7}
287
+ if is_best
288
+ else None,
289
+ )
290
+
291
+ if is_best:
292
+ fig.text(
293
+ 0.02,
294
+ 0.02,
295
+ "* Best parameter set (lowest RMSD)",
296
+ fontsize=8,
297
+ style="italic",
298
+ color="red",
299
+ )
300
+
301
+ pdf_pages.savefig(fig, bbox_inches="tight")
302
+ plt.clf()
303
+
304
+
305
+ def run_psf_simulation(
306
+ tel_model,
307
+ site_model,
308
+ args_dict,
309
+ pars,
310
+ data_to_plot,
311
+ radius,
312
+ pdf_pages=None,
313
+ is_best=False,
314
+ return_simulated_data=False,
315
+ ):
316
+ """
317
+ Run the simulation for one set of parameters and return D80, RMSD.
318
+
319
+ Parameters
320
+ ----------
321
+ tel_model : TelescopeModel
322
+ Telescope model object.
323
+ site_model : SiteModel
324
+ Site model object.
325
+ args_dict : dict
326
+ Dictionary containing parsed command-line arguments.
327
+ pars : dict
328
+ Parameter set dictionary.
329
+ data_to_plot : dict
330
+ Data dictionary for plotting.
331
+ radius : array-like
332
+ Radius data.
333
+ pdf_pages : PdfPages, optional
334
+ PDF pages object for plotting. If None, no plotting is done.
335
+ is_best : bool, optional
336
+ Whether this is the best parameter set for highlighting in plots.
337
+ return_simulated_data : bool, optional
338
+ If True, returns simulated data as third element in return tuple.
339
+
340
+ Returns
341
+ -------
342
+ tuple
343
+ (d80, rmsd) if return_simulated_data=False
344
+ (d80, rmsd, simulated_data) if return_simulated_data=True
345
+ """
346
+ d80, im = _run_ray_tracing_simulation(tel_model, site_model, args_dict, pars)
347
+
348
+ if radius is None:
349
+ raise ValueError("Radius data is not available.")
350
+
351
+ simulated_data = im.get_cumulative_data(radius * u.cm)
352
+ rmsd = calculate_rmsd(data_to_plot["measured"][CUMULATIVE_PSF], simulated_data[CUMULATIVE_PSF])
353
+
354
+ # Handle plotting if requested
355
+ if pdf_pages is not None and args_dict.get("plot_all", False):
356
+ data_to_plot["simulated"] = simulated_data
357
+ _create_psf_simulation_plot(data_to_plot, pars, d80, rmsd, is_best, pdf_pages)
358
+ del data_to_plot["simulated"]
359
+
360
+ return (d80, rmsd, simulated_data) if return_simulated_data else (d80, rmsd)
361
+
362
+
363
+ def load_and_process_data(args_dict):
364
+ """
365
+ Load and process data if specified in the command-line arguments.
366
+
367
+ Returns
368
+ -------
369
+ - data_to_plot: OrderedDict containing loaded and processed data.
370
+ - radius: Radius data from loaded data (if available).
371
+ """
372
+ data_to_plot = OrderedDict()
373
+ radius = None
374
+ if args_dict["data"] is not None:
375
+ data_file = gen.find_file(args_dict["data"], args_dict["model_path"])
376
+ data_to_plot["measured"] = load_psf_data(data_file)
377
+ radius = data_to_plot["measured"][RADIUS_CM]
378
+ return data_to_plot, radius
379
+
380
+
381
+ def _create_plot_for_parameters(pars, rmsd, d80, simulated_data, data_to_plot, is_best, pdf_pages):
382
+ """
383
+ Create a single plot for a parameter set.
384
+
385
+ Parameters
386
+ ----------
387
+ pars : dict
388
+ Parameter set dictionary
389
+ rmsd : float
390
+ RMSD value for this parameter set
391
+ d80 : float
392
+ D80 value for this parameter set
393
+ simulated_data : array
394
+ Simulated data for plotting
395
+ data_to_plot : dict
396
+ Data dictionary for plotting
397
+ is_best : bool
398
+ Whether this is the best parameter set
399
+ pdf_pages : PdfPages
400
+ PDF pages object to save the plot
401
+ """
402
+ original_simulated = data_to_plot.get("simulated")
403
+ data_to_plot["simulated"] = simulated_data
404
+
405
+ fig = visualize.plot_1d(
406
+ data_to_plot,
407
+ plot_difference=True,
408
+ no_markers=True,
409
+ )
410
+ ax = fig.get_axes()[0]
411
+ ax.set_ylim(0, 1.05)
412
+ ax.set_ylabel(CUMULATIVE_PSF)
413
+
414
+ title_prefix = "* " if is_best else ""
415
+
416
+ ax.set_title(
417
+ f"{title_prefix}reflection = "
418
+ f"{pars['mirror_reflection_random_angle'][0]:.5f}, "
419
+ f"{pars['mirror_reflection_random_angle'][1]:.5f}, "
420
+ f"{pars['mirror_reflection_random_angle'][2]:.5f}\n"
421
+ f"align_vertical = {pars['mirror_align_random_vertical'][0]:.5f}, "
422
+ f"{pars['mirror_align_random_vertical'][1]:.5f}, "
423
+ f"{pars['mirror_align_random_vertical'][2]:.5f}, "
424
+ f"{pars['mirror_align_random_vertical'][3]:.5f}\n"
425
+ f"align_horizontal = {pars['mirror_align_random_horizontal'][0]:.5f}, "
426
+ f"{pars['mirror_align_random_horizontal'][1]:.5f}, "
427
+ f"{pars['mirror_align_random_horizontal'][2]:.5f}, "
428
+ f"{pars['mirror_align_random_horizontal'][3]:.5f}"
429
+ )
430
+
431
+ d80_color = "red" if is_best else "black"
432
+ d80_weight = "bold" if is_best else "normal"
433
+
434
+ ax.text(
435
+ 0.5,
436
+ 0.3,
437
+ f"D80 = {d80:.5f} cm\nRMSD = {rmsd:.4f}",
438
+ verticalalignment="center",
439
+ horizontalalignment="left",
440
+ transform=ax.transAxes,
441
+ color=d80_color,
442
+ weight=d80_weight,
443
+ bbox={"boxstyle": "round,pad=0.3", "facecolor": "yellow", "alpha": 0.7}
444
+ if is_best
445
+ else None,
446
+ )
447
+
448
+ if is_best:
449
+ fig.text(
450
+ 0.02,
451
+ 0.02,
452
+ "* Best parameter set (lowest RMSD)",
453
+ fontsize=8,
454
+ style="italic",
455
+ color="red",
456
+ )
457
+
458
+ pdf_pages.savefig(fig, bbox_inches="tight")
459
+ plt.clf()
460
+
461
+ if original_simulated is not None:
462
+ data_to_plot["simulated"] = original_simulated
463
+
464
+
465
+ def _create_all_plots(results, best_pars, data_to_plot, pdf_pages):
466
+ """
467
+ Create plots for all parameter sets if requested.
468
+
469
+ Parameters
470
+ ----------
471
+ results : list
472
+ List of (pars, rmsd, d80, simulated_data) tuples
473
+ best_pars : dict
474
+ Best parameter set for highlighting
475
+ data_to_plot : dict
476
+ Data dictionary for plotting
477
+ pdf_pages : PdfPages
478
+ PDF pages object to save plots
479
+ """
480
+ logger.info("Creating plots for all parameter sets...")
481
+
482
+ for i, (pars, rmsd, d80, simulated_data) in enumerate(results):
483
+ is_best = pars is best_pars
484
+ logger.info(f"Creating plot {i + 1}/{len(results)}{' (BEST)' if is_best else ''}")
485
+
486
+ _create_plot_for_parameters(
487
+ pars, rmsd, d80, simulated_data, data_to_plot, is_best, pdf_pages
488
+ )
489
+
490
+
491
+ def find_best_parameters(
492
+ all_parameters, tel_model, site_model, args_dict, data_to_plot, radius, pdf_pages=None
493
+ ):
494
+ """
495
+ Find the best parameters by running simulations for all parameter sets.
496
+
497
+ Loop over all parameter sets, run the simulation, compute RMSD,
498
+ and return the best parameters and their RMSD.
499
+ """
500
+ best_rmsd = float("inf")
501
+ best_pars = None
502
+ best_d80 = None
503
+ results = [] # Store (pars, rmsd, d80, simulated_data)
504
+
505
+ logger.info(f"Running {len(all_parameters)} simulations...")
506
+
507
+ for i, pars in enumerate(all_parameters):
508
+ try:
509
+ logger.info(f"Running simulation {i + 1}/{len(all_parameters)}")
510
+ d80, rmsd, simulated_data = run_psf_simulation(
511
+ tel_model,
512
+ site_model,
513
+ args_dict,
514
+ pars,
515
+ data_to_plot,
516
+ radius,
517
+ return_simulated_data=True,
518
+ pdf_pages=None,
519
+ )
520
+ except (ValueError, RuntimeError) as e:
521
+ logger.warning(f"Simulation failed for parameters {pars}: {e}")
522
+ continue
523
+
524
+ results.append((pars, rmsd, d80, simulated_data))
525
+ if rmsd < best_rmsd:
526
+ best_rmsd = rmsd
527
+ best_pars = pars
528
+ best_d80 = d80
529
+
530
+ logger.info(f"Best RMSD found: {best_rmsd:.5f}")
531
+
532
+ # Create all plots if requested
533
+ if pdf_pages is not None and args_dict.get("plot_all", False) and results:
534
+ _create_all_plots(results, best_pars, data_to_plot, pdf_pages)
535
+
536
+ return best_pars, best_d80, results
537
+
538
+
539
+ def create_d80_vs_offaxis_plot(tel_model, site_model, args_dict, best_pars, output_dir):
540
+ """
541
+ Create D80 vs off-axis angle plot using the best parameters.
542
+
543
+ Parameters
544
+ ----------
545
+ tel_model : TelescopeModel
546
+ Telescope model object.
547
+ site_model : SiteModel
548
+ Site model object.
549
+ args_dict : dict
550
+ Dictionary containing parsed command-line arguments.
551
+ best_pars : dict
552
+ Best parameter set.
553
+ output_dir : Path
554
+ Output directory for saving plots.
555
+ """
556
+ logger.info("Creating D80 vs off-axis angle plot with best parameters...")
557
+
558
+ # Apply best parameters to telescope model
559
+ tel_model.change_multiple_parameters(**best_pars)
560
+
561
+ # Create off-axis angle array
562
+ max_offset = args_dict.get("max_offset", MAX_OFFSET_DEFAULT)
563
+ offset_steps = args_dict.get("offset_steps", OFFSET_STEPS_DEFAULT)
564
+ off_axis_angles = np.linspace(
565
+ 0,
566
+ max_offset,
567
+ int(max_offset / offset_steps) + 1,
568
+ )
569
+
570
+ ray = RayTracing(
571
+ telescope_model=tel_model,
572
+ site_model=site_model,
573
+ simtel_path=args_dict["simtel_path"],
574
+ zenith_angle=args_dict["zenith"] * u.deg,
575
+ source_distance=args_dict["src_distance"] * u.km,
576
+ off_axis_angle=off_axis_angles * u.deg,
577
+ )
578
+
579
+ logger.info(f"Running ray tracing for {len(off_axis_angles)} off-axis angles...")
580
+ ray.simulate(test=args_dict.get("test", False), force=True)
581
+ ray.analyze(force=True)
582
+
583
+ for key in ["d80_cm", "d80_deg"]:
584
+ plt.figure(figsize=(10, 6), tight_layout=True)
585
+
586
+ ray.plot(key, marker="o", linestyle="-", color="blue", linewidth=2, markersize=6)
587
+
588
+ plt.title(
589
+ f"PSF D80 vs Off-axis Angle - {tel_model.name}\n"
590
+ f"Best Parameters: \n"
591
+ f"reflection=[{best_pars['mirror_reflection_random_angle'][0]:.4f},"
592
+ f"{best_pars['mirror_reflection_random_angle'][1]:.4f},"
593
+ f"{best_pars['mirror_reflection_random_angle'][2]:.4f}],\n"
594
+ f"align_horizontal={best_pars['mirror_align_random_horizontal'][0]:.4f}\n"
595
+ f"align_vertical={best_pars['mirror_align_random_vertical'][0]:.4f}\n"
596
+ )
597
+ plt.xlabel("Off-axis Angle (degrees)")
598
+ plt.ylabel("D80 (cm)" if key == "d80_cm" else "D80 (degrees)")
599
+ plt.ylim(bottom=0)
600
+ plt.xticks(rotation=45)
601
+ plt.xlim(0, max_offset)
602
+ plt.grid(True, alpha=0.3)
603
+
604
+ plot_file_name = f"tune_psf_{tel_model.name}_best_params_{key}.pdf"
605
+ plot_file = output_dir.joinpath(plot_file_name)
606
+ visualize.save_figure(plt, plot_file, log_title=f"D80 vs off-axis ({key})")
607
+
608
+ plt.close("all")
609
+
610
+
611
+ def write_tested_parameters_to_file(results, best_pars, best_d80, output_dir, tel_model):
612
+ """
613
+ Write all tested parameters and their metrics to a text file.
614
+
615
+ Parameters
616
+ ----------
617
+ results : list
618
+ List of (pars, rmsd, d80, simulated_data) tuples
619
+ best_pars : dict
620
+ Best parameter set
621
+ best_d80 : float
622
+ Best D80 value
623
+ output_dir : Path
624
+ Output directory path
625
+ tel_model : TelescopeModel
626
+ Telescope model object for filename generation
627
+ """
628
+ param_file = output_dir.joinpath(f"psf_optimization_{tel_model.name}.log")
629
+ with open(param_file, "w", encoding="utf-8") as f:
630
+ f.write("# PSF Parameter Optimization Log\n")
631
+ f.write(f"# Telescope: {tel_model.name}\n")
632
+ f.write(f"# Total parameter sets tested: {len(results)}\n")
633
+ f.write("#" + "=" * 60 + "\n\n")
634
+
635
+ f.write("PARAMETER TESTING RESULTS:\n")
636
+ for i, (pars, rmsd, d80, _) in enumerate(results):
637
+ is_best = pars is best_pars
638
+ status = "BEST" if is_best else "TESTED"
639
+ f.write(f"[{status}] Set {i + 1:03d}: RMSD={rmsd:.5f}, D80={d80:.5f} cm\n")
640
+ for par, value in pars.items():
641
+ f.write(f" {par}: {value}\n")
642
+ f.write("\n")
643
+
644
+ f.write("OPTIMIZATION SUMMARY:\n")
645
+ f.write(f"Best RMSD: {min(result[1] for result in results):.5f}\n")
646
+ f.write(f"Best D80: {best_d80:.5f} cm\n")
647
+ f.write("\nOPTIMIZED PARAMETERS:\n")
648
+ for par, value in best_pars.items():
649
+ f.write(f"{par}: {value}\n")
650
+ return param_file
651
+
652
+
653
+ def _add_units_to_psf_parameters(best_pars):
654
+ """
655
+ Add proper astropy units to PSF parameters based on their schemas.
656
+
657
+ Parameters
658
+ ----------
659
+ best_pars : dict
660
+ Dictionary with PSF parameter names as keys and values as lists
661
+
662
+ Returns
663
+ -------
664
+ dict
665
+ Dictionary with same keys but values converted to astropy quantities with units
666
+ """
667
+ psf_pars_with_units = {}
668
+
669
+ for param_name, param_values in best_pars.items():
670
+ if param_name == "mirror_reflection_random_angle":
671
+ psf_pars_with_units[param_name] = [
672
+ param_values[0] * u.deg,
673
+ param_values[1] * u.dimensionless_unscaled,
674
+ param_values[2] * u.deg,
675
+ ]
676
+ elif param_name in ["mirror_align_random_horizontal", "mirror_align_random_vertical"]:
677
+ psf_pars_with_units[param_name] = [
678
+ param_values[0] * u.deg,
679
+ param_values[1] * u.deg,
680
+ param_values[2] * u.dimensionless_unscaled,
681
+ param_values[3] * u.dimensionless_unscaled,
682
+ ]
683
+ else:
684
+ psf_pars_with_units[param_name] = param_values
685
+
686
+ return psf_pars_with_units
687
+
688
+
689
+ def export_psf_parameters(best_pars, tel_model, parameter_version, output_dir):
690
+ """
691
+ Export PSF parameters as simulation model parameter files.
692
+
693
+ Parameters
694
+ ----------
695
+ best_pars : dict
696
+ Best parameter set
697
+ tel_model : TelescopeModel
698
+ Telescope model object
699
+ parameter_version : str
700
+ Parameter version string
701
+ output_dir : Path
702
+ Output directory path
703
+ """
704
+ try:
705
+ logger.info("Exporting best PSF parameters as simulation model parameter files")
706
+ psf_pars_with_units = _add_units_to_psf_parameters(best_pars)
707
+ parameter_output_path = output_dir / tel_model.name
708
+ for parameter_name, parameter_value in psf_pars_with_units.items():
709
+ writer.ModelDataWriter.dump_model_parameter(
710
+ parameter_name=parameter_name,
711
+ value=parameter_value,
712
+ instrument=tel_model.name,
713
+ parameter_version=parameter_version,
714
+ output_file=f"{parameter_name}-{parameter_version}.json",
715
+ output_path=parameter_output_path,
716
+ use_plain_output_path=True,
717
+ )
718
+ logger.info(f"simulation model parameter files exported to {output_dir}")
719
+ except ImportError as e:
720
+ logger.warning(f"Could not export simulation parameters: {e}")
721
+ except (ValueError, KeyError, OSError) as e:
722
+ logger.error(f"Error exporting simulation parameters: {e}")
723
+
724
+
725
+ def run_psf_optimization_workflow(tel_model, site_model, args_dict, output_dir):
726
+ """
727
+ Run the complete PSF parameter optimization workflow.
728
+
729
+ This function consolidates the main optimization logic to make the application lighter.
730
+
731
+ Parameters
732
+ ----------
733
+ tel_model : TelescopeModel
734
+ Telescope model object
735
+ site_model : SiteModel
736
+ Site model object
737
+ args_dict : dict
738
+ Dictionary containing parsed command-line arguments
739
+ output_dir : Path
740
+ Output directory path
741
+
742
+ Returns
743
+ -------
744
+ None
745
+ All results are saved to files and printed to console
746
+ """
747
+ # Generate parameter sets
748
+ all_parameters = []
749
+ mrra_0, mfr_0, mrra2_0, mar_0 = get_previous_values(tel_model)
750
+
751
+ n_runs = args_dict.get("n_runs")
752
+ generate_random_parameters(
753
+ all_parameters, n_runs, args_dict, mrra_0, mfr_0, mrra2_0, mar_0, tel_model
754
+ )
755
+
756
+ data_to_plot, radius = load_and_process_data(args_dict)
757
+
758
+ # Preparing figure name and PDF pages for plotting
759
+ plot_file_name = "_".join(("tune_psf", tel_model.name + ".pdf"))
760
+ plot_file = output_dir.joinpath(plot_file_name)
761
+ pdf_pages = PdfPages(plot_file)
762
+
763
+ # Find best parameters
764
+ best_pars, best_d80, results = find_best_parameters(
765
+ all_parameters, tel_model, site_model, args_dict, data_to_plot, radius, pdf_pages
766
+ )
767
+
768
+ plt.close()
769
+ pdf_pages.close()
770
+
771
+ # Write all tested parameters and their metrics to a file
772
+ param_file = write_tested_parameters_to_file(
773
+ results, best_pars, best_d80, output_dir, tel_model
774
+ )
775
+ print(f"\nParameter results written to {param_file}")
776
+
777
+ # Automatically create D80 vs off-axis angle plot for best parameters
778
+ create_d80_vs_offaxis_plot(tel_model, site_model, args_dict, best_pars, output_dir)
779
+ print("D80 vs off-axis angle plots created successfully")
780
+
781
+ print("\nBest parameters:")
782
+ for par, value in best_pars.items():
783
+ print(f"{par} = {value}")
784
+
785
+ # Export best parameters as simulation model parameter files (if flag is provided)
786
+ if args_dict.get("write_psf_parameters", False):
787
+ export_psf_parameters(
788
+ best_pars,
789
+ tel_model,
790
+ args_dict.get("parameter_version", "0.0.0"),
791
+ output_dir.parent,
792
+ )