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,1029 @@
1
+ """Tools for maintaining the prototype archive of opsim output and metadata."""
2
+
3
+ __all__ = [
4
+ "make_sim_archive_dir",
5
+ "transfer_archive_dir",
6
+ "check_opsim_archive_resource",
7
+ "read_archived_sim_metadata",
8
+ "make_sim_archive_cli",
9
+ "compile_sim_metadata",
10
+ "read_sim_metadata_from_hdf",
11
+ "verify_compiled_sim_metadata",
12
+ "compile_sim_archive_metadata_cli",
13
+ "find_latest_prenight_sim_for_nights",
14
+ "fetch_sim_for_nights",
15
+ "fetch_obsloctap_visits",
16
+ "fetch_sim_stats_for_night",
17
+ "export_sim_to_prototype_sim_archive",
18
+ ]
19
+
20
+ import argparse
21
+ import datetime
22
+ import hashlib
23
+ import logging
24
+ import os
25
+ from pathlib import Path
26
+ from tempfile import TemporaryDirectory
27
+ from typing import Any
28
+ from urllib.parse import urlparse
29
+ from uuid import UUID
30
+
31
+ import numpy as np
32
+ import pandas as pd
33
+ import yaml
34
+ from astropy.time import Time
35
+ from lsst.resources import ResourcePath
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.utils.opsim_utils import get_sim_data
41
+ from rubin_sim.sim_archive import vseqarchive
42
+
43
+ from .sim_archive import make_sim_data_dir as make_sim_archive_dir
44
+
45
+ LOGGER = logging.getLogger(__name__)
46
+
47
+ try:
48
+ from lsst.resources import ResourcePath
49
+ except ModuleNotFoundError:
50
+ LOGGER.error("Module lsst.resources required to use rubin_sim.sim_archive.")
51
+
52
+
53
+ def _next_sim_date_and_index(archive_base_uri: str) -> tuple:
54
+ insert_date = datetime.datetime.now(datetime.UTC).date().isoformat()
55
+ insert_date_rpath = ResourcePath(archive_base_uri).join(insert_date, forceDirectory=True)
56
+ if not insert_date_rpath.exists():
57
+ insert_date_rpath.mkdir()
58
+ LOGGER.debug(f"Created {insert_date_rpath}.")
59
+
60
+ # Number the sims in the insert date dir by
61
+ # looing for all the interger directories, and choosing the next one.
62
+ found_ids = []
63
+ for base_dir, found_dirs, found_files in insert_date_rpath.walk():
64
+ if base_dir == insert_date_rpath:
65
+ for found_dir in found_dirs:
66
+ try:
67
+ found_dir_index = found_dir[:-1] if found_dir.endswith("/") else found_dir
68
+ found_ids.append(int(found_dir_index))
69
+ except ValueError:
70
+ pass
71
+
72
+ new_id = max(found_ids) + 1 if len(found_ids) > 0 else 1
73
+ resource_rpath = insert_date_rpath.join(f"{new_id}", forceDirectory=True)
74
+ resource_rpath.mkdir()
75
+ LOGGER.debug(f"Created {resource_rpath}.")
76
+ return insert_date, new_id, resource_rpath
77
+
78
+
79
+ def transfer_archive_dir(
80
+ archive_dir: str, archive_base_uri: str = "s3://rubin:rubin-scheduler-prenight/opsim/"
81
+ ) -> ResourcePath:
82
+ """Transfer the contents of an archive directory to an resource.
83
+
84
+ Parameters
85
+ ----------
86
+ archive_dir : `str`
87
+ The path to the archive directory containing the files to be
88
+ transferred.
89
+ archive_base_uri : `str`, optional
90
+ The base URI where the archive files will be transferred to.
91
+ Default is "s3://rubin:rubin-scheduler-prenight/opsim/".
92
+
93
+ Returns
94
+ -------
95
+ resource_rpath : `ResourcePath`
96
+ The destination resource.
97
+ """
98
+
99
+ LOGGER.debug(f"Beginning copy of {archive_dir} to {archive_base_uri}.")
100
+ metadata_fname = Path(archive_dir).joinpath("sim_metadata.yaml")
101
+ with open(metadata_fname, "r") as metadata_io:
102
+ sim_metadata = yaml.safe_load(metadata_io)
103
+ LOGGER.debug(f"Completed read of {archive_dir}.")
104
+
105
+ insert_date, new_id, resource_rpath = _next_sim_date_and_index(archive_base_uri)
106
+
107
+ # Include the metadata file itself.
108
+ sim_metadata["files"]["metadata"] = {"name": "sim_metadata.yaml"}
109
+
110
+ for file_info in sim_metadata["files"].values():
111
+ source_fname = Path(archive_dir).joinpath(file_info["name"])
112
+ with open(source_fname, "rb") as source_io:
113
+ content = source_io.read()
114
+ LOGGER.debug(f"Read {source_fname}.")
115
+
116
+ destination_rpath = resource_rpath.join(file_info["name"])
117
+ destination_rpath.write(content)
118
+
119
+ LOGGER.info(f"Copied {source_fname} to {destination_rpath}")
120
+
121
+ return resource_rpath
122
+
123
+
124
+ def check_opsim_archive_resource(archive_uri: str) -> dict:
125
+ """Check the contents of an opsim archive resource.
126
+
127
+ Parameters
128
+ ----------
129
+ archive_uri : `str`
130
+ The URI of the archive resource to be checked.
131
+
132
+ Returns
133
+ -------
134
+ validity: `dict`
135
+ A dictionary of files checked, and their validity.
136
+ """
137
+ LOGGER.debug(f"Starting to check file hashes in opsim sim archive {archive_uri}.")
138
+ metadata_path = ResourcePath(archive_uri).join("sim_metadata.yaml")
139
+ with metadata_path.open(mode="r") as metadata_io:
140
+ sim_metadata = yaml.safe_load(metadata_io)
141
+ LOGGER.debug(f"Read sim metadata from {metadata_path}.)")
142
+
143
+ results = {}
144
+
145
+ for file_info in sim_metadata["files"].values():
146
+ resource_path = ResourcePath(archive_uri).join(file_info["name"])
147
+ LOGGER.info(f"Reading {resource_path}.")
148
+ content = resource_path.read()
149
+
150
+ results[file_info["name"]] = file_info["md5"] == hashlib.md5(content).hexdigest()
151
+ if results[file_info["name"]]:
152
+ LOGGER.debug(f"{resource_path} checked and found to match recorded md5.")
153
+ else:
154
+ LOGGER.debug(f"{resource_path} has an md5 that differs from the recorded md5!")
155
+
156
+ return results
157
+
158
+
159
+ def _build_archived_sim_label(base_uri: str, metadata_resource: ResourcePath, metadata: dict) -> str:
160
+ label_base = metadata_resource.dirname().geturl().removeprefix(base_uri).rstrip("/").lstrip("/")
161
+
162
+ # If a label is supplied by the metadata, use it
163
+ if "label" in metadata:
164
+ label = f"{label_base} {metadata['label']}"
165
+ return label
166
+
167
+ try:
168
+ sim_dates = metadata["simulated_dates"]
169
+ first_date = sim_dates["first"]
170
+ last_date = sim_dates["last"]
171
+ label = f"{label_base} of {first_date}"
172
+ if last_date != first_date:
173
+ label = f"{label} through {last_date}"
174
+ except KeyError:
175
+ label = label_base
176
+
177
+ if "scheduler_version" in metadata:
178
+ label = f"{label} with {metadata['scheduler_version']}"
179
+
180
+ return label
181
+
182
+
183
+ def read_archived_sim_metadata(
184
+ base_uri: str,
185
+ latest: str = None,
186
+ num_nights: int = 5,
187
+ compilation_resource: ResourcePath | str | None = None,
188
+ verify_compilation: bool = False,
189
+ ) -> dict:
190
+ """Read metadata for a time range of archived opsim output.
191
+
192
+ Parameters
193
+ ----------
194
+ base_uri : `str`
195
+ The base URI of the archive resource to be checked.
196
+ latest : `str`, optional
197
+ The date of the latest simulation whose metadata should be loaded.
198
+ This is the date on which the simulations was added to the archive,
199
+ not necessarily the date on which the simulation was run, or any
200
+ of the dates simulated.
201
+ Default is today.
202
+ num_nights : `int`
203
+ The number of nights of the date window to load.
204
+ compilation_resource : `ResourcePath` or `str` or `None`
205
+ The ResourcePath to an hdf5 compilation of metadata.
206
+ verify_compilation : `bool`
207
+ Verify that metadata in compilation corresponds to an existing
208
+ resource, and the all existing resources have metadata. Defaults
209
+ to False, which will work provided that the compilation is complete
210
+ and correct up to the date of the last simulation it includes.
211
+
212
+ Returns
213
+ -------
214
+ sim_metadata: `dict`
215
+ A dictionary of metadata for simulations in the date range.
216
+ """
217
+ latest_mjd = int(Time.now().mjd if latest is None else Time(latest).mjd)
218
+ earliest_mjd = int(latest_mjd - (num_nights - 1))
219
+ LOGGER.debug(
220
+ f"Looking for simulation metadata with MJD between {earliest_mjd} and {latest_mjd} in {base_uri}."
221
+ )
222
+
223
+ compilation = {}
224
+ compiled_uris_by_date = {}
225
+ max_compiled_date = "1900-01-01"
226
+ if compilation_resource is not None:
227
+ LOGGER.debug(f"Reading metadata cache {compilation_resource}.")
228
+ try:
229
+ compilation.update(read_sim_metadata_from_hdf(compilation_resource))
230
+ for uri in compilation:
231
+ iso_date = Path(urlparse(uri).path).parts[-2]
232
+ if iso_date not in compiled_uris_by_date:
233
+ compiled_uris_by_date[iso_date] = []
234
+ compiled_uris_by_date[iso_date].append(uri)
235
+ max_compiled_date = max(max_compiled_date, iso_date)
236
+ LOGGER.debug(f"Maximum simulation execution date in metadata cache: {max_compiled_date}")
237
+ except FileNotFoundError:
238
+ LOGGER.warning(f"No metadata cache {compilation_resource}, not using cache.")
239
+ pass
240
+
241
+ all_metadata = {}
242
+ for mjd in range(earliest_mjd, latest_mjd + 1):
243
+ iso_date = Time(mjd, format="mjd").iso[:10]
244
+
245
+ # Make the comparison >=, rather than >, so
246
+ # it won't miss sims in which the compilation does not complete
247
+ # the final date.
248
+ if verify_compilation or (iso_date >= max_compiled_date):
249
+ date_resource = ResourcePath(base_uri).join(iso_date, forceDirectory=True)
250
+ if date_resource.exists():
251
+ for base_dir, found_dirs, found_files in date_resource.walk(
252
+ file_filter=r".*sim_metadata.yaml"
253
+ ):
254
+ for found_file in found_files:
255
+ found_resource = ResourcePath(base_dir).join(found_file)
256
+ LOGGER.debug(f"Found {found_resource}")
257
+ sim_uri = str(found_resource.dirname())
258
+ if sim_uri in compilation:
259
+ LOGGER.debug(f"Not reading {found_resource}, already in the read compliation.")
260
+ these_metadata = compilation[sim_uri]
261
+ else:
262
+ LOGGER.debug(f"Reading {found_resource} (absent from compilation).")
263
+ these_metadata = yaml.safe_load(found_resource.read().decode("utf-8"))
264
+ these_metadata["label"] = _build_archived_sim_label(
265
+ base_uri, found_resource, these_metadata
266
+ )
267
+ LOGGER.debug(f"Read successfully: {found_resource}")
268
+ if iso_date < max_compiled_date:
269
+ LOGGER.error(
270
+ f"Simulation at {sim_uri} expected but not found in compilation."
271
+ )
272
+ all_metadata[sim_uri] = these_metadata
273
+ else:
274
+ LOGGER.debug(f"No simulations found with generation date of {iso_date}")
275
+ else:
276
+ if iso_date in compiled_uris_by_date:
277
+ for sim_uri in compiled_uris_by_date[iso_date]:
278
+ all_metadata[sim_uri] = compilation[sim_uri]
279
+
280
+ if verify_compilation:
281
+ if iso_date in compiled_uris_by_date:
282
+ for sim_uri in compiled_uris_by_date[iso_date]:
283
+ if sim_uri not in all_metadata:
284
+ message = f"Simulation at {sim_uri} in compiled metadata but not archive."
285
+ print(message)
286
+ LOGGER.error(message)
287
+ else:
288
+ LOGGER.debug(
289
+ f"Date {iso_date} not expected to be in the metadata compilation, not checking for it."
290
+ )
291
+
292
+ if len(all_metadata) == 0:
293
+ earliest_iso = Time(earliest_mjd, format="mjd").iso[:10]
294
+ latest_iso = Time(latest_mjd, format="mjd").iso[:10]
295
+ LOGGER.info(f"No simulations run between {earliest_iso} through {latest_iso} found in {base_uri}")
296
+
297
+ return all_metadata
298
+
299
+
300
+ def make_sim_archive_cli(*args: Any) -> str:
301
+ parser = argparse.ArgumentParser(description="Add files to sim archive")
302
+ parser.add_argument(
303
+ "label",
304
+ type=str,
305
+ help="A label for the simulation.",
306
+ )
307
+ parser.add_argument(
308
+ "opsim",
309
+ type=str,
310
+ help="File name of opsim database.",
311
+ )
312
+ parser.add_argument("--rewards", type=str, default=None, help="A rewards HDF5 file.")
313
+ parser.add_argument(
314
+ "--scheduler_version",
315
+ type=str,
316
+ default=None,
317
+ help="The version of the scheduler that producte the opsim database.",
318
+ )
319
+ parser.add_argument(
320
+ "--scheduler",
321
+ type=str,
322
+ default=None,
323
+ help="A snapshot of the scheduler used to produce the database, at the start of the simulation.",
324
+ )
325
+ parser.add_argument(
326
+ "--script", type=str, default=None, help="The file name of the script run to create the simulation."
327
+ )
328
+
329
+ notebook_help = "The file name of the notebook run to create the simulation."
330
+ notebook_help = notebook_help + " This can be produced using the %%notebook magic."
331
+ parser.add_argument(
332
+ "--notebook",
333
+ type=str,
334
+ default=None,
335
+ help=notebook_help,
336
+ )
337
+ parser.add_argument(
338
+ "--archive_base_uri",
339
+ type=str,
340
+ default="s3://rubin:rubin-scheduler-prenight/opsim/",
341
+ help="Base URI for the archive",
342
+ )
343
+ parser.add_argument("--tags", type=str, default=[], nargs="*", help="The tags on the simulation.")
344
+ parser.add_argument("--telescope", type=str, default="simonyi", help="The telescope simulated.")
345
+ arg_values = parser.parse_args() if len(args) == 0 else parser.parse_args(args)
346
+
347
+ observations = SchemaConverter().opsim2obs(arg_values.opsim)
348
+
349
+ if arg_values.rewards is not None:
350
+ try:
351
+ reward_df = pd.read_hdf(arg_values.rewards, "reward_df")
352
+ except KeyError:
353
+ reward_df = None
354
+
355
+ try:
356
+ obs_rewards = pd.read_hdf(arg_values.rewards, "obs_rewards")
357
+ except KeyError:
358
+ obs_rewards = None
359
+ else:
360
+ reward_df = None
361
+ obs_rewards = None
362
+
363
+ filename_args = ["scheduler", "script", "notebook"]
364
+ in_files = {}
365
+ for filename_arg in filename_args:
366
+ try:
367
+ filename = getattr(arg_values, filename_arg)
368
+ if filename is not None:
369
+ in_files[filename] = filename
370
+ except AttributeError:
371
+ pass
372
+
373
+ data_path = make_sim_archive_dir(
374
+ observations,
375
+ reward_df,
376
+ obs_rewards,
377
+ in_files,
378
+ tags=arg_values.tags,
379
+ label=arg_values.label,
380
+ opsim_metadata={"telescope": arg_values.telescope},
381
+ )
382
+ LOGGER.info(f"Created simulation archived directory: {data_path.name}")
383
+
384
+ sim_archive_uri = transfer_archive_dir(data_path.name, arg_values.archive_base_uri)
385
+ LOGGER.info(f"Transferred {data_path} to {sim_archive_uri}")
386
+
387
+ return sim_archive_uri
388
+
389
+
390
+ def compile_sim_metadata(
391
+ archive_uri: str, compilation_resource: str | ResourcePath, num_nights: int = 10000, append: bool = False
392
+ ) -> ResourcePath:
393
+ """Read sim archive metadata and export it to tables in an hdf5 files.
394
+
395
+ Parameters
396
+ ----------
397
+ archive_uri : `str`
398
+ URI of the sim archive from which to read metadata.
399
+ compilation_resource : `str` or `ResourcePath`
400
+ Resource for hdf5 file to be written to
401
+ num_nights : `int`, optional
402
+ Number of nights to include, by default 10000
403
+ append : `bool`, optional
404
+ Do not rebuild the whole compilation, but instead reread what is
405
+ there already, and add new metadata after the last date already
406
+ include. Defaults to False.
407
+
408
+ Returns
409
+ -------
410
+ compilation_fname : `ResourcePath`
411
+ The resource to which the hdf5 file was written.
412
+ """
413
+ LOGGER.debug("Starting compile_sim_metadata.")
414
+
415
+ if append:
416
+ sim_metadata = read_archived_sim_metadata(
417
+ archive_uri, num_nights=num_nights, compilation_resource=compilation_resource
418
+ )
419
+ else:
420
+ sim_metadata = read_archived_sim_metadata(archive_uri, num_nights=num_nights)
421
+
422
+ sim_rows = []
423
+ file_rows = []
424
+ sim_runner_kwargs = []
425
+ tags = []
426
+ for uri, metadata in list(sim_metadata.items()):
427
+ sim_row = {"sim_uri": uri}
428
+ for key, value in metadata.items():
429
+ match key:
430
+ case "files":
431
+ for file_type, file_metadata in value.items():
432
+ this_file = {"sim_uri": uri, "file_type": file_type} | file_metadata
433
+ file_rows.append(this_file)
434
+ case "sim_runner_kwargs":
435
+ these_args = {"sim_uri": uri} | value
436
+ sim_runner_kwargs.append(these_args)
437
+ case "tags":
438
+ for tag in value:
439
+ tags.append({"sim_uri": uri, "tag": tag})
440
+ case "simulated_dates":
441
+ sim_row["first_simulated_date"] = value["start"] if "start" in value else value["first"]
442
+ sim_row["last_simulated_date"] = value["end"] if "end" in value else value["last"]
443
+ case _:
444
+ sim_row[key] = value
445
+ sim_rows.append(sim_row)
446
+
447
+ sim_df = pd.DataFrame(sim_rows).set_index("sim_uri")
448
+ file_df = pd.DataFrame(file_rows).set_index("sim_uri")
449
+ sim_runner_kwargs_df = pd.DataFrame(sim_runner_kwargs).set_index("sim_uri")
450
+ tags_df = pd.DataFrame(tags).set_index("sim_uri")
451
+
452
+ with TemporaryDirectory() as local_data_dir:
453
+ local_hdf_fname = Path(local_data_dir).joinpath("scheduler.pickle.xz")
454
+ sim_df.to_hdf(local_hdf_fname, key="simulations", format="table")
455
+ file_df.to_hdf(local_hdf_fname, key="files", format="table")
456
+ sim_runner_kwargs_df.to_hdf(local_hdf_fname, key="kwargs", format="table")
457
+ tags_df.to_hdf(local_hdf_fname, key="tags", format="table")
458
+
459
+ with open(local_hdf_fname, "rb") as local_hdf_io:
460
+ hdf_bytes = local_hdf_io.read()
461
+
462
+ if isinstance(compilation_resource, str):
463
+ compilation_resource = ResourcePath(compilation_resource)
464
+
465
+ assert isinstance(compilation_resource, ResourcePath)
466
+ LOGGER.info(f"Writing metadata compilation to {compilation_resource}")
467
+ compilation_resource.write(hdf_bytes)
468
+
469
+ return compilation_resource
470
+
471
+
472
+ def read_sim_metadata_from_hdf(compilation_resource: str | ResourcePath) -> dict:
473
+ """Read sim archive metadata from an hdf5 file.
474
+ Return a dict as if it were generated by read_archived_sim_metadata.
475
+
476
+ Parameters
477
+ ----------
478
+ compilation_fname : `str` or `ResourcePath`
479
+ The source of the hdf5 data to read.
480
+
481
+ Returns
482
+ -------
483
+ sim_archive_metadata: `dict`
484
+ A nested dictionary with the simulation metadata.
485
+ """
486
+
487
+ if isinstance(compilation_resource, str):
488
+ compilation_resource = ResourcePath(compilation_resource)
489
+ assert isinstance(compilation_resource, ResourcePath)
490
+
491
+ with compilation_resource.as_local() as local_compilation_resource:
492
+ compilation_fname: str = local_compilation_resource.ospath
493
+ LOGGER.debug(f"{compilation_resource} copied to {compilation_fname}.")
494
+ sim_df = pd.read_hdf(compilation_fname, "simulations")
495
+ file_df = pd.read_hdf(compilation_fname, "files")
496
+ sim_runner_kwargs_df = pd.read_hdf(compilation_fname, "kwargs")
497
+ tags_df = pd.read_hdf(compilation_fname, "tags")
498
+
499
+ def make_file_dict(g: pd.Series | pd.DataFrame) -> dict:
500
+ if isinstance(g, pd.Series):
501
+ g = g.to_frame().T
502
+
503
+ return g.set_index("file_type").T.to_dict()
504
+
505
+ sim_df["files"] = file_df.groupby("sim_uri").apply(make_file_dict)
506
+
507
+ def make_kwargs_dict(g):
508
+ sim_kwargs = g.to_dict(orient="records")[0]
509
+ # Keyword args that are not set get recorded as nans.
510
+ # Do not include them in the dictionary.
511
+ nan_keys = []
512
+ for key in sim_kwargs:
513
+ try:
514
+ if np.isnan(sim_kwargs[key]):
515
+ nan_keys.append(key)
516
+ except TypeError:
517
+ pass
518
+
519
+ for key in nan_keys:
520
+ del sim_kwargs[key]
521
+
522
+ return sim_kwargs
523
+
524
+ sim_df["sim_runner_kwargs"] = sim_runner_kwargs_df.groupby("sim_uri").apply(make_kwargs_dict)
525
+
526
+ sim_df["tags"] = tags_df.groupby("sim_uri").agg({"tag": list})
527
+
528
+ sim_metadata = sim_df.to_dict(orient="index")
529
+
530
+ for sim_uri in sim_metadata:
531
+ # Return begin and end date to their nested dict format.
532
+ sim_metadata[sim_uri]["simulated_dates"] = {
533
+ "first": sim_metadata[sim_uri]["first_simulated_date"],
534
+ "last": sim_metadata[sim_uri]["last_simulated_date"],
535
+ }
536
+ del sim_metadata[sim_uri]["first_simulated_date"]
537
+ del sim_metadata[sim_uri]["last_simulated_date"]
538
+
539
+ # Unset keys show up as nans.
540
+ # Do not put them in the resultant dict.
541
+ nan_keys = []
542
+ for key in sim_metadata[sim_uri]:
543
+ try:
544
+ if np.isnan(sim_metadata[sim_uri][key]):
545
+ nan_keys.append(key)
546
+ except TypeError:
547
+ pass
548
+
549
+ for key in nan_keys:
550
+ del sim_metadata[sim_uri][key]
551
+
552
+ return sim_metadata
553
+
554
+
555
+ def verify_compiled_sim_metadata(
556
+ archive_uri: str, compilation_resource: str | ResourcePath, num_nights: int = 10000
557
+ ) -> list[dict]:
558
+ """Verify that a compilation of sim archive metadata matches directaly
559
+ read metadata.
560
+
561
+ Parameters
562
+ ----------
563
+ archive_uri : `str`
564
+ Archive from which to directly read metadata.
565
+ compilation_resource : `str` or `ResourcePath`
566
+ Resource for the metadata compilation
567
+ num_nights : `int`, optional
568
+ number of nights to check, by default 10000
569
+
570
+ Returns
571
+ -------
572
+ differences : `list[dict]`
573
+ A list of dicts describing differences. If they match, it will return
574
+ and empty list.
575
+ """
576
+
577
+ direct_sim_metadata = read_archived_sim_metadata(archive_uri, num_nights=num_nights)
578
+
579
+ try:
580
+ # One old sim uses a couple of non-standard keywords, so update them.
581
+ simulated_dates = direct_sim_metadata["s3://rubin:rubin-scheduler-prenight/opsim/2023-12-15/1/"][
582
+ "simulated_dates"
583
+ ]
584
+ simulated_dates["first"] = simulated_dates["start"]
585
+ del simulated_dates["start"]
586
+ simulated_dates["last"] = simulated_dates["end"]
587
+ del simulated_dates["end"]
588
+ except KeyError:
589
+ # If the archive doesn't have this old sim, don't worry about it.
590
+ pass
591
+
592
+ compiled_sim_metadata = read_sim_metadata_from_hdf(compilation_resource)
593
+
594
+ # Test that everything in direct_sim_metadata has a corresponding matching
595
+ # entry in the compilation.
596
+ differences = []
597
+ for sim_uri in direct_sim_metadata:
598
+ for key in direct_sim_metadata[sim_uri]:
599
+ try:
600
+ if direct_sim_metadata[sim_uri][key] != compiled_sim_metadata[sim_uri][key]:
601
+ differences.append(
602
+ {
603
+ "sim_uri": sim_uri,
604
+ "key": key,
605
+ "direct_value": direct_sim_metadata[sim_uri][key],
606
+ "compiled_value": compiled_sim_metadata[sim_uri][key],
607
+ }
608
+ )
609
+ except KeyError:
610
+ differences.append(
611
+ {
612
+ "sim_uri": sim_uri,
613
+ "key": key,
614
+ "direct_value": direct_sim_metadata[sim_uri][key],
615
+ "compiled_value": "MISSING",
616
+ }
617
+ )
618
+
619
+ # Test that everything in the compilation has a corresponding matching
620
+ # entry in direct_sim_metadata.
621
+ for sim_uri in compiled_sim_metadata:
622
+ for key in compiled_sim_metadata[sim_uri]:
623
+ if sim_uri not in direct_sim_metadata:
624
+ differences.append(
625
+ {
626
+ "sim_uri": sim_uri,
627
+ "direct_value": "MISSING",
628
+ }
629
+ )
630
+ elif key not in direct_sim_metadata[sim_uri]:
631
+ differences.append(
632
+ {
633
+ "sim_uri": sim_uri,
634
+ "key": key,
635
+ "direct_value": "MISSING",
636
+ "compiled_value": compiled_sim_metadata[sim_uri][key],
637
+ }
638
+ )
639
+
640
+ return differences
641
+
642
+
643
+ def compile_sim_archive_metadata_cli(*args: Any) -> None:
644
+ parser = argparse.ArgumentParser(description="Create a metadata compilation HDF5 file at a URI")
645
+ parser.add_argument(
646
+ "--compilation_uri",
647
+ type=str,
648
+ default=None,
649
+ help="The URI of the metadata archive compilation to write. "
650
+ + "Defaults to compilation_metadate.h5 in the archive.",
651
+ )
652
+ parser.add_argument(
653
+ "--archive_base_uri",
654
+ type=str,
655
+ default="s3://rubin:rubin-scheduler-prenight/opsim/",
656
+ help="Base URI for the archive",
657
+ )
658
+ parser.add_argument(
659
+ "--append",
660
+ action="store_true",
661
+ help="Do not rebuild the whole compilation, "
662
+ + "but add new simulations with dates after the last current entry.",
663
+ )
664
+
665
+ log_file = os.environ.get("SIM_ARCHIVE_LOG_FILE", None)
666
+ if log_file is not None:
667
+ logging.basicConfig(
668
+ filename=log_file, format="%(asctime)s: %(message)s", datefmt="%Y-%m-%dT%H:%M:%S%z"
669
+ )
670
+ else:
671
+ logging.basicConfig(level=logging.INFO)
672
+
673
+ arg_values = parser.parse_args() if len(args) == 0 else parser.parse_args(args)
674
+ archive_uri = arg_values.archive_base_uri
675
+ compilation_uri = arg_values.compilation_uri
676
+ append = arg_values.append
677
+ if compilation_uri is None:
678
+ compilation_resource = ResourcePath(archive_uri).join("compiled_metadata_cache.h5")
679
+ else:
680
+ compilation_resource = ResourcePath(compilation_uri)
681
+
682
+ compilation_resource = compile_sim_metadata(archive_uri, compilation_resource, append=append)
683
+
684
+
685
+ def find_latest_prenight_sim_for_nights(
686
+ first_day_obs: str | None = None,
687
+ last_day_obs: str | None = None,
688
+ tags: tuple[str] = ("ideal", "nominal"),
689
+ telescope: str = "simonyi",
690
+ max_simulation_age: int = 2,
691
+ archive_uri: str = "s3://rubin:rubin-scheduler-prenight/opsim/",
692
+ compilation_uri: str = "s3://rubin:rubin-scheduler-prenight/opsim/compiled_metadata_cache.h5",
693
+ ) -> pd.DataFrame:
694
+ """Find the most recent prenight simulation that covers a night.
695
+
696
+ Parameters
697
+ ----------
698
+ first_day_obs : `str` or `None`
699
+ The date of the evening for the first night for which to get
700
+ a simulation. If `None`, then the current date will be used.
701
+ last_day_obs : `str` or `None`
702
+ The date of the evening for the last night for which to get
703
+ a simulation. If `None`, then the current date will be used.
704
+ tags : `tuple[str]`
705
+ A tuple of tags to filter simulations by.
706
+ Defaults to ``('ideal', 'nominal')``.
707
+ telescope : `str`
708
+ The telescope to search for (simonyi or auxtel).
709
+ Defaults to simonyi.
710
+ max_simulation_age : `int`
711
+ The maximum age of simulations to consider, in days.
712
+ Simulations older than ``max_simulation_age`` will not be considered.
713
+ Defaults to 2.
714
+ archive_uri : `str`
715
+ The URI of the archive from which to fetch the simulation.
716
+ Defaults to ``s3://rubin:rubin-scheduler-prenight/opsim/``.
717
+ compilation_uri : `str`
718
+ The URI of the compiled metadata HDF5 file for efficient querying.
719
+ Defaults to
720
+ ``s3://rubin:rubin-scheduler-prenight/opsim/compiled_metadata_cache.h5``.
721
+
722
+ Returns
723
+ -------
724
+ sim_metadata : `dict`
725
+ A dictionary with metadata for the simulation.
726
+ """
727
+
728
+ if first_day_obs is None:
729
+ first_day_obs = Time(Time.now().mjd - 0.5, format="mjd").iso[:10]
730
+ if last_day_obs is None:
731
+ last_day_obs = first_day_obs
732
+
733
+ sim_metadata = read_archived_sim_metadata(
734
+ archive_uri, num_nights=max_simulation_age, compilation_resource=compilation_uri
735
+ )
736
+ LOGGER.debug(f"Total simulations it the last {max_simulation_age} days: {len(sim_metadata)}.")
737
+
738
+ best_sim = None
739
+ for uri, sim in sim_metadata.items():
740
+ sim["uri"] = uri
741
+ sim["exec_date"] = uri.split("/")[-3]
742
+ sim["date_index"] = int(uri.split("/")[-2])
743
+
744
+ if sim["simulated_dates"]["first"] > first_day_obs:
745
+ continue
746
+ if sim["simulated_dates"]["last"] < last_day_obs:
747
+ continue
748
+ if "telescope" in sim and sim["telescope"].lower() != telescope.lower():
749
+ continue
750
+ if not set(tags).issubset(sim["tags"]):
751
+ continue
752
+ if best_sim is not None:
753
+ if sim["exec_date"] < best_sim["exec_date"]:
754
+ continue
755
+ if sim["date_index"] < best_sim["date_index"]:
756
+ continue
757
+ best_sim = sim
758
+
759
+ if best_sim is not None:
760
+ best_sim["opsim_rp"] = (
761
+ ResourcePath(archive_uri)
762
+ .join(best_sim["exec_date"], forceDirectory=True)
763
+ .join(f"{best_sim['date_index']}", forceDirectory=True)
764
+ .join(best_sim["files"]["observations"]["name"])
765
+ )
766
+ LOGGER.info(f"Most recent simulation meeting requested criteria is {best_sim['uri']}.")
767
+ else:
768
+ LOGGER.debug("No simulations met the requested criteria.")
769
+
770
+ return best_sim
771
+
772
+
773
+ def fetch_sim_for_nights(
774
+ first_day_obs: str | None = None,
775
+ last_day_obs: str | None = None,
776
+ which_sim: ResourcePath | str | dict | None = None,
777
+ get_sim_data_kwargs: dict | None = None,
778
+ ) -> pd.DataFrame | None:
779
+ """Fetches visit metadata from an opsim database for specified nights.
780
+
781
+ Parameters
782
+ ----------
783
+ first_day_obs : `str` or `None`
784
+ The date of the evening for the first night for which to get
785
+ a simulation. If `None`, then the current date will be used.
786
+ last_day_obs : `str` or `None`
787
+ The date of the evening for the last night for which to get
788
+ a simulation. If `None`, then the current date will be used.
789
+ which_sim : `ResourcePath` or `str` or `dict` on `None`
790
+ The ``resourcePath`` or URL of the opsim file from which to
791
+ load visits, or the arguments to
792
+ ``find_latest_prenight_sim_for_nights``
793
+ to use to determine which simulation to load. ``None`` uses
794
+ default arguments to ``find_latest_prenight_sim_for_nights``.
795
+ Defaults to ``None``.
796
+ git_sim_data_kwargs : `dict`
797
+ Additional arguments to ``get_sim_data`` to use to load
798
+ the visits.
799
+
800
+ Returns
801
+ -------
802
+ visits : `pd.DataFrame`
803
+ A pandas DataFrame containing visit parameters.
804
+ """
805
+
806
+ if first_day_obs is None:
807
+ first_day_obs = Time(Time.now().mjd - 0.5, format="mjd").iso[:10]
808
+ if last_day_obs is None:
809
+ last_day_obs = first_day_obs
810
+
811
+ match which_sim:
812
+ case ResourcePath():
813
+ opsim_rp = which_sim
814
+ case str():
815
+ opsim_rp = ResourcePath(which_sim)
816
+ case dict():
817
+ opsim_rp = find_latest_prenight_sim_for_nights(first_day_obs, last_day_obs, **which_sim)[
818
+ "opsim_rp"
819
+ ]
820
+ case None:
821
+ opsim_rp = find_latest_prenight_sim_for_nights(first_day_obs, last_day_obs)["opsim_rp"]
822
+ case _:
823
+ raise NotImplementedError()
824
+
825
+ assert isinstance(opsim_rp, ResourcePath)
826
+
827
+ if get_sim_data_kwargs is None:
828
+ get_sim_data_kwargs = {}
829
+ assert isinstance(get_sim_data_kwargs, dict)
830
+
831
+ # Limit visits returned to the nights we requested
832
+ if "stackers" not in get_sim_data_kwargs:
833
+ get_sim_data_kwargs["stackers"] = []
834
+ dayobsiso_requested = maf.DayObsISOStacker in [s.__class__ for s in get_sim_data_kwargs["stackers"]]
835
+ if not dayobsiso_requested:
836
+ # We want it to filter out dates that were not requested,
837
+ # so add it to the stacker even if it was not requested.
838
+ get_sim_data_kwargs["stackers"].append(maf.DayObsISOStacker())
839
+
840
+ visits = get_sim_data(opsim_rp, **get_sim_data_kwargs)
841
+
842
+ LOGGER.debug(f"Loaded {len(visits)} from {opsim_rp}")
843
+ visits_df = pd.DataFrame(visits)
844
+ visits_df = visits_df.loc[
845
+ (first_day_obs <= visits_df["day_obs_iso8601"]) & (visits_df["day_obs_iso8601"] <= last_day_obs),
846
+ :,
847
+ ]
848
+ # If it dayobsiso was not requested, do not return it.
849
+ if not dayobsiso_requested:
850
+ visits_df.drop(columns="day_obs_iso8601", inplace=True)
851
+
852
+ return visits_df
853
+
854
+
855
+ def fetch_obsloctap_visits(
856
+ day_obs: str | None = None, nights: int = 2, telescope: str = "simonyi"
857
+ ) -> pd.DataFrame:
858
+ """Return visits from latest nominal prenight briefing simulation.
859
+
860
+ Parameters
861
+ ----------
862
+ day_obs : `str`
863
+ The day_obs of the night, in YYYY-MM-DD format (e.g. 2025-03-26).
864
+ Default None will use the date of the next sunset.
865
+ nights : `int`
866
+ The number of nights of observations to return.
867
+ Defaults to 2.
868
+ telescope : `str`
869
+ The telescope to get visits for: "simonyi" or "auxtel".
870
+ Defaults to "simonyi".
871
+
872
+ Returns
873
+ -------
874
+ visits : `pd.DataFrame`
875
+ The visits from the prenight simulation.
876
+ """
877
+ dbcols = [
878
+ "observationStartMJD",
879
+ "fieldRA",
880
+ "fieldDec",
881
+ "rotSkyPos",
882
+ "band",
883
+ "visitExposureTime",
884
+ "night",
885
+ "target_name",
886
+ ]
887
+
888
+ # Start with the first night that starts after the reference time,
889
+ # which is the current time by default.
890
+ # So, if the reference time is during a night, it starts with the
891
+ # following night.
892
+ night_bounds = pd.DataFrame(Almanac().sunsets)
893
+ reference_time = Time.now() if day_obs is None else Time(day_obs, format="iso", scale="utc")
894
+ first_night = night_bounds.query(f"sunset > {reference_time.mjd}").night.min()
895
+ last_night = first_night + nights - 1
896
+
897
+ night_bounds.set_index("night", inplace=True)
898
+ start_mjd = night_bounds.loc[first_night, "sunset"]
899
+ end_mjd = night_bounds.loc[last_night, "sunrise"]
900
+
901
+ first_day_obs = Time(start_mjd - 0.5, format="mjd").iso[:10]
902
+ last_day_obs = Time(end_mjd - 0.5, format="mjd").iso[:10]
903
+
904
+ which_sim = {
905
+ "tags": ("ideal", "nominal"),
906
+ "telescope": telescope,
907
+ "max_simulation_age": int(np.ceil(Time.now().mjd - reference_time.mjd)) + 1,
908
+ }
909
+ get_sim_data_kwargs = {
910
+ "sqlconstraint": (f"observationStartMJD BETWEEN {start_mjd} AND {end_mjd}",),
911
+ "dbcols": dbcols,
912
+ }
913
+ visits = fetch_sim_for_nights(
914
+ first_day_obs, last_day_obs, which_sim=which_sim, get_sim_data_kwargs=get_sim_data_kwargs
915
+ )
916
+
917
+ return visits
918
+
919
+
920
+ def fetch_sim_stats_for_night(day_obs: str | int | None = None) -> dict:
921
+ """Count the visits on a night in the latest nominal sim for a night.
922
+
923
+ Parameters
924
+ ----------
925
+ day_obs : `str` or 'int' or `None`
926
+ Date (in UTC-12hrs timezone) for which to get the count of visits,
927
+ in ISO8601 (YYYY-MM-DD as a string) or int dayobs (int(YYYYMMDD))
928
+ or `None` (day_obs including the evening of yesterday in local time).
929
+
930
+ Returns
931
+ -------
932
+ sim_stats : `dict`
933
+ A dict with statistics for the night.
934
+ Presently, it has one key: `nominal_visits`, the number of visits
935
+ in the latest nominal simulation.
936
+ """
937
+
938
+ # Maybe we should move schedview.DayObs into rubin_sim so we can use it
939
+ # here without introducing a schedview dependency.
940
+ match day_obs:
941
+ case str():
942
+ pass
943
+ case int():
944
+ day_obs = datetime.datetime.strptime(str(day_obs), "%Y%m%d").date().isoformat()
945
+ case None:
946
+ day_obs = (datetime.date.today() - datetime.timedelta(days=1)).isoformat()
947
+ case _:
948
+ raise NotImplementedError(f"Cannot get day_obs from a {type(day_obs)}.")
949
+
950
+ assert isinstance(day_obs, str)
951
+
952
+ # Initialize the dictionary with the night stats
953
+ night_sim_stats = {}
954
+
955
+ # Count the visits in the latest simulated nominal night
956
+
957
+ # Go far enough back to get the automatic pre-nights for the night
958
+ max_simulation_age = int(np.ceil(Time.now().mjd - Time(day_obs).mjd)) + 3
959
+
960
+ visits = fetch_sim_for_nights(
961
+ first_day_obs=day_obs,
962
+ last_day_obs=day_obs,
963
+ which_sim={"max_simulation_age": max_simulation_age},
964
+ )
965
+ night_sim_stats["nominal_visits"] = len(visits) if isinstance(visits, pd.DataFrame) else 0
966
+
967
+ # We can add whatever additional stats we like here. Possibilities include
968
+ # statistics from multiple simulations, counts for different bands in the
969
+ # nominal simulation, numbers of DDF/WFD/ToO visits, etc.
970
+
971
+ return night_sim_stats
972
+
973
+
974
+ def export_sim_to_prototype_sim_archive(
975
+ archive_metadata: vseqarchive.VisitSequenceArchiveMetadata, sim_uuid: UUID, proto_sim_archive_url: str
976
+ ) -> ResourcePath:
977
+ """Export a simulation to the prototype simulation archive.
978
+
979
+ Parameters
980
+ ----------
981
+ archive_metadata : `vseqarchive.VisitSequenceArchiveMetadata`
982
+ Interface to the visit‑sequence metadata database from
983
+ which the simulation is to be imported.
984
+ sim_uuid : `uuid.UUID`
985
+ UUID of the simulation to be exported.
986
+ proto_sim_archive_url : ``str``
987
+ Base URL of the prototype simulation archive to which
988
+ the simulations is to be exported.
989
+
990
+ Returns
991
+ -------
992
+ sim_rp: `lsst.resources.ResourcePath`
993
+ ResourcePath pointing to the root of the newly created
994
+ prototype archive entry.
995
+ """
996
+
997
+ insert_date, new_id, proto_sim_rpath = _next_sim_date_and_index(proto_sim_archive_url)
998
+ metadata = yaml.safe_load(archive_metadata.sim_metadata_yaml(sim_uuid))
999
+
1000
+ have_opsim = False
1001
+ for file_type in metadata["files"]:
1002
+ if file_type == "observations":
1003
+ have_opsim = True
1004
+
1005
+ origin = ResourcePath(metadata["files"][file_type]["url"])
1006
+ destination = proto_sim_rpath.join(origin.basename())
1007
+ destination.transfer_from(origin, "copy")
1008
+ metadata["files"][file_type]["url"] = destination.geturl()
1009
+
1010
+ if not have_opsim:
1011
+ visits_url = archive_metadata.get_visitseq_url(sim_uuid)
1012
+ visits_rp = ResourcePath(visits_url)
1013
+ with visits_rp.as_local() as visits_local_rp:
1014
+ with TemporaryDirectory() as temp_dir:
1015
+ opsimdb_fname = str(Path(temp_dir) / "opsim.db")
1016
+ vseqarchive.hdf5_to_opsimdb(visits_local_rp.ospath, opsimdb_fname)
1017
+ opsimdb_rp = proto_sim_rpath.join("opsim.db")
1018
+ opsimdb_rp.transfer_from(ResourcePath(opsimdb_fname), "copy")
1019
+ metadata["files"]["observations"] = {"name": "opsim.db", "url": opsimdb_rp.geturl()}
1020
+
1021
+ metadata["files"]["metadata"] = {"name": "sim_metadata.yaml"}
1022
+ metadata["files"]["metadata"]["url"] = proto_sim_rpath.join(
1023
+ metadata["files"]["metadata"]["name"]
1024
+ ).geturl()
1025
+
1026
+ metadata_yaml = yaml.dump(metadata)
1027
+ ResourcePath(metadata["files"]["metadata"]["url"]).write(metadata_yaml.encode("utf-8"))
1028
+
1029
+ return proto_sim_rpath