rubin-sim 2.6.1.dev2__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 (387) hide show
  1. batch/compile_prenight_metadata_cache.sh +34 -0
  2. batch/run_auxtel_prenight_sims.sh +163 -0
  3. batch/run_prenight_sims.sh +239 -0
  4. docs/.gitignore +3 -0
  5. docs/Makefile +32 -0
  6. docs/api.rst +25 -0
  7. docs/conf.py +8 -0
  8. docs/data-api.rst +12 -0
  9. docs/data-download.rst +70 -0
  10. docs/developer.rst +14 -0
  11. docs/documenteer.toml +11 -0
  12. docs/index.rst +22 -0
  13. docs/installation.rst +76 -0
  14. docs/introduction.rst +37 -0
  15. docs/maf-api-batches.rst +12 -0
  16. docs/maf-api-db.rst +12 -0
  17. docs/maf-api-maf-contrib.rst +12 -0
  18. docs/maf-api-maps.rst +12 -0
  19. docs/maf-api-metricbundles.rst +12 -0
  20. docs/maf-api-metrics.rst +12 -0
  21. docs/maf-api-plots.rst +12 -0
  22. docs/maf-api-run-comparison.rst +12 -0
  23. docs/maf-api-slicers.rst +12 -0
  24. docs/maf-api-stackers.rst +12 -0
  25. docs/maf-api-utils.rst +12 -0
  26. docs/maf-api.rst +22 -0
  27. docs/maf.rst +60 -0
  28. docs/metric_list.py +49 -0
  29. docs/moving-objects-api.rst +12 -0
  30. docs/moving-objects.rst +22 -0
  31. docs/phot-utils-api.rst +12 -0
  32. docs/phot-utils.rst +11 -0
  33. docs/prenight.rst +117 -0
  34. docs/protoarchive.rst +276 -0
  35. docs/satellite-constellations-api.rst +12 -0
  36. docs/satellite-constellations.rst +13 -0
  37. docs/selfcal-api.rst +12 -0
  38. docs/selfcal.rst +15 -0
  39. docs/sim-archive-api.rst +12 -0
  40. docs/skybrightness-api.rst +12 -0
  41. docs/skybrightness.rst +23 -0
  42. docs/user-guide.rst +28 -0
  43. docs/vseqarchive-architecture.rst +438 -0
  44. docs/vseqarchive-setup.rst +238 -0
  45. docs/vseqarchive.rst +225 -0
  46. rubin_sim/__init__.py +7 -0
  47. rubin_sim/data/__init__.py +1 -0
  48. rubin_sim/data/rs_download_data.py +155 -0
  49. rubin_sim/maf/__init__.py +34 -0
  50. rubin_sim/maf/batches/__init__.py +18 -0
  51. rubin_sim/maf/batches/altaz_batch.py +161 -0
  52. rubin_sim/maf/batches/col_map_dict.py +274 -0
  53. rubin_sim/maf/batches/common.py +388 -0
  54. rubin_sim/maf/batches/ddf_batch.py +728 -0
  55. rubin_sim/maf/batches/filterchange_batch.py +182 -0
  56. rubin_sim/maf/batches/glance_batch.py +591 -0
  57. rubin_sim/maf/batches/hourglass_batch.py +79 -0
  58. rubin_sim/maf/batches/info_batch.py +114 -0
  59. rubin_sim/maf/batches/metadata_batch.py +558 -0
  60. rubin_sim/maf/batches/moving_objects_batch.py +1587 -0
  61. rubin_sim/maf/batches/openshutter_batch.py +100 -0
  62. rubin_sim/maf/batches/radar_limited.py +1100 -0
  63. rubin_sim/maf/batches/science_radar_batch.py +1910 -0
  64. rubin_sim/maf/batches/skycoverage.py +155 -0
  65. rubin_sim/maf/batches/slew_batch.py +146 -0
  66. rubin_sim/maf/batches/srd_batch.py +637 -0
  67. rubin_sim/maf/batches/time_batch.py +757 -0
  68. rubin_sim/maf/batches/time_sci_batch.py +105 -0
  69. rubin_sim/maf/batches/visitdepth_batch.py +485 -0
  70. rubin_sim/maf/db/__init__.py +2 -0
  71. rubin_sim/maf/db/add_run.py +34 -0
  72. rubin_sim/maf/db/results_db.py +1074 -0
  73. rubin_sim/maf/db/tracking_db.py +332 -0
  74. rubin_sim/maf/ddf_dir.py +53 -0
  75. rubin_sim/maf/generate_ss.py +218 -0
  76. rubin_sim/maf/glance_dir.py +58 -0
  77. rubin_sim/maf/maf_contrib/__init__.py +25 -0
  78. rubin_sim/maf/maf_contrib/calculate_lsst_field_visibility_astropy.py +170 -0
  79. rubin_sim/maf/maf_contrib/depth_limited_num_gal_metric.py +109 -0
  80. rubin_sim/maf/maf_contrib/example_new_metrics.py +40 -0
  81. rubin_sim/maf/maf_contrib/filter_pair_t_gaps_metric.py +208 -0
  82. rubin_sim/maf/maf_contrib/grb_transient_metric.py +249 -0
  83. rubin_sim/maf/maf_contrib/gw170817_det_metric.py +72 -0
  84. rubin_sim/maf/maf_contrib/intervals_between_obs_metric.py +68 -0
  85. rubin_sim/maf/maf_contrib/kne_metrics.py +473 -0
  86. rubin_sim/maf/maf_contrib/lss_metrics.py +45 -0
  87. rubin_sim/maf/maf_contrib/lss_obs_strategy/__init__.py +3 -0
  88. rubin_sim/maf/maf_contrib/lss_obs_strategy/constants_for_pipeline.py +73 -0
  89. rubin_sim/maf/maf_contrib/lss_obs_strategy/galaxy_counts_metric_extended.py +205 -0
  90. rubin_sim/maf/maf_contrib/lss_obs_strategy/galaxy_counts_with_pixel_calibration.py +171 -0
  91. rubin_sim/maf/maf_contrib/lv_dwarfs/__init__.py +1 -0
  92. rubin_sim/maf/maf_contrib/lv_dwarfs/lv_dwarfs_metrics.py +388 -0
  93. rubin_sim/maf/maf_contrib/microlensing_metric.py +593 -0
  94. rubin_sim/maf/maf_contrib/num_obs_in_survey_time_overlap_metric.py +43 -0
  95. rubin_sim/maf/maf_contrib/periodic_metric.py +51 -0
  96. rubin_sim/maf/maf_contrib/periodic_star_metric.py +186 -0
  97. rubin_sim/maf/maf_contrib/periodic_star_modulation_metric.py +236 -0
  98. rubin_sim/maf/maf_contrib/presto_color_kne_pop_metric.py +441 -0
  99. rubin_sim/maf/maf_contrib/selfcal_uniformity_metric.py +171 -0
  100. rubin_sim/maf/maf_contrib/star_count_mass_metric.py +64 -0
  101. rubin_sim/maf/maf_contrib/star_count_metric.py +56 -0
  102. rubin_sim/maf/maf_contrib/star_counts/__init__.py +8 -0
  103. rubin_sim/maf/maf_contrib/star_counts/abs_mag.py +324 -0
  104. rubin_sim/maf/maf_contrib/star_counts/coords.py +99 -0
  105. rubin_sim/maf/maf_contrib/star_counts/readme.txt +1 -0
  106. rubin_sim/maf/maf_contrib/star_counts/spec_type.py +91 -0
  107. rubin_sim/maf/maf_contrib/star_counts/starcount.py +30 -0
  108. rubin_sim/maf/maf_contrib/star_counts/starcount_bymass.py +93 -0
  109. rubin_sim/maf/maf_contrib/star_counts/stellardensity.py +59 -0
  110. rubin_sim/maf/maf_contrib/static_probes_fom_summary_metric.py +386 -0
  111. rubin_sim/maf/maf_contrib/tdes_pop_metric.py +362 -0
  112. rubin_sim/maf/maf_contrib/transient_ascii_sed_metric.py +652 -0
  113. rubin_sim/maf/maf_contrib/triplet_metric.py +156 -0
  114. rubin_sim/maf/maf_contrib/var_depth_metric.py +157 -0
  115. rubin_sim/maf/maf_contrib/xrb_metrics.py +523 -0
  116. rubin_sim/maf/maf_contrib/young_stellar_objects_metric.py +200 -0
  117. rubin_sim/maf/maf_night_report.py +200 -0
  118. rubin_sim/maf/make_fbs_tracking_db.py +90 -0
  119. rubin_sim/maf/maps/__init__.py +8 -0
  120. rubin_sim/maf/maps/base_map.py +72 -0
  121. rubin_sim/maf/maps/create_gaia_density_map.py +158 -0
  122. rubin_sim/maf/maps/dust_map.py +66 -0
  123. rubin_sim/maf/maps/dust_map_3d.py +190 -0
  124. rubin_sim/maf/maps/ebv_3d_hp.py +170 -0
  125. rubin_sim/maf/maps/ebv_hp.py +64 -0
  126. rubin_sim/maf/maps/gal_coords_map.py +22 -0
  127. rubin_sim/maf/maps/galactic_plane_priority_maps.py +210 -0
  128. rubin_sim/maf/maps/stellar_density_map.py +73 -0
  129. rubin_sim/maf/maps/trilegal_map.py +82 -0
  130. rubin_sim/maf/metadata_dir.py +90 -0
  131. rubin_sim/maf/metric_bundles/__init__.py +3 -0
  132. rubin_sim/maf/metric_bundles/metric_bundle.py +816 -0
  133. rubin_sim/maf/metric_bundles/metric_bundle_group.py +834 -0
  134. rubin_sim/maf/metric_bundles/mo_metric_bundle.py +646 -0
  135. rubin_sim/maf/metrics/__init__.py +52 -0
  136. rubin_sim/maf/metrics/agn_time_lag_metric.py +118 -0
  137. rubin_sim/maf/metrics/agnstructure.py +141 -0
  138. rubin_sim/maf/metrics/area_summary_metrics.py +120 -0
  139. rubin_sim/maf/metrics/base_metric.py +215 -0
  140. rubin_sim/maf/metrics/brown_dwarf_metric.py +162 -0
  141. rubin_sim/maf/metrics/cadence_metrics.py +400 -0
  142. rubin_sim/maf/metrics/calibration_metrics.py +618 -0
  143. rubin_sim/maf/metrics/chip_vendor_metric.py +43 -0
  144. rubin_sim/maf/metrics/color_slope_metrics.py +189 -0
  145. rubin_sim/maf/metrics/cosmology_summary_metrics.py +1212 -0
  146. rubin_sim/maf/metrics/coverage_metric.py +48 -0
  147. rubin_sim/maf/metrics/crowding_metric.py +255 -0
  148. rubin_sim/maf/metrics/cumulative_metric.py +46 -0
  149. rubin_sim/maf/metrics/dcr_metric.py +93 -0
  150. rubin_sim/maf/metrics/exgal_m5.py +57 -0
  151. rubin_sim/maf/metrics/fft_metric.py +27 -0
  152. rubin_sim/maf/metrics/galactic_plane_metrics.py +375 -0
  153. rubin_sim/maf/metrics/galplane_time_sampling_metrics.py +236 -0
  154. rubin_sim/maf/metrics/hourglass_metric.py +130 -0
  155. rubin_sim/maf/metrics/incremental_template_metric.py +255 -0
  156. rubin_sim/maf/metrics/kuiper_metrics.py +26 -0
  157. rubin_sim/maf/metrics/mo_metrics.py +1496 -0
  158. rubin_sim/maf/metrics/mo_summary_metrics.py +464 -0
  159. rubin_sim/maf/metrics/night_pointing_metric.py +70 -0
  160. rubin_sim/maf/metrics/optimal_m5_metric.py +94 -0
  161. rubin_sim/maf/metrics/pair_metric.py +69 -0
  162. rubin_sim/maf/metrics/periodic_detect_metric.py +133 -0
  163. rubin_sim/maf/metrics/phase_gap_metric.py +172 -0
  164. rubin_sim/maf/metrics/qso_number_counts_metric.py +138 -0
  165. rubin_sim/maf/metrics/scaling_metrics.py +148 -0
  166. rubin_sim/maf/metrics/schedview_metrics.py +39 -0
  167. rubin_sim/maf/metrics/season_metrics.py +344 -0
  168. rubin_sim/maf/metrics/simple_metrics.py +546 -0
  169. rubin_sim/maf/metrics/sky_sat_metric.py +18 -0
  170. rubin_sim/maf/metrics/sn_cadence_metric.py +104 -0
  171. rubin_sim/maf/metrics/sn_n_sn_metric.py +1364 -0
  172. rubin_sim/maf/metrics/sn_sl_metric.py +216 -0
  173. rubin_sim/maf/metrics/sn_snr_metric.py +715 -0
  174. rubin_sim/maf/metrics/snr_weight.py +24 -0
  175. rubin_sim/maf/metrics/star_density.py +77 -0
  176. rubin_sim/maf/metrics/string_count_metric.py +95 -0
  177. rubin_sim/maf/metrics/summary_metrics.py +201 -0
  178. rubin_sim/maf/metrics/surfb_metric.py +175 -0
  179. rubin_sim/maf/metrics/technical_metrics.py +271 -0
  180. rubin_sim/maf/metrics/tgaps.py +412 -0
  181. rubin_sim/maf/metrics/tomography_models.py +2034 -0
  182. rubin_sim/maf/metrics/transient_metrics.py +268 -0
  183. rubin_sim/maf/metrics/uniformity_metrics.py +418 -0
  184. rubin_sim/maf/metrics/use_metrics.py +55 -0
  185. rubin_sim/maf/metrics/vector_metrics.py +190 -0
  186. rubin_sim/maf/metrics/visit_groups_metric.py +341 -0
  187. rubin_sim/maf/metrics/weak_lensing_systematics_metric.py +210 -0
  188. rubin_sim/maf/plots/__init__.py +14 -0
  189. rubin_sim/maf/plots/hg_plotters.py +1117 -0
  190. rubin_sim/maf/plots/hourglass_plotters.py +120 -0
  191. rubin_sim/maf/plots/mo_plotters.py +358 -0
  192. rubin_sim/maf/plots/nd_plotters.py +220 -0
  193. rubin_sim/maf/plots/neo_distance_plotter.py +140 -0
  194. rubin_sim/maf/plots/night_pointing_plotter.py +96 -0
  195. rubin_sim/maf/plots/oned_plotters.py +114 -0
  196. rubin_sim/maf/plots/perceptual_rainbow.py +30 -0
  197. rubin_sim/maf/plots/plot_handler.py +766 -0
  198. rubin_sim/maf/plots/skyproj_plotters.py +540 -0
  199. rubin_sim/maf/plots/spatial_plotters.py +1029 -0
  200. rubin_sim/maf/plots/special_plotters.py +256 -0
  201. rubin_sim/maf/plots/two_d_plotters.py +177 -0
  202. rubin_sim/maf/plots/xyplotter.py +37 -0
  203. rubin_sim/maf/run_comparison/__init__.py +5 -0
  204. rubin_sim/maf/run_comparison/archive.py +584 -0
  205. rubin_sim/maf/run_comparison/gather_summaries.py +125 -0
  206. rubin_sim/maf/run_comparison/microlensing_compare.py +514 -0
  207. rubin_sim/maf/run_comparison/radar_plot.py +202 -0
  208. rubin_sim/maf/run_comparison/summary_plots.py +710 -0
  209. rubin_sim/maf/run_moving_calc.py +239 -0
  210. rubin_sim/maf/run_moving_fractions.py +128 -0
  211. rubin_sim/maf/run_moving_join.py +103 -0
  212. rubin_sim/maf/run_selfcal_metric.py +92 -0
  213. rubin_sim/maf/scimaf_dir.py +87 -0
  214. rubin_sim/maf/show_maf.py +227 -0
  215. rubin_sim/maf/slicers/__init__.py +13 -0
  216. rubin_sim/maf/slicers/base_slicer.py +513 -0
  217. rubin_sim/maf/slicers/base_spatial_slicer.py +202 -0
  218. rubin_sim/maf/slicers/healpix_sdss_slicer.py +118 -0
  219. rubin_sim/maf/slicers/healpix_slicer.py +189 -0
  220. rubin_sim/maf/slicers/healpix_subset_slicer.py +293 -0
  221. rubin_sim/maf/slicers/hourglass_slicer.py +37 -0
  222. rubin_sim/maf/slicers/mo_slicer.py +171 -0
  223. rubin_sim/maf/slicers/movie_slicer.py +288 -0
  224. rubin_sim/maf/slicers/nd_slicer.py +147 -0
  225. rubin_sim/maf/slicers/one_d_slicer.py +210 -0
  226. rubin_sim/maf/slicers/time_interval_slicers.py +283 -0
  227. rubin_sim/maf/slicers/uni_slicer.py +57 -0
  228. rubin_sim/maf/slicers/user_points_slicer.py +141 -0
  229. rubin_sim/maf/stackers/__init__.py +13 -0
  230. rubin_sim/maf/stackers/base_stacker.py +221 -0
  231. rubin_sim/maf/stackers/coord_stackers.py +224 -0
  232. rubin_sim/maf/stackers/date_stackers.py +192 -0
  233. rubin_sim/maf/stackers/general_stackers.py +683 -0
  234. rubin_sim/maf/stackers/get_col_info.py +104 -0
  235. rubin_sim/maf/stackers/label_stackers.py +114 -0
  236. rubin_sim/maf/stackers/m5_optimal_stacker.py +126 -0
  237. rubin_sim/maf/stackers/mo_phase.py +265 -0
  238. rubin_sim/maf/stackers/mo_stackers.py +486 -0
  239. rubin_sim/maf/stackers/n_follow_stacker.py +187 -0
  240. rubin_sim/maf/stackers/neo_dist_stacker.py +126 -0
  241. rubin_sim/maf/stackers/sdss_stackers.py +30 -0
  242. rubin_sim/maf/stackers/sn_stacker.py +152 -0
  243. rubin_sim/maf/stackers/teff_stacker.py +154 -0
  244. rubin_sim/maf/utils/__init__.py +6 -0
  245. rubin_sim/maf/utils/astrometry_utils.py +93 -0
  246. rubin_sim/maf/utils/generate_fov_map.py +58 -0
  247. rubin_sim/maf/utils/get_date_version.py +28 -0
  248. rubin_sim/maf/utils/maf_utils.py +212 -0
  249. rubin_sim/maf/utils/opsim_utils.py +259 -0
  250. rubin_sim/maf/utils/output_utils.py +34 -0
  251. rubin_sim/maf/utils/sn_n_sn_utils.py +894 -0
  252. rubin_sim/maf/utils/sn_utils.py +369 -0
  253. rubin_sim/maf/utils/stellar_mags.py +197 -0
  254. rubin_sim/maf/web/__init__.py +2 -0
  255. rubin_sim/maf/web/favicon.ico +0 -0
  256. rubin_sim/maf/web/maf_run_results.py +645 -0
  257. rubin_sim/maf/web/maf_tracking.py +139 -0
  258. rubin_sim/maf/web/sorttable.js +496 -0
  259. rubin_sim/maf/web/templates/allmetricresults.html +72 -0
  260. rubin_sim/maf/web/templates/configs.html +28 -0
  261. rubin_sim/maf/web/templates/macros.html +243 -0
  262. rubin_sim/maf/web/templates/master.html +294 -0
  263. rubin_sim/maf/web/templates/metricselect.html +91 -0
  264. rubin_sim/maf/web/templates/multicolor.html +147 -0
  265. rubin_sim/maf/web/templates/results.html +82 -0
  266. rubin_sim/maf/web/templates/runselect.html +58 -0
  267. rubin_sim/maf/web/templates/stats.html +50 -0
  268. rubin_sim/moving_objects/__init__.py +8 -0
  269. rubin_sim/moving_objects/base_obs.py +468 -0
  270. rubin_sim/moving_objects/cheby_fits.py +689 -0
  271. rubin_sim/moving_objects/cheby_values.py +231 -0
  272. rubin_sim/moving_objects/chebyshev_utils.py +365 -0
  273. rubin_sim/moving_objects/direct_obs.py +376 -0
  274. rubin_sim/moving_objects/make_lsst_obs.py +282 -0
  275. rubin_sim/moving_objects/ooephemerides.py +643 -0
  276. rubin_sim/moving_objects/orbits.py +507 -0
  277. rubin_sim/moving_objects/pre_generate.py +37 -0
  278. rubin_sim/moving_objects/utils.py +61 -0
  279. rubin_sim/phot_utils/__init__.py +7 -0
  280. rubin_sim/phot_utils/bandpass.py +425 -0
  281. rubin_sim/phot_utils/photometric_parameters.py +449 -0
  282. rubin_sim/phot_utils/physical_parameters.py +50 -0
  283. rubin_sim/phot_utils/predicted_zeropoints.py +245 -0
  284. rubin_sim/phot_utils/sed.py +1773 -0
  285. rubin_sim/phot_utils/sed_utils.py +52 -0
  286. rubin_sim/phot_utils/signaltonoise.py +695 -0
  287. rubin_sim/phot_utils/spectral_resampling.py +161 -0
  288. rubin_sim/satellite_constellations/__init__.py +3 -0
  289. rubin_sim/satellite_constellations/basis_function.py +40 -0
  290. rubin_sim/satellite_constellations/model_observatory.py +157 -0
  291. rubin_sim/satellite_constellations/sat_utils.py +488 -0
  292. rubin_sim/selfcal/__init__.py +4 -0
  293. rubin_sim/selfcal/generate_catalog.py +188 -0
  294. rubin_sim/selfcal/offsets.py +119 -0
  295. rubin_sim/selfcal/solver.py +143 -0
  296. rubin_sim/selfcal/star_tools.py +42 -0
  297. rubin_sim/sim_archive/__init__.py +14 -0
  298. rubin_sim/sim_archive/conda_packages.py +65 -0
  299. rubin_sim/sim_archive/future_vsarch.py +480 -0
  300. rubin_sim/sim_archive/make_snapshot.py +255 -0
  301. rubin_sim/sim_archive/prenight.py +216 -0
  302. rubin_sim/sim_archive/prenightindex.py +229 -0
  303. rubin_sim/sim_archive/prototype.py +1029 -0
  304. rubin_sim/sim_archive/sim_archive.py +680 -0
  305. rubin_sim/sim_archive/tempdb.py +56 -0
  306. rubin_sim/sim_archive/util.py +146 -0
  307. rubin_sim/sim_archive/vseqarchive.py +965 -0
  308. rubin_sim/sim_archive/vseqmetadata.py +1721 -0
  309. rubin_sim/skybrightness/__init__.py +5 -0
  310. rubin_sim/skybrightness/allsky_db.py +93 -0
  311. rubin_sim/skybrightness/data/ESO_Spectra/eso_tools.py +505 -0
  312. rubin_sim/skybrightness/data/solarSpec/package.py +22 -0
  313. rubin_sim/skybrightness/generate_hdf5.py +243 -0
  314. rubin_sim/skybrightness/interp_components.py +798 -0
  315. rubin_sim/skybrightness/sky_model.py +771 -0
  316. rubin_sim/skybrightness/twilight_func.py +83 -0
  317. rubin_sim/skybrightness/utils.py +91 -0
  318. rubin_sim-2.6.1.dev2.dist-info/METADATA +95 -0
  319. rubin_sim-2.6.1.dev2.dist-info/RECORD +387 -0
  320. rubin_sim-2.6.1.dev2.dist-info/WHEEL +5 -0
  321. rubin_sim-2.6.1.dev2.dist-info/entry_points.txt +24 -0
  322. rubin_sim-2.6.1.dev2.dist-info/licenses/LICENSE +674 -0
  323. rubin_sim-2.6.1.dev2.dist-info/top_level.txt +5 -0
  324. sql/make_vseqmeta.sql +251 -0
  325. tests/data/test_data.py +26 -0
  326. tests/data/test_ddf_grid.py +17 -0
  327. tests/maf/test_3x2fom.py +68 -0
  328. tests/maf/test_archive.py +162 -0
  329. tests/maf/test_basemetrics.py +99 -0
  330. tests/maf/test_batchcommon.py +36 -0
  331. tests/maf/test_batches.py +159 -0
  332. tests/maf/test_cadencemetrics.py +388 -0
  333. tests/maf/test_calibrationmetrics.py +242 -0
  334. tests/maf/test_color_slopes.py +71 -0
  335. tests/maf/test_cosmology_summaries.py +20 -0
  336. tests/maf/test_gathersummaries.py +48 -0
  337. tests/maf/test_healpixslicer.py +439 -0
  338. tests/maf/test_healpixsubsetslicer.py +273 -0
  339. tests/maf/test_hgplotters.py +145 -0
  340. tests/maf/test_hourglassmetric.py +33 -0
  341. tests/maf/test_io.py +187 -0
  342. tests/maf/test_json.py +169 -0
  343. tests/maf/test_maps.py +199 -0
  344. tests/maf/test_metricbundle.py +69 -0
  345. tests/maf/test_mometrics.py +422 -0
  346. tests/maf/test_movieslicer.py +216 -0
  347. tests/maf/test_ndslicer.py +240 -0
  348. tests/maf/test_neodistanceplotter.py +41 -0
  349. tests/maf/test_onedslicer.py +245 -0
  350. tests/maf/test_opsimutils.py +80 -0
  351. tests/maf/test_plotters.py +134 -0
  352. tests/maf/test_resultsdb.py +145 -0
  353. tests/maf/test_schedviewmetrics.py +14 -0
  354. tests/maf/test_simplemetrics.py +200 -0
  355. tests/maf/test_snmetrics.py +335 -0
  356. tests/maf/test_snmetrics_nsn.py +123 -0
  357. tests/maf/test_stackers.py +423 -0
  358. tests/maf/test_stellarmags.py +50 -0
  359. tests/maf/test_stringcount.py +26 -0
  360. tests/maf/test_summary_plots.py +197 -0
  361. tests/maf/test_summarymetrics.py +82 -0
  362. tests/maf/test_technicalmetrics.py +267 -0
  363. tests/maf/test_templatemetrics.py +86 -0
  364. tests/maf/test_timeintervalslicers.py +97 -0
  365. tests/maf/test_trackingdb.py +88 -0
  366. tests/maf/test_unislicer.py +116 -0
  367. tests/maf/test_vectormetrics.py +225 -0
  368. tests/maf/test_visitgroupsmetric.py +181 -0
  369. tests/moving_objects/test_camera.py +39 -0
  370. tests/moving_objects/test_chebyfits.py +188 -0
  371. tests/moving_objects/test_chebyshevutils.py +76 -0
  372. tests/moving_objects/test_chebyvalues.py +245 -0
  373. tests/moving_objects/test_ephemerides.py +219 -0
  374. tests/moving_objects/test_orbits.py +170 -0
  375. tests/moving_objects/test_sed_data.py +61 -0
  376. tests/phot_utils/test_approximatebandpasses.py +91 -0
  377. tests/phot_utils/test_photometricparameters.py +268 -0
  378. tests/phot_utils/test_photometry.py +61 -0
  379. tests/phot_utils/test_predicted_zeropoints.py +53 -0
  380. tests/phot_utils/test_read_bandpasses.py +29 -0
  381. tests/phot_utils/test_sed.py +368 -0
  382. tests/phot_utils/test_snr.py +370 -0
  383. tests/satellite_constellations/test_satellites.py +36 -0
  384. tests/sim_archive/test_make_snapshot.py +24 -0
  385. tests/sim_archive/test_prototype_sim_archive.py +204 -0
  386. tests/sim_archive/test_vseqarchive.py +947 -0
  387. tests/skybrightness/test_skymodel.py +359 -0
@@ -0,0 +1,680 @@
1
+ """Tools for maintaining the archive of opsim output and metadata."""
2
+
3
+ __all__ = [
4
+ "make_sim_data_dir",
5
+ "drive_sim",
6
+ "find_latest_prenight_sim_for_nights",
7
+ "fetch_sim_for_nights",
8
+ "fetch_obsloctap_visits",
9
+ "fetch_sim_stats_for_night",
10
+ ]
11
+
12
+ import hashlib
13
+ import logging
14
+ import lzma
15
+ import os
16
+ import pickle
17
+ import shutil
18
+ import socket
19
+ from datetime import date, datetime, timedelta
20
+ from numbers import Integral, Number
21
+ from pathlib import Path
22
+ from tempfile import TemporaryDirectory
23
+ from typing import Any, Optional, Sequence, cast
24
+ from zoneinfo import ZoneInfo
25
+
26
+ import numpy as np
27
+ import pandas as pd
28
+ import rubin_scheduler
29
+ import yaml
30
+ from astropy.time import Time
31
+ from lsst.resources import ResourcePath
32
+ from pandas.api.types import is_numeric_dtype
33
+ from rubin_scheduler.scheduler import sim_runner
34
+ from rubin_scheduler.scheduler.model_observatory import ModelObservatory
35
+ from rubin_scheduler.scheduler.schedulers import CoreScheduler
36
+ from rubin_scheduler.scheduler.utils import SchemaConverter
37
+ from rubin_scheduler.site_models.almanac import Almanac
38
+
39
+ from rubin_sim import maf
40
+ from rubin_sim.maf.stackers import BaseStacker
41
+ from rubin_sim.maf.utils.opsim_utils import get_sim_data
42
+ from rubin_sim.sim_archive.prenightindex import get_prenight_index, select_latest_prenight_sim
43
+ from rubin_sim.sim_archive.util import dayobs_to_date
44
+ from rubin_sim.sim_archive.vseqarchive import compute_nightly_stats, get_visits
45
+ from rubin_sim.sim_archive.vseqmetadata import VisitSequenceArchiveMetadata
46
+
47
+ LOGGER = logging.getLogger(__name__)
48
+
49
+
50
+ def make_sim_data_dir(
51
+ observations: np.recarray,
52
+ reward_df: pd.DataFrame | None = None,
53
+ obs_rewards: pd.DataFrame | None = None,
54
+ in_files: dict = {},
55
+ sim_runner_kwargs: dict = {},
56
+ tags: list = [],
57
+ label: str | None = None,
58
+ data_path: str | Path | None = None,
59
+ opsim_metadata: dict | None = None,
60
+ ) -> Path | str:
61
+ """Create or fill a local simulation archive directory.
62
+
63
+ Parameters
64
+ ----------
65
+ observations : `np.recarray`
66
+ The observations data, in the "obs" format as accepted and
67
+ created by `SchemaConverter`.
68
+ reward_df : `pd.DataFrame`, optional
69
+ The reward data, by default None.
70
+ obs_rewards : `pd.DataFrame`, optional
71
+ The observation rewards data, by default None.
72
+ in_files : `dict`, optional
73
+ Additional input files to be included in the archive,
74
+ by default {}.
75
+ sim_runner_kwargs : `dict`, optional
76
+ Additional simulation runner keyword arguments, by default {}.
77
+ tags : `list` [`str`], optional
78
+ A list of tags/keywords to be included in the metadata, by
79
+ default [].
80
+ label : `str`, optional
81
+ A label to be included in the metadata, by default None.
82
+ data_path : `str` or `Path`, optional
83
+ The path to the simulation archive directory, by default None.
84
+ opsim_metadata : `dict`
85
+ Metadata to be included.
86
+
87
+ Returns
88
+ -------
89
+ data_dir : `Path` or `TemporaryDirectory`
90
+ The temporary directory containing the simulation archive.
91
+ """
92
+ if data_path is None:
93
+ data_dir = TemporaryDirectory()
94
+ data_path = Path(data_dir.name)
95
+ else:
96
+ data_dir = None
97
+
98
+ if not isinstance(data_path, Path):
99
+ data_path = Path(data_path)
100
+
101
+ files = {}
102
+
103
+ # Save the observations
104
+ files["observations"] = {"name": "opsim.db"}
105
+ opsim_output_fname = data_path.joinpath(files["observations"]["name"])
106
+ SchemaConverter().obs2opsim(observations, filename=opsim_output_fname)
107
+
108
+ # Save the rewards
109
+ if reward_df is not None and obs_rewards is not None:
110
+ files["rewards"] = {"name": "rewards.h5"}
111
+ rewards_fname = data_path.joinpath(files["rewards"]["name"])
112
+ if reward_df is not None:
113
+ reward_df.to_hdf(rewards_fname, key="reward_df")
114
+ if obs_rewards is not None:
115
+ obs_rewards.to_hdf(rewards_fname, key="obs_rewards")
116
+
117
+ # Save basic statistics
118
+ files["statistics"] = {"name": "obs_stats.txt"}
119
+ stats_fname = data_path.joinpath(files["statistics"]["name"])
120
+ with open(stats_fname, "w") as stats_io:
121
+ print(SchemaConverter().obs2opsim(observations).describe().T.to_csv(sep="\t"), file=stats_io)
122
+
123
+ # Add supplied files
124
+ for file_type, fname in in_files.items():
125
+ files[file_type] = {"name": Path(fname).name}
126
+ try:
127
+ shutil.copyfile(fname, data_path.joinpath(files[file_type]["name"]))
128
+ except shutil.SameFileError:
129
+ pass
130
+
131
+ # Add file hashes
132
+ for file_type in files:
133
+ fname = data_path.joinpath(files[file_type]["name"])
134
+ with open(fname, "rb") as file_io:
135
+ content = file_io.read()
136
+
137
+ files[file_type]["md5"] = hashlib.md5(content).hexdigest()
138
+
139
+ def convert_mjd_to_dayobs(mjd: float) -> str:
140
+ # Use dayObs defn. from SITCOMTN-32: https://sitcomtn-032.lsst.io/
141
+ evening_local_mjd = np.floor(mjd - 0.5).astype(int)
142
+ evening_local_iso = Time(evening_local_mjd, format="mjd").iso[:10]
143
+ assert isinstance(evening_local_iso, str)
144
+ return evening_local_iso
145
+
146
+ if opsim_metadata is None:
147
+ opsim_metadata = {}
148
+
149
+ opsim_metadata["scheduler_version"] = rubin_scheduler.__version__
150
+ opsim_metadata["host"] = socket.getfqdn()
151
+ opsim_metadata["username"] = os.environ["USER"]
152
+
153
+ simulation_dates = {}
154
+ if "sim_start_mjd" in sim_runner_kwargs:
155
+ simulation_dates["first"] = convert_mjd_to_dayobs(sim_runner_kwargs["sim_start_mjd"])
156
+
157
+ if "sim_duration" in sim_runner_kwargs:
158
+ simulation_dates["last"] = convert_mjd_to_dayobs(
159
+ sim_runner_kwargs["sim_start_mjd"] + sim_runner_kwargs["sim_duration"] - 1
160
+ )
161
+ else:
162
+ simulation_dates["first"] = convert_mjd_to_dayobs(observations["mjd"].min())
163
+ simulation_dates["last"] = convert_mjd_to_dayobs(observations["mjd"].max())
164
+
165
+ if len(sim_runner_kwargs) > 0:
166
+ opsim_metadata["sim_runner_kwargs"] = {}
167
+ for key, value in sim_runner_kwargs.items():
168
+ # Cast numpy number types to ints, floats, and reals to avoid
169
+ # confusing the yaml module.
170
+ match value:
171
+ case bool():
172
+ opsim_metadata["sim_runner_kwargs"][key] = value
173
+ case Integral():
174
+ opsim_metadata["sim_runner_kwargs"][key] = int(value)
175
+ case Number():
176
+ opsim_metadata["sim_runner_kwargs"][key] = float(value)
177
+ case _:
178
+ opsim_metadata["sim_runner_kwargs"][key] = str(value)
179
+
180
+ opsim_metadata["simulated_dates"] = simulation_dates
181
+ opsim_metadata["files"] = files
182
+
183
+ if len(tags) > 0:
184
+ for tag in tags:
185
+ assert isinstance(tag, str), "Tags must be strings."
186
+ opsim_metadata["tags"] = tags
187
+
188
+ if label is not None:
189
+ assert isinstance(label, str), "The sim label must be a string."
190
+ opsim_metadata["label"] = label
191
+
192
+ sim_metadata_fname = data_path.joinpath("sim_metadata.yaml")
193
+ with open(sim_metadata_fname, "w") as sim_metadata_io:
194
+ print(yaml.dump(opsim_metadata, indent=4), file=sim_metadata_io)
195
+
196
+ files["metadata"] = {"name": sim_metadata_fname}
197
+
198
+ if data_dir is not None:
199
+ # If we created a temporary directory, if we do not return it, it
200
+ # will get automatically cleaned up, losing our work.
201
+ # So, return it.
202
+ return data_dir
203
+
204
+ return data_path
205
+
206
+
207
+ def drive_sim(
208
+ observatory: ModelObservatory,
209
+ scheduler: CoreScheduler,
210
+ label: str | None = None,
211
+ tags: list = [],
212
+ script: str | None = None,
213
+ notebook: str | None = None,
214
+ opsim_metadata: dict | None = None,
215
+ **kwargs: Any,
216
+ ) -> tuple:
217
+ """Run a simulation and archive the results.
218
+
219
+ Parameters
220
+ ----------
221
+ observatory : `ModelObservatory`
222
+ The model for the observatory.
223
+ scheduler : `CoreScheduler`
224
+ The scheduler to use.
225
+ archive_uri : `str`, optional
226
+ The root URI of the archive resource into which the results should
227
+ be stored. Defaults to None.
228
+ label : `str`, optional
229
+ The label for the simulation in the archive. Defaults to None.
230
+ tags : `list` of `str`, optional
231
+ The tags for the simulation in the archive. Defaults to an
232
+ empty list.
233
+ script : `str`
234
+ The filename of the script producing this simulation.
235
+ Defaults to None.
236
+ notebook : `str`, optional
237
+ The filename of the notebook producing the simulation.
238
+ Defaults to None.
239
+ opsim_metadata : `dict`, optional
240
+ Extra metadata to store in the archive.
241
+
242
+ Returns
243
+ -------
244
+ observatory : `ModelObservatory`
245
+ The model for the observatory.
246
+ scheduler : `CoreScheduler`
247
+ The scheduler used.
248
+ observations : `numpy.recarray`
249
+ The observations produced.
250
+ reward_df : `pandas.DataFrame`, optional
251
+ The table of rewards. Present if ``record_rewards``
252
+ or ``scheduler.keep_rewards`` is True.
253
+ obs_rewards : `pandas.Series`, optional
254
+ The mapping of entries in reward_df to observations. Present if
255
+ ``record_rewards``` or ``scheduler.keep_rewards`` is True.
256
+ resource_path : `ResourcePath`, optional
257
+ The resource path to the archive of the simulation. Present if
258
+ ``archive_uri`` was set.
259
+
260
+ Notes
261
+ -----
262
+ Additional parameters not described above will be passed into
263
+ ``sim_runner``.
264
+
265
+ If the ``archive_uri`` parameter is not supplied, ``sim_runner`` is run
266
+ directly, so that ``drive_sim`` can act as a drop-in replacement of
267
+ ``sim_runner``.
268
+
269
+ In a jupyter notebook, the notebook can be saved for the notebook
270
+ paramater using ``%notebook $notebook_fname`` (where ``notebook_fname``
271
+ is variable holding the filename for the notebook) in the cell prior
272
+ to calling ``drive_sim``.
273
+ """
274
+ if "record_rewards" in kwargs:
275
+ if kwargs["record_rewards"] and not scheduler.keep_rewards:
276
+ raise ValueError("To keep rewards, scheduler.keep_rewards must be True")
277
+ else:
278
+ kwargs["record_rewards"] = scheduler.keep_rewards
279
+
280
+ in_files = {}
281
+ if script is not None:
282
+ in_files["script"] = script
283
+
284
+ if notebook is not None:
285
+ in_files["notebook"] = notebook
286
+
287
+ with TemporaryDirectory() as local_data_dir:
288
+ LOGGER.debug(f"Using temporary directory {local_data_dir}.")
289
+ # We want to store the state of the scheduler at the start of
290
+ # the sim, so we need to save it now before we run the simulation.
291
+ scheduler_path = Path(local_data_dir).joinpath("scheduler.pickle.xz")
292
+ with lzma.open(scheduler_path, "wb", format=lzma.FORMAT_XZ) as pio:
293
+ pickle.dump(scheduler, pio)
294
+ in_files["scheduler"] = scheduler_path.as_posix()
295
+
296
+ LOGGER.debug("About to call sim_runner.")
297
+ sim_results = sim_runner(observatory, scheduler, **kwargs)
298
+ LOGGER.debug("sim_runner complete.")
299
+
300
+ observations = sim_results[2]
301
+ reward_df = sim_results[3] if scheduler.keep_rewards else None
302
+ obs_rewards = sim_results[4] if scheduler.keep_rewards else None
303
+
304
+ data_dir = make_sim_data_dir(
305
+ observations,
306
+ reward_df=reward_df,
307
+ obs_rewards=obs_rewards,
308
+ in_files=in_files,
309
+ sim_runner_kwargs=kwargs,
310
+ tags=tags,
311
+ label=label,
312
+ opsim_metadata=opsim_metadata,
313
+ )
314
+
315
+ resource_path = ResourcePath(data_dir.name, forceDirctory=True) # type: ignore
316
+
317
+ results = sim_results + (resource_path,)
318
+ return results
319
+
320
+
321
+ def find_latest_prenight_sim_for_nights(
322
+ first_day_obs: str | None = None,
323
+ last_day_obs: str | None = None,
324
+ tags: tuple[str, ...] = ("ideal", "nominal"),
325
+ telescope: str = "simonyi",
326
+ max_simulation_age: int = 2,
327
+ get_prenight_index_kwargs: dict | None = None,
328
+ ) -> dict:
329
+ """Find the most recent prenight simulation that covers a night.
330
+
331
+ Parameters
332
+ ----------
333
+ first_day_obs : `str` or `None`
334
+ The date of the evening for the first night for which to get
335
+ a simulation. If `None`, then the current date will be used.
336
+ last_day_obs : `str` or `None`
337
+ The date of the evening for the last night for which to get
338
+ a simulation. If `None`, then the current date will be used.
339
+ tags : `tuple[str]`
340
+ A tuple of tags to filter simulations by.
341
+ Defaults to ``('ideal', 'nominal')``.
342
+ telescope : `str`
343
+ The telescope to search for (simonyi or auxtel).
344
+ Defaults to simonyi.
345
+ max_simulation_age : `int`
346
+ The maximum age of simulations to consider, in days.
347
+ Simulations older than ``max_simulation_age`` will not be considered.
348
+ Defaults to 2.
349
+
350
+ Returns
351
+ -------
352
+ sim_metadata : `dict`
353
+ A dictionary with metadata for the simulation.
354
+ """
355
+ if first_day_obs is None:
356
+ first_day_obs = datetime.now(ZoneInfo("Etc/GMT+12")).date().isoformat()
357
+ if last_day_obs is None:
358
+ last_day_obs = first_day_obs
359
+
360
+ if get_prenight_index_kwargs is None:
361
+ get_prenight_index_kwargs = {}
362
+ assert isinstance(get_prenight_index_kwargs, dict)
363
+
364
+ sims_for_first_night = get_prenight_index(first_day_obs, telescope, **get_prenight_index_kwargs)
365
+ if first_day_obs != last_day_obs:
366
+ sims_for_last_night = get_prenight_index(last_day_obs, telescope, **get_prenight_index_kwargs)
367
+ full_range_sims = set(sims_for_first_night.index) & set(sims_for_last_night.index)
368
+ else:
369
+ full_range_sims = set(sims_for_first_night.index)
370
+
371
+ result: dict = {}
372
+ if full_range_sims:
373
+ candidate_sims = sims_for_first_night.loc[tuple(full_range_sims), :]
374
+ maybe_result = select_latest_prenight_sim(candidate_sims, tags, max_simulation_age)
375
+ if maybe_result is not None:
376
+ assert isinstance(maybe_result, pd.Series)
377
+ result = maybe_result.to_dict()
378
+
379
+ return result
380
+
381
+
382
+ def fetch_sim_for_nights(
383
+ first_day_obs: str | None = None,
384
+ last_day_obs: str | None = None,
385
+ which_sim: ResourcePath | str | dict | None = None,
386
+ stackers: Sequence[BaseStacker] = (),
387
+ get_sim_data_kwargs: dict | None = None,
388
+ ) -> pd.DataFrame | None:
389
+ """Fetches visit metadata from an opsim database for specified nights.
390
+
391
+ Parameters
392
+ ----------
393
+ first_day_obs : `str` or `None`
394
+ The date of the evening for the first night for which to get
395
+ a simulation. If `None`, then the current date will be used.
396
+ last_day_obs : `str` or `None`
397
+ The date of the evening for the last night for which to get
398
+ a simulation. If `None`, then the current date will be used.
399
+ which_sim : `ResourcePath` or `str` or `dict` on `None`
400
+ The ``resourcePath`` or URL of the opsim file from which to
401
+ load visits, or the arguments to
402
+ ``find_latest_prenight_sim_for_nights``
403
+ to use to determine which simulation to load. ``None`` uses
404
+ default arguments to ``find_latest_prenight_sim_for_nights``.
405
+ Defaults to ``None``.
406
+ stackers : `Sequence[BaseStacker]`
407
+ A sequence of maf stackers to apply.
408
+ get_sim_data_kwargs : `dict`
409
+ Additional arguments to ``get_sim_data`` to use to load
410
+ the visits.
411
+
412
+ Returns
413
+ -------
414
+ visits : `pd.DataFrame`
415
+ A pandas DataFrame containing visit parameters.
416
+ """
417
+ if first_day_obs is None:
418
+ first_day_obs = datetime.now(ZoneInfo("Etc/GMT+12")).date().isoformat()
419
+ if last_day_obs is None:
420
+ last_day_obs = first_day_obs
421
+
422
+ opsim_rp: ResourcePath | None = None
423
+ match which_sim:
424
+ case ResourcePath():
425
+ opsim_rp = which_sim
426
+ case str():
427
+ opsim_rp = ResourcePath(which_sim)
428
+ case dict():
429
+ this_sim = find_latest_prenight_sim_for_nights(first_day_obs, last_day_obs, **which_sim)
430
+ if len(this_sim) == 0:
431
+ raise ValueError("No matching simulations found")
432
+ visitseq_url = this_sim["visitseq_url"]
433
+ if visitseq_url is not None:
434
+ opsim_rp = ResourcePath(visitseq_url)
435
+ elif "opsim" in this_sim["files"]:
436
+ opsim_rp = ResourcePath(this_sim["files"]["opsim"])
437
+ else:
438
+ raise ValueError("No visits found")
439
+ case None:
440
+ this_sim = find_latest_prenight_sim_for_nights(first_day_obs, last_day_obs)
441
+ if len(this_sim) == 0:
442
+ raise ValueError("No matching simulations found")
443
+ visitseq_url = this_sim["visitseq_url"]
444
+ if visitseq_url is not None:
445
+ opsim_rp = ResourcePath(visitseq_url)
446
+ elif "opsim" in this_sim["files"]:
447
+ opsim_rp = ResourcePath(this_sim["files"]["opsim"])
448
+ else:
449
+ raise ValueError("Ne visits found")
450
+ case _:
451
+ raise NotImplementedError()
452
+
453
+ assert isinstance(opsim_rp, ResourcePath)
454
+
455
+ if len(stackers) == 0:
456
+ if isinstance(get_sim_data_kwargs, dict) and "stackers" in get_sim_data_kwargs:
457
+ stackers = get_sim_data_kwargs["stackers"]
458
+ else:
459
+ stackers = []
460
+ assert isinstance(stackers, list)
461
+
462
+ dayobsiso_requested = maf.DayObsISOStacker in [s.__class__ for s in stackers]
463
+ if not dayobsiso_requested:
464
+ # We want it to filter out dates that were not requested,
465
+ # so add it to the stacker even if it was not requested.
466
+ stackers.append(maf.DayObsISOStacker())
467
+
468
+ if opsim_rp.getExtension() in (".db", ".sqlite3"):
469
+ if get_sim_data_kwargs is None:
470
+ get_sim_data_kwargs = {}
471
+ assert isinstance(get_sim_data_kwargs, dict)
472
+ get_sim_data_kwargs["stackers"] = stackers
473
+ visits_recarray = get_sim_data(opsim_rp, **get_sim_data_kwargs)
474
+ visits = pd.DataFrame(visits_recarray)
475
+ else:
476
+ visits = get_visits(opsim_rp, stackers=stackers)
477
+
478
+ LOGGER.debug(f"Loaded {len(visits)} from {opsim_rp}")
479
+ on_requested_dates = (first_day_obs <= visits["day_obs_iso8601"]) & (
480
+ visits["day_obs_iso8601"] <= last_day_obs
481
+ )
482
+ visits = visits.loc[on_requested_dates, :]
483
+ # If it dayobsiso was not requested, do not return it.
484
+ if not dayobsiso_requested:
485
+ visits.drop(columns="day_obs_iso8601", inplace=True)
486
+
487
+ return visits
488
+
489
+
490
+ def fetch_obsloctap_visits(
491
+ day_obs: str | None = None,
492
+ nights: int = 2,
493
+ telescope: str = "simonyi",
494
+ columns: Sequence[str] = (
495
+ "observationStartMJD",
496
+ "fieldRA",
497
+ "fieldDec",
498
+ "rotSkyPos",
499
+ "band",
500
+ "visitExposureTime",
501
+ "night",
502
+ "target_name",
503
+ ),
504
+ ) -> pd.DataFrame | None:
505
+ """Return visits from latest nominal prenight briefing simulation.
506
+
507
+ Parameters
508
+ ----------
509
+ day_obs : `str`
510
+ The day_obs of the night, in YYYY-MM-DD format (e.g. 2025-03-26).
511
+ Default None will use the date of the next sunset.
512
+ nights : `int`
513
+ The number of nights of observations to return.
514
+ Defaults to 2.
515
+ telescope : `str`
516
+ The telescope to get visits for: "simonyi" or "auxtel".
517
+ Defaults to "simonyi".
518
+ colums : `Sequence`
519
+ A sequence of columns from the simulation to include.
520
+
521
+ Returns
522
+ -------
523
+ visits : `pd.DataFrame`
524
+ The visits from the prenight simulation.
525
+ """
526
+ # Start with the first night that starts after the reference time,
527
+ # which is the current time by default.
528
+ # So, if the reference time is during a night, it starts with the
529
+ # following night.
530
+ night_bounds = pd.DataFrame(Almanac().sunsets)
531
+ current_mjd = Time.now().mjd
532
+ assert isinstance(current_mjd, float)
533
+ reference_mjd = (Time.now() if day_obs is None else Time(day_obs, format="iso", scale="utc")).mjd
534
+ assert isinstance(reference_mjd, float)
535
+ first_night = night_bounds.query(f"sunset > {reference_mjd}").night.min()
536
+ last_night = first_night + nights - 1
537
+
538
+ night_bounds.set_index("night", inplace=True)
539
+ start_mjd = night_bounds.loc[first_night, "sunset"]
540
+ assert isinstance(start_mjd, float)
541
+ end_mjd = night_bounds.loc[last_night, "sunrise"]
542
+ assert isinstance(end_mjd, float)
543
+
544
+ first_day_obs = Time(start_mjd - 0.5, format="mjd").iso[:10]
545
+ assert isinstance(first_day_obs, str)
546
+ last_day_obs = Time(end_mjd - 0.5, format="mjd").iso[:10]
547
+ assert isinstance(last_day_obs, str)
548
+
549
+ which_sim = {
550
+ "tags": ("ideal", "nominal"),
551
+ "telescope": telescope,
552
+ "max_simulation_age": int(np.ceil(current_mjd - reference_mjd)) + 1,
553
+ }
554
+ visits = fetch_sim_for_nights(first_day_obs, last_day_obs, which_sim=which_sim)
555
+ if visits is not None:
556
+ assert isinstance(visits, pd.DataFrame)
557
+ visits = visits.loc[:, list(columns)]
558
+
559
+ return visits
560
+
561
+
562
+ def fetch_sim_stats_for_night(
563
+ day_obs: str | int | None = None,
564
+ tags: tuple[str, ...] = ("ideal", "nominal"),
565
+ telescope: str = "simonyi",
566
+ max_simulation_age: int = 2,
567
+ host: Optional[str] = None,
568
+ user: Optional[str] = None,
569
+ database: Optional[str] = None,
570
+ schema: Optional[str] = None,
571
+ prenight_index_path: str | ResourcePath | None = None,
572
+ ) -> dict:
573
+ """Count the visits on a night in the latest nominal sim for a night.
574
+
575
+ Parameters
576
+ ----------
577
+ day_obs : `str` or 'int' or `None`
578
+ Integer dayobs (int(YYYYMMDD)) or ISO string (``YYYY-MM-DD``)
579
+ or `None` (day_obs including the evening of yesterday in local time).
580
+ tags : `tuple[str]`
581
+ A tuple of tags to filter simulations by.
582
+ Defaults to ``('ideal', 'nominal')``.
583
+ telescope : `str`
584
+ The telescope to search for (simonyi or auxtel).
585
+ Defaults to simonyi.
586
+ max_simulation_age : `int`
587
+ The maximum age of simulations to consider, in days.
588
+ Simulations older than ``max_simulation_age`` will not be considered.
589
+ Defaults to 2.
590
+ host : `str` or `None`, optional
591
+ Database host address.
592
+ user : `str` or `None`, optional
593
+ Database user name.
594
+ database : `str` or `None`, optional
595
+ Database name.
596
+ schema : `str` or None, optional
597
+ The schema in the database to use.
598
+ prenight_index_path : `str` or `ResourcePath`, optional
599
+ Root path to the bucket files (used only if the database call fails).
600
+
601
+ Returns
602
+ -------
603
+ sim_stats : `dict`
604
+ A dict with statistics for the night. Returns an empty dict when
605
+ no statistics are availble.
606
+ """
607
+ if day_obs is None:
608
+ day_obs = (date.today() - timedelta(days=1)).isoformat()
609
+ else:
610
+ day_obs = dayobs_to_date(day_obs).isoformat()
611
+
612
+ sim = find_latest_prenight_sim_for_nights(
613
+ day_obs,
614
+ tags=tags,
615
+ telescope=telescope,
616
+ max_simulation_age=max_simulation_age,
617
+ get_prenight_index_kwargs={
618
+ "host": host,
619
+ "user": user,
620
+ "database": database,
621
+ "schema": schema,
622
+ "prenight_index_path": prenight_index_path,
623
+ },
624
+ )
625
+
626
+ if "stats" in sim:
627
+ stats_maybe = sim["stats"]
628
+ else:
629
+ LOGGER.info(
630
+ f"Querying the metadata database (host {host}, user {user}, schema {schema})"
631
+ "for stats on {day_obs}"
632
+ )
633
+ vseq_metadata = VisitSequenceArchiveMetadata(
634
+ metadata_db_kwargs={"host": host, "user": user, "database": database}, metadata_db_schema=schema
635
+ )
636
+ sims_with_stats = vseq_metadata.sims_on_night_with_stats(
637
+ day_obs, tags=tags, telescope=telescope, max_simulation_age=max_simulation_age
638
+ ).set_index("visitseq_uuid")
639
+ try:
640
+ stats_maybe = sims_with_stats.loc[sim["visitseq_uuid"], "stats"]
641
+ except KeyError:
642
+ stats_maybe = None
643
+ if stats_maybe is None:
644
+ LOGGER.info("Stats not found in metadata database.")
645
+
646
+ stats = cast(dict[str, Any], stats_maybe) if isinstance(stats_maybe, dict) else {}
647
+
648
+ if len(stats) == 0 and isinstance(sim["visitseq_url"], str):
649
+ # We still got nothin'.
650
+ # Get the visits themselves and compute the stats
651
+ # from them.
652
+ LOGGER.info("Computing stats from visits.")
653
+ visits = get_visits(sim["visitseq_url"])
654
+ columns = [c for c in visits.columns if is_numeric_dtype(visits[c])]
655
+ stats_df = compute_nightly_stats(visits, columns=tuple(columns))
656
+ stats_maybe = stats_df.set_index("value_name").to_dict(orient="index")
657
+ stats = cast(dict[str, Any], stats_maybe) if isinstance(stats_maybe, dict) else {}
658
+
659
+ # We need to report number of nominal visits.
660
+ # This is not a directly recorded statistic,
661
+ # but every statistic records the number of
662
+ # values from which its statitics were calculated,
663
+ # which is what we want.
664
+ # Go through the values we have, and get the first
665
+ # with counts reported.
666
+ if "nominal_visits" not in stats:
667
+ for value_name in stats.keys():
668
+ value_stats = stats[value_name]
669
+ if isinstance(value_stats, dict):
670
+ if "count" in stats[value_name]:
671
+ stats["nominal_visits"] = stats[value_name]["count"]
672
+ break
673
+
674
+ # If we still do not have a count of visits, get
675
+ # the actual visits and count them
676
+ if isinstance(sim["visitseq_url"], str):
677
+ visits = get_visits(sim["visitseq_url"])
678
+ stats["nominal_visits"] = len(visits)
679
+
680
+ return stats