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.
- batch/compile_prenight_metadata_cache.sh +34 -0
- batch/run_auxtel_prenight_sims.sh +163 -0
- batch/run_prenight_sims.sh +239 -0
- docs/.gitignore +3 -0
- docs/Makefile +32 -0
- docs/api.rst +25 -0
- docs/conf.py +8 -0
- docs/data-api.rst +12 -0
- docs/data-download.rst +70 -0
- docs/developer.rst +14 -0
- docs/documenteer.toml +11 -0
- docs/index.rst +22 -0
- docs/installation.rst +76 -0
- docs/introduction.rst +37 -0
- docs/maf-api-batches.rst +12 -0
- docs/maf-api-db.rst +12 -0
- docs/maf-api-maf-contrib.rst +12 -0
- docs/maf-api-maps.rst +12 -0
- docs/maf-api-metricbundles.rst +12 -0
- docs/maf-api-metrics.rst +12 -0
- docs/maf-api-plots.rst +12 -0
- docs/maf-api-run-comparison.rst +12 -0
- docs/maf-api-slicers.rst +12 -0
- docs/maf-api-stackers.rst +12 -0
- docs/maf-api-utils.rst +12 -0
- docs/maf-api.rst +22 -0
- docs/maf.rst +60 -0
- docs/metric_list.py +49 -0
- docs/moving-objects-api.rst +12 -0
- docs/moving-objects.rst +22 -0
- docs/phot-utils-api.rst +12 -0
- docs/phot-utils.rst +11 -0
- docs/prenight.rst +117 -0
- docs/protoarchive.rst +276 -0
- docs/satellite-constellations-api.rst +12 -0
- docs/satellite-constellations.rst +13 -0
- docs/selfcal-api.rst +12 -0
- docs/selfcal.rst +15 -0
- docs/sim-archive-api.rst +12 -0
- docs/skybrightness-api.rst +12 -0
- docs/skybrightness.rst +23 -0
- docs/user-guide.rst +28 -0
- docs/vseqarchive-architecture.rst +438 -0
- docs/vseqarchive-setup.rst +238 -0
- docs/vseqarchive.rst +225 -0
- rubin_sim/__init__.py +7 -0
- rubin_sim/data/__init__.py +1 -0
- rubin_sim/data/rs_download_data.py +155 -0
- rubin_sim/maf/__init__.py +34 -0
- rubin_sim/maf/batches/__init__.py +18 -0
- rubin_sim/maf/batches/altaz_batch.py +161 -0
- rubin_sim/maf/batches/col_map_dict.py +274 -0
- rubin_sim/maf/batches/common.py +388 -0
- rubin_sim/maf/batches/ddf_batch.py +728 -0
- rubin_sim/maf/batches/filterchange_batch.py +182 -0
- rubin_sim/maf/batches/glance_batch.py +591 -0
- rubin_sim/maf/batches/hourglass_batch.py +79 -0
- rubin_sim/maf/batches/info_batch.py +114 -0
- rubin_sim/maf/batches/metadata_batch.py +558 -0
- rubin_sim/maf/batches/moving_objects_batch.py +1587 -0
- rubin_sim/maf/batches/openshutter_batch.py +100 -0
- rubin_sim/maf/batches/radar_limited.py +1100 -0
- rubin_sim/maf/batches/science_radar_batch.py +1910 -0
- rubin_sim/maf/batches/skycoverage.py +155 -0
- rubin_sim/maf/batches/slew_batch.py +146 -0
- rubin_sim/maf/batches/srd_batch.py +637 -0
- rubin_sim/maf/batches/time_batch.py +757 -0
- rubin_sim/maf/batches/time_sci_batch.py +105 -0
- rubin_sim/maf/batches/visitdepth_batch.py +485 -0
- rubin_sim/maf/db/__init__.py +2 -0
- rubin_sim/maf/db/add_run.py +34 -0
- rubin_sim/maf/db/results_db.py +1074 -0
- rubin_sim/maf/db/tracking_db.py +332 -0
- rubin_sim/maf/ddf_dir.py +53 -0
- rubin_sim/maf/generate_ss.py +218 -0
- rubin_sim/maf/glance_dir.py +58 -0
- rubin_sim/maf/maf_contrib/__init__.py +25 -0
- rubin_sim/maf/maf_contrib/calculate_lsst_field_visibility_astropy.py +170 -0
- rubin_sim/maf/maf_contrib/depth_limited_num_gal_metric.py +109 -0
- rubin_sim/maf/maf_contrib/example_new_metrics.py +40 -0
- rubin_sim/maf/maf_contrib/filter_pair_t_gaps_metric.py +208 -0
- rubin_sim/maf/maf_contrib/grb_transient_metric.py +249 -0
- rubin_sim/maf/maf_contrib/gw170817_det_metric.py +72 -0
- rubin_sim/maf/maf_contrib/intervals_between_obs_metric.py +68 -0
- rubin_sim/maf/maf_contrib/kne_metrics.py +473 -0
- rubin_sim/maf/maf_contrib/lss_metrics.py +45 -0
- rubin_sim/maf/maf_contrib/lss_obs_strategy/__init__.py +3 -0
- rubin_sim/maf/maf_contrib/lss_obs_strategy/constants_for_pipeline.py +73 -0
- rubin_sim/maf/maf_contrib/lss_obs_strategy/galaxy_counts_metric_extended.py +205 -0
- rubin_sim/maf/maf_contrib/lss_obs_strategy/galaxy_counts_with_pixel_calibration.py +171 -0
- rubin_sim/maf/maf_contrib/lv_dwarfs/__init__.py +1 -0
- rubin_sim/maf/maf_contrib/lv_dwarfs/lv_dwarfs_metrics.py +388 -0
- rubin_sim/maf/maf_contrib/microlensing_metric.py +593 -0
- rubin_sim/maf/maf_contrib/num_obs_in_survey_time_overlap_metric.py +43 -0
- rubin_sim/maf/maf_contrib/periodic_metric.py +51 -0
- rubin_sim/maf/maf_contrib/periodic_star_metric.py +186 -0
- rubin_sim/maf/maf_contrib/periodic_star_modulation_metric.py +236 -0
- rubin_sim/maf/maf_contrib/presto_color_kne_pop_metric.py +441 -0
- rubin_sim/maf/maf_contrib/selfcal_uniformity_metric.py +171 -0
- rubin_sim/maf/maf_contrib/star_count_mass_metric.py +64 -0
- rubin_sim/maf/maf_contrib/star_count_metric.py +56 -0
- rubin_sim/maf/maf_contrib/star_counts/__init__.py +8 -0
- rubin_sim/maf/maf_contrib/star_counts/abs_mag.py +324 -0
- rubin_sim/maf/maf_contrib/star_counts/coords.py +99 -0
- rubin_sim/maf/maf_contrib/star_counts/readme.txt +1 -0
- rubin_sim/maf/maf_contrib/star_counts/spec_type.py +91 -0
- rubin_sim/maf/maf_contrib/star_counts/starcount.py +30 -0
- rubin_sim/maf/maf_contrib/star_counts/starcount_bymass.py +93 -0
- rubin_sim/maf/maf_contrib/star_counts/stellardensity.py +59 -0
- rubin_sim/maf/maf_contrib/static_probes_fom_summary_metric.py +386 -0
- rubin_sim/maf/maf_contrib/tdes_pop_metric.py +362 -0
- rubin_sim/maf/maf_contrib/transient_ascii_sed_metric.py +652 -0
- rubin_sim/maf/maf_contrib/triplet_metric.py +156 -0
- rubin_sim/maf/maf_contrib/var_depth_metric.py +157 -0
- rubin_sim/maf/maf_contrib/xrb_metrics.py +523 -0
- rubin_sim/maf/maf_contrib/young_stellar_objects_metric.py +200 -0
- rubin_sim/maf/maf_night_report.py +200 -0
- rubin_sim/maf/make_fbs_tracking_db.py +90 -0
- rubin_sim/maf/maps/__init__.py +8 -0
- rubin_sim/maf/maps/base_map.py +72 -0
- rubin_sim/maf/maps/create_gaia_density_map.py +158 -0
- rubin_sim/maf/maps/dust_map.py +66 -0
- rubin_sim/maf/maps/dust_map_3d.py +190 -0
- rubin_sim/maf/maps/ebv_3d_hp.py +170 -0
- rubin_sim/maf/maps/ebv_hp.py +64 -0
- rubin_sim/maf/maps/gal_coords_map.py +22 -0
- rubin_sim/maf/maps/galactic_plane_priority_maps.py +210 -0
- rubin_sim/maf/maps/stellar_density_map.py +73 -0
- rubin_sim/maf/maps/trilegal_map.py +82 -0
- rubin_sim/maf/metadata_dir.py +90 -0
- rubin_sim/maf/metric_bundles/__init__.py +3 -0
- rubin_sim/maf/metric_bundles/metric_bundle.py +816 -0
- rubin_sim/maf/metric_bundles/metric_bundle_group.py +834 -0
- rubin_sim/maf/metric_bundles/mo_metric_bundle.py +646 -0
- rubin_sim/maf/metrics/__init__.py +52 -0
- rubin_sim/maf/metrics/agn_time_lag_metric.py +118 -0
- rubin_sim/maf/metrics/agnstructure.py +141 -0
- rubin_sim/maf/metrics/area_summary_metrics.py +120 -0
- rubin_sim/maf/metrics/base_metric.py +215 -0
- rubin_sim/maf/metrics/brown_dwarf_metric.py +162 -0
- rubin_sim/maf/metrics/cadence_metrics.py +400 -0
- rubin_sim/maf/metrics/calibration_metrics.py +618 -0
- rubin_sim/maf/metrics/chip_vendor_metric.py +43 -0
- rubin_sim/maf/metrics/color_slope_metrics.py +189 -0
- rubin_sim/maf/metrics/cosmology_summary_metrics.py +1212 -0
- rubin_sim/maf/metrics/coverage_metric.py +48 -0
- rubin_sim/maf/metrics/crowding_metric.py +255 -0
- rubin_sim/maf/metrics/cumulative_metric.py +46 -0
- rubin_sim/maf/metrics/dcr_metric.py +93 -0
- rubin_sim/maf/metrics/exgal_m5.py +57 -0
- rubin_sim/maf/metrics/fft_metric.py +27 -0
- rubin_sim/maf/metrics/galactic_plane_metrics.py +375 -0
- rubin_sim/maf/metrics/galplane_time_sampling_metrics.py +236 -0
- rubin_sim/maf/metrics/hourglass_metric.py +130 -0
- rubin_sim/maf/metrics/incremental_template_metric.py +255 -0
- rubin_sim/maf/metrics/kuiper_metrics.py +26 -0
- rubin_sim/maf/metrics/mo_metrics.py +1496 -0
- rubin_sim/maf/metrics/mo_summary_metrics.py +464 -0
- rubin_sim/maf/metrics/night_pointing_metric.py +70 -0
- rubin_sim/maf/metrics/optimal_m5_metric.py +94 -0
- rubin_sim/maf/metrics/pair_metric.py +69 -0
- rubin_sim/maf/metrics/periodic_detect_metric.py +133 -0
- rubin_sim/maf/metrics/phase_gap_metric.py +172 -0
- rubin_sim/maf/metrics/qso_number_counts_metric.py +138 -0
- rubin_sim/maf/metrics/scaling_metrics.py +148 -0
- rubin_sim/maf/metrics/schedview_metrics.py +39 -0
- rubin_sim/maf/metrics/season_metrics.py +344 -0
- rubin_sim/maf/metrics/simple_metrics.py +546 -0
- rubin_sim/maf/metrics/sky_sat_metric.py +18 -0
- rubin_sim/maf/metrics/sn_cadence_metric.py +104 -0
- rubin_sim/maf/metrics/sn_n_sn_metric.py +1364 -0
- rubin_sim/maf/metrics/sn_sl_metric.py +216 -0
- rubin_sim/maf/metrics/sn_snr_metric.py +715 -0
- rubin_sim/maf/metrics/snr_weight.py +24 -0
- rubin_sim/maf/metrics/star_density.py +77 -0
- rubin_sim/maf/metrics/string_count_metric.py +95 -0
- rubin_sim/maf/metrics/summary_metrics.py +201 -0
- rubin_sim/maf/metrics/surfb_metric.py +175 -0
- rubin_sim/maf/metrics/technical_metrics.py +271 -0
- rubin_sim/maf/metrics/tgaps.py +412 -0
- rubin_sim/maf/metrics/tomography_models.py +2034 -0
- rubin_sim/maf/metrics/transient_metrics.py +268 -0
- rubin_sim/maf/metrics/uniformity_metrics.py +418 -0
- rubin_sim/maf/metrics/use_metrics.py +55 -0
- rubin_sim/maf/metrics/vector_metrics.py +190 -0
- rubin_sim/maf/metrics/visit_groups_metric.py +341 -0
- rubin_sim/maf/metrics/weak_lensing_systematics_metric.py +210 -0
- rubin_sim/maf/plots/__init__.py +14 -0
- rubin_sim/maf/plots/hg_plotters.py +1117 -0
- rubin_sim/maf/plots/hourglass_plotters.py +120 -0
- rubin_sim/maf/plots/mo_plotters.py +358 -0
- rubin_sim/maf/plots/nd_plotters.py +220 -0
- rubin_sim/maf/plots/neo_distance_plotter.py +140 -0
- rubin_sim/maf/plots/night_pointing_plotter.py +96 -0
- rubin_sim/maf/plots/oned_plotters.py +114 -0
- rubin_sim/maf/plots/perceptual_rainbow.py +30 -0
- rubin_sim/maf/plots/plot_handler.py +766 -0
- rubin_sim/maf/plots/skyproj_plotters.py +540 -0
- rubin_sim/maf/plots/spatial_plotters.py +1029 -0
- rubin_sim/maf/plots/special_plotters.py +256 -0
- rubin_sim/maf/plots/two_d_plotters.py +177 -0
- rubin_sim/maf/plots/xyplotter.py +37 -0
- rubin_sim/maf/run_comparison/__init__.py +5 -0
- rubin_sim/maf/run_comparison/archive.py +584 -0
- rubin_sim/maf/run_comparison/gather_summaries.py +125 -0
- rubin_sim/maf/run_comparison/microlensing_compare.py +514 -0
- rubin_sim/maf/run_comparison/radar_plot.py +202 -0
- rubin_sim/maf/run_comparison/summary_plots.py +710 -0
- rubin_sim/maf/run_moving_calc.py +239 -0
- rubin_sim/maf/run_moving_fractions.py +128 -0
- rubin_sim/maf/run_moving_join.py +103 -0
- rubin_sim/maf/run_selfcal_metric.py +92 -0
- rubin_sim/maf/scimaf_dir.py +87 -0
- rubin_sim/maf/show_maf.py +227 -0
- rubin_sim/maf/slicers/__init__.py +13 -0
- rubin_sim/maf/slicers/base_slicer.py +513 -0
- rubin_sim/maf/slicers/base_spatial_slicer.py +202 -0
- rubin_sim/maf/slicers/healpix_sdss_slicer.py +118 -0
- rubin_sim/maf/slicers/healpix_slicer.py +189 -0
- rubin_sim/maf/slicers/healpix_subset_slicer.py +293 -0
- rubin_sim/maf/slicers/hourglass_slicer.py +37 -0
- rubin_sim/maf/slicers/mo_slicer.py +171 -0
- rubin_sim/maf/slicers/movie_slicer.py +288 -0
- rubin_sim/maf/slicers/nd_slicer.py +147 -0
- rubin_sim/maf/slicers/one_d_slicer.py +210 -0
- rubin_sim/maf/slicers/time_interval_slicers.py +283 -0
- rubin_sim/maf/slicers/uni_slicer.py +57 -0
- rubin_sim/maf/slicers/user_points_slicer.py +141 -0
- rubin_sim/maf/stackers/__init__.py +13 -0
- rubin_sim/maf/stackers/base_stacker.py +221 -0
- rubin_sim/maf/stackers/coord_stackers.py +224 -0
- rubin_sim/maf/stackers/date_stackers.py +192 -0
- rubin_sim/maf/stackers/general_stackers.py +683 -0
- rubin_sim/maf/stackers/get_col_info.py +104 -0
- rubin_sim/maf/stackers/label_stackers.py +114 -0
- rubin_sim/maf/stackers/m5_optimal_stacker.py +126 -0
- rubin_sim/maf/stackers/mo_phase.py +265 -0
- rubin_sim/maf/stackers/mo_stackers.py +486 -0
- rubin_sim/maf/stackers/n_follow_stacker.py +187 -0
- rubin_sim/maf/stackers/neo_dist_stacker.py +126 -0
- rubin_sim/maf/stackers/sdss_stackers.py +30 -0
- rubin_sim/maf/stackers/sn_stacker.py +152 -0
- rubin_sim/maf/stackers/teff_stacker.py +154 -0
- rubin_sim/maf/utils/__init__.py +6 -0
- rubin_sim/maf/utils/astrometry_utils.py +93 -0
- rubin_sim/maf/utils/generate_fov_map.py +58 -0
- rubin_sim/maf/utils/get_date_version.py +28 -0
- rubin_sim/maf/utils/maf_utils.py +212 -0
- rubin_sim/maf/utils/opsim_utils.py +259 -0
- rubin_sim/maf/utils/output_utils.py +34 -0
- rubin_sim/maf/utils/sn_n_sn_utils.py +894 -0
- rubin_sim/maf/utils/sn_utils.py +369 -0
- rubin_sim/maf/utils/stellar_mags.py +197 -0
- rubin_sim/maf/web/__init__.py +2 -0
- rubin_sim/maf/web/favicon.ico +0 -0
- rubin_sim/maf/web/maf_run_results.py +645 -0
- rubin_sim/maf/web/maf_tracking.py +139 -0
- rubin_sim/maf/web/sorttable.js +496 -0
- rubin_sim/maf/web/templates/allmetricresults.html +72 -0
- rubin_sim/maf/web/templates/configs.html +28 -0
- rubin_sim/maf/web/templates/macros.html +243 -0
- rubin_sim/maf/web/templates/master.html +294 -0
- rubin_sim/maf/web/templates/metricselect.html +91 -0
- rubin_sim/maf/web/templates/multicolor.html +147 -0
- rubin_sim/maf/web/templates/results.html +82 -0
- rubin_sim/maf/web/templates/runselect.html +58 -0
- rubin_sim/maf/web/templates/stats.html +50 -0
- rubin_sim/moving_objects/__init__.py +8 -0
- rubin_sim/moving_objects/base_obs.py +468 -0
- rubin_sim/moving_objects/cheby_fits.py +689 -0
- rubin_sim/moving_objects/cheby_values.py +231 -0
- rubin_sim/moving_objects/chebyshev_utils.py +365 -0
- rubin_sim/moving_objects/direct_obs.py +376 -0
- rubin_sim/moving_objects/make_lsst_obs.py +282 -0
- rubin_sim/moving_objects/ooephemerides.py +643 -0
- rubin_sim/moving_objects/orbits.py +507 -0
- rubin_sim/moving_objects/pre_generate.py +37 -0
- rubin_sim/moving_objects/utils.py +61 -0
- rubin_sim/phot_utils/__init__.py +7 -0
- rubin_sim/phot_utils/bandpass.py +425 -0
- rubin_sim/phot_utils/photometric_parameters.py +449 -0
- rubin_sim/phot_utils/physical_parameters.py +50 -0
- rubin_sim/phot_utils/predicted_zeropoints.py +245 -0
- rubin_sim/phot_utils/sed.py +1773 -0
- rubin_sim/phot_utils/sed_utils.py +52 -0
- rubin_sim/phot_utils/signaltonoise.py +695 -0
- rubin_sim/phot_utils/spectral_resampling.py +161 -0
- rubin_sim/satellite_constellations/__init__.py +3 -0
- rubin_sim/satellite_constellations/basis_function.py +40 -0
- rubin_sim/satellite_constellations/model_observatory.py +157 -0
- rubin_sim/satellite_constellations/sat_utils.py +488 -0
- rubin_sim/selfcal/__init__.py +4 -0
- rubin_sim/selfcal/generate_catalog.py +188 -0
- rubin_sim/selfcal/offsets.py +119 -0
- rubin_sim/selfcal/solver.py +143 -0
- rubin_sim/selfcal/star_tools.py +42 -0
- rubin_sim/sim_archive/__init__.py +14 -0
- rubin_sim/sim_archive/conda_packages.py +65 -0
- rubin_sim/sim_archive/future_vsarch.py +480 -0
- rubin_sim/sim_archive/make_snapshot.py +255 -0
- rubin_sim/sim_archive/prenight.py +216 -0
- rubin_sim/sim_archive/prenightindex.py +229 -0
- rubin_sim/sim_archive/prototype.py +1029 -0
- rubin_sim/sim_archive/sim_archive.py +680 -0
- rubin_sim/sim_archive/tempdb.py +56 -0
- rubin_sim/sim_archive/util.py +146 -0
- rubin_sim/sim_archive/vseqarchive.py +965 -0
- rubin_sim/sim_archive/vseqmetadata.py +1721 -0
- rubin_sim/skybrightness/__init__.py +5 -0
- rubin_sim/skybrightness/allsky_db.py +93 -0
- rubin_sim/skybrightness/data/ESO_Spectra/eso_tools.py +505 -0
- rubin_sim/skybrightness/data/solarSpec/package.py +22 -0
- rubin_sim/skybrightness/generate_hdf5.py +243 -0
- rubin_sim/skybrightness/interp_components.py +798 -0
- rubin_sim/skybrightness/sky_model.py +771 -0
- rubin_sim/skybrightness/twilight_func.py +83 -0
- rubin_sim/skybrightness/utils.py +91 -0
- rubin_sim-2.6.1.dev2.dist-info/METADATA +95 -0
- rubin_sim-2.6.1.dev2.dist-info/RECORD +387 -0
- rubin_sim-2.6.1.dev2.dist-info/WHEEL +5 -0
- rubin_sim-2.6.1.dev2.dist-info/entry_points.txt +24 -0
- rubin_sim-2.6.1.dev2.dist-info/licenses/LICENSE +674 -0
- rubin_sim-2.6.1.dev2.dist-info/top_level.txt +5 -0
- sql/make_vseqmeta.sql +251 -0
- tests/data/test_data.py +26 -0
- tests/data/test_ddf_grid.py +17 -0
- tests/maf/test_3x2fom.py +68 -0
- tests/maf/test_archive.py +162 -0
- tests/maf/test_basemetrics.py +99 -0
- tests/maf/test_batchcommon.py +36 -0
- tests/maf/test_batches.py +159 -0
- tests/maf/test_cadencemetrics.py +388 -0
- tests/maf/test_calibrationmetrics.py +242 -0
- tests/maf/test_color_slopes.py +71 -0
- tests/maf/test_cosmology_summaries.py +20 -0
- tests/maf/test_gathersummaries.py +48 -0
- tests/maf/test_healpixslicer.py +439 -0
- tests/maf/test_healpixsubsetslicer.py +273 -0
- tests/maf/test_hgplotters.py +145 -0
- tests/maf/test_hourglassmetric.py +33 -0
- tests/maf/test_io.py +187 -0
- tests/maf/test_json.py +169 -0
- tests/maf/test_maps.py +199 -0
- tests/maf/test_metricbundle.py +69 -0
- tests/maf/test_mometrics.py +422 -0
- tests/maf/test_movieslicer.py +216 -0
- tests/maf/test_ndslicer.py +240 -0
- tests/maf/test_neodistanceplotter.py +41 -0
- tests/maf/test_onedslicer.py +245 -0
- tests/maf/test_opsimutils.py +80 -0
- tests/maf/test_plotters.py +134 -0
- tests/maf/test_resultsdb.py +145 -0
- tests/maf/test_schedviewmetrics.py +14 -0
- tests/maf/test_simplemetrics.py +200 -0
- tests/maf/test_snmetrics.py +335 -0
- tests/maf/test_snmetrics_nsn.py +123 -0
- tests/maf/test_stackers.py +423 -0
- tests/maf/test_stellarmags.py +50 -0
- tests/maf/test_stringcount.py +26 -0
- tests/maf/test_summary_plots.py +197 -0
- tests/maf/test_summarymetrics.py +82 -0
- tests/maf/test_technicalmetrics.py +267 -0
- tests/maf/test_templatemetrics.py +86 -0
- tests/maf/test_timeintervalslicers.py +97 -0
- tests/maf/test_trackingdb.py +88 -0
- tests/maf/test_unislicer.py +116 -0
- tests/maf/test_vectormetrics.py +225 -0
- tests/maf/test_visitgroupsmetric.py +181 -0
- tests/moving_objects/test_camera.py +39 -0
- tests/moving_objects/test_chebyfits.py +188 -0
- tests/moving_objects/test_chebyshevutils.py +76 -0
- tests/moving_objects/test_chebyvalues.py +245 -0
- tests/moving_objects/test_ephemerides.py +219 -0
- tests/moving_objects/test_orbits.py +170 -0
- tests/moving_objects/test_sed_data.py +61 -0
- tests/phot_utils/test_approximatebandpasses.py +91 -0
- tests/phot_utils/test_photometricparameters.py +268 -0
- tests/phot_utils/test_photometry.py +61 -0
- tests/phot_utils/test_predicted_zeropoints.py +53 -0
- tests/phot_utils/test_read_bandpasses.py +29 -0
- tests/phot_utils/test_sed.py +368 -0
- tests/phot_utils/test_snr.py +370 -0
- tests/satellite_constellations/test_satellites.py +36 -0
- tests/sim_archive/test_make_snapshot.py +24 -0
- tests/sim_archive/test_prototype_sim_archive.py +204 -0
- tests/sim_archive/test_vseqarchive.py +947 -0
- 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
|