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,965 @@
1
+ import datetime
2
+ import hashlib
3
+ import logging
4
+ import os
5
+ from pathlib import Path
6
+ from tempfile import TemporaryDirectory
7
+ from typing import Sequence, Tuple
8
+ from uuid import UUID
9
+
10
+ import click
11
+ import pandas as pd
12
+ from astropy.time import Time
13
+ from lsst.resources import ResourcePath
14
+
15
+ from rubin_sim.maf.stackers import BaseStacker
16
+
17
+ from .util import compute_conda_env, dayobs_to_date, hdf5_to_opsimdb, opsimdb_to_hdf5
18
+ from .vseqmetadata import VSARCHIVE_PGSCHEMA, VisitSequenceArchiveMetadata
19
+
20
+ __all__ = [
21
+ "compute_nightly_stats",
22
+ "get_visits",
23
+ "record_visitseq_metadata",
24
+ "set_visitseq_url",
25
+ "get_visitseq_url",
26
+ "update_visitseq_metadata",
27
+ "is_tagged",
28
+ "tag",
29
+ "untag",
30
+ "comment",
31
+ "get_comments",
32
+ "archive_file",
33
+ "get_file",
34
+ "add_nightly_stats",
35
+ "query_nightly_stats",
36
+ "record_conda_env",
37
+ "import_proto",
38
+ "export_proto",
39
+ "ARCHIVE_URL",
40
+ "PRENIGHT_INDEX_URL",
41
+ ]
42
+
43
+ ARCHIVE_URL = "s3://rubin:rubin-scheduler-prenight/opsim/vseq/"
44
+ PRENIGHT_INDEX_URL = "s3://rubin:rubin-scheduler-prenight/opsim/prenight_index/"
45
+ SQLITE_EXTINSIONS = {".db", ".sqlite", ".sqlite3", ".db3"}
46
+
47
+ LOGGER = logging.getLogger(__name__)
48
+
49
+ #
50
+ # Computation
51
+ #
52
+
53
+
54
+ def compute_nightly_stats(
55
+ visits: pd.DataFrame, columns: Tuple[str, ...] = ("altitude", "azimuth")
56
+ ) -> pd.DataFrame:
57
+ if "day_obs" not in visits.columns:
58
+ # Pandas seems to work better with type hinding that astropy Time
59
+ start_mjd_field = ""
60
+ for candidate_field in ["obs_start_mjd", "observationStartMJD"]:
61
+ if candidate_field in visits.columns:
62
+ start_mjd_field = candidate_field
63
+ break
64
+ if len(start_mjd_field) == 0:
65
+ raise ValueError("No day_obs or start mjd field found")
66
+
67
+ times = pd.to_datetime(visits.loc[:, start_mjd_field] + 2400000, unit="D", origin="julian")
68
+ visits = visits.assign(day_obs=pd.Series(times).dt.date)
69
+
70
+ stats_df = (
71
+ visits.groupby("day_obs")[list(columns)]
72
+ .describe(percentiles=[0.05, 0.25, 0.5, 0.75, 0.95])
73
+ .stack(level=0)
74
+ .reset_index()
75
+ .rename(
76
+ columns={
77
+ "level_1": "value_name",
78
+ "5%": "p05",
79
+ "25%": "q1",
80
+ "50%": "median",
81
+ "75%": "q3",
82
+ "95%": "p95",
83
+ }
84
+ )
85
+ )
86
+ stats_df["accumulated"] = False
87
+ return stats_df
88
+
89
+
90
+ def construct_base_resource_path(
91
+ archive_base: ResourcePath, telescope: str, creation_time: Time, visitseq_uuid: UUID
92
+ ) -> ResourcePath:
93
+ """Build the base ``ResourcePath`` for a visit‑sequence
94
+ archive entry.
95
+
96
+ Parameters
97
+ ----------
98
+ archive_base : `ResourcePath`
99
+ The base of the archive relative to which the visit sequence
100
+ will be added.
101
+ telescope : `str`
102
+ Identifier for the telescope (e.g. ``"simonyi"`` or ``"auxtel"`).
103
+ creation_time : `Time`
104
+ The timestamp when the visit sequence was created.
105
+ visitseq_uuid : `UUID`
106
+ The unique identifier of the visit sequence.
107
+
108
+ Returns
109
+ -------
110
+ ResourcePath
111
+ A ``ResourcePath`` instance representing the base directory for
112
+ this visit sequence. The directory is structured as:
113
+
114
+ ``<archive_base>/<telescope>/<date>/<visitseq_uuid>/``
115
+
116
+ where ``<date>`` is the UTC‑12 date derived from ``creation_time``.
117
+ """
118
+ creation_time_iso_dayobs = dayobs_to_date(Time(creation_time.mjd, format="mjd")).isoformat()
119
+ visitseq_base_rp = (
120
+ archive_base.join(telescope)
121
+ .join(creation_time_iso_dayobs)
122
+ .join(str(visitseq_uuid), forceDirectory=True)
123
+ )
124
+ return visitseq_base_rp
125
+
126
+
127
+ #
128
+ # Interaction with the file store
129
+ #
130
+
131
+
132
+ def _write_data_to_archive(content: bytes, visitseq_base_rp: ResourcePath, file_name: str) -> ResourcePath:
133
+ # Make sure the base for the visit sequence exists
134
+ if not visitseq_base_rp.exists():
135
+ visitseq_base_rp.mkdir()
136
+ LOGGER.debug(f"Created {visitseq_base_rp}.")
137
+
138
+ destination_rp = visitseq_base_rp.join(file_name)
139
+ LOGGER.debug(f"Writing {destination_rp}.")
140
+ destination_rp.write(content)
141
+ LOGGER.debug(f"Wrote {destination_rp}.")
142
+ return destination_rp
143
+
144
+
145
+ def _write_file_to_archive(
146
+ origin: str | Path,
147
+ visitseq_base_rp: ResourcePath,
148
+ ) -> Tuple[ResourcePath, bytes]:
149
+ file_path = origin if isinstance(origin, Path) else Path(origin)
150
+ with open(file_path, "rb") as origin_io:
151
+ content = origin_io.read()
152
+
153
+ content_sha256 = bytes.fromhex(hashlib.sha256(content).hexdigest())
154
+ destination_rp = _write_data_to_archive(content, visitseq_base_rp, file_path.name)
155
+ return destination_rp, content_sha256
156
+
157
+
158
+ def add_file(
159
+ vsarch: VisitSequenceArchiveMetadata,
160
+ uuid: UUID,
161
+ origin: str | Path,
162
+ file_type: str,
163
+ archive_base: str | ResourcePath,
164
+ update: bool = False,
165
+ ) -> ResourcePath:
166
+ """Archive a file associated with a visit sequence and
167
+ register its location in the metadata database.
168
+
169
+ Parameters
170
+ ----------
171
+ vsarch : `VisitSequenceArchiveMetadata`
172
+ Instance of `VisitSequenceArchiveMetadata` used to
173
+ query the sequence metadata and register the file.
174
+ uuid : `UUID`
175
+ The unique identifier of the visit sequence that the file
176
+ should be linked to.
177
+ origin : `str` or `pathlib.Path`
178
+ Path to the local file to be archived.
179
+ file_type : `str`
180
+ Identifier for the type of file being archived.
181
+ archive_base : `lsst.resources.ResourcePath`
182
+ Base location of the archive.
183
+ update : `bool`, optional
184
+ If ``True`` and a record for the same ``visitseq_uuid`` and
185
+ ``file_type`` already exists, the existing row will be
186
+ updated with the new SHA‑256 hash and URL. If ``False`` a
187
+ `ValueError` will be raised on duplicate.
188
+
189
+ Returns
190
+ -------
191
+ file_rp : `lsst.resources.ResourcePath`
192
+ The `ResourcePath` pointing to the archived file
193
+ on the file store.
194
+ """
195
+ if isinstance(archive_base, str):
196
+ archive_base = ResourcePath(archive_base)
197
+ assert isinstance(archive_base, ResourcePath)
198
+
199
+ visitseq_metadata = vsarch.get_visitseq_metadata(uuid)
200
+ uuid = visitseq_metadata["visitseq_uuid"]
201
+ telescope = visitseq_metadata["telescope"]
202
+ creation_time = Time(visitseq_metadata["creation_time"])
203
+ visitseq_base_rp = construct_base_resource_path(archive_base, telescope, creation_time, uuid)
204
+
205
+ # If we are adding a file that looks like opsim sqlite3 output but
206
+ # specify the file type as visits, convert to hdf5.
207
+ if file_type == "visits" and Path(origin).suffix.lower() in SQLITE_EXTINSIONS:
208
+ with TemporaryDirectory() as temp_dir:
209
+ new_origin = Path(temp_dir).joinpath("visits.h5")
210
+ opsimdb_to_hdf5(origin, new_origin)
211
+ location = add_file(vsarch, uuid, new_origin, "visits", archive_base, update)
212
+ return location
213
+
214
+ location, file_sha256 = _write_file_to_archive(origin, visitseq_base_rp)
215
+ if file_type == "visits":
216
+ vsarch.set_visitseq_url(uuid, location.geturl())
217
+ else:
218
+ vsarch.register_file(uuid, file_type, file_sha256, location, update=update)
219
+
220
+ return location
221
+
222
+
223
+ def get_visits(
224
+ visits_url: str | ResourcePath,
225
+ query: str = "",
226
+ stackers: Sequence[BaseStacker] = (),
227
+ ) -> pd.DataFrame:
228
+ """Retrieve visits for a visit sequence.
229
+
230
+ Parameters
231
+ ----------
232
+ visits_url : `str`
233
+ The origin of the visits.
234
+ query : `str`
235
+ A query in `pandas.DataFrame.query` syntax to select visits
236
+ from the returned sequence. Applied before stackers are run.
237
+ stackers : `Sequence` [`BaseStacker`]
238
+ A sequence of maf stackers to apply.
239
+
240
+ Returns
241
+ -------
242
+ visits: `pd.DataFrame`
243
+ The visits in the sequence.
244
+ """
245
+ origin_rp = ResourcePath(visits_url)
246
+
247
+ with TemporaryDirectory() as temp_dir:
248
+ h5_destination = ResourcePath(temp_dir).join("visits.h5")
249
+ h5_destination.transfer_from(origin_rp, "copy")
250
+ visits = pd.read_hdf(h5_destination.ospath, key="observations")
251
+
252
+ assert isinstance(visits, pd.DataFrame)
253
+
254
+ if len(query) > 0:
255
+ visits.query(query, inplace=True)
256
+
257
+ if len(stackers) > 0:
258
+ visit_records = visits.to_records()
259
+ for stacker in stackers:
260
+ visit_records = stacker.run(visit_records)
261
+ visits = pd.DataFrame(visit_records)
262
+
263
+ return visits
264
+
265
+
266
+ #
267
+ # API
268
+ #
269
+
270
+
271
+ @click.group()
272
+ @click.option(
273
+ "--database",
274
+ default=None,
275
+ help="PostgreSQL database name of the metadata database",
276
+ )
277
+ @click.option(
278
+ "--host",
279
+ default=None,
280
+ help="PostgreSQL host address of the metadata database",
281
+ )
282
+ @click.option(
283
+ "--user",
284
+ default=None,
285
+ help="PostgreSQL user name to use to connect to the metadata database",
286
+ )
287
+ @click.option(
288
+ "--schema",
289
+ default=None,
290
+ help="Schema of the metadata database containing the visit‑sequence tables",
291
+ )
292
+ @click.pass_context
293
+ def vseqarchive(
294
+ click_context: click.Context,
295
+ database: str,
296
+ host: str,
297
+ user: str | None,
298
+ schema: str,
299
+ ) -> None:
300
+ """visitseq command line interface."""
301
+ metadata_db_kwargs = {}
302
+ if database is not None:
303
+ metadata_db_kwargs["database"] = database
304
+
305
+ if user is not None:
306
+ metadata_db_kwargs["user"] = user
307
+
308
+ if host is not None:
309
+ metadata_db_kwargs["host"] = host
310
+
311
+ if schema is None:
312
+ if "VSARCHIVE_PGSCHEMA" in os.environ:
313
+ schema = os.environ["VSARCHIVE_PGSCHEMA"]
314
+ else:
315
+ schema = VSARCHIVE_PGSCHEMA
316
+ assert isinstance(schema, str)
317
+
318
+ click_context.obj = VisitSequenceArchiveMetadata(metadata_db_kwargs, schema)
319
+
320
+
321
+ @vseqarchive.command()
322
+ @click.argument("table", type=str)
323
+ @click.argument("visits_file", type=click.Path(exists=True))
324
+ @click.argument("label")
325
+ @click.option(
326
+ "--telescope",
327
+ default="simonyi",
328
+ show_default=True,
329
+ help="Telescope identifier (e.g. simonyi or auxtel)",
330
+ )
331
+ @click.option("--url", default=None, help="Optional URL for the visits file")
332
+ @click.option("--first_day_obs", default=None, help="First night of observations (YYYY-MM-DD or int)")
333
+ @click.option("--last_day_obs", default=None, help="Last night of observations (YYYY-MM-DD or int)")
334
+ @click.option(
335
+ "--creation_time",
336
+ default=None,
337
+ help="Creation time of the visit sequence in ISO format (e.g. 2025-01-01T00:00:00)",
338
+ )
339
+ @click.pass_obj
340
+ def record_visitseq_metadata(
341
+ vsarch: VisitSequenceArchiveMetadata,
342
+ table: str,
343
+ visits_file: str,
344
+ label: str,
345
+ telescope: str,
346
+ url: str | None,
347
+ first_day_obs: str | int | None,
348
+ last_day_obs: str | int | None,
349
+ creation_time: str | None,
350
+ ) -> None:
351
+ """Record visit‑sequence metadata from an HDF5 visits file.
352
+
353
+ Parameters
354
+ ----------
355
+ vsarch : `VisitSequenceArchiveMetadata`
356
+ The database interface instance.
357
+ table : `str`
358
+ Target table for the record (e.g. ``visitseq``, ``simulations``,
359
+ ``completed`` or ``mixedvisitseq``).
360
+ visits_file : `str`
361
+ Path to the HDF5 file (or SQLite3 file) containing the
362
+ ``visits`` dataset.
363
+ label : `str`
364
+ Human‑readable label for the visit sequence.
365
+ telescope : `str`, optional
366
+ Telescope identifier, default ``simonyi``.
367
+ url : `str`, optional
368
+ URL where the visits file can be downloaded.
369
+ first_day_obs : `str` or `int`, optional
370
+ First day of observation in SMTN‑032 format, ISO string, or
371
+ ``YYYY‑MM‑DD``.
372
+ last_day_obs : `str` or `int`, optional
373
+ Last day of observation (same formats as ``first_day_obs``).
374
+ creation_time : `str`, optional
375
+ ISO‑formatted timestamp of when the sequence was created;
376
+ if omitted, the current time is used.
377
+
378
+ Returns
379
+ -------
380
+ None
381
+ The UUID of the inserted visit sequence is printed to stdout.
382
+ """
383
+
384
+ if Path(visits_file).suffix.lower() in SQLITE_EXTINSIONS:
385
+ with TemporaryDirectory() as temp_dir:
386
+ hdf5_path = Path(temp_dir).joinpath("visits.h5")
387
+ opsimdb_to_hdf5(visits_file, hdf5_path)
388
+ visits_df = pd.read_hdf(str(hdf5_path), key="observations")
389
+ else:
390
+ visits_df = pd.read_hdf(visits_file, key="observations")
391
+ assert isinstance(visits_df, pd.DataFrame)
392
+
393
+ # Convert the optional creation time string to an astropy Time object
394
+ creation_ap_time = Time(creation_time) if creation_time is not None else Time.now()
395
+
396
+ # Record the visit sequence metadata
397
+ visitseq_uuid = vsarch.record_visitseq_metadata(
398
+ visits_df,
399
+ label,
400
+ telescope=telescope,
401
+ table=table,
402
+ url=url,
403
+ first_day_obs=first_day_obs,
404
+ last_day_obs=last_day_obs,
405
+ creation_time=creation_ap_time,
406
+ )
407
+ print(visitseq_uuid)
408
+
409
+
410
+ @vseqarchive.command()
411
+ @click.argument("uuid", type=click.UUID)
412
+ @click.option(
413
+ "--table",
414
+ default="visitseq",
415
+ show_default=True,
416
+ help="Table to query (e.g. visitseq, simulations, completed, mixedvisitseq)",
417
+ )
418
+ @click.pass_obj
419
+ def get_visitseq_metadata(vsarch: VisitSequenceArchiveMetadata, uuid: UUID, table: str) -> None:
420
+ """Retrieve and display metadata for a visit sequence.
421
+
422
+ Parameters
423
+ ----------
424
+ vsarch : `VisitSequenceArchiveMetadata`
425
+ The metadata interface instance.
426
+ uuid : `UUID`
427
+ The UUID of the visit sequence to query.
428
+ table : `str`, optional
429
+ Name of the child table to search; one of ``visitseq``,
430
+ ``simulations``, ``completed`` or ``mixedvisitseq``.
431
+ Defaults to ``visitseq``.
432
+ """
433
+ sequence_metadata = vsarch.get_visitseq_metadata(uuid, table=table)
434
+
435
+ # Print the DataFrame as a tab‑separated table
436
+ print(sequence_metadata.to_frame().T.to_csv(sep="\t", index=False).rstrip("\n"))
437
+
438
+
439
+ @vseqarchive.command()
440
+ @click.argument("uuid", type=click.UUID)
441
+ @click.argument("url", type=click.STRING)
442
+ @click.pass_obj
443
+ def set_visitseq_url(vsarch: VisitSequenceArchiveMetadata, uuid: UUID, url: str) -> None:
444
+ """Update the stored URL for a visit‑sequence file.
445
+
446
+ Parameters
447
+ ----------
448
+ vsarch : `VisitSequenceArchiveMetadata`
449
+ The metadata interface instance.
450
+ uuid : `UUID`
451
+ The UUID of the visit sequence whose URL is to be updated.
452
+ url : `str`
453
+ The new URL pointing to the visits file.
454
+ """
455
+ vsarch.set_visitseq_url(uuid, url)
456
+
457
+
458
+ @vseqarchive.command()
459
+ @click.argument("uuid", type=click.UUID)
460
+ @click.pass_obj
461
+ def get_visitseq_url(vsarch: VisitSequenceArchiveMetadata, uuid: UUID) -> None:
462
+ """Retrieve and print the URL for the visits in a visit sequence.
463
+
464
+ Parameters
465
+ ----------
466
+ vsarch : `VisitSequenceArchiveMetadata`
467
+ The metadata interface instance.
468
+ uuid : `UUID`
469
+ The UUID of the visit sequence whose URL is to be retrieved.
470
+ """
471
+ url = vsarch.get_visitseq_url(uuid)
472
+ click.echo(url)
473
+
474
+
475
+ @vseqarchive.command()
476
+ @click.argument("uuid", type=click.UUID)
477
+ @click.argument("field", type=str)
478
+ @click.argument("value", type=str)
479
+ @click.pass_obj
480
+ def update_visitseq_metadata(
481
+ vsarch: VisitSequenceArchiveMetadata,
482
+ uuid: UUID,
483
+ field: str,
484
+ value: str,
485
+ ) -> None:
486
+ """Update a single metadata field for a visit sequence.
487
+
488
+ Parameters
489
+ ----------
490
+ vsarch : `VisiteSequenceArchiveMetadata`
491
+ An instance of the interface to the metadata archive.
492
+ uuid : `UUID`
493
+ The UUID of the visit sequence to update.
494
+ field : `str`
495
+ The name of the column in the metadata table to modify.
496
+ value : `str`
497
+ The new value for the field. Postgresql will cast the type
498
+ to what is expected in the database schema.
499
+ """
500
+ # psycopg2 usually converts strings to the right
501
+ # type base on column type in the database, but if
502
+ # hex values are provided for bytes colums it
503
+ # gets it wrong.
504
+ # So, if we're asking to update a bytes column, convert
505
+ # it from hex ourselves.
506
+ update_value: str | bytes = value
507
+ if field.endswith("sha256"):
508
+ update_value = bytes.fromhex(value)
509
+
510
+ vsarch.update_visitseq_metadata(uuid, field, update_value)
511
+
512
+
513
+ @vseqarchive.command()
514
+ @click.argument("uuid", type=click.UUID)
515
+ @click.argument("tag", type=str)
516
+ @click.pass_obj
517
+ def is_tagged(vsarch: VisitSequenceArchiveMetadata, uuid: UUID, tag: str) -> None:
518
+ """Return whether a visit sequence is tagged with a given tag.
519
+
520
+ The command prints ``true`` if the tag exists for the visit sequence
521
+ and ``false`` otherwise.
522
+
523
+ Parameters
524
+ ----------
525
+ vsarch : `VisitSequenceArchiveMetadata`
526
+ The interface to the metadata database.
527
+ uuid : `UUID`
528
+ The UUID of the visit sequence to check.
529
+ tag : `str`
530
+ The tag name to query.
531
+ """
532
+ seq_is_tagged = vsarch.is_tagged(uuid, tag)
533
+ if seq_is_tagged:
534
+ click.echo("true")
535
+ else:
536
+ click.echo("false")
537
+
538
+
539
+ @vseqarchive.command()
540
+ @click.argument("uuid", type=click.UUID)
541
+ @click.argument("tags", nargs=-1, required=True)
542
+ @click.pass_obj
543
+ def tag(vsarch: VisitSequenceArchiveMetadata, uuid: UUID, tags: Tuple[str, ...]) -> None:
544
+ """Add one or more tags to a visit sequence.
545
+
546
+ Parameters
547
+ ----------
548
+ vsarch : `VisitSequenceArchiveMetadata`
549
+ The interface to the metadata database.
550
+ uuid : `UUID`
551
+ The UUID of the visit sequence to tag.
552
+ tags : `str`
553
+ One or more tag names to add.
554
+ """
555
+ if len(tags) < 1:
556
+ raise ValueError("At least one tag must be requested.")
557
+
558
+ vsarch.tag(uuid, *tags)
559
+
560
+
561
+ @vseqarchive.command()
562
+ @click.argument("uuid", type=click.UUID)
563
+ @click.argument("tag", type=str)
564
+ @click.pass_obj
565
+ def untag(vsarch: VisitSequenceArchiveMetadata, uuid: UUID, tag: str) -> None:
566
+ """Remove a tag from a visit sequence.
567
+
568
+ Parameters
569
+ ----------
570
+ vsarch : `VisitSequenceArchiveMetadata`
571
+ The interface to the metadata database.
572
+ uuid : `UUID`
573
+ The UUID of the visit sequence to modify.
574
+ tag : `str`
575
+ The name of the tag to remove.
576
+ """
577
+ vsarch.untag(uuid, tag)
578
+
579
+
580
+ @vseqarchive.command()
581
+ @click.argument("uuid", type=click.UUID)
582
+ @click.argument("comment")
583
+ @click.option(
584
+ "--author",
585
+ default=None,
586
+ help="The author of the comment",
587
+ )
588
+ @click.pass_obj
589
+ def comment(
590
+ vsarch: VisitSequenceArchiveMetadata, uuid: UUID, comment: str, author: str | None = None
591
+ ) -> None:
592
+ """Attach a comment to a visit sequence in the metadata database.
593
+
594
+ Parameters
595
+ ----------
596
+ vsarch : `VisitSequenceArchiveMetadata`
597
+ The interface to the metadata database.
598
+ uuid : `UUID`
599
+ The UUID of the visit sequence to comment on.
600
+ comment : `str`
601
+ The comment text to attach.
602
+ author : `str`, optional
603
+ The author of the comment. If omitted the comment will be recorded
604
+ without an author field.
605
+ """
606
+ vsarch.comment(uuid, comment, author)
607
+
608
+
609
+ @vseqarchive.command()
610
+ @click.argument("uuid", type=click.UUID)
611
+ @click.pass_obj
612
+ def get_comments(vsarch: VisitSequenceArchiveMetadata, uuid: UUID) -> None:
613
+ """Retrieve all comments attached to a specific visit sequence.
614
+
615
+ Parameters
616
+ ----------
617
+ uuid : `UUID`
618
+ The UUID of the visit sequence whose comments should be fetched.
619
+ """
620
+ comments_df = vsarch.get_comments(uuid)
621
+ if not comments_df.empty:
622
+ # Pandas will include the index by default; omit it.
623
+ click.echo(comments_df.to_csv(sep="\t", index=False).rstrip("\n"))
624
+
625
+
626
+ @vseqarchive.command()
627
+ @click.argument("uuid", type=click.UUID)
628
+ @click.argument("origin", type=click.Path(exists=True))
629
+ @click.argument("file_type", type=click.STRING)
630
+ @click.option(
631
+ "--archive-base",
632
+ default=ARCHIVE_URL,
633
+ show_default=True,
634
+ help="Base directory for the archive (e.g. file://data/archive).",
635
+ )
636
+ @click.option(
637
+ "--update",
638
+ is_flag=True,
639
+ help="If set, overwrite an existing record for the same visitseq_uuid and file_type.",
640
+ )
641
+ @click.pass_obj
642
+ def archive_file(
643
+ vsarch: VisitSequenceArchiveMetadata,
644
+ uuid: UUID,
645
+ origin: str,
646
+ file_type: str,
647
+ archive_base: str,
648
+ update: bool,
649
+ ) -> None:
650
+ """Archive a file and register its location in the metadata database.
651
+
652
+ Parameters
653
+ ----------
654
+ vsarch : `VisitSequenceArchiveMetadata`
655
+ The metadata interface instance.
656
+ uuid : `UUID`
657
+ The UUID of the visit sequence that the file should be linked to.
658
+ origin : `str`
659
+ Path to the local file to be archived.
660
+ file_type : `str`
661
+ Identifier for the type of file being archived.
662
+ archive_base : `str`, optional
663
+ Base location of the archive.
664
+ update : `bool`, optional
665
+ If true, an existing record for the same
666
+ ``visitseq_uuid``/``file_type`` combination is updated rather
667
+ than raising an error.
668
+ """
669
+ # Convert the base path string into a ResourcePath
670
+ archive_base_rp = ResourcePath(archive_base)
671
+
672
+ archived_location = add_file(vsarch, uuid, origin, file_type, archive_base_rp, update=update)
673
+
674
+ click.echo(archived_location.geturl())
675
+
676
+
677
+ @vseqarchive.command()
678
+ @click.argument("uuid", type=click.UUID)
679
+ @click.argument("file_type", type=click.STRING)
680
+ @click.argument("destination", type=click.Path(exists=False))
681
+ @click.pass_obj
682
+ def get_file(
683
+ vsarch: VisitSequenceArchiveMetadata,
684
+ uuid: UUID,
685
+ file_type: str,
686
+ destination: str,
687
+ ) -> None:
688
+ """Retrieve a registered file and copy it to a local destination.
689
+
690
+ Parameters
691
+ ----------
692
+ vsarch : `VisitSequenceArchiveMetadata`
693
+ The metadata interface used to look up the file URL.
694
+ uuid : `UUID`
695
+ The unique identifier of the visit sequence containing the
696
+ requested file.
697
+ file_type : `str`
698
+ The handle for the type of file to retrieve (e.g. ``visits``).
699
+ destination : `str`
700
+ Path where the retrieved file should be written. If the
701
+ destination does not exist it will be created. For files
702
+ registered with the ``visits`` type and a SQLite3 extension
703
+ suffix, the file will be downloaded as an HDF5 ``visits.h5`` file,
704
+ converted back to the original opsim SQLite format, and written to
705
+ ``destination``.
706
+ """
707
+ file_url = vsarch.get_file_url(uuid, file_type)
708
+ origin_rp = ResourcePath(file_url)
709
+ if file_type == "visits" and Path(destination).suffix.lower() in SQLITE_EXTINSIONS:
710
+ with TemporaryDirectory() as temp_dir:
711
+ h5_destination = ResourcePath(temp_dir).join("visits.h5")
712
+ h5_destination.transfer_from(origin_rp, "copy")
713
+ click.echo(f"Copied {origin_rp.geturl()} to {h5_destination.geturl()}")
714
+ hdf5_to_opsimdb(h5_destination.ospath, destination)
715
+ click.echo(f"Converted to opsim and wrote to {destination}")
716
+ else:
717
+ destination_rp = ResourcePath(destination)
718
+ destination_rp.transfer_from(origin_rp, "copy")
719
+ click.echo(f"Copied {origin_rp.geturl()} to {destination_rp.geturl()}")
720
+
721
+
722
+ @vseqarchive.command()
723
+ @click.argument("uuid", type=click.UUID)
724
+ @click.argument("visits_file", type=click.Path(exists=True))
725
+ @click.argument(
726
+ "columns",
727
+ type=str,
728
+ required=False,
729
+ nargs=-1,
730
+ )
731
+ @click.pass_obj
732
+ def add_nightly_stats(
733
+ vsarch: VisitSequenceArchiveMetadata,
734
+ uuid: UUID,
735
+ visits_file: str,
736
+ columns: tuple[str, ...],
737
+ ) -> None:
738
+ """Compute nightly statistics for a visit sequence and add
739
+ them to the visit sequence metadata database.
740
+
741
+ Parameters
742
+ ----------
743
+ vsarch : `VisitSequenceArchiveMetadata`
744
+ The metadata interface.
745
+ uuid : `UUID`
746
+ The UUID of the visit sequence to attach the statistics to.
747
+ visits_file : `str`
748
+ Path to an HDF5 file containing a ``visits`` dataset.
749
+ columns : `tuple`
750
+ List of columns for which stats should be added.
751
+ """
752
+ # Load the visits table from the HDF5 file
753
+ visits_df = pd.read_hdf(visits_file, key="observations")
754
+ assert isinstance(visits_df, pd.DataFrame), "Expected a DataFrame from key 'observations'"
755
+
756
+ # Compute the nightly statistics, passing the requested columns if any
757
+ if columns:
758
+ stats_df = compute_nightly_stats(visits_df, columns=tuple(columns))
759
+ else:
760
+ stats_df = compute_nightly_stats(visits_df)
761
+
762
+ # Insert into the database
763
+ vsarch.insert_nightly_stats(uuid, stats_df)
764
+
765
+ # Print the statistics as TSV
766
+ tsv = stats_df.to_csv(sep="\t", index=False).rstrip("\n")
767
+ click.echo(tsv)
768
+
769
+
770
+ @vseqarchive.command()
771
+ @click.argument("uuid", type=click.UUID)
772
+ @click.pass_obj
773
+ def query_nightly_stats(vsarch: VisitSequenceArchiveMetadata, uuid: UUID) -> None:
774
+ """Retrieve nightly statistics for a visit sequence and
775
+ print them as a tab‑separated table.
776
+
777
+ Parameters
778
+ ----------
779
+ vsarch : `VisitSequenceArchiveMetadata`
780
+ The metadata interface.
781
+ uuid : `UUID`
782
+ The unique identifier of the visit sequence.
783
+ """
784
+ stats_df = vsarch.query_nightly_stats(uuid)
785
+ output = stats_df.to_csv(sep="\t", index=False).rstrip("\n") if not stats_df.empty else ""
786
+ click.echo(output)
787
+
788
+
789
+ @vseqarchive.command()
790
+ @click.pass_obj
791
+ def record_conda_env(vsarch: VisitSequenceArchiveMetadata) -> None:
792
+ """Record the current Conda environment in the metadata database,
793
+ and print the hash used as a key for the env in the database.
794
+
795
+ Parameters
796
+ ----------
797
+ vsarch : `VisitSequenceArchiveMetadata`
798
+ The metadata interface.
799
+ """
800
+ env_hash, env_json = compute_conda_env()
801
+ vsarch.record_conda_env(env_hash, env_json)
802
+ click.echo(env_hash.hex())
803
+
804
+
805
+ @vseqarchive.command("import-proto")
806
+ @click.argument("archive_base", type=str)
807
+ @click.argument("sim_date", type=str)
808
+ @click.argument("sim_index", type=str)
809
+ @click.option(
810
+ "--proto-sim-archive-url",
811
+ default="s3://rubin:rubin-scheduler-prenight/opsim/",
812
+ show_default=True,
813
+ help="Base URI of the prototype simulation archive.",
814
+ )
815
+ @click.pass_obj
816
+ def import_proto(
817
+ vsarch: VisitSequenceArchiveMetadata,
818
+ archive_base: str,
819
+ sim_date: str,
820
+ sim_index: str,
821
+ proto_sim_archive_url: str = "s3://rubin:rubin-scheduler-prenight/opsim/",
822
+ ) -> None:
823
+ """Import a simulation from the prototype simulation archive,
824
+ and print the UUID of the resultant entry in the destintation
825
+ archive.
826
+
827
+ Parameters
828
+ ----------
829
+ vsarch : `VisitSequenceArchiveMetadata`
830
+ The metadata interface instance.
831
+ archive_base :`str`
832
+ Base location of the destination archive.
833
+ sim_date : `str`
834
+ The ISO‑formatted date (e.g. ``'2025-03-12'``) by
835
+ which the prototype simulation is indexed.
836
+ sim_index : `str`
837
+ The index of the simulation which completes the
838
+ identification of the simulation in the prototype.
839
+ proto_sim_archive_url : `str`, optional
840
+ Base URI of the prototype simulation archive. Defaults to
841
+ ``s3://rubin:rubin-scheduler-prenight/opsim/``.
842
+ """
843
+ sim_uuid = vsarch.import_sim_from_prototype_sim_archive(
844
+ archive_base,
845
+ sim_date,
846
+ sim_index,
847
+ proto_sim_archive_url,
848
+ )
849
+ click.echo(sim_uuid)
850
+
851
+
852
+ @vseqarchive.command("export-proto")
853
+ @click.argument("uuid", type=click.UUID)
854
+ @click.option(
855
+ "--proto-sim-archive-url",
856
+ default="s3://rubin:rubin-scheduler-prenight/opsim/",
857
+ show_default=True,
858
+ help="Base URL of the prototype simulation archive to which to export.",
859
+ )
860
+ @click.pass_obj
861
+ def export_proto(
862
+ vsarch: VisitSequenceArchiveMetadata,
863
+ uuid: UUID,
864
+ proto_sim_archive_url: str = "s3://rubin:rubin-scheduler-prenight/opsim/",
865
+ ) -> None:
866
+ """Export a simulation from the visit sequence archive
867
+ to the prototype simulation archive, and print the
868
+ URL in the prototype sim archive.
869
+
870
+ Parameters
871
+ ----------
872
+ vsarch : `VisitSequenceArchiveMetadata`
873
+ The metadata interface instance.
874
+ uuid : `UUID`
875
+ UUID of the simulation to export.
876
+ proto_sim_archive_url : `str`
877
+ Base URL of the prototype simulation archive to which the simulation
878
+ should be exported.
879
+ """
880
+ # Import here to avoid circular imports
881
+ from .prototype import export_sim_to_prototype_sim_archive
882
+
883
+ destination_rp = export_sim_to_prototype_sim_archive(
884
+ archive_metadata=vsarch,
885
+ sim_uuid=uuid,
886
+ proto_sim_archive_url=proto_sim_archive_url,
887
+ )
888
+ click.echo(destination_rp.geturl())
889
+
890
+
891
+ @vseqarchive.command()
892
+ @click.argument("dayobs", type=click.DateTime(formats=["%Y-%m-%d", "%Y%m%d"]))
893
+ @click.argument("telescope", type=click.Choice(["simonyi", "auxtel"]))
894
+ @click.option(
895
+ "--destination",
896
+ default=PRENIGHT_INDEX_URL,
897
+ type=str,
898
+ help="Base of the resource to which to write the prenight index.",
899
+ )
900
+ @click.pass_obj
901
+ def make_prenight_index(
902
+ vsarch: VisitSequenceArchiveMetadata,
903
+ dayobs: str | datetime.datetime | datetime.date | int | Time,
904
+ telescope: str,
905
+ destination: str,
906
+ ) -> None:
907
+ """Write a json index of prenight for a telescope and dayobs to a resource.
908
+
909
+ Parameters
910
+ ----------
911
+ vsarch : `VisitSequenceArchiveMetadata`
912
+ The metadata interface instance.
913
+ dayobs : `datetime` or `date` or `int` or `str`
914
+ A datetime in the desired dayobs
915
+ telescope : `str`
916
+ The telescope, either ``simonyi`` or ``auxtel``
917
+ destination : `str`
918
+ Base of the resource to which to write the prenight index.
919
+ """
920
+ dayobs = dayobs_to_date(dayobs)
921
+ assert isinstance(dayobs, datetime.date)
922
+
923
+ prenights = vsarch.sims_on_night_with_stats(
924
+ dayobs, tags=("prenight",), telescope=telescope, max_simulation_age=4000
925
+ ).set_index("visitseq_uuid")
926
+
927
+ def to_hex(data: memoryview | None) -> str | None:
928
+ if data is None:
929
+ return None
930
+
931
+ assert isinstance(data, memoryview)
932
+ return data.hex()
933
+
934
+ prenights["conda_env_sha256"] = prenights["conda_env_sha256"].apply(to_hex)
935
+
936
+ def to_str_dayobs(d: datetime.date | None) -> str | None:
937
+ if d is None:
938
+ return None
939
+ return d.isoformat()
940
+
941
+ for column in ("sim_creation_day_obs", "first_day_obs", "last_day_obs", "parent_last_day_obs"):
942
+ prenights[column] = prenights[column].apply(to_str_dayobs)
943
+
944
+ # Convert UUID type into something to_json can deal with.
945
+ prenights.index = prenights.index.map(str)
946
+ for row in prenights.index:
947
+ parent_visitseq_uuid = prenights.loc[row, "parent_visitseq_uuid"]
948
+ if isinstance(parent_visitseq_uuid, UUID):
949
+ prenights.loc[row, "parent_visitseq_uuid"] = str(parent_visitseq_uuid)
950
+
951
+ table_json = prenights.to_json(orient="index", date_format="iso", indent=4)
952
+
953
+ destination_dir_rp = (
954
+ ResourcePath(destination)
955
+ .join(telescope)
956
+ .join(str(dayobs.year))
957
+ .join(str(dayobs.month), forceDirectory=True)
958
+ )
959
+ destination_rp = destination_dir_rp.join(f"{telescope}_prenights_for_{dayobs.isoformat()}.json")
960
+ destination_rp.write(table_json.encode())
961
+ click.echo(destination_rp.geturl())
962
+
963
+
964
+ if __name__ == "__main__":
965
+ vseqarchive()