gammasimtools 0.18.0__py3-none-any.whl → 0.20.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 (367) hide show
  1. {gammasimtools-0.18.0.dist-info → gammasimtools-0.20.0.dist-info}/METADATA +24 -69
  2. gammasimtools-0.20.0.dist-info/RECORD +395 -0
  3. {gammasimtools-0.18.0.dist-info → gammasimtools-0.20.0.dist-info}/entry_points.txt +11 -4
  4. {gammasimtools-0.18.0.dist-info → gammasimtools-0.20.0.dist-info}/licenses/LICENSE +1 -1
  5. simtools/_version.py +16 -3
  6. simtools/applications/calculate_incident_angles.py +182 -0
  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 +17 -14
  10. simtools/applications/db_add_value_from_json_to_db.py +8 -10
  11. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +8 -13
  12. simtools/applications/db_generate_compound_indexes.py +65 -0
  13. simtools/applications/db_get_file_from_db.py +12 -24
  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 +17 -11
  17. simtools/applications/derive_psf_parameters.py +59 -309
  18. simtools/applications/derive_trigger_rates.py +91 -0
  19. simtools/applications/docs_produce_array_element_report.py +1 -1
  20. simtools/applications/docs_produce_calibration_reports.py +1 -1
  21. simtools/applications/docs_produce_model_parameter_reports.py +1 -1
  22. simtools/applications/docs_produce_simulation_configuration_report.py +1 -1
  23. simtools/applications/generate_corsika_histograms.py +1 -1
  24. simtools/applications/generate_default_metadata.py +8 -24
  25. simtools/applications/generate_simtel_event_data.py +11 -11
  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 +2 -2
  30. simtools/applications/plot_array_layout.py +3 -3
  31. simtools/applications/plot_simtel_events.py +421 -0
  32. simtools/applications/plot_tabular_data.py +9 -2
  33. simtools/applications/plot_tabular_data_for_model_parameter.py +2 -1
  34. simtools/applications/print_version.py +8 -9
  35. simtools/applications/production_derive_corsika_limits.py +6 -7
  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 +9 -5
  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 +46 -11
  55. simtools/configuration/configurator.py +4 -4
  56. simtools/corsika/corsika_config.py +45 -25
  57. simtools/corsika/corsika_histograms.py +6 -5
  58. simtools/data_model/data_reader.py +2 -3
  59. simtools/data_model/metadata_collector.py +32 -36
  60. simtools/data_model/metadata_model.py +15 -12
  61. simtools/data_model/model_data_writer.py +13 -32
  62. simtools/data_model/schema.py +74 -24
  63. simtools/data_model/validate_data.py +42 -12
  64. simtools/db/db_handler.py +125 -62
  65. simtools/db/db_model_upload.py +14 -19
  66. simtools/dependencies.py +98 -30
  67. simtools/io/ascii_handler.py +279 -0
  68. simtools/{io_operations → io}/io_handler.py +25 -3
  69. simtools/job_execution/htcondor_script_generator.py +15 -4
  70. simtools/layout/array_layout.py +1 -1
  71. simtools/layout/array_layout_utils.py +51 -12
  72. simtools/model/array_model.py +41 -5
  73. simtools/model/flasher_model.py +106 -0
  74. simtools/model/model_parameter.py +4 -4
  75. simtools/model/model_repository.py +197 -2
  76. simtools/model/site_model.py +25 -0
  77. simtools/model/telescope_model.py +3 -1
  78. simtools/production_configuration/derive_corsika_limits.py +336 -427
  79. simtools/production_configuration/derive_production_statistics_handler.py +7 -6
  80. simtools/production_configuration/generate_production_grid.py +9 -11
  81. simtools/production_configuration/merge_corsika_limits.py +528 -0
  82. simtools/ray_tracing/incident_angles.py +706 -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/runners/corsika_runner.py +1 -1
  88. simtools/runners/corsika_simtel_runner.py +14 -5
  89. simtools/runners/runner_services.py +10 -5
  90. simtools/runners/simtools_runner.py +267 -0
  91. simtools/schemas/application_workflow.metaschema.yml +101 -68
  92. simtools/schemas/input/MST_mirror_2f_measurements.schema.yml +1 -1
  93. simtools/schemas/input/single_pe_spectrum.schema.yml +1 -1
  94. simtools/schemas/metadata.metaschema.yml +577 -3
  95. simtools/schemas/model_parameter.metaschema.yml +6 -6
  96. simtools/schemas/model_parameter_and_data_schema.metaschema.yml +4 -4
  97. simtools/schemas/model_parameters/adjust_gain.schema.yml +1 -1
  98. simtools/schemas/model_parameters/altitude.schema.yml +1 -1
  99. simtools/schemas/model_parameters/array_coordinates.schema.yml +1 -1
  100. simtools/schemas/model_parameters/array_coordinates_UTM.schema.yml +1 -1
  101. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +1 -1
  102. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +1 -1
  103. simtools/schemas/model_parameters/array_layouts.schema.yml +1 -1
  104. simtools/schemas/model_parameters/array_triggers.schema.yml +1 -1
  105. simtools/schemas/model_parameters/array_window.schema.yml +1 -1
  106. simtools/schemas/model_parameters/asum_clipping.schema.yml +1 -1
  107. simtools/schemas/model_parameters/asum_offset.schema.yml +1 -1
  108. simtools/schemas/model_parameters/asum_shaping.schema.yml +1 -1
  109. simtools/schemas/model_parameters/asum_threshold.schema.yml +1 -1
  110. simtools/schemas/model_parameters/atmospheric_profile.schema.yml +1 -1
  111. simtools/schemas/model_parameters/atmospheric_transmission.schema.yml +1 -1
  112. simtools/schemas/model_parameters/axes_offsets.schema.yml +1 -1
  113. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +1 -1
  114. simtools/schemas/model_parameters/camera_body_shape.schema.yml +1 -1
  115. simtools/schemas/model_parameters/camera_config_file.schema.yml +1 -1
  116. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +1 -1
  117. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +1 -1
  118. simtools/schemas/model_parameters/camera_degraded_map.schema.yml +1 -1
  119. simtools/schemas/model_parameters/camera_depth.schema.yml +1 -1
  120. simtools/schemas/model_parameters/camera_filter.schema.yml +1 -1
  121. simtools/schemas/model_parameters/camera_filter_incidence_angle.schema.yml +1 -1
  122. simtools/schemas/model_parameters/camera_pixels.schema.yml +1 -1
  123. simtools/schemas/model_parameters/camera_transmission.schema.yml +1 -1
  124. simtools/schemas/model_parameters/channels_per_chip.schema.yml +1 -1
  125. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +1 -1
  126. simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +1 -1
  127. simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +1 -1
  128. simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +1 -1
  129. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +1 -1
  130. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +1 -1
  131. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +1 -1
  132. simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +1 -1
  133. simtools/schemas/model_parameters/corsika_observation_level.schema.yml +1 -1
  134. simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +1 -1
  135. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +3 -3
  136. simtools/schemas/model_parameters/dark_events.schema.yml +1 -1
  137. simtools/schemas/model_parameters/default_trigger.schema.yml +1 -1
  138. simtools/schemas/model_parameters/design_model.schema.yml +1 -1
  139. simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +1 -1
  140. simtools/schemas/model_parameters/disc_bins.schema.yml +1 -1
  141. simtools/schemas/model_parameters/disc_start.schema.yml +1 -1
  142. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +1 -1
  143. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +1 -1
  144. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +1 -1
  145. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +1 -1
  146. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +1 -1
  147. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +1 -1
  148. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +1 -1
  149. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +1 -1
  150. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +1 -1
  151. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +1 -1
  152. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +1 -1
  153. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +1 -1
  154. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +1 -1
  155. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +1 -1
  156. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +1 -1
  157. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +1 -1
  158. simtools/schemas/model_parameters/dish_shape_length.schema.yml +1 -1
  159. simtools/schemas/model_parameters/dsum_clipping.schema.yml +1 -1
  160. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +1 -1
  161. simtools/schemas/model_parameters/dsum_offset.schema.yml +1 -1
  162. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +1 -1
  163. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +1 -1
  164. simtools/schemas/model_parameters/dsum_prescale.schema.yml +1 -1
  165. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +1 -1
  166. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +1 -1
  167. simtools/schemas/model_parameters/dsum_shaping.schema.yml +1 -1
  168. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +1 -1
  169. simtools/schemas/model_parameters/dsum_threshold.schema.yml +2 -2
  170. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +1 -1
  171. simtools/schemas/model_parameters/effective_focal_length.schema.yml +1 -1
  172. simtools/schemas/model_parameters/epsg_code.schema.yml +1 -1
  173. simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +1 -1
  174. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +1 -1
  175. simtools/schemas/model_parameters/fadc_bins.schema.yml +1 -1
  176. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +1 -1
  177. simtools/schemas/model_parameters/fadc_dev_pedestal.schema.yml +1 -1
  178. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +1 -1
  179. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +1 -1
  180. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +1 -1
  181. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +1 -1
  182. simtools/schemas/model_parameters/fadc_lg_dev_pedestal.schema.yml +1 -1
  183. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +1 -1
  184. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +1 -1
  185. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +1 -1
  186. simtools/schemas/model_parameters/fadc_lg_max_sum.schema.yml +1 -1
  187. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +1 -1
  188. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +1 -1
  189. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +1 -1
  190. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +1 -1
  191. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +1 -1
  192. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +1 -1
  193. simtools/schemas/model_parameters/fadc_long_event_threshold.schema.yml +35 -0
  194. simtools/schemas/model_parameters/fadc_long_sum_bins.schema.yml +41 -0
  195. simtools/schemas/model_parameters/fadc_long_sum_offset.schema.yml +38 -0
  196. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +1 -1
  197. simtools/schemas/model_parameters/fadc_max_sum.schema.yml +1 -1
  198. simtools/schemas/model_parameters/fadc_mhz.schema.yml +1 -1
  199. simtools/schemas/model_parameters/fadc_noise.schema.yml +1 -1
  200. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +1 -1
  201. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +1 -1
  202. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +1 -1
  203. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +1 -1
  204. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +1 -1
  205. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +1 -1
  206. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +1 -1
  207. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +1 -1
  208. simtools/schemas/model_parameters/fake_mirror_list.schema.yml +1 -1
  209. simtools/schemas/model_parameters/flatfielding.schema.yml +1 -1
  210. simtools/schemas/model_parameters/focal_length.schema.yml +1 -1
  211. simtools/schemas/model_parameters/focal_surface_parameters.schema.yml +1 -1
  212. simtools/schemas/model_parameters/focal_surface_ref_radius.schema.yml +1 -1
  213. simtools/schemas/model_parameters/focus_offset.schema.yml +1 -1
  214. simtools/schemas/model_parameters/gain_variation.schema.yml +1 -1
  215. simtools/schemas/model_parameters/geomag_horizontal.schema.yml +1 -1
  216. simtools/schemas/model_parameters/geomag_rotation.schema.yml +1 -1
  217. simtools/schemas/model_parameters/geomag_vertical.schema.yml +1 -1
  218. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +1 -1
  219. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +1 -1
  220. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +1 -1
  221. simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
  222. simtools/schemas/model_parameters/laser_external_trigger.schema.yml +1 -1
  223. simtools/schemas/model_parameters/laser_photons.schema.yml +1 -1
  224. simtools/schemas/model_parameters/laser_pulse_exptime.schema.yml +1 -1
  225. simtools/schemas/model_parameters/laser_pulse_offset.schema.yml +1 -1
  226. simtools/schemas/model_parameters/laser_pulse_sigtime.schema.yml +1 -1
  227. simtools/schemas/model_parameters/laser_pulse_twidth.schema.yml +1 -1
  228. simtools/schemas/model_parameters/laser_var_photons.schema.yml +1 -1
  229. simtools/schemas/model_parameters/laser_wavelength.schema.yml +1 -1
  230. simtools/schemas/model_parameters/led_events.schema.yml +1 -1
  231. simtools/schemas/model_parameters/led_photons.schema.yml +1 -1
  232. simtools/schemas/model_parameters/led_pulse_offset.schema.yml +1 -1
  233. simtools/schemas/model_parameters/led_pulse_sigtime.schema.yml +1 -1
  234. simtools/schemas/model_parameters/led_var_photons.schema.yml +1 -1
  235. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +1 -1
  236. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +50 -1
  237. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +1 -1
  238. simtools/schemas/model_parameters/min_photons.schema.yml +1 -1
  239. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +1 -1
  240. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +1 -1
  241. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +1 -1
  242. simtools/schemas/model_parameters/mirror_class.schema.yml +1 -1
  243. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +1 -1
  244. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +1 -1
  245. simtools/schemas/model_parameters/mirror_list.schema.yml +1 -1
  246. simtools/schemas/model_parameters/mirror_offset.schema.yml +1 -1
  247. simtools/schemas/model_parameters/mirror_panel_2f_measurements.schema.yml +1 -1
  248. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +1 -1
  249. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +1 -1
  250. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +1 -1
  251. simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +1 -1
  252. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +1 -1
  253. simtools/schemas/model_parameters/nsb_gain_drop_scale.schema.yml +1 -1
  254. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +1 -1
  255. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +1 -1
  256. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +2 -2
  257. simtools/schemas/model_parameters/nsb_reference_value.schema.yml +1 -1
  258. simtools/schemas/model_parameters/nsb_scaling_factor.schema.yml +1 -1
  259. simtools/schemas/model_parameters/nsb_sky_map.schema.yml +1 -1
  260. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +23 -30
  261. simtools/schemas/model_parameters/num_gains.schema.yml +1 -1
  262. simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +1 -1
  263. simtools/schemas/model_parameters/optics_properties.schema.yml +1 -1
  264. simtools/schemas/model_parameters/parabolic_dish.schema.yml +1 -1
  265. simtools/schemas/model_parameters/pedestal_events.schema.yml +1 -1
  266. simtools/schemas/model_parameters/photon_delay.schema.yml +1 -1
  267. simtools/schemas/model_parameters/photons_per_run.schema.yml +1 -1
  268. simtools/schemas/model_parameters/pixel_cells.schema.yml +1 -1
  269. simtools/schemas/model_parameters/pixels_parallel.schema.yml +1 -1
  270. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +1 -1
  271. simtools/schemas/model_parameters/pm_average_gain.schema.yml +1 -1
  272. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +1 -1
  273. simtools/schemas/model_parameters/pm_gain_index.schema.yml +1 -1
  274. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +1 -1
  275. simtools/schemas/model_parameters/pm_transit_time.schema.yml +1 -1
  276. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +1 -1
  277. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +1 -1
  278. simtools/schemas/model_parameters/primary_mirror_diameter.schema.yml +1 -1
  279. simtools/schemas/model_parameters/primary_mirror_hole_diameter.schema.yml +1 -1
  280. simtools/schemas/model_parameters/primary_mirror_incidence_angle.schema.yml +11 -1
  281. simtools/schemas/model_parameters/primary_mirror_parameters.schema.yml +1 -1
  282. simtools/schemas/model_parameters/primary_mirror_ref_radius.schema.yml +1 -1
  283. simtools/schemas/model_parameters/primary_mirror_segmentation.schema.yml +1 -1
  284. simtools/schemas/model_parameters/qe_variation.schema.yml +1 -1
  285. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +1 -1
  286. simtools/schemas/model_parameters/random_focal_length.schema.yml +1 -1
  287. simtools/schemas/model_parameters/random_generator.schema.yml +1 -1
  288. simtools/schemas/model_parameters/random_mono_probability.schema.yml +1 -1
  289. simtools/schemas/model_parameters/reference_point_altitude.schema.yml +1 -1
  290. simtools/schemas/model_parameters/reference_point_latitude.schema.yml +1 -1
  291. simtools/schemas/model_parameters/reference_point_longitude.schema.yml +1 -1
  292. simtools/schemas/model_parameters/reference_point_utm_east.schema.yml +1 -1
  293. simtools/schemas/model_parameters/reference_point_utm_north.schema.yml +1 -1
  294. simtools/schemas/model_parameters/sampled_output.schema.yml +1 -1
  295. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +1 -1
  296. simtools/schemas/model_parameters/secondary_mirror_baffle.schema.yml +1 -1
  297. simtools/schemas/model_parameters/secondary_mirror_degraded_map.schema.yml +1 -1
  298. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +1 -1
  299. simtools/schemas/model_parameters/secondary_mirror_diameter.schema.yml +1 -1
  300. simtools/schemas/model_parameters/secondary_mirror_hole_diameter.schema.yml +1 -1
  301. simtools/schemas/model_parameters/secondary_mirror_incidence_angle.schema.yml +11 -1
  302. simtools/schemas/model_parameters/secondary_mirror_parameters.schema.yml +1 -1
  303. simtools/schemas/model_parameters/secondary_mirror_ref_radius.schema.yml +1 -1
  304. simtools/schemas/model_parameters/secondary_mirror_reflectivity.schema.yml +11 -1
  305. simtools/schemas/model_parameters/secondary_mirror_segmentation.schema.yml +1 -1
  306. simtools/schemas/model_parameters/secondary_mirror_shadow_diameter.schema.yml +1 -1
  307. simtools/schemas/model_parameters/secondary_mirror_shadow_offset.schema.yml +1 -1
  308. simtools/schemas/model_parameters/stars.schema.yml +2 -2
  309. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +1 -1
  310. simtools/schemas/model_parameters/tailcut_scale.schema.yml +1 -1
  311. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +1 -1
  312. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +1 -1
  313. simtools/schemas/model_parameters/telescope_random_error.schema.yml +1 -1
  314. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +1 -1
  315. simtools/schemas/model_parameters/telescope_transmission.schema.yml +1 -1
  316. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +1 -1
  317. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +1 -1
  318. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +1 -1
  319. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +1 -1
  320. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +1 -1
  321. simtools/schemas/model_parameters/transit_time_error.schema.yml +1 -1
  322. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +1 -1
  323. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +1 -1
  324. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +1 -1
  325. simtools/schemas/model_parameters/trigger_pixels.schema.yml +1 -1
  326. simtools/schemas/plot_configuration.metaschema.yml +5 -2
  327. simtools/schemas/production_configuration_metrics.schema.yml +12 -2
  328. simtools/schemas/production_tables.schema.yml +7 -2
  329. simtools/simtel/simtel_config_reader.py +2 -2
  330. simtools/simtel/simtel_config_writer.py +33 -23
  331. simtools/simtel/simtel_io_event_histograms.py +483 -0
  332. simtools/simtel/simtel_io_event_reader.py +65 -43
  333. simtools/simtel/simtel_io_event_writer.py +40 -20
  334. simtools/simtel/simtel_io_metadata.py +1 -1
  335. simtools/simtel/simtel_table_reader.py +95 -13
  336. simtools/simtel/simulator_array.py +138 -10
  337. simtools/simtel/simulator_camera_efficiency.py +32 -23
  338. simtools/simtel/simulator_light_emission.py +437 -271
  339. simtools/simtel/simulator_ray_tracing.py +1 -1
  340. simtools/simulator.py +105 -147
  341. simtools/telescope_trigger_rates.py +119 -0
  342. simtools/testing/configuration.py +24 -26
  343. simtools/testing/helpers.py +2 -2
  344. simtools/testing/log_inspector.py +52 -0
  345. simtools/testing/validate_output.py +87 -37
  346. simtools/utils/general.py +125 -255
  347. simtools/utils/geometry.py +56 -0
  348. simtools/utils/names.py +1 -1
  349. simtools/visualization/legend_handlers.py +180 -264
  350. simtools/visualization/plot_array_layout.py +20 -8
  351. simtools/visualization/plot_incident_angles.py +431 -0
  352. simtools/visualization/plot_pixels.py +1 -1
  353. simtools/visualization/plot_simtel_event_histograms.py +376 -0
  354. simtools/visualization/plot_simtel_events.py +816 -0
  355. simtools/visualization/plot_tables.py +133 -37
  356. simtools/visualization/visualize.py +1 -100
  357. gammasimtools-0.18.0.dist-info/RECORD +0 -376
  358. simtools/applications/calculate_trigger_rate.py +0 -187
  359. simtools/applications/generate_sim_telarray_histograms.py +0 -196
  360. simtools/production_configuration/derive_corsika_limits_grid.py +0 -232
  361. simtools/simtel/simtel_io_histogram.py +0 -621
  362. simtools/simtel/simtel_io_histograms.py +0 -552
  363. {gammasimtools-0.18.0.dist-info → gammasimtools-0.20.0.dist-info}/WHEEL +0 -0
  364. {gammasimtools-0.18.0.dist-info → gammasimtools-0.20.0.dist-info}/top_level.txt +0 -0
  365. /simtools/{io_operations → io}/hdf5_handler.py +0 -0
  366. /simtools/{io_operations → io}/legacy_data_handler.py +0 -0
  367. /simtools/{io_operations/io_table_handler.py → io/table_handler.py} +0 -0
@@ -12,17 +12,16 @@ query point.
12
12
  """
13
13
 
14
14
  import itertools
15
- import json
16
15
  import logging
17
16
  from pathlib import Path
18
17
 
19
18
  import astropy.units as u
20
19
 
20
+ from simtools.io import ascii_handler
21
21
  from simtools.production_configuration.calculate_statistical_uncertainties_grid_point import (
22
22
  StatisticalUncertaintyEvaluator,
23
23
  )
24
24
  from simtools.production_configuration.interpolation_handler import InterpolationHandler
25
- from simtools.utils.general import collect_data_from_file
26
25
 
27
26
 
28
27
  class ProductionStatisticsHandler:
@@ -48,7 +47,7 @@ class ProductionStatisticsHandler:
48
47
  self.args = args_dict
49
48
  self.logger = logging.getLogger(__name__)
50
49
  self.output_path = output_path
51
- self.metrics = collect_data_from_file(self.args["metrics_file"])
50
+ self.metrics = ascii_handler.collect_data_from_file(self.args["metrics_file"])
52
51
  self.evaluator_instances = []
53
52
  self.interpolation_handler = None
54
53
  self.grid_points_production = self._load_grid_points_production()
@@ -56,7 +55,7 @@ class ProductionStatisticsHandler:
56
55
  def _load_grid_points_production(self):
57
56
  """Load grid points from the JSON file."""
58
57
  grid_points_production_file = self.args["grid_points_production_file"]
59
- return collect_data_from_file(grid_points_production_file)
58
+ return ascii_handler.collect_data_from_file(grid_points_production_file)
60
59
 
61
60
  def initialize_evaluators(self):
62
61
  """Initialize StatisticalUncertaintyEvaluator instances for the given grid point."""
@@ -127,8 +126,10 @@ class ProductionStatisticsHandler:
127
126
  output_filename = self.args["output_file"]
128
127
  self.output_path.mkdir(parents=True, exist_ok=True)
129
128
  output_file_path = self.output_path.joinpath(output_filename)
130
- with open(output_file_path, "w", encoding="utf-8") as f:
131
- json.dump(output_data, f, indent=4)
129
+ ascii_handler.write_data_to_file(
130
+ data=output_data,
131
+ output_file=output_file_path,
132
+ )
132
133
  self.logger.info(f"Output saved to {self.output_path}")
133
134
 
134
135
  def plot_production_statistics_comparison(self):
@@ -9,7 +9,6 @@ Additionally, it allows for converting between Altitude/Azimuth and Right Ascens
9
9
  Declination coordinates. The resulting grid points are saved to a file.
10
10
  """
11
11
 
12
- import json
13
12
  import logging
14
13
 
15
14
  import numpy as np
@@ -19,6 +18,8 @@ from astropy.table import Table
19
18
  from astropy.units import Quantity
20
19
  from scipy.interpolate import griddata
21
20
 
21
+ from simtools.io import ascii_handler
22
+
22
23
 
23
24
  class GridGeneration:
24
25
  """
@@ -331,7 +332,7 @@ class GridGeneration:
331
332
  point["dec"] = radec.dec.deg * u.deg
332
333
  return grid_points
333
334
 
334
- def serialize_grid_points(self, grid_points, output_file=None):
335
+ def serialize_grid_points(self, grid_points, output_file):
335
336
  """Serialize the grid output and save to a file or print to the console."""
336
337
  cleaned_points = []
337
338
 
@@ -346,15 +347,12 @@ class GridGeneration:
346
347
 
347
348
  cleaned_points.append(cleaned_point)
348
349
 
349
- output_data = json.dumps(cleaned_points, indent=4)
350
-
351
- if output_file:
352
- with open(output_file, "w", encoding="utf-8") as f:
353
- f.write(output_data)
354
- self._logger.info(f"Output saved to {output_file}")
355
- else:
356
- self._logger.info(output_data)
357
- return output_data
350
+ ascii_handler.write_data_to_file(
351
+ data=cleaned_points,
352
+ output_file=output_file,
353
+ sort_keys=False,
354
+ )
355
+ self._logger.info(f"Output saved to {output_file}")
358
356
 
359
357
  def serialize_quantity(self, value):
360
358
  """Serialize Quantity."""
@@ -0,0 +1,528 @@
1
+ """Class for merging CORSIKA limit tables and checking grid completeness."""
2
+
3
+ import logging
4
+ from itertools import product
5
+ from pathlib import Path
6
+
7
+ import matplotlib.pyplot as plt
8
+ import numpy as np
9
+ from astropy.table import unique, vstack
10
+
11
+ import simtools.utils.general as gen
12
+ from simtools.data_model import data_reader
13
+ from simtools.data_model.metadata_collector import MetadataCollector
14
+ from simtools.io import io_handler
15
+
16
+ _logger = logging.getLogger(__name__)
17
+
18
+ ZENITH_LABEL = "Zenith [deg]"
19
+
20
+
21
+ class CorsikaMergeLimits:
22
+ """Class for merging CORSIKA limit tables and checking grid completeness."""
23
+
24
+ def __init__(self, output_dir=None):
25
+ """Initialize CorsikaMergeLimits.
26
+
27
+ Parameters
28
+ ----------
29
+ output_dir : Path or str, optional
30
+ Output directory path. If None, will use the default from IOHandler.
31
+ """
32
+ self.output_dir = (
33
+ io_handler.IOHandler().get_output_directory() if output_dir is None else output_dir
34
+ )
35
+
36
+ def read_file_list(self, file_list_path):
37
+ """Read a list of input files from a text file.
38
+
39
+ The text file should contain one file path per line.
40
+ Lines starting with '#' are treated as comments and ignored.
41
+ Empty lines are also ignored.
42
+
43
+ Parameters
44
+ ----------
45
+ file_list_path : Path or str
46
+ Path to the text file containing the list of input files.
47
+
48
+ Returns
49
+ -------
50
+ list
51
+ List of Path objects for the input files.
52
+
53
+ Raises
54
+ ------
55
+ FileNotFoundError
56
+ If the file list does not exist.
57
+ """
58
+ file_list_path = Path(file_list_path).expanduser()
59
+ _logger.info(f"Reading input files from list file: {file_list_path}")
60
+
61
+ if not file_list_path.exists():
62
+ raise FileNotFoundError(f"Input files list not found: {file_list_path}")
63
+
64
+ files = []
65
+ with open(file_list_path, encoding="utf-8") as f:
66
+ for line in f:
67
+ line = line.strip()
68
+ if line and not line.startswith("#"): # Skip empty lines and comments
69
+ file_path = Path(line).expanduser()
70
+ files.append(file_path)
71
+
72
+ _logger.info(f"Found {len(files)} files in list file {file_list_path}")
73
+ return files
74
+
75
+ def _read_and_collect_tables(self, input_files):
76
+ """Read tables from files and collect metadata. Move loss_fraction from meta to column."""
77
+ tables = []
78
+ metadata = {}
79
+ # Track grid points and their associated values to check for inconsistencies
80
+ grid_point_values = {}
81
+ duplicate_points = []
82
+ inconsistent_points = []
83
+
84
+ for file_path in input_files:
85
+ table = data_reader.read_table_from_file(file_path)
86
+ # Move loss_fraction from meta to column
87
+ table["loss_fraction"] = table.meta.pop("loss_fraction")
88
+ tables.append(table)
89
+
90
+ for row in table:
91
+ grid_point = (row["zenith"], row["azimuth"], row["nsb_level"], row["array_name"])
92
+
93
+ if grid_point in grid_point_values:
94
+ duplicate_points.append(grid_point)
95
+
96
+ current_values = {
97
+ col: row[col]
98
+ for col in row.colnames
99
+ if col not in ["zenith", "azimuth", "nsb_level", "array_name"]
100
+ }
101
+ previous_values = grid_point_values[grid_point]
102
+
103
+ keys_to_compare = set(current_values.keys()) & set(previous_values.keys()) - {
104
+ "telescope_ids"
105
+ }
106
+ if any(
107
+ not np.array_equal(current_values[k], previous_values[k])
108
+ for k in keys_to_compare
109
+ ):
110
+ inconsistent_points.append(
111
+ {
112
+ "grid_point": grid_point,
113
+ "file": str(file_path),
114
+ "previous_file": grid_point_values[grid_point]["__file__"],
115
+ }
116
+ )
117
+
118
+ grid_point_values[grid_point] = current_values
119
+ grid_point_values[grid_point]["__file__"] = str(file_path)
120
+ else:
121
+ values = {
122
+ col: row[col]
123
+ for col in row.colnames
124
+ if col not in ["zenith", "azimuth", "nsb_level", "array_name"]
125
+ }
126
+ values["__file__"] = str(file_path)
127
+ grid_point_values[grid_point] = values
128
+
129
+ if not metadata:
130
+ metadata = table.meta
131
+
132
+ return (
133
+ tables,
134
+ metadata,
135
+ set(grid_point_values.keys()),
136
+ duplicate_points,
137
+ inconsistent_points,
138
+ )
139
+
140
+ def _report_and_merge(self, tables, metadata, duplicate_points, inconsistent_points):
141
+ """Report issues and merge tables.
142
+
143
+ Parameters
144
+ ----------
145
+ tables : list
146
+ List of tables to merge.
147
+ metadata : dict
148
+ Metadata to include in the merged table.
149
+ duplicate_points : list
150
+ List of grid points that occur in multiple tables.
151
+ inconsistent_points : list
152
+ List of grid points with inconsistent values across tables.
153
+
154
+ Returns
155
+ -------
156
+ astropy.table.Table
157
+ The merged table.
158
+
159
+ Raises
160
+ ------
161
+ ValueError
162
+ If inconsistent duplicate grid points are found.
163
+ """
164
+ if duplicate_points:
165
+ _logger.warning(f"Found {len(duplicate_points)} duplicate grid points across tables")
166
+ _logger.warning(f"First few duplicates: {duplicate_points[:5]}")
167
+
168
+ if inconsistent_points:
169
+ message = (
170
+ f"Found {len(inconsistent_points)} grid points with inconsistent values in "
171
+ "tables. This likely indicates an issue with the input data. "
172
+ f"First inconsistent point: {inconsistent_points[0]}"
173
+ )
174
+ _logger.error(message)
175
+ raise ValueError(message)
176
+
177
+ _logger.info("All duplicates have consistent values. Last occurrence will be kept.")
178
+
179
+ merged_table = vstack(tables, metadata_conflicts="silent")
180
+ merged_table.meta.update(metadata)
181
+ return merged_table
182
+
183
+ def _remove_duplicates(self, merged_table):
184
+ """Remove duplicate grid points from the merged table, keeping the last occurrence."""
185
+ keys = ["array_name", "zenith", "azimuth", "nsb_level"]
186
+
187
+ reversed_table = merged_table[::-1]
188
+ unique_table = unique(reversed_table, keys=keys, keep="first")
189
+
190
+ return unique_table[::-1]
191
+
192
+ def merge_tables(self, input_files):
193
+ """Merge multiple CORSIKA limit tables into a single table.
194
+
195
+ This function reads and merges CORSIKA limit tables from multiple files,
196
+ handling duplicate grid points by checking for consistency and raising an
197
+ error if inconsistent duplicates are found. It also converts the loss_fraction
198
+ value from metadata to a table column and logs a message if multiple
199
+ loss_fraction values are found.
200
+
201
+ Parameters
202
+ ----------
203
+ input_files : list of Path or str
204
+ List of paths to CORSIKA limit table files to merge.
205
+
206
+ Returns
207
+ -------
208
+ astropy.table.Table
209
+ The merged table with duplicates removed, containing all rows from input files.
210
+ The table will be sorted by array_name, zenith, azimuth, and nsb_level.
211
+
212
+ Raises
213
+ ------
214
+ ValueError
215
+ If inconsistent duplicate grid points are found.
216
+ """
217
+ _logger.info(f"Merging {len(input_files)} CORSIKA limit tables")
218
+
219
+ tables, metadata, grid_points, duplicate_points, inconsistent_points = (
220
+ self._read_and_collect_tables(input_files)
221
+ )
222
+ merged_table = self._report_and_merge(
223
+ tables, metadata, duplicate_points, inconsistent_points
224
+ )
225
+
226
+ if "loss_fraction" in merged_table.colnames:
227
+ unique_loss_fractions = np.unique(merged_table["loss_fraction"])
228
+ if len(unique_loss_fractions) > 1:
229
+ _logger.info(
230
+ f"Found multiple loss_fraction values in merged table: {unique_loss_fractions}."
231
+ " Make sure this is intended."
232
+ )
233
+
234
+ merged_table.sort(["array_name", "zenith", "azimuth", "nsb_level"])
235
+
236
+ if duplicate_points:
237
+ original_count = len(merged_table)
238
+ merged_table = self._remove_duplicates(merged_table)
239
+ _logger.info(f"Removed {original_count - len(merged_table)} duplicate grid points")
240
+
241
+ _logger.info(
242
+ f"Merged table has {len(merged_table)} rows with {len(grid_points)} unique grid points"
243
+ )
244
+ return merged_table
245
+
246
+ def check_grid_completeness(self, merged_table, grid_definition):
247
+ """Check if the grid is complete by verifying all expected combinations exist.
248
+
249
+ This function checks whether all combinations of zenith, azimuth, nsb_level, and array_name
250
+ specified in the grid_definition are present in the merged_table.
251
+
252
+ Parameters
253
+ ----------
254
+ merged_table : astropy.table.Table
255
+ The merged table containing CORSIKA limit data.
256
+ grid_definition : dict
257
+ Dictionary defining the grid dimensions with keys:
258
+ 'zenith': list of zenith angles,
259
+ 'azimuth': list of azimuth angles,
260
+ 'nsb_level': list of NSB levels,
261
+ 'array_name': list of array name
262
+
263
+ Returns
264
+ -------
265
+ tuple
266
+ A tuple containing: is_complete (bool) that is True if all expected combinations
267
+ are found in the table, and info_dict (dict) with detailed information about the
268
+ completeness check including expected points, found points, and missing combinations.
269
+ """
270
+ if not grid_definition:
271
+ _logger.info("No grid definition provided, skipping completeness check.")
272
+ return True, {}
273
+
274
+ expected_combinations = list(
275
+ product(
276
+ grid_definition.get("zenith", []),
277
+ grid_definition.get("azimuth", []),
278
+ grid_definition.get("nsb_level", []),
279
+ grid_definition.get("array_name", []),
280
+ )
281
+ )
282
+ _logger.info(f"Expected {len(expected_combinations)} grid point combinations")
283
+
284
+ found_combinations_set = set(
285
+ zip(
286
+ np.array(merged_table["zenith"].value, dtype=str),
287
+ np.array(merged_table["azimuth"].value, dtype=str),
288
+ np.array(merged_table["nsb_level"], dtype=str),
289
+ np.array(merged_table["array_name"], dtype=str),
290
+ )
291
+ )
292
+ _logger.info(f"Found {len(found_combinations_set)} unique grid points in merged table")
293
+
294
+ expected_combinations_str = {tuple(map(str, combo)) for combo in expected_combinations}
295
+
296
+ missing_combinations_str = expected_combinations_str - found_combinations_set
297
+
298
+ missing_combinations = [
299
+ combo
300
+ for combo in expected_combinations
301
+ if tuple(map(str, combo)) in missing_combinations_str
302
+ ]
303
+
304
+ is_complete = not missing_combinations
305
+ return is_complete, {
306
+ "expected": len(expected_combinations),
307
+ "found": len(found_combinations_set),
308
+ "missing": missing_combinations,
309
+ "found_str": found_combinations_set,
310
+ "expected_str": expected_combinations_str,
311
+ }
312
+
313
+ def _plot_single_grid_coverage(
314
+ self, ax, zeniths, azimuths, nsb, array_name, found_combinations_str
315
+ ):
316
+ """Plot grid coverage for a single NSB and array_name."""
317
+ z_grid = np.zeros((len(zeniths), len(azimuths)))
318
+ for i, zenith in enumerate(zeniths):
319
+ for j, azimuth in enumerate(azimuths):
320
+ point_str = (str(zenith), str(azimuth), str(nsb), str(array_name))
321
+ if point_str in found_combinations_str:
322
+ z_grid[i, j] = 1
323
+
324
+ az_vals = azimuths.value if hasattr(azimuths, "value") else azimuths
325
+ zen_vals = zeniths.value if hasattr(zeniths, "value") else zeniths
326
+ extent = [
327
+ min(az_vals) - 0.5,
328
+ max(az_vals) + 0.5,
329
+ max(zen_vals) + 0.5,
330
+ min(zen_vals) - 0.5,
331
+ ]
332
+ colors = ["red", "green"]
333
+ cmap = plt.matplotlib.colors.ListedColormap(colors)
334
+ im = ax.imshow(z_grid, cmap=cmap, vmin=0, vmax=1, extent=extent)
335
+
336
+ cbar = plt.colorbar(
337
+ im,
338
+ ax=ax,
339
+ ticks=[0, 1],
340
+ label="Coverage",
341
+ shrink=0.25,
342
+ pad=0.02,
343
+ )
344
+ cbar.set_ticklabels(["Missing", "Present"])
345
+ ax.set_title(f"Grid Coverage: NSB={nsb}, Array Name={array_name}")
346
+ ax.set_xlabel("Azimuth [deg]")
347
+ ax.set_ylabel(ZENITH_LABEL)
348
+ ax.set_xticks(az_vals)
349
+ ax.set_yticks(zen_vals)
350
+ ax.grid(which="major", linestyle="-", linewidth="0.5", color="black", alpha=0.3)
351
+
352
+ def plot_grid_coverage(self, merged_table, grid_definition):
353
+ """Generate plots showing grid coverage for each combination of NSB level and array name.
354
+
355
+ Creates a series of heatmap plots showing which grid points (combinations of zenith and
356
+ azimuth angles) are present or missing in the merged table, for each combination of
357
+ NSB level and array name.
358
+
359
+ Parameters
360
+ ----------
361
+ merged_table : astropy.table.Table
362
+ The merged table containing CORSIKA limit data.
363
+ grid_definition : dict
364
+ Dictionary defining the grid dimensions with keys:
365
+ 'zenith': list of zenith angles,
366
+ 'azimuth': list of azimuth angles,
367
+ 'nsb_level': list of NSB levels,
368
+ 'array_name': list of array names
369
+
370
+ Returns
371
+ -------
372
+ list
373
+ List of Path objects pointing to the saved plot files.
374
+ """
375
+ if not grid_definition:
376
+ _logger.info("No grid definition provided, skipping grid coverage plots.")
377
+ return []
378
+
379
+ _logger.info("Generating grid coverage plots")
380
+ output_files = []
381
+
382
+ _, completeness_info = self.check_grid_completeness(merged_table, grid_definition)
383
+ found_combinations_str = completeness_info.get("found_str", set())
384
+
385
+ unique_values = {
386
+ "zeniths": np.array(grid_definition.get("zenith", [])),
387
+ "azimuths": np.array(grid_definition.get("azimuth", [])),
388
+ "nsb_levels": np.array(grid_definition.get("nsb_level", [])),
389
+ "array_names": np.array(grid_definition.get("array_name", [])),
390
+ }
391
+
392
+ for nsb, array_name in product(unique_values["nsb_levels"], unique_values["array_names"]):
393
+ _, ax = plt.subplots(figsize=(10, 8))
394
+ self._plot_single_grid_coverage(
395
+ ax,
396
+ unique_values["zeniths"],
397
+ unique_values["azimuths"],
398
+ nsb,
399
+ array_name,
400
+ found_combinations_str,
401
+ )
402
+ output_file = self.output_dir / f"grid_coverage_{nsb}_{array_name}.png"
403
+ plt.tight_layout()
404
+ plt.savefig(output_file, bbox_inches="tight")
405
+ plt.close()
406
+ output_files.append(output_file)
407
+ return output_files
408
+
409
+ def plot_limits(self, merged_table):
410
+ """Create plots showing the derived limits for each combination of array_name and azimuth.
411
+
412
+ Creates plots showing the lower energy limit, upper radius limit, and viewcone radius
413
+ versus zenith angle for each combination of array_name and azimuth angle. Each plot has
414
+ lines for different NSB levels.
415
+
416
+ Parameters
417
+ ----------
418
+ merged_table : astropy.table.Table
419
+ The merged table containing CORSIKA limit data.
420
+
421
+ Returns
422
+ -------
423
+ list
424
+ List of Path objects pointing to the saved plot files.
425
+ """
426
+ _logger.info("Generating limit plots")
427
+ output_files = []
428
+
429
+ grouped_by_layout_az = merged_table.group_by(["array_name", "azimuth"])
430
+
431
+ for group in grouped_by_layout_az.groups:
432
+ array_name = group["array_name"][0]
433
+ azimuth = group["azimuth"][0]
434
+ azimuth_value = azimuth.value if hasattr(azimuth, "value") else azimuth
435
+
436
+ fig, axes = plt.subplots(1, 3, figsize=(18, 6))
437
+ legend_handles, legend_labels = [], []
438
+
439
+ grouped_by_nsb = group.group_by("nsb_level")
440
+ colors = plt.get_cmap("viridis")(np.linspace(0, 1, len(grouped_by_nsb.groups)))
441
+
442
+ for i, nsb_group in enumerate(grouped_by_nsb.groups):
443
+ nsb_level = nsb_group["nsb_level"][0]
444
+ plot_columns = [
445
+ "zenith",
446
+ "lower_energy_limit",
447
+ "upper_radius_limit",
448
+ "viewcone_radius",
449
+ ]
450
+ agg_data = nsb_group[plot_columns].group_by("zenith").groups.aggregate(np.mean)
451
+ agg_data.sort("zenith")
452
+ zeniths = agg_data["zenith"].value
453
+
454
+ (line,) = axes[0].plot(
455
+ zeniths, agg_data["lower_energy_limit"], "o-", color=colors[i]
456
+ )
457
+ axes[1].plot(zeniths, agg_data["upper_radius_limit"], "o-", color=colors[i])
458
+ axes[2].plot(zeniths, agg_data["viewcone_radius"], "o-", color=colors[i])
459
+ legend_handles.append(line)
460
+ legend_labels.append(f"NSB={nsb_level}")
461
+
462
+ axes[0].set_title("Lower Energy Limit vs Zenith")
463
+ axes[0].set_xlabel(ZENITH_LABEL)
464
+ axes[0].set_ylabel("Lower Energy Limit [TeV]")
465
+ axes[0].grid(True)
466
+ axes[1].set_title("Upper Radius Limit vs Zenith")
467
+ axes[1].set_xlabel(ZENITH_LABEL)
468
+ axes[1].set_ylabel("Upper Radius Limit [m]")
469
+ axes[1].grid(True)
470
+ axes[2].set_title("Viewcone Radius vs Zenith")
471
+ axes[2].set_xlabel(ZENITH_LABEL)
472
+ axes[2].set_ylabel("Viewcone Radius [deg]")
473
+ axes[2].grid(True)
474
+
475
+ fig.legend(legend_handles, legend_labels, loc="lower center", ncol=len(legend_labels))
476
+ plt.suptitle(f"CORSIKA Limits: Array Name={array_name}, Azimuth={azimuth_value} deg")
477
+ plt.tight_layout()
478
+ plt.subplots_adjust(bottom=0.15)
479
+
480
+ output_file = self.output_dir / f"limits_{array_name}_azimuth{azimuth_value}.png"
481
+ plt.savefig(output_file)
482
+ plt.close(fig)
483
+ output_files.append(output_file)
484
+ return output_files
485
+
486
+ def write_merged_table(self, merged_table, output_file, input_files, grid_completeness):
487
+ """Write the merged table to file and save metadata.
488
+
489
+ Writes the merged table to the specified output file in ECSV format and
490
+ saves relevant metadata about the merge process, including input files,
491
+ grid completeness statistics, and row count.
492
+
493
+ Parameters
494
+ ----------
495
+ merged_table : astropy.table.Table
496
+ The merged table to write to file.
497
+ output_file : Path or str
498
+ Path where the merged table will be written.
499
+ input_files : list of Path or str
500
+ List of input files used to create the merged table.
501
+ grid_completeness : dict
502
+ Dictionary with grid completeness information from check_grid_completeness.
503
+
504
+ Returns
505
+ -------
506
+ Path or str
507
+ The path to the written file (same as output_file).
508
+ """
509
+ merged_table.meta.update(
510
+ {
511
+ "created_by": "simtools-production-merge-corsika-limits",
512
+ "creation_date": gen.now_date_time_in_isoformat(),
513
+ "input_files_count": len(input_files),
514
+ }
515
+ )
516
+ merged_table.write(output_file, format="ascii.ecsv", overwrite=True)
517
+ _logger.info(f"Merged table written to {output_file}")
518
+
519
+ metadata = {
520
+ "input_files": [str(f) for f in input_files],
521
+ "grid_completeness": grid_completeness.get("is_complete", False),
522
+ "missing_points": len(grid_completeness.get("missing", [])),
523
+ "total_expected_points": grid_completeness.get("expected", 0),
524
+ "found_points": grid_completeness.get("found", 0),
525
+ "row_count": len(merged_table),
526
+ }
527
+ MetadataCollector.dump(metadata, output_file)
528
+ return output_file