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,1496 @@
|
|
|
1
|
+
__all__ = (
|
|
2
|
+
"BaseMoMetric",
|
|
3
|
+
"NObsMetric",
|
|
4
|
+
"NObsNoSinglesMetric",
|
|
5
|
+
"NNightsMetric",
|
|
6
|
+
"ObsArcMetric",
|
|
7
|
+
"DiscoveryMetric",
|
|
8
|
+
"DiscoveryNChancesMetric",
|
|
9
|
+
"DiscoveryNObsMetric",
|
|
10
|
+
"DiscoveryTimeMetric",
|
|
11
|
+
"DiscoveryDistanceMetric",
|
|
12
|
+
"DiscoveryRadecMetric",
|
|
13
|
+
"DiscoveryEclonlatMetric",
|
|
14
|
+
"DiscoveryVelocityMetric",
|
|
15
|
+
"ActivityOverTimeMetric",
|
|
16
|
+
"ActivityOverPeriodMetric",
|
|
17
|
+
"MagicDiscoveryMetric",
|
|
18
|
+
"HighVelocityMetric",
|
|
19
|
+
"HighVelocityNightsMetric",
|
|
20
|
+
"LightcurveInversionAsteroidMetric",
|
|
21
|
+
"ColorAsteroidMetric",
|
|
22
|
+
"InstantaneousColorMetric",
|
|
23
|
+
"LightcurveColorOuterMetric",
|
|
24
|
+
"PeakVMagMetric",
|
|
25
|
+
"KnownObjectsMetric",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
|
|
30
|
+
from .base_metric import BaseMetric
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _set_vis(sso_obs, snr_limit, snr_col, vis_col):
|
|
34
|
+
if snr_limit is not None:
|
|
35
|
+
vis = np.where(sso_obs[snr_col] >= snr_limit)[0]
|
|
36
|
+
else:
|
|
37
|
+
vis = np.where(sso_obs[vis_col] > 0)[0]
|
|
38
|
+
return vis
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BaseMoMetric(BaseMetric):
|
|
42
|
+
"""Base class for the moving object metrics.
|
|
43
|
+
Intended to be used with the Moving Object Slicer.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
cols : `list` [`str`] or None
|
|
48
|
+
List of the column names needed to run the metric.
|
|
49
|
+
These columns must be in the moving object data files.
|
|
50
|
+
metric_name : `str` or None
|
|
51
|
+
Name of the metric.
|
|
52
|
+
If None, a name is created based on the class name.
|
|
53
|
+
units : `str`, opt
|
|
54
|
+
Units for the resulting metric values.
|
|
55
|
+
badval : `float`, opt
|
|
56
|
+
Flag "bad" value returned if the metric cannot be calculated.
|
|
57
|
+
comment : `str` or None, opt
|
|
58
|
+
A default comment to use for the DisplayDict (display caption)
|
|
59
|
+
if no value is provided elsewhere.
|
|
60
|
+
child_metrics : `list` [`~BaseChildMetric`] or None, opt
|
|
61
|
+
A list of child metrics to run on the results of (this) metric.
|
|
62
|
+
Child metrics take the metric results from this metric and
|
|
63
|
+
add some additional processing or pull out a particular value.
|
|
64
|
+
The results of the child metric are passed to a new MoMetricBundle.
|
|
65
|
+
app_mag_col : `str`, opt
|
|
66
|
+
Name of the apparent magnitude column
|
|
67
|
+
in the object observations. Typically added by a stacker.
|
|
68
|
+
app_mag_v_col : `str`, opt
|
|
69
|
+
Name of the apparent magnitude V band column
|
|
70
|
+
in the objects observations.
|
|
71
|
+
m5_col : `str`, opt
|
|
72
|
+
Name of the m5 limiting magnitude column
|
|
73
|
+
in the objects observations.
|
|
74
|
+
night_col : `str`, opt
|
|
75
|
+
Name of the night column in the objects observations.
|
|
76
|
+
mjd_col : `str`, opt
|
|
77
|
+
Name of the MJD column in the objects observations.
|
|
78
|
+
snr_col : `str`, opt
|
|
79
|
+
Name of the column describing the SNR of this object in a given
|
|
80
|
+
observation, in the objects observations. Added by a stacker.
|
|
81
|
+
vis_col : `str`, opt
|
|
82
|
+
Name of the column describing the probability of detecting
|
|
83
|
+
this object in a given observation. Added by a stacker.
|
|
84
|
+
ra_col : `str`, opt
|
|
85
|
+
Name of the column describing the RA of this object
|
|
86
|
+
in the objects observations.
|
|
87
|
+
dec_col : `str`, opt
|
|
88
|
+
Name of the column describing the Declination of this object
|
|
89
|
+
in the objects observations.
|
|
90
|
+
seeing_col : `str`, opt
|
|
91
|
+
Name of the column describing the seeing to be used in
|
|
92
|
+
evaluations of this object, in the objects observations.
|
|
93
|
+
Tpyically this is the geometric seeing, for evaluating streak length.
|
|
94
|
+
exp_time_col : `str`, opt
|
|
95
|
+
Name of the exposure time column in the objects observations.
|
|
96
|
+
filter_col : `str`, opt
|
|
97
|
+
Name of the column describing the filter used for a given observation,
|
|
98
|
+
in the objects observations.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(
|
|
102
|
+
self,
|
|
103
|
+
cols=None,
|
|
104
|
+
metric_name=None,
|
|
105
|
+
units="#",
|
|
106
|
+
badval=0,
|
|
107
|
+
comment=None,
|
|
108
|
+
child_metrics=None,
|
|
109
|
+
app_mag_col="appMag",
|
|
110
|
+
app_mag_v_col="appMagV",
|
|
111
|
+
m5_col="fiveSigmaDepth",
|
|
112
|
+
night_col="night",
|
|
113
|
+
mjd_col="observationStartMJD",
|
|
114
|
+
snr_col="SNR",
|
|
115
|
+
vis_col="vis",
|
|
116
|
+
ra_col="ra",
|
|
117
|
+
dec_col="dec",
|
|
118
|
+
seeing_col="seeingFwhmGeom",
|
|
119
|
+
exp_time_col="visitExposureTime",
|
|
120
|
+
filter_col="band",
|
|
121
|
+
):
|
|
122
|
+
# Set metric name.
|
|
123
|
+
self.name = metric_name
|
|
124
|
+
if self.name is None:
|
|
125
|
+
self.name = self.__class__.__name__.replace("Metric", "", 1)
|
|
126
|
+
# Set badval and units, leave space for 'comment'
|
|
127
|
+
# (tied to display_dict).
|
|
128
|
+
self.badval = badval
|
|
129
|
+
self.units = units
|
|
130
|
+
self.comment = comment
|
|
131
|
+
# Set some commonly used column names.
|
|
132
|
+
self.m5_col = m5_col
|
|
133
|
+
self.app_mag_col = app_mag_col
|
|
134
|
+
self.app_mag_v_col = app_mag_v_col
|
|
135
|
+
self.night_col = night_col
|
|
136
|
+
self.mjd_col = mjd_col
|
|
137
|
+
self.snr_col = snr_col
|
|
138
|
+
self.vis_col = vis_col
|
|
139
|
+
self.ra_col = ra_col
|
|
140
|
+
self.dec_col = dec_col
|
|
141
|
+
self.seeing_col = seeing_col
|
|
142
|
+
self.exp_time_col = exp_time_col
|
|
143
|
+
self.filter_col = filter_col
|
|
144
|
+
self.cols_req = [
|
|
145
|
+
self.app_mag_col,
|
|
146
|
+
self.m5_col,
|
|
147
|
+
self.night_col,
|
|
148
|
+
self.mjd_col,
|
|
149
|
+
self.snr_col,
|
|
150
|
+
self.vis_col,
|
|
151
|
+
]
|
|
152
|
+
if cols is not None:
|
|
153
|
+
for col in cols:
|
|
154
|
+
self.cols_req.append(col)
|
|
155
|
+
|
|
156
|
+
if child_metrics is None:
|
|
157
|
+
try:
|
|
158
|
+
if not isinstance(self.child_metrics, dict):
|
|
159
|
+
raise ValueError("self.child_metrics must be a dictionary (possibly empty)")
|
|
160
|
+
except AttributeError:
|
|
161
|
+
self.child_metrics = {}
|
|
162
|
+
self.metric_dtype = "float"
|
|
163
|
+
else:
|
|
164
|
+
if not isinstance(child_metrics, dict):
|
|
165
|
+
raise ValueError("childmetrics must be provided as a dictionary.")
|
|
166
|
+
self.child_metrics = child_metrics
|
|
167
|
+
self.metric_dtype = "object"
|
|
168
|
+
self.shape = 1
|
|
169
|
+
|
|
170
|
+
def run(self, sso_obs, orb, hval):
|
|
171
|
+
"""Calculate the metric value.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
sso_obs : `np.ndarray`, (N,)
|
|
176
|
+
The input data to the metric (same as the parent metric).
|
|
177
|
+
orb : `np.ndarray`, (N,)
|
|
178
|
+
The information about the orbit for which the metric is
|
|
179
|
+
being calculated.
|
|
180
|
+
hval : `float`
|
|
181
|
+
The H value for which the metric is being calculated.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
metric_val : `float` or `np.ndarray` or `dict`
|
|
186
|
+
"""
|
|
187
|
+
raise NotImplementedError
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class BaseChildMetric(BaseMoMetric):
|
|
191
|
+
"""Base class for child metrics.
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
parentDiscoveryMetric : `~BaseMoMetric`
|
|
196
|
+
The 'parent' metric which generated the metric data used
|
|
197
|
+
calculate this 'child' metric.
|
|
198
|
+
badval : `float`, opt
|
|
199
|
+
Value to return when metric cannot be calculated.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def __init__(self, parent_discovery_metric, badval=0, **kwargs):
|
|
203
|
+
super().__init__(badval=badval, **kwargs)
|
|
204
|
+
self.parent_metric = parent_discovery_metric
|
|
205
|
+
self.child_metrics = {}
|
|
206
|
+
if "metricDtype" in kwargs:
|
|
207
|
+
self.metric_dtype = kwargs["metricDtype"]
|
|
208
|
+
else:
|
|
209
|
+
self.metric_dtype = "float"
|
|
210
|
+
|
|
211
|
+
def run(self, sso_obs, orb, hval, metric_values):
|
|
212
|
+
"""Calculate the child metric value.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
sso_obs : `np.ndarray`, (N,)
|
|
217
|
+
The input data to the metric (same as the parent metric).
|
|
218
|
+
orb : `np.ndarray`, (N,)
|
|
219
|
+
The information about the orbit for which the metric is
|
|
220
|
+
being calculated.
|
|
221
|
+
hval : `float`
|
|
222
|
+
The H value for which the metric is being calculated.
|
|
223
|
+
metric_values : `dict` or `np.ndarray`, (N,)
|
|
224
|
+
The return value from the parent metric.
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
metric_val : `float`
|
|
229
|
+
"""
|
|
230
|
+
raise NotImplementedError
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class NObsMetric(BaseMoMetric):
|
|
234
|
+
"""
|
|
235
|
+
Count the total number of observations where an SSobject was 'visible'.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
snr_limit : `float` or None
|
|
240
|
+
If the snr_limit is None, detection of the object in a visit is
|
|
241
|
+
determined using the _calcVis method (completeness calculation).
|
|
242
|
+
If not None, the snr is calculated and used as a flat cutoff instead.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
def __init__(self, snr_limit=None, **kwargs):
|
|
246
|
+
super().__init__(**kwargs)
|
|
247
|
+
self.snr_limit = snr_limit
|
|
248
|
+
|
|
249
|
+
def run(self, sso_obs, orb, hval):
|
|
250
|
+
if self.snr_limit is not None:
|
|
251
|
+
vis = np.where(sso_obs[self.snr_col] >= self.snr_limit)[0]
|
|
252
|
+
return vis.size
|
|
253
|
+
else:
|
|
254
|
+
vis = np.where(sso_obs[self.vis_col] > 0)[0]
|
|
255
|
+
return vis.size
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class NObsNoSinglesMetric(BaseMoMetric):
|
|
259
|
+
"""Count the number of observations for an SSobject, without singles.
|
|
260
|
+
Don't include observations where it was a single observation on a night.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
snr_limit : `float` or None
|
|
265
|
+
If the snr_limit is None, detection of the object in a visit is
|
|
266
|
+
determined using the _calcVis method (completeness calculation).
|
|
267
|
+
If not None, the snr is calculated and used as a flat cutoff instead.
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
def __init__(self, snr_limit=None, **kwargs):
|
|
271
|
+
super().__init__(**kwargs)
|
|
272
|
+
self.snr_limit = snr_limit
|
|
273
|
+
|
|
274
|
+
def run(self, sso_obs, orb, hval):
|
|
275
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
276
|
+
if len(vis) == 0:
|
|
277
|
+
return 0
|
|
278
|
+
nights = sso_obs[self.night_col][vis]
|
|
279
|
+
nights = nights.astype("int")
|
|
280
|
+
ncounts = np.bincount(nights)
|
|
281
|
+
nobs = ncounts[np.where(ncounts > 1)].sum()
|
|
282
|
+
return nobs
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class NNightsMetric(BaseMoMetric):
|
|
286
|
+
"""Count the number of distinct nights an SSobject is observed.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
snr_limit : `float` or None
|
|
291
|
+
If the snr_limit is None, detection of the object in a visit is
|
|
292
|
+
determined using the _calcVis method (completeness calculation).
|
|
293
|
+
If not None, the snr is calculated and used as a flat cutoff instead.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
def __init__(self, snr_limit=None, **kwargs):
|
|
297
|
+
super().__init__(**kwargs)
|
|
298
|
+
self.snr_limit = snr_limit
|
|
299
|
+
|
|
300
|
+
def run(self, sso_obs, orb, hval):
|
|
301
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
302
|
+
if len(vis) == 0:
|
|
303
|
+
return 0
|
|
304
|
+
nights = len(np.unique(sso_obs[self.night_col][vis]))
|
|
305
|
+
return nights
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class ObsArcMetric(BaseMoMetric):
|
|
309
|
+
"""Calculate the difference in time between the first and last observation
|
|
310
|
+
of an SSobject.
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
snr_limit : `float` or None
|
|
315
|
+
If the snr_limit is None, detection of the object in a visit is
|
|
316
|
+
determined using the _calcVis method (completeness calculation).
|
|
317
|
+
If not None, the snr is calculated and used as a flat cutoff instead.
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
def __init__(self, snr_limit=None, **kwargs):
|
|
321
|
+
super().__init__(**kwargs)
|
|
322
|
+
self.snr_limit = snr_limit
|
|
323
|
+
|
|
324
|
+
def run(self, sso_obs, orb, hval):
|
|
325
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
326
|
+
if len(vis) == 0:
|
|
327
|
+
return 0
|
|
328
|
+
arc = sso_obs[self.mjd_col][vis].max() - sso_obs[self.mjd_col][vis].min()
|
|
329
|
+
return arc
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class DiscoveryMetric(BaseMoMetric):
|
|
333
|
+
"""Identify the discovery opportunities for an SSobject.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
n_obs_per_night : `int`, opt
|
|
338
|
+
Number of observations required within a single night. Default 2.
|
|
339
|
+
t_min : `float`, opt
|
|
340
|
+
Minimum time span between observations in a single night, in days.
|
|
341
|
+
Default 5 minutes (5/60/24).
|
|
342
|
+
t_max : `float`, opt
|
|
343
|
+
Maximum time span between observations in a single night, in days.
|
|
344
|
+
Default 90 minutes.
|
|
345
|
+
n_nights_per_window : `int`, opt
|
|
346
|
+
Number of nights required with observations, within the track window.
|
|
347
|
+
Default 3.
|
|
348
|
+
t_window : `int`, opt
|
|
349
|
+
Number of nights included in the track window. Default 15.
|
|
350
|
+
snr_limit : None or `float`, opt
|
|
351
|
+
SNR limit to use for observations.
|
|
352
|
+
If snr_limit is None, (default), then it uses
|
|
353
|
+
the completeness calculation added to the 'vis' column
|
|
354
|
+
(probabilistic visibility, based on 5-sigma limit).
|
|
355
|
+
If snr_limit is not None, it uses this SNR value as a cutoff.
|
|
356
|
+
metricName : `str`, opt
|
|
357
|
+
The metric name to use.
|
|
358
|
+
Default will be to construct
|
|
359
|
+
Discovery_nObsPerNightxnNightsPerWindowintWindow.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def __init__(
|
|
363
|
+
self,
|
|
364
|
+
n_obs_per_night=2,
|
|
365
|
+
t_min=5.0 / 60.0 / 24.0,
|
|
366
|
+
t_max=90.0 / 60.0 / 24.0,
|
|
367
|
+
n_nights_per_window=3,
|
|
368
|
+
t_window=15,
|
|
369
|
+
snr_limit=None,
|
|
370
|
+
badval=None,
|
|
371
|
+
**kwargs,
|
|
372
|
+
):
|
|
373
|
+
# Define anything needed by the child metrics first.
|
|
374
|
+
self.snr_limit = snr_limit
|
|
375
|
+
self.child_metrics = {
|
|
376
|
+
"N_Chances": DiscoveryNChancesMetric(self),
|
|
377
|
+
"N_Obs": DiscoveryNObsMetric(self),
|
|
378
|
+
"Time": DiscoveryTimeMetric(self),
|
|
379
|
+
"Distance": DiscoveryDistanceMetric(self),
|
|
380
|
+
"RADec": DiscoveryRadecMetric(self),
|
|
381
|
+
"EcLonLat": DiscoveryEclonlatMetric(self),
|
|
382
|
+
}
|
|
383
|
+
if "metric_name" in kwargs:
|
|
384
|
+
metric_name = kwargs.get("metric_name")
|
|
385
|
+
del kwargs["metric_name"]
|
|
386
|
+
else:
|
|
387
|
+
metric_name = "Discovery_%.0fx%.0fin%.0f" % (
|
|
388
|
+
n_obs_per_night,
|
|
389
|
+
n_nights_per_window,
|
|
390
|
+
t_window,
|
|
391
|
+
)
|
|
392
|
+
# Set up for inheriting from __init__.
|
|
393
|
+
super().__init__(metric_name=metric_name, child_metrics=self.child_metrics, badval=badval, **kwargs)
|
|
394
|
+
# Define anything needed for this metric.
|
|
395
|
+
self.n_obs_per_night = n_obs_per_night
|
|
396
|
+
self.t_min = t_min
|
|
397
|
+
self.t_max = t_max
|
|
398
|
+
self.n_nights_per_window = n_nights_per_window
|
|
399
|
+
self.t_window = t_window
|
|
400
|
+
|
|
401
|
+
def run(self, sso_obs, orb, hval):
|
|
402
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
403
|
+
if len(vis) == 0:
|
|
404
|
+
return self.badval
|
|
405
|
+
# Identify discovery opportunities.
|
|
406
|
+
# Identify visits where the 'night' changes.
|
|
407
|
+
vis_sort = np.argsort(sso_obs[self.mjd_col][vis])
|
|
408
|
+
nights = sso_obs[self.night_col][vis][vis_sort]
|
|
409
|
+
# print 'all nights', nights
|
|
410
|
+
n = np.unique(nights)
|
|
411
|
+
# Identify all the indexes where the night changes in value.
|
|
412
|
+
n_idx = np.searchsorted(nights, n)
|
|
413
|
+
# print 'nightchanges', nights[n_idx]
|
|
414
|
+
# Count the number of observations per night (except last night)
|
|
415
|
+
obs_per_night = (n_idx - np.roll(n_idx, 1))[1:]
|
|
416
|
+
# Add the number of observations on the last night.
|
|
417
|
+
obs_last_night = np.array([len(nights) - n_idx[-1]])
|
|
418
|
+
obs_per_night = np.concatenate((obs_per_night, obs_last_night))
|
|
419
|
+
# Find the nights with more than nObsPerNight.
|
|
420
|
+
n_with_x_obs = n[np.where(obs_per_night >= self.n_obs_per_night)]
|
|
421
|
+
n_idx_many = np.searchsorted(nights, n_with_x_obs)
|
|
422
|
+
n_idx_many_end = np.searchsorted(nights, n_with_x_obs, side="right") - 1
|
|
423
|
+
# Check that nObsPerNight observations are within tMin/tMax
|
|
424
|
+
times_start = sso_obs[self.mjd_col][vis][vis_sort][n_idx_many]
|
|
425
|
+
times_end = sso_obs[self.mjd_col][vis][vis_sort][n_idx_many_end]
|
|
426
|
+
# Identify the nights with 'clearly good' observations.
|
|
427
|
+
good = np.where(
|
|
428
|
+
(times_end - times_start >= self.t_min) & (times_end - times_start <= self.t_max),
|
|
429
|
+
1,
|
|
430
|
+
0,
|
|
431
|
+
)
|
|
432
|
+
# Identify the nights where we need more investigation
|
|
433
|
+
# (a subset of the visits may be within the interval).
|
|
434
|
+
check = np.where(
|
|
435
|
+
(good == 0)
|
|
436
|
+
& (n_idx_many_end + 1 - n_idx_many > self.n_obs_per_night)
|
|
437
|
+
& (times_end - times_start > self.t_max)
|
|
438
|
+
)[0]
|
|
439
|
+
for i, j, c in zip(vis_sort[n_idx_many][check], vis_sort[n_idx_many_end][check], check):
|
|
440
|
+
t = sso_obs[self.mjd_col][vis][vis_sort][i : j + 1]
|
|
441
|
+
dtimes = (np.roll(t, 1 - self.n_obs_per_night) - t)[:-1]
|
|
442
|
+
tidx = np.where((dtimes >= self.t_min) & (dtimes <= self.t_max))[0]
|
|
443
|
+
if len(tidx) > 0:
|
|
444
|
+
good[c] = 1
|
|
445
|
+
# 'good' provides mask for observations which could count as
|
|
446
|
+
# 'good to make tracklets' against sso_obs[vis_sort][n_idx_many].
|
|
447
|
+
# Now identify tracklets which can make tracks.
|
|
448
|
+
good_idx = vis_sort[n_idx_many][good == 1]
|
|
449
|
+
good_idx_ends = vis_sort[n_idx_many_end][good == 1]
|
|
450
|
+
# print 'good tracklets', nights[good_idx]
|
|
451
|
+
if len(good_idx) < self.n_nights_per_window:
|
|
452
|
+
return self.badval
|
|
453
|
+
delta_nights = (
|
|
454
|
+
np.roll(sso_obs[self.night_col][vis][good_idx], 1 - self.n_nights_per_window)
|
|
455
|
+
- sso_obs[self.night_col][vis][good_idx]
|
|
456
|
+
)
|
|
457
|
+
# Identify the index in sso_obs[vis][good_idx] (sorted by mjd)
|
|
458
|
+
# where the discovery opportunity starts.
|
|
459
|
+
start_idxs = np.where((delta_nights >= 0) & (delta_nights <= self.t_window))[0]
|
|
460
|
+
# Identify the index where the discovery opportunity ends.
|
|
461
|
+
end_idxs = np.zeros(len(start_idxs), dtype="int")
|
|
462
|
+
for i, sIdx in enumerate(start_idxs):
|
|
463
|
+
in_window = np.where(
|
|
464
|
+
sso_obs[self.night_col][vis][good_idx] - sso_obs[self.night_col][vis][good_idx][sIdx]
|
|
465
|
+
<= self.t_window
|
|
466
|
+
)[0]
|
|
467
|
+
end_idxs[i] = in_window.max()
|
|
468
|
+
# Convert back to index based on sso_obs[vis] (sorted by expMJD).
|
|
469
|
+
start_idxs = good_idx[start_idxs]
|
|
470
|
+
end_idxs = good_idx_ends[end_idxs]
|
|
471
|
+
return {
|
|
472
|
+
"start": start_idxs[0:1],
|
|
473
|
+
"end": end_idxs[0:1],
|
|
474
|
+
"n_chances": len(start_idxs),
|
|
475
|
+
# "trackletNights": sso_obs[self.night_col][vis][good_idx][0],
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class DiscoveryNChancesMetric(BaseChildMetric):
|
|
480
|
+
"""Calculate total number of discovery opportunities for an SSobject.
|
|
481
|
+
|
|
482
|
+
Returns total number of discovery opportunities.
|
|
483
|
+
Child metric to be used with the Discovery Metric.
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
parentDiscoveryMetric : `~BaseMoMetric`
|
|
488
|
+
The 'parent' metric which generated the metric data used
|
|
489
|
+
calculate this 'child' metric.
|
|
490
|
+
badval : `float`, opt
|
|
491
|
+
Value to return when metric cannot be calculated.
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
def __init__(
|
|
495
|
+
self,
|
|
496
|
+
parent_discovery_metric,
|
|
497
|
+
badval=0,
|
|
498
|
+
**kwargs,
|
|
499
|
+
):
|
|
500
|
+
super().__init__(parent_discovery_metric, badval=badval, **kwargs)
|
|
501
|
+
self.night_start = None # night_start
|
|
502
|
+
self.night_end = None # night_end
|
|
503
|
+
self.snr_limit = parent_discovery_metric.snr_limit
|
|
504
|
+
# Update the metric name to use the night_start/night_end values,
|
|
505
|
+
# if an overriding name is not given.
|
|
506
|
+
if "metric_name" not in kwargs:
|
|
507
|
+
if self.night_start is not None:
|
|
508
|
+
self.name = self.name + "_n%d" % (self.night_start)
|
|
509
|
+
if self.night_end is not None:
|
|
510
|
+
self.name = self.name + "_n%d" % (self.night_end)
|
|
511
|
+
|
|
512
|
+
def run(self, sso_obs, orb, hval, metric_values):
|
|
513
|
+
"""Return the number of different discovery chances we
|
|
514
|
+
had for each object/H combination.
|
|
515
|
+
"""
|
|
516
|
+
return metric_values["n_chances"]
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
class DiscoveryNObsMetric(BaseChildMetric):
|
|
520
|
+
"""Calculates the number of observations in the first discovery
|
|
521
|
+
track of an SSobject.
|
|
522
|
+
|
|
523
|
+
Parameters
|
|
524
|
+
----------
|
|
525
|
+
parentDiscoveryMetric : `~BaseMoMetric`
|
|
526
|
+
The 'parent' metric which generated the metric data used
|
|
527
|
+
calculate this 'child' metric.
|
|
528
|
+
badval : `float`, opt
|
|
529
|
+
Value to return when metric cannot be calculated.
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
def __init__(self, parent_discovery_metric, badval=0, **kwargs):
|
|
533
|
+
super().__init__(parent_discovery_metric, badval=badval, **kwargs)
|
|
534
|
+
# The number of the discovery chance to use.
|
|
535
|
+
self.i = 0
|
|
536
|
+
|
|
537
|
+
def run(self, sso_obs, orb, hval, metric_values):
|
|
538
|
+
if self.i >= len(metric_values["start"]):
|
|
539
|
+
return 0
|
|
540
|
+
start_idx = metric_values["start"][self.i]
|
|
541
|
+
end_idx = metric_values["end"][self.i]
|
|
542
|
+
nobs = end_idx - start_idx
|
|
543
|
+
return nobs
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class DiscoveryTimeMetric(BaseChildMetric):
|
|
547
|
+
"""Returns the time of the first discovery track of an SSobject.
|
|
548
|
+
|
|
549
|
+
Parameters
|
|
550
|
+
----------
|
|
551
|
+
parentDiscoveryMetric : `~BaseMoMetric`
|
|
552
|
+
The 'parent' metric which generated the metric data used
|
|
553
|
+
calculate this 'child' metric.
|
|
554
|
+
badval : `float`, opt
|
|
555
|
+
Value to return when metric cannot be calculated.
|
|
556
|
+
"""
|
|
557
|
+
|
|
558
|
+
def __init__(self, parent_discovery_metric, t_start=None, badval=-999, **kwargs):
|
|
559
|
+
super().__init__(parent_discovery_metric, badval=badval, **kwargs)
|
|
560
|
+
self.i = 0
|
|
561
|
+
self.t_start = t_start
|
|
562
|
+
self.snr_limit = parent_discovery_metric.snr_limit
|
|
563
|
+
|
|
564
|
+
def run(self, sso_obs, orb, hval, metric_values):
|
|
565
|
+
if self.i >= len(metric_values["start"]):
|
|
566
|
+
return self.badval
|
|
567
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
568
|
+
if len(vis) == 0:
|
|
569
|
+
return self.badval
|
|
570
|
+
vis_sort = np.argsort(sso_obs[self.mjd_col][vis])
|
|
571
|
+
times = sso_obs[self.mjd_col][vis][vis_sort]
|
|
572
|
+
start_idx = metric_values["start"][self.i]
|
|
573
|
+
t_disc = times[start_idx]
|
|
574
|
+
if self.t_start is not None:
|
|
575
|
+
t_disc = t_disc - self.t_start
|
|
576
|
+
return t_disc
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
class DiscoveryDistanceMetric(BaseChildMetric):
|
|
580
|
+
"""Returns the distance of the first discovery track of an SSobject.
|
|
581
|
+
|
|
582
|
+
Parameters
|
|
583
|
+
----------
|
|
584
|
+
parentDiscoveryMetric : `~BaseMoMetric`
|
|
585
|
+
The 'parent' metric which generated the metric data used
|
|
586
|
+
calculate this 'child' metric.
|
|
587
|
+
badval : `float`, opt
|
|
588
|
+
Value to return when metric cannot be calculated.
|
|
589
|
+
"""
|
|
590
|
+
|
|
591
|
+
def __init__(self, parent_discovery_metric, distance_col="geo_dist", badval=-999, **kwargs):
|
|
592
|
+
super().__init__(parent_discovery_metric, badval=badval, **kwargs)
|
|
593
|
+
self.i = 0
|
|
594
|
+
self.distance_col = distance_col
|
|
595
|
+
self.snr_limit = parent_discovery_metric.snr_limit
|
|
596
|
+
|
|
597
|
+
def run(self, sso_obs, orb, hval, metric_values):
|
|
598
|
+
if self.i >= len(metric_values["start"]):
|
|
599
|
+
return self.badval
|
|
600
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
601
|
+
if len(vis) == 0:
|
|
602
|
+
return self.badval
|
|
603
|
+
vis_sort = np.argsort(sso_obs[self.mjd_col][vis])
|
|
604
|
+
dists = sso_obs[self.distance_col][vis][vis_sort]
|
|
605
|
+
start_idx = metric_values["start"][self.i]
|
|
606
|
+
dist_disc = dists[start_idx]
|
|
607
|
+
return dist_disc
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
class DiscoveryRadecMetric(BaseChildMetric):
|
|
611
|
+
"""Returns the RA/Dec of the first discovery track of an SSobject.
|
|
612
|
+
|
|
613
|
+
Parameters
|
|
614
|
+
----------
|
|
615
|
+
parentDiscoveryMetric : `~BaseMoMetric`
|
|
616
|
+
The 'parent' metric which generated the metric data used
|
|
617
|
+
calculate this 'child' metric.
|
|
618
|
+
badval : `float`, opt
|
|
619
|
+
Value to return when metric cannot be calculated.
|
|
620
|
+
"""
|
|
621
|
+
|
|
622
|
+
def __init__(self, parent_discovery_metric, badval=None, **kwargs):
|
|
623
|
+
super().__init__(parent_discovery_metric, badval=badval, **kwargs)
|
|
624
|
+
self.i = 0
|
|
625
|
+
self.snr_limit = parent_discovery_metric.snr_limit
|
|
626
|
+
self.metric_dtype = "object"
|
|
627
|
+
|
|
628
|
+
def run(self, sso_obs, orb, hval, metric_values):
|
|
629
|
+
if self.i >= len(metric_values["start"]):
|
|
630
|
+
return self.badval
|
|
631
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
632
|
+
if len(vis) == 0:
|
|
633
|
+
return self.badval
|
|
634
|
+
vis_sort = np.argsort(sso_obs[self.mjd_col][vis])
|
|
635
|
+
ra = sso_obs[self.ra_col][vis][vis_sort]
|
|
636
|
+
dec = sso_obs[self.dec_col][vis][vis_sort]
|
|
637
|
+
start_idx = metric_values["start"][self.i]
|
|
638
|
+
return (ra[start_idx], dec[start_idx])
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
class DiscoveryEclonlatMetric(BaseChildMetric):
|
|
642
|
+
"""Returns the ecliptic lon/lat and solar elong of the first discovery
|
|
643
|
+
track of an SSobject.
|
|
644
|
+
|
|
645
|
+
Parameters
|
|
646
|
+
----------
|
|
647
|
+
parentDiscoveryMetric : `~BaseMoMetric`
|
|
648
|
+
The 'parent' metric which generated the metric data used
|
|
649
|
+
calculate this 'child' metric.
|
|
650
|
+
badval : `float`, opt
|
|
651
|
+
Value to return when metric cannot be calculated.
|
|
652
|
+
"""
|
|
653
|
+
|
|
654
|
+
def __init__(self, parent_discovery_metric, badval=None, **kwargs):
|
|
655
|
+
super().__init__(parent_discovery_metric, badval=badval, **kwargs)
|
|
656
|
+
self.i = 0
|
|
657
|
+
self.snr_limit = parent_discovery_metric.snr_limit
|
|
658
|
+
self.metric_dtype = "object"
|
|
659
|
+
|
|
660
|
+
def run(self, sso_obs, orb, hval, metric_values):
|
|
661
|
+
if self.i >= len(metric_values["start"]):
|
|
662
|
+
return self.badval
|
|
663
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
664
|
+
if len(vis) == 0:
|
|
665
|
+
return self.badval
|
|
666
|
+
vis_sort = np.argsort(sso_obs[self.mjd_col][vis])
|
|
667
|
+
ec_lon = sso_obs["ec_lon"][vis][vis_sort]
|
|
668
|
+
ec_lat = sso_obs["ec_lat"][vis][vis_sort]
|
|
669
|
+
solar_elong = sso_obs["solar_elong"][vis][vis_sort]
|
|
670
|
+
start_idx = metric_values["start"][self.i]
|
|
671
|
+
return (ec_lon[start_idx], ec_lat[start_idx], solar_elong[start_idx])
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
class DiscoveryVelocityMetric(BaseChildMetric):
|
|
675
|
+
"""Returns the sky velocity of the first discovery track of an SSobject.
|
|
676
|
+
|
|
677
|
+
Parameters
|
|
678
|
+
----------
|
|
679
|
+
parentDiscoveryMetric : `~BaseMoMetric`
|
|
680
|
+
The 'parent' metric which generated the metric data used
|
|
681
|
+
calculate this 'child' metric.
|
|
682
|
+
badval : `float`, opt
|
|
683
|
+
Value to return when metric cannot be calculated.
|
|
684
|
+
"""
|
|
685
|
+
|
|
686
|
+
def __init__(self, parent_discovery_metric, badval=-999, **kwargs):
|
|
687
|
+
super().__init__(parent_discovery_metric, badval=badval, **kwargs)
|
|
688
|
+
self.i = 0
|
|
689
|
+
self.snr_limit = parent_discovery_metric.snrLimit
|
|
690
|
+
|
|
691
|
+
def run(self, sso_obs, orb, hval, metric_values):
|
|
692
|
+
if self.i >= len(metric_values["start"]):
|
|
693
|
+
return self.badval
|
|
694
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
695
|
+
if len(vis) == 0:
|
|
696
|
+
return self.badval
|
|
697
|
+
vis_sort = np.argsort(sso_obs[self.mjd_col][vis])
|
|
698
|
+
velocity = sso_obs["velocity"][vis][vis_sort]
|
|
699
|
+
start_idx = metric_values["start"][self.i]
|
|
700
|
+
return velocity[start_idx]
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
class ActivityOverTimeMetric(BaseMoMetric):
|
|
704
|
+
"""Count fraction of survey we could identify activity for an SSobject.
|
|
705
|
+
|
|
706
|
+
Counts the time periods where we would have a chance to detect activity on
|
|
707
|
+
a moving object.
|
|
708
|
+
Splits observations into time periods set by 'window',
|
|
709
|
+
then looks for observations within each window,
|
|
710
|
+
and reports what fraction of the total windows receive 'nObs' visits.
|
|
711
|
+
|
|
712
|
+
Parameters
|
|
713
|
+
----------
|
|
714
|
+
window : `float`
|
|
715
|
+
The (repeated) time period to search for activity.
|
|
716
|
+
snr_limit : None or `float`, opt
|
|
717
|
+
SNR limit to use for observations.
|
|
718
|
+
If snr_limit is None, then it uses
|
|
719
|
+
the completeness calculation added to the 'vis' column
|
|
720
|
+
(probabilistic visibility, based on 5-sigma limit).
|
|
721
|
+
If snr_limit is not None, it uses this SNR value as a cutoff.
|
|
722
|
+
survey_years : `float`, opt
|
|
723
|
+
The length of time of the survey. The test `window` is repeated
|
|
724
|
+
over `survey_years`, and then a fraction calculated from the
|
|
725
|
+
number of bins in which observations were acquired compared to the
|
|
726
|
+
total number of bins.
|
|
727
|
+
metric_name : `str` or None, opt
|
|
728
|
+
Name for the metric. If None, one is created from the class name.
|
|
729
|
+
"""
|
|
730
|
+
|
|
731
|
+
def __init__(self, window, snr_limit=5, survey_years=10.0, metric_name=None, **kwargs):
|
|
732
|
+
if metric_name is None:
|
|
733
|
+
metric_name = "Chance of detecting activity lasting %.0f days" % (window)
|
|
734
|
+
super().__init__(metric_name=metric_name, **kwargs)
|
|
735
|
+
self.snr_limit = snr_limit
|
|
736
|
+
self.window = window
|
|
737
|
+
self.survey_years = survey_years
|
|
738
|
+
self.window_bins = np.arange(0, self.survey_years * 365 + self.window / 2.0, self.window)
|
|
739
|
+
self.n_windows = len(self.window_bins)
|
|
740
|
+
self.units = "%.1f Day Windows" % (self.window)
|
|
741
|
+
|
|
742
|
+
def run(self, sso_obs, orb, hval):
|
|
743
|
+
# For cometary activity, expect activity at the same point in its
|
|
744
|
+
# orbit at the same time, mostly
|
|
745
|
+
# For collisions, expect activity at random times
|
|
746
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
747
|
+
if len(vis) == 0:
|
|
748
|
+
return self.badval
|
|
749
|
+
n, b = np.histogram(sso_obs[vis][self.night_col], bins=self.window_bins)
|
|
750
|
+
activity_windows = np.where(n > 0)[0].size
|
|
751
|
+
return activity_windows / float(self.n_windows)
|
|
752
|
+
|
|
753
|
+
|
|
754
|
+
class ActivityOverPeriodMetric(BaseMoMetric):
|
|
755
|
+
"""Count fraction of object period we could identify activity
|
|
756
|
+
for an SSobject.
|
|
757
|
+
|
|
758
|
+
Count the fraction of the orbit (when split into n_bins) that receive
|
|
759
|
+
observations, in order to have a chance to detect activity.
|
|
760
|
+
|
|
761
|
+
Parameters
|
|
762
|
+
----------
|
|
763
|
+
bin_size : `float`
|
|
764
|
+
Like `window` for the ActivityOverTimeMetric,
|
|
765
|
+
but describes how much of the orbit
|
|
766
|
+
(considered in mean motion) should be included in a given bin.
|
|
767
|
+
In degrees.
|
|
768
|
+
snr_limit : None or `float`, opt
|
|
769
|
+
SNR limit to use for observations.
|
|
770
|
+
If snr_limit is None, then it uses
|
|
771
|
+
the completeness calculation added to the 'vis' column
|
|
772
|
+
(probabilistic visibility, based on 5-sigma limit).
|
|
773
|
+
If snr_limit is not None, it uses this SNR value as a cutoff.
|
|
774
|
+
q_col : `str`, opt
|
|
775
|
+
The name of the q column in the objects orbit data.
|
|
776
|
+
e_col : `str`, opt
|
|
777
|
+
The name of the eccentricity column in the objects orbit data.
|
|
778
|
+
t_peri_col : `str`, opt
|
|
779
|
+
The name of the time of perihelion column in the objects orbit data.
|
|
780
|
+
anomaly_col : `str`, opt
|
|
781
|
+
The name of the mean anomaly column in the objects orbit data.
|
|
782
|
+
metric_name : `str` or None, opt
|
|
783
|
+
Name for the metric. If None, one is created from the class name.
|
|
784
|
+
"""
|
|
785
|
+
|
|
786
|
+
def __init__(
|
|
787
|
+
self,
|
|
788
|
+
bin_size,
|
|
789
|
+
snr_limit=5,
|
|
790
|
+
q_col="q",
|
|
791
|
+
e_col="e",
|
|
792
|
+
a_col="a",
|
|
793
|
+
t_peri_col="tPeri",
|
|
794
|
+
anomaly_col="meanAnomaly",
|
|
795
|
+
metric_name=None,
|
|
796
|
+
**kwargs,
|
|
797
|
+
):
|
|
798
|
+
"""
|
|
799
|
+
@ bin_size : size of orbit slice, in degrees.
|
|
800
|
+
"""
|
|
801
|
+
if metric_name is None:
|
|
802
|
+
metric_name = "Chance of detecting activity covering %.1f of the orbit" % (bin_size)
|
|
803
|
+
super().__init__(metric_name=metric_name, **kwargs)
|
|
804
|
+
self.q_col = q_col
|
|
805
|
+
self.e_col = e_col
|
|
806
|
+
self.a_col = a_col
|
|
807
|
+
self.t_peri_col = t_peri_col
|
|
808
|
+
self.anomaly_col = anomaly_col
|
|
809
|
+
self.snr_limit = snr_limit
|
|
810
|
+
self.bin_size = np.radians(bin_size)
|
|
811
|
+
self.anomaly_bins = np.arange(0, 2 * np.pi, self.bin_size)
|
|
812
|
+
self.anomaly_bins = np.concatenate([self.anomaly_bins, np.array([2 * np.pi])])
|
|
813
|
+
self.n_bins = len(self.anomaly_bins) - 1
|
|
814
|
+
self.units = "%.1f deg" % (np.degrees(self.bin_size))
|
|
815
|
+
|
|
816
|
+
def run(self, sso_obs, orb, hval):
|
|
817
|
+
# For cometary activity, expect activity at the same point in its
|
|
818
|
+
# orbit at the same time, mostly
|
|
819
|
+
# For collisions, expect activity at random times
|
|
820
|
+
if self.a_col in orb.keys():
|
|
821
|
+
a = orb[self.a_col]
|
|
822
|
+
elif self.q_col in orb.keys():
|
|
823
|
+
a = orb[self.q_col] / (1 - orb[self.e_col])
|
|
824
|
+
else:
|
|
825
|
+
return self.badval
|
|
826
|
+
|
|
827
|
+
period = np.power(a, 3.0 / 2.0) * 365.25 # days
|
|
828
|
+
|
|
829
|
+
if self.anomaly_col in orb.keys():
|
|
830
|
+
curranomaly = np.radians(
|
|
831
|
+
orb[self.anomaly_col] + (sso_obs[self.mjd_col] - orb["epoch"]) / period * 360.0
|
|
832
|
+
) % (2 * np.pi)
|
|
833
|
+
elif self.t_peri_col in orb.keys():
|
|
834
|
+
curranomaly = ((sso_obs[self.mjd_col] - orb[self.t_peri_col]) / period) % (2 * np.pi)
|
|
835
|
+
else:
|
|
836
|
+
return self.badval
|
|
837
|
+
|
|
838
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
839
|
+
if len(vis) == 0:
|
|
840
|
+
return self.badval
|
|
841
|
+
n, b = np.histogram(curranomaly[vis], bins=self.anomaly_bins)
|
|
842
|
+
activity_windows = np.where(n > 0)[0].size
|
|
843
|
+
return activity_windows / float(self.n_bins)
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
class MagicDiscoveryMetric(BaseMoMetric):
|
|
847
|
+
"""Count the number of nights with discovery opportunities
|
|
848
|
+
with very good software for an SSobject.
|
|
849
|
+
|
|
850
|
+
Parameters
|
|
851
|
+
----------
|
|
852
|
+
n_obs : `int`, opt
|
|
853
|
+
Total number of observations required for discovery.
|
|
854
|
+
t_window : `float`, opt
|
|
855
|
+
The timespan of the discovery window (days).
|
|
856
|
+
snr_limit : `float` or None
|
|
857
|
+
If None, uses the probabilistic detection likelihood.
|
|
858
|
+
If float, uses the SNR value as a flat cutoff value.
|
|
859
|
+
"""
|
|
860
|
+
|
|
861
|
+
def __init__(self, n_obs=6, t_window=60, snr_limit=None, **kwargs):
|
|
862
|
+
super().__init__(**kwargs)
|
|
863
|
+
self.snr_limit = snr_limit
|
|
864
|
+
self.n_obs = n_obs
|
|
865
|
+
self.t_window = t_window
|
|
866
|
+
self.badval = 0
|
|
867
|
+
|
|
868
|
+
def run(self, sso_obs, orb, hval):
|
|
869
|
+
# Calculate visibility for this orbit at this H.
|
|
870
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
871
|
+
if len(vis) < self.n_obs:
|
|
872
|
+
return self.badval
|
|
873
|
+
t_nights = np.sort(sso_obs[self.night_col])[vis]
|
|
874
|
+
u_nights = np.unique(t_nights)
|
|
875
|
+
indx_w = np.searchsorted(t_nights, u_nights, "left")
|
|
876
|
+
indx_p = np.searchsorted(t_nights - self.t_window, u_nights, "left")
|
|
877
|
+
n_disc = np.where(indx_p - indx_w >= self.n_obs)[0].size
|
|
878
|
+
return n_disc
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
class HighVelocityMetric(BaseMoMetric):
|
|
882
|
+
"""Count number of times an SSobject appears trailed.
|
|
883
|
+
|
|
884
|
+
Count the number of times an asteroid is observed with a velocity
|
|
885
|
+
high enough to make it appear trailed by a factor of (psf_factor)*PSF -
|
|
886
|
+
i.e. velocity >= psf_factor * seeing / visitExpTime.
|
|
887
|
+
Simply counts the total number of observations with high velocity.
|
|
888
|
+
|
|
889
|
+
Parameters
|
|
890
|
+
----------
|
|
891
|
+
psf_factor : `float`, opt
|
|
892
|
+
The factor to multiply the seeing/VisitExpTime by to compare against
|
|
893
|
+
velocity.
|
|
894
|
+
snr_limit : `float` or None
|
|
895
|
+
If None, uses the probabilistic detection likelihood.
|
|
896
|
+
If float, uses the SNR value as a flat cutoff value.
|
|
897
|
+
"""
|
|
898
|
+
|
|
899
|
+
def __init__(self, psf_factor=2.0, snr_limit=None, velocity_col="velocity", **kwargs):
|
|
900
|
+
super().__init__(**kwargs)
|
|
901
|
+
self.velocity_col = velocity_col
|
|
902
|
+
self.snr_limit = snr_limit
|
|
903
|
+
self.psf_factor = psf_factor
|
|
904
|
+
self.badval = 0
|
|
905
|
+
|
|
906
|
+
def run(self, sso_obs, orb, hval):
|
|
907
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
908
|
+
if len(vis) == 0:
|
|
909
|
+
return self.badval
|
|
910
|
+
high_velocity_obs = np.where(
|
|
911
|
+
sso_obs[self.velocity_col][vis]
|
|
912
|
+
>= (24.0 * self.psf_factor * sso_obs[self.seeing_col][vis] / sso_obs[self.exp_time_col][vis])
|
|
913
|
+
)[0]
|
|
914
|
+
return high_velocity_obs.size
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
class HighVelocityNightsMetric(BaseMoMetric):
|
|
918
|
+
"""Count the number of discovery opportunities (via trailing) for an
|
|
919
|
+
SSobject.
|
|
920
|
+
|
|
921
|
+
Determine the first time an asteroid is observed is observed with a
|
|
922
|
+
velocity high enough to make it appear trailed by a factor of
|
|
923
|
+
psf_factor*PSF with n_obs_per_night observations within a given night.
|
|
924
|
+
|
|
925
|
+
Parameters
|
|
926
|
+
----------
|
|
927
|
+
psf_factor: `float`, opt
|
|
928
|
+
Object velocity (deg/day) must be
|
|
929
|
+
>= 24 * psf_factor * seeingGeom (") / visitExpTime (s).
|
|
930
|
+
Default is 2 (i.e. object trailed over 2 psf's).
|
|
931
|
+
n_obs_per_night : `int`, opt
|
|
932
|
+
Number of observations per night required. Default 2.
|
|
933
|
+
snr_limit : `float` or None
|
|
934
|
+
If snr_limit is set as a float, then requires object to be above
|
|
935
|
+
snr_limit SNR in the image.
|
|
936
|
+
If snr_limit is None, this uses the probabilistic 'visibility'
|
|
937
|
+
calculated by the vis stacker, which means SNR ~ 5.
|
|
938
|
+
velocity_col : `str`, opt
|
|
939
|
+
Name of the velocity column in the obs file.
|
|
940
|
+
Default 'velocity'. (note this is deg/day).
|
|
941
|
+
|
|
942
|
+
Returns
|
|
943
|
+
-------
|
|
944
|
+
time : `float`
|
|
945
|
+
The time of the first detection where the conditions are satisfed.
|
|
946
|
+
"""
|
|
947
|
+
|
|
948
|
+
def __init__(self, psf_factor=2.0, n_obs_per_night=2, snr_limit=None, velocity_col="velocity", **kwargs):
|
|
949
|
+
super().__init__(**kwargs)
|
|
950
|
+
self.velocity_col = velocity_col
|
|
951
|
+
self.snr_limit = snr_limit
|
|
952
|
+
self.psf_factor = psf_factor
|
|
953
|
+
self.n_obs_per_night = n_obs_per_night
|
|
954
|
+
|
|
955
|
+
def run(self, sso_obs, orb, hval):
|
|
956
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
957
|
+
if len(vis) == 0:
|
|
958
|
+
return self.badval
|
|
959
|
+
high_velocity_obs = np.where(
|
|
960
|
+
sso_obs[self.velocity_col][vis]
|
|
961
|
+
>= (24.0 * self.psf_factor * sso_obs[self.seeing_col][vis] / sso_obs[self.exp_time_col][vis])
|
|
962
|
+
)[0]
|
|
963
|
+
if len(high_velocity_obs) == 0:
|
|
964
|
+
return 0
|
|
965
|
+
nights = sso_obs[self.night_col][vis][high_velocity_obs]
|
|
966
|
+
n = np.unique(nights)
|
|
967
|
+
n_idx = np.searchsorted(nights, n)
|
|
968
|
+
# Count the number of observations per night (except last night)
|
|
969
|
+
obs_per_night = (n_idx - np.roll(n_idx, 1))[1:]
|
|
970
|
+
# Add the number of observations on the last night.
|
|
971
|
+
obs_last_night = np.array([len(nights) - n_idx[-1]])
|
|
972
|
+
obs_per_night = np.concatenate((obs_per_night, obs_last_night))
|
|
973
|
+
# Find the nights with at least nObsPerNight visits
|
|
974
|
+
# (this is already looking at only high velocity observations).
|
|
975
|
+
n_with_x_obs = n[np.where(obs_per_night >= self.n_obs_per_night)]
|
|
976
|
+
if len(n_with_x_obs) > 0:
|
|
977
|
+
found = sso_obs[np.where(sso_obs[self.night_col] == n_with_x_obs[0])][self.mjd_col][0]
|
|
978
|
+
else:
|
|
979
|
+
found = self.badval
|
|
980
|
+
return found
|
|
981
|
+
|
|
982
|
+
|
|
983
|
+
class LightcurveInversionAsteroidMetric(BaseMoMetric):
|
|
984
|
+
"""Evaluate the liklihood that the detections could be used to enable
|
|
985
|
+
lightcurve inversion. This metric is generally applicable only to inner
|
|
986
|
+
solar system objects (NEOs, MBAs).
|
|
987
|
+
|
|
988
|
+
Parameters
|
|
989
|
+
----------
|
|
990
|
+
weight_det : `float`, opt
|
|
991
|
+
The SNR-weighted number of detections required (per bandpass in any
|
|
992
|
+
ONE of the filters in filterlist).
|
|
993
|
+
Default 50.
|
|
994
|
+
snr_limit : `float` or None, opt
|
|
995
|
+
If snr_limit is set as a float, then requires object to be
|
|
996
|
+
above snr_limit SNR in the image.
|
|
997
|
+
If snr_limit is None, this uses the probabilistic 'visibility'
|
|
998
|
+
calculated by the vis stacker,
|
|
999
|
+
which means SNR ~ 5. Default is None.
|
|
1000
|
+
snr_max : `float`, opt
|
|
1001
|
+
Maximum value toward the SNR-weighting to consider. Default 100.
|
|
1002
|
+
filterlist : `list` [`str`], opt
|
|
1003
|
+
The filters which the lightcurve inversion could be based on.
|
|
1004
|
+
Requirements must be met in one of these filters.
|
|
1005
|
+
|
|
1006
|
+
Returns
|
|
1007
|
+
-------
|
|
1008
|
+
metric_value : `int`
|
|
1009
|
+
0 (could not perform lightcurve inversion) or 1 (could)
|
|
1010
|
+
|
|
1011
|
+
Notes
|
|
1012
|
+
-----
|
|
1013
|
+
This metric determines if the cumulative sum of observations of a
|
|
1014
|
+
target are enough to enable lightcurve inversion for shape modeling.
|
|
1015
|
+
For this to be true, multiple conditions need to be
|
|
1016
|
+
satisfied:
|
|
1017
|
+
|
|
1018
|
+
1) The SNR-weighted number of observations (each observation is weighted
|
|
1019
|
+
by its SNR, up to a max of 100) must be larger than the
|
|
1020
|
+
threshold weight_det (default 50)
|
|
1021
|
+
2) Ecliptic longitudinal coverage needs to be at least 90 degrees,
|
|
1022
|
+
and the absolute deviation needs to be at least 1/8th the
|
|
1023
|
+
longitudinal coverage.
|
|
1024
|
+
3) The phase angle coverage needs to span at least 5 degrees.
|
|
1025
|
+
|
|
1026
|
+
For evaluation of condition 2, the median ecliptic longitude is
|
|
1027
|
+
subtracted from all longitudes, and the modulo 360 of those values
|
|
1028
|
+
is taken. This ensures that the wrap around 360 is handled correctly.
|
|
1029
|
+
|
|
1030
|
+
For more information on the above conditions, please see
|
|
1031
|
+
https://docs.google.com/document/d/1GAriM7trpTS08uanjUF7PyKALB2JBTjVT7Y6R30i0-8/edit?usp=sharing
|
|
1032
|
+
|
|
1033
|
+
Contributed by Steve Chesley, Wes Fraser, Josef Durech, and the
|
|
1034
|
+
inner solar system working group.
|
|
1035
|
+
"""
|
|
1036
|
+
|
|
1037
|
+
def __init__(
|
|
1038
|
+
self, weight_det=50, snr_limit=None, snr_max=100, filterlist=("u", "g", "r", "i", "z", "y"), **kwargs
|
|
1039
|
+
):
|
|
1040
|
+
super().__init__(**kwargs)
|
|
1041
|
+
self.snr_limit = snr_limit
|
|
1042
|
+
self.snr_max = snr_max
|
|
1043
|
+
self.weight_det = weight_det
|
|
1044
|
+
self.filterlist = filterlist
|
|
1045
|
+
|
|
1046
|
+
def run(self, sso_obs, orb, hval):
|
|
1047
|
+
# Calculate the clipped SNR - ranges from snrLimit / SNR+vis to snrMax.
|
|
1048
|
+
clip_snr = np.minimum(sso_obs[self.snr_col], self.snr_max)
|
|
1049
|
+
if self.snr_limit is not None:
|
|
1050
|
+
clip_snr = np.where(sso_obs[self.snr_col] <= self.snr_limit, 0, clip_snr)
|
|
1051
|
+
else:
|
|
1052
|
+
clip_snr = np.where(sso_obs[self.vis_col] == 0, 0, clip_snr)
|
|
1053
|
+
if len(np.where(clip_snr > 0)[0]) == 0:
|
|
1054
|
+
return 0
|
|
1055
|
+
# Check each filter in filterlist:
|
|
1056
|
+
# stop as soon as find a filter that matches requirements.
|
|
1057
|
+
inversion_possible = 0
|
|
1058
|
+
for f in self.filterlist:
|
|
1059
|
+
# Is the SNR-weight sum of observations in this filter high enough?
|
|
1060
|
+
match = np.where(sso_obs[self.filter_col] == f)
|
|
1061
|
+
snr_sum = np.sum(clip_snr[match]) / self.snr_max
|
|
1062
|
+
if snr_sum < self.weight_det:
|
|
1063
|
+
# Do not have enough SNR-weighted observations,
|
|
1064
|
+
# so skip on to the next filter.
|
|
1065
|
+
continue
|
|
1066
|
+
# Is the ecliptic longitude coverage for the visible
|
|
1067
|
+
# observations sufficient?
|
|
1068
|
+
# Is the phase coverage sufficient?
|
|
1069
|
+
vis = np.where(clip_snr[match] > 0)
|
|
1070
|
+
ec_l = sso_obs["ecLon"][match][vis]
|
|
1071
|
+
phase_angle = sso_obs["phase"][match][vis]
|
|
1072
|
+
# Calculate the absolute deviation and range of ecliptic longitude.
|
|
1073
|
+
ec_l_centred = (ec_l - np.median(ec_l)) % 360.0
|
|
1074
|
+
a_dev = np.sum(np.abs(ec_l_centred - np.mean(ec_l_centred))) / len(ec_l_centred)
|
|
1075
|
+
d_l = np.max(ec_l) - np.min(ec_l)
|
|
1076
|
+
# Calculate the range of the phase angle
|
|
1077
|
+
dp = np.max(phase_angle) - np.min(phase_angle)
|
|
1078
|
+
# Metric requirement is that d_l >= 90 deg, absolute
|
|
1079
|
+
# deviation is greater than d_l/8
|
|
1080
|
+
# and then that the phase coverage is more than 5 degrees.
|
|
1081
|
+
# Stop as soon as find a case where this is true.
|
|
1082
|
+
if d_l >= 90.0 and a_dev >= d_l / 8 and dp >= 5:
|
|
1083
|
+
inversion_possible += 1
|
|
1084
|
+
break
|
|
1085
|
+
return inversion_possible
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
class ColorAsteroidMetric(BaseMoMetric):
|
|
1089
|
+
"""Calculate the likelihood of being able to calculate the color of an
|
|
1090
|
+
object. This metric is appropriate for MBAs and NEOs,
|
|
1091
|
+
and other inner solar system objects.
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
Parameters
|
|
1095
|
+
----------
|
|
1096
|
+
weight_det: float, opt
|
|
1097
|
+
The SNR-weighted number of detections required (per bandpass in any
|
|
1098
|
+
ONE of the filters in filterlist).
|
|
1099
|
+
Default 10.
|
|
1100
|
+
snr_limit: float or None, opt
|
|
1101
|
+
If snr_limit is set as a float, then requires object to be above
|
|
1102
|
+
snr_limit SNR in the image.
|
|
1103
|
+
If snr_limit is None, this uses the probabilistic 'visibility'
|
|
1104
|
+
calculated by the vis stacker,
|
|
1105
|
+
which means SNR ~ 5. Default is None.
|
|
1106
|
+
snr_max: float, opt
|
|
1107
|
+
Maximum value toward the SNR-weighting to consider. Default 20.
|
|
1108
|
+
|
|
1109
|
+
Returns
|
|
1110
|
+
-------
|
|
1111
|
+
flag : `int`
|
|
1112
|
+
An integer 'flag' that indicates whether the mean magnitude
|
|
1113
|
+
(and thus a color) was determined in:
|
|
1114
|
+
0 = no bands
|
|
1115
|
+
1 = g and (r or i) and (z or y).
|
|
1116
|
+
i.e. obtain colors g-r or g-i PLUS g-z or g-y
|
|
1117
|
+
2 = Any 4 different filters (from grizy).
|
|
1118
|
+
i.e. colors = g-r, r-i, i-z, OR r-i, i-z, z-y..
|
|
1119
|
+
3 = All 5 from grizy. i.e. colors g-r, r-i, i-z, z-y.
|
|
1120
|
+
4 = All 6 filters (ugrizy) -- best possible! add u-g.
|
|
1121
|
+
|
|
1122
|
+
Notes
|
|
1123
|
+
-----
|
|
1124
|
+
The metric evaluates if the SNR-weighted number of observations are
|
|
1125
|
+
enough to determine an approximate lightcurve and phase function --
|
|
1126
|
+
and from this, then a color for the asteroid can be determined.
|
|
1127
|
+
The assumption is that you must fit the lightcurve/phase function
|
|
1128
|
+
in each bandpass, and could do this well-enough if you have at least
|
|
1129
|
+
weight_det SNR-weighted observations in the bandpass.
|
|
1130
|
+
e.g. to find a g-r color, you must have 10 (SNR-weighted) obs in g
|
|
1131
|
+
and 10 in r.
|
|
1132
|
+
|
|
1133
|
+
For more details, see
|
|
1134
|
+
https://docs.google.com/document/d/1GAriM7trpTS08uanjUF7PyKALB2JBTjVT7Y6R30i0-8/edit?usp=sharing
|
|
1135
|
+
|
|
1136
|
+
Contributed by Wes Fraser, Steven Chesley
|
|
1137
|
+
& the inner solar system working group.
|
|
1138
|
+
"""
|
|
1139
|
+
|
|
1140
|
+
def __init__(self, weight_det=10, snr_max=20, snr_limit=None, **kwargs):
|
|
1141
|
+
super().__init__(**kwargs)
|
|
1142
|
+
self.weight_det = weight_det
|
|
1143
|
+
self.snr_limit = snr_limit
|
|
1144
|
+
self.snr_max = snr_max
|
|
1145
|
+
self.filterlist = ("u", "g", "r", "i", "z", "y")
|
|
1146
|
+
|
|
1147
|
+
def run(self, sso_obs, orb, hval):
|
|
1148
|
+
clip_snr = np.minimum(sso_obs[self.snr_col], self.snr_max)
|
|
1149
|
+
if self.snr_limit is not None:
|
|
1150
|
+
clip_snr = np.where(sso_obs[self.snr_col] <= self.snr_limit, 0, clip_snr)
|
|
1151
|
+
else:
|
|
1152
|
+
clip_snr = np.where(sso_obs[self.vis_col] == 0, 0, clip_snr)
|
|
1153
|
+
if len(np.where(clip_snr > 0)[0]) == 0:
|
|
1154
|
+
return self.badval
|
|
1155
|
+
|
|
1156
|
+
# Evaluate SNR-weighted number of observations in each filter.
|
|
1157
|
+
filter_weight = {}
|
|
1158
|
+
for f in self.filterlist:
|
|
1159
|
+
match = np.where(sso_obs[self.filter_col] == f)
|
|
1160
|
+
snrweight = np.sum(clip_snr[match]) / self.snr_max
|
|
1161
|
+
# If the snrweight exceeds the weightDet, add it to the dictionary.
|
|
1162
|
+
if snrweight > self.weight_det:
|
|
1163
|
+
filter_weight[f] = snrweight
|
|
1164
|
+
|
|
1165
|
+
# Now assign a flag:
|
|
1166
|
+
# 0 = no bands
|
|
1167
|
+
# 1 = g and (r or i) and (z or y).
|
|
1168
|
+
# i.e. obtain colors g-r or g-i PLUS g-z or g-y
|
|
1169
|
+
# 2 = Any 4 different filters (from grizy).
|
|
1170
|
+
# i.e. colors = g-r, r-i, i-z, OR r-i, i-z, z-y..
|
|
1171
|
+
# 3 = All 5 from grizy. i.e. colors g-r, r-i, i-z, z-y.
|
|
1172
|
+
# 4 = All 6 filters (ugrizy) -- best possible! add u-g.
|
|
1173
|
+
all_six = set(self.filterlist)
|
|
1174
|
+
good_five = set(["g", "r", "i", "z", "y"])
|
|
1175
|
+
|
|
1176
|
+
if len(filter_weight) == 0:
|
|
1177
|
+
# this lets us stop evaluating here if possible.
|
|
1178
|
+
flag = 0
|
|
1179
|
+
elif all_six.intersection(filter_weight) == all_six:
|
|
1180
|
+
flag = 4
|
|
1181
|
+
elif good_five.intersection(filter_weight) == good_five:
|
|
1182
|
+
flag = 3
|
|
1183
|
+
elif len(good_five.intersection(filter_weight)) == 4:
|
|
1184
|
+
flag = 2
|
|
1185
|
+
elif "g" in filter_weight:
|
|
1186
|
+
# Have 'g' - do we have (r or i) and (z or y)
|
|
1187
|
+
if ("r" in filter_weight or "i" in filter_weight) and (
|
|
1188
|
+
"z" in filter_weight or "y" in filter_weight
|
|
1189
|
+
):
|
|
1190
|
+
flag = 1
|
|
1191
|
+
else:
|
|
1192
|
+
flag = 0
|
|
1193
|
+
else:
|
|
1194
|
+
flag = 0
|
|
1195
|
+
|
|
1196
|
+
return flag
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
class LightcurveColorOuterMetric(BaseMoMetric):
|
|
1200
|
+
"""Calculate the liklihood of being able to calculate a color and
|
|
1201
|
+
lightcurve for outer solar system objects.
|
|
1202
|
+
|
|
1203
|
+
Parameters
|
|
1204
|
+
----------
|
|
1205
|
+
snr_limit : `float` or None, opt
|
|
1206
|
+
If snr_limit is set as a float, then requires object to be above
|
|
1207
|
+
snr_limit SNR in the image.
|
|
1208
|
+
If snr_limit is None, this uses the probabilistic 'visibility'
|
|
1209
|
+
calculated by the vis stacker,
|
|
1210
|
+
which means SNR ~ 5. Default is None.
|
|
1211
|
+
num_req : `int`, opt
|
|
1212
|
+
Number of observations required for a lightcurve fitting. Default 30.
|
|
1213
|
+
num_sec_filt : `int`, opt
|
|
1214
|
+
Number of observations required in a secondary band for color only.
|
|
1215
|
+
Default 20.
|
|
1216
|
+
filterlist : `list` [`str`], opt
|
|
1217
|
+
Filters that the primary/secondary measurements can be in.
|
|
1218
|
+
|
|
1219
|
+
Returns
|
|
1220
|
+
-------
|
|
1221
|
+
flag : `int`
|
|
1222
|
+
A flag that indicates whether a color/lightcurve was generated in:
|
|
1223
|
+
0 = no lightcurve
|
|
1224
|
+
(although may have had 'color' in one or more band)
|
|
1225
|
+
1 = a lightcurve in a single filter
|
|
1226
|
+
(but no additional color information)
|
|
1227
|
+
2+ = lightcurves in more than one filter (or lightcurve + color)
|
|
1228
|
+
e.g. lightcurve in 2 bands,
|
|
1229
|
+
with additional color information in another = 3.
|
|
1230
|
+
|
|
1231
|
+
Notes
|
|
1232
|
+
-----
|
|
1233
|
+
This metric is appropriate for outer solar system objects,
|
|
1234
|
+
such as TNOs and SDOs.
|
|
1235
|
+
|
|
1236
|
+
This metric evaluates whether the number of observations is
|
|
1237
|
+
sufficient to fit a lightcurve in a primary and secondary bandpass.
|
|
1238
|
+
The primary bandpass requires more observations than the secondary.
|
|
1239
|
+
Essentially, it's a complete lightcurve in one or both bandpasses, with at
|
|
1240
|
+
least a semi-complete lightcurve in the secondary band.
|
|
1241
|
+
|
|
1242
|
+
The lightcurve/color can be calculated with any two of the
|
|
1243
|
+
bandpasses in filterlist.
|
|
1244
|
+
|
|
1245
|
+
Contributed by Wes Fraser.
|
|
1246
|
+
"""
|
|
1247
|
+
|
|
1248
|
+
def __init__(
|
|
1249
|
+
self, snr_limit=None, num_req=30, num_sec_filt=20, filterlist=("u", "g", "r", "i", "z", "y"), **kwargs
|
|
1250
|
+
):
|
|
1251
|
+
super().__init__(**kwargs)
|
|
1252
|
+
self.snr_limit = snr_limit
|
|
1253
|
+
self.num_req = num_req
|
|
1254
|
+
self.num_sec_filt = num_sec_filt
|
|
1255
|
+
self.filterlist = filterlist
|
|
1256
|
+
|
|
1257
|
+
def run(self, sso_obs, orb, hval):
|
|
1258
|
+
vis = _set_vis(sso_obs, self.snr_limit, self.snr_col, self.vis_col)
|
|
1259
|
+
if len(vis) == 0:
|
|
1260
|
+
return 0
|
|
1261
|
+
|
|
1262
|
+
lightcurves = set()
|
|
1263
|
+
colors = set()
|
|
1264
|
+
for f in self.filterlist:
|
|
1265
|
+
nmatch = np.where(sso_obs[vis][self.filter_col] == f)[0]
|
|
1266
|
+
if len(nmatch) >= self.num_req:
|
|
1267
|
+
lightcurves.add(f)
|
|
1268
|
+
if len(nmatch) >= self.num_sec_filt:
|
|
1269
|
+
colors.add(f)
|
|
1270
|
+
|
|
1271
|
+
# Set the flags - first the number of filters with lightcurves.
|
|
1272
|
+
flag = len(lightcurves)
|
|
1273
|
+
# And check if there were extra filters which had enough for a color
|
|
1274
|
+
# but not enough for a full lightcurve.
|
|
1275
|
+
if len(colors.difference(lightcurves)) > 0:
|
|
1276
|
+
# If there was no lightcurve available to match against:
|
|
1277
|
+
if len(lightcurves) == 0:
|
|
1278
|
+
flag = 0
|
|
1279
|
+
else:
|
|
1280
|
+
# We had a lightcurve and now can add a color.
|
|
1281
|
+
flag += 1
|
|
1282
|
+
return flag
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
class InstantaneousColorMetric(BaseMoMetric):
|
|
1286
|
+
"""Identify SSobjects which could have observations suitable to
|
|
1287
|
+
determine instanteous colors.
|
|
1288
|
+
|
|
1289
|
+
This is roughly defined as objects which have more than n_pairs pairs
|
|
1290
|
+
of observations with SNR greater than snr_limit,
|
|
1291
|
+
in bands bandOne and bandTwo, within n_hours.
|
|
1292
|
+
|
|
1293
|
+
Parameters
|
|
1294
|
+
----------
|
|
1295
|
+
n_pairs : `int`, opt
|
|
1296
|
+
The number of pairs of observations (in each band) that must be
|
|
1297
|
+
within n_hours. Default 1.
|
|
1298
|
+
snr_limit : `float`, opt
|
|
1299
|
+
The SNR limit for the observations. Default 10.
|
|
1300
|
+
n_hours : `float`, opt
|
|
1301
|
+
The time interval between observations in the two bandpasses (hours).
|
|
1302
|
+
Default 0.5 hours.
|
|
1303
|
+
b_one : `str`, opt
|
|
1304
|
+
The first bandpass for the color. Default 'g'.
|
|
1305
|
+
b_two : `str`, opt
|
|
1306
|
+
The second bandpass for the color. Default 'r'.
|
|
1307
|
+
|
|
1308
|
+
Returns
|
|
1309
|
+
-------
|
|
1310
|
+
flag : `int`
|
|
1311
|
+
0 (no color possible under these constraints) or 1 (color possible).
|
|
1312
|
+
"""
|
|
1313
|
+
|
|
1314
|
+
def __init__(self, n_pairs=1, snr_limit=10, n_hours=0.5, b_one="g", b_two="r", **kwargs):
|
|
1315
|
+
super().__init__(**kwargs)
|
|
1316
|
+
self.n_pairs = n_pairs
|
|
1317
|
+
self.snr_limit = snr_limit
|
|
1318
|
+
self.n_hours = n_hours
|
|
1319
|
+
self.b_one = b_one
|
|
1320
|
+
self.b_two = b_two
|
|
1321
|
+
self.badval = -666
|
|
1322
|
+
|
|
1323
|
+
def run(self, sso_obs, orb, hval):
|
|
1324
|
+
vis = np.where(sso_obs[self.snr_col] >= self.snr_limit)[0]
|
|
1325
|
+
if len(vis) < self.n_pairs * 2:
|
|
1326
|
+
return 0
|
|
1327
|
+
b_one_obs = np.where(sso_obs[self.filter_col][vis] == self.b_one)[0]
|
|
1328
|
+
b_two_obs = np.where(sso_obs[self.filter_col][vis] == self.b_two)[0]
|
|
1329
|
+
timesb_one = sso_obs[self.mjd_col][vis][b_one_obs]
|
|
1330
|
+
timesb_two = sso_obs[self.mjd_col][vis][b_two_obs]
|
|
1331
|
+
if len(timesb_one) == 0 or len(timesb_two) == 0:
|
|
1332
|
+
return 0
|
|
1333
|
+
d_time = self.n_hours / 24.0
|
|
1334
|
+
# Calculate the time between the closest pairs of observations.
|
|
1335
|
+
in_order = np.searchsorted(timesb_one, timesb_two, "right")
|
|
1336
|
+
in_order = np.where(in_order - 1 > 0, in_order - 1, 0)
|
|
1337
|
+
dt_pairs = timesb_two - timesb_one[in_order]
|
|
1338
|
+
if len(np.where(dt_pairs < d_time)[0]) >= self.n_pairs:
|
|
1339
|
+
found = 1
|
|
1340
|
+
else:
|
|
1341
|
+
found = 0
|
|
1342
|
+
return found
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
class PeakVMagMetric(BaseMoMetric):
|
|
1346
|
+
"""Pull out the peak V magnitude of all observations of the SSobject."""
|
|
1347
|
+
|
|
1348
|
+
def __init__(self, **kwargs):
|
|
1349
|
+
super().__init__(**kwargs)
|
|
1350
|
+
|
|
1351
|
+
def run(self, sso_obs, orb, hval):
|
|
1352
|
+
peak_vmag = np.min(sso_obs[self.app_mag_v_col])
|
|
1353
|
+
return peak_vmag
|
|
1354
|
+
|
|
1355
|
+
|
|
1356
|
+
class KnownObjectsMetric(BaseMoMetric):
|
|
1357
|
+
"""Identify SSobjects which could be classified as 'previously known'
|
|
1358
|
+
based on their peak V magnitude.
|
|
1359
|
+
This is most appropriate for NEO surveys, where most of the sky has
|
|
1360
|
+
been covered so the exact location
|
|
1361
|
+
(beyond being in the visible sky) is not as important.
|
|
1362
|
+
|
|
1363
|
+
Default parameters tuned to match NEO survey capabilities.
|
|
1364
|
+
Returns the time at which each first reached that threshold V magnitude.
|
|
1365
|
+
The default values are calibrated using the NEOs larger than 140m
|
|
1366
|
+
discovered in the last 20 years and assuming a 30% completeness in 2017.
|
|
1367
|
+
|
|
1368
|
+
Note: the default parameters here were set up in ~2012, and are likely
|
|
1369
|
+
out of date (potentially adding another epoch of discovery).
|
|
1370
|
+
|
|
1371
|
+
Parameters
|
|
1372
|
+
-----------
|
|
1373
|
+
elong_thresh : `float`, opt
|
|
1374
|
+
The cutoff in solar elongation to consider an object 'visible'.
|
|
1375
|
+
Default 100 deg.
|
|
1376
|
+
v_mag_thresh1 : `float`, opt
|
|
1377
|
+
The magnitude threshold for previously known objects. Default 20.0.
|
|
1378
|
+
eff1 : `float`, opt
|
|
1379
|
+
The likelihood of actually achieving each individual input observation.
|
|
1380
|
+
If the input observations include one observation per day,
|
|
1381
|
+
an 'eff' value of 0.3 would mean that (on average) only one third
|
|
1382
|
+
of these observations would be achieved. This is similar to the level
|
|
1383
|
+
for LSST, which can cover the visible sky every 3-4 days.
|
|
1384
|
+
Default 0.1
|
|
1385
|
+
t_switch1 : `float`, opt
|
|
1386
|
+
The (MJD) time to switch between v_mag_thresh1 + eff1 to
|
|
1387
|
+
v_mag_thresh2 + eff2, e.g. the end of the first period.
|
|
1388
|
+
Default 53371 (2005).
|
|
1389
|
+
v_mag_thresh2 : `float`, opt
|
|
1390
|
+
The magnitude threshhold for previously known objects. Default 22.0.
|
|
1391
|
+
This is based on assuming PS and other surveys will be efficient
|
|
1392
|
+
down to V=22.
|
|
1393
|
+
eff2 : `float`, opt
|
|
1394
|
+
The efficiency of observations during the second period of time.
|
|
1395
|
+
Default 0.1
|
|
1396
|
+
t_switch2 : `float`, opt
|
|
1397
|
+
The (MJD) time to switch between v_mag_thresh2 + eff2 to
|
|
1398
|
+
v_mag_thresh3 + eff3.
|
|
1399
|
+
Default 57023 (2015).
|
|
1400
|
+
v_mag_thresh3 : `float`, opt
|
|
1401
|
+
The magnitude threshold during the third period.
|
|
1402
|
+
Default 22.0, based on PS1 + Catalina.
|
|
1403
|
+
eff3 : `float`, opt
|
|
1404
|
+
The efficiency of observations during the third period. Default 0.1
|
|
1405
|
+
t_switch3 : `float`, opt
|
|
1406
|
+
The (MJD) time to switch between v_mag_thresh3 + eff3
|
|
1407
|
+
to v_mag_thresh4 + eff4.
|
|
1408
|
+
Default 59580 (2022).
|
|
1409
|
+
v_mag_thresh4 : `float`, opt
|
|
1410
|
+
The magnitude threshhold during the fourth (last) period.
|
|
1411
|
+
Default 22.0, based on PS1 + Catalina.
|
|
1412
|
+
eff4 : `float`, opt
|
|
1413
|
+
The efficiency of observations during the fourth (last) period.
|
|
1414
|
+
Default 0.2
|
|
1415
|
+
"""
|
|
1416
|
+
|
|
1417
|
+
def __init__(
|
|
1418
|
+
self,
|
|
1419
|
+
elong_thresh=100.0,
|
|
1420
|
+
v_mag_thresh1=20.0,
|
|
1421
|
+
eff1=0.1,
|
|
1422
|
+
t_switch1=53371,
|
|
1423
|
+
v_mag_thresh2=21.5,
|
|
1424
|
+
eff2=0.1,
|
|
1425
|
+
t_switch2=57023,
|
|
1426
|
+
v_mag_thresh3=22.0,
|
|
1427
|
+
eff3=0.1,
|
|
1428
|
+
t_switch3=59580,
|
|
1429
|
+
v_mag_thresh4=22.0,
|
|
1430
|
+
eff4=0.2,
|
|
1431
|
+
elong_col="Elongation",
|
|
1432
|
+
mjd_col="MJD(UTC)",
|
|
1433
|
+
**kwargs,
|
|
1434
|
+
):
|
|
1435
|
+
super().__init__(**kwargs)
|
|
1436
|
+
self.elong_thresh = elong_thresh
|
|
1437
|
+
self.elong_col = elong_col
|
|
1438
|
+
self.v_mag_thresh1 = v_mag_thresh1
|
|
1439
|
+
self.eff1 = eff1
|
|
1440
|
+
self.t_switch1 = t_switch1
|
|
1441
|
+
self.v_mag_thresh2 = v_mag_thresh2
|
|
1442
|
+
self.eff2 = eff2
|
|
1443
|
+
self.t_switch2 = t_switch2
|
|
1444
|
+
self.v_mag_thresh3 = v_mag_thresh3
|
|
1445
|
+
self.eff3 = eff3
|
|
1446
|
+
self.t_switch3 = t_switch3
|
|
1447
|
+
self.v_mag_thresh4 = v_mag_thresh4
|
|
1448
|
+
self.eff4 = eff4
|
|
1449
|
+
self.mjd_col = mjd_col
|
|
1450
|
+
self.badval = int(t_switch3) + 365 * 1000
|
|
1451
|
+
|
|
1452
|
+
def _pick_obs(self, potential_obs_times, eff):
|
|
1453
|
+
# From a set of potential observations, apply an efficiency
|
|
1454
|
+
# And return the minimum time (if any)
|
|
1455
|
+
rand_pick = np.random.rand(len(potential_obs_times))
|
|
1456
|
+
picked = np.where(rand_pick <= eff)[0]
|
|
1457
|
+
if len(picked) > 0:
|
|
1458
|
+
disc_time = potential_obs_times[picked].min()
|
|
1459
|
+
else:
|
|
1460
|
+
disc_time = None
|
|
1461
|
+
return disc_time
|
|
1462
|
+
|
|
1463
|
+
def run(self, sso_obs, orb, hval):
|
|
1464
|
+
visible = np.where(sso_obs[self.elong_col] >= self.elong_thresh, 1, 0)
|
|
1465
|
+
discovery_time = None
|
|
1466
|
+
# Look for discovery in any of the three periods.
|
|
1467
|
+
# First period.
|
|
1468
|
+
obs1 = np.where((sso_obs[self.mjd_col] < self.t_switch1) & visible)[0]
|
|
1469
|
+
over_peak = np.where(sso_obs[self.app_mag_v_col][obs1] <= self.v_mag_thresh1)[0]
|
|
1470
|
+
if len(over_peak) > 0:
|
|
1471
|
+
discovery_time = self._pick_obs(sso_obs[self.mjd_col][obs1][over_peak], self.eff1)
|
|
1472
|
+
# Second period.
|
|
1473
|
+
if discovery_time is None:
|
|
1474
|
+
obs2 = np.where(
|
|
1475
|
+
(sso_obs[self.mjd_col] >= self.t_switch1) & (sso_obs[self.mjd_col] < self.t_switch2) & visible
|
|
1476
|
+
)[0]
|
|
1477
|
+
over_peak = np.where(sso_obs[self.app_mag_v_col][obs2] <= self.v_mag_thresh2)[0]
|
|
1478
|
+
if len(over_peak) > 0:
|
|
1479
|
+
discovery_time = self._pick_obs(sso_obs[self.mjd_col][obs2][over_peak], self.eff2)
|
|
1480
|
+
# Third period.
|
|
1481
|
+
if discovery_time is None:
|
|
1482
|
+
obs3 = np.where(
|
|
1483
|
+
(sso_obs[self.mjd_col] >= self.t_switch2) & (sso_obs[self.mjd_col] < self.t_switch3) & visible
|
|
1484
|
+
)[0]
|
|
1485
|
+
over_peak = np.where(sso_obs[self.app_mag_v_col][obs3] <= self.v_mag_thresh3)[0]
|
|
1486
|
+
if len(over_peak) > 0:
|
|
1487
|
+
discovery_time = self._pick_obs(sso_obs[self.mjd_col][obs3][over_peak], self.eff3)
|
|
1488
|
+
# Fourth period.
|
|
1489
|
+
if discovery_time is None:
|
|
1490
|
+
obs4 = np.where((sso_obs[self.mjd_col] >= self.t_switch3) & visible)[0]
|
|
1491
|
+
over_peak = np.where(sso_obs[self.app_mag_v_col][obs4] <= self.v_mag_thresh4)[0]
|
|
1492
|
+
if len(over_peak) > 0:
|
|
1493
|
+
discovery_time = self._pick_obs(sso_obs[self.mjd_col][obs4][over_peak], self.eff4)
|
|
1494
|
+
if discovery_time is None:
|
|
1495
|
+
discovery_time = self.badval
|
|
1496
|
+
return discovery_time
|