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,1212 @@
1
+ __all__ = (
2
+ "StaticProbesFoMEmulatorMetricSimple",
3
+ "TotalPowerMetric",
4
+ "TomographicClusteringSigma8biasMetric",
5
+ "MultibandMeanzBiasMetric",
6
+ "get_stats_by_region",
7
+ "stripiness_test_statistic",
8
+ "StripinessMetric",
9
+ "UniformAreaFoMFractionMetric",
10
+ "UDropoutsNumbersShallowestDepth",
11
+ "UDropoutsArea",
12
+ )
13
+
14
+ import warnings
15
+ from copy import deepcopy
16
+
17
+ import healpy as hp
18
+ import numpy as np
19
+ from astropy import units as u
20
+ from astropy.coordinates import SkyCoord
21
+ from scipy import interpolate
22
+ from scipy.stats import median_abs_deviation
23
+ from sklearn.cluster import KMeans
24
+
25
+ from ..maf_contrib.static_probes_fom_summary_metric import StaticProbesFoMEmulatorMetric
26
+ from .area_summary_metrics import AreaThresholdMetric
27
+ from .base_metric import BaseMetric
28
+ from .simple_metrics import PercentileMetric
29
+
30
+ # Cosmology-related summary metrics.
31
+ # These generally calculate a FoM for various DESC metrics.
32
+
33
+
34
+ class TotalPowerMetric(BaseMetric):
35
+ """Calculate the total power in the angular power spectrum,
36
+ between lmin/lmax.
37
+
38
+ Parameters
39
+ ----------
40
+ lmin : `float`, optional
41
+ Minimum ell value to include when calculating total power.
42
+ lmax : `float`, optional
43
+ Maximum ell value to include when calculating total power.
44
+ remove_monopole : `bool`, optional
45
+ Flag to remove monopole when calculating total power.
46
+ remove_dipole : `bool`, optional
47
+ Flag to remove dipole when calculating total power.
48
+ col : `str`, optional
49
+ The column name to operate on.
50
+ For summary metrics, this is almost always `metricdata`.
51
+ mask_val : `float` or np.nan, optional
52
+ The mask value to apply to the metric values when passed.
53
+ If this attribute exists, the metric_values will be passed
54
+ using metric_values.filled(mask_val).
55
+ If mask_val is `None` for a metric, metric_values will be passed
56
+ using metric_values.compressed().
57
+ """
58
+
59
+ def __init__(
60
+ self,
61
+ col="metricdata",
62
+ lmin=100.0,
63
+ lmax=300.0,
64
+ remove_monopole=True,
65
+ remove_dipole=True,
66
+ mask_val=np.nan,
67
+ **kwargs,
68
+ ):
69
+ self.lmin = lmin
70
+ self.lmax = lmax
71
+ self.remove_monopole = remove_monopole
72
+ self.remove_dipole = remove_dipole
73
+ super(TotalPowerMetric, self).__init__(col=col, mask_val=mask_val, **kwargs)
74
+
75
+ def run(self, data_slice, slice_point=None):
76
+ # Calculate the power spectrum.
77
+ data = data_slice[self.colname]
78
+ if self.remove_monopole:
79
+ data = hp.remove_monopole(data, verbose=False, bad=self.mask_val)
80
+ if self.remove_dipole:
81
+ cl = hp.anafast(hp.remove_dipole(data_slice[self.colname]))
82
+ else:
83
+ cl = hp.anafast(data_slice[self.colname])
84
+ ell = np.arange(np.size(cl))
85
+ condition = np.where((ell <= self.lmax) & (ell >= self.lmin))[0]
86
+ totalpower = np.sum(cl[condition] * (2 * ell[condition] + 1))
87
+ return totalpower
88
+
89
+
90
+ class StaticProbesFoMEmulatorMetricSimple(BaseMetric):
91
+ """This calculates the Figure of Merit for the combined
92
+ static probes (3x2pt, i.e., Weak Lensing, LSS, Clustering).
93
+ This FoM is purely statistical and does not factor in systematics.
94
+
95
+ Parameters
96
+ ----------
97
+ year : `int`, optional
98
+ The year of the survey to calculate FoM.
99
+ This calibrates expected depth and area.
100
+
101
+ Returns
102
+ -------
103
+ result : `float`
104
+ The simple 3x2pt FoM emulator value, for the
105
+ years where the correlation between area/depth and value is defined.
106
+
107
+ Notes
108
+ -----
109
+ This FoM is purely statistical and does not factor in systematics.
110
+ The implementation here is simpler than in
111
+ `rubin_sim.maf.mafContrib.StaticProbesFoMEmulatorMetric`, and that
112
+ more sophisticated version should replace this metric.
113
+
114
+ This version of the emulator was used to generate the results in
115
+ https://ui.adsabs.harvard.edu/abs/2018arXiv181200515L/abstract
116
+
117
+ Note that this is truly a summary metric and should be run on the
118
+ output of Exgalm5_with_cuts.
119
+ """
120
+
121
+ def __init__(self, year=10, col=None, **kwargs):
122
+
123
+ super().__init__(col=col, **kwargs)
124
+ if col is None:
125
+ self.col = "metricdata"
126
+ self.year = year
127
+ # Set a mask_val so that all metric data is passed
128
+ # for summary calculation, even masked values.
129
+ self.mask_val = -1
130
+
131
+ def run(self, data_slice, slice_point=None):
132
+ """
133
+ Args:
134
+ data_slice (ndarray): Values passed to metric by the slicer,
135
+ which the metric will use to calculate metric values
136
+ at each slice_point.
137
+ slice_point (Dict): Dictionary of slice_point metadata passed
138
+ to each metric.
139
+ Returns:
140
+ float: Interpolated static-probe statistical Figure-of-Merit.
141
+ Raises:
142
+ ValueError: If year is not one of the 4 for which a FoM is
143
+ calculated
144
+ """
145
+ nside = hp.npix2nside(len(data_slice))
146
+ # Chop off any outliers
147
+ good_pix = np.where(data_slice[self.col] > 0)[0]
148
+ if np.size(good_pix) == 0:
149
+ return self.badval
150
+
151
+ # Calculate area and med depth from
152
+ area = hp.nside2pixarea(nside, degrees=True) * np.size(good_pix)
153
+ median_depth = np.median(data_slice[self.col][good_pix])
154
+ # FoM is calculated at the following values
155
+ if self.year == 1:
156
+ areas = [7500, 13000, 16000]
157
+ depths = [24.9, 25.2, 25.5]
158
+ fom_arr = [
159
+ [1.212257e02, 1.462689e02, 1.744913e02],
160
+ [1.930906e02, 2.365094e02, 2.849131e02],
161
+ [2.316956e02, 2.851547e02, 3.445717e02],
162
+ ]
163
+ elif self.year == 3:
164
+ areas = [10000, 15000, 20000]
165
+ depths = [25.5, 25.8, 26.1]
166
+ fom_arr = [
167
+ [1.710645e02, 2.246047e02, 2.431472e02],
168
+ [2.445209e02, 3.250737e02, 3.516395e02],
169
+ [3.173144e02, 4.249317e02, 4.595133e02],
170
+ ]
171
+
172
+ elif self.year == 6:
173
+ areas = [10000, 15000, 2000]
174
+ depths = [25.9, 26.1, 26.3]
175
+ fom_arr = [
176
+ [2.346060e02, 2.414678e02, 2.852043e02],
177
+ [3.402318e02, 3.493120e02, 4.148814e02],
178
+ [4.452766e02, 4.565497e02, 5.436992e02],
179
+ ]
180
+
181
+ elif self.year == 10:
182
+ areas = [10000, 15000, 20000]
183
+ depths = [26.3, 26.5, 26.7]
184
+ fom_arr = [
185
+ [2.887266e02, 2.953230e02, 3.361616e02],
186
+ [4.200093e02, 4.292111e02, 4.905306e02],
187
+ [5.504419e02, 5.624697e02, 6.441837e02],
188
+ ]
189
+ else:
190
+ warnings.warn("FoMEmulator is not defined for this year")
191
+ return self.badval
192
+
193
+ # Interpolate FoM to the actual values for this sim
194
+ areas = [[i] * 3 for i in areas]
195
+ depths = [depths] * 3
196
+ f = interpolate.RectBivateateSpline(np.ravel(areas), np.ravel(depths), np.ravel(fom_arr))
197
+ fom = f(area, median_depth)[0]
198
+ return fom
199
+
200
+
201
+ class TomographicClusteringSigma8biasMetric(BaseMetric):
202
+ """Compute bias on sigma8 due to spurious contamination of density maps.
203
+ Run as summary metric on NestedLinearMultibandModelMetric.
204
+
205
+ Points of contact / contributors: Boris Leistedt
206
+
207
+ Parameters
208
+ ----------
209
+ density_tomograph_model : `dict`
210
+ dictionary containing models calculated for fiducial N(z)s and Cells:
211
+ lmax : numpy.array of int, of shape (Nbins, )
212
+ lmax corresponding to kmax of 0.05
213
+ poly1d_coefs_loglog : numpy.array of float, of shape (Nbins, )
214
+ polynomial fits to log(C_ell) vs log(ell) computed for CCL
215
+ sigma8square_model (float)
216
+ value of sigma8^2 used as fiducal model for CCL
217
+ power_multiplier : `float`, optional
218
+ fraction of power (variance) which is uncorrected
219
+ and thus biases sigma8
220
+ lmin : `int`, optional
221
+ lmin for the analysis
222
+ convert_to_sigma8 : `str`, optional
223
+ Convert the bias to sigma8 instead of sigma8^2
224
+ (via change of variables for the uncertainty)
225
+
226
+ Returns
227
+ -------
228
+ result : `float`
229
+ Value of sigma8 bias calculated from this model:
230
+ (sigma8^2_obs - sigma^2_model) / error on sigma8^2_obs
231
+ if `convert_to_sigma8` is True,
232
+ then it is about sigma8 instead of sigma8^2.
233
+
234
+ Notes
235
+ -----
236
+ This is a summary metric to be run on the results
237
+ of the NestedLinearMultibandModelMetric.
238
+
239
+ NestedLinearMultibandModelMetric converts 6-band depth maps into
240
+ a set of maps (e.g tomographic redshift bins) which describe
241
+ spurious density fluctuations in each bin.
242
+
243
+ This summary metric multiplies the maps by the parameter power_multiplier,
244
+ which can be used to describe the fraction of power uncorrected by
245
+ systematics mitigation schemes, computes the total power
246
+ (via angular power spectra with lmin-lmax limits)
247
+ and then infers sigma8^2 via a model of the angular power spectra.
248
+ By taking sigma8_obs minus sigma8_model divided by the uncertainty,
249
+ one derives a bias.
250
+ """
251
+
252
+ def __init__(
253
+ self,
254
+ density_tomography_model,
255
+ power_multiplier=0.1,
256
+ lmin=10,
257
+ convert_to_sigma8=True,
258
+ badval=hp.UNSEEN,
259
+ **kwargs,
260
+ ):
261
+ super().__init__(col="metricdata", **kwargs)
262
+ # Set mask_val, so that we receive metric_values.filled(mask_val)
263
+ self.mask_val = badval
264
+ self.badval = badval
265
+
266
+ self.convert_to_sigma8 = convert_to_sigma8
267
+
268
+ self.power_multiplier = power_multiplier
269
+ self.lmin = lmin
270
+ self.density_tomography_model = density_tomography_model
271
+ # to compute angular power spectra and total power,
272
+ # initialize an array of metrics, with the right lmin and lmax.
273
+ self.totalPowerMetrics = [
274
+ TotalPowerMetric(lmin=lmin, lmax=lmax, mask_val=self.mask_val)
275
+ for lmax in density_tomography_model["lmax"]
276
+ ]
277
+ self.areaThresholdMetric = AreaThresholdMetric(
278
+ col="metricdata",
279
+ metric_name="FootprintFraction_bin",
280
+ lower_threshold=badval,
281
+ upper_threshold=np.inf,
282
+ mask_val=self.mask_val,
283
+ )
284
+
285
+ def run(self, data_slice, slice_point=None):
286
+ # need to define an array of bad values for the masked pixels
287
+ badval_arr = np.repeat(self.badval, len(self.density_tomography_model["lmax"]))
288
+ # converts the input recarray to an array
289
+ data_slice_list = [
290
+ badval_arr if isinstance(x, float) else x for x in data_slice["metricdata"].tolist()
291
+ ]
292
+ # should be (nbins, npix)
293
+ data_slice_arr = np.asarray(data_slice_list, dtype=float).T
294
+ data_slice_arr[~np.isfinite(data_slice_arr)] = (
295
+ self.badval
296
+ ) # need to work with TotalPowerMetric and healpix
297
+
298
+ # measure valid sky fractions and total power
299
+ # (via angular power spectra) in each bin.
300
+ # The original metric returns an array at each slice_point (of the
301
+ # original slicer) -- so there is a bit of "rearrangement" that
302
+ # has to happen to be able to pass a np.array with right dtype
303
+ # (i.e. dtype = [("metricdata", float)]) to each call to
304
+ # the AreaThresholdMetric and TotalPowerMetric `run` methods.
305
+ totalsky = 42000
306
+ fskys = np.array(
307
+ [
308
+ self.areaThresholdMetric.run(np.core.records.fromrecords(x, dtype=[("metricdata", float)]))
309
+ / totalsky
310
+ for x in data_slice_arr
311
+ ]
312
+ ) # sky fraction
313
+ spuriousdensitypowers = (
314
+ np.array(
315
+ [
316
+ self.totalPowerMetrics[i].run(
317
+ np.core.records.fromrecords(x, dtype=[("metricdata", float)])
318
+ )
319
+ for i, x in enumerate(data_slice_arr)
320
+ ]
321
+ )
322
+ / fskys
323
+ )
324
+ # some gymnastics needed to convert each slice into a recarray.
325
+ # this could probably be avoided if recarrays were returned
326
+ # by the original nested/vector metric,
327
+ # except that we would need to manipulate the names in various
328
+ # places, which I wanted to avoid,
329
+ # so for now the main metric returns an array per healpix pixel
330
+ # (not a recarray) and puts together
331
+ # healpix maps which we need to convert to a recarray to pass to
332
+ # AreaThresholdMetric and TotalPowerMetric
333
+
334
+ def solve_for_multiplicative_factor(spurious_powers, model_cells, fskys, lmin, power_multiplier):
335
+ """
336
+ Infer multiplicative factor sigma8^2 (and uncertainty)
337
+ from the model Cells and observed total powers
338
+ since it os a Gaussian posterior distribution.
339
+ """
340
+ # solve for multiplicative sigma8^2 term between
341
+ # measured angular power spectra
342
+ # (spurious measured Cells times power_multiplier)
343
+ # and model ones (polynomial model from CCL).
344
+ n_bins = model_cells["lmax"].size
345
+ assert len(spurious_powers) == n_bins
346
+ assert len(fskys) == n_bins
347
+ assert model_cells["poly1d_coefs_loglog"].shape[0] == n_bins
348
+ totalvar_mod = np.zeros((n_bins, 1))
349
+ totalvar_obs = np.zeros((n_bins, 1))
350
+ totalvar_var = np.zeros((n_bins, 1))
351
+ # loop over tomographic bins
352
+ # hardcoded; assumed CCL cosmology
353
+ sigma8square_model = model_cells["sigma8square_model"]
354
+ for i in range(n_bins):
355
+ # get model Cells from polynomial model (in log log space)
356
+ ells = np.arange(lmin, model_cells["lmax"][i])
357
+ polynomial_model = np.poly1d(model_cells["poly1d_coefs_loglog"][i, :])
358
+ cells_model = np.exp(polynomial_model(np.log(ells)))
359
+
360
+ # model variance is sum of cells x (2l+1)
361
+ totalvar_mod[i, 0] = np.sum(cells_model * (2 * ells + 1))
362
+
363
+ # observations is spurious power noiseless model
364
+ totalvar_obs[i, 0] = totalvar_mod[i, 0] + spurious_powers[i] * power_multiplier
365
+
366
+ # simple model variance of cell based on Gaussian covariance
367
+ cells_var = 2 * cells_model**2 / (2 * ells + 1) / fskys[i]
368
+ totalvar_var[i, 0] = np.sum(cells_var * (2 * ells + 1) ** 2)
369
+
370
+ # model assumed sigma8 = 0.8
371
+ # (add CCL cosmology here? or how I obtained them + documentation)
372
+
373
+ # model Cell variance divided by sigma8^2,
374
+ # which is the common normalization
375
+ transfers = totalvar_mod / sigma8square_model
376
+
377
+ # model ratio: formula for posterior distribution on unknown
378
+ # multiplicative factor in multivariate Gaussian likelihood
379
+ # Gaussian likelihood
380
+ FOT = np.sum(transfers[:, 0] * totalvar_obs[:, 0] / totalvar_var[:, 0])
381
+ FTT = np.sum(transfers[:, 0] * transfers[:, 0] / totalvar_var[:, 0])
382
+ # mean and stddev of multiplicative factor
383
+ sigma8square_fit = FOT / FTT
384
+ sigma8square_error = FTT**-0.5
385
+
386
+ return sigma8square_fit, sigma8square_error, sigma8square_model
387
+
388
+ # solve for the gaussian posterior distribution on sigma8^2
389
+ sigma8square_fit, sigma8square_error, sigma8square_model = solve_for_multiplicative_factor(
390
+ spuriousdensitypowers, self.density_tomography_model, fskys, self.lmin, self.power_multiplier
391
+ )
392
+
393
+ results_sigma8_square_bias = (sigma8square_fit - sigma8square_model) / sigma8square_error
394
+
395
+ if not self.convert_to_sigma8:
396
+ return results_sigma8_square_bias
397
+
398
+ else:
399
+
400
+ # turn result into bias on sigma8,
401
+ # via change of variable and simple propagation of uncertainty.
402
+ sigma8_fit = sigma8square_fit**0.5
403
+ sigma8_model = sigma8square_model**0.5
404
+ sigma8_error = 0.5 * sigma8square_error * sigma8_fit / sigma8square_fit
405
+ results_sigma8_bias = (sigma8_fit - sigma8_model) / sigma8_error
406
+ return results_sigma8_bias
407
+
408
+
409
+ class MultibandMeanzBiasMetric(BaseMetric):
410
+ """
411
+ Run as summary metric on MultibandExgalM5.
412
+
413
+ Parameters
414
+ ----------
415
+ year : `int`, optional
416
+ The year of the survey to calculate the bias.
417
+ This is used to derive the dm/dz derivative used to
418
+ translate m5 rms into dz rms.
419
+
420
+ meanz_tomograph_model : `dict`
421
+ dictionary containing models calculated for fiducial N(z):
422
+
423
+ meanz: numpy.float
424
+ the meanz within a tomographic bin at a given band
425
+ dz_dm5: numpy.float
426
+ the absolute value of the derivative of z in a bin as a function
427
+ of the depth, m5 magnitude. This is later used to
428
+ translate fluctuations
429
+ in depth to fluctuations in tomographic redshift
430
+
431
+ Returns
432
+ -------
433
+ result : `float` array
434
+ The ratio of this bias to the desired DESC y1 upper
435
+ bound on the bias, and the ratio
436
+ between the clbias and the y10 DESC SRD requirement.
437
+ Desired values are less than 1 by Y10.
438
+
439
+ Notes
440
+ -----
441
+ This is a summary metric to be run on the results
442
+ of the MultibandExgalM5.
443
+
444
+ MultibandExgalM5 provides the m5 depth in all LSST bands
445
+ given a specific slice.
446
+
447
+ This summary metric takes those depths and reads the derivatives
448
+ from the tomogrpahic model,
449
+ computes the bias in shear signal Cl and then computes the ratio of
450
+ that bias to the Y1 goal and
451
+ Y10 science requirement.
452
+ """
453
+
454
+ def __init__(
455
+ self,
456
+ meanz_tomography_model,
457
+ filter_list=["u", "g", "r", "i", "z", "y"],
458
+ year=10,
459
+ zbins=4,
460
+ **kwargs,
461
+ ):
462
+
463
+ super().__init__(col="metricdata", **kwargs)
464
+ # Set mask_val, so that we receive metric_values.filled(mask_val)
465
+ self.mask_val = np.nan
466
+ self.badval = np.nan
467
+ self.rms_func = np.nanstd
468
+ self.year = year
469
+ self.filter_list = filter_list
470
+ self.zbins = zbins
471
+ self.tomo_model = meanz_tomography_model
472
+
473
+ def run(self, data_slice, slice_point=None):
474
+
475
+ def compute_dzfromdm(zbins, band_name, year):
476
+ """This computes the dm/dz relationship calibrated from simulations
477
+ by Jeff Newmann and Qianjun Hang, which forms the
478
+ meanz_tomographic_model.
479
+
480
+ Parameters
481
+ ----------
482
+ zbins : `int`
483
+ The number of tomographic bins considered. For now
484
+ this is zbins < 5
485
+ band_name : `str`
486
+ The assumed filter band
487
+ model_z: dict
488
+ The meanz_tomographic_model assumed in this work
489
+
490
+ Returns
491
+ -------
492
+ dzdminterp : `float` array
493
+ The interpolated value of the derivative dz/dm
494
+ meanzinterp : `float` array
495
+ The meanz in each tomographic bin.
496
+
497
+ """
498
+ meanzinterp = np.zeros(zbins)
499
+ dzdminterp = np.zeros(zbins)
500
+
501
+ for z in range(zbins):
502
+ meanzinterp[z] = self.tomo_model["year%i" % (year + 1)]["meanz"][z][band_name]
503
+ dzdminterp[z] = self.tomo_model["year%i" % (year + 1)]["dz_dm5"][z][band_name]
504
+
505
+ return dzdminterp, meanzinterp
506
+
507
+ # Not entirely sure if we should include these
508
+ # here or in a separate module/file
509
+ def use_zbins(meanz_vals, figure_9_mean_z=np.array([0.2, 0.4, 0.7, 1.0]), figure_9_width=0.2):
510
+ """This computes which redshift bands are within the range
511
+ specified in https://arxiv.org/pdf/2305.15406.pdf and
512
+ can safely be used
513
+ to compute what Cl bias result from z fluctuations
514
+ caused by rms variations in the m5.
515
+
516
+
517
+ Parameters
518
+ ----------
519
+ meanz_vals : `float` array
520
+ Array of meanz values to be used.
521
+
522
+ Returns
523
+ -------
524
+ use_bins : `boolean` array
525
+ An array of boolean values of length meanz_vals
526
+
527
+ """
528
+ max_z_use = np.max(figure_9_mean_z) + 2 * figure_9_width
529
+ use_bins = meanz_vals < max_z_use
530
+
531
+ return use_bins
532
+
533
+ def compute_Clbias(meanz_vals, scatter_mean_z_values):
534
+ """This computes the Cl bias
535
+ that results z fluctuations caused by rms variations in the m5.
536
+
537
+
538
+
539
+ Parameters
540
+ ----------
541
+ meanz_vals : `float` array
542
+ Array of meanz values to be used.
543
+
544
+ scatter_mean_z_values : `float` array
545
+ Array of rms values of the z fluctuations
546
+
547
+
548
+ Returns
549
+ -------
550
+ clbiasvals : `float` array
551
+ An array of values of the clbias
552
+
553
+ mean_z_values_use : `float` array
554
+ An array of the meanz values that are within the
555
+ interpolation range of 2305.15406
556
+
557
+ Notes
558
+ ------
559
+ This interpolates from the Figure 9 in
560
+ https://arxiv.org/pdf/2305.15406.pdf
561
+
562
+ """
563
+
564
+ figure_9_mean_z = np.array([0.2, 0.4, 0.7, 1.0])
565
+ figure_9_Clbias = np.array([1e-3, 2e-3, 5e-3, 1.1e-2])
566
+ figure_9_width = 0.2
567
+ figure_9_mean_z_scatter = 0.02
568
+
569
+ mzvals = np.array([float(mz) for mz in meanz_vals])
570
+ sctz = np.array([float(sz) for sz in scatter_mean_z_values])
571
+
572
+ fit_res = np.polyfit(figure_9_mean_z, figure_9_Clbias, 2)
573
+ poly_fit = np.poly1d(fit_res)
574
+ use_bins = use_zbins(meanz_vals, figure_9_mean_z, figure_9_width)
575
+
576
+ mean_z_values_use = mzvals[use_bins]
577
+ sctz_use = sctz[use_bins]
578
+
579
+ Clbias = poly_fit(mean_z_values_use)
580
+ rescale_fac = sctz_use / figure_9_mean_z_scatter
581
+ Clbias *= rescale_fac
582
+ fit_res_bias = np.polyfit(mean_z_values_use, Clbias, 1)
583
+ poly_fit_bias = np.poly1d(fit_res_bias)
584
+
585
+ clbiasvals = poly_fit_bias(mean_z_values_use)
586
+ return clbiasvals, mean_z_values_use
587
+
588
+ result = np.empty(self.zbins, dtype=[("name", np.str_, 20), ("y1ratio", float), ("y10ratio", float)])
589
+ result["name"][0] = "MultibandMeanzBiasMetric"
590
+
591
+ # Technically don't need this for now (isn't used in previous one)
592
+ # need to define an array of bad values for the masked pixels
593
+ badval_arr = np.repeat(self.badval, len(self.filter_list))
594
+ # converts the input recarray to an array
595
+ data_slice_list = [
596
+ badval_arr if isinstance(x, float) else x for x in data_slice["metricdata"].tolist()
597
+ ]
598
+ # should be (nbins, npix)
599
+ data_slice_arr = np.asarray(data_slice_list, dtype=float).T
600
+ data_slice_arr[~np.isfinite(data_slice_arr)] = np.nan
601
+
602
+ # measure rms in each bin.
603
+
604
+ # XXX--this is probably meant to be run on
605
+ # only valid values, so changing to nanstd
606
+ rmsarray = self.rms_func(data_slice_arr, axis=1)
607
+
608
+ # totdz = 0
609
+ avmeanz = 0
610
+ totclbias = 0
611
+
612
+ for i, filt in enumerate(self.filter_list):
613
+ dzdminterp, meanzinterp = compute_dzfromdm(self.zbins, filt, self.year)
614
+ stdz = [float(np.abs(dz)) * float(rmsarray[i]) for dz in dzdminterp]
615
+
616
+ clbias, meanz_use = compute_Clbias(meanzinterp, stdz)
617
+
618
+ # totdz += [float(st**2) for st in stdz]
619
+ totclbias += clbias
620
+ avmeanz += meanzinterp
621
+
622
+ # These should be included in self rather than hard coded
623
+ y10_req = 0.003
624
+ y1_goal = 0.013
625
+
626
+ # clbiastot = np.max(clbias) # if adding doesn't
627
+ # work over z range -- CHECK
628
+ y10ratio = totclbias / y10_req
629
+ y1ratio = totclbias / y1_goal
630
+
631
+ result["y1ratio"] = y1ratio
632
+ result["y10ratio"] = y10ratio
633
+ return result
634
+
635
+
636
+ # Let's make code that pulls out the northern/southern galactic regions,
637
+ # and gets statistics of the footprint by region.
638
+ def _is_ngp(ra, dec):
639
+ c = SkyCoord(ra=ra * u.degree, dec=dec * u.degree, frame="icrs")
640
+ lat = c.galactic.b.deg
641
+ return lat >= 0
642
+
643
+
644
+ def get_stats_by_region(use_map, nside, maskval=0, region="all"):
645
+ if region not in ["all", "north", "south"]:
646
+ raise ValueError("Invalid region %s" % region)
647
+ to_use = use_map > maskval
648
+
649
+ if region != "all":
650
+ # Find the north/south part of the map as requested
651
+ npix = hp.nside2npix(nside)
652
+ ra, dec = hp.pix2ang(hp.npix2nside(npix), range(npix), lonlat=True)
653
+ ngp_mask = _is_ngp(ra, dec)
654
+ if region == "north":
655
+ reg_mask = ngp_mask
656
+ else:
657
+ reg_mask = ~ngp_mask
658
+ to_use = to_use & reg_mask
659
+
660
+ # Calculate the desired stats
661
+ reg_mad = median_abs_deviation(use_map[to_use])
662
+ reg_median = np.median(use_map[to_use])
663
+ reg_std = np.std(use_map[to_use])
664
+
665
+ # Return the values
666
+ return (reg_mad, reg_median, reg_std)
667
+
668
+
669
+ def stripiness_test_statistic(data_slice, nside):
670
+ """
671
+ A utility to find whether a particular routine has stripey
672
+ features in the exposure time map.
673
+ """
674
+ # Analyze the exposure time map to get MAD, median, std in north/south.
675
+ mad = {}
676
+ med = {}
677
+ frac_scatter = {}
678
+ regions = ["north", "south"]
679
+ for region in regions:
680
+ mad[region], med[region], _ = get_stats_by_region(data_slice, nside, region=region)
681
+ frac_scatter[region] = mad[region] / med[region]
682
+ test_statistic = frac_scatter["north"] / frac_scatter["south"] - 1
683
+ return test_statistic
684
+
685
+
686
+ class StripinessMetric(BaseMetric):
687
+ """
688
+ Run as summary metric on NestedRIZExptimeExgalM5Metric.
689
+
690
+ This metric uses maps of the combined RIZ exposure time
691
+ to identify stripes or residual rolling features,
692
+ for the UniformAreaFoMFractionMetric.
693
+ The number returned quantifies the stripiness of the field.
694
+ In UniformAreaFoMFractionMetric it is a test statistic:
695
+ if it is outside of +-0.7/np.sqrt(year) then
696
+ the RIZ exposure time map is considered to have stripes.
697
+
698
+ Points of contact / contributors: Rachel Mandelbaum, Boris Leistedt
699
+
700
+ Parameters
701
+ ----------
702
+ year: `int`
703
+ year of observation, in order to adjust the cut on
704
+ the depth fluctuations for the stipe detection
705
+ nside: `int`
706
+ must be the nside at which the base metric
707
+ NestedRIZExptimeExgalM5Metric is calculated at
708
+ verbose: bool, optional
709
+ if true, will display the segmentation maps and the areas and
710
+ FOMs of the two regions found
711
+ Returns
712
+ -------
713
+ result: `float`
714
+ A number quantifying the stripiness of the RIZ exposure time.
715
+ """
716
+
717
+ def __init__(
718
+ self,
719
+ year,
720
+ nside,
721
+ verbose=True,
722
+ **kwargs,
723
+ ):
724
+ self.year = year
725
+ self.mask_val = hp.UNSEEN
726
+ self.verbose = verbose
727
+ self.nside = nside
728
+ names = ["exgal_m5", "riz_exptime"]
729
+ types = [float] * 2
730
+ self.mask_val_arr = np.zeros(1, dtype=list(zip(names, types)))
731
+ self.mask_val_arr["exgal_m5"] = self.mask_val
732
+ self.mask_val_arr["riz_exptime"] = self.mask_val
733
+
734
+ super().__init__(col="metricdata", **kwargs)
735
+
736
+ def run(self, data_slice, slice_point=None):
737
+ data_slice_list = [
738
+ self.mask_val_arr if isinstance(x, float) else x for x in data_slice["metricdata"].tolist()
739
+ ]
740
+ data_slice_arr = np.asarray(data_slice_list, dtype=self.mask_val_arr.dtype)
741
+ # a bit of gymnastics to make sure all bad values
742
+ # (nan, -666) are recast as hp.UNSEEN
743
+ ind = data_slice_arr["riz_exptime"] == -666
744
+ ind |= ~np.isfinite(data_slice_arr["riz_exptime"])
745
+ data_slice_arr["riz_exptime"][ind.ravel()] = hp.UNSEEN
746
+ # sanity check
747
+ nside = hp.npix2nside(data_slice["metricdata"].size)
748
+ assert nside == self.nside
749
+
750
+ test_statistic = stripiness_test_statistic(data_slice_arr["riz_exptime"].ravel(), nside)
751
+
752
+ return test_statistic
753
+
754
+
755
+ class UniformAreaFoMFractionMetric(BaseMetric):
756
+ """
757
+ Run as summary metric on NestedRIZExptimeExgalM5Metric.
758
+
759
+ This metric uses maps of the combined RIZ exposure time and i-band depth
760
+ maps (with a consistent set of area cuts)
761
+ to identify potential reductions in cosmological constraining power due to
762
+ substantial large-scale power
763
+ in non-uniform coadds at a particular data release.
764
+ The RIZ exposure time map is used to identify whether there
765
+ are residual rolling features.
766
+ Under the hood, this runs the StripinessMetric.
767
+ If not, the metric returns 1. If there are such features, then the region
768
+ is segmented into similar-depth regions
769
+ and the one with the largest cosmological constraining power is presumed
770
+ to be used for science.
771
+ In that case, the metric returns the 3x2pt FoM
772
+ (StaticProbesFoMEmulatorMetric,
773
+ quantifying weak lensing and large-scale structure constraining power)
774
+ for the largest of those regions,
775
+ divided by that for the full region if it had been usable.
776
+
777
+
778
+
779
+ Points of contact / contributors: Rachel Mandelbaum, Boris Leistedt
780
+
781
+ Parameters
782
+ ----------
783
+ year: `int`
784
+ year of observation, in order to adjust the
785
+ cut on the depth fluctuations
786
+ for the stipe detection
787
+ nside: `int`
788
+ must be the nside at which the base metric
789
+ NestedRIZExptimeExgalM5Metric
790
+ is calculated at
791
+ verbose: bool, optional
792
+ if true, will display the segmentation maps and the areas and FOMs of
793
+ the two regions found
794
+ Returns
795
+ -------
796
+ result: `float`
797
+ The ratio of the FOMs of the two areas found
798
+ """
799
+
800
+ def __init__(
801
+ self,
802
+ year,
803
+ nside,
804
+ verbose=True,
805
+ **kwargs,
806
+ ):
807
+ self.year = year
808
+ self.mask_val = hp.UNSEEN
809
+ self.verbose = verbose
810
+ self.nside = nside
811
+ names = ["exgal_m5", "riz_exptime"]
812
+ types = [float] * 2
813
+ self.mask_val_arr = np.zeros(1, dtype=list(zip(names, types)))
814
+ self.mask_val_arr["exgal_m5"] = self.mask_val
815
+ self.mask_val_arr["riz_exptime"] = self.mask_val
816
+
817
+ self.threebyTwoSummary = StaticProbesFoMEmulatorMetric(
818
+ nside=nside, metric_name="3x2ptFoM", col="exgal_m5"
819
+ )
820
+ super().__init__(col="metricdata", **kwargs)
821
+
822
+ def run(self, data_slice, slice_point=None):
823
+ data_slice_list = [
824
+ self.mask_val_arr if isinstance(x, float) else x for x in data_slice["metricdata"].tolist()
825
+ ]
826
+ data_slice_arr = np.asarray(data_slice_list, dtype=self.mask_val_arr.dtype)
827
+ # a bit of gymnastics to make sure all bad values
828
+ # (nan, -666) are recast as hp.UNSEEN
829
+ ind = data_slice_arr["riz_exptime"] == -666
830
+ ind |= ~np.isfinite(data_slice_arr["riz_exptime"])
831
+ ind = data_slice_arr["exgal_m5"] == -666
832
+ ind |= ~np.isfinite(data_slice_arr["exgal_m5"])
833
+ data_slice_arr["exgal_m5"][ind.ravel()] = hp.UNSEEN
834
+ data_slice_arr["riz_exptime"][ind.ravel()] = hp.UNSEEN
835
+ # sanity check
836
+ nside = hp.npix2nside(data_slice["metricdata"].size)
837
+ assert nside == self.nside
838
+
839
+ # Check for stripiness
840
+ threshold = 0.7 / np.sqrt(self.year)
841
+ test_statistic = stripiness_test_statistic(data_slice_arr["riz_exptime"].ravel(), nside)
842
+
843
+ if np.abs(test_statistic) < np.abs(threshold):
844
+ stripes = False
845
+ else:
846
+ stripes = True
847
+
848
+ def apply_clustering(clustering_data):
849
+ # A thin wrapper around sklearn routines
850
+ # (can swap out which one we are using systematically).
851
+ # We fix parameters like `n_clusters` since realistically
852
+ # we know for rolling that we should expect 2 clusters.
853
+ # from sklearn.cluster import SpectralClustering
854
+ # clustering = SpectralClustering(n_clusters=2,
855
+ # assign_labels='discretize', random_state=0).fit(clustering_data)
856
+
857
+ clustering = KMeans(n_clusters=2, random_state=0, n_init="auto").fit(clustering_data)
858
+ labels = clustering.labels_ + 1
859
+ return labels
860
+
861
+ def expand_labels(depth_map, labels, maskval=0):
862
+ # A utility to apply the labels from a masked
863
+ # version of a depth map back to the entire depth map.
864
+ expanded_labels = np.zeros(hp.nside2npix(nside))
865
+ cutval = maskval + 0.1
866
+ expanded_labels[depth_map > cutval] = labels
867
+ return expanded_labels
868
+
869
+ def get_area_stats(depth_map, labels, maskval=0, n_clusters=2):
870
+ # A routine to get some statistics of the
871
+ # clustering: area fractions, median map values
872
+ expanded_labels = expand_labels(depth_map, labels, maskval=maskval)
873
+ cutval = maskval + 0.1
874
+ area_frac = []
875
+ med_val = []
876
+ for i in range(n_clusters):
877
+ new_map = depth_map.copy()
878
+ new_map[expanded_labels != i + 1] = maskval
879
+ this_area_frac = len(new_map[new_map > cutval]) / len(depth_map[depth_map > cutval])
880
+ this_med_val = np.median(new_map[new_map > cutval])
881
+ area_frac.append(this_area_frac)
882
+ med_val.append(this_med_val)
883
+ return area_frac, med_val
884
+
885
+ def show_clusters(depth_map, labels, maskval=0, n_clusters=2, min=500, max=3000):
886
+ # A routine to show the clusters found by the unsupervised
887
+ # clustering algorithm (start with original map then 2 clusters).
888
+ expanded_labels = expand_labels(depth_map, labels, maskval=maskval)
889
+ hp.visufunc.mollview(depth_map, min=min, max=max)
890
+ for i in range(n_clusters):
891
+ new_map = depth_map.copy()
892
+ new_map[expanded_labels != i + 1] = maskval
893
+ hp.visufunc.mollview(new_map, min=min, max=max)
894
+ return get_area_stats(depth_map, labels, maskval=maskval, n_clusters=n_clusters)
895
+
896
+ def make_clustering_dataset(depth_map, maskval=0, priority_fac=0.9, nside=64):
897
+ """
898
+ A utility routine to get a dataset for unsupervised clustering.
899
+ Note:
900
+ - We want the unmasked regions of the depth map only.
901
+ - We assume masked regions are set to `maskval`,
902
+ and cut 0.1 magnitudes above that.
903
+ - We really want it to look at depth fluctuations.
904
+ So, we have to rescale the
905
+ RA/dec dimensions to avoid them being prioritized
906
+ because their values are larger and
907
+ have more variation than depth. Currently we rescale RA/dec
908
+ such that their standard deviations are 1-priority_fac times
909
+ the standard deviation of the depth map. That's why
910
+ priority_fac is a tunable parameter; it should be between 0 and 1
911
+ """
912
+ if priority_fac < 0 or priority_fac >= 1:
913
+ raise ValueError("priority_fac must lie between 0 and 1")
914
+ theta, phi = hp.pixelfunc.pix2ang(nside, ipix=np.arange(hp.nside2npix(nside)))
915
+ # theta is 0 at the north pole, pi/2 at equator,
916
+ # pi at south pole; phi maps to RA
917
+ ra = np.rad2deg(phi)
918
+ dec = np.rad2deg(0.5 * np.pi - theta)
919
+
920
+ # Make a 3D numpy array containing the unmasked regions,
921
+ # including a rescaling factor to prioritize the depth
922
+ n_unmasked = len(depth_map[depth_map > 0.1])
923
+ my_data = np.zeros((n_unmasked, 3))
924
+ cutval = 0.1 + maskval
925
+ my_data[:, 0] = (
926
+ ra[depth_map > cutval]
927
+ * (1 - priority_fac)
928
+ * np.std(depth_map[depth_map > cutval])
929
+ / np.std(ra[depth_map > cutval])
930
+ )
931
+ my_data[:, 1] = (
932
+ dec[depth_map > cutval]
933
+ * (1 - priority_fac)
934
+ * np.std(depth_map[depth_map > cutval])
935
+ / np.std(dec[depth_map > cutval])
936
+ )
937
+ my_data[:, 2] = depth_map[depth_map > cutval]
938
+ return my_data
939
+
940
+ if not stripes:
941
+ return 1
942
+ else:
943
+ # Do the clustering if we got to this point
944
+ if self.verbose:
945
+ print("Verbose mode - Carrying out the clustering exercise for this map")
946
+ clustering_data = make_clustering_dataset(data_slice_arr["riz_exptime"].ravel(), nside=nside)
947
+ labels = apply_clustering(clustering_data)
948
+ area_frac, med_val = get_area_stats(data_slice_arr["riz_exptime"].ravel(), labels)
949
+ if self.verbose:
950
+ print("Verbose mode - showing original map and clusters identified for this map")
951
+ show_clusters(data_slice_arr["riz_exptime"].ravel(), labels)
952
+ print("Area fractions", area_frac)
953
+ print("Median exposure time values", med_val)
954
+ print("Median exposure time ratio", np.max(med_val) / np.min(med_val))
955
+ print("Verbose mode - proceeding with area cuts")
956
+ # Get the FoM without/with cuts. We want to check the FoM for each
957
+ # area, if we're doing cuts, and
958
+ # return the higher one. This will typically be for the
959
+ # larger area, but not necessarily, if the smaller area
960
+ # is deeper.
961
+ expanded_labels = expand_labels(data_slice_arr["riz_exptime"].ravel(), labels)
962
+ my_hpid_1 = expanded_labels == 1 # np.where(expanded_labels == 1)[0]
963
+ my_hpid_2 = expanded_labels == 2 # np.where(expanded_labels == 2)[0]
964
+ # should this be labels or expanded_labels?!
965
+
966
+ # copies need to be recarrays
967
+ data_slice_subset_2 = deepcopy(data_slice_arr)
968
+ data_slice_subset_1 = deepcopy(data_slice_arr)
969
+ data_slice_subset_1[my_hpid_2] = self.mask_val_arr
970
+ data_slice_subset_2[my_hpid_1] = self.mask_val_arr
971
+ fom1 = self.threebyTwoSummary.run(data_slice_subset_1)
972
+ fom2 = self.threebyTwoSummary.run(data_slice_subset_2)
973
+ fom = np.max((fom1, fom2))
974
+ fom_total = self.threebyTwoSummary.run(data_slice_arr)
975
+ if self.verbose:
976
+ print("FOMs:", fom1, fom2, fom, fom_total)
977
+ return fom / fom_total
978
+
979
+
980
+ class UDropoutsNumbersShallowestDepth(BaseMetric):
981
+ """
982
+ Run as summary metric on MultibandExgalM5.
983
+
984
+
985
+
986
+ Parameters
987
+ ----------
988
+ year : `int`, optional
989
+ The year of the survey to calculate the bias.
990
+ This is used to derive the dm/dz derivative used to translate
991
+ m5 rms into dz rms.
992
+
993
+ Returns
994
+ -------
995
+ result : `float` array
996
+ The ratio of this bias to the desired DESC y1 upper bound on
997
+ the bias, and the ratio
998
+ between the clbias and the y10 DESC SRD requirement.
999
+ Desired values are less than 1 by Y10.
1000
+
1001
+
1002
+ Notes
1003
+ -----
1004
+ This is a summary metric to be run on the results
1005
+ of the MultibandExgalM5.
1006
+
1007
+ MultibandExgalM5 provides the m5 depth in all LSST
1008
+ bands given a specific slice.
1009
+
1010
+ """
1011
+
1012
+ def __init__(self, filter_list=["u", "g", "r", "i", "z", "y"], **kwargs):
1013
+ super().__init__(col="metricdata", percentile=5, **kwargs)
1014
+ # Set mask_val, so that we receive metric_values.filled(mask_val)
1015
+ self.mask_val = hp.UNSEEN
1016
+ self.badval = hp.UNSEEN
1017
+ self.percentileMetric = PercentileMetric(percentile=5, name="percentile")
1018
+ self.filter_list = filter_list
1019
+
1020
+ def run(self, data_slice, slice_point=None):
1021
+ # Technically don't need this for now (isn't used in previous one)
1022
+ # need to define an array of bad values for the masked pixels
1023
+ badval_arr = np.repeat(self.badval, len(self.filter_list))
1024
+ # converts the input recarray to an array
1025
+ data_slice_list = [
1026
+ badval_arr if isinstance(x, float) else x for x in data_slice["metricdata"].tolist()
1027
+ ]
1028
+ # should be (nbins, npix)
1029
+ data_slice_arr = np.asarray(data_slice_list, dtype=float).T
1030
+ data_slice_arr[~np.isfinite(data_slice_arr)] = (
1031
+ hp.UNSEEN
1032
+ ) # need to work with TotalPowerMetric and healpix
1033
+
1034
+ # measure rms in each bin.
1035
+ # The original metric returns an array at each slice_point (of the
1036
+ # original slicer) -- so there is a bit of "rearrangement" that
1037
+ # has to happen to be able to pass a np.array with right dtype
1038
+ # (i.e. dtype = [("metricdata", float)]) to each call to
1039
+ # the rmsMetric `run` methods.
1040
+ percentiles = np.array(
1041
+ [
1042
+ self.percentileMetric.run(np.core.records.fromrecords(x, dtype=[("metricdata", float)]))
1043
+ for x in data_slice_arr
1044
+ ]
1045
+ ) # 6 numbers
1046
+
1047
+ import astropy.units as u
1048
+ from astropy.cosmology import Planck18
1049
+ from astropy.modeling.models import Schechter1D
1050
+
1051
+ def schecter_lf(
1052
+ m_grid: np.ndarray,
1053
+ log_phi_star: float = -2.84,
1054
+ M_star: float = -20.91,
1055
+ alpha: float = -1.68,
1056
+ redshift: float = 3,
1057
+ ) -> np.ndarray:
1058
+ """Schecter Luminosity Function on grid of apparent magnitudes.
1059
+
1060
+ Defaults are for z~3 u-dropout Luminosity Function from Table 6
1061
+ of Harikane et al. 2022.
1062
+
1063
+ Parameters
1064
+ ----------
1065
+ m_grid: np.ndarray
1066
+ Array of apparent AB magnitudes on which to calculate the
1067
+ luminosity function.
1068
+ log_phi_star: float, default=-2.84
1069
+ Natural log of phi_star, the normalization of the luminosity
1070
+ function in units of mag^-1 Mpc^-3
1071
+ M_star: float, default=-20.91
1072
+ The characteristic absolute magnitude where the power-law form
1073
+ of the luminosity function cuts off.
1074
+ alpha: float, default=-1.68
1075
+ The power law index, also known as the faint-end slope.
1076
+ redshift: float, default=3
1077
+ Redshift used for converting apparent magnitudes into absolute
1078
+ magnitudes.
1079
+
1080
+ Returns
1081
+ -------
1082
+ np.ndarray
1083
+ Number density in units mag^-1 Mpc^-3
1084
+ """
1085
+ # Convert observed magnitudes to absolute
1086
+ DL = Planck18.luminosity_distance(redshift).to(u.pc).value # Lum. Dist. in pc
1087
+ M_grid = m_grid - 5 * np.log10(DL / 10) + 2.5 * np.log10(1 + redshift)
1088
+
1089
+ # Calculate luminosity function in absolute magnitudes
1090
+ schechter = Schechter1D(10**log_phi_star, M_star, alpha)
1091
+
1092
+ return schechter(M_grid)
1093
+
1094
+ def number_density(
1095
+ m_grid: np.ndarray,
1096
+ LF: np.ndarray,
1097
+ redshift: float = 3,
1098
+ dz: float = 1,
1099
+ ) -> float:
1100
+ """Calculate number density per deg^2.
1101
+
1102
+ Parameters
1103
+ ----------
1104
+ m_grid: np.ndarray
1105
+ Array of apparent AB magnitudes corresponding to the
1106
+ luminosity function values.
1107
+ LF: np.ndarray
1108
+ The luminosity function evaluated on m_grid, in units
1109
+ mag^-1 Mpc^-3.
1110
+ redshift: float, default=3
1111
+ The central redshift used for evaluating comoving quantities.
1112
+ dz: float, default=1
1113
+ The full width of the redshift bin
1114
+
1115
+ Returns
1116
+ -------
1117
+ float
1118
+ The total number density of galaxies in units deg^-2.
1119
+ """
1120
+ # Calculate comoving depth of redshift bin (Mpc)
1121
+ chi_far = Planck18.comoving_distance(redshift + dz / 2)
1122
+ chi_near = Planck18.comoving_distance(redshift - dz / 2)
1123
+ dchi = chi_far - chi_near
1124
+
1125
+ # Calculate number density (mag^-1 Mpc^-2)
1126
+ n_dm = (LF / u.Mpc**3) * dchi
1127
+
1128
+ # Convert to mag^-1 deg^-2
1129
+ deg_per_Mpc = Planck18.arcsec_per_kpc_comoving(redshift).to(u.deg / u.Mpc)
1130
+ n_dm /= deg_per_Mpc**2
1131
+
1132
+ # Integrate the luminosity function
1133
+ n = np.trapz(n_dm, m_grid)
1134
+
1135
+ return n.value
1136
+
1137
+ u5 = percentiles[self.filter_list == "u"] # uband m5
1138
+ u3 = u5 + 2.5 * np.log10(5 / 3) # convert to uband 3-sigma
1139
+ g_cut = u3 - 1.5 # cut on g band
1140
+ # Calculate LF up to g_cut
1141
+ m_grid = np.linspace(20, g_cut)
1142
+ LF = schecter_lf(m_grid)
1143
+ n_deg2 = number_density(m_grid, LF) # number of objects per sq deg
1144
+
1145
+ return n_deg2
1146
+
1147
+
1148
+ class UDropoutsArea(BaseMetric):
1149
+ """
1150
+ Run as summary metric on MultibandExgalM5.
1151
+
1152
+ Parameters
1153
+ ----------
1154
+ year : `int`, optional
1155
+ The year of the survey to calculate the bias.
1156
+ This is used to derive the dm/dz derivative used to translate m5
1157
+ rms into dz rms.
1158
+
1159
+ Returns
1160
+ -------
1161
+ result : `float` array
1162
+ The ratio of this bias to the desired DESC y1 upper bound on
1163
+ the bias, and the ratio
1164
+ between the clbias and the y10 DESC SRD requirement.
1165
+ Desired values are less than 1 by Y10.
1166
+
1167
+
1168
+ Notes
1169
+ -----
1170
+ This is a summary metric to be run on the results
1171
+ of the MultibandExgalM5.
1172
+
1173
+ MultibandExgalM5 provides the m5 depth in all LSST
1174
+ bands given a specific slice.
1175
+
1176
+ """
1177
+
1178
+ def __init__(self, filter_list=["u", "g", "r", "i", "z", "y"], **kwargs):
1179
+ super().__init__(col="metricdata", percentile=5, **kwargs)
1180
+ # Set mask_val, so that we receive metric_values.filled(mask_val)
1181
+ self.mask_val = hp.UNSEEN
1182
+ self.badval = hp.UNSEEN
1183
+ self.percentileMetric = PercentileMetric(percentile=5, name="percentile")
1184
+ self.filter_list = filter_list
1185
+
1186
+ def run(self, data_slice, slice_point=None):
1187
+ # Technically don't need this for now (isn't used in previous one)
1188
+ # need to define an array of bad values for the masked pixels
1189
+ badval_arr = np.repeat(self.badval, len(self.filter_list))
1190
+ # converts the input recarray to an array
1191
+ data_slice_list = [
1192
+ badval_arr if isinstance(x, float) else x for x in data_slice["metricdata"].tolist()
1193
+ ]
1194
+ # should be (nbins, npix)
1195
+ data_slice_arr = np.asarray(data_slice_list, dtype=float).T
1196
+ data_slice_arr[~np.isfinite(data_slice_arr)] = (
1197
+ hp.UNSEEN
1198
+ ) # need to work with TotalPowerMetric and healpix
1199
+
1200
+ # measure rms in each bin.
1201
+ # The original metric returns an array at each slice_point (of the
1202
+ # original slicer) -- so there is a bit of "rearrangement" that
1203
+ # has to happen to be able to pass a np.array with right dtype
1204
+ # (i.e. dtype = [("metricdata", float)]) to each call to
1205
+ # the rmsMetric `run` methods.
1206
+ percentiles = np.array(
1207
+ [
1208
+ self.percentileMetric.run(np.core.records.fromrecords(x, dtype=[("metricdata", float)]))
1209
+ for x in data_slice_arr
1210
+ ]
1211
+ ) # 6 numbers
1212
+ return percentiles