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