gammasimtools 0.6.1__py3-none-any.whl → 0.8.2__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 (355) hide show
  1. gammasimtools-0.8.2.dist-info/METADATA +173 -0
  2. gammasimtools-0.8.2.dist-info/RECORD +345 -0
  3. {gammasimtools-0.6.1.dist-info → gammasimtools-0.8.2.dist-info}/WHEEL +1 -1
  4. gammasimtools-0.8.2.dist-info/entry_points.txt +31 -0
  5. simtools/_dev_version/__init__.py +9 -0
  6. simtools/_version.py +2 -2
  7. simtools/applications/calculate_trigger_rate.py +210 -0
  8. simtools/applications/convert_all_model_parameters_from_simtel.py +372 -0
  9. simtools/applications/{print_array_elements.py → convert_geo_coordinates_of_array_elements.py} +58 -63
  10. simtools/applications/convert_model_parameter_from_simtel.py +119 -0
  11. simtools/applications/{add_file_to_db.py → db_add_file_to_db.py} +70 -60
  12. simtools/applications/db_add_model_parameters_from_repository_to_db.py +184 -0
  13. simtools/applications/db_add_value_from_json_to_db.py +105 -0
  14. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +180 -0
  15. simtools/applications/db_get_array_layouts_from_db.py +162 -0
  16. simtools/applications/{get_file_from_db.py → db_get_file_from_db.py} +30 -34
  17. simtools/applications/db_get_parameter_from_db.py +131 -0
  18. simtools/applications/db_inspect_databases.py +52 -0
  19. simtools/applications/derive_mirror_rnda.py +39 -255
  20. simtools/applications/derive_psf_parameters.py +441 -0
  21. simtools/applications/generate_array_config.py +82 -0
  22. simtools/applications/generate_corsika_histograms.py +52 -52
  23. simtools/applications/generate_default_metadata.py +5 -8
  24. simtools/applications/generate_regular_arrays.py +117 -0
  25. simtools/applications/generate_simtel_array_histograms.py +97 -56
  26. simtools/applications/plot_array_layout.py +345 -115
  27. simtools/applications/production_generate_simulation_config.py +158 -0
  28. simtools/applications/production_scale_events.py +168 -0
  29. simtools/applications/simulate_light_emission.py +478 -0
  30. simtools/applications/simulate_prod.py +97 -175
  31. simtools/applications/submit_data_from_external.py +9 -12
  32. simtools/applications/submit_model_parameter_from_external.py +122 -0
  33. simtools/applications/validate_camera_efficiency.py +35 -102
  34. simtools/applications/validate_camera_fov.py +20 -19
  35. simtools/applications/{compare_cumulative_psf.py → validate_cumulative_psf.py} +45 -44
  36. simtools/applications/validate_file_using_schema.py +111 -47
  37. simtools/applications/validate_optics.py +17 -22
  38. simtools/camera_efficiency.py +193 -202
  39. simtools/configuration/commandline_parser.py +384 -96
  40. simtools/configuration/configurator.py +55 -71
  41. simtools/constants.py +5 -5
  42. simtools/corsika/corsika_config.py +482 -342
  43. simtools/corsika/corsika_histograms.py +226 -204
  44. simtools/corsika/corsika_histograms_visualize.py +23 -24
  45. simtools/corsika/primary_particle.py +159 -0
  46. simtools/data_model/data_reader.py +25 -20
  47. simtools/data_model/format_checkers.py +52 -0
  48. simtools/data_model/metadata_collector.py +211 -185
  49. simtools/data_model/metadata_model.py +115 -37
  50. simtools/data_model/model_data_writer.py +335 -26
  51. simtools/data_model/validate_data.py +366 -154
  52. simtools/db/db_array_elements.py +130 -0
  53. simtools/db/db_from_repo_handler.py +106 -0
  54. simtools/db/db_handler.py +1246 -0
  55. simtools/io_operations/hdf5_handler.py +3 -1
  56. simtools/io_operations/io_handler.py +32 -57
  57. simtools/job_execution/job_manager.py +82 -69
  58. simtools/layout/array_layout.py +325 -537
  59. simtools/layout/geo_coordinates.py +8 -11
  60. simtools/layout/telescope_position.py +163 -86
  61. simtools/model/array_model.py +312 -259
  62. simtools/model/calibration_model.py +50 -0
  63. simtools/model/camera.py +277 -523
  64. simtools/model/mirrors.py +68 -49
  65. simtools/model/model_parameter.py +602 -0
  66. simtools/model/model_utils.py +11 -39
  67. simtools/model/site_model.py +161 -0
  68. simtools/model/telescope_model.py +143 -633
  69. simtools/production_configuration/calculate_statistical_errors_grid_point.py +454 -0
  70. simtools/production_configuration/event_scaler.py +146 -0
  71. simtools/production_configuration/generate_simulation_config.py +193 -0
  72. simtools/production_configuration/interpolation_handler.py +197 -0
  73. simtools/ray_tracing/__init__.py +0 -0
  74. simtools/ray_tracing/mirror_panel_psf.py +280 -0
  75. simtools/{psf_analysis.py → ray_tracing/psf_analysis.py} +133 -47
  76. simtools/ray_tracing/ray_tracing.py +646 -0
  77. simtools/runners/__init__.py +0 -0
  78. simtools/runners/corsika_runner.py +240 -0
  79. simtools/runners/corsika_simtel_runner.py +225 -0
  80. simtools/runners/runner_services.py +307 -0
  81. simtools/runners/simtel_runner.py +224 -0
  82. simtools/schemas/array_elements.yml +137 -0
  83. simtools/schemas/integration_tests_config.metaschema.yml +93 -0
  84. simtools/schemas/metadata.metaschema.yml +6 -0
  85. simtools/schemas/model_parameter.metaschema.yml +78 -0
  86. simtools/schemas/{data.metaschema.yml → model_parameter_and_data_schema.metaschema.yml} +27 -44
  87. simtools/schemas/model_parameters/adjust_gain.schema.yml +37 -0
  88. simtools/schemas/model_parameters/altitude.schema.yml +37 -0
  89. simtools/schemas/model_parameters/array_coordinates.schema.yml +33 -0
  90. simtools/schemas/model_parameters/array_coordinates_UTM.schema.yml +77 -0
  91. simtools/schemas/model_parameters/array_element_position_ground.schema.yml +39 -0
  92. simtools/schemas/model_parameters/array_element_position_utm.schema.yml +39 -0
  93. simtools/schemas/model_parameters/array_layouts.schema.yml +48 -0
  94. simtools/schemas/model_parameters/array_triggers.schema.yml +93 -0
  95. simtools/schemas/model_parameters/asum_clipping.schema.yml +38 -0
  96. simtools/schemas/model_parameters/asum_offset.schema.yml +35 -0
  97. simtools/schemas/model_parameters/asum_shaping.schema.yml +35 -0
  98. simtools/schemas/model_parameters/asum_threshold.schema.yml +38 -0
  99. simtools/schemas/model_parameters/atmospheric_profile.schema.yml +32 -0
  100. simtools/schemas/model_parameters/atmospheric_transmission.schema.yml +35 -0
  101. simtools/schemas/model_parameters/axes_offsets.schema.yml +53 -0
  102. simtools/schemas/model_parameters/camera_body_diameter.schema.yml +40 -0
  103. simtools/schemas/model_parameters/camera_body_shape.schema.yml +45 -0
  104. simtools/schemas/model_parameters/camera_config_file.schema.yml +40 -0
  105. simtools/schemas/model_parameters/camera_config_rotate.schema.yml +36 -0
  106. simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +43 -0
  107. simtools/schemas/model_parameters/camera_degraded_map.schema.yml +42 -0
  108. simtools/schemas/model_parameters/camera_depth.schema.yml +42 -0
  109. simtools/schemas/model_parameters/camera_filter.schema.yml +45 -0
  110. simtools/schemas/model_parameters/camera_filter_incidence_angle.schema.yml +29 -0
  111. simtools/schemas/model_parameters/camera_pixels.schema.yml +36 -0
  112. simtools/schemas/model_parameters/camera_transmission.schema.yml +41 -0
  113. simtools/schemas/model_parameters/channels_per_chip.schema.yml +36 -0
  114. simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +35 -0
  115. simtools/schemas/model_parameters/corsika_cherenkov_photon_bunch_size.schema.yml +27 -0
  116. simtools/schemas/model_parameters/corsika_cherenkov_photon_wavelength_range.schema.yml +38 -0
  117. simtools/schemas/model_parameters/corsika_first_interaction_height.schema.yml +28 -0
  118. simtools/schemas/model_parameters/corsika_iact_io_buffer.schema.yml +23 -0
  119. simtools/schemas/model_parameters/corsika_iact_max_bunches.schema.yml +27 -0
  120. simtools/schemas/model_parameters/corsika_iact_split_auto.schema.yml +28 -0
  121. simtools/schemas/model_parameters/corsika_longitudinal_shower_development.schema.yml +27 -0
  122. simtools/schemas/model_parameters/corsika_observation_level.schema.yml +38 -0
  123. simtools/schemas/model_parameters/corsika_particle_kinetic_energy_cutoff.schema.yml +52 -0
  124. simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +27 -0
  125. simtools/schemas/model_parameters/dark_events.schema.yml +32 -0
  126. simtools/schemas/model_parameters/default_trigger.schema.yml +35 -0
  127. simtools/schemas/model_parameters/design_model.schema.yml +31 -0
  128. simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +32 -0
  129. simtools/schemas/model_parameters/disc_bins.schema.yml +39 -0
  130. simtools/schemas/model_parameters/disc_start.schema.yml +41 -0
  131. simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +42 -0
  132. simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +41 -0
  133. simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +41 -0
  134. simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +39 -0
  135. simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +40 -0
  136. simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +41 -0
  137. simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +33 -0
  138. simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +42 -0
  139. simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +37 -0
  140. simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +44 -0
  141. simtools/schemas/model_parameters/discriminator_threshold.schema.yml +36 -0
  142. simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +45 -0
  143. simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +40 -0
  144. simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +41 -0
  145. simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +38 -0
  146. simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +38 -0
  147. simtools/schemas/model_parameters/dish_shape_length.schema.yml +41 -0
  148. simtools/schemas/model_parameters/dsum_clipping.schema.yml +38 -0
  149. simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +38 -0
  150. simtools/schemas/model_parameters/dsum_offset.schema.yml +37 -0
  151. simtools/schemas/model_parameters/dsum_pedsub.schema.yml +33 -0
  152. simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +39 -0
  153. simtools/schemas/model_parameters/dsum_prescale.schema.yml +44 -0
  154. simtools/schemas/model_parameters/dsum_presum_max.schema.yml +38 -0
  155. simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +45 -0
  156. simtools/schemas/model_parameters/dsum_shaping.schema.yml +44 -0
  157. simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +32 -0
  158. simtools/schemas/model_parameters/dsum_threshold.schema.yml +43 -0
  159. simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +42 -0
  160. simtools/schemas/model_parameters/effective_focal_length.schema.yml +61 -0
  161. simtools/schemas/model_parameters/epsg_code.schema.yml +37 -0
  162. simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +35 -0
  163. simtools/schemas/model_parameters/fadc_amplitude.schema.yml +46 -0
  164. simtools/schemas/model_parameters/fadc_bins.schema.yml +40 -0
  165. simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +50 -0
  166. simtools/schemas/model_parameters/fadc_dev_pedestal.schema.yml +38 -0
  167. simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +42 -0
  168. simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +49 -0
  169. simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +47 -0
  170. simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +51 -0
  171. simtools/schemas/model_parameters/fadc_lg_dev_pedestal.schema.yml +37 -0
  172. simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +43 -0
  173. simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +49 -0
  174. simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +43 -0
  175. simtools/schemas/model_parameters/fadc_lg_max_sum.schema.yml +39 -0
  176. simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +42 -0
  177. simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +40 -0
  178. simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +50 -0
  179. simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +42 -0
  180. simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +41 -0
  181. simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +42 -0
  182. simtools/schemas/model_parameters/fadc_max_signal.schema.yml +43 -0
  183. simtools/schemas/model_parameters/fadc_max_sum.schema.yml +39 -0
  184. simtools/schemas/model_parameters/fadc_mhz.schema.yml +31 -0
  185. simtools/schemas/model_parameters/fadc_noise.schema.yml +41 -0
  186. simtools/schemas/model_parameters/fadc_pedestal.schema.yml +40 -0
  187. simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +39 -0
  188. simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +50 -0
  189. simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +43 -0
  190. simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +43 -0
  191. simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +42 -0
  192. simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +41 -0
  193. simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +42 -0
  194. simtools/schemas/model_parameters/flatfielding.schema.yml +37 -0
  195. simtools/schemas/model_parameters/focal_length.schema.yml +45 -0
  196. simtools/schemas/model_parameters/focal_surface_parameters.schema.yml +158 -0
  197. simtools/schemas/model_parameters/focal_surface_ref_radius.schema.yml +29 -0
  198. simtools/schemas/model_parameters/focus_offset.schema.yml +66 -0
  199. simtools/schemas/model_parameters/gain_variation.schema.yml +43 -0
  200. simtools/schemas/model_parameters/geomag_horizontal.schema.yml +34 -0
  201. simtools/schemas/model_parameters/geomag_rotation.schema.yml +37 -0
  202. simtools/schemas/model_parameters/geomag_vertical.schema.yml +34 -0
  203. simtools/schemas/model_parameters/hg_lg_variation.schema.yml +36 -0
  204. simtools/schemas/model_parameters/iobuf_maximum.schema.yml +34 -0
  205. simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +34 -0
  206. simtools/schemas/model_parameters/laser_events.schema.yml +36 -0
  207. simtools/schemas/model_parameters/laser_external_trigger.schema.yml +35 -0
  208. simtools/schemas/model_parameters/laser_photons.schema.yml +32 -0
  209. simtools/schemas/model_parameters/laser_pulse_exptime.schema.yml +34 -0
  210. simtools/schemas/model_parameters/laser_pulse_offset.schema.yml +34 -0
  211. simtools/schemas/model_parameters/laser_pulse_sigtime.schema.yml +33 -0
  212. simtools/schemas/model_parameters/laser_pulse_twidth.schema.yml +33 -0
  213. simtools/schemas/model_parameters/laser_var_photons.schema.yml +33 -0
  214. simtools/schemas/model_parameters/laser_wavelength.schema.yml +33 -0
  215. simtools/schemas/model_parameters/led_events.schema.yml +34 -0
  216. simtools/schemas/model_parameters/led_photons.schema.yml +34 -0
  217. simtools/schemas/model_parameters/led_pulse_offset.schema.yml +32 -0
  218. simtools/schemas/model_parameters/led_pulse_sigtime.schema.yml +33 -0
  219. simtools/schemas/model_parameters/led_var_photons.schema.yml +34 -0
  220. simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +41 -0
  221. simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +43 -0
  222. simtools/schemas/model_parameters/min_photoelectrons.schema.yml +35 -0
  223. simtools/schemas/model_parameters/min_photons.schema.yml +32 -0
  224. simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +36 -0
  225. simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +64 -0
  226. simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +64 -0
  227. simtools/schemas/model_parameters/mirror_class.schema.yml +41 -0
  228. simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +51 -0
  229. simtools/schemas/model_parameters/mirror_focal_length.schema.yml +42 -0
  230. simtools/schemas/model_parameters/mirror_list.schema.yml +38 -0
  231. simtools/schemas/model_parameters/mirror_offset.schema.yml +41 -0
  232. simtools/schemas/model_parameters/mirror_panel_2f_measurements.schema.yml +39 -0
  233. simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +61 -0
  234. simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +40 -0
  235. simtools/schemas/model_parameters/multiplicity_offset.schema.yml +46 -0
  236. simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +51 -0
  237. simtools/schemas/model_parameters/nsb_gain_drop_scale.schema.yml +37 -0
  238. simtools/schemas/model_parameters/nsb_offaxis.schema.yml +79 -0
  239. simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +47 -0
  240. simtools/schemas/model_parameters/nsb_reference_spectrum.schema.yml +34 -0
  241. simtools/schemas/model_parameters/nsb_reference_value.schema.yml +33 -0
  242. simtools/schemas/model_parameters/nsb_scaling_factor.schema.yml +35 -0
  243. simtools/schemas/model_parameters/nsb_skymap.schema.yml +39 -0
  244. simtools/schemas/model_parameters/nsb_spectrum.schema.yml +50 -0
  245. simtools/schemas/model_parameters/num_gains.schema.yml +34 -0
  246. simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +33 -0
  247. simtools/schemas/model_parameters/optics_properties.schema.yml +31 -0
  248. simtools/schemas/model_parameters/parabolic_dish.schema.yml +32 -0
  249. simtools/schemas/model_parameters/pedestal_events.schema.yml +32 -0
  250. simtools/schemas/model_parameters/photon_delay.schema.yml +38 -0
  251. simtools/schemas/model_parameters/photons_per_run.schema.yml +33 -0
  252. simtools/schemas/model_parameters/pixel_cells.schema.yml +35 -0
  253. simtools/schemas/model_parameters/pixels_parallel.schema.yml +54 -0
  254. simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +40 -0
  255. simtools/schemas/model_parameters/pm_average_gain.schema.yml +34 -0
  256. simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +40 -0
  257. simtools/schemas/model_parameters/pm_gain_index.schema.yml +36 -0
  258. simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +41 -0
  259. simtools/schemas/model_parameters/pm_transit_time.schema.yml +63 -0
  260. simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +39 -0
  261. simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +42 -0
  262. simtools/schemas/model_parameters/primary_mirror_diameter.schema.yml +33 -0
  263. simtools/schemas/model_parameters/primary_mirror_hole_diameter.schema.yml +33 -0
  264. simtools/schemas/model_parameters/primary_mirror_incidence_angle.schema.yml +29 -0
  265. simtools/schemas/model_parameters/primary_mirror_parameters.schema.yml +168 -0
  266. simtools/schemas/model_parameters/primary_mirror_ref_radius.schema.yml +36 -0
  267. simtools/schemas/model_parameters/primary_mirror_segmentation.schema.yml +34 -0
  268. simtools/schemas/model_parameters/qe_variation.schema.yml +43 -0
  269. simtools/schemas/model_parameters/quantum_efficiency.schema.yml +42 -0
  270. simtools/schemas/model_parameters/random_focal_length.schema.yml +45 -0
  271. simtools/schemas/model_parameters/random_generator.schema.yml +36 -0
  272. simtools/schemas/model_parameters/reference_point_altitude.schema.yml +35 -0
  273. simtools/schemas/model_parameters/reference_point_latitude.schema.yml +36 -0
  274. simtools/schemas/model_parameters/reference_point_longitude.schema.yml +36 -0
  275. simtools/schemas/model_parameters/reference_point_utm_east.schema.yml +34 -0
  276. simtools/schemas/model_parameters/reference_point_utm_north.schema.yml +34 -0
  277. simtools/schemas/model_parameters/sampled_output.schema.yml +31 -0
  278. simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +34 -0
  279. simtools/schemas/model_parameters/secondary_mirror_baffle.schema.yml +79 -0
  280. simtools/schemas/model_parameters/secondary_mirror_degraded_map.schema.yml +42 -0
  281. simtools/schemas/model_parameters/secondary_mirror_degraded_reflection.schema.yml +41 -0
  282. simtools/schemas/model_parameters/secondary_mirror_diameter.schema.yml +33 -0
  283. simtools/schemas/model_parameters/secondary_mirror_hole_diameter.schema.yml +36 -0
  284. simtools/schemas/model_parameters/secondary_mirror_incidence_angle.schema.yml +29 -0
  285. simtools/schemas/model_parameters/secondary_mirror_parameters.schema.yml +168 -0
  286. simtools/schemas/model_parameters/secondary_mirror_ref_radius.schema.yml +36 -0
  287. simtools/schemas/model_parameters/secondary_mirror_reflectivity.schema.yml +35 -0
  288. simtools/schemas/model_parameters/secondary_mirror_segmentation.schema.yml +37 -0
  289. simtools/schemas/model_parameters/secondary_mirror_shadow_diameter.schema.yml +40 -0
  290. simtools/schemas/model_parameters/secondary_mirror_shadow_offset.schema.yml +40 -0
  291. simtools/schemas/model_parameters/store_photoelectrons.schema.yml +41 -0
  292. simtools/schemas/model_parameters/tailcut_scale.schema.yml +40 -0
  293. simtools/schemas/model_parameters/telescope_axis_height.schema.yml +31 -0
  294. simtools/schemas/model_parameters/telescope_random_angle.schema.yml +35 -0
  295. simtools/schemas/model_parameters/telescope_random_error.schema.yml +34 -0
  296. simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +37 -0
  297. simtools/schemas/model_parameters/telescope_transmission.schema.yml +113 -0
  298. simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +41 -0
  299. simtools/schemas/model_parameters/teltrig_min_time.schema.yml +36 -0
  300. simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +36 -0
  301. simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +37 -0
  302. simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +38 -0
  303. simtools/schemas/model_parameters/transit_time_error.schema.yml +45 -0
  304. simtools/schemas/model_parameters/transit_time_jitter.schema.yml +36 -0
  305. simtools/schemas/model_parameters/trigger_current_limit.schema.yml +32 -0
  306. simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +53 -0
  307. simtools/schemas/model_parameters/trigger_pixels.schema.yml +40 -0
  308. simtools/simtel/simtel_config_reader.py +353 -0
  309. simtools/simtel/simtel_config_writer.py +244 -63
  310. simtools/simtel/{simtel_events.py → simtel_io_events.py} +26 -25
  311. simtools/simtel/simtel_io_histogram.py +661 -0
  312. simtools/simtel/simtel_io_histograms.py +569 -0
  313. simtools/simtel/simulator_array.py +145 -0
  314. simtools/simtel/{simtel_runner_camera_efficiency.py → simulator_camera_efficiency.py} +76 -52
  315. simtools/simtel/simulator_light_emission.py +473 -0
  316. simtools/simtel/simulator_ray_tracing.py +262 -0
  317. simtools/simulator.py +220 -446
  318. simtools/testing/__init__.py +0 -0
  319. simtools/testing/assertions.py +151 -0
  320. simtools/testing/configuration.py +226 -0
  321. simtools/testing/helpers.py +42 -0
  322. simtools/testing/validate_output.py +240 -0
  323. simtools/utils/general.py +340 -437
  324. simtools/utils/geometry.py +12 -12
  325. simtools/utils/names.py +257 -644
  326. simtools/utils/value_conversion.py +176 -0
  327. simtools/version.py +3 -1
  328. simtools/visualization/legend_handlers.py +135 -152
  329. simtools/visualization/plot_camera.py +379 -0
  330. simtools/visualization/visualize.py +346 -167
  331. gammasimtools-0.6.1.dist-info/METADATA +0 -180
  332. gammasimtools-0.6.1.dist-info/RECORD +0 -91
  333. gammasimtools-0.6.1.dist-info/entry_points.txt +0 -23
  334. simtools/_dev_version/scm_version.py +0 -10
  335. simtools/applications/db_development_tools/add_new_parameter_to_db.py +0 -81
  336. simtools/applications/db_development_tools/add_unit_to_parameter_in_db.py +0 -59
  337. simtools/applications/db_development_tools/mark_non_optics_parameters_non_applicable.py +0 -102
  338. simtools/applications/get_parameter.py +0 -92
  339. simtools/applications/make_regular_arrays.py +0 -160
  340. simtools/applications/produce_array_config.py +0 -136
  341. simtools/applications/production.py +0 -313
  342. simtools/applications/sim_showers_for_trigger_rates.py +0 -187
  343. simtools/applications/tune_psf.py +0 -334
  344. simtools/corsika/corsika_default_config.py +0 -282
  345. simtools/corsika/corsika_runner.py +0 -450
  346. simtools/corsika_simtel/corsika_simtel_runner.py +0 -197
  347. simtools/db_handler.py +0 -1480
  348. simtools/ray_tracing.py +0 -525
  349. simtools/simtel/simtel_histograms.py +0 -414
  350. simtools/simtel/simtel_runner.py +0 -244
  351. simtools/simtel/simtel_runner_array.py +0 -293
  352. simtools/simtel/simtel_runner_ray_tracing.py +0 -277
  353. {gammasimtools-0.6.1.dist-info → gammasimtools-0.8.2.dist-info}/LICENSE +0 -0
  354. {gammasimtools-0.6.1.dist-info → gammasimtools-0.8.2.dist-info}/top_level.txt +0 -0
  355. /simtools/{corsika_simtel → db}/__init__.py +0 -0
simtools/utils/general.py CHANGED
@@ -1,301 +1,62 @@
1
- """
2
- General functions useful across different parts of the code.
3
- """
1
+ """General functions useful across different parts of the code."""
4
2
 
5
3
  import copy
6
4
  import json
7
5
  import logging
8
6
  import os
9
- import re
10
7
  import tempfile
11
8
  import time
12
9
  import urllib.error
13
10
  import urllib.request
14
- from collections import namedtuple
15
11
  from pathlib import Path
16
12
  from urllib.parse import urlparse
17
13
 
18
- import astropy.units as u
19
- from astropy.io.misc import yaml
14
+ import numpy as np
15
+ import yaml
20
16
 
21
17
  __all__ = [
18
+ "InvalidConfigDataError",
22
19
  "change_dict_keys_case",
23
- "collect_data_from_file_or_dict",
20
+ "collect_data_from_file",
24
21
  "collect_final_lines",
25
22
  "collect_kwargs",
26
- "InvalidConfigData",
27
- "InvalidConfigEntry",
28
- "MissingRequiredConfigEntry",
29
- "UnableToIdentifyConfigEntry",
23
+ "get_log_excerpt",
30
24
  "get_log_level_from_user",
31
25
  "remove_substring_recursively_from_dict",
32
- "separate_args_and_config_data",
33
26
  "set_default_kwargs",
34
- "validate_config_data",
35
- "get_log_excerpt",
36
27
  "sort_arrays",
37
28
  ]
38
29
 
39
30
  _logger = logging.getLogger(__name__)
40
31
 
41
32
 
42
- class UnableToIdentifyConfigEntry(Exception):
43
- """Exception for unable to indentify configuration entry."""
44
-
45
-
46
- class MissingRequiredConfigEntry(Exception):
47
- """Exception for missing required configuration entry."""
48
-
49
-
50
- class InvalidConfigEntry(Exception):
51
- """Exception for invalid configuration entry."""
52
-
53
-
54
- class InvalidConfigData(Exception):
33
+ class InvalidConfigDataError(Exception):
55
34
  """Exception for invalid configuration data."""
56
35
 
57
36
 
58
- def validate_config_data(config_data, parameters, ignore_unidentified=False):
37
+ def join_url_or_path(url_or_path, *args):
59
38
  """
60
- Validate a generic config_data dict by using the info
61
- given by the parameters dict. The entries will be validated
62
- in terms of length, units and names.
63
-
64
- See data/test-data/test_parameters.yml for an example of the structure
65
- of the parameters dict.
66
-
67
- Parameters
68
- ----------
69
- config_data: dict
70
- Input config data.
71
- parameters: dict
72
- Parameter information necessary for validation.
73
- ignore_unidentified: bool
74
- If set to True, unidentified parameters provided in config_data are ignored
75
- and a debug message is printed. Otherwise, an unidentified parameter leads to an error.
76
-
77
- Raises
78
- ------
79
- UnableToIdentifyConfigEntry
80
- When an entry in config_data cannot be identified among the parameters.
81
- MissingRequiredConfigEntry
82
- When a parameter without default value is not given in config_data.
83
- InvalidConfigEntry
84
- When an entry in config_data is invalid (wrong len, wrong unit, ...).
85
-
86
- Returns
87
- -------
88
- namedtuple:
89
- Containing the validated config data entries.
90
- """
91
-
92
- # Dict to be filled and returned
93
- out_data = {}
94
-
95
- if config_data is None:
96
- config_data = {}
97
-
98
- # Collecting all entries given as in config_data.
99
- for key_data, value_data in config_data.items():
100
- is_identified = False
101
- # Searching for the key in the parameters.
102
- for par_name, par_info in parameters.items():
103
- names = par_info.get("names", [])
104
- if key_data != par_name and key_data.lower() not in [n.lower() for n in names]:
105
- continue
106
- # Matched parameter
107
- validated_value = _validate_and_convert_value(par_name, par_info, value_data)
108
- out_data[par_name] = validated_value
109
- is_identified = True
110
-
111
- # Raising error for an unidentified input.
112
- if not is_identified:
113
- msg = f"Entry {key_data} in config_data cannot be identified"
114
- if ignore_unidentified:
115
- _logger.debug(f"{msg}, ignoring.")
116
- else:
117
- _logger.error(f"{msg}, stopping.")
118
- raise UnableToIdentifyConfigEntry(msg)
119
-
120
- # Checking for parameters with default option.
121
- # If it is not given, filling it with the default value.
122
- for par_name, par_info in parameters.items():
123
- if par_name in out_data:
124
- continue
125
- if "default" in par_info.keys() and par_info["default"] is not None:
126
- default_value = par_info["default"]
127
- if not isinstance(default_value, u.Quantity) and "unit" in par_info:
128
- default_value *= par_info["unit"]
129
- validated_value = _validate_and_convert_value(par_name, par_info, default_value)
130
- out_data[par_name] = validated_value
131
- elif "default" in par_info.keys() and par_info["default"] is None:
132
- out_data[par_name] = None
133
- else:
134
- msg = f"Required entry in config_data {par_name} was not given (there may be more)."
135
- _logger.error(msg)
136
- raise MissingRequiredConfigEntry(msg)
137
-
138
- configuration_data = namedtuple("configuration_data", out_data)
139
- return configuration_data(**out_data)
39
+ Join URL or path with additional subdirectories and file.
140
40
 
141
-
142
- def _validate_and_convert_value_without_units(value, value_keys, par_name, par_info):
143
- """
144
- Validate input user parameter for input values without units.
41
+ This is the equivalent to Path.join(), with extended functionality
42
+ working also for URLs.
145
43
 
146
44
  Parameters
147
45
  ----------
148
- value: list
149
- list of user input values.
150
- value_keys: list
151
- list of keys if user input was a dict; otherwise None.
152
- par_name: str
153
- name of parameter.
154
- par_info: dict
155
- dictionary with parameter info.
46
+ url_or_path: str or Path
47
+ URL or path to be extended.
48
+ args: list
49
+ Additional arguments to be added to the URL or path.
156
50
 
157
51
  Returns
158
52
  -------
159
- list, dict
160
- validated and converted input data
53
+ str or Path
54
+ Extended URL or path.
161
55
 
162
56
  """
163
-
164
- _, undefined_length = _check_value_entry_length(value, par_name, par_info)
165
-
166
- # Checking if values have unit and raising error, if so.
167
- if all(isinstance(v, str) for v in value):
168
- # In case values are string, e.g. mirror_numbers = 'all'
169
- # This is needed otherwise the elif condition will break
170
- pass
171
- elif any(u.Quantity(v).unit != u.dimensionless_unscaled for v in value):
172
- msg = f"Config entry {par_name} should not have units"
173
- _logger.error(msg)
174
- raise InvalidConfigEntry(msg)
175
-
176
- if value_keys:
177
- return dict(zip(value_keys, value))
178
- return value if len(value) > 1 or undefined_length else value[0]
179
-
180
-
181
- def _check_value_entry_length(value, par_name, par_info):
182
- """
183
- Validate length of user input parameters
184
-
185
- Parameters
186
- ----------
187
- value: list
188
- list of user input values
189
- par_name: str
190
- name of parameter
191
- par_info: dict
192
- dictionary with parameter info
193
-
194
- Returns
195
- -------
196
- value_length: int
197
- length of input list
198
- undefined_length: bool
199
- state of input list
200
-
201
- """
202
-
203
- # Checking the entry length
204
- value_length = len(value)
205
- _logger.debug(f"Value len of {par_name}: {value_length}")
206
- undefined_length = False
207
- try:
208
- if par_info["len"] is None:
209
- undefined_length = True
210
- elif value_length != par_info["len"]:
211
- msg = f"Config entry with wrong len: {par_name}"
212
- _logger.error(msg)
213
- raise InvalidConfigEntry(msg)
214
- except KeyError:
215
- _logger.error("Missing len entry in par_info")
216
- raise
217
-
218
- return value_length, undefined_length
219
-
220
-
221
- def _validate_and_convert_value_with_units(value, value_keys, par_name, par_info):
222
- """
223
- Validate input user parameter for input values with units.
224
-
225
- Parameters
226
- ----------
227
- value: list
228
- list of user input values
229
- value_keys: list
230
- list of keys if user input was a dict; otherwise None
231
- par_name: str
232
- name of parameter
233
-
234
- Returns
235
- -------
236
- list, dict
237
- validated and converted input data
238
-
239
- """
240
-
241
- value_length, undefined_length = _check_value_entry_length(value, par_name, par_info)
242
-
243
- par_unit = copy_as_list(par_info["unit"])
244
-
245
- if undefined_length and len(par_unit) != 1:
246
- msg = f"Config entry with undefined length should have a single unit: {par_name}"
247
- _logger.error(msg)
248
- raise InvalidConfigEntry(msg)
249
- if len(par_unit) == 1:
250
- par_unit *= value_length
251
-
252
- # Checking units and converting them, if needed.
253
- value_with_units = []
254
- for arg, unit in zip(value, par_unit):
255
- # In case a entry is None, None should be returned.
256
- if unit is None or arg is None:
257
- value_with_units.append(arg)
258
- continue
259
-
260
- # Converting strings to Quantity
261
- if isinstance(arg, str):
262
- arg = u.quantity.Quantity(arg)
263
-
264
- if not isinstance(arg, u.quantity.Quantity):
265
- msg = f"Config entry given without unit: {par_name}"
266
- _logger.error(msg)
267
- raise InvalidConfigEntry(msg)
268
- if not arg.unit.is_equivalent(unit):
269
- msg = f"Config entry given with wrong unit: {par_name}"
270
- _logger.error(msg)
271
- raise InvalidConfigEntry(msg)
272
- value_with_units.append(arg.to(unit).value)
273
-
274
- if value_keys:
275
- return dict(zip(value_keys, value_with_units))
276
-
277
- return (
278
- value_with_units if len(value_with_units) > 1 or undefined_length else value_with_units[0]
279
- )
280
-
281
-
282
- def _validate_and_convert_value(par_name, par_info, value_in):
283
- """
284
- Validate input user parameter and convert it to the right units, if needed.
285
- Returns the validated arguments in a list.
286
- """
287
-
288
- if isinstance(value_in, dict):
289
- value = [d for (k, d) in value_in.items()]
290
- value_keys = [k for (k, d) in value_in.items()]
291
- else:
292
- value = copy_as_list(value_in)
293
- value_keys = None
294
-
295
- if "unit" not in par_info.keys():
296
- return _validate_and_convert_value_without_units(value, value_keys, par_name, par_info)
297
-
298
- return _validate_and_convert_value_with_units(value, value_keys, par_name, par_info)
57
+ if "://" in str(url_or_path):
58
+ return "/".join([url_or_path.rstrip("/"), *args])
59
+ return Path(url_or_path).joinpath(*args)
299
60
 
300
61
 
301
62
  def is_url(url):
@@ -313,7 +74,6 @@ def is_url(url):
313
74
  True if url is a valid URL.
314
75
 
315
76
  """
316
-
317
77
  try:
318
78
  result = urlparse(url)
319
79
  return all([result.scheme, result.netloc])
@@ -324,6 +84,7 @@ def is_url(url):
324
84
  def collect_data_from_http(url):
325
85
  """
326
86
  Download yaml or json file from url and return it contents as dict.
87
+
327
88
  File is downloaded as a temporary file and deleted afterwards.
328
89
 
329
90
  Parameters
@@ -340,76 +101,69 @@ def collect_data_from_http(url):
340
101
  ------
341
102
  TypeError
342
103
  If url is not a valid URL.
343
- urllib.error.HTTPError
104
+ FileNotFoundError
344
105
  If downloading the yaml file fails.
345
106
 
346
107
  """
347
-
348
- _logger.debug(f"Downloaded yaml file from {url}")
349
108
  try:
350
- with tempfile.NamedTemporaryFile() as tmp_file:
109
+ with tempfile.NamedTemporaryFile(mode="w+t") as tmp_file:
351
110
  urllib.request.urlretrieve(url, tmp_file.name)
352
111
  if url.endswith("yml") or url.endswith("yaml"):
353
- data = yaml.load(tmp_file)
112
+ try:
113
+ data = yaml.safe_load(tmp_file)
114
+ except yaml.constructor.ConstructorError:
115
+ data = _load_yaml_using_astropy(tmp_file)
354
116
  elif url.endswith("json"):
355
117
  data = json.load(tmp_file)
118
+ elif url.endswith("list"):
119
+ lines = tmp_file.readlines()
120
+ data = [line.strip() for line in lines]
356
121
  else:
357
122
  msg = f"File extension of {url} not supported (should be json or yaml)"
358
123
  _logger.error(msg)
359
124
  raise TypeError(msg)
360
- except TypeError:
125
+ except TypeError as exc:
361
126
  msg = "Invalid url {url}"
362
127
  _logger.error(msg)
363
- raise
364
- except urllib.error.HTTPError:
365
- msg = f"Failed to download yaml file from {url}"
128
+ raise TypeError(msg) from exc
129
+ except urllib.error.HTTPError as exc:
130
+ msg = f"Failed to download file from {url}"
366
131
  _logger.error(msg)
367
- raise
132
+ raise FileNotFoundError(msg) from exc
368
133
 
134
+ _logger.debug(f"Downloaded file from {url}")
369
135
  return data
370
136
 
371
137
 
372
- def collect_data_from_file_or_dict(file_name, in_dict, allow_empty=False):
138
+ def collect_data_from_file(file_name):
373
139
  """
374
- Collect input data that can be given either as a dict or as a yaml/json file.
140
+ Collect data from file based on its extension.
375
141
 
376
142
  Parameters
377
143
  ----------
378
144
  file_name: str
379
- Name of the yaml/json file.
380
- in_dict: dict
381
- Data as dict.
382
- allow_empty: bool
383
- If True, an error won't be raised in case both yaml and dict are None.
145
+ Name of the yaml/json/ascii file.
384
146
 
385
147
  Returns
386
148
  -------
387
- data: dict
388
- Data as dict.
389
- """
390
-
391
- if file_name is not None:
392
- if in_dict is not None:
393
- _logger.warning("Both in_dict and file_name were given - file_name will be used")
394
- if is_url(str(file_name)):
395
- data = collect_data_from_http(file_name)
396
- elif Path(file_name).suffix.lower() == ".json":
397
- with open(file_name, encoding="utf-8") as file:
398
- data = json.load(file)
399
- else:
400
- with open(file_name, encoding="utf-8") as file:
401
- data = yaml.load(file)
402
- return data
403
- if in_dict is not None:
404
- return dict(in_dict)
149
+ data: dict or list
150
+ Data as dict or list.
151
+ """
152
+ if is_url(file_name):
153
+ return collect_data_from_http(file_name)
405
154
 
406
- msg = "Input has not been provided (neither by file, nor by dict)"
407
- if allow_empty:
408
- _logger.debug(msg)
409
- return None
155
+ with open(file_name, encoding="utf-8") as file:
156
+ if Path(file_name).suffix.lower() == ".json":
157
+ return json.load(file)
158
+
159
+ if Path(file_name).suffix.lower() == ".list":
160
+ lines = file.readlines()
161
+ return [line.strip() for line in lines]
410
162
 
411
- _logger.debug(msg)
412
- raise InvalidConfigData(msg)
163
+ try:
164
+ return yaml.safe_load(file)
165
+ except yaml.constructor.ConstructorError:
166
+ return _load_yaml_using_astropy(file)
413
167
 
414
168
 
415
169
  def collect_kwargs(label, in_kwargs):
@@ -422,6 +176,7 @@ def collect_kwargs(label, in_kwargs):
422
176
  Label to be collected in kwargs.
423
177
  in_kwargs: dict
424
178
  kwargs.
179
+
425
180
  Returns
426
181
  -------
427
182
  dict
@@ -528,7 +283,6 @@ def get_log_level_from_user(log_level):
528
283
  logging.LEVEL
529
284
  The requested logging level to be used as input to logging.setLevel().
530
285
  """
531
-
532
286
  possible_levels = {
533
287
  "info": logging.INFO,
534
288
  "debug": logging.DEBUG,
@@ -537,7 +291,10 @@ def get_log_level_from_user(log_level):
537
291
  "error": logging.ERROR,
538
292
  "critical": logging.CRITICAL,
539
293
  }
540
- log_level_lower = log_level.lower()
294
+ try:
295
+ log_level_lower = log_level.lower()
296
+ except AttributeError:
297
+ log_level_lower = log_level
541
298
  if log_level_lower not in possible_levels:
542
299
  raise ValueError(
543
300
  f"'{log_level}' is not a logging level, "
@@ -569,37 +326,9 @@ def copy_as_list(value):
569
326
  return [value]
570
327
 
571
328
 
572
- def separate_args_and_config_data(expected_args, **kwargs):
573
- """
574
- Separate kwargs into the arguments expected for instancing a class and the dict to be given as
575
- config_data. This function is specific for methods from_kwargs in classes which use the
576
- validate_config_data system.
577
-
578
- Parameters
579
- ----------
580
- expected_args: list of str
581
- List of arguments expected for the class.
582
- **kwargs
583
-
584
- Returns
585
- -------
586
- dict, dict
587
- A dict with the args collected and another one with config_data.
588
- """
589
- args = {}
590
- config_data = {}
591
- for key, value in kwargs.items():
592
- if key in expected_args:
593
- args[key] = value
594
- else:
595
- config_data[key] = value
596
-
597
- return args, config_data
598
-
599
-
600
329
  def program_is_executable(program):
601
330
  """
602
- Checks if program exists and is executable
331
+ Check if program exists and is executable.
603
332
 
604
333
  Follows https://stackoverflow.com/questions/377017/
605
334
 
@@ -619,12 +348,31 @@ def program_is_executable(program):
619
348
  if is_exe(exe_file):
620
349
  return exe_file
621
350
  except KeyError:
622
- _logger.debug("PATH environment variable is not set.")
351
+ _logger.warning("PATH environment variable is not set.")
623
352
  return None
624
353
 
625
354
  return None
626
355
 
627
356
 
357
+ def _search_directory(directory, filename, rec=False):
358
+ if not Path(directory).exists():
359
+ _logger.debug(f"Directory {directory} does not exist")
360
+ return None
361
+
362
+ file = Path(directory).joinpath(filename)
363
+ if file.exists():
364
+ _logger.debug(f"File {filename} found in {directory}")
365
+ return file
366
+
367
+ if rec:
368
+ for subdir in Path(directory).iterdir():
369
+ if subdir.is_dir():
370
+ file = _search_directory(subdir, filename, True)
371
+ if file:
372
+ return file
373
+ return None
374
+
375
+
628
376
  def find_file(name, loc):
629
377
  """
630
378
  Search for files inside of given directories, recursively, and return its full path.
@@ -633,7 +381,7 @@ def find_file(name, loc):
633
381
  ----------
634
382
  name: str
635
383
  File name to be searched for.
636
- loc: Path
384
+ loc: Path or list of Path
637
385
  Location of where to search for the file.
638
386
 
639
387
  Returns
@@ -646,40 +394,19 @@ def find_file(name, loc):
646
394
  FileNotFoundError
647
395
  If the desired file is not found.
648
396
  """
649
-
650
- all_locations = copy.copy(loc)
651
- all_locations = [all_locations] if not isinstance(all_locations, list) else all_locations
652
-
653
- def _search_directory(directory, filename, rec=False):
654
- if not Path(directory).exists():
655
- msg = f"Directory {directory} does not exist"
656
- _logger.debug(msg)
657
- return None
658
-
659
- file = Path(directory).joinpath(filename)
660
- if file.exists():
661
- _logger.debug(f"File {filename} found in {directory}")
662
- return file
663
- if not rec: # Not recursively
664
- return None
665
-
666
- for subdir in Path(directory).iterdir():
667
- if not subdir.is_dir():
668
- continue
669
- file = _search_directory(subdir, filename, True)
670
- if file is not None:
671
- return file
672
- return None
397
+ all_locations = [loc] if not isinstance(loc, list) else loc
673
398
 
674
399
  # Searching file locally
675
400
  file = _search_directory(".", name)
676
- if file is not None:
401
+ if file:
677
402
  return file
403
+
678
404
  # Searching file in given locations
679
- for location_now in all_locations:
680
- file = _search_directory(location_now, name, True)
681
- if file is not None:
405
+ for location in all_locations:
406
+ file = _search_directory(location, name, True)
407
+ if file:
682
408
  return file
409
+
683
410
  msg = f"File {name} could not be found in {all_locations}"
684
411
  _logger.error(msg)
685
412
  raise FileNotFoundError(msg)
@@ -701,7 +428,6 @@ def get_log_excerpt(log_file, n_last_lines=30):
701
428
  str
702
429
  Excerpt from log file with header/footer
703
430
  """
704
-
705
431
  return (
706
432
  "\n\nRuntime error - See below the relevant part of the log/err file.\n\n"
707
433
  f"{log_file}\n"
@@ -712,9 +438,7 @@ def get_log_excerpt(log_file, n_last_lines=30):
712
438
 
713
439
 
714
440
  def get_file_age(file_path):
715
- """
716
- Get the age of a file in seconds since the last modification.
717
- """
441
+ """Get the age of a file in seconds since the last modification."""
718
442
  if not Path(file_path).is_file():
719
443
  raise FileNotFoundError(f"'{file_path}' does not exist or is not a file.")
720
444
 
@@ -722,14 +446,44 @@ def get_file_age(file_path):
722
446
  modification_time = file_stats.st_mtime
723
447
  current_time = time.time()
724
448
 
725
- file_age_minutes = (current_time - modification_time) / 60
726
- return file_age_minutes
449
+ return (current_time - modification_time) / 60
450
+
451
+
452
+ def _process_dict_keys(input_dict, case_func):
453
+ """
454
+ Process dictionary keys recursively.
455
+
456
+ Parameters
457
+ ----------
458
+ input_dict: dict
459
+ Dictionary to be processed.
460
+ case_func: function
461
+ Function to change case of keys (e.g., str.lower, str.upper).
462
+
463
+ Returns
464
+ -------
465
+ dict
466
+ Processed dictionary with keys changed.
467
+ """
468
+ output_dict = {}
469
+ for key, value in input_dict.items():
470
+ processed_key = case_func(key)
471
+ if isinstance(value, dict):
472
+ output_dict[processed_key] = _process_dict_keys(value, case_func)
473
+ elif isinstance(value, list):
474
+ processed_list = [
475
+ _process_dict_keys(item, case_func) if isinstance(item, dict) else item
476
+ for item in value
477
+ ]
478
+ output_dict[processed_key] = processed_list
479
+ else:
480
+ output_dict[processed_key] = value
481
+ return output_dict
727
482
 
728
483
 
729
484
  def change_dict_keys_case(data_dict, lower_case=True):
730
485
  """
731
- Change keys of a dictionary to lower or upper case. Crawls through the dictionary and changes\
732
- all keys. Takes into account list of dictionaries, as e.g. found in the top level data model.
486
+ Change keys of a dictionary to lower or upper case recursively.
733
487
 
734
488
  Parameters
735
489
  ----------
@@ -737,37 +491,28 @@ def change_dict_keys_case(data_dict, lower_case=True):
737
491
  Dictionary to be converted.
738
492
  lower_case: bool
739
493
  Change keys to lower (upper) case if True (False).
494
+
495
+ Returns
496
+ -------
497
+ dict
498
+ Dictionary with keys converted to lower or upper case.
740
499
  """
500
+ # Determine which case function to use
501
+ case_func = str.lower if lower_case else str.upper
741
502
 
742
- _return_dict = {}
743
503
  try:
744
- for key in data_dict.keys():
745
- if lower_case:
746
- _key_changed = key.lower()
747
- else:
748
- _key_changed = key.upper()
749
- if isinstance(data_dict[key], dict):
750
- _return_dict[_key_changed] = change_dict_keys_case(data_dict[key], lower_case)
751
- elif isinstance(data_dict[key], list):
752
- _tmp_list = []
753
- for _list_entry in data_dict[key]:
754
- if isinstance(_list_entry, dict):
755
- _tmp_list.append(change_dict_keys_case(_list_entry, lower_case))
756
- else:
757
- _tmp_list.append(_list_entry)
758
- _return_dict[_key_changed] = _tmp_list
759
- else:
760
- _return_dict[_key_changed] = data_dict[key]
761
- except AttributeError:
504
+ return _process_dict_keys(data_dict, case_func)
505
+ except AttributeError as exc:
762
506
  _logger.error(f"Input is not a proper dictionary: {data_dict}")
763
- raise
764
- return _return_dict
507
+ raise AttributeError from exc
765
508
 
766
509
 
767
510
  def remove_substring_recursively_from_dict(data_dict, substring="\n"):
768
511
  """
769
- Remove substrings from all strings in a dictionary. Recursively crawls through the dictionary
770
- This e.g., allows to remove all newline characters from a dictionary.
512
+ Remove substrings from all strings in a dictionary.
513
+
514
+ Recursively crawls through the dictionary This e.g., allows to remove all newline characters
515
+ from a dictionary.
771
516
 
772
517
  Parameters
773
518
  ----------
@@ -790,9 +535,11 @@ def remove_substring_recursively_from_dict(data_dict, substring="\n"):
790
535
  item.replace(substring, "") if isinstance(item, str) else item for item in value
791
536
  ]
792
537
  modified_items = [
793
- remove_substring_recursively_from_dict(item, substring)
794
- if isinstance(item, dict)
795
- else item
538
+ (
539
+ remove_substring_recursively_from_dict(item, substring)
540
+ if isinstance(item, dict)
541
+ else item
542
+ )
796
543
  for item in modified_items
797
544
  ]
798
545
  data_dict[key] = modified_items
@@ -804,18 +551,18 @@ def remove_substring_recursively_from_dict(data_dict, substring="\n"):
804
551
 
805
552
 
806
553
  def sort_arrays(*args):
807
- """Sort arrays
554
+ """Sort arrays.
808
555
 
809
556
  Parameters
810
557
  ----------
811
558
  *args
812
559
  Arguments to be sorted.
560
+
813
561
  Returns
814
562
  -------
815
563
  list
816
564
  Sorted args.
817
565
  """
818
-
819
566
  if len(args) == 0:
820
567
  return args
821
568
  order_array = copy.copy(args[0])
@@ -826,59 +573,215 @@ def sort_arrays(*args):
826
573
  return new_args
827
574
 
828
575
 
829
- def extract_type_of_value(value) -> str:
576
+ def user_confirm():
577
+ """
578
+ Ask the user to enter y or n (case-insensitive) on the command line.
579
+
580
+ Returns
581
+ -------
582
+ bool:
583
+ True if the answer is Y/y.
584
+
585
+ """
586
+ while True:
587
+ try:
588
+ answer = input("Is this OK? [y/n]").lower()
589
+ return answer == "y"
590
+ except EOFError:
591
+ break
592
+ return False
593
+
594
+
595
+ def _get_value_dtype(value):
596
+ """
597
+ Get the data type of the given value.
598
+
599
+ Parameters
600
+ ----------
601
+ Value to determine the data type.
602
+
603
+ Returns
604
+ -------
605
+ type:
606
+ Data type of the value.
607
+ """
608
+ if isinstance(value, (list | np.ndarray)):
609
+ value = np.array(value)
610
+ return value.dtype
611
+
612
+ return type(value)
613
+
614
+
615
+ def validate_data_type(reference_dtype, value=None, dtype=None, allow_subtypes=True):
616
+ """
617
+ Validate data type of value or type object against a reference data type.
618
+
619
+ Allow to check for exact data type or allow subtypes (e.g. uint is accepted for int).
620
+ Take into account 'file' type as used in the model parameter database.
621
+
622
+ Parameters
623
+ ----------
624
+ reference_dtype: str
625
+ Reference data type to be checked against.
626
+ value: any, optional
627
+ Value to be checked (if dtype is None).
628
+ dtype: type, optional
629
+ Type object to be checked (if value is None).
630
+ allow_subtypes: bool, optional
631
+ If True, allow subtypes to be accepted.
632
+
633
+ Returns
634
+ -------
635
+ bool:
636
+ True if the data type is valid.
830
637
  """
831
- Extract the string representation of the the type of a value.
832
- For example, for a string, it returns 'str' rather than '<class 'str'>'.
833
- Take into account also the case where the value is a numpy type.
638
+ if value is None and dtype is None:
639
+ raise ValueError("Either value or dtype must be given.")
640
+
641
+ if value is not None and dtype is None:
642
+ dtype = _get_value_dtype(value)
643
+
644
+ # Strict comparison
645
+ if not allow_subtypes:
646
+ return np.issubdtype(dtype, reference_dtype)
647
+
648
+ # Allow any sub-type of integer or float for success
649
+ if (np.issubdtype(dtype, np.str_) or np.issubdtype(dtype, "object")) and reference_dtype in (
650
+ "string",
651
+ "str",
652
+ "file",
653
+ ):
654
+ return True
655
+
656
+ if np.issubdtype(dtype, np.bool_) and reference_dtype in ("boolean", "bool"):
657
+ return True
658
+
659
+ if np.issubdtype(dtype, np.integer) and (
660
+ np.issubdtype(reference_dtype, np.integer) or np.issubdtype(reference_dtype, np.floating)
661
+ ):
662
+ return True
663
+
664
+ if np.issubdtype(dtype, np.floating) and np.issubdtype(reference_dtype, np.floating):
665
+ return True
666
+
667
+ return False
668
+
669
+
670
+ def convert_list_to_string(data, comma_separated=False, shorten_list=False, collapse_list=False):
834
671
  """
835
- _type = str(type(value))
836
- if "numpy" in _type:
837
- return re.sub(r"\d+", "", _type.split("'")[1].split(".")[-1])
838
- if "astropy" in _type:
839
- raise NotImplementedError("Astropy types are not supported yet.")
672
+ Convert arrays to string (if required).
673
+
674
+ Parameters
675
+ ----------
676
+ data: object
677
+ Object of data to convert (e.g., double or list)
678
+ comma_separated: bool
679
+ If True, returns elements as a comma-separated string (default is space-separated).
680
+ shorten_list: bool
681
+ If True and all elements in the list are identical, returns a summary string
682
+ like "all: value". This is useful to make the configuration files more readable.
683
+ collapse_list: bool
684
+ If True and all elements in the list are identical, returns a single value
685
+ instead of the entire list.
686
+
687
+ Returns
688
+ -------
689
+ object or str:
690
+ Converted data as string (if required)
691
+
692
+ """
693
+ if data is None or not isinstance(data, list | np.ndarray):
694
+ return data
695
+ if shorten_list and len(data) > 10 and all(np.isclose(item, data[0]) for item in data):
696
+ return f"all: {data[0]}"
697
+ if collapse_list and len(sorted(set(data))) == 1:
698
+ data = [data[0]]
699
+ if comma_separated:
700
+ return ", ".join(str(item) for item in data)
701
+ return " ".join(str(item) for item in data)
702
+
703
+
704
+ def convert_string_to_list(data_string, is_float=True):
705
+ """
706
+ Convert string (as used e.g. in sim_telarray) to list.
707
+
708
+ Allow coma or space separated strings.
840
709
 
841
- _type = _type.split("'")[1]
842
- return _type
710
+ Parameters
711
+ ----------
712
+ data_string: object
713
+ String to be converted
843
714
 
715
+ Returns
716
+ -------
717
+ list, str
718
+ Converted data from string (if required).
719
+ Return data_string if conversion fails.
844
720
 
845
- def get_value_unit_type(value):
846
721
  """
847
- Get the value, unit and type of a value.
848
- The value is stripped of its unit and the unit is returned
849
- in its string form (i.e., to_string()).
850
- The type is returned as a string representation of the type.
851
- For example, for a string, it returns 'str' rather than '<class 'str'>'.
722
+ try:
723
+ if is_float:
724
+ return [float(v) for v in data_string.split()]
725
+ return [int(v) for v in data_string.split()]
726
+ except ValueError:
727
+ pass
728
+ if "," in data_string:
729
+ result = data_string.split(",")
730
+ return [item.strip() for item in result]
731
+ if " " in data_string:
732
+ return data_string.split()
733
+ return data_string
734
+
852
735
 
853
- Note that Quantities are always floats, even if the original value is represented as an int.
736
+ def _load_yaml_using_astropy(file):
737
+ """
738
+ Load a yaml file using astropy's yaml loader.
854
739
 
855
740
  Parameters
856
741
  ----------
857
- value: str, int, float, bool, u.Quantity
858
- Value to be parsed.
742
+ file: file
743
+ File to be loaded.
859
744
 
860
745
  Returns
861
746
  -------
862
- type of value, str, str
863
- Value, unit in string representation (to_string())),
864
- and string representation of the type of the value.
747
+ dict
748
+ Dictionary containing the file content.
749
+ """
750
+ # pylint: disable=import-outside-toplevel
751
+ import astropy.io.misc.yaml as astropy_yaml
752
+
753
+ file.seek(0)
754
+ return astropy_yaml.load(file)
755
+
756
+
757
+ def read_file_encoded_in_utf_or_latin(file_name):
865
758
  """
759
+ Read a file encoded in UTF-8 or Latin-1.
866
760
 
867
- base_value = value
868
- base_unit = None
869
- base_type = ""
870
- if isinstance(value, (str, u.Quantity)):
761
+ Parameters
762
+ ----------
763
+ file_name: str
764
+ Name of the file to be read.
765
+
766
+ Returns
767
+ -------
768
+ list
769
+ List of lines read from the file.
770
+
771
+ Raises
772
+ ------
773
+ UnicodeDecodeError
774
+ If the file cannot be decoded using UTF-8 or Latin-1.
775
+ """
776
+ try:
777
+ with open(file_name, encoding="utf-8") as file:
778
+ lines = file.readlines()
779
+ except UnicodeDecodeError:
780
+ logging.debug("Unable to decode file using UTF-8. Trying Latin-1.")
871
781
  try:
872
- _quantity_value = u.Quantity(value)
873
- base_value = _quantity_value.value
874
- base_type = extract_type_of_value(base_value)
875
- if _quantity_value.unit.to_string() != "":
876
- base_unit = _quantity_value.unit.to_string()
877
- except TypeError:
878
- base_value = value
879
- base_type = "str"
880
- else:
881
- base_value = value
882
- base_type = extract_type_of_value(base_value)
782
+ with open(file_name, encoding="latin-1") as file:
783
+ lines = file.readlines()
784
+ except UnicodeDecodeError as exc:
785
+ raise UnicodeDecodeError("Unable to decode file using UTF-8 or Latin-1.") from exc
883
786
 
884
- return base_value, base_unit, base_type
787
+ return lines