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,1117 @@
1
+ """Second generation hourglass plotting classes."""
2
+
3
+ # pylint: disable=too-many-arguments
4
+ # pylint: disable=super-init-not-called
5
+ # pylint: disable=too-many-instance-attributes
6
+ # pylint: disable=too-few-public-methods
7
+ __all__ = (
8
+ "GeneralHourglassPlot",
9
+ "CategoricalHourglassPlotMixin",
10
+ "RangeHourglassCategoricalPlot",
11
+ "MonthHourglassCategoricalPlot",
12
+ "MonthHourglassUsePlot",
13
+ "TimeUseHourglassPlotMixin",
14
+ "MonthHourglassPlot",
15
+ "YearHourglassCategoricalPlot",
16
+ "YearHourglassPlot",
17
+ "YearHourglassUsePlot",
18
+ )
19
+
20
+ import calendar
21
+ import copy
22
+ import logging
23
+ from collections import OrderedDict
24
+
25
+ import astropy.coordinates
26
+ import astropy.time
27
+ import matplotlib as mpl
28
+ import matplotlib.pyplot as plt
29
+ import numpy as np
30
+ import pandas as pd
31
+ from astropy import units as u
32
+ from rubin_scheduler.utils.ddf_locations import ddf_locations
33
+ from rubin_scheduler.utils.riseset import riseset_times
34
+
35
+ from .plot_handler import BasePlotter
36
+
37
+
38
+ class GeneralHourglassPlot(BasePlotter):
39
+ """Make an hourglass plot
40
+
41
+ Parameters
42
+ ----------
43
+ tz : `str`
44
+ The timezone to use
45
+ site : `str`
46
+ The site name (sent to `astropy.coordinates.EarthLocation.of_site`)
47
+ solar_time : `bool`
48
+ Use solar time as the x axis (instead of civil time)?
49
+ marked_ra : `dict`
50
+ A dictionary of RA values (in deg) for which to label transit lines.
51
+
52
+ A general feature of the hourglass plot is that you can pass more
53
+ data (such as the metric values, calculated for the entire survey)
54
+ to the plotter than are actually included in the plot. The plotter
55
+ will pull out the select subset of data, defined by kwargs.
56
+
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ tz="Chile/Continental",
62
+ site="Cerro Pachon",
63
+ solar_time=True,
64
+ marked_ra=None,
65
+ ):
66
+ self.plot_type = "GeneralizedHourglass"
67
+ self.object_plotter = False # pylint: disable=invalid-name
68
+ self.default_plot_dict = {
69
+ "title": None,
70
+ "xlabel": "Hours after midnight",
71
+ "ylabel": "MJD",
72
+ "x_min": -6,
73
+ "x_max": 6,
74
+ "figsize": None,
75
+ "cmap": plt.get_cmap("viridis"),
76
+ "colorbar": True,
77
+ "legend": False,
78
+ "legend_ncols": 2,
79
+ "legend_bbox_to_anchor": (0.9, 0.0),
80
+ "legend_loc": "lower right",
81
+ }
82
+ self.plot_dict = copy.copy(self.default_plot_dict) # pylint: disable=invalid-name
83
+ self.tz = tz # pylint: disable=invalid-name
84
+ if isinstance(site, str):
85
+ self.site = astropy.coordinates.EarthLocation.of_site(site)
86
+ else:
87
+ self.site = site
88
+
89
+ self.solar_time = solar_time
90
+ self.color_map = {}
91
+ self.marked_ra = {} if marked_ra is None else marked_ra
92
+
93
+ def _add_axis_labels(self, ax): # pylint: disable=invalid-name, no-self-use
94
+ """Add title/x/y labels to one set of axes.
95
+
96
+ Parameters
97
+ ----------
98
+ ax : `matplotlib.axes.Axes`
99
+ The axis to which to add labels
100
+ """
101
+ # Isolate code to add axis titles and labels because we need
102
+ # to be able to call it independently of the actually plotting,
103
+ # so that when we plot grids of axes on the same figure
104
+ # we don't have overlaps and repeats.
105
+ ax.set_title(self.plot_dict["title"])
106
+ ax.set_xlabel(self.plot_dict["xlabel"])
107
+ ax.set_ylabel(self.plot_dict["ylabel"])
108
+
109
+ def _plot(self, fig, intervals):
110
+ """Create axes for the data and add the data itself.
111
+
112
+ This method does not add the legend or color bar. Use the
113
+ __call__ method to combine all needed steps: handling creation
114
+ of the intervals DataFrame, calling this method, and then
115
+ adding any necessary legends or colorbars.
116
+
117
+ Parameters
118
+ ----------
119
+ fig : `matplotlib.figure.Figure`
120
+ figure on which to plot
121
+ intervals : `pandas.DataFrame`
122
+ data to be plotted, with "mjd", "duration", and "value" columns.
123
+ "duration" should be in units of hours.
124
+
125
+ Returns
126
+ -------
127
+ color_mappable : `matplotlib.cm.ScalarMappable`
128
+ map of scalar data to colors
129
+ axes : numpy.array
130
+ a numpy array of matplotlib axes.
131
+
132
+ """
133
+
134
+ # This is similar to the __call__ method
135
+ # in other plotters, but isolates the making of the plot itself
136
+ # from other things __call__ needs to do, such as reformat the
137
+ # data and add figure-wide elements like the legend and color
138
+ # bar, so that these tasks can be replaced independently in
139
+ # subclases without needing to repeat the other elements of
140
+ # call, which might not change.
141
+
142
+ # Subclasses should override this method to change
143
+ # how the plotting itself is done, as distinct from
144
+ # restructuring the data or modifying other elements of the
145
+ # figure such as the color bar and legend.
146
+
147
+ ax = fig.add_axes([0, 0, 1, 1]) # pylint: disable=invalid-name
148
+ color_mappable = self._plot_one_axis(intervals, ax)
149
+ self._add_axis_labels(ax, self.plot_dict)
150
+ return [color_mappable, np.array([ax])]
151
+
152
+ def _plot_one_axis(self, intervals, ax, epoch_mjd=None): # pylint: disable=invalid-name
153
+ """Actually add the data to one axis of an hourglass plot
154
+
155
+ includes adding the twilight/transit lines, does not include any
156
+ supporting infrastructure or plot elements: it does not create
157
+ the figure or axis, or add labels, color bars, or other
158
+ elements of the plot that do not represent the data itself.
159
+
160
+ Parameters
161
+ ----------
162
+ intervals : `pandas.DataFrame`
163
+ data to be plotted, with "mjd", "duration", and "value" columns.
164
+ "duration" should be in units of hours.
165
+ ax : `matplotlib.axes.Axes`
166
+ the axes on which to plot data
167
+ epoch_mjd : `float`
168
+ the MJD used to set y limits for the plot
169
+ """
170
+
171
+ # Add content relevant to an individual axis.
172
+ # It is separated from _plot so that it can be called
173
+ # in different subplots, while _plot drives the the content
174
+ # of all subplots.
175
+
176
+ # Add the 'block' data
177
+ if len(intervals) > 0:
178
+ if self.solar_time:
179
+ (
180
+ night_mjds,
181
+ hours_after_midnight,
182
+ ) = _compute_hours_after_solar_midnight(intervals["mjd"].values, self.site)
183
+ else:
184
+ (
185
+ night_mjds,
186
+ hours_after_midnight,
187
+ ) = _compute_hours_after_midnight(intervals["mjd"].values)
188
+
189
+ color_mappable = self._plot_value_bars(
190
+ intervals["value"],
191
+ night_mjds,
192
+ hours_after_midnight,
193
+ intervals["duration"],
194
+ epoch_mjd,
195
+ ax,
196
+ )
197
+ else:
198
+ color_mappable = None
199
+
200
+ try:
201
+ ax.set_ylim(self.plot_dict["y_max"], self.plot_dict["y_min"])
202
+ except KeyError:
203
+ ax.set_ylim(ax.get_ylim()[-1] + 1, -0.5)
204
+ ax.set_xlim(self.plot_dict["x_min"], self.plot_dict["x_max"])
205
+
206
+ start_mjd = ax.get_ylim()[1] + epoch_mjd
207
+ end_mjd = ax.get_ylim()[0] + epoch_mjd
208
+ # Add the twilight boundaries and moon transit lines
209
+ _astron_hourglass(start_mjd + 0.5, end_mjd + 1, ax, self.site, self.solar_time)
210
+ # Add the transits
211
+ self._plot_transits(start_mjd + 0.5, end_mjd + 1, ax)
212
+
213
+ return color_mappable
214
+
215
+ def _plot_value_bars( # pylint: disable=invalid-name
216
+ self,
217
+ values,
218
+ night_mjds,
219
+ hours_after_midnight,
220
+ duration,
221
+ epoch_mjd,
222
+ ax,
223
+ ):
224
+ """Add the color bar blocks for the metric values.
225
+
226
+ Parameters
227
+ ----------
228
+ values : `numpy.array`
229
+ values to assign to the value bars
230
+ night_mjds : `numpy.array`
231
+ array if integer MJDs of the nights
232
+ for each bar.
233
+ hours_after_midnight : `numpy.array`
234
+ array of times (hours after midnight) for the
235
+ start times of each bar.
236
+ duration : `numpy.array`
237
+ array of durations (in hours) for each bar
238
+ epoch_mjd : `int`
239
+ the reference MJD of the vertical axis
240
+ of the plot, for example the start of the
241
+ month if the vertical axis is to be labeled
242
+ with the day of month.
243
+ ax : `matplotlib.axes.Axes`
244
+ the axes on which to plot the bars
245
+
246
+ Returns
247
+ -------
248
+ color_mappable : `matplotlib.cm.ScalarMappable`
249
+ relates values to colors for the bars
250
+ (see the matplotlib documentation)
251
+ """
252
+
253
+ # Drives the plotting of the elements that represent the metric itself,
254
+ # changing as a function of time.
255
+
256
+ # Find the colors to be used.
257
+ colors, color_mappable = self._map_colors(values)
258
+
259
+ # Add the bars.
260
+ ax.bar(
261
+ hours_after_midnight,
262
+ height=0.8,
263
+ width=duration,
264
+ bottom=night_mjds - epoch_mjd - 0.4,
265
+ color=colors,
266
+ linewidth=0,
267
+ align="edge",
268
+ )
269
+
270
+ return color_mappable
271
+
272
+ def _map_colors(self, values): # pylint: disable=invalid-name, no-self-use
273
+ """Get colors corresponding to values
274
+
275
+ Paramaters
276
+ ----------
277
+ values : `numpy.array`
278
+ Values for which to get colors
279
+
280
+ Returns
281
+ -------
282
+ colors : `numpy.array`
283
+ colors corresponding to the provided values
284
+ color_mappable : `matplotlib.cm.ScalarMappable`
285
+ relates values to colors for the bars
286
+ (see the matplotlib documentation)
287
+ """
288
+
289
+ # Manages the mapping of metric values to colors in the
290
+ # horizonatal lines in the hourglass plot.
291
+ cmap = self.plot_dict["cmap"]
292
+ if values.max() == values.min():
293
+ colors = cmap(0.5)
294
+ else:
295
+ try:
296
+ color_limits = self.plot_dict["color_limits"]
297
+ except KeyError:
298
+ color_limits = (values.min(), values.max())
299
+ colors = cmap((values - color_limits[0]) / (color_limits[1] - color_limits[0]))
300
+
301
+ norm = mpl.colors.Normalize(vmin=color_limits[0], vmax=color_limits[1])
302
+ color_mappable = plt.cm.ScalarMappable(cmap=cmap, norm=norm)
303
+ color_mappable.set_array([])
304
+
305
+ return colors, color_mappable
306
+
307
+ def __call__(self, metric_value, slicer, user_plot_dict, fig=None):
308
+ """Restructure the metric data to use, and build the figure.
309
+
310
+ Parameters
311
+ ----------
312
+ metric_value : `numpy.ma.MaskedArray`
313
+ The metric values from the bundle.
314
+ slicer : `rubin_sim.maf.slicers.TwoDSlicer`
315
+ The slicer.
316
+ user_plot_dict: `dict`
317
+ Dictionary of plot parameters set by user
318
+ (overrides default values).
319
+ fig : `matplotlib.figure.Figure`
320
+ Matplotlib figure number to use. Default = None, starts new figure.
321
+
322
+ Returns
323
+ -------
324
+ fig : `matplotlib.figure.Figure`
325
+ Figure with the plot.
326
+ """
327
+ # Highest level driver for the plotter.
328
+ # Prepares data structures and figure-wide elements
329
+
330
+ # Building the figure entails plotting it the relevant
331
+ # class._plot function and adding plot decorations with
332
+ # the relevant class._add_colorbar and class._add_figure_legend
333
+ # methods.
334
+
335
+ intervals = pd.DataFrame(
336
+ {
337
+ "mjd": slicer.slice_points["mjd"],
338
+ "duration": (slicer.slice_points["duration"] * u.s).to_value(
339
+ u.hour # pylint: disable=no-member
340
+ ),
341
+ "value": metric_value,
342
+ }
343
+ )
344
+ # Set up a copy of the plot dict to contain entries relevant
345
+ # for this plot
346
+ self.plot_dict = copy.copy(self.default_plot_dict) # pylint: disable=invalid-name
347
+ self.plot_dict.update(user_plot_dict)
348
+
349
+ # Generate the figure
350
+ if fig is None:
351
+ fig = plt.figure(figsize=self.plot_dict["figsize"])
352
+
353
+ # Add the plots
354
+ color_mappable, axes = self._plot(fig, intervals)
355
+
356
+ # add the colorbar, if requested
357
+ if self.plot_dict["colorbar"] and color_mappable is not None:
358
+ self._add_colorbar(fig, color_mappable, axes)
359
+
360
+ # add the legend, if requested
361
+ if self.plot_dict["legend"] and len(self.color_map) > 0:
362
+ fig = self._add_figure_legend(fig, axes)
363
+
364
+ return fig
365
+
366
+ def _add_figure_legend(self, fig, axes):
367
+ """Creates and adds the figure legend.
368
+
369
+ Parameters
370
+ ----------
371
+ fig : `matplotlib.figure.Figure`
372
+ figure on which to plot
373
+ axes : `matplotlib.axes.Axes`
374
+ axes from which to extract
375
+ automatic labels.
376
+
377
+ Returns
378
+ -------
379
+ fig : `matplotlib.figure.Figure`
380
+ The matplotlib figure
381
+ """
382
+ # Creates and adds the figure legend. This method follows two
383
+ # stages: first, it adds entries for each element in the color
384
+ # map (defined in self.color_map). Then, it adds any
385
+ # additional elements of the plats that were given matplotlib
386
+ # labels, but are not already in the legend by way of the
387
+ # color map. The user may limit the enties in the color map
388
+ # by way of the legend_elements key in the plot_dict, which if
389
+ # preset limits the elements of the key to only those
390
+ # requested.
391
+
392
+ # Build a dictionary and build lable and handle lists
393
+ # later, so that we can ensure that the order of the
394
+ # labels in the keys matches the order specified by the
395
+ # user, if present.
396
+ handle_dict = OrderedDict()
397
+
398
+ # Label everything we have assigned a color
399
+ for label in self.color_map:
400
+ if "legend_elements" in self.plot_dict:
401
+ if label not in self.plot_dict["legend_elements"]:
402
+ continue
403
+
404
+ handle_dict[label] = mpl.patches.Patch(facecolor=self.color_map[label], label=label)
405
+
406
+ # If we missed anything we have assigned a label while
407
+ # plotting, automatically pick it up, but avoid
408
+ # duplication
409
+ auto_handles, auto_labels = axes.T.flatten()[-1].get_legend_handles_labels()
410
+ for auto_handle, auto_label in zip(auto_handles, auto_labels):
411
+ handle_dict[auto_label] = auto_handle
412
+
413
+ # If the user specified which elements they wanted labels,
414
+ # include only them (if present).
415
+ # Otherwise, unclude all defined labels
416
+ if "legend_elements" in self.plot_dict:
417
+ desired_labels = self.plot_dict["legend_elements"]
418
+ else:
419
+ desired_labels = handle_dict.keys()
420
+
421
+ labels = [lbl for lbl in desired_labels if lbl in handle_dict]
422
+ handles = [handle_dict[lbl] for lbl in desired_labels if lbl in handle_dict]
423
+
424
+ fig.legend(
425
+ handles,
426
+ labels,
427
+ loc=self.plot_dict["legend_loc"],
428
+ ncol=self.plot_dict["legend_ncols"],
429
+ bbox_to_anchor=self.plot_dict["legend_bbox_to_anchor"],
430
+ )
431
+
432
+ return fig
433
+
434
+ def _add_colorbar(self, fig, color_mappable, axes): # pylint: disable=invalid-name, no-self-use
435
+ """Add a colorbar.
436
+
437
+ Parameters
438
+ ----------
439
+ fig : `matplotlib.figure.Figure`
440
+ figure on which to plot
441
+ color_mappable : `matplotlib.cm.ScalarMappable`
442
+ map of scalar data to colors
443
+ axes : `matplotlib.axes.Axes`
444
+ The axes for which to add the colorbar
445
+ """
446
+
447
+ colorbar_kwargs = {"fraction": 0.05}
448
+ if "colorbar_ticks" in self.plot_dict:
449
+ colorbar_kwargs["ticks"] = self.plot_dict["colorbar_ticks"]
450
+ fig.colorbar(color_mappable, ax=axes, **colorbar_kwargs)
451
+
452
+ def _plot_dates( # pylint: disable=invalid-name
453
+ self,
454
+ intervals,
455
+ start_tstamp,
456
+ end_tstamp,
457
+ ax,
458
+ epoch_tstamp=None,
459
+ ):
460
+ """Plot data on one axis for a range of dates.
461
+
462
+ Parameters
463
+ ----------
464
+ intervals : `pandas.DataFrame`
465
+ data to be plotted, with "mjd", "duration", and "value" columns.
466
+ "duration" should be in units of hours.
467
+ start_tstamp : `pandas.Timestamp`
468
+ The first time to include in the axes
469
+ end_tstamp : `pandas.Timestamp`
470
+ The last time to include in the axes
471
+ ax : `matplotlib.axes.Axes`
472
+ the axes on which to plot data
473
+ epoch_tstamp : `float`
474
+ the time used to set y limits for the plot
475
+
476
+
477
+ Returns
478
+ -------
479
+ color_mappable : `matplotlib.cm.ScalarMappable`
480
+ map of data to colors
481
+ """
482
+ if self.plot_dict is None:
483
+ self.plot_dict = {}
484
+
485
+ start_mjd = start_tstamp.to_julian_date() - 2400000.5
486
+ end_mjd = end_tstamp.to_julian_date() - 2400000.5
487
+ epoch_mjd = 0 if epoch_tstamp is None else epoch_tstamp.to_julian_date() - 2400000.5
488
+
489
+ these_intervals = intervals.query(f"{start_mjd}<mjd<{end_mjd}")
490
+ color_mappable = self._plot_one_axis(these_intervals, ax, epoch_mjd)
491
+ return color_mappable
492
+
493
+ def _plot_month(self, intervals, month, year, ax):
494
+ """Plots data as function of date in the month.
495
+
496
+ intervals : `pandas.DataFrame`
497
+ data to be plotted, with "mjd", "duration", and "value" columns.
498
+ "duration" should be in units of hours.
499
+ month : `int`
500
+ Month number whose dates to plot
501
+ year : `int`
502
+ Year of the month to plot
503
+ ax : `matplotlib.axes.Axes`
504
+ the axes on which to plot data
505
+
506
+ Returns
507
+ -------
508
+ color_mappable : `matplotlib.cm.ScalarMappable`
509
+ map of data to colors
510
+ """
511
+
512
+ if self.plot_dict is None:
513
+ self.plot_dict = {}
514
+
515
+ start_tstamp = pd.Timestamp(year=year, month=month, day=1, hour=12, tz=self.tz)
516
+ end_tstamp = start_tstamp + pd.DateOffset(months=1)
517
+ epoch_tstamp = pd.Timestamp(year=year, month=month, day=1, hour=0, tz="UTC") - pd.DateOffset(days=1)
518
+ color_mappable = self._plot_dates(
519
+ intervals,
520
+ start_tstamp,
521
+ end_tstamp,
522
+ ax,
523
+ epoch_tstamp=epoch_tstamp,
524
+ )
525
+ ax.set_yticks(np.arange(1, 32, 7))
526
+ return color_mappable
527
+
528
+ def _plot_transits(self, start_mjd, end_mjd, ax): # pylint: disable=invalid-name
529
+ """Plot transit lines on a set of axes.
530
+
531
+ Parameters
532
+ ----------
533
+ start_mjd : `int`
534
+ Start time for the axes
535
+ end_mjd: `int`
536
+ End time for the exes
537
+ ax: `matplotlib.axes.Axes`
538
+ the axes on which to plot transit lines
539
+ """
540
+ # Add transit lines for each of the RA values
541
+ # it self.marked_ra.
542
+ mjds = np.arange(start_mjd, end_mjd + 1, 1)
543
+
544
+ for field in self.marked_ra:
545
+ ra = self.marked_ra[field] # pylint: disable=invalid-name
546
+ try:
547
+ color = self.color_map[field]
548
+ except KeyError:
549
+ continue
550
+
551
+ transit_mjds = _compute_coord_transit_mjds(mjds, ra, self.site)
552
+
553
+ if self.solar_time:
554
+ (
555
+ transit_nights,
556
+ transit_hams,
557
+ ) = _compute_hours_after_solar_midnight(transit_mjds, self.site)
558
+ else:
559
+ transit_nights, transit_hams = _compute_hours_after_midnight(transit_mjds)
560
+
561
+ ax.plot(
562
+ transit_hams,
563
+ transit_nights - start_mjd + 1,
564
+ color=color,
565
+ label=field + "-transit",
566
+ )
567
+
568
+
569
+ #
570
+ # Plot appearance subclasses
571
+ #
572
+
573
+ # The following subclasses change the arrangement of dates on the
574
+ # hourglass plot.
575
+
576
+
577
+ class RangeHourglassPlot(GeneralHourglassPlot):
578
+ """Make an hourglass plot for a range of dates
579
+
580
+ Parameters
581
+ ----------
582
+ start_date : `str`
583
+ The start date (in a format parseable by `pandas.Timestamp`)
584
+ end_date : `str`
585
+ The end date (in a format parseable by `pandas.Timestamp`)
586
+
587
+ Keyward arguments are passed to `GeneralHourglassPlot`
588
+ """
589
+
590
+ def __init__(self, start_date, end_date, **kwargs):
591
+ super().__init__(**kwargs)
592
+ self.plot_type = "RangeHourglass"
593
+ self.start_date = pd.Timestamp(start_date)
594
+ self.end_date = pd.Timestamp(end_date)
595
+
596
+ def _plot(self, fig, intervals):
597
+ """Make the hourglass plot for a range of dates.
598
+
599
+ This method does not add the legend or color bar. Use the
600
+ __call__ method to handle creation of the intervals DataFrame,
601
+ call this method, and then add any necessary legends or
602
+ colorbars.
603
+
604
+ Parameters
605
+ ----------
606
+ fig : `matplotlib.figure.Figure`
607
+ figure on which to plot
608
+ intervals : `pandas.DataFrame`
609
+ data to be plotted, with "mjd", "duration", and "value" columns.
610
+ "duration" should be in units of hours.
611
+
612
+ """
613
+ ax = fig.add_axes([0, 0, 1, 1]) # pylint: disable=invalid-name
614
+ color_mappable = self._plot_dates(intervals, self.start_date, self.end_date, ax)
615
+ self._add_axis_labels(ax, self.plot_dict)
616
+ return color_mappable, np.array([ax])
617
+
618
+
619
+ class MonthHourglassPlot(GeneralHourglassPlot):
620
+ """Make an hourglass plot for a month
621
+
622
+ Parameters
623
+ ----------
624
+ month : `int`
625
+ The month number (1-12).
626
+ year : `int`
627
+ The year.
628
+
629
+ Keyward arguments are passed to `GeneralHourglassPlot`
630
+
631
+ Note that this pulls the chosen month's data out of the metric
632
+ values calculated for the entire survey. Unsubclassed, this class
633
+ expects to obtain float/int data. To use it with other data, its
634
+ subclass that includes the appropriate mixin
635
+ (MonthHourglassCategoricalPlot or MonthHourglassUsePlot) or a
636
+ custom subclass.
637
+
638
+ """
639
+
640
+ def __init__(self, month, year, **kwargs):
641
+ super().__init__(**kwargs)
642
+ self.plot_type = "MonthHourglass"
643
+ self.default_plot_dict["ylabel"] = "day of month"
644
+ self.default_plot_dict["y_max"] = calendar.monthrange(year, month)[1] + 0.5
645
+ self.default_plot_dict["y_min"] = 0.5
646
+ self.month = month
647
+ self.year = year
648
+
649
+ def _plot(self, fig, intervals):
650
+ """Make the hourglass plot for a month.
651
+
652
+ This method does not add the legend or color bar. Use the
653
+ __call__ method to handle creation of the intervals DataFrame,
654
+ call this method, and then add any necessary legends or
655
+ colorbars.
656
+
657
+ Parameters
658
+ ----------
659
+ fig : `matplotlib.figure.Figure`
660
+ figure on which to plot
661
+ intervals : `pandas.DataFrame`
662
+ data to be plotted, with "mjd", "duration", and "value" columns.
663
+ "duration" should be in units of hours.
664
+
665
+ """
666
+
667
+ ax = fig.add_axes([0, 0, 1, 1]) # pylint: disable=invalid-name
668
+ color_mappable = self._plot_month(intervals, self.month, self.year, ax)
669
+ self._add_axis_labels(ax)
670
+ return color_mappable, np.array([ax])
671
+
672
+
673
+ class YearHourglassPlot(GeneralHourglassPlot):
674
+ """Make an array of monthly hourglass plots for a year.
675
+
676
+ Parameters
677
+ ----------
678
+ year : `int`
679
+ The year.
680
+
681
+ Keyward arguments are passed to `GeneralHourglassPlot`
682
+
683
+ Note that this plot pulls the chosen year's data out of the metric
684
+ values calculated for the entire survey. Like the
685
+ MonthHourglassPlot class, this class expects to obtain float/int
686
+ data. To use it with other data, its subclass that includes the
687
+ appropriate mixin (YearHourglassCategoricalPlot or
688
+ YearHourglassUsePlot) or a custom subclass.
689
+
690
+ """
691
+
692
+ def __init__(self, year, **kwargs):
693
+ super().__init__(**kwargs)
694
+ self.year = year
695
+ self.plot_type = "YearHourglass"
696
+ self.default_plot_dict["ylabel"] = "day of month"
697
+ self.default_plot_dict["y_max"] = 31.5
698
+ self.default_plot_dict["y_min"] = 0.5
699
+
700
+ def _plot(self, fig, intervals):
701
+ """Make a series of month plots, arranged for the entire year.
702
+
703
+ This method does not add the legend or color bar. Use the
704
+ __call__ method to handle creation of the intervals DataFrame,
705
+ call this method, and then add any necessary legends or
706
+ colorbars.
707
+
708
+ Parameters
709
+ ----------
710
+ fig : `matplotlib.figure.Figure`
711
+ figure on which to plot
712
+ intervals : `pandas.DataFrame`
713
+ data to be plotted, with "mjd", "duration", and "value" columns.
714
+ "duration" should be in units of hours.
715
+
716
+ """
717
+
718
+ axes = fig.subplots(
719
+ 3,
720
+ 4,
721
+ sharex=True,
722
+ sharey=True,
723
+ gridspec_kw={"wspace": 0.025, "hspace": 0},
724
+ )
725
+
726
+ for month, ax in zip(np.arange(1, 13), axes.T.flatten()): # pylint: disable=invalid-name
727
+ logging.info("Working on %s, %d", calendar.month_name[month], self.year)
728
+ self.plot_dict["y_max"] = calendar.monthrange(self.year, month)[1] + 0.5
729
+ color_mappable = self._plot_month(intervals, month, self.year, ax)
730
+ ax.set_title("")
731
+ ax.set_title(calendar.month_abbr[month], y=1, x=0.005, pad=-15, loc="left")
732
+
733
+ self._add_figure_labels(fig, axes)
734
+ return color_mappable, axes.ravel()
735
+
736
+ def _add_figure_labels(self, fig, axes): # pylint: disable=invalid-name, no-self-use
737
+ """Generate the figure labels.
738
+
739
+ Parameters
740
+ ----------
741
+ fig : `matplotlib.figure.Figure`
742
+ figure to which to add lables
743
+ ax : `matplotlib.axes.Axes`
744
+ The axis to which to add labels
745
+ """
746
+ axes[1, 0].set_ylabel(self.plot_dict["ylabel"])
747
+ fig.suptitle(self.plot_dict["title"], y=0.9)
748
+ fig.text(0.5, 0.05, self.plot_dict["xlabel"], ha="center")
749
+
750
+
751
+ #
752
+ # Mixin classes to change handling of metric data
753
+ #
754
+
755
+ # Mixin classes avoid the need to duplicate code for every appearance
756
+ # type subclass. Rather that need to duplicate code between
757
+ # RangeHourglassPlot, MonthHourglassPlot, and YearHourglassPlot to
758
+ # enable them all to support hourglass data, a single "mixin" class
759
+ # allows the needed modifications to be defined once, and then "mixed
760
+ # in" to each of these classes (see RangeHourglassCategoricalPlot,
761
+ # MonthHourglassCategoricalPlot, and YearHourglassCategoricalPlot
762
+ # below).
763
+
764
+
765
+ class CategoricalHourglassPlotMixin:
766
+ """A mix-in to allow the HourglassPlot to accept categorical data,
767
+ rather than float/int.
768
+ """
769
+
770
+ def __init__(self, *args, **kwargs):
771
+ super().__init__(*args, **kwargs)
772
+ self.object_plotter = True # pylint: disable=invalid-name
773
+ self.plot_type = ( # pylint: disable=invalid-name
774
+ "Categorical" + self.plot_type
775
+ ) # pylint: disable=invalid-name
776
+ self.default_plot_dict["cmap"] = plt.get_cmap("tab10")
777
+ self.default_plot_dict["assigned_colors"] = OrderedDict()
778
+ self.default_plot_dict["legend"] = True
779
+ self.default_plot_dict["colorbar"] = False
780
+ if "marked_ra" not in kwargs:
781
+ self.marked_ra = {}
782
+
783
+ def _map_colors(self, values):
784
+ cmap = self.plot_dict["cmap"]
785
+ assigned_colors = self.plot_dict["assigned_colors"]
786
+ self.color_map = {}
787
+ if len(self.color_map.keys()) == 0:
788
+ self.color_map = _assign_category_colors(
789
+ np.unique(values),
790
+ cmap,
791
+ assigned_colors=assigned_colors,
792
+ )
793
+
794
+ colors = np.array(np.vectorize(self.color_map.get)(values)).T.tolist()
795
+ color_mappable = None
796
+
797
+ return colors, color_mappable
798
+
799
+
800
+ class RangeHourglassCategoricalPlot(CategoricalHourglassPlotMixin, RangeHourglassPlot):
801
+ """Plot categorical data for a range of dates."""
802
+
803
+
804
+ class MonthHourglassCategoricalPlot(CategoricalHourglassPlotMixin, MonthHourglassPlot):
805
+ """Plot categorical data for a Month."""
806
+
807
+
808
+ class YearHourglassCategoricalPlot(CategoricalHourglassPlotMixin, YearHourglassPlot):
809
+ """Plot categorical data for a year."""
810
+
811
+
812
+ class TimeUseHourglassPlotMixin(CategoricalHourglassPlotMixin):
813
+ """A mix-in to allow the HourglassPlot to accept categorical 'use' data
814
+ rather than float/int."""
815
+
816
+ def __init__(self, *args, **kwargs):
817
+ """Customize hourglass plotter for time use"""
818
+ super().__init__(*args, **kwargs)
819
+ self.plot_type = "Use" + self.plot_type # pylint: disable=invalid-name
820
+ self.default_plot_dict["cmap"] = plt.get_cmap("tab10")
821
+ self.default_plot_dict["colorbar"] = False
822
+ self.default_plot_dict["assigned_colors"] = OrderedDict(
823
+ (
824
+ ("wide with only IR", 3),
825
+ ("wide with u, g, or r", 0),
826
+ ("greedy", 1),
827
+ )
828
+ )
829
+
830
+ self.default_plot_dict["legend"] = True
831
+ if "marked_ra" not in kwargs:
832
+ self.marked_ra = {f: c[0] for f, c in ddf_locations().items()}
833
+
834
+
835
+ class MonthHourglassUsePlot(TimeUseHourglassPlotMixin, MonthHourglassPlot):
836
+ """Plot categorical 'use' data for one month."""
837
+
838
+
839
+ class YearHourglassUsePlot(TimeUseHourglassPlotMixin, YearHourglassPlot):
840
+ """Plot categorical 'use' data for one year."""
841
+
842
+
843
+ # internal functions & classes
844
+
845
+
846
+ def _assign_category_colors(uses, cmap, use_colors=None, assigned_colors=None):
847
+ """Set a dictionary of nice colors for the use blocks.
848
+ Options allow specifing pre-defined elements for some categories."""
849
+
850
+ use_colors = OrderedDict() if use_colors is None else use_colors
851
+ assigned_colors = OrderedDict() if assigned_colors is None else assigned_colors
852
+
853
+ available_idxs = list(range(cmap.N))
854
+
855
+ used_colors = set(use_colors.values())
856
+ for cmap_idx in range(cmap.N):
857
+ # Skip gray because it is confusing against background
858
+ this_cmap = cmap(cmap_idx)
859
+ if this_cmap[0] == this_cmap[1] and this_cmap[0] == this_cmap[2]:
860
+ available_idxs.remove(cmap_idx)
861
+
862
+ # If a color has been used already, don't reuse it for something else
863
+ if cmap(cmap_idx) in used_colors:
864
+ available_idxs.remove(cmap_idx)
865
+
866
+ for use, cmap_idx in assigned_colors.items():
867
+ if cmap_idx in available_idxs:
868
+ available_idxs.remove(cmap_idx)
869
+ use_colors[use] = cmap(cmap_idx)
870
+ else:
871
+ assert use_colors[use] == cmap(cmap_idx)
872
+
873
+ for use in uses:
874
+ if use not in use_colors:
875
+ use_idx = available_idxs[0]
876
+ use_colors[use] = cmap(use_idx)
877
+ available_idxs.remove(use_idx)
878
+
879
+ return use_colors
880
+
881
+
882
+ def _compute_hours_after_midnight(mjd, tz="Chile/Continental"): # pylint: disable=invalid-name
883
+ """Calculate the civil 'hours' value for the hourglass plot.
884
+
885
+ Parameters
886
+ ----------
887
+ mjd : `numpy.array`
888
+ array of Modified Julian Dates
889
+ tz : `str`
890
+ The timezone (to be passed to
891
+ `pandas.DatetimeIndex.tz_convret`)
892
+
893
+ Returns
894
+ -------
895
+ night_mjds : `numpy.array`
896
+ array of interger nights for the dates of the provided mjds.
897
+ hours_after_midnight : `numpy.array`
898
+ array of hours after local civil midnight in the timezone
899
+ provided, for each input mjd value.
900
+ """
901
+
902
+ utc_datetimes = pd.to_datetime(mjd + 2400000.5, unit="D", origin="julian").tz_localize("UTC")
903
+ local_times = pd.DatetimeIndex(utc_datetimes).tz_convert(tz)
904
+ night_mjds = np.floor(local_times.to_julian_date() - 2400001).astype(int).values
905
+ hours_after_midnight = (local_times.to_julian_date().values - 2400001.5 - night_mjds) * 24
906
+ return night_mjds, hours_after_midnight
907
+
908
+
909
+ def _compute_hours_after_solar_midnight(mjd, site, tz="Chile/Continental"): # pylint: disable=invalid-name
910
+ """Calculate the solar 'hours' value for the hourglass plot.
911
+
912
+ Parameters
913
+ ----------
914
+ mjd : `numpy.array`
915
+ array of Modified Julian Dates
916
+ tz : `str`
917
+ The timezone (to be passed to
918
+ `pandas.DatetimeIndex.tz_convret`)
919
+
920
+ Returns
921
+ -------
922
+ night_mjds : `numpy.array`
923
+ array of interger nights for the dates of the provided mjds.
924
+ hours_after_midnight : `numpy.array`
925
+ array of hours after mean local solar midnight (antitransit),
926
+ for each input mjd value.
927
+ """
928
+
929
+ times = astropy.time.Time(mjd, format="mjd", location=site)
930
+ mean_solar_jd = times.ut1.mjd + site.lon.deg / 360
931
+ mean_solar_time = astropy.coordinates.Angle(
932
+ mean_solar_jd * 360, unit=u.deg # pylint: disable=no-member
933
+ ).wrap_at(
934
+ 180 * u.deg # pylint: disable=no-member
935
+ )
936
+ hours_after_midnight = (
937
+ mean_solar_time.to_value(u.deg) * 24 / 360.0 # pylint: disable=no-member # pylint: disable=no-member
938
+ ) # pylint: disable=no-member
939
+ utc_datetimes = pd.to_datetime(mjd + 2400000.5, unit="D", origin="julian").tz_localize("UTC")
940
+ local_times = pd.DatetimeIndex(utc_datetimes).tz_convert(tz)
941
+ night_mjds = np.floor(local_times.to_julian_date() - 2400001).astype(int).values
942
+ return night_mjds, hours_after_midnight
943
+
944
+
945
+ def _compute_coord_transit_mjds(mjds, ra, site): # pylint: disable=invalid-name
946
+ """Compute the coordinate transit times.
947
+
948
+ Parameters
949
+ ----------
950
+ mjds : `numpy.array`
951
+ array of Modified Julian Dates for the nights
952
+ for which to calculate transit
953
+ ra : `float`
954
+ R.A. for which to calculate transit
955
+ in degrees
956
+ site : `astropy.coordinates.EarthLocation`
957
+ location of the telescope site
958
+
959
+ Return
960
+ ------
961
+ mjds : `numpy.array`
962
+ array of MJDs of transit times.
963
+ """
964
+ # Used for transit lines for the hourglass plot.
965
+
966
+ # Calculate the local sidereal time at mjds - in degrees (for RA
967
+ # comparison)
968
+ lsts = astropy.time.Time(mjds, format="mjd", location=site).sidereal_time("apparent").deg
969
+ # pylint: disable=invalid-name, no-member
970
+ ha = astropy.coordinates.Angle(lsts - ra, unit=u.deg).wrap_at(180 * u.deg)
971
+ mjds = mjds - ha.to_value(u.deg) / 360
972
+ return mjds
973
+
974
+
975
+ def _compute_moon_transit_mjds(mjds, site):
976
+ """Compute the transit times for the moon,
977
+
978
+ Parameters
979
+ ----------
980
+ mjds : `numpy.array`
981
+ array of Modified Julian Dates for the nights
982
+ for which to calculate transit
983
+ site : `astropy.coordinates.EarthLocation`
984
+ location of the telescope site
985
+
986
+ Return
987
+ ------
988
+ mjds : `numpy.array`
989
+ array of MJDs of transit times.
990
+ """
991
+ # for transit lines for the hourglass plot.
992
+
993
+ for _ in np.arange(3):
994
+ times = astropy.time.Time(mjds, format="mjd", location=site)
995
+ moon_coords = astropy.coordinates.get_body("moon", times)
996
+ # Calculate the local sidereal time at mjds
997
+ lsts = astropy.time.Time(mjds, format="mjd", location=site).sidereal_time("apparent")
998
+ # pylint: disable=invalid-name, no-member
999
+ ha = (lsts - moon_coords.ra).wrap_at(180 * u.deg)
1000
+ mjds = mjds - ha.to_value(u.deg) / 360
1001
+ return mjds
1002
+
1003
+
1004
+ def _astron_hourglass(
1005
+ start_mjd, end_mjd, ax, site, solar_time
1006
+ ): # pylint: disable=invalid-name, too-many-locals, too-many-branches
1007
+ """Add the moon transit, night and twilight start/end lines.
1008
+
1009
+ Parameters
1010
+ ----------
1011
+ start_mjd : `int`
1012
+ Start time for the axes
1013
+ end_mjd: `int`
1014
+ End time for the exes
1015
+ ax: `matplotlib.axes.Axes`
1016
+ the axes on which to plot transit lines
1017
+ site : `astropy.coordinates.EarthLocation`
1018
+ location of the telescope site
1019
+ solar_time : `bool`
1020
+ True to use mean local solar midnight
1021
+ False to use civil midnight
1022
+
1023
+ Returns
1024
+ -------
1025
+ ax: `matplotlib.axes.Axes`
1026
+ the axes on transit lines were plotted
1027
+ """
1028
+
1029
+ mjds = np.arange(start_mjd, end_mjd + 1, 1)
1030
+ start_mjd = mjds[0]
1031
+
1032
+ # Moon transit
1033
+
1034
+ cal_night_mjds = mjds - site.lon.deg / 360
1035
+
1036
+ moon_transit_mjds = _compute_moon_transit_mjds(cal_night_mjds, site)
1037
+
1038
+ if solar_time:
1039
+ (
1040
+ moon_transit_nights,
1041
+ moon_transit_hams,
1042
+ ) = _compute_hours_after_solar_midnight(moon_transit_mjds, site)
1043
+ else:
1044
+ moon_transit_nights, moon_transit_hams = _compute_hours_after_midnight(moon_transit_mjds)
1045
+ # Loop to avoid wrapping
1046
+ moon_lines = np.cumsum(np.diff(moon_transit_hams, prepend=moon_transit_hams[0]) < 0)
1047
+ moon_label = "moon"
1048
+ for moon_line in np.unique(moon_lines):
1049
+ these_hams = moon_transit_hams[moon_lines == moon_line]
1050
+ these_nights = moon_transit_nights[moon_lines == moon_line]
1051
+ ax.plot(
1052
+ these_hams,
1053
+ these_nights - start_mjd + 1,
1054
+ color="yellow",
1055
+ linewidth=8,
1056
+ alpha=0.5,
1057
+ label=moon_label,
1058
+ )
1059
+ moon_label = None
1060
+
1061
+ # Moon rise and set
1062
+ for direction in ("up", "down"):
1063
+ moon_event_mjds = riseset_times(cal_night_mjds, direction, alt=0, body="moon")
1064
+ if solar_time:
1065
+ (
1066
+ moon_event_nights,
1067
+ moon_event_hams,
1068
+ ) = _compute_hours_after_solar_midnight(moon_event_mjds, site)
1069
+ else:
1070
+ moon_event_nights, moon_event_hams = _compute_hours_after_midnight(moon_event_mjds)
1071
+ # Loop to avoid wrapping
1072
+ moon_lines = np.cumsum(np.diff(moon_event_hams, prepend=moon_event_hams[0]) < 0)
1073
+ for moon_line in np.unique(moon_lines):
1074
+ these_hams = moon_event_hams[moon_lines == moon_line]
1075
+ these_nights = moon_event_nights[moon_lines == moon_line]
1076
+ ax.plot(
1077
+ these_hams,
1078
+ these_nights - start_mjd + 1,
1079
+ color="yellow",
1080
+ linestyle="dotted",
1081
+ )
1082
+
1083
+ # Twilight
1084
+ guess_offset = {"up": 0.2, "down": -0.2}
1085
+ twilight_shade = {0: "0.7", -6: "0.6", -12: "0.4", -18: "black"}
1086
+
1087
+ for alt in twilight_shade:
1088
+ pm_event_mjds = riseset_times(cal_night_mjds + guess_offset["down"], "down", alt=alt, body="sun")
1089
+ am_event_mjds = riseset_times(cal_night_mjds + guess_offset["up"], "up", alt=alt, body="sun")
1090
+ if solar_time:
1091
+ (
1092
+ pm_event_nights,
1093
+ pm_event_hams,
1094
+ ) = _compute_hours_after_solar_midnight(pm_event_mjds, site)
1095
+ (
1096
+ am_event_nights,
1097
+ am_event_hams,
1098
+ ) = _compute_hours_after_solar_midnight(am_event_mjds, site)
1099
+ else:
1100
+ (
1101
+ pm_event_nights,
1102
+ pm_event_hams,
1103
+ ) = _compute_hours_after_midnight(pm_event_mjds)
1104
+ (
1105
+ am_event_nights,
1106
+ am_event_hams,
1107
+ ) = _compute_hours_after_midnight(am_event_mjds)
1108
+ assert np.array_equal(am_event_nights, pm_event_nights)
1109
+ ax.fill_betweenx(
1110
+ am_event_nights - start_mjd + 1,
1111
+ pm_event_hams,
1112
+ am_event_hams,
1113
+ color=twilight_shade[alt],
1114
+ zorder=-10,
1115
+ )
1116
+
1117
+ return ax