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,255 @@
1
+ __all__ = [
2
+ "get_scheduler",
3
+ "save_scheduler",
4
+ "add_make_scheduler_snapshot_args",
5
+ "make_scheduler_snapshot_cli",
6
+ "get_scheduler_from_config",
7
+ "get_scheduler_instance_from_repo",
8
+ ]
9
+
10
+ import argparse
11
+ import bz2
12
+ import gzip
13
+ import importlib.util
14
+ import logging
15
+ import lzma
16
+ import pickle
17
+ import sys
18
+ import types
19
+ import typing
20
+ from pathlib import Path
21
+ from tempfile import TemporaryDirectory
22
+
23
+ import numpy as np
24
+ from git import Repo
25
+ from rubin_scheduler.scheduler.example import example_scheduler
26
+ from rubin_scheduler.scheduler.model_observatory import ModelObservatory
27
+ from rubin_scheduler.scheduler.schedulers.core_scheduler import CoreScheduler
28
+ from rubin_scheduler.scheduler.utils import SchemaConverter
29
+
30
+
31
+ def get_scheduler_from_config(config_script_path: str | Path) -> typing.Tuple[int, CoreScheduler]:
32
+ """Generate a CoreScheduler according to a configuration in a file.
33
+
34
+ Parameters
35
+ ----------
36
+ config_script_path : `str`
37
+ The configuration script path
38
+
39
+ Returns
40
+ -------
41
+ nside : `int`
42
+ The nside.
43
+ scheduler : `CoreScheduler`
44
+ An instance of the Rubin Observatory FBS.
45
+
46
+ Raises
47
+ ------
48
+ ValueError
49
+ If the config file is invalid, or has invalid content.
50
+ """
51
+
52
+ # Follow example from official python docs:
53
+ # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
54
+ # The T&S supplied files have code in a __name__ == 'config' conditional
55
+ # that we need to be executed, so we *must* name the module "config"
56
+ config_module_name: str = "config"
57
+ config_module_spec = importlib.util.spec_from_file_location(config_module_name, config_script_path)
58
+ if config_module_spec is None or config_module_spec.loader is None:
59
+ # Make type checking happy
60
+ raise ValueError(f"Cannot load config file {config_script_path}")
61
+
62
+ config_module: types.ModuleType = importlib.util.module_from_spec(config_module_spec)
63
+ sys.modules[config_module_name] = config_module
64
+ config_module_spec.loader.exec_module(config_module)
65
+
66
+ try:
67
+ scheduler: CoreScheduler = config_module.scheduler
68
+ nside: int = scheduler.nside
69
+ except NameError:
70
+ nside, scheduler = config_module.get_scheduler()
71
+ assert isinstance(nside, int)
72
+ assert isinstance(scheduler, CoreScheduler)
73
+
74
+ return nside, scheduler
75
+
76
+
77
+ def get_scheduler_instance_from_repo(
78
+ config_repo: str,
79
+ config_script: str,
80
+ config_ref: str = "main",
81
+ ) -> CoreScheduler:
82
+ """Generate a CoreScheduler according to a configuration in git.
83
+
84
+ Parameters
85
+ ----------
86
+ config_repo : `str`
87
+ The git repository with the configuration.
88
+ config_script : `str`
89
+ The configuration script path (relative to the repository root).
90
+ config_ref : `str`, optional
91
+ The branch of the repository to use, by default "main"
92
+
93
+ Returns
94
+ -------
95
+ scheduler : `CoreScheduler`
96
+ An instance of the Rubin Observatory FBS.
97
+
98
+ Raises
99
+ ------
100
+ ValueError
101
+ If the config file is invalid, or has invalid content.
102
+ """
103
+
104
+ with TemporaryDirectory() as local_config_repo_parent:
105
+ repo: Repo = Repo.clone_from(config_repo, local_config_repo_parent)
106
+ repo.git.checkout(config_ref)
107
+ full_config_script_path: Path = Path(repo.working_dir).joinpath(config_script)
108
+ scheduler = get_scheduler_from_config(full_config_script_path)[1]
109
+
110
+ return scheduler
111
+
112
+
113
+ def get_scheduler(
114
+ config_repo: str | None = None,
115
+ config_script: str | None = None,
116
+ config_ref: str = "main",
117
+ visits_db: str | None = None,
118
+ ) -> CoreScheduler:
119
+ """Generate a CoreScheduler according to a configuration.
120
+
121
+ Parameters
122
+ ----------
123
+ config_repo : `str`
124
+ The git repository with the configuration.
125
+ config_script : `str`
126
+ The configuration script path (relative to the repository root).
127
+ config_ref : `str`, optional
128
+ The branch of the repository to use, by default "main"
129
+ visits_db : `str` or `None`
130
+ Database from which to load pre-existing visits
131
+
132
+ Returns
133
+ -------
134
+ scheduler : `CoreScheduler`
135
+ An instance of the Rubin Observatory FBS.
136
+
137
+ Raises
138
+ ------
139
+ ValueError
140
+ If the config file is invalid, or has invalid content.
141
+ """
142
+ if config_repo is not None and len(config_repo) > 0:
143
+ if config_script is None:
144
+ raise ValueError("If the config repo is set, the script must be as well.")
145
+ logging.info(
146
+ f"Instantiating scheduler form {config_script} on the {config_ref} branch of {config_repo}"
147
+ )
148
+ scheduler = get_scheduler_instance_from_repo(
149
+ config_repo=config_repo, config_script=config_script, config_ref=config_ref
150
+ )
151
+ elif config_script is not None:
152
+ logging.info(f"Reading scheduler from {config_script}")
153
+ scheduler = get_scheduler_from_config(config_script)[1]
154
+ else:
155
+ logging.info("Creating example scheduler")
156
+ example_scheduler_result = example_scheduler()
157
+ if isinstance(example_scheduler_result, CoreScheduler):
158
+ scheduler = example_scheduler_result
159
+ else:
160
+ # It might return a observatory, scheduler, observations tuple
161
+ # instead.
162
+ scheduler = example_scheduler_result[1]
163
+
164
+ if isinstance(visits_db, str) and len(visits_db) > 0:
165
+ logging.info(f"Adding visits from {visits_db}.")
166
+ obs: np.recarray = SchemaConverter().opsim2obs(visits_db)
167
+ if len(obs) > 0:
168
+ scheduler.add_observations_array(obs)
169
+ logging.info("Finished adding visits")
170
+
171
+ return scheduler
172
+
173
+
174
+ def save_scheduler(scheduler: CoreScheduler, file_name: str) -> None:
175
+ """Save an instances of the scheduler in a pickle file,
176
+ compressed according to its extension.
177
+
178
+ Parameters
179
+ ----------
180
+ scheduler : `CoreScheduler`
181
+ The scheduler to save.
182
+ file_name : `str`
183
+ The file in which to save the schedulers.
184
+ """
185
+ opener: typing.Callable = open
186
+
187
+ if file_name.endswith(".bz2"):
188
+ opener = bz2.open
189
+ elif file_name.endswith(".xz"):
190
+ opener = lzma.open
191
+ elif file_name.endswith(".gz"):
192
+ opener = gzip.open
193
+
194
+ with opener(file_name, "wb") as pio:
195
+ pickle.dump(scheduler, pio)
196
+
197
+
198
+ def add_make_scheduler_snapshot_args(parser: argparse.ArgumentParser) -> None:
199
+ """Add arguments needed for saving a scheduler to an argument parser."""
200
+ parser.add_argument("--scheduler_fname", type=str, help="The file in which to save the scheduler.")
201
+ parser.add_argument(
202
+ "--repo", type=str, default=None, help="The repository from which to load the configuration."
203
+ )
204
+ parser.add_argument(
205
+ "--script", type=str, default=None, help="The path to the config script (relative to the repo root)."
206
+ )
207
+ parser.add_argument(
208
+ "--ref",
209
+ type=str,
210
+ default="main",
211
+ help="The git reference (tag/commit/branch) of the repo from which to get the script",
212
+ )
213
+ parser.add_argument(
214
+ "--visits", type=str, default="", help="Opsim database from which to load previous visits."
215
+ )
216
+
217
+
218
+ def make_scheduler_snapshot_cli(cli_args: list = []) -> None:
219
+ parser = argparse.ArgumentParser(description="Create a scheduler pickle")
220
+ add_make_scheduler_snapshot_args(parser)
221
+ args: argparse.Namespace = parser.parse_args() if len(cli_args) == 0 else parser.parse_args(cli_args)
222
+
223
+ logging.basicConfig(
224
+ level=logging.DEBUG,
225
+ format="%(asctime)s:%(name)s:%(levelname)s:%(message)s",
226
+ datefmt="%Y-%m-%dT%H:%M:%S",
227
+ )
228
+
229
+ scheduler: CoreScheduler = get_scheduler(args.repo, args.script, args.ref, args.visits)
230
+
231
+ save_scheduler(scheduler, args.scheduler_fname)
232
+
233
+
234
+ def make_ideal_model_observatory_cli(cli_args: list = []) -> None:
235
+ parser = argparse.ArgumentParser(description="Create an ideal model observatory pickle")
236
+ parser.add_argument("scheduler", type=str, help="Scheduler pickle file to read.")
237
+ parser.add_argument("model_observatory", type=str, help="Model observatory pickle file to write.")
238
+ args: argparse.Namespace = parser.parse_args() if len(cli_args) == 0 else parser.parse_args(cli_args)
239
+
240
+ with open(args.scheduler, "rb") as scheduler_io:
241
+ scheduler = pickle.load(scheduler_io)
242
+
243
+ observatory = ModelObservatory(
244
+ nside=scheduler.nside,
245
+ cloud_data="ideal",
246
+ seeing_data="ideal",
247
+ downtimes="ideal",
248
+ )
249
+
250
+ with open(args.model_observatory, "wb") as observatory_io:
251
+ pickle.dump(observatory, observatory_io)
252
+
253
+
254
+ if __name__ == "__main__":
255
+ make_scheduler_snapshot_cli()
@@ -0,0 +1,216 @@
1
+ """Tools for running the set of simulations used for a pre-night briefing."""
2
+
3
+ __all__ = ["AnomalousOverheadFunc", "run_prenight_sim_cli"]
4
+
5
+ import argparse
6
+ import logging
7
+ import pickle
8
+ import warnings
9
+ from datetime import date
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+ from matplotlib.pylab import Generator
14
+ from rubin_scheduler.scheduler import sim_runner
15
+ from rubin_scheduler.site_models import Almanac
16
+
17
+ from rubin_sim.sim_archive import make_sim_data_dir
18
+
19
+ from .util import dayobs_to_date
20
+
21
+ LOGGER = logging.getLogger(__name__)
22
+
23
+
24
+ class AnomalousOverheadFunc:
25
+ """Callable to return random overhead.
26
+
27
+ Parameters
28
+ ----------
29
+ seed : `int`
30
+ Random number seed.
31
+ slew_scale : `float`
32
+ The scale for the scatter in the slew offest (seconds).
33
+ visit_scale : `float`, optional
34
+ The scale for the scatter in the visit overhead offset (seconds).
35
+ Defaults to 0.0.
36
+ slew_loc : `float`, optional
37
+ The location of the scatter in the slew offest (seconds).
38
+ Defaults to 0.0.
39
+ visit_loc : `float`, optional
40
+ The location of the scatter in the visit offset (seconds).
41
+ Defaults to 0.0.
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ seed: int,
47
+ slew_scale: float,
48
+ visit_scale: float = 0.0,
49
+ slew_loc: float = 0.0,
50
+ visit_loc: float = 0.0,
51
+ ) -> None:
52
+ self.rng: Generator = np.random.default_rng(seed)
53
+ self.visit_loc: float = visit_loc
54
+ self.visit_scale: float = visit_scale
55
+ self.slew_loc: float = slew_loc
56
+ self.slew_scale: float = slew_scale
57
+
58
+ def __call__(self, visittime: float, slewtime: float) -> float:
59
+ """Return a randomized offset for the visit overhead.
60
+
61
+ Parameters
62
+ ----------
63
+ visittime : `float`
64
+ The visit time (seconds).
65
+ slewtime : `float`
66
+ The slew time (seconds).
67
+
68
+ Returns
69
+ -------
70
+ offset: `float`
71
+ Random offset (seconds).
72
+ """
73
+
74
+ slew_overhead: float = slewtime * self.rng.normal(self.slew_loc, self.slew_scale)
75
+
76
+ # Slew might be faster that expected, but it will never take negative
77
+ # time.
78
+ if (slewtime + slew_overhead) < 0:
79
+ slew_overhead = 0.0
80
+
81
+ visit_overhead: float = visittime * self.rng.normal(self.slew_loc, self.slew_scale)
82
+ # There might be anomalous overhead that makes visits take longer,
83
+ # but faster is unlikely.
84
+ if visit_overhead < 0:
85
+ visit_overhead = 0.0
86
+
87
+ return slew_overhead + visit_overhead
88
+
89
+
90
+ def compute_sim_start_and_end(day_obs: int, sim_nights: int, delay: float = 0) -> tuple[float, float]:
91
+ """Compute simulation start and end time for a prenight simulation.
92
+
93
+ Parameters
94
+ ----------
95
+ day_obs: `int`
96
+ The starting dayobs as YYYYMMDD.
97
+ sim_nights: `int`
98
+ The number of nights to simulate
99
+ delay: `float`
100
+ A delayed start, in minutes.
101
+
102
+ Return
103
+ ------
104
+ sim_start_mjd, sim_duration : `tuple`
105
+ The starting MJD and simulation duration in days.
106
+ """
107
+ # Find the start of observing for the specified day_obs.
108
+ # Almanac.get_sunset_info does not use day_obs, so
109
+ # translate to DJD, Dublin Julian Date.
110
+ # Like plain Julian Date, DJD rolls over at the
111
+ # same time as day_obs, but unlike JD, the year
112
+ # of the epoch works with python's datetime.date.
113
+ # Note that MJD - DJD = 15019.5
114
+ # and has epoch 1899-12-31T12:00:00.
115
+ sunsets = Almanac().sunsets
116
+ djd_mjd_offset = 15019.5
117
+ djd0 = date(1899, 12, 31)
118
+
119
+ night0_djd = int(np.floor(sunsets["sunset"][0] - djd_mjd_offset))
120
+ day_obs_date = dayobs_to_date(day_obs)
121
+ day_obs_djd = (day_obs_date - djd0).days
122
+ day_obs_start_night = day_obs_djd - night0_djd
123
+ sim_start_mjd = sunsets["sun_n12_setting"][day_obs_start_night] + delay / (24 * 60.0)
124
+ day_obs_end_night = day_obs_start_night + sim_nights - 1
125
+ sim_end_mjd = sunsets["sunrise"][day_obs_end_night]
126
+ sim_duration = sim_end_mjd - sim_start_mjd
127
+ assert np.ceil(sim_duration) == sim_nights
128
+ return sim_start_mjd, sim_duration
129
+
130
+
131
+ def run_prenight_sim_cli(cli_args: list = []) -> int:
132
+ parser = argparse.ArgumentParser(description="Run an SV simulation.")
133
+ parser.add_argument("scheduler", type=str, help="scheduler pickle file.")
134
+ parser.add_argument("observatory", type=str, help="model observatory pickle file.")
135
+ parser.add_argument("day_obs", type=int, help="start day obs.")
136
+ parser.add_argument("sim_nights", type=int, help="number of nights to run.")
137
+ parser.add_argument("--keep_rewards", action="store_true", help="Compute rewards data.")
138
+ parser.add_argument("--telescope", type=str, default="simonyi", help="The telescope simulated.")
139
+ parser.add_argument("--label", type=str, default="", help="The tags on the simulation.")
140
+ parser.add_argument("--delay", type=float, default=0.0, help="Minutes after nominal to start.")
141
+ parser.add_argument("--anom_overhead_scale", type=float, default=0.0, help="scale of scatter in the slew")
142
+ parser.add_argument(
143
+ "--anom_overhead_seed",
144
+ type=int,
145
+ default=1,
146
+ help="random number seed for anomalous scatter in overhead",
147
+ )
148
+ parser.add_argument("--tags", type=str, default=[], nargs="*", help="The tags on the simulation.")
149
+ parser.add_argument("--results", type=str, default="", help="Results directory.")
150
+ args = parser.parse_args() if len(cli_args) == 0 else parser.parse_args(cli_args)
151
+ day_obs = args.day_obs
152
+ sim_nights = args.sim_nights
153
+ keep_rewards = args.keep_rewards
154
+ tags = args.tags
155
+ label = args.label
156
+ telescope = args.telescope
157
+ delay = args.delay
158
+ anom_overhead_scale = args.anom_overhead_scale
159
+ anom_overhead_seed = args.anom_overhead_seed
160
+ results_dir = args.results if len(args.results) > 0 else None
161
+
162
+ with open(args.scheduler, "rb") as scheduler_io:
163
+ scheduler = pickle.load(scheduler_io)
164
+
165
+ with open(args.observatory, "rb") as observatory_io:
166
+ observatory = pickle.load(observatory_io)
167
+
168
+ if anom_overhead_scale > 0:
169
+ anomalous_overhead_func = AnomalousOverheadFunc(anom_overhead_seed, anom_overhead_scale)
170
+ else:
171
+ anomalous_overhead_func = None
172
+
173
+ if keep_rewards:
174
+ scheduler.keep_rewards = keep_rewards
175
+
176
+ sim_start_mjd, sim_duration = compute_sim_start_and_end(day_obs, sim_nights, delay)
177
+ observatory.mjd = sim_start_mjd
178
+
179
+ LOGGER.info("Starting simulation")
180
+ with warnings.catch_warnings():
181
+ warnings.simplefilter("ignore", RuntimeWarning)
182
+ results = sim_runner(
183
+ observatory,
184
+ scheduler,
185
+ sim_start_mjd=sim_start_mjd,
186
+ sim_duration=sim_duration,
187
+ record_rewards=keep_rewards,
188
+ verbose=True,
189
+ anomalous_overhead_func=anomalous_overhead_func,
190
+ )
191
+ observatory, scheduler, obs = results[:3]
192
+ rewards, obs_rewards = (None, None) if len(results) < 5 else results[3:5]
193
+ LOGGER.info("Simulation complete.")
194
+
195
+ LOGGER.info("Writing simulation results")
196
+ data_path = make_sim_data_dir(
197
+ obs,
198
+ rewards,
199
+ obs_rewards,
200
+ in_files={"scheduler": args.scheduler, "observatory": args.observatory},
201
+ tags=tags,
202
+ label=label,
203
+ opsim_metadata={"telescope": telescope},
204
+ data_path=results_dir,
205
+ )
206
+ output_dirname: str = "Unknown"
207
+ if isinstance(data_path, Path):
208
+ assert isinstance(data_path, Path)
209
+ output_dirname = data_path.name
210
+ else:
211
+ assert isinstance(data_path, str)
212
+ output_dirname = data_path
213
+
214
+ LOGGER.info(f"Wrote results in directory: {output_dirname}")
215
+
216
+ return 0
@@ -0,0 +1,229 @@
1
+ __all__ = ["get_prenight_index", "select_latest_prenight_sim"]
2
+
3
+ import logging
4
+ import os
5
+ from datetime import date
6
+
7
+ import pandas as pd
8
+ from lsst.resources import ResourcePath
9
+
10
+ if "PRENIGHT_INDEX_URL" in os.environ:
11
+ PRENIGHT_INDEX_URL = os.environ["PRENIGHT_INDEX_URL"]
12
+ else:
13
+ from rubin_sim.sim_archive.vseqarchive import PRENIGHT_INDEX_URL
14
+
15
+ from rubin_sim.sim_archive.vseqmetadata import VisitSequenceArchiveMetadata
16
+
17
+ from .util import dayobs_to_date
18
+
19
+ MAX_AGE = 365
20
+ LOGGER = logging.getLogger(__name__)
21
+
22
+
23
+ def get_prenight_index_from_database(
24
+ day_obs: str | int | date,
25
+ telescope: str = "simonyi",
26
+ schema: str | None = None,
27
+ host: str | None = None,
28
+ user: str | None = None,
29
+ database: str | None = None,
30
+ ) -> pd.DataFrame:
31
+ """
32
+ Retrieve the pre‑night observation index for a given night from the
33
+ LSST SIM Archive.
34
+
35
+ Parameters
36
+ ----------
37
+ day_obs : `str` or `int` or `date`
38
+ The night for which to fetch the index. Accepts a date string,
39
+ YYYYMMDD encoded into an integer, or a :class:`datetime.date`
40
+ telescope : `str`, optional
41
+ Telescope name (default ``"simonyi"``). Passed directly to
42
+ :class:`~rubin_sim.sim_archive.vseqmetadata.VisitSequenceArchiveMetadata`.
43
+ schema : `str` or `None`, optional
44
+ Optional database schema name.
45
+ host : `str` or `None`, optional
46
+ Database host address.
47
+ user : `str` or `None`, optional
48
+ Database user name.
49
+ database : `str` or `None`, optional
50
+ Database name.
51
+
52
+ Returns
53
+ -------
54
+ prenights: `pandas.DataFrame`
55
+ A DataFrame indexed by ``visitseq_uuid`` containing all pre‑night
56
+ entries for the requested night. Columns mirror those returned
57
+ by the underlying `sims_on_nights` call.
58
+
59
+ Notes
60
+ -----
61
+ * The function uses a hard‑coded maximum simulation age of
62
+ :data:`MAX_AGE` (365 days) to limit the search.
63
+ * If any of ``host``, ``user`` or ``database`` are supplied, they
64
+ override the defaults in the ``metadata_dsn`` dictionary passed to
65
+ :class:`~rubin_sim.sim_archive.vseqmetadata.VisitSequenceArchiveMetadata`.
66
+ """
67
+
68
+ day_obs_date = dayobs_to_date(day_obs)
69
+ assert isinstance(day_obs_date, date)
70
+
71
+ metadata_dsn = {}
72
+ if host is not None:
73
+ metadata_dsn["host"] = host
74
+ if user is not None:
75
+ metadata_dsn["user"] = user
76
+ if database is not None:
77
+ metadata_dsn["database"] = database
78
+
79
+ visit_seq_archive_metadata = VisitSequenceArchiveMetadata(metadata_dsn, schema)
80
+ prenights = visit_seq_archive_metadata.sims_on_nights(
81
+ day_obs_date, day_obs_date, tags=["prenight"], telescope=telescope, max_simulation_age=MAX_AGE
82
+ ).set_index("visitseq_uuid")
83
+ LOGGER.info(f"Got metadata on {len(prenights)} simulations from database")
84
+ return prenights
85
+
86
+
87
+ def get_prenight_index_from_bucket(
88
+ day_obs: str | int | date,
89
+ telescope: str = "simonyi",
90
+ prenight_index_path: str | ResourcePath = PRENIGHT_INDEX_URL,
91
+ ) -> pd.DataFrame:
92
+ """
93
+ Load the pre‑night observation index for a given night from a remote
94
+ bucket.
95
+
96
+ Parameters
97
+ ----------
98
+ day_obs : `str` or `int` or `date`
99
+ The night for which to fetch the index. Accepts a date string,
100
+ YYYYMMDD encoded into an integer, a ``datetime.date``
101
+ telescope : `str`, optional
102
+ Telescope name (default ``"simonyi"``).
103
+ prenight_index_path : `str` or `ResourcePath`, optional
104
+ The root path where the pre‑night index
105
+ JSON files are stored. It can be a string URL or a
106
+ ``ResourcePath`` instance.
107
+
108
+ Returns
109
+ -------
110
+ prenights : `pandas.DataFrame`
111
+ A DataFrame indexed by the JSON key (``visitseq_uuid``) containing
112
+ all pre‑night entries for the requested night. The columns match
113
+ those stored in the JSON file.
114
+ """
115
+ day_obs_date = dayobs_to_date(day_obs)
116
+ assert isinstance(day_obs_date, date)
117
+
118
+ prenight_index_path = ResourcePath(prenight_index_path, forceDirectory=True)
119
+ assert isinstance(prenight_index_path, ResourcePath)
120
+
121
+ year = day_obs_date.year
122
+ month = day_obs_date.month
123
+ isodate = day_obs_date.isoformat()
124
+
125
+ prenight_index_resource_path = (
126
+ prenight_index_path.join(telescope)
127
+ .join(str(year))
128
+ .join(str(month))
129
+ .join(f"{telescope}_prenights_for_{isodate}.json")
130
+ )
131
+ with prenight_index_resource_path.as_local() as local_resource_path:
132
+ prenights = pd.read_json(local_resource_path.ospath, orient="index")
133
+
134
+ return prenights
135
+
136
+
137
+ def get_prenight_index(
138
+ day_obs: str | int | date,
139
+ telescope: str = "simonyi",
140
+ schema: str | None = None,
141
+ host: str | None = None,
142
+ user: str | None = None,
143
+ database: str | None = None,
144
+ prenight_index_path: str | ResourcePath | None = None,
145
+ ) -> pd.DataFrame:
146
+ """Retrieve the pre‑night observation index for a given night.
147
+
148
+ Parameters
149
+ ----------
150
+ day_obs : `str` or `int` or `date`
151
+ Night for which to fetch the index.
152
+ telescope : `str`
153
+ Telescope name (default ``simonyi``).
154
+ schema : `str` or `None`, optional
155
+ Optional database schema name.
156
+ host : `str` or `None`, optional
157
+ Database host address.
158
+ user : `str` or `None`, optional
159
+ Database user name.
160
+ database : `str` or `None`, optional
161
+ Database name.
162
+ prenight_index_path : `str` or `ResourcePath`, optional
163
+ Root path to the bucket files (used only if the database call fails).
164
+
165
+ Returns
166
+ -------
167
+ prenights: `pd.DataFrame`
168
+ Pre‑night index indexed by ``visitseq_uuid``.
169
+ """
170
+ try:
171
+ # Try the database first
172
+ prenights = get_prenight_index_from_database(day_obs, telescope, schema, host, user, database)
173
+ except Exception:
174
+ # Fall back to the bucket
175
+ LOGGER.info("Database not accessible, falling back on the index in the S3 bucket.")
176
+ if prenight_index_path is None:
177
+ prenight_index_path = PRENIGHT_INDEX_URL
178
+ try:
179
+ prenights = get_prenight_index_from_bucket(day_obs, telescope, prenight_index_path)
180
+ except FileNotFoundError:
181
+ prenights = pd.DataFrame()
182
+
183
+ return prenights
184
+
185
+
186
+ def select_latest_prenight_sim(
187
+ prenights: pd.DataFrame,
188
+ tags: tuple[str, ...] = ("ideal", "nominal"),
189
+ max_simulation_age: int = 2,
190
+ ) -> dict | None:
191
+ """Select the best prenight simulation from a DataFrame.
192
+
193
+ Parameters
194
+ ----------
195
+ prenights: `pd.DataFrame`
196
+ The table of prenights from which to select.
197
+ tags : `tuple` [`str`]
198
+ A tuple of tags to filter simulations by.
199
+ Defaults to ``('ideal', 'nominal')``.
200
+ max_simulation_age : `int`
201
+ The maximum age of simulations to consider, in days.
202
+ Simulations older than ``max_simulation_age`` will not be considered.
203
+ Defaults to 2.
204
+
205
+ Returns
206
+ -------
207
+ best_sim : `dict` or `None`
208
+ A dictionary with metadata for the simulation.
209
+ """
210
+ best_sim = None
211
+ for _, sim in prenights.reset_index().iterrows():
212
+ if not set(tags).issubset(sim["tags"]):
213
+ continue
214
+ if best_sim is not None:
215
+ if sim["creation_time"] < best_sim["creation_time"]:
216
+ continue
217
+
218
+ age = (pd.Timestamp.now(tz="UTC") - sim["creation_time"]).days
219
+ if age > max_simulation_age:
220
+ continue
221
+
222
+ best_sim = sim
223
+
224
+ if best_sim is not None:
225
+ LOGGER.info(f"Most recent simulation meeting requested criteria is {best_sim['visitseq_url']}.")
226
+ else:
227
+ LOGGER.debug("No simulations met the requested criteria.")
228
+
229
+ return best_sim