diff --git a/doc/_autosummary/draco.analysis.beamform.rst b/doc/_autosummary/draco.analysis.beamform.rst new file mode 100644 index 000000000..7c20adc23 --- /dev/null +++ b/doc/_autosummary/draco.analysis.beamform.rst @@ -0,0 +1,43 @@ +draco.analysis.beamform +======================= + +.. automodule:: draco.analysis.beamform + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + icrs_to_cirs + + + + + + .. rubric:: Classes + + .. autosummary:: + + BeamForm + BeamFormBase + BeamFormCat + BeamFormExternal + BeamFormExternalBase + BeamFormExternalCat + HealpixBeamForm + RingMapBeamForm + RingMapStack2D + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.delay.rst b/doc/_autosummary/draco.analysis.delay.rst new file mode 100644 index 000000000..76d635d42 --- /dev/null +++ b/doc/_autosummary/draco.analysis.delay.rst @@ -0,0 +1,57 @@ +draco.analysis.delay +==================== + +.. automodule:: draco.analysis.delay + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + delay_power_spectrum_gibbs + delay_spectrum_gibbs + delay_spectrum_gibbs_cross + delay_spectrum_wiener_filter + flatten_axes + fourier_matrix + fourier_matrix_c2c + fourier_matrix_c2r + fourier_matrix_r2c + match_axes + null_delay_filter + stokes_I + + + + + + .. rubric:: Classes + + .. autosummary:: + + DelayCrossPowerSpectrumEstimator + DelayFilter + DelayFilterBase + DelayGeneralContainerBase + DelayGibbsSamplerBase + DelayPowerSpectrumGeneralEstimator + DelayPowerSpectrumStokesIEstimator + DelaySpectrumEstimator + DelaySpectrumEstimatorBase + DelaySpectrumWienerBase + DelaySpectrumWienerEstimator + DelayTransformBase + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.fgfilter.rst b/doc/_autosummary/draco.analysis.fgfilter.rst new file mode 100644 index 000000000..741421e7a --- /dev/null +++ b/doc/_autosummary/draco.analysis.fgfilter.rst @@ -0,0 +1,30 @@ +draco.analysis.fgfilter +======================= + +.. automodule:: draco.analysis.fgfilter + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + KLModeProject + SVDModeProject + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.flagging.rst b/doc/_autosummary/draco.analysis.flagging.rst new file mode 100644 index 000000000..b5ac56271 --- /dev/null +++ b/doc/_autosummary/draco.analysis.flagging.rst @@ -0,0 +1,62 @@ +draco.analysis.flagging +======================= + +.. automodule:: draco.analysis.flagging + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + complex_med + destripe + inverse_binom_cdf_prob + mad + medfilt + p_to_sigma + sigma_to_p + tv_channels_flag + + + + + + .. rubric:: Classes + + .. autosummary:: + + ApplyBaselineMask + ApplyRFIMask + ApplyTimeFreqMask + BlendStack + CollapseBaselineMask + DayMask + FindBeamformedOutliers + MaskBadGains + MaskBaselines + MaskBeamformedOutliers + MaskBeamformedWeights + MaskData + MaskFreq + MaskMModeData + RFIMask + RFISensitivityMask + RadiometerWeight + SanitizeWeights + SmoothVisWeight + ThresholdVisWeightBaseline + ThresholdVisWeightFrequency + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.mapmaker.rst b/doc/_autosummary/draco.analysis.mapmaker.rst new file mode 100644 index 000000000..6bf8b7565 --- /dev/null +++ b/doc/_autosummary/draco.analysis.mapmaker.rst @@ -0,0 +1,38 @@ +draco.analysis.mapmaker +======================= + +.. automodule:: draco.analysis.mapmaker + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + pinv_svd + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaseMapMaker + DirtyMapMaker + MaximumLikelihoodMapMaker + WienerMapMaker + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.powerspectrum.rst b/doc/_autosummary/draco.analysis.powerspectrum.rst new file mode 100644 index 000000000..e50d3f770 --- /dev/null +++ b/doc/_autosummary/draco.analysis.powerspectrum.rst @@ -0,0 +1,29 @@ +draco.analysis.powerspectrum +============================ + +.. automodule:: draco.analysis.powerspectrum + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + QuadraticPSEstimation + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.rst b/doc/_autosummary/draco.analysis.rst new file mode 100644 index 000000000..cc7419ba8 --- /dev/null +++ b/doc/_autosummary/draco.analysis.rst @@ -0,0 +1,23 @@ +draco.analysis +============== + +.. automodule:: draco.analysis + + + + + + + + + + + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.sensitivity.rst b/doc/_autosummary/draco.analysis.sensitivity.rst new file mode 100644 index 000000000..36eb8b492 --- /dev/null +++ b/doc/_autosummary/draco.analysis.sensitivity.rst @@ -0,0 +1,29 @@ +draco.analysis.sensitivity +========================== + +.. automodule:: draco.analysis.sensitivity + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + ComputeSystemSensitivity + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.sidereal.rst b/doc/_autosummary/draco.analysis.sidereal.rst new file mode 100644 index 000000000..5f7b8fab8 --- /dev/null +++ b/doc/_autosummary/draco.analysis.sidereal.rst @@ -0,0 +1,35 @@ +draco.analysis.sidereal +======================= + +.. automodule:: draco.analysis.sidereal + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + SiderealGrouper + SiderealRegridder + SiderealRegridderCubic + SiderealRegridderLinear + SiderealRegridderNearest + SiderealStacker + SiderealStackerMatch + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.sourcestack.rst b/doc/_autosummary/draco.analysis.sourcestack.rst new file mode 100644 index 000000000..c93bf3889 --- /dev/null +++ b/doc/_autosummary/draco.analysis.sourcestack.rst @@ -0,0 +1,31 @@ +draco.analysis.sourcestack +========================== + +.. automodule:: draco.analysis.sourcestack + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + GroupSourceStacks + RandomSubset + SourceStack + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.svdfilter.rst b/doc/_autosummary/draco.analysis.svdfilter.rst new file mode 100644 index 000000000..052e9e8c5 --- /dev/null +++ b/doc/_autosummary/draco.analysis.svdfilter.rst @@ -0,0 +1,36 @@ +draco.analysis.svdfilter +======================== + +.. automodule:: draco.analysis.svdfilter + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + svd_em + + + + + + .. rubric:: Classes + + .. autosummary:: + + SVDFilter + SVDSpectrumEstimator + + + + + + + + + diff --git a/doc/_autosummary/draco.analysis.transform.rst b/doc/_autosummary/draco.analysis.transform.rst new file mode 100644 index 000000000..35c549912 --- /dev/null +++ b/doc/_autosummary/draco.analysis.transform.rst @@ -0,0 +1,43 @@ +draco.analysis.transform +======================== + +.. automodule:: draco.analysis.transform + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + CollateProducts + Downselect + FrequencyRebin + HPFTimeStream + MModeInverseTransform + MModeTransform + MixData + ReduceBase + ReduceVar + Regridder + SelectFreq + SelectPol + ShiftRA + SiderealMModeResample + TransformJanskyToKelvin + + + + + + + + + diff --git a/doc/_autosummary/draco.core.containers.rst b/doc/_autosummary/draco.core.containers.rst new file mode 100644 index 000000000..5538547b7 --- /dev/null +++ b/doc/_autosummary/draco.core.containers.rst @@ -0,0 +1,91 @@ +draco.core.containers +===================== + +.. automodule:: draco.core.containers + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + copy_datasets_filter + empty_like + empty_timestream + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaselineMask + CommonModeGainData + CommonModeSiderealGainData + ContainerBase + DataWeightContainer + DelayContainer + DelayCrossSpectrum + DelayCutoff + DelaySpectrum + DelayTransform + FormedBeam + FormedBeamHA + FormedBeamHAMask + FormedBeamMask + FreqContainer + FrequencyStack + FrequencyStackByPol + GainData + GainDataBase + GridBeam + HEALPixBeam + HealpixContainer + HybridVisMModes + HybridVisStream + KLModes + MContainer + MModes + Map + MockFrequencyStack + MockFrequencyStackByPol + Powerspectrum2D + RFIMask + RingMap + RingMapMask + SVDModes + SVDSpectrum + SampleVarianceContainer + SiderealBaselineMask + SiderealContainer + SiderealGainData + SiderealRFIMask + SiderealStream + SourceCatalog + SpectroscopicCatalog + Stack3D + StaticGainData + SystemSensitivity + TODContainer + TableBase + TimeStream + TrackBeam + VisBase + VisContainer + VisGridStream + WaveletSpectrum + + + + + + + + + diff --git a/doc/_autosummary/draco.core.io.rst b/doc/_autosummary/draco.core.io.rst new file mode 100644 index 000000000..298b1626a --- /dev/null +++ b/doc/_autosummary/draco.core.io.rst @@ -0,0 +1,54 @@ +draco.core.io +============= + +.. automodule:: draco.core.io + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_beamtransfer + get_telescope + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaseLoadFiles + FindFiles + LoadBasicCont + LoadBeamTransfer + LoadFITSCatalog + LoadFiles + LoadFilesFromParams + LoadMaps + LoadProductManager + Print + Save + SaveConfig + SaveModuleVersions + SaveZarrZip + SelectionsMixin + Truncate + WaitZarrZip + ZarrZipHandle + ZipZarrContainers + + + + + + + + + diff --git a/doc/_autosummary/draco.core.misc.rst b/doc/_autosummary/draco.core.misc.rst new file mode 100644 index 000000000..cafaef194 --- /dev/null +++ b/doc/_autosummary/draco.core.misc.rst @@ -0,0 +1,34 @@ +draco.core.misc +=============== + +.. automodule:: draco.core.misc + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + AccumulateList + ApplyGain + CheckMPIEnvironment + MakeCopy + PassOn + WaitUntil + + + + + + + + + diff --git a/doc/_autosummary/draco.core.rst b/doc/_autosummary/draco.core.rst new file mode 100644 index 000000000..ebf85019d --- /dev/null +++ b/doc/_autosummary/draco.core.rst @@ -0,0 +1,23 @@ +draco.core +========== + +.. automodule:: draco.core + + + + + + + + + + + + + + + + + + + diff --git a/doc/_autosummary/draco.core.task.rst b/doc/_autosummary/draco.core.task.rst new file mode 100644 index 000000000..8717307e7 --- /dev/null +++ b/doc/_autosummary/draco.core.task.rst @@ -0,0 +1,43 @@ +draco.core.task +=============== + +.. automodule:: draco.core.task + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + group_tasks + + + + + + .. rubric:: Classes + + .. autosummary:: + + Delete + LoggedTask + MPILogFilter + MPILoggedTask + MPITask + ReturnFirstInputOnFinish + ReturnLastInputOnFinish + SetMPILogging + SingleTask + + + + + + + + + diff --git a/doc/_autosummary/draco.synthesis.gain.rst b/doc/_autosummary/draco.synthesis.gain.rst new file mode 100644 index 000000000..bbf0ac0ee --- /dev/null +++ b/doc/_autosummary/draco.synthesis.gain.rst @@ -0,0 +1,41 @@ +draco.synthesis.gain +==================== + +.. automodule:: draco.synthesis.gain + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + constrained_gaussian_realisation + gaussian_realisation + generate_fluctuations + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaseGains + GainStacker + RandomGains + RandomSiderealGains + SiderealGains + + + + + + + + + diff --git a/doc/_autosummary/draco.synthesis.noise.rst b/doc/_autosummary/draco.synthesis.noise.rst new file mode 100644 index 000000000..195c3810f --- /dev/null +++ b/doc/_autosummary/draco.synthesis.noise.rst @@ -0,0 +1,32 @@ +draco.synthesis.noise +===================== + +.. automodule:: draco.synthesis.noise + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + GaussianNoise + GaussianNoiseDataset + ReceiverTemperature + SampleNoise + + + + + + + + + diff --git a/doc/_autosummary/draco.synthesis.rst b/doc/_autosummary/draco.synthesis.rst new file mode 100644 index 000000000..c6b8dc2b6 --- /dev/null +++ b/doc/_autosummary/draco.synthesis.rst @@ -0,0 +1,23 @@ +draco.synthesis +=============== + +.. automodule:: draco.synthesis + + + + + + + + + + + + + + + + + + + diff --git a/doc/_autosummary/draco.synthesis.stream.rst b/doc/_autosummary/draco.synthesis.stream.rst new file mode 100644 index 000000000..ab16dd0a3 --- /dev/null +++ b/doc/_autosummary/draco.synthesis.stream.rst @@ -0,0 +1,32 @@ +draco.synthesis.stream +====================== + +.. automodule:: draco.synthesis.stream + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + ExpandProducts + MakeSiderealDayStream + MakeTimeStream + SimulateSidereal + + + + + + + + + diff --git a/doc/_autosummary/draco.util.exception.rst b/doc/_autosummary/draco.util.exception.rst new file mode 100644 index 000000000..68c0a1e2c --- /dev/null +++ b/doc/_autosummary/draco.util.exception.rst @@ -0,0 +1,29 @@ +draco.util.exception +==================== + +.. automodule:: draco.util.exception + + + + + + + + + + + + + + + + .. rubric:: Exceptions + + .. autosummary:: + + ConfigError + + + + + diff --git a/doc/_autosummary/draco.util.random.rst b/doc/_autosummary/draco.util.random.rst new file mode 100644 index 000000000..ba95d0cfb --- /dev/null +++ b/doc/_autosummary/draco.util.random.rst @@ -0,0 +1,41 @@ +draco.util.random +================= + +.. automodule:: draco.util.random + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + complex_normal + complex_wishart + default_rng + mpi_random_seed + standard_complex_normal + standard_complex_wishart + + + + + + .. rubric:: Classes + + .. autosummary:: + + MultithreadedRNG + RandomTask + + + + + + + + + diff --git a/doc/_autosummary/draco.util.regrid.rst b/doc/_autosummary/draco.util.regrid.rst new file mode 100644 index 000000000..657fa7859 --- /dev/null +++ b/doc/_autosummary/draco.util.regrid.rst @@ -0,0 +1,32 @@ +draco.util.regrid +================= + +.. automodule:: draco.util.regrid + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + band_wiener + lanczos_forward_matrix + lanczos_inverse_matrix + lanczos_kernel + + + + + + + + + + + + + diff --git a/doc/_autosummary/draco.util.rfi.rst b/doc/_autosummary/draco.util.rfi.rst new file mode 100644 index 000000000..bbab95a48 --- /dev/null +++ b/doc/_autosummary/draco.util.rfi.rst @@ -0,0 +1,32 @@ +draco.util.rfi +============== + +.. automodule:: draco.util.rfi + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + sir + sir1d + sumthreshold + sumthreshold_py + + + + + + + + + + + + + diff --git a/doc/_autosummary/draco.util.rst b/doc/_autosummary/draco.util.rst new file mode 100644 index 000000000..25892cf49 --- /dev/null +++ b/doc/_autosummary/draco.util.rst @@ -0,0 +1,23 @@ +draco.util +========== + +.. automodule:: draco.util + + + + + + + + + + + + + + + + + + + diff --git a/doc/_autosummary/draco.util.testing.rst b/doc/_autosummary/draco.util.testing.rst new file mode 100644 index 000000000..bf1f03027 --- /dev/null +++ b/doc/_autosummary/draco.util.testing.rst @@ -0,0 +1,36 @@ +draco.util.testing +================== + +.. automodule:: draco.util.testing + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + mock_freq_data + + + + + + .. rubric:: Classes + + .. autosummary:: + + DummyTask + RandomFreqData + + + + + + + + + diff --git a/doc/_autosummary/draco.util.tools.rst b/doc/_autosummary/draco.util.tools.rst new file mode 100644 index 000000000..0ad7cdce8 --- /dev/null +++ b/doc/_autosummary/draco.util.tools.rst @@ -0,0 +1,40 @@ +draco.util.tools +================ + +.. automodule:: draco.util.tools + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + apply_gain + baseline_vector + calculate_redundancy + cmap + extract_diagonal + find_inputs + find_key + find_keys + icmap + polarization_map + redefine_stack_index_map + window_generalised + + + + + + + + + + + + + diff --git a/doc/_autosummary/draco.util.truncate.rst b/doc/_autosummary/draco.util.truncate.rst new file mode 100644 index 000000000..446a4ffbe --- /dev/null +++ b/doc/_autosummary/draco.util.truncate.rst @@ -0,0 +1,31 @@ +draco.util.truncate +=================== + +.. automodule:: draco.util.truncate + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + bit_truncate + bit_truncate_fixed + bit_truncate_weights + + + + + + + + + + + + + diff --git a/docs/.buildinfo b/docs/.buildinfo new file mode 100644 index 000000000..0af5ee3ac --- /dev/null +++ b/docs/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 89175e471918c541c1d40d1ce20de310 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/.doctrees/_autosummary/draco.analysis.beamform.doctree b/docs/.doctrees/_autosummary/draco.analysis.beamform.doctree new file mode 100644 index 000000000..44623b7db Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.beamform.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.delay.doctree b/docs/.doctrees/_autosummary/draco.analysis.delay.doctree new file mode 100644 index 000000000..b6b865c72 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.delay.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.doctree b/docs/.doctrees/_autosummary/draco.analysis.doctree new file mode 100644 index 000000000..d76937de2 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.fgfilter.doctree b/docs/.doctrees/_autosummary/draco.analysis.fgfilter.doctree new file mode 100644 index 000000000..9dd2ed665 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.fgfilter.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.flagging.doctree b/docs/.doctrees/_autosummary/draco.analysis.flagging.doctree new file mode 100644 index 000000000..0c79e58e7 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.flagging.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.mapmaker.doctree b/docs/.doctrees/_autosummary/draco.analysis.mapmaker.doctree new file mode 100644 index 000000000..d01371f8c Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.mapmaker.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.powerspectrum.doctree b/docs/.doctrees/_autosummary/draco.analysis.powerspectrum.doctree new file mode 100644 index 000000000..3449bf867 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.powerspectrum.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.sensitivity.doctree b/docs/.doctrees/_autosummary/draco.analysis.sensitivity.doctree new file mode 100644 index 000000000..c4b22977e Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.sensitivity.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.sidereal.doctree b/docs/.doctrees/_autosummary/draco.analysis.sidereal.doctree new file mode 100644 index 000000000..8b22acace Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.sidereal.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.sourcestack.doctree b/docs/.doctrees/_autosummary/draco.analysis.sourcestack.doctree new file mode 100644 index 000000000..0ced05616 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.sourcestack.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.svdfilter.doctree b/docs/.doctrees/_autosummary/draco.analysis.svdfilter.doctree new file mode 100644 index 000000000..039ad0f22 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.svdfilter.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.analysis.transform.doctree b/docs/.doctrees/_autosummary/draco.analysis.transform.doctree new file mode 100644 index 000000000..4164be98f Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.analysis.transform.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.core.containers.doctree b/docs/.doctrees/_autosummary/draco.core.containers.doctree new file mode 100644 index 000000000..54b0c757d Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.core.containers.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.core.doctree b/docs/.doctrees/_autosummary/draco.core.doctree new file mode 100644 index 000000000..9e413c5a6 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.core.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.core.io.doctree b/docs/.doctrees/_autosummary/draco.core.io.doctree new file mode 100644 index 000000000..e0b107b5f Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.core.io.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.core.misc.doctree b/docs/.doctrees/_autosummary/draco.core.misc.doctree new file mode 100644 index 000000000..d2c9e0205 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.core.misc.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.core.task.doctree b/docs/.doctrees/_autosummary/draco.core.task.doctree new file mode 100644 index 000000000..a34f02102 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.core.task.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.synthesis.doctree b/docs/.doctrees/_autosummary/draco.synthesis.doctree new file mode 100644 index 000000000..905ad4b8b Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.synthesis.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.synthesis.gain.doctree b/docs/.doctrees/_autosummary/draco.synthesis.gain.doctree new file mode 100644 index 000000000..e5cede7cc Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.synthesis.gain.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.synthesis.noise.doctree b/docs/.doctrees/_autosummary/draco.synthesis.noise.doctree new file mode 100644 index 000000000..8ee84a15e Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.synthesis.noise.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.synthesis.stream.doctree b/docs/.doctrees/_autosummary/draco.synthesis.stream.doctree new file mode 100644 index 000000000..a4c380e65 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.synthesis.stream.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.util.doctree b/docs/.doctrees/_autosummary/draco.util.doctree new file mode 100644 index 000000000..c6455beb1 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.util.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.util.exception.doctree b/docs/.doctrees/_autosummary/draco.util.exception.doctree new file mode 100644 index 000000000..c07c8b0ee Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.util.exception.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.util.random.doctree b/docs/.doctrees/_autosummary/draco.util.random.doctree new file mode 100644 index 000000000..3cc02da90 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.util.random.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.util.regrid.doctree b/docs/.doctrees/_autosummary/draco.util.regrid.doctree new file mode 100644 index 000000000..d2f3adaf9 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.util.regrid.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.util.rfi.doctree b/docs/.doctrees/_autosummary/draco.util.rfi.doctree new file mode 100644 index 000000000..6fbdbfc8e Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.util.rfi.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.util.testing.doctree b/docs/.doctrees/_autosummary/draco.util.testing.doctree new file mode 100644 index 000000000..e46da54e8 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.util.testing.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.util.tools.doctree b/docs/.doctrees/_autosummary/draco.util.tools.doctree new file mode 100644 index 000000000..0c8f7faa9 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.util.tools.doctree differ diff --git a/docs/.doctrees/_autosummary/draco.util.truncate.doctree b/docs/.doctrees/_autosummary/draco.util.truncate.doctree new file mode 100644 index 000000000..6c033e0e5 Binary files /dev/null and b/docs/.doctrees/_autosummary/draco.util.truncate.doctree differ diff --git a/docs/.doctrees/dev.doctree b/docs/.doctrees/dev.doctree new file mode 100644 index 000000000..71cc6f58d Binary files /dev/null and b/docs/.doctrees/dev.doctree differ diff --git a/docs/.doctrees/environment.pickle b/docs/.doctrees/environment.pickle new file mode 100644 index 000000000..b80cc1565 Binary files /dev/null and b/docs/.doctrees/environment.pickle differ diff --git a/docs/.doctrees/index.doctree b/docs/.doctrees/index.doctree new file mode 100644 index 000000000..f45c604d3 Binary files /dev/null and b/docs/.doctrees/index.doctree differ diff --git a/docs/.doctrees/reference.doctree b/docs/.doctrees/reference.doctree new file mode 100644 index 000000000..ac29d391d Binary files /dev/null and b/docs/.doctrees/reference.doctree differ diff --git a/docs/.doctrees/tutorial.doctree b/docs/.doctrees/tutorial.doctree new file mode 100644 index 000000000..0a8fa4b8e Binary files /dev/null and b/docs/.doctrees/tutorial.doctree differ diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/docs/_autosummary/draco.analysis.beamform.html b/docs/_autosummary/draco.analysis.beamform.html new file mode 100644 index 000000000..935337b6d --- /dev/null +++ b/docs/_autosummary/draco.analysis.beamform.html @@ -0,0 +1,636 @@ + + + + + + + draco.analysis.beamform — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.beamform

+

Beamform visibilities to the location of known sources.

+

Functions

+ + + + + + +

icrs_to_cirs(ra, dec, epoch[, apparent])

Convert a set of positions from ICRS to CIRS at a given data.

+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

BeamForm()

BeamForm for a single source catalog and multiple visibility datasets.

BeamFormBase()

Base class for beam forming tasks.

BeamFormCat()

BeamForm for multiple source catalogs and a single visibility dataset.

BeamFormExternal()

Beamform a single catalog and multiple datasets using an external beam model.

BeamFormExternalBase()

Base class for tasks that beamform using an external model of the primary beam.

BeamFormExternalCat()

Beamform multiple catalogs and a single dataset using an external beam model.

HealpixBeamForm()

Beamform by extracting the pixel containing each source form a Healpix map.

RingMapBeamForm()

Beamform by extracting the pixel containing each source form a RingMap.

RingMapStack2D()

Stack RingMap's on sources directly.

+
+
+class draco.analysis.beamform.BeamForm[source]
+

Bases: BeamFormBase

+

BeamForm for a single source catalog and multiple visibility datasets.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Parse the visibility data and beamforms all sources.

+
+
Parameters:
+

data (containers.SiderealStream or containers.TimeStream) – Data to beamform on.

+
+
Returns:
+

formed_beam – Formed beams at each source.

+
+
Return type:
+

containers.FormedBeam or containers.FormedBeamHA

+
+
+
+ +
+
+setup(manager, source_cat)[source]
+

Parse the source catalog and performs the generic setup.

+
+
Parameters:
+
    +
  • manager (either ProductManager, BeamTransfer or TransitTelescope) – Contains a TransitTelescope object describing the telescope.

  • +
  • source_cat (containers.SourceCatalog) – Catalog of points to beamform at.

  • +
+
+
+
+ +
+ +
+
+class draco.analysis.beamform.BeamFormBase[source]
+

Bases: SingleTask

+

Base class for beam forming tasks.

+

Defines a few useful methods. Not to be used directly +but as parent class for BeamForm and BeamFormCat.

+
+
+collapse_ha
+

Sum over hour-angle/time to complete the beamforming. Default is True.

+
+
Type:
+

bool

+
+
+
+ +
+
+polarization
+
+
Determines the polarizations that will be output:
    +
  • ‘I’ : Stokes I only.

  • +
  • ‘full’ : ‘XX’, ‘XY’, ‘YX’ and ‘YY’ in this order. (default)

  • +
  • ‘copol’ : ‘XX’ and ‘YY’ only.

  • +
  • ‘stokes’ : ‘I’, ‘Q’, ‘U’ and ‘V’ in this order. Not implemented.

  • +
+
+
+
+
Type:
+

string

+
+
+
+ +
+
+weight
+
+
How to weight the redundant baselines when adding:
    +
  • ‘natural’ : each baseline weighted by its redundancy (default)

  • +
  • ‘uniform’ : each baseline given equal weight

  • +
  • ‘inverse_variance’ : each baseline weighted by the weight attribute

  • +
+
+
+
+
Type:
+

string

+
+
+
+ +
+
+no_beam_model
+

Do not include a primary beam factor in the beamforming +weights, i.e., use uniform weighting as a function of hour angle +and declination.

+
+
Type:
+

string

+
+
+
+ +
+
+timetrack
+

How long (in seconds) to track sources at each side of transit. +Default is 900 seconds. Total transit time will be 2 * timetrack.

+
+
Type:
+

float

+
+
+
+ +
+
+variable_timetrack
+

Scale the total time to track each source by the secant of the +source declination, so that all sources are tracked through +the same angle on the sky. Default is False.

+
+
Type:
+

bool

+
+
+
+ +
+
+freqside
+

Number of frequencies to process at each side of the source. +Default (None) processes all frequencies.

+
+
Type:
+

int

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Generic process method.

+

Performs all the beamforming, but not the data parsing. +To be complemented by specific process methods in daughter tasks.

+
+
Returns:
+

formed_beam – Formed beams at each source. Shape depends on parameter +collapse_ha.

+
+
Return type:
+

containers.FormedBeam or containers.FormedBeamHA

+
+
+
+ +
+
+process_finish()[source]
+

Clear lists holding copies of data.

+

These lists will persist beyond this task being done, so +the data stored there will continue to use memory.

+
+ +
+
+setup(manager)[source]
+

Generic setup method.

+

To be complemented by specific setup methods in daughter tasks.

+
+
Parameters:
+

manager (either ProductManager, BeamTransfer or TransitTelescope) – Contains a TransitTelescope object describing the telescope.

+
+
+
+ +
+ +
+
+class draco.analysis.beamform.BeamFormCat[source]
+

Bases: BeamFormBase

+

BeamForm for multiple source catalogs and a single visibility dataset.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(source_cat)[source]
+

Parse the source catalog and beamforms all sources.

+
+
Parameters:
+

source_cat (containers.SourceCatalog) – Catalog of points to beamform at.

+
+
Returns:
+

formed_beam – Formed beams at each source.

+
+
Return type:
+

containers.FormedBeam or containers.FormedBeamHA

+
+
+
+ +
+
+setup(manager, data)[source]
+

Parse the visibility data and performs the generic setup.

+
+
Parameters:
+
    +
  • manager (either ProductManager, BeamTransfer or TransitTelescope) – Contains a TransitTelescope object describing the telescope.

  • +
  • data (containers.SiderealStream or containers.TimeStream) – Data to beamform on.

  • +
+
+
+
+ +
+ +
+
+class draco.analysis.beamform.BeamFormExternal[source]
+

Bases: BeamFormExternalBase, BeamForm

+

Beamform a single catalog and multiple datasets using an external beam model.

+

The setup method requires [beam, manager, source_cat] as arguments.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.beamform.BeamFormExternalBase[source]
+

Bases: BeamFormBase

+

Base class for tasks that beamform using an external model of the primary beam.

+

The primary beam is provided to the task during setup. Do not use this class +directly, instead use BeamFormExternal and BeamFormExternalCat.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup(beam, *args)[source]
+

Initialize the beam.

+
+
Parameters:
+
    +
  • beam (GridBeam) – Model for the primary beam.

  • +
  • args (optional) – Additional argument to pass to the super class

  • +
+
+
+
+ +
+ +
+
+class draco.analysis.beamform.BeamFormExternalCat[source]
+

Bases: BeamFormExternalBase, BeamFormCat

+

Beamform multiple catalogs and a single dataset using an external beam model.

+

The setup method requires [beam, manager, data] as arguments.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.beamform.HealpixBeamForm[source]
+

Bases: SingleTask

+

Beamform by extracting the pixel containing each source form a Healpix map.

+

Unless it has an explicit epoch attribute, the Healpix map is assumed to be in the +same coordinate epoch as the catalog. If it does, the input catalog is assumed to be +in ICRS and then is precessed to the CIRS coordinates in the epoch of the map.

+
+
+fwhm
+

Smooth the map with a Gaussian with the specified FWHM in degrees. If None +(default), leave at native map resolution. This will modify the input map in +place.

+
+
Type:
+

float

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(catalog: SourceCatalog) FormedBeam[source]
+

Extract sources from a ringmap.

+
+
Parameters:
+

catalog – The catalog to extract sources from.

+
+
Returns:
+

The source spectra.

+
+
Return type:
+

formed_beam

+
+
+
+ +
+
+setup(hpmap: Map)[source]
+

Set the map to extract beams from at each catalog location.

+
+
Parameters:
+

hpmap – The Healpix map to extract the sources from.

+
+
+
+ +
+ +
+
+class draco.analysis.beamform.RingMapBeamForm[source]
+

Bases: SingleTask

+

Beamform by extracting the pixel containing each source form a RingMap.

+

This is significantly faster than Beamform or BeamformCat with the caveat +that they can beamform exactly on a source whereas this task is at the mercy of +what was done to produce the RingMap (use DeconvolveHybridM for best +results).

+

Unless it has an explicit lsd attribute, the ring map is assumed to be in the +same coordinate epoch as the catalog. If it does, the input catalog is assumed to be +in ICRS and then is precessed to the CIRS coordinates in the epoch of the map.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(catalog: SourceCatalog) FormedBeam[source]
+

Extract sources from a ringmap.

+
+
Parameters:
+

catalog – The catalog to extract sources from.

+
+
Returns:
+

The source spectra.

+
+
Return type:
+

sources

+
+
+
+ +
+
+setup(telescope: ProductManager | BeamTransfer | TransitTelescope, ringmap: RingMap)[source]
+

Set the telescope object.

+
+
Parameters:
+
    +
  • telescope – The telescope object to use.

  • +
  • ringmap – The ringmap to extract the sources from. See the class documentation for how +the epoch is determined.

  • +
+
+
+
+ +
+ +
+
+class draco.analysis.beamform.RingMapStack2D[source]
+

Bases: RingMapBeamForm

+

Stack RingMap’s on sources directly.

+
+
Parameters:
+
    +
  • num_ra (int) – The number of RA and DEC pixels to stack either side of the source.

  • +
  • num_dec (int) – The number of RA and DEC pixels to stack either side of the source.

  • +
  • num_freq (int) – Number of final frequency channels either side of the source redshift to +stack.

  • +
  • freq_width (float) – Length of frequency interval either side of source to use in MHz.

  • +
  • weight ({"patch", "dec", "enum"}) – How to weight the data. If “input” the data is weighted on a pixel by pixel +basis according to the input data. If “patch” then the inverse of the +variance of the extracted patch is used. If “dec” then the inverse variance +of each declination strip is used.

  • +
+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(catalog: SourceCatalog) FormedBeam[source]
+

Extract sources from a ringmap.

+
+
Parameters:
+

catalog – The catalog to extract sources from.

+
+
Returns:
+

The source spectra.

+
+
Return type:
+

sources

+
+
+
+ +
+ +
+
+draco.analysis.beamform.icrs_to_cirs(ra, dec, epoch, apparent=True)[source]
+

Convert a set of positions from ICRS to CIRS at a given data.

+
+
Parameters:
+
    +
  • ra (float or np.ndarray) – Positions of source in ICRS coordinates including an optional +redshift position.

  • +
  • dec (float or np.ndarray) – Positions of source in ICRS coordinates including an optional +redshift position.

  • +
  • epoch (time_like) – Time to convert the positions to. Can be any type convertible to a +time using caput.time.ensure_unix.

  • +
  • apparent (bool) – Calculate the apparent position (includes abberation and deflection).

  • +
+
+
Returns:
+

ra_cirs, dec_cirs – Arrays of the positions in CIRS coordiantes.

+
+
Return type:
+

float or np.ndarray

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.delay.html b/docs/_autosummary/draco.analysis.delay.html new file mode 100644 index 000000000..66d139fcd --- /dev/null +++ b/docs/_autosummary/draco.analysis.delay.html @@ -0,0 +1,1073 @@ + + + + + + + draco.analysis.delay — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.delay

+

Delay space spectrum estimation and filtering.

+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

delay_power_spectrum_gibbs(data, N, Ni, ...)

Estimate the delay power spectrum by Gibbs sampling.

delay_spectrum_gibbs(data, N, Ni, initial_S)

Estimate the delay power spectrum by Gibbs sampling.

delay_spectrum_gibbs_cross(data, N, Ni, ...)

Estimate the delay power spectrum by Gibbs sampling.

delay_spectrum_wiener_filter(delay_PS, data, ...)

Estimate the delay spectrum from an input frequency spectrum by Wiener filtering.

flatten_axes(dset, axes_to_keep[, match_dset])

Move the specified axes of the dataset to the back, and flatten all others.

fourier_matrix(N[, fsel])

Generate a Fourier matrix to represent a real to complex FFT.

fourier_matrix_c2c(N[, fsel])

Generate a Fourier matrix to represent a complex to complex FFT.

fourier_matrix_c2r(N[, fsel])

Generate a Fourier matrix to represent a complex to real FFT.

fourier_matrix_r2c(N[, fsel])

Generate a Fourier matrix to represent a real to complex FFT.

match_axes(dset1, dset2)

Make sure that dset2 has the same set of axes as dset1.

null_delay_filter(freq, max_delay, mask[, ...])

Take frequency data and null out any delays below some value.

stokes_I(sstream, tel)

Extract instrumental Stokes I from a time/sidereal stream.

+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

DelayCrossPowerSpectrumEstimator()

A delay cross power spectrum estimator.

DelayFilter()

Remove delays less than a given threshold.

DelayFilterBase()

Remove delays less than a given threshold.

DelayGeneralContainerBase()

Base class for freq->delay transforms that collapse over several dataset axes.

DelayGibbsSamplerBase()

Base class for delay power spectrum estimation via Gibbs sampling (non-functional).

DelayPowerSpectrumGeneralEstimator()

Class to measure delay power spectrum of general container via Gibbs sampling.

DelayPowerSpectrumStokesIEstimator()

Class to measure delay power spectrum of Stokes-I visibilities via Gibbs sampling.

DelaySpectrumEstimator

alias of DelayPowerSpectrumStokesIEstimator

DelaySpectrumEstimatorBase

alias of DelayPowerSpectrumGeneralEstimator

DelaySpectrumWienerBase

alias of DelaySpectrumWienerEstimator

DelaySpectrumWienerEstimator()

Class to measure delay spectrum of general container via Wiener filtering.

DelayTransformBase()

Base class for transforming from frequency to delay (non-functional).

+
+
+class draco.analysis.delay.DelayCrossPowerSpectrumEstimator[source]
+

Bases: DelayGeneralContainerBase, DelayGibbsSamplerBase

+

A delay cross power spectrum estimator.

+

This takes multiple compatible FreqContainer`s as inputs and will return a +`DelayCrossSpectrum container with the full pair-wise cross power spectrum.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.delay.DelayFilter[source]
+

Bases: SingleTask

+

Remove delays less than a given threshold.

+

This is performed by projecting the data onto the null space that is orthogonal +to any mode at low delays.

+

Note that for this task to work best the zero entries in the weights dataset +should factorize in frequency-time for each baseline. A mostly optimal masking +can be generated using the draco.analysis.flagging.MaskFreq task.

+
+
+delay_cut
+

Delay value to filter at in seconds.

+
+
Type:
+

float

+
+
+
+ +
+
+za_cut
+

Sine of the maximum zenith angle included in baseline-dependent delay +filtering. Default is 1 which corresponds to the horizon (ie: filters out all +zenith angles). Setting to zero turns off baseline dependent cut.

+
+
Type:
+

float

+
+
+
+ +
+
+extra_cut
+

Increase the delay threshold beyond the baseline dependent term.

+
+
Type:
+

float

+
+
+
+ +
+
+weight_tol
+

Maximum weight kept in the masked data, as a fraction of the largest weight +in the original dataset.

+
+
Type:
+

float

+
+
+
+ +
+
+telescope_orientation
+

Determines if the baseline-dependent delay cut is based on the north-south +component, the east-west component or the full baseline length. For +cylindrical telescopes oriented in the NS direction (like CHIME) use ‘NS’. +The default is ‘NS’.

+
+
Type:
+

one of (‘NS’, ‘EW’, ‘none’)

+
+
+
+ +
+
+window
+

Apply the window function to the data when applying the filter.

+
+
Type:
+

bool

+
+
+
+ +

Notes

+

The delay cut applied is max(za_cut * baseline / c + extra_cut, delay_cut).

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(ss)[source]
+

Filter out delays from a SiderealStream or TimeStream.

+
+
Parameters:
+

ss (containers.SiderealStream) – Data to filter.

+
+
Returns:
+

ss_filt – Filtered dataset.

+
+
Return type:
+

containers.SiderealStream

+
+
+
+ +
+
+setup(telescope)[source]
+

Set the telescope needed to obtain baselines.

+
+
Parameters:
+

telescope (TransitTelescope) – The telescope object to use

+
+
+
+ +
+ +
+
+class draco.analysis.delay.DelayFilterBase[source]
+

Bases: SingleTask

+

Remove delays less than a given threshold.

+

This is performed by projecting the data onto the null space that is orthogonal +to any mode at low delays.

+

Note that for this task to work best the zero entries in the weights dataset +should factorize in frequency-time for each baseline. A mostly optimal masking +can be generated using the draco.analysis.flagging.MaskFreq task.

+
+
+delay_cut
+

Delay value to filter at in seconds.

+
+
Type:
+

float

+
+
+
+ +
+
+window
+

Apply the window function to the data when applying the filter.

+
+
Type:
+

bool

+
+
+
+ +
+
+axis
+

The main axis to iterate over. The delay cut can be varied for each element +of this axis. If not set, a suitable default is picked for the container +type.

+
+
Type:
+

str

+
+
+
+ +
+
+dataset
+

Apply the delay filter to this dataset. If not set, a suitable default +is picked for the container type.

+
+
Type:
+

str

+
+
+
+ +

Notes

+

The delay cut applied is max(za_cut * baseline / c + extra_cut, delay_cut).

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(ss: FreqContainerType) FreqContainerType[source]
+

Filter out delays from a SiderealStream or TimeStream.

+
+
Parameters:
+

ss – Data to filter.

+
+
Returns:
+

Filtered dataset.

+
+
Return type:
+

ss_filt

+
+
+
+ +
+
+setup(telescope: ProductManager | BeamTransfer | TransitTelescope)[source]
+

Set the telescope needed to obtain baselines.

+
+
Parameters:
+

telescope – The telescope object to use

+
+
+
+ +
+ +
+
+class draco.analysis.delay.DelayGeneralContainerBase[source]
+

Bases: DelayTransformBase

+

Base class for freq->delay transforms that collapse over several dataset axes.

+

The delay spectrum or power spectrum output is indexed by a baseline axis. This +axis is the composite axis of all the axes in the container except the frequency +axis or the average_axis. These constituent axes are included in the index map, +and their order is given by the baseline_axes attribute.

+
+
+dataset
+

Calculate the delay spectrum of this dataset (e.g., “vis”, “map”, “beam”). If +not set, assume the input is a DataWeightContainer and use the main data +dataset.

+
+
Type:
+

str, optional

+
+
+
+ +
+
+average_axis
+

Name of the axis to take the average over.

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.delay.DelayGibbsSamplerBase[source]
+

Bases: DelayTransformBase, RandomTask

+

Base class for delay power spectrum estimation via Gibbs sampling (non-functional).

+

The spectrum is calculated by Gibbs sampling. The spectrum returned is the median +of the final half of the samples calculated.

+
+
+nsamp
+

The number of Gibbs samples to draw.

+
+
Type:
+

int, optional

+
+
+
+ +
+
+initial_amplitude
+

The Gibbs sampler will be initialized with a flat power spectrum with +this amplitude. Default: 10.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+save_samples
+

The entire chain of samples will be saved rather than just the final +result. Default: False

+
+
Type:
+

bool, optional.

+
+
+
+ +
+
+initial_sample_path
+

File path to load an initial power spectrum sample. If no file is given, +start with a flat power spectrum. Default: None

+
+
Type:
+

str, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.delay.DelayPowerSpectrumGeneralEstimator[source]
+

Bases: DelayGibbsSamplerBase, DelayGeneralContainerBase

+

Class to measure delay power spectrum of general container via Gibbs sampling.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.delay.DelayPowerSpectrumStokesIEstimator[source]
+

Bases: DelayGibbsSamplerBase

+

Class to measure delay power spectrum of Stokes-I visibilities via Gibbs sampling.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup(telescope)[source]
+

Set the telescope needed to generate Stokes I.

+
+
Parameters:
+

telescope (TransitTelescope) – Telescope object we’ll use for baseline and polarization information.

+
+
+
+ +
+ +
+
+draco.analysis.delay.DelaySpectrumEstimator
+

alias of DelayPowerSpectrumStokesIEstimator

+
+ +
+
+draco.analysis.delay.DelaySpectrumEstimatorBase
+

alias of DelayPowerSpectrumGeneralEstimator

+
+ +
+
+draco.analysis.delay.DelaySpectrumWienerBase
+

alias of DelaySpectrumWienerEstimator

+
+ +
+
+class draco.analysis.delay.DelaySpectrumWienerEstimator[source]
+

Bases: DelayGeneralContainerBase

+

Class to measure delay spectrum of general container via Wiener filtering.

+

The spectrum is calculated by applying a Wiener filter to the input frequency +spectrum, assuming an input model for the delay power spectrum of the signal and +that the noise power is described by the weights of the input container. See +https://arxiv.org/abs/2202.01242, Eq. A6 for details.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup(dps: DelaySpectrum)[source]
+

Set the delay power spectrum to use as the signal covariance.

+
+
Parameters:
+

dps (containers.DelaySpectrum) – Delay power spectrum for signal part of Wiener filter.

+
+
+
+ +
+ +
+
+class draco.analysis.delay.DelayTransformBase[source]
+

Bases: SingleTask

+

Base class for transforming from frequency to delay (non-functional).

+
+
+freq_zero
+

The physical frequency (in MHz) of the zero channel. That is the DC +channel coming out of the F-engine. If not specified, use the first +frequency channel of the stream.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+freq_spacing
+

The spacing between the underlying channels (in MHz). This is conjugate +to the length of a frame of time samples that is transformed. If not +set, then use the smallest gap found between channels in the dataset.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+nfreq
+

The number of frequency channels in the full set produced by the +F-engine. If not set, assume the last included frequency is the last of +the full set (or is the penultimate if skip_nyquist is set).

+
+
Type:
+

int, optional

+
+
+
+ +
+
+skip_nyquist
+

Whether the Nyquist frequency is included in the data. This is True by +default to align with the output of CASPER PFBs.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+apply_window
+

Whether to apply apodisation to frequency axis. Default: True.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+window
+

Apodisation to perform on frequency axis. Default: ‘nuttall’.

+
+
Type:
+

window available in draco.util.tools.window_generalised(), optional

+
+
+
+ +
+
+complex_timedomain
+

Whether to assume the original time samples that were channelized into a +frequency spectrum were purely real (False) or complex (True). If True, +freq_zero, nfreq, and skip_nyquist are ignored. Default: False.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+weight_boost
+

Multiply weights in the input container by this factor. This causes the task to +assume the noise power in the data is weight_boost times lower, which is +useful if you want the “true” noise to not be downweighted by the Wiener filter, +or have it included in the Gibbs sampler. Default: 1.0.

+
+
Type:
+

float, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(ss)[source]
+

Estimate the delay spectrum or power spectrum.

+
+
Parameters:
+

ss (containers.FreqContainer) – Data to transform. Must have a frequency axis.

+
+
Returns:
+

out_cont – Output delay spectrum or delay power spectrum.

+
+
Return type:
+

containers.DelayTransform or containers.DelaySpectrum

+
+
+
+ +
+ +
+
+draco.analysis.delay.delay_power_spectrum_gibbs(data, N, Ni, initial_S, window='nuttall', fsel=None, niter=20, rng=None, complex_timedomain=False)[source]
+

Estimate the delay power spectrum by Gibbs sampling.

+

This routine estimates the spectrum at the N delay samples conjugate to +an input frequency spectrum with N/2 + 1 channels (if the delay spectrum is +assumed real) or N channels (if the delay spectrum is assumed complex). +A subset of these channels can be specified using the fsel argument.

+
+
Parameters:
+
    +
  • data (np.ndarray[:, freq]) – Data to estimate the delay spectrum of.

  • +
  • N (int) – The length of the output delay spectrum. There are assumed to be N/2 + 1 +total frequency channels if assuming a real delay spectrum, or N channels +for a complex delay spectrum.

  • +
  • Ni (np.ndarray[freq]) – Inverse noise variance.

  • +
  • initial_S (np.ndarray[delay]) – The initial delay power spectrum guess.

  • +
  • window (one of {'nuttall', 'blackman_nuttall', 'blackman_harris', None}, optional) – Apply an apodisation function. Default: ‘nuttall’.

  • +
  • fsel (np.ndarray[freq], optional) – Indices of channels that we have data at. By default assume all channels.

  • +
  • niter (int, optional) – Number of Gibbs samples to generate.

  • +
  • rng (np.random.Generator, optional) – A generator to use to produce the random samples.

  • +
  • complex_timedomain (bool, optional) – If True, assume input data arose from a complex timestream. If False, assume +input data arose from a real timestream, such that the first and last frequency +channels have purely real values. Default: False.

  • +
+
+
Returns:
+

spec – List of spectrum samples.

+
+
Return type:
+

list

+
+
+
+ +
+
+draco.analysis.delay.delay_spectrum_gibbs(data, N, Ni, initial_S, window='nuttall', fsel=None, niter=20, rng=None, complex_timedomain=False)
+

Estimate the delay power spectrum by Gibbs sampling.

+

This routine estimates the spectrum at the N delay samples conjugate to +an input frequency spectrum with N/2 + 1 channels (if the delay spectrum is +assumed real) or N channels (if the delay spectrum is assumed complex). +A subset of these channels can be specified using the fsel argument.

+
+
Parameters:
+
    +
  • data (np.ndarray[:, freq]) – Data to estimate the delay spectrum of.

  • +
  • N (int) – The length of the output delay spectrum. There are assumed to be N/2 + 1 +total frequency channels if assuming a real delay spectrum, or N channels +for a complex delay spectrum.

  • +
  • Ni (np.ndarray[freq]) – Inverse noise variance.

  • +
  • initial_S (np.ndarray[delay]) – The initial delay power spectrum guess.

  • +
  • window (one of {'nuttall', 'blackman_nuttall', 'blackman_harris', None}, optional) – Apply an apodisation function. Default: ‘nuttall’.

  • +
  • fsel (np.ndarray[freq], optional) – Indices of channels that we have data at. By default assume all channels.

  • +
  • niter (int, optional) – Number of Gibbs samples to generate.

  • +
  • rng (np.random.Generator, optional) – A generator to use to produce the random samples.

  • +
  • complex_timedomain (bool, optional) – If True, assume input data arose from a complex timestream. If False, assume +input data arose from a real timestream, such that the first and last frequency +channels have purely real values. Default: False.

  • +
+
+
Returns:
+

spec – List of spectrum samples.

+
+
Return type:
+

list

+
+
+
+ +
+
+draco.analysis.delay.delay_spectrum_gibbs_cross(data: ndarray, N: int, Ni: ndarray, initial_S: ndarray, window: str = 'nuttall', fsel: ndarray | None = None, niter: int = 20, rng: Generator | None = None) list[ndarray][source]
+

Estimate the delay power spectrum by Gibbs sampling.

+

This routine estimates the spectrum at the N delay samples conjugate to +an input frequency spectrum with N/2 + 1 channels (if the delay spectrum is +assumed real) or N channels (if the delay spectrum is assumed complex). +A subset of these channels can be specified using the fsel argument.

+
+
Parameters:
+
    +
  • data – A 3D array of [dataset, sample, freq]. The delay cross-power spectrum of these +will be calculated.

  • +
  • N – The length of the output delay spectrum. There are assumed to be N/2 + 1 +total frequency channels if assuming a real delay spectrum, or N channels +for a complex delay spectrum.

  • +
  • Ni – Inverse noise variance as a 3D [dataset, sample, freq] array.

  • +
  • initial_S – The initial delay cross-power spectrum guess. A 3D array of [data1, data2, +delay].

  • +
  • window (one of {'nuttall', 'blackman_nuttall', 'blackman_harris', None}, optional) – Apply an apodisation function. Default: ‘nuttall’.

  • +
  • fsel – Indices of channels that we have data at. By default assume all channels.

  • +
  • niter – Number of Gibbs samples to generate.

  • +
  • rng – A generator to use to produce the random samples.

  • +
+
+
Returns:
+

spec – List of cross-power spectrum samples.

+
+
Return type:
+

list

+
+
+
+ +
+
+draco.analysis.delay.delay_spectrum_wiener_filter(delay_PS, data, N, Ni, window='nuttall', fsel=None, complex_timedomain=False)[source]
+

Estimate the delay spectrum from an input frequency spectrum by Wiener filtering.

+

This routine estimates the spectrum at the N delay samples conjugate to +an input frequency spectrum with N/2 + 1 channels (if the delay spectrum is +assumed real) or N channels (if the delay spectrum is assumed complex). +A subset of these channels can be specified using the fsel argument.

+
+
Parameters:
+
    +
  • delay_PS (np.ndarray[ndelay]) – Delay power spectrum to use for the signal covariance in the Wiener filter.

  • +
  • data (np.ndarray[nsample, freq]) – Data to estimate the delay spectrum of.

  • +
  • N (int) – The length of the output delay spectrum. There are assumed to be N/2 + 1 +total frequency channels if assuming a real delay spectrum, or N channels +for a complex delay spectrum.

  • +
  • Ni (np.ndarray[freq]) – Inverse noise variance.

  • +
  • fsel (np.ndarray[freq], optional) – Indices of channels that we have data at. By default assume all channels.

  • +
  • window (one of {'nuttall', 'blackman_nuttall', 'blackman_harris', None}, optional) – Apply an apodisation function. Default: ‘nuttall’.

  • +
  • complex_timedomain (bool, optional) – If True, assume input data arose from a complex timestream. If False, assume +input data arose from a real timestream, such that the first and last frequency +channels have purely real values. Default: False.

  • +
+
+
Returns:
+

y_spec – Delay spectrum for each element of the sample axis.

+
+
Return type:
+

np.ndarray[nsample, ndelay]

+
+
+
+ +
+
+draco.analysis.delay.flatten_axes(dset: MemDatasetDistributed, axes_to_keep: List[str], match_dset: MemDatasetDistributed | None = None) Tuple[MPIArray, List[str]][source]
+

Move the specified axes of the dataset to the back, and flatten all others.

+

Optionally this will add length-1 axes to match the axes of another dataset.

+
+
Parameters:
+
    +
  • dset – The dataset to reshape.

  • +
  • axes_to_keep – The names of the axes to keep.

  • +
  • match_dset – An optional dataset to match the shape of.

  • +
+
+
Returns:
+

    +
  • flat_array – The MPIArray representing the re-arranged dataset. Distributed along the +flattened axis.

  • +
  • flat_axes – The names of the flattened axes from slowest to fastest varying.

  • +
+

+
+
+
+ +
+
+draco.analysis.delay.fourier_matrix(N: int, fsel: ndarray | None = None) ndarray[source]
+

Generate a Fourier matrix to represent a real to complex FFT.

+
+
Parameters:
+
    +
  • N (integer) – Length of timestream that we are transforming to. Must be even.

  • +
  • fsel (array_like, optional) – Indexes of the frequency channels to include in the transformation +matrix. By default, assume all channels.

  • +
+
+
Returns:
+

Fr – An array performing the Fourier transform from a real time series to +frequencies packed as alternating real and imaginary elements,

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.analysis.delay.fourier_matrix_c2c(N, fsel=None)[source]
+

Generate a Fourier matrix to represent a complex to complex FFT.

+

These Fourier conventions match numpy.fft.fft().

+
+
Parameters:
+
    +
  • N (integer) – Length of timestream that we are transforming to.

  • +
  • fsel (array_like, optional) – Indices of the frequency channels to include in the transformation +matrix. By default, assume all channels.

  • +
+
+
Returns:
+

F – An array performing the Fourier transform from a complex time series to +frequencies, with both input and output packed as alternating real and +imaginary elements.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.analysis.delay.fourier_matrix_c2r(N, fsel=None)[source]
+

Generate a Fourier matrix to represent a complex to real FFT.

+
+
Parameters:
+
    +
  • N (integer) – Length of timestream that we are transforming to. Must be even.

  • +
  • fsel (array_like, optional) – Indexes of the frequency channels to include in the transformation +matrix. By default, assume all channels.

  • +
+
+
Returns:
+

Fr – An array performing the Fourier transform from frequencies packed as +alternating real and imaginary elements, to the real time series.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.analysis.delay.fourier_matrix_r2c(N, fsel=None)[source]
+

Generate a Fourier matrix to represent a real to complex FFT.

+
+
Parameters:
+
    +
  • N (integer) – Length of timestream that we are transforming to. Must be even.

  • +
  • fsel (array_like, optional) – Indexes of the frequency channels to include in the transformation +matrix. By default, assume all channels.

  • +
+
+
Returns:
+

Fr – An array performing the Fourier transform from a real time series to +frequencies packed as alternating real and imaginary elements,

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.analysis.delay.match_axes(dset1, dset2)[source]
+

Make sure that dset2 has the same set of axes as dset1.

+

Sometimes the weights are missing axes (usually where the entries would all be +the same), we need to map these into one another and expand the weights to the +same size as the visibilities. This assumes that the vis/weight axes are in the +same order when present

+
+
Parameters:
+
    +
  • dset1 – The dataset with more axes.

  • +
  • dset2 – The dataset with a subset of axes. For the moment these are assumed to be in +the same order.

  • +
+
+
Returns:
+

A view of dset2 with length-1 axes inserted to match the axes missing from +dset1.

+
+
Return type:
+

dset2_view

+
+
+
+ +
+
+draco.analysis.delay.null_delay_filter(freq, max_delay, mask, num_delay=200, tol=1e-08, window=True)[source]
+

Take frequency data and null out any delays below some value.

+
+
Parameters:
+
    +
  • freq (np.ndarray[freq]) – Frequencies we have data at.

  • +
  • max_delay (float) – Maximum delay to keep.

  • +
  • mask (np.ndarray[freq]) – Frequencies to mask out.

  • +
  • num_delay (int, optional) – Number of delay values to use.

  • +
  • tol (float, optional) – Cut off value for singular values.

  • +
  • window (bool, optional) – Apply a window function to the data while filtering.

  • +
+
+
Returns:
+

filter – The filter as a 2D matrix.

+
+
Return type:
+

np.ndarray[freq, freq]

+
+
+
+ +
+
+draco.analysis.delay.stokes_I(sstream, tel)[source]
+

Extract instrumental Stokes I from a time/sidereal stream.

+
+
Parameters:
+
    +
  • sstream (containers.SiderealStream, container.TimeStream) – Stream of correlation data.

  • +
  • tel (TransitTelescope) – Instance describing the telescope.

  • +
+
+
Returns:
+

    +
  • vis_I (mpiarray.MPIArray[nbase, nfreq, ntime]) – The instrumental Stokes I visibilities, distributed over baselines.

  • +
  • vis_weight (mpiarray.MPIArray[nbase, nfreq, ntime]) – The weights for each visibility, distributed over baselines.

  • +
  • ubase (np.ndarray[nbase, 2]) – Baseline vectors corresponding to output.

  • +
+

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.fgfilter.html b/docs/_autosummary/draco.analysis.fgfilter.html new file mode 100644 index 000000000..61a7f5736 --- /dev/null +++ b/docs/_autosummary/draco.analysis.fgfilter.html @@ -0,0 +1,227 @@ + + + + + + + draco.analysis.fgfilter — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.fgfilter

+

Tasks for foreground filtering data.

+

Classes

+ + + + + + + + + +

KLModeProject()

Project between the SVD and KL basis.

SVDModeProject()

SVD projection between the raw m-modes and the reduced degrees of freedom.

+
+
+class draco.analysis.fgfilter.KLModeProject[source]
+

Bases: _ProjectFilterBase

+

Project between the SVD and KL basis.

+
+
+threshold
+

KL mode threshold.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+klname
+

Name of filter to use.

+
+
Type:
+

str

+
+
+
+ +
+
+mode
+

Which projection to perform. Into the KL basis (forward), out of the +KL basis (backward), and forward then backward in order to KL filter the +data through the basis (filter).

+
+
Type:
+

string

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup(manager)[source]
+

Set the product manager that holds the saved KL modes.

+
+ +
+ +
+
+class draco.analysis.fgfilter.SVDModeProject[source]
+

Bases: _ProjectFilterBase

+

SVD projection between the raw m-modes and the reduced degrees of freedom.

+

Note that this produces the packed SVD modes, with the modes from each +frequency concatenated.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup(bt)[source]
+

Set the beamtransfer instance.

+
+
Parameters:
+

bt (BeamTransfer) – This can also take a ProductManager instance.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.flagging.html b/docs/_autosummary/draco.analysis.flagging.html new file mode 100644 index 000000000..3e040fd88 --- /dev/null +++ b/docs/_autosummary/draco.analysis.flagging.html @@ -0,0 +1,1630 @@ + + + + + + + draco.analysis.flagging — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.flagging

+

Tasks for flagging out bad or unwanted data.

+

This includes data quality flagging on timestream data; sun excision on sidereal +data; and pre-map making flagging on m-modes.

+

The convention for flagging/masking is True for contaminated samples that should +be excluded and False for clean samples.

+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

complex_med(x, *args, **kwargs)

Complex median, done by applying to the real/imag parts individually.

destripe(x, w[, axis])

Subtract the median along a specified axis.

inverse_binom_cdf_prob(k, N, F)

Calculate the trial probability that gives the CDF.

mad(x, mask[, base_size, mad_size, debug, sigma])

Calculate the MAD of freq-time data.

medfilt(x, mask, size, *args)

Apply a moving median filter to masked data.

p_to_sigma(p)

Get the sigma exceeded by the tails of a Gaussian with probability p.

sigma_to_p(sigma)

Get the probability of an excursion larger than sigma for a Gaussian.

tv_channels_flag(x, freq[, sigma, f, debug])

Perform a higher sensitivity flagging for the TV stations.

+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

ApplyBaselineMask()

Apply a distributed mask that varies across baselines.

ApplyRFIMask

alias of ApplyTimeFreqMask

ApplyTimeFreqMask()

Apply a time-frequency mask to the data.

BlendStack()

Mix a small amount of a stack into data to regularise RFI gaps.

CollapseBaselineMask()

Collapse a baseline-dependent mask along the baseline axis.

DayMask()

Crudely simulate a masking out of the daytime data.

FindBeamformedOutliers()

Identify beamformed visibilities that deviate from our expectation for noise.

MaskBadGains()

Get a mask of regions with bad gain.

MaskBaselines()

Mask out baselines from a dataset.

MaskBeamformedOutliers()

Mask beamformed visibilities that deviate from our expectation for noise.

MaskBeamformedWeights()

Mask beamformed visibilities with anomalously large weights before stacking.

MaskData

alias of MaskMModeData

MaskFreq()

Make a mask for certain frequencies.

MaskMModeData()

Mask out mmode data ahead of map making.

RFIMask()

Crappy RFI masking.

RFISensitivityMask()

Slightly less crappy RFI masking.

RadiometerWeight()

Update vis_weight according to the radiometer equation.

SanitizeWeights()

Flags weights outside of a valid range.

SmoothVisWeight()

Smooth the visibility weights with a median filter.

ThresholdVisWeightBaseline()

Form a mask corresponding to weights that are below some threshold.

ThresholdVisWeightFrequency()

Create a mask to remove all weights below a per-frequency threshold.

+
+
+class draco.analysis.flagging.ApplyBaselineMask[source]
+

Bases: SingleTask

+

Apply a distributed mask that varies across baselines.

+

No broadcasting is done, so the data and mask should have the same +axes. This shouldn’t be used for non-distributed time-freq masks.

+

This task may produce output with shared datasets. Be warned that +this can produce unexpected outputs if not properly taken into +account.

+
+
+share
+

Which datasets should we share with the input. If “none” we create a +full copy of the data, if “vis” or “map” we create a copy only of the modified +weight dataset and the unmodified vis dataset is shared, if “all” we +modify in place and return the input container.

+
+
Type:
+

{“all”, “none”, “vis”, “map”}

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data: TimeStream, mask: BaselineMask) TimeStream[source]
+
+process(data: SiderealStream, mask: SiderealBaselineMask) SiderealStream
+

Flag data by zeroing the weights.

+
+
Parameters:
+
    +
  • data – Data to apply mask to. Must have a stack axis

  • +
  • mask – A baseline-dependent mask

  • +
+
+
Returns:
+

The masked data. Masking is done in place.

+
+
Return type:
+

data

+
+
+
+ +
+ +
+
+draco.analysis.flagging.ApplyRFIMask
+

alias of ApplyTimeFreqMask

+
+ +
+
+class draco.analysis.flagging.ApplyTimeFreqMask[source]
+

Bases: SingleTask

+

Apply a time-frequency mask to the data.

+

Typically this is used to ask out all inputs at times and +frequencies contaminated by RFI.

+

This task may produce output with shared datasets. Be warned that +this can produce unexpected outputs if not properly taken into +account.

+
+
+share
+

Which datasets should we share with the input. If “none” we create a +full copy of the data, if “vis” or “map” we create a copy only of the modified +weight dataset and the unmodified vis dataset is shared, if “all” we +modify in place and return the input container.

+
+
Type:
+

{“all”, “none”, “vis”, “map”}

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(tstream, rfimask)[source]
+

Apply the mask by zeroing the weights.

+
+
Parameters:
+
    +
  • tstream (timestream or sidereal stream) – A timestream or sidereal stream like container. For example, +containers.TimeStream, andata.CorrData or +containers.SiderealStream.

  • +
  • rfimask (containers.RFIMask) – An RFI mask for the same period of time.

  • +
+
+
Returns:
+

tstream – The masked timestream. Note that the masking is done in place.

+
+
Return type:
+

timestream or sidereal stream

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.BlendStack[source]
+

Bases: SingleTask

+

Mix a small amount of a stack into data to regularise RFI gaps.

+

This is designed to mix in a small amount of a stack into a day of data (which +will have RFI masked gaps) to attempt to regularise operations which struggle to +deal with time variable masks, e.g. DelaySpectrumEstimator.

+
+
+frac
+

The relative weight to give the stack in the average. This multiplies the +weights already in the stack, and so it should be remembered that these may +already be significantly higher than the single day weights.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+match_median
+

Estimate the median in the time/RA direction from the common samples and use +this to match any quasi time-independent bias of the data (e.g. cross talk).

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+subtract
+

Rather than taking an average, instead subtract out the blending stack +from the input data in the common samples to calculate the difference +between them. The interpretation of frac is a scaling of the inverse +variance of the stack to an inverse variance of a prior on the +difference, e.g. a frac = 1e-4 means that we expect the standard +deviation of the difference between the data and the stacked data to be +100x larger than the noise of the stacked data.

+
+
Type:
+

bool, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Blend a small amount of the stack into the incoming data.

+
+
Parameters:
+

data (VisContainer) – The data to be blended into. This is modified in place.

+
+
Returns:
+

data_blend – The modified data. This is the same object as the input, and it has been +modified in place.

+
+
Return type:
+

VisContainer

+
+
+
+ +
+
+setup(data_stack)[source]
+

Set the stacked data.

+
+
Parameters:
+

data_stack (VisContainer) – Data stack to blend

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.CollapseBaselineMask[source]
+

Bases: SingleTask

+

Collapse a baseline-dependent mask along the baseline axis.

+

The output is a frequency/time mask that is True for any freq/time sample +for which any baseline is masked in the input mask.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(baseline_mask: BaselineMask | SiderealBaselineMask) RFIMask | SiderealRFIMask[source]
+

Collapse input mask over baseline axis.

+
+
Parameters:
+

baseline_mask (BaselineMask or SiderealBaselineMask) – Input baseline-dependent mask

+
+
Returns:
+

mask_cont – Output baseline-independent mask.

+
+
Return type:
+

RFIMask or SiderealRFIMask

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.DayMask[source]
+

Bases: SingleTask

+

Crudely simulate a masking out of the daytime data.

+
+
+start, end
+

Start and end of masked out region.

+
+
Type:
+

float

+
+
+
+ +
+
+width
+

Use a smooth transition of given width between the fully masked and +unmasked data. This is interior to the region marked by start and end.

+
+
Type:
+

float

+
+
+
+ +
+
+zero_data
+

Zero the data in addition to modifying the noise weights +(default is True).

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+remove_average
+

Estimate and remove the mean level from each visibilty. This estimate +does not use data from the masked region.

+
+
Type:
+

bool, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sstream)[source]
+

Apply a day time mask.

+
+
Parameters:
+

sstream (containers.SiderealStream) – Unmasked sidereal stack.

+
+
Returns:
+

mstream – Masked sidereal stream.

+
+
Return type:
+

containers.SiderealStream

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.FindBeamformedOutliers[source]
+

Bases: SingleTask

+

Identify beamformed visibilities that deviate from our expectation for noise.

+
+
+nsigma
+

Beamformed visibilities whose magnitude is greater than nsigma times +the expected standard deviation of the noise, given by sqrt(1 / weight), +will be masked.

+
+
Type:
+

float

+
+
+
+ +
+
+window
+

If provided, the outlier mask will be extended to cover neighboring pixels. +This list provides the number of pixels in each dimension that a single +outlier will mask. Only supported for RingMap containers, where the list +should be length 2 with [nra, nel], and FormedBeamHA containers, where the list +should be length 1 with [nha,].

+
+
Type:
+

list of int

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Create a mask that indicates outlier beamformed visibilities.

+
+
Parameters:
+

data (FormedBeam, FormedBeamHA, or RingMap) – Beamformed visibilities.

+
+
Returns:
+

out – Container with a boolean mask where True indicates +outlier beamformed visibilities.

+
+
Return type:
+

FormedBeamMask, FormedBeamHAMask, or RingMapMask

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.MaskBadGains[source]
+

Bases: SingleTask

+

Get a mask of regions with bad gain.

+

Assumes that bad gains are set to 1.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Generate a time-freq mask.

+
+
Parameters:
+

data (andata.Corrdata or container.ContainerBase with a gain dataset) – Data containing the gains to be flagged. Must have a gain dataset.

+
+
Returns:
+

mask – Time-freq mask

+
+
Return type:
+

RFIMask container

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.MaskBaselines[source]
+

Bases: SingleTask

+

Mask out baselines from a dataset.

+

This task may produce output with shared datasets. Be warned that +this can produce unexpected outputs if not properly taken into +account.

+
+
+mask_long_ns
+

Mask out baselines longer than a given distance in the N/S direction.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+mask_short
+

Mask out baselines shorter than a given distance.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+mask_short_ew
+

Mask out baselines shorter then a given distance in the East-West +direction. Useful for masking out intra-cylinder baselines for +North-South oriented cylindrical telescopes.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+mask_short_ns
+

Mask out baselines shorter then a given distance in the North-South +direction.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+missing_threshold
+

Mask any baseline that is missing more than this fraction of samples. This is +measured relative to other baselines.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+zero_data
+

Zero the data in addition to modifying the noise weights +(default is False).

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+share
+

Which datasets should we share with the input. If “none” we create a +full copy of the data, if “vis” we create a copy only of the modified +weight dataset and the unmodified vis dataset is shared, if “all” we +modify in place and return the input container.

+
+
Type:
+

{“all”, “none”, “vis”}

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(ss)[source]
+

Apply the mask to data.

+
+
Parameters:
+

ss (SiderealStream or TimeStream) – Data to mask. Applied in place.

+
+
+
+ +
+
+setup(telescope)[source]
+

Set the telescope model.

+
+
Parameters:
+

telescope (TransitTelescope) – The telescope object to use

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.MaskBeamformedOutliers[source]
+

Bases: SingleTask

+

Mask beamformed visibilities that deviate from our expectation for noise.

+

This is operating under the assumption that, after proper foreground filtering, +the beamformed visibilities should be consistent with noise.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data, mask)[source]
+

Mask outlier beamformed visibilities.

+
+
Parameters:
+
+
+
Returns:
+

data – The input container with the weight dataset set to zero +for samples that were identified as outliers.

+
+
Return type:
+

FormedBeam or RingMap

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.MaskBeamformedWeights[source]
+

Bases: SingleTask

+

Mask beamformed visibilities with anomalously large weights before stacking.

+
+
+nmed
+

Any weight that is more than nmed times the median weight +over all objects and frequencies will be set to zero. +Default is 8.0.

+
+
Type:
+

float

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Mask large weights.

+
+
Parameters:
+

data (FormedBeam) – Beamformed visibilities.

+
+
Returns:
+

data – The input container with the weight dataset set to zero +if the weights exceed the threshold.

+
+
Return type:
+

FormedBeam

+
+
+
+ +
+ +
+
+draco.analysis.flagging.MaskData
+

alias of MaskMModeData

+
+ +
+
+class draco.analysis.flagging.MaskFreq[source]
+

Bases: SingleTask

+

Make a mask for certain frequencies.

+
+
+bad_freq_ind
+

A list containing frequencies to flag out. Each entry can either be an +integer giving an individual frequency index to remove, or 2-tuples giving +start and end indices of a range to flag (as with a standard slice, the end +is not included.)

+
+
Type:
+

list, optional

+
+
+
+ +
+
+factorize
+

Find the smallest factorizable mask of the time-frequency axis that covers all +samples already flagged in the data.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+all_time
+

Only include frequencies where all time samples are present.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+mask_missing_data
+

Mask time-freq samples where some baselines (for visibily data) or +polarisations/elevations (for ring map data) are missing.

+
+
Type:
+

bool, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data: VisContainer | RingMap) RFIMask | SiderealRFIMask[source]
+

Make the mask.

+
+
Parameters:
+

data – The data to mask.

+
+
Returns:
+

Frequency mask container

+
+
Return type:
+

mask_cont

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.MaskMModeData[source]
+

Bases: SingleTask

+

Mask out mmode data ahead of map making.

+
+
+auto_correlations
+

Exclude auto correlations if set (default=False).

+
+
Type:
+

bool

+
+
+
+ +
+
+m_zero
+

Ignore the m=0 mode (default=False).

+
+
Type:
+

bool

+
+
+
+ +
+
+positive_m
+

Include positive m-modes (default=True).

+
+
Type:
+

bool

+
+
+
+ +
+
+negative_m
+

Include negative m-modes (default=True).

+
+
Type:
+

bool

+
+
+
+ +
+
+mask_low_m
+

If set, mask out m’s lower than this threshold.

+
+
Type:
+

int, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(mmodes)[source]
+

Mask out unwanted datain the m-modes.

+
+
Parameters:
+

mmodes (containers.MModes) – Mmode container to mask

+
+
Returns:
+

mmodes – Same object as input with masking applied

+
+
Return type:
+

containers.MModes

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.RFIMask[source]
+

Bases: SingleTask

+

Crappy RFI masking.

+
+
+sigma
+

The false positive rate of the flagger given as sigma value assuming +the non-RFI samples are Gaussian.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+tv_fraction
+

Number of bad samples in a digital TV channel that cause the whole +channel to be flagged.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+stack_ind
+

Which stack to process to derive flags for the whole dataset.

+
+
Type:
+

int

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sstream: SiderealStream) SiderealRFIMask[source]
+
+process(sstream: TimeStream) RFIMask
+

Apply a day time mask.

+
+
Parameters:
+

sstream – Unmasked sidereal or time stream visibility data.

+
+
Returns:
+

The derived RFI mask.

+
+
Return type:
+

mask

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.RFISensitivityMask[source]
+

Bases: SingleTask

+

Slightly less crappy RFI masking.

+
+
+mask_type
+

One of ‘mad’, ‘sumthreshold’ or ‘combine’. +Default is combine, which uses the sumthreshold everywhere +except around the transits of the Sun, CasA and CygA where it +applies the MAD mask to avoid masking out the transits.

+
+
Type:
+

string, optional

+
+
+
+ +
+
+include_pol
+

The list of polarisations to include. Default is to use all +polarisations.

+
+
Type:
+

list of strings, optional

+
+
+
+ +
+
+remove_median
+

Remove median accross times for each frequency? +Recommended. Default: True.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+sir
+

Apply scale invariant rank (SIR) operator on top of final mask? +We find that this is advisable while we still haven’t flagged +out all the static bands properly. Default: True.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+sigma
+

The false positive rate of the flagger given as sigma value assuming +the non-RFI samples are Gaussian. +Used for the MAD and TV station flaggers.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+max_m
+

Maximum size of the SumThreshold window to use. +The default (8) seems to work well with sensitivity data.

+
+
Type:
+

int, optional

+
+
+
+ +
+
+start_threshold_sigma
+

The desired threshold for the SumThreshold algorithm at the +final window size (determined by max m) given as a +number of standard deviations (to be estimated from the +sensitivity map excluding weight and static masks). +The default (8) seems to work well with sensitivity data +using the default max_m.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+tv_fraction
+

Number of bad samples in a digital TV channel that cause the whole +channel to be flagged.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+tv_base_size
+

The size of the region used to estimate the baseline for the TV channel +detection.

+
+
Type:
+

[int, int]

+
+
+
+ +
+
+tv_mad_size
+

The size of the region used to estimate the MAD for the TV channel detection.

+
+
Type:
+

[int, int]

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sensitivity)[source]
+

Derive an RFI mask from sensitivity data.

+
+
Parameters:
+

sensitivity (containers.SystemSensitivity) – Sensitivity data to derive the RFI mask from.

+
+
Returns:
+

rfimask – RFI mask derived from sensitivity.

+
+
Return type:
+

containers.RFIMask

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.RadiometerWeight[source]
+

Bases: SingleTask

+

Update vis_weight according to the radiometer equation.

+
+\[\text{weight}_{ij} = N_\text{samp} / V_{ii} V_{jj}\]
+
+
+replace
+

Replace any existing weights (default). If False then we multiply the +existing weights by the radiometer values.

+
+
Type:
+

bool, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(stream)[source]
+

Change the vis weight.

+
+
Parameters:
+

stream (SiderealStream or TimeStream) – Data to be weighted. This is done in place.

+
+
Returns:
+

stream

+
+
Return type:
+

SiderealStream or TimeStream

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.SanitizeWeights[source]
+

Bases: SingleTask

+

Flags weights outside of a valid range.

+

Flags any weights above a max threshold and below a minimum threshold. +Baseline dependent, so only some baselines may be flagged.

+
+
+max_thresh
+

largest value to keep

+
+
Type:
+

float

+
+
+
+ +
+
+min_thresh
+

smallest value to keep

+
+
Type:
+

float

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Mask any weights outside of the threshold range.

+
+
Parameters:
+

data (andata.CorrData or containers.VisContainer object) – Data containing the weights to be flagged

+
+
Returns:
+

data – Data object with high/low weights masked in-place

+
+
Return type:
+

same object as data

+
+
+
+ +
+
+setup()[source]
+

Validate the max and min values.

+
+
Raises:
+

ValueError – if min_thresh is larger than max_thresh

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.SmoothVisWeight[source]
+

Bases: SingleTask

+

Smooth the visibility weights with a median filter.

+

This is done in-place.

+
+
+kernel_size
+

Size of the kernel for the median filter in time points. +Default is 31, corresponding to ~5 minutes window for 10s cadence data.

+
+
Type:
+

int, optional

+
+
+
+ +
+
+mask_zeros
+

Mask out zero-weight entries when taking the moving weighted median.

+
+
Type:
+

bool, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data: TimeStream) TimeStream[source]
+

Smooth the weights with a median filter.

+
+
Parameters:
+

data – Data containing the weights to be smoothed

+
+
Returns:
+

Data object containing the same data as the input, but with the +weights substituted by the smoothed ones.

+
+
Return type:
+

data

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.ThresholdVisWeightBaseline[source]
+

Bases: SingleTask

+

Form a mask corresponding to weights that are below some threshold.

+

The threshold is determined as maximum(absolute_threshold, +relative_threshold * average(weight)) and is evaluated per product/stack +entry. The user can specify whether to use a mean or median as the average, +but note that the mean is much more likely to be biased by anomalously +high- or low-weight samples (both of which are present in raw CHIME data). +The user can also specify that weights below some threshold should not be +considered when taking the average and constructing the mask (the default +is to only ignore zero-weight samples).

+

The task outputs a BaselineMask or SiderealBaselineMask depending on the +input container.

+
+
Parameters:
+
    +
  • average_type (string, optional) – Type of average to use (“median” or “mean”). Default: “median”.

  • +
  • absolute_threshold (float, optional) – Any weights with values less than this number will be set to zero. +Default: 1e-7.

  • +
  • relative_threshold (float, optional) – Any weights with values less than this number times the average weight +will be set to zero. Default: 1e-6.

  • +
  • ignore_absolute_threshold (float, optional) – Any weights with values less than this number will be ignored when +taking averages and constructing the mask. Default: 0.0.

  • +
  • pols_to_flag (string, optional) – Which polarizations to flag. “copol” only flags XX and YY baselines, +while “all” flags everything. Default: “all”.

  • +
+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(stream) BaselineMask | SiderealBaselineMask[source]
+

Construct baseline-dependent mask.

+
+
Parameters:
+

stream (.core.container with weight attribute) – Input container whose weights are used to construct the mask.

+
+
Returns:
+

out – The output baseline-dependent mask.

+
+
Return type:
+

BaselineMask or SiderealBaselineMask

+
+
+
+ +
+
+setup(telescope)[source]
+

Set the telescope model.

+
+
Parameters:
+

telescope (TransitTelescope) – The telescope object to use

+
+
+
+ +
+ +
+
+class draco.analysis.flagging.ThresholdVisWeightFrequency[source]
+

Bases: SingleTask

+

Create a mask to remove all weights below a per-frequency threshold.

+

A single relative threshold is set for each frequency along with an absolute +minimum weight threshold. Masking is done relative to the mean baseline.

+
+
Parameters:
+
    +
  • absolute_threshold (float) – Any weights with values less than this number will be set to zero.

  • +
  • relative_threshold (float) – Any weights with values less than this number times the average weight +will be set to zero.

  • +
+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(stream)[source]
+

Make a baseline-independent mask.

+
+
Parameters:
+

stream (.core.container with weight attribute) – Container to mask

+
+
Returns:
+

out – RFIMask container with mask set

+
+
Return type:
+

RFIMask container

+
+
+
+ +
+ +
+
+draco.analysis.flagging.complex_med(x, *args, **kwargs)[source]
+

Complex median, done by applying to the real/imag parts individually.

+
+
Parameters:
+
    +
  • x (np.ndarray) – Array to apply to.

  • +
  • *args (list, dict) – Passed straight through to np.nanmedian

  • +
  • **kwargs (list, dict) – Passed straight through to np.nanmedian

  • +
+
+
Returns:
+

m – Median.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.analysis.flagging.destripe(x, w, axis=1)[source]
+

Subtract the median along a specified axis.

+
+
Parameters:
+
    +
  • x (np.ndarray) – Array to destripe.

  • +
  • w (np.ndarray) – Mask array for points to include (True) or ignore (False).

  • +
  • axis (int, optional) – Axis to apply destriping along.

  • +
+
+
Returns:
+

y – Destriped array.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.analysis.flagging.inverse_binom_cdf_prob(k, N, F)[source]
+

Calculate the trial probability that gives the CDF.

+

This gets the trial probability that gives an overall cumulative +probability for Pr(X <= k; N, p) = F

+
+
Parameters:
+
    +
  • k (int) – Maximum number of successes.

  • +
  • N (int) – Total number of trials.

  • +
  • F (float) – The cumulative probability for (k, N).

  • +
+
+
Returns:
+

p – The trial probability.

+
+
Return type:
+

float

+
+
+
+ +
+
+draco.analysis.flagging.mad(x, mask, base_size=(11, 3), mad_size=(21, 21), debug=False, sigma=True)[source]
+

Calculate the MAD of freq-time data.

+
+
Parameters:
+
    +
  • x (np.ndarray) – Data to filter.

  • +
  • mask (np.ndarray) – Initial mask.

  • +
  • base_size (tuple) – Size of the window to use in (freq, time) when +estimating the baseline.

  • +
  • mad_size (tuple) – Size of the window to use in (freq, time) when +estimating the MAD.

  • +
  • debug (bool, optional) – If True, return deviation and mad arrays as well

  • +
  • sigma (bool, optional) – Rescale the output into units of Gaussian sigmas.

  • +
+
+
Returns:
+

mad – Size of deviation at each point in MAD units. This output may contain +NaN’s for regions of missing data.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.analysis.flagging.medfilt(x, mask, size, *args)[source]
+

Apply a moving median filter to masked data.

+

The application is done by iterative filling to +overcome the fact we don’t have an actual implementation +of a nanmedian.

+
+
Parameters:
+
    +
  • x (np.ndarray) – Data to filter.

  • +
  • mask (np.ndarray) – Mask of data to filter out.

  • +
  • size (tuple) – Size of the window in each dimension.

  • +
  • args (optional) – Additional arguments to pass to the moving weighted median

  • +
+
+
Returns:
+

y – The masked data. Data within the mask is undefined.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.analysis.flagging.p_to_sigma(p)[source]
+

Get the sigma exceeded by the tails of a Gaussian with probability p.

+
+ +
+
+draco.analysis.flagging.sigma_to_p(sigma)[source]
+

Get the probability of an excursion larger than sigma for a Gaussian.

+
+ +
+
+draco.analysis.flagging.tv_channels_flag(x, freq, sigma=5, f=0.5, debug=False)[source]
+

Perform a higher sensitivity flagging for the TV stations.

+

This flags a whole TV station band if more than fraction f of the samples +within a station band exceed a given threshold. The threshold is calculated +by wanting a fixed false positive rate (as described by sigma) for fraction +f of samples exceeding the threshold

+
+
Parameters:
+
    +
  • x (np.ndarray[freq, time]) – Deviations of data in sigma units.

  • +
  • freq (np.ndarray[freq]) – Frequency of samples in MHz.

  • +
  • sigma (float, optional) – The probability of a false positive given as a sigma of a Gaussian.

  • +
  • f (float, optional) – Fraction of bad samples within each channel before flagging the whole +thing.

  • +
  • debug (bool, optional) – Returns (mask, fraction) instead to give extra debugging info.

  • +
+
+
Returns:
+

mask – Mask of the input data.

+
+
Return type:
+

np.ndarray[bool]

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.html b/docs/_autosummary/draco.analysis.html new file mode 100644 index 000000000..9a36543f8 --- /dev/null +++ b/docs/_autosummary/draco.analysis.html @@ -0,0 +1,176 @@ + + + + + + + draco.analysis — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

beamform

Beamform visibilities to the location of known sources.

delay

Delay space spectrum estimation and filtering.

fgfilter

Tasks for foreground filtering data.

flagging

Tasks for flagging out bad or unwanted data.

mapmaker

Map making from driftscan data using the m-mode formalism.

powerspectrum

Power spectrum estimation code.

sensitivity

Sensitivity Analysis Tasks.

sidereal

Take timestream data and regridding it into sidereal days which can be stacked.

sourcestack

Source Stack Analysis Tasks.

svdfilter

A set of tasks for SVD filtering the m-modes.

transform

Miscellaneous transformations to do on data.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.mapmaker.html b/docs/_autosummary/draco.analysis.mapmaker.html new file mode 100644 index 000000000..836dbd055 --- /dev/null +++ b/docs/_autosummary/draco.analysis.mapmaker.html @@ -0,0 +1,301 @@ + + + + + + + draco.analysis.mapmaker — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.mapmaker

+

Map making from driftscan data using the m-mode formalism.

+

Functions

+ + + + + + +

pinv_svd(M[, acond, rcond])

Generate the pseudo-inverse from an svd.

+

Classes

+ + + + + + + + + + + + + + + +

BaseMapMaker()

Rudimetary m-mode map maker.

DirtyMapMaker()

Generate a dirty map.

MaximumLikelihoodMapMaker()

Generate a Maximum Likelihood map using the Moore-Penrose pseudo-inverse.

WienerMapMaker()

Generate a Wiener filtered map.

+
+
+class draco.analysis.mapmaker.BaseMapMaker[source]
+

Bases: SingleTask

+

Rudimetary m-mode map maker.

+
+
+nside
+

Resolution of output Healpix map.

+
+
Type:
+

int

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(mmodes)[source]
+

Make a map from the given m-modes.

+
+
Parameters:
+

mmodes (containers.MModes) – Data to map

+
+
Returns:
+

map

+
+
Return type:
+

containers.Map

+
+
+
+ +
+
+setup(bt)[source]
+

Set the beamtransfer matrices to use.

+
+
Parameters:
+

bt (beamtransfer.BeamTransfer or manager.ProductManager) – Beam transfer manager object (or ProductManager) containing all the +pre-generated beam transfer matrices.

+
+
+
+ +
+ +
+
+class draco.analysis.mapmaker.DirtyMapMaker[source]
+

Bases: BaseMapMaker

+

Generate a dirty map.

+

Notes

+

The dirty map is produced by generating a set of \(a_{lm}\) coefficients +using

+
+\[\hat{\mathbf{a}} = \mathbf{B}^\dagger \mathbf{N}^{-1} \mathbf{v}\]
+

and then performing the spherical harmonic transform to get the sky intensity.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.mapmaker.MaximumLikelihoodMapMaker[source]
+

Bases: BaseMapMaker

+

Generate a Maximum Likelihood map using the Moore-Penrose pseudo-inverse.

+

Notes

+

The dirty map is produced by generating a set of \(a_{lm}\) coefficients +using

+
+\[\hat{\mathbf{a}} = \left( \mathbf{N}^{-1/2 }\mathbf{B} \right) ^+ \mathbf{N}^{-1/2} \mathbf{v}\]
+

where the superscript \(+\) denotes the pseudo-inverse.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.mapmaker.WienerMapMaker[source]
+

Bases: BaseMapMaker

+

Generate a Wiener filtered map.

+

Assumes that the signal is a Gaussian random field described by +a power-law power spectum.

+
+
+prior_amp
+

An amplitude prior to use for the map maker. In Kelvin.

+
+
Type:
+

float

+
+
+
+ +
+
+prior_tilt
+

Power law index prior for the power spectrum.

+
+
Type:
+

float

+
+
+
+ +

Notes

+

The Wiener map is produced by generating a set of \(a_{lm}\) coefficients +using

+
+\[\hat{\mathbf{a}} = \left( \mathbf{S}^{-1} + \mathbf{B}^\dagger +\mathbf{N}^{-1} \mathbf{B} \right)^{-1} \mathbf{B}^\dagger \mathbf{N}^{-1} \mathbf{v}\]
+

where the signal covariance matrix \(\mathbf{S}\) is assumed to be +governed by a power law power spectrum for each polarisation component.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+draco.analysis.mapmaker.pinv_svd(M, acond=0.0001, rcond=0.001)[source]
+

Generate the pseudo-inverse from an svd.

+

Not really clear why I’m not just using la.pinv2 instead

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.powerspectrum.html b/docs/_autosummary/draco.analysis.powerspectrum.html new file mode 100644 index 000000000..c67970841 --- /dev/null +++ b/docs/_autosummary/draco.analysis.powerspectrum.html @@ -0,0 +1,211 @@ + + + + + + + draco.analysis.powerspectrum — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.powerspectrum

+

Power spectrum estimation code.

+

Classes

+ + + + + + +

QuadraticPSEstimation()

Estimate a power spectrum from a set of KLModes.

+
+
+class draco.analysis.powerspectrum.QuadraticPSEstimation[source]
+

Bases: SingleTask

+

Estimate a power spectrum from a set of KLModes.

+
+
+psname
+

Name of power spectrum to use. Must be precalculated in the driftscan +products.

+
+
Type:
+

str

+
+
+
+ +
+
+pstype
+

Type of power spectrum estimate to calculate. One of ‘unwindowed’, +‘minimum_variance’ or ‘uncorrelated’.

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(klmodes)[source]
+

Estimate the power spectrum from the given data.

+
+
Parameters:
+

klmodes (containers.KLModes) – KLModes for which to estimate the power spectrum

+
+
Returns:
+

ps

+
+
Return type:
+

containers.PowerSpectrum

+
+
+
+ +
+
+setup(manager)[source]
+

Set the ProductManager instance to use.

+
+
Parameters:
+

manager (ProductManager) – Manager object to use

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.sensitivity.html b/docs/_autosummary/draco.analysis.sensitivity.html new file mode 100644 index 000000000..28b5c7c84 --- /dev/null +++ b/docs/_autosummary/draco.analysis.sensitivity.html @@ -0,0 +1,199 @@ + + + + + + + draco.analysis.sensitivity — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.sensitivity

+

Sensitivity Analysis Tasks.

+

Classes

+ + + + + + +

ComputeSystemSensitivity()

Compute the sensitivity of beamformed visibilities.

+
+
+class draco.analysis.sensitivity.ComputeSystemSensitivity[source]
+

Bases: SingleTask

+

Compute the sensitivity of beamformed visibilities.

+
+
Parameters:
+

exclude_intracyl (bool) – Exclude the intracylinder baselines in the sensitivity estimate. +Default is to use all baselines. Note that a RuntimeError +will be raised if exclude_intracyl is True and the visibilities +have already been stacked over cylinder.

+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Estimate the sensitivity of the input data.

+
+
Parameters:
+

data (TODContainer) – Must have a weight property that contains an +estimate of the inverse variance of the noise +in each visibility. The visibilities can be +stacked to any level of redundancy.

+
+
Returns:
+

metrics – Contains the measured and radiometric estimates of +the noise in the beamformed visibilities.

+
+
Return type:
+

SystemSensitivity

+
+
+
+ +
+
+setup(telescope)[source]
+

Save the telescope model.

+
+
Parameters:
+

telescope (TransitTelescope) – Telescope object to use

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.sidereal.html b/docs/_autosummary/draco.analysis.sidereal.html new file mode 100644 index 000000000..212b75421 --- /dev/null +++ b/docs/_autosummary/draco.analysis.sidereal.html @@ -0,0 +1,515 @@ + + + + + + + draco.analysis.sidereal — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.sidereal

+

Take timestream data and regridding it into sidereal days which can be stacked.

+
+

Usage

+

Generally you would want to use these tasks together. Sending time stream data +into SiderealGrouper, then feeding that into +SiderealRegridder to grid onto each sidereal day, and then into +SiderealStacker if you want to combine the different days.

+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + +

SiderealGrouper()

Group individual timestreams together into whole Sidereal days.

SiderealRegridder()

Take a sidereal days worth of data, and put onto a regular grid.

SiderealRegridderCubic()

Regrid onto the sidereal day using cubic Hermite spline interpolation.

SiderealRegridderLinear()

Regrid onto the sidereal day using linear interpolation.

SiderealRegridderNearest()

Regrid onto the sidereal day using nearest neighbor interpolation.

SiderealStacker()

Take in a set of sidereal days, and stack them up.

SiderealStackerMatch()

Take in a set of sidereal days, and stack them up.

+
+
+class draco.analysis.sidereal.SiderealGrouper[source]
+

Bases: SingleTask

+

Group individual timestreams together into whole Sidereal days.

+
+
+padding
+

Extra amount of a sidereal day to pad each timestream by. Useful for +getting rid of interpolation artifacts.

+
+
Type:
+

float

+
+
+
+ +
+
+offset
+

Time in seconds to subtract before determining the LSD. Useful if the +desired output is not a full sideral stream, but rather a narrow window +around source transits on different sideral days. In that case, one +should set this quantity to 240 * (source_ra - 180).

+
+
Type:
+

float

+
+
+
+ +
+
+min_day_length
+

Require at least this fraction of a full sidereal day to process.

+
+
Type:
+

float

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(tstream)[source]
+

Load in each sidereal day.

+
+
Parameters:
+

tstream (containers.TimeStream) – Timestream to group together.

+
+
Returns:
+

ts – Returns the timestream of each sidereal day when we have received +the last file, otherwise returns None.

+
+
Return type:
+

containers.TimeStream or None

+
+
+
+ +
+
+process_finish()[source]
+

Return the final sidereal day.

+
+
Returns:
+

ts – Returns the timestream of the final sidereal day if it’s long +enough, otherwise returns None.

+
+
Return type:
+

containers.TimeStream or None

+
+
+
+ +
+
+setup(manager)[source]
+

Set the local observers position.

+
+
Parameters:
+

manager (Observer) – An Observer object holding the geographic location of the telescope. +Note that TransitTelescope instances are also +Observers.

+
+
+
+ +
+ +
+
+class draco.analysis.sidereal.SiderealRegridder[source]
+

Bases: Regridder

+

Take a sidereal days worth of data, and put onto a regular grid.

+

Uses a maximum-likelihood inverse of a Lanczos interpolation to do the +regridding. This gives a reasonably local regridding, that is pretty well +behaved in m-space.

+
+
+samples
+

Number of samples across the sidereal day.

+
+
Type:
+

int

+
+
+
+ +
+
+lanczos_width
+

Width of the Lanczos interpolation kernel.

+
+
Type:
+

int

+
+
+
+ +
+
+snr_cov
+

Ratio of signal covariance to noise covariance (used for Wiener filter).

+
+
Type:
+

float

+
+
+
+ +
+
+down_mix
+

Down mix the visibility prior to interpolation using the fringe rate +of a source at zenith. This is un-done after the interpolation.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Regrid the sidereal day.

+
+
Parameters:
+

data (containers.TimeStream) – Timestream data for the day (must have a LSD attribute).

+
+
Returns:
+

sdata – The regularly gridded sidereal timestream.

+
+
Return type:
+

containers.SiderealStream

+
+
+
+ +
+
+setup(manager)[source]
+

Set the local observers position.

+
+
Parameters:
+

manager (Observer) – An Observer object holding the geographic location of the telescope. +Note that TransitTelescope instances are also +Observers.

+
+
+
+ +
+ +
+
+class draco.analysis.sidereal.SiderealRegridderCubic[source]
+

Bases: SiderealRegridder

+

Regrid onto the sidereal day using cubic Hermite spline interpolation.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.sidereal.SiderealRegridderLinear[source]
+

Bases: SiderealRegridder

+

Regrid onto the sidereal day using linear interpolation.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.sidereal.SiderealRegridderNearest[source]
+

Bases: SiderealRegridder

+

Regrid onto the sidereal day using nearest neighbor interpolation.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.sidereal.SiderealStacker[source]
+

Bases: SingleTask

+

Take in a set of sidereal days, and stack them up.

+

Also computes the variance over sideral days using an +algorithm that updates the sum of square differences from +the current mean, which is less prone to numerical issues. +See West, D.H.D. (1979). https://doi.org/10.1145/359146.359153.

+
+
+tag
+

The tag to give the stack.

+
+
Type:
+

str (default: “stack”)

+
+
+
+ +
+
+weight
+

The weighting to use in the stack. +Either uniform or inverse_variance.

+
+
Type:
+

str (default: “inverse_variance”)

+
+
+
+ +
+
+with_sample_variance
+

Add a dataset containing the sample variance +of the visibilities over sidereal days to the +sidereal stack.

+
+
Type:
+

bool (default: False)

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sdata)[source]
+

Stack up sidereal days.

+
+
Parameters:
+

sdata (containers.SiderealStream) – Individual sidereal day to add to stack.

+
+
+
+ +
+
+process_finish()[source]
+

Normalize the stack and return the result.

+
+
Returns:
+

stack – Stack of sidereal days.

+
+
Return type:
+

containers.SiderealStream

+
+
+
+ +
+ +
+
+class draco.analysis.sidereal.SiderealStackerMatch[source]
+

Bases: SingleTask

+

Take in a set of sidereal days, and stack them up.

+

This treats the time average of each input sidereal stream as an extra source of +noise and uses a Wiener filter approach to consistent stack the individual streams +together while accounting for their distinct coverage in RA. In practice this is +used for co-adding stacks with different sidereal coverage while marginalising out +the effects of the different cross talk contributions that each input stream may +have.

+

There is no uniquely correct solution for the sidereal average (or m=0 mode) of the +output stream. This task fixes this unknown mode by setting the median of each 24 +hour period to zero. Note this is not the same as setting the m=0 mode to be zero.

+
+
Parameters:
+

tag (str) – The tag to give the stack.

+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sdata)[source]
+

Stack up sidereal days.

+
+
Parameters:
+

sdata (containers.SiderealStream) – Individual sidereal day to stack up.

+
+
+
+ +
+
+process_finish()[source]
+

Construct and emit sidereal stack.

+
+
Returns:
+

stack – Stack of sidereal days.

+
+
Return type:
+

containers.SiderealStream

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.sourcestack.html b/docs/_autosummary/draco.analysis.sourcestack.html new file mode 100644 index 000000000..4e12a885c --- /dev/null +++ b/docs/_autosummary/draco.analysis.sourcestack.html @@ -0,0 +1,329 @@ + + + + + + + draco.analysis.sourcestack — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.sourcestack

+

Source Stack Analysis Tasks.

+

Classes

+ + + + + + + + + + + + +

GroupSourceStacks()

Accumulate many frequency stacks into a single container.

RandomSubset()

Take a large mock catalog and draw number catalogs of a given size.

SourceStack()

Stack the product of draco.analysis.BeamForm accross sources.

+
+
+class draco.analysis.sourcestack.GroupSourceStacks[source]
+

Bases: SingleTask

+

Accumulate many frequency stacks into a single container.

+
+
+ngroup
+

The number of frequency stacks to accumulate into a +single container.

+
+
Type:
+

int

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(stack)[source]
+

Add a FrequencyStack to the list.

+

As soon as list contains ngroup items, they will be collapsed +into a single container and output by the task.

+
+
Parameters:
+

stack (containers.FrequencyStack, containers.FrequencyStackByPol,) – containers.MockFrequencyStack, containers.MockFrequencyStackByPol

+
+
Returns:
+

out – The previous ngroup FrequencyStacks accumulated into a single container.

+
+
Return type:
+

containers.MockFrequencyStack, containers.MockFrequencyStackByPol

+
+
+
+ +
+
+process_finish()[source]
+

Return whatever FrequencyStacks are currently in the list.

+
+
Returns:
+

out – The remaining frequency stacks accumulated into a single container.

+
+
Return type:
+

containers.MockFrequencyStack, containers.MockFrequencyStackByPol

+
+
+
+ +
+
+setup()[source]
+

Create a list to be populated by the process method.

+
+ +
+ +
+
+class draco.analysis.sourcestack.RandomSubset[source]
+

Bases: SingleTask, RandomTask

+

Take a large mock catalog and draw number catalogs of a given size.

+
+
+number
+

Number of catalogs to construct.

+
+
Type:
+

int

+
+
+
+ +
+
+size
+

Number of objects in each catalog.

+
+
Type:
+

int

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Draw a new random catalog.

+
+
Returns:
+

new_catalog – A catalog of the same type as the input catalog, with a random set of +objects.

+
+
Return type:
+

containers.SourceCatalog or containers.FormedBeam

+
+
+
+ +
+
+setup(catalog)[source]
+

Set the full mock catalog.

+
+
Parameters:
+

catalog (containers.SourceCatalog or containers.FormedBeam) – The mock catalog to draw from.

+
+
+
+ +
+ +
+
+class draco.analysis.sourcestack.SourceStack[source]
+

Bases: SingleTask

+

Stack the product of draco.analysis.BeamForm accross sources.

+

For this to work BeamForm must have been run with collapse_ha = True (default).

+
+
+freqside
+

Number of frequency bins to keep on each side of source bin +when stacking. Default: 50.

+
+
Type:
+

int

+
+
+
+ +
+
+single_source_bin_index
+

Only stack on sources in frequency bin with this index. +Useful for isolating stacking signal from a narrow frequency range. +Default: None.

+
+
Type:
+

int, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(formed_beam)[source]
+

Receives a formed beam object and stack across sources.

+
+
Parameters:
+

formed_beam (containers.FormedBeam object) – Formed beams to stack over sources.

+
+
Returns:
+

stack – The stack of sources.

+
+
Return type:
+

containers.FrequencyStack object

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.svdfilter.html b/docs/_autosummary/draco.analysis.svdfilter.html new file mode 100644 index 000000000..02792389b --- /dev/null +++ b/docs/_autosummary/draco.analysis.svdfilter.html @@ -0,0 +1,288 @@ + + + + + + + draco.analysis.svdfilter — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.svdfilter

+

A set of tasks for SVD filtering the m-modes.

+

Functions

+ + + + + + +

svd_em(A, mask[, niter, rank, full_matrices])

Perform an SVD with missing entries using Expectation-Maximisation.

+

Classes

+ + + + + + + + + +

SVDFilter()

SVD filter the m-modes to remove the most correlated components.

SVDSpectrumEstimator()

Calculate the SVD spectrum of a set of m-modes.

+
+
+class draco.analysis.svdfilter.SVDFilter[source]
+

Bases: SingleTask

+

SVD filter the m-modes to remove the most correlated components.

+
+
+niter
+

Number of iterations of EM to perform.

+
+
Type:
+

int

+
+
+
+ +
+
+local_threshold
+

Cut out modes with singular value higher than local_threshold times the +largest mode on each m.

+
+
Type:
+

float

+
+
+
+ +
+
+global_threshold
+

Remove modes with singular value higher than global_threshold times the +largest mode on any m

+
+
Type:
+

float

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(mmodes)[source]
+

Filter MModes using an SVD.

+
+
Parameters:
+

mmodes (container.MModes) – MModes to process

+
+
Returns:
+

mmodes

+
+
Return type:
+

container.MModes

+
+
+
+ +
+ +
+
+class draco.analysis.svdfilter.SVDSpectrumEstimator[source]
+

Bases: SingleTask

+

Calculate the SVD spectrum of a set of m-modes.

+
+
+niter
+

Number of iterations of EM to perform.

+
+
Type:
+

int

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(mmodes)[source]
+

Calculate the spectrum.

+
+
Parameters:
+

mmodes (containers.MModes) – MModes to find the spectrum of.

+
+
Returns:
+

spectrum

+
+
Return type:
+

containers.SVDSpectrum

+
+
+
+ +
+ +
+
+draco.analysis.svdfilter.svd_em(A, mask, niter=5, rank=5, full_matrices=False)[source]
+

Perform an SVD with missing entries using Expectation-Maximisation.

+

This assumes that the matrix is well approximated by only a few modes in +order fill the missing entries. This is probably not a proper EM scheme, but +is not far off.

+
+
Parameters:
+
    +
  • A (np.ndarray) – Matrix to SVD.

  • +
  • mask (np.ndarray) – Boolean array of masked values. Missing values are True.

  • +
  • niter (int, optional) – Number of iterations to perform.

  • +
  • rank (int, optional) – Set the rank of the approximation used to fill the missing values.

  • +
  • full_matrices (bool, optional) – Return the full span of eigenvectors and values (see scipy.linalg.svd +for a fuller description).

  • +
+
+
Returns:
+

u, sig, vh – The singular values and vectors.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.analysis.transform.html b/docs/_autosummary/draco.analysis.transform.html new file mode 100644 index 000000000..2afda9e32 --- /dev/null +++ b/docs/_autosummary/draco.analysis.transform.html @@ -0,0 +1,1037 @@ + + + + + + + draco.analysis.transform — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.analysis.transform

+

Miscellaneous transformations to do on data.

+

This includes grouping frequencies and products to performing the m-mode transform.

+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

CollateProducts()

Extract and order the correlation products for map-making.

Downselect()

Apply axis selections to a container.

FrequencyRebin()

Rebin neighbouring frequency channels.

HPFTimeStream()

High pass filter a timestream.

MModeInverseTransform()

Transform m-modes to sidereal stream.

MModeTransform()

Transform a sidereal stream to m-modes.

MixData()

Mix together pieces of data with specified weights.

ReduceBase()

Apply a weighted reduction operation across specific axes.

ReduceVar()

Take the weighted variance of a container.

Regridder()

Interpolate time-ordered data onto a regular grid.

SelectFreq()

Select a subset of frequencies from a container.

SelectPol()

Extract a subset of polarisations, including Stokes parameters.

ShiftRA()

Add a shift to the RA axis.

SiderealMModeResample()

Resample a sidereal stream by FFT.

TransformJanskyToKelvin()

Task to convert from Jy to Kelvin and vice-versa.

+
+
+class draco.analysis.transform.CollateProducts[source]
+

Bases: SingleTask

+

Extract and order the correlation products for map-making.

+

The task will take a sidereal task and format the products that are needed +or the map-making. It uses a BeamTransfer instance to figure out what these +products are, and how they should be ordered. It similarly selects only the +required frequencies.

+

It is important to note that while the input +SiderealStream can contain more feeds and frequencies +than are contained in the BeamTransfers, the converse is not true. That is, +all the frequencies and feeds that are in the BeamTransfers must be found in +the timestream object.

+
+
Parameters:
+

weight (string ('natural', 'uniform', or 'inverse_variance')) –

+
How to weight the redundant baselines when stacking:

’natural’ - each baseline weighted by its redundancy (default) +‘uniform’ - each baseline given equal weight +‘inverse_variance’ - each baseline weighted by the weight attribute

+
+
+

+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(ss: SiderealStream) SiderealStream[source]
+
+process(ss: TimeStream) TimeStream
+

Select and reorder the products.

+
+
Parameters:
+

ss – Data with products

+
+
Returns:
+

Dataset containing only the required products.

+
+
Return type:
+

sp

+
+
+
+ +
+
+setup(tel)[source]
+

Set the Telescope instance to use.

+
+
Parameters:
+

tel (TransitTelescope) – Telescope object to use

+
+
+
+ +
+ +
+
+class draco.analysis.transform.Downselect[source]
+

Bases: SelectionsMixin, SingleTask

+

Apply axis selections to a container.

+

Apply slice or np.take operations across multiple axes of a container. +The selections are applied to every dataset.

+

If a dataset is distributed, there must be at least one axis not included +in the selections.

+
+
+process(data: ContainerBase) ContainerBase[source]
+

Apply downselections to the container.

+
+
Parameters:
+

data – Container to process

+
+
Returns:
+

Container of same type as the input with specific axis selections. +Any datasets not included in the selections will not be initialized.

+
+
Return type:
+

out

+
+
+
+ +
+ +
+
+class draco.analysis.transform.FrequencyRebin[source]
+

Bases: SingleTask

+

Rebin neighbouring frequency channels.

+
+
Parameters:
+

channel_bin (int) – Number of channels to in together.

+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(ss)[source]
+

Take the input dataset and rebin the frequencies.

+
+
Parameters:
+

ss (containers.SiderealStream or containers.TimeStream) – Input data to rebin. Can also be an andata.CorrData instance, +however the output will be a containers.TimeStream instance.

+
+
Returns:
+

sb – Rebinned data. Type should match the input.

+
+
Return type:
+

containers.SiderealStream or containers.TimeStream

+
+
+
+ +
+ +
+
+class draco.analysis.transform.HPFTimeStream[source]
+

Bases: SingleTask

+

High pass filter a timestream.

+

This is done by solving for a low-pass filtered version of the timestream and then +subtracting it from the original.

+
+
Parameters:
+
    +
  • tau – Timescale in seconds to filter out fluctuations below.

  • +
  • pad – Implicitly pad the timestream with this many multiples of tau worth of zeros. +This is used to mitigate edge effects. The default is 2.

  • +
  • window – Use a Blackman window when determining the low-pass filtered timestream. When +applied this approximately doubles the length of the timescale, which is only +crudely corrected for.

  • +
  • prior – This should be approximately the size of the large scale fluctuations that we +will use as a regulariser.

  • +
+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(tstream: TODContainer) TODContainer[source]
+

High pass filter a time stream.

+
+
Parameters:
+

tstream – A TOD container that also implements DataWeightContainer.

+
+
Returns:
+

The high-pass filtered time stream.

+
+
Return type:
+

filtered_tstream

+
+
+
+ +
+ +
+
+class draco.analysis.transform.MModeInverseTransform[source]
+

Bases: SingleTask

+

Transform m-modes to sidereal stream.

+

Currently ignores any noise weighting.

+
+

Warning

+

Using apply_integration_window will modify the input mmodes.

+
+
+
+nra
+

Number of RA bins in the output. Note that if the number of samples does not +Nyquist sample the maximum m, information may be lost. If not set, then try to +get from an original_nra attribute on the incoming MModes, otherwise determine +an appropriate number of RA bins from the mmax.

+
+
Type:
+

int

+
+
+
+ +
+
+apply_integration_window
+

Apply the effect of the finite width of the RA integration (presuming a +rectangular integration window). This is applied to both the visibilities and +the weights. If this is true, as a side effect the input data will be modified +in place.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(mmodes: MContainer) SiderealContainer[source]
+

Perform the m-mode inverse transform.

+
+
Parameters:
+

mmodes (containers.MModes) – The input m-modes.

+
+
Returns:
+

sstream – The output sidereal stream.

+
+
Return type:
+

containers.SiderealStream

+
+
+
+ +
+ +
+
+class draco.analysis.transform.MModeTransform[source]
+

Bases: SingleTask

+

Transform a sidereal stream to m-modes.

+

Currently ignores any noise weighting.

+

The maximum m used in the container is derived from the number of +time samples, or if a manager is supplied telescope.mmax is used.

+
+
+remove_integration_window
+

Deconvolve the effect of the finite width of the RA integration (presuming it +was a rectangular integration window). This is applied to both the visibilities +and the weights.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sstream: SiderealContainer) MContainer[source]
+

Perform the m-mode transform.

+
+
Parameters:
+

sstream (containers.SiderealStream or containers.HybridVisStream) – The input sidereal stream.

+
+
Returns:
+

mmodes

+
+
Return type:
+

containers.MModes

+
+
+
+ +
+
+setup(manager: ProductManager | BeamTransfer | TransitTelescope | None = None)[source]
+

Set the telescope instance if a manager object is given.

+

This is used to set the mmax used in the transform.

+
+
Parameters:
+

manager (manager.ProductManager, optional) – The telescope/manager used to set the mmax. If not set, mmax +is derived from the timestream.

+
+
+
+ +
+ +
+
+class draco.analysis.transform.MixData[source]
+

Bases: SingleTask

+

Mix together pieces of data with specified weights.

+

This can generate arbitrary linear combinations of the data and weights for both +SiderealStream and RingMap objects, and can be used for many purposes such as: +adding together simulated timestreams, injecting signal into data, replacing weights +in simulated data with those from real data, etc.

+

All coefficients are applied naively to generate the final combinations, i.e. no +normalisations or weighted summation is performed.

+
+
+data_coeff
+

A list of coefficients to apply to the data dataset of each input containter to +produce the final output. These are applied to either the vis or map dataset +depending on the the type of the input container.

+
+
Type:
+

list

+
+
+
+ +
+
+weight_coeff
+

Coefficient to be applied to each input containers weights to generate the +output.

+
+
Type:
+

list

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data: SiderealStream | RingMap)[source]
+

Add the input data into the mixed data output.

+
+
Parameters:
+

data – The data to be added into the mix.

+
+
+
+ +
+
+process_finish() SiderealStream | RingMap[source]
+

Return the container with the mixed inputs.

+
+
Returns:
+

The mixed data.

+
+
Return type:
+

mixed_data

+
+
+
+ +
+
+setup()[source]
+

Check the lists have the same length.

+
+ +
+ +
+
+class draco.analysis.transform.ReduceBase[source]
+

Bases: SingleTask

+

Apply a weighted reduction operation across specific axes.

+

This is non-functional without overriding the reduction method.

+

There must be at least one axis not included in the reduction.

+
+
+axes
+

Axis names to apply the reduction to

+
+
Type:
+

list

+
+
+
+ +
+
+dataset
+

Dataset name to reduce.

+
+
Type:
+

str

+
+
+
+ +
+
+weighting
+

Which type of weighting to use, if applicable. Options are “none”, +“masked”, or “weighted”

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data: ContainerBase) ContainerBase[source]
+

Downselect and apply the reduction operation to the data.

+
+
Parameters:
+

data – Dataset to process.

+
+
Returns:
+

Dataset of same type as input with axes reduced. Any datasets +which are not included in the reduction list will not be initialized, +other than weights.

+
+
Return type:
+

out

+
+
+
+ +
+
+reduction(arr: ndarray, weight: ndarray, axis: tuple) Tuple[ndarray, ndarray][source]
+

Overwrite to implement the reductino operation.

+
+ +
+ +
+
+class draco.analysis.transform.ReduceVar[source]
+

Bases: ReduceBase

+

Take the weighted variance of a container.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+reduction(arr, weight, axis)[source]
+

Apply a weighted variance.

+
+ +
+ +
+
+class draco.analysis.transform.Regridder[source]
+

Bases: SingleTask

+

Interpolate time-ordered data onto a regular grid.

+

Uses a maximum-likelihood inverse of a Lanczos interpolation to do the +regridding. This gives a reasonably local regridding, that is pretty well +behaved in m-space.

+
+
+samples
+

Number of samples to interpolate onto.

+
+
Type:
+

int

+
+
+
+ +
+
+start
+

Start of the interpolated samples.

+
+
Type:
+

float

+
+
+
+ +
+
+end
+

End of the interpolated samples.

+
+
Type:
+

float

+
+
+
+ +
+
+lanczos_width
+

Width of the Lanczos interpolation kernel.

+
+
Type:
+

int

+
+
+
+ +
+
+snr_cov
+

Ratio of signal covariance to noise covariance (used for Wiener filter).

+
+
Type:
+

float

+
+
+
+ +
+
+mask_zero_weight
+

Mask the output noise weights at frequencies where the weights were +zero for all time samples.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Regrid visibility data in the time direction.

+
+
Parameters:
+

data (containers.TODContainer) – Time-ordered data.

+
+
Returns:
+

new_data – The regularly gridded interpolated timestream.

+
+
Return type:
+

containers.TODContainer

+
+
+
+ +
+
+setup(observer)[source]
+

Set the local observers position.

+
+
Parameters:
+

observer (Observer) – An Observer object holding the geographic location of the telescope. +Note that TransitTelescope instances are also +Observers.

+
+
+
+ +
+ +
+
+class draco.analysis.transform.SelectFreq[source]
+

Bases: SingleTask

+

Select a subset of frequencies from a container.

+
+
+freq_physical
+

List of physical frequencies in MHz. +Given first priority.

+
+
Type:
+

list

+
+
+
+ +
+
+channel_range
+

Range of frequency channel indices, either +[start, stop, step], [start, stop], or [stop] +is acceptable. Given second priority.

+
+
Type:
+

list

+
+
+
+ +
+
+channel_index
+

List of frequency channel indices. +Given third priority.

+
+
Type:
+

list

+
+
+
+ +
+
+freq_physical_range
+

Range of physical frequencies to include given as (low_freq, high_freq). +Given fourth priority.

+
+
Type:
+

list

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Selet a subset of the frequencies.

+
+
Parameters:
+

data (containers.ContainerBase) – A data container with a frequency axis.

+
+
Returns:
+

newdata – New container with trimmed frequencies.

+
+
Return type:
+

containers.ContainerBase

+
+
+
+ +
+ +
+
+class draco.analysis.transform.SelectPol[source]
+

Bases: SingleTask

+

Extract a subset of polarisations, including Stokes parameters.

+

This currently only extracts Stokes I.

+
+
+pol
+

Polarisations to extract. Only Stokes I extraction is supported (i.e. pol = +[“I”]).

+
+
Type:
+

list

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(polcont)[source]
+

Extract the specified polarisation from the input.

+

This will combine polarisation pairs to get instrumental Stokes polarisations if +requested.

+
+
Parameters:
+

polcont (ContainerBase) – A container with a polarisation axis.

+
+
Returns:
+

selectedpolcont – A new container with the selected polarisation.

+
+
Return type:
+

same as polcont

+
+
+
+ +
+ +
+
+class draco.analysis.transform.ShiftRA[source]
+

Bases: SingleTask

+

Add a shift to the RA axis.

+

This is useful for fixing a bug in earlier revisions of CHIME processing.

+
+
Parameters:
+
    +
  • delta (float) – The shift to add to the RA axis.

  • +
  • periodic (bool, optional) – If True, wrap any time sample that is shifted to RA > 360 deg around to its +360-degree-periodic counterpart, and likewise for any sample that is shifted +to RA < 0 deg. This wrapping is applied to the RA index_map along with any +dataset with an ra axis. Default: False.

  • +
+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sscont: SiderealContainer) SiderealContainer[source]
+

Add a shift to the input sidereal container.

+
+
Parameters:
+

sscont – The container to shift. The input is modified in place.

+
+
Returns:
+

The shifted container.

+
+
Return type:
+

sscont

+
+
+
+ +
+ +
+
+class draco.analysis.transform.SiderealMModeResample[source]
+

Bases: TaskGroup

+

Resample a sidereal stream by FFT.

+

This performs a forward and inverse m-mode transform to resample a sidereal stream.

+
+
+nra
+

The number of RA bins for the output stream.

+
+
Type:
+

int

+
+
+
+ +
+
+remove_integration_window, apply_integration_window
+

Remove the integration window from the incoming data, and/or apply it to the +output sidereal stream.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.analysis.transform.TransformJanskyToKelvin[source]
+

Bases: SingleTask

+

Task to convert from Jy to Kelvin and vice-versa.

+

This integrates over the primary beams in the telescope class to derive the +brightness temperature to flux conversion.

+
+
+convert_Jy_to_K
+

If True, apply a Jansky to Kelvin conversion factor. If False apply a Kelvin to +Jansky conversion.

+
+
Type:
+

bool

+
+
+
+ +
+
+reference_declination
+

The declination to set the flux reference for. A source transiting at this +declination will produce a visibility signal equal to its flux. If None +(default) use the zenith.

+
+
Type:
+

float, optional

+
+
+
+ +
+
+share
+

Which datasets should the output share with the input. Default is “all”.

+
+
Type:
+

{“none”, “all”}

+
+
+
+ +
+
+nside
+

The NSIDE to use for the primary beam area calculation. This may need to be +increased for beams with intricate small scale structure. Default is 256.

+
+
Type:
+

int

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sstream: SiderealStream) SiderealStream[source]
+

Apply the brightness temperature to flux conversion to the data.

+
+
Parameters:
+

sstream – The visibilities to apply the conversion to. They are converted to/from +brightness temperature units depending on the setting of convert_Jy_to_K.

+
+
Returns:
+

Visibilities with the conversion applied. This may be the same as the input +container if share == “all”.

+
+
Return type:
+

new_sstream

+
+
+
+ +
+
+setup(telescope: ProductManager | BeamTransfer | TransitTelescope)[source]
+

Set the telescope object.

+
+
Parameters:
+

telescope – An object we can get a telescope object from. This telescope must be able to +calculate the beams at all incoming frequencies.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.core.containers.html b/docs/_autosummary/draco.core.containers.html new file mode 100644 index 000000000..cb95cd82f --- /dev/null +++ b/docs/_autosummary/draco.core.containers.html @@ -0,0 +1,1827 @@ + + + + + + + draco.core.containers — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.core.containers

+

Distributed containers for holding various types of analysis data.

+
+

Containers

+ +
+

Container Base Classes

+ +
+
+

Helper Routines

+

These routines are designed to be replaced by other packages trying to insert +their own custom container types.

+ +
+
+

Functions

+ + + + + + + + + + + + +

copy_datasets_filter(source, dest[, axis, ...])

Copy datasets while filtering a given axis.

empty_like(obj, **kwargs)

Create an empty container like obj.

empty_timestream(**kwargs)

Create a new timestream container.

+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

BaselineMask(*args, **kwargs)

A container for holding a baseline-dependent mask for a timestream.

CommonModeGainData(*args, **kwargs)

Parallel container for holding gain data common to all inputs.

CommonModeSiderealGainData([ra])

Parallel container for holding sidereal gain data common to all inputs.

ContainerBase(*args, **kwargs)

A base class for pipeline containers.

DataWeightContainer(*args, **kwargs)

A base class for containers with generic data/weight datasets.

DelayContainer(*args, **kwargs)

A container with a delay axis.

DelayCrossSpectrum(*args[, weight_boost, sample])

Container for a delay cross power spectra.

DelayCutoff(*args, **kwargs)

Container for a delay cutoff.

DelaySpectrum(*args[, weight_boost, sample])

Container for a delay power spectrum.

DelayTransform([weight_boost])

Container for a delay spectrum.

FormedBeam(*args, **kwargs)

Container for formed beams.

FormedBeamHA(*args, **kwargs)

Container for formed beams.

FormedBeamHAMask(*args, **kwargs)

Mask bad formed beams as a function of hour angle.

FormedBeamMask(*args, **kwargs)

Mask bad formed beams.

FreqContainer(*args, **kwargs)

A pipeline container for data with a frequency axis.

FrequencyStack(*args, **kwargs)

Container for a frequency stack.

FrequencyStackByPol(*args, **kwargs)

Container for a frequency stack split by polarisation.

GainData(*args, **kwargs)

Parallel container for holding gain data.

GainDataBase(*args, **kwargs)

A container interface for gain-like data.

GridBeam([coords])

Generic container for representing a 2D beam on a rectangular grid.

HEALPixBeam([coords, ordering])

Container for representing the spherical 2-d beam in a HEALPix grid.

HealpixContainer([nside])

Base class container for holding Healpix map data.

HybridVisMModes([mmax, oddra])

Visibilities beamformed in the NS direction and m-mode transformed in RA.

HybridVisStream([ra])

Visibilities beamformed only in the NS direction.

KLModes([mmax, oddra])

Parallel container for holding KL filtered m-mode data.

MContainer([mmax, oddra])

Container for holding m-mode type data.

MModes(*args, **kwargs)

Parallel container for holding m-mode data.

Map([polarisation])

Container for holding multi-frequency sky maps.

MockFrequencyStack(*args, **kwargs)

Container for holding a frequency stack for multiple mock catalogs.

MockFrequencyStackByPol(*args, **kwargs)

Container for holding a frequency stack split by pol for multiple mock catalogs.

Powerspectrum2D([kperp_edges, kpar_edges])

Container for a 2D cartesian power spectrum.

RFIMask(*args, **kwargs)

A container for holding an RFI mask for a timestream.

RingMap([ra])

Container for holding multifrequency ring maps.

RingMapMask([ra])

Mask bad ringmap pixels.

SVDModes([mmax, oddra])

Parallel container for holding SVD m-mode data.

SVDSpectrum(*args, **kwargs)

Container for an m-mode SVD spectrum.

SampleVarianceContainer(*args, **kwargs)

Base container for holding the sample variance over observations.

SiderealBaselineMask([ra])

A container for holding a baseline-dependent mask for a sidereal stream.

SiderealContainer([ra])

A pipeline container for data with an RA axis.

SiderealGainData([ra])

Parallel container for holding sidereal gain data.

SiderealRFIMask([ra])

A container for holding an RFI mask for a sidereal stream.

SiderealStream(*args, **kwargs)

A container for holding a visibility dataset in sidereal time.

SourceCatalog(*args, **kwargs)

A basic container for holding astronomical source catalogs.

SpectroscopicCatalog(*args, **kwargs)

A container for spectroscopic catalogs.

Stack3D(*args, **kwargs)

Container for a 3D frequency stack.

StaticGainData(*args, **kwargs)

Parallel container for holding static gain data (i.e. non time varying).

SystemSensitivity(*args, **kwargs)

A container for holding the total system sensitivity.

TODContainer(*args, **kwargs)

A pipeline container for time ordered data.

TableBase(*args, **kwargs)

A base class for containers holding tables of data.

TimeStream(*args, **kwargs)

A container for holding a visibility dataset in time.

TrackBeam([theta, phi, coords, track_type])

Container for a sequence of beam samples at arbitrary locations on the sphere.

VisBase(*args, **kwargs)

A very basic class for visibility data.

VisContainer(*args, **kwargs)

A base container for holding a visibility dataset.

VisGridStream([ra])

Visibilities gridded into a 2D array.

WaveletSpectrum(*args, **kwargs)

Container for a wavelet power spectrum.

+
+
+class draco.core.containers.BaselineMask(*args, **kwargs)[source]
+

Bases: FreqContainer, TODContainer

+

A container for holding a baseline-dependent mask for a timestream.

+

The mask is True for contaminated samples that should be excluded, and +False for clean samples.

+

Unlike RFIMask, this is distributed by default.

+
+
+property mask
+

Get the mask dataset.

+
+ +
+
+property stack
+

The stack definition as an index (and conjugation) of a member product.

+
+ +
+ +
+
+class draco.core.containers.CommonModeGainData(*args, **kwargs)[source]
+

Bases: FreqContainer, TODContainer, GainDataBase

+

Parallel container for holding gain data common to all inputs.

+
+ +
+
+class draco.core.containers.CommonModeSiderealGainData(ra=None, *args, **kwargs)[source]
+

Bases: FreqContainer, SiderealContainer, GainDataBase

+

Parallel container for holding sidereal gain data common to all inputs.

+
+ +
+
+class draco.core.containers.ContainerBase(*args, **kwargs)[source]
+

Bases: BasicCont

+

A base class for pipeline containers.

+

This class is designed to do much of the work of setting up pipeline +containers. It should be derived from, and two variables set _axes and +_dataset_spec. See the Notes section for details.

+
+
Parameters:
+
    +
  • data_group (memh5.MemDiskGroup) – A container to pass through for making a shallow copy. This is used by +routine like caput.tod.concatenate and generally shouldn’t be used +directly. Either a keyword argument, or the first positional argument.

  • +
  • axes_from (memh5.BasicCont, optional) – Another container to copy axis definitions from. Must be supplied as +keyword argument.

  • +
  • attrs_from (memh5.BasicCont, optional) – Another container to copy attributes from. Must be supplied as keyword +argument. This applies to attributes in default datasets too.

  • +
  • dsets_from (memh5.BasicCont, optional) – A container to copy datasets from. Any dataset which an axis whose definition +has been explicitly set (i.e. does not come from axes_from) will not be +copied.

  • +
  • copy_from (memh5.BasicCont, optional) – Set axes_from, attrs_from and dsets_from to this instance if they are +not set explicitly.

  • +
  • skip_datasets (bool, optional) – Skip creating datasets. They must all be added manually with +.add_dataset regardless of the entry in .dataset_spec. Default is False.

  • +
  • distributed (bool, optional) – Should this be a distributed container. Defaults to True.

  • +
  • comm (mpi4py.MPI.Comm, optional) – The MPI communicator to distribute over. Use COMM_WORLD if not set.

  • +
  • allow_chunked (bool, optional) – Allow the datasets to be chunked. Default is True.

  • +
  • kwargs (dict) – Should contain entries for all other axes.

  • +
+
+
+

Notes

+

Inheritance from other ContainerBase subclasses should work as expected, +with datasets defined in super classes appearing as expected, and being +overridden where they are redefined in the derived class.

+

The variable _axes should be a tuple containing the names of axes that +datasets in this container will use.

+

The variable _dataset_spec should define the datasets. It’s a dictionary +with the name of the dataset as key. Each entry should be another +dictionary, the entry ‘axes’ is mandatory and should be a list of the axes +the dataset has (these should correspond to entries in _axes), as is +dtype which should be a datatype understood by numpy. Other possible +entries are:

+
    +
  • initialise : if set to True the dataset will be created as the +container is initialised.

  • +
  • distributed : the dataset will be distributed if the entry is True, if +False it won’t be, and if not set it will be distributed if the +container is set to be.

  • +
  • distributed_axis : the axis to distribute over. Should be a name given +in the axes entry.

  • +
+
+
+add_dataset(name)[source]
+

Create an empty dataset.

+

The dataset must be defined in the specification for the container.

+
+
Parameters:
+

name (string) – Name of the dataset to create.

+
+
Returns:
+

dset

+
+
Return type:
+

memh5.MemDataset

+
+
+
+ +
+
+property axes
+

The set of axes for this container including any defined on the instance.

+
+ +
+
+copy(shared=None)[source]
+

Copy this container, optionally sharing the source datasets.

+

This routine will create a copy of the container. By default this is +as full copy with the contents fully independent. However, a set of +dataset names can be given that will share the same data as the +source to save memory for large datasets. These will just view the +same memory, so any modification to either the original or the copy +will be visible to the other. This includes all write operations, +addition and removal of attributes, redistribution etc. This +functionality should be used with caution and clearly documented.

+
+
Parameters:
+

shared (list, optional) – A list of datasets whose content will be shared with the original.

+
+
Returns:
+

copy – The copied container.

+
+
Return type:
+

subclass of ContainerBase

+
+
+
+ +
+
+property dataset_spec
+

Return a copy of the fully resolved dataset specifiction as a dictionary.

+
+ +
+
+property datasets
+

Return the datasets in this container.

+

Do not try to add a new dataset by assigning to an item of this +property. Use create_dataset instead.

+
+
Returns:
+

datasets – Entries are caput.memh5 datasets.

+
+
Return type:
+

read only dictionary

+
+
+
+ +
+ +
+
+class draco.core.containers.DataWeightContainer(*args, **kwargs)[source]
+

Bases: ContainerBase

+

A base class for containers with generic data/weight datasets.

+

This is meant such that tasks can operate generically over containers with this +common structure. The data and weight datasets are expected to have the same size, +though this isn’t checked. Subclasses must define _data_dset_name and +_weight_dset_name.

+
+
+property data: MemDataset
+

The main dataset.

+
+ +
+
+property weight: MemDataset
+

The weights for each data point.

+
+ +
+ +
+
+class draco.core.containers.DelayContainer(*args, **kwargs)[source]
+

Bases: ContainerBase

+

A container with a delay axis.

+
+
+property delay: ndarray
+

The delay axis in microseconds.

+
+ +
+ +
+
+class draco.core.containers.DelayCrossSpectrum(*args, weight_boost=1.0, sample=1, **kwargs)[source]
+

Bases: DelaySpectrum

+

Container for a delay cross power spectra.

+
+
+property spectrum
+

Get the spectrum dataset.

+
+ +
+ +
+
+class draco.core.containers.DelayCutoff(*args, **kwargs)[source]
+

Bases: ContainerBase

+

Container for a delay cutoff.

+
+
+property cutoff
+

Get the cutoff dataset.

+
+ +
+
+property el
+

Get the el axis.

+
+ +
+
+property pol
+

Get the pol axis.

+
+ +
+ +
+
+class draco.core.containers.DelaySpectrum(*args, weight_boost=1.0, sample=1, **kwargs)[source]
+

Bases: DelayContainer

+

Container for a delay power spectrum.

+

Notes

+

A note about definitions: for a dataset with a frequency axis, the corresponding +delay spectrum is the result of Fourier transforming in frequency, while the delay +power spectrum is obtained by taking the squared magnitude of each element of the +delay spectrum, and then usually averaging over some other axis. Our unfortunate +convention is to store a delay power spectrum in a DelaySpectrum container, and +store a delay spectrum in a DelayTransform +container.

+
+
+property freq
+

Get the frequency axis of the input data.

+
+ +
+
+property spectrum
+

Get the spectrum dataset.

+
+ +
+
+property weight_boost
+

Get the weight boost factor.

+

If set, this factor was used to set the assumed noise when computing the +spectrum.

+
+ +
+ +
+
+class draco.core.containers.DelayTransform(weight_boost=1.0, *args, **kwargs)[source]
+

Bases: DelayContainer

+

Container for a delay spectrum.

+

Notes

+

See the docstring for DelaySpectrum for a +description of the difference between DelayTransform and DelaySpectrum.

+
+
+property freq
+

Get the frequency axis of the input data.

+
+ +
+
+property spectrum
+

Get the spectrum dataset.

+
+ +
+
+property weight_boost
+

Get the weight boost factor.

+

If set, this factor was used to set the assumed noise when computing the +spectrum.

+
+ +
+ +
+
+class draco.core.containers.FormedBeam(*args, **kwargs)[source]
+

Bases: FreqContainer, DataWeightContainer

+

Container for formed beams.

+
+
+property beam
+

Get the beam dataset.

+
+ +
+
+property frequency
+

Get the frequency axis.

+
+ +
+
+property id
+

Get the object id axis.

+
+ +
+
+property pol
+

Get the pol axis.

+
+ +
+ +
+
+class draco.core.containers.FormedBeamHA(*args, **kwargs)[source]
+

Bases: FormedBeam

+

Container for formed beams.

+

These have not been collapsed in the hour angle (HA) axis.

+
+
+property ha
+

Get the hour angle dataset.

+
+ +
+ +
+
+class draco.core.containers.FormedBeamHAMask(*args, **kwargs)[source]
+

Bases: FormedBeamMask

+

Mask bad formed beams as a function of hour angle.

+
+ +
+
+class draco.core.containers.FormedBeamMask(*args, **kwargs)[source]
+

Bases: FreqContainer

+

Mask bad formed beams.

+
+
+property mask
+

Get the mask dataset.

+
+ +
+ +
+
+class draco.core.containers.FreqContainer(*args, **kwargs)[source]
+

Bases: ContainerBase

+

A pipeline container for data with a frequency axis.

+

This works like a normal ContainerBase container, but already has a freq +axis defined, and specific properties for dealing with frequencies.

+
+
+property freq
+

The physical frequency associated with each entry of the time axis.

+

By convention this property should return the frequency in MHz at the centre +of each of frequency channel.

+
+ +
+ +
+
+class draco.core.containers.FrequencyStack(*args, **kwargs)[source]
+

Bases: FreqContainer, DataWeightContainer

+

Container for a frequency stack.

+

In general used to hold the product of draco.analysis.SourceStack +The stacked signal of frequency slices of the data in the direction +of sources of interest.

+
+
+property stack
+

Get the stack dataset.

+
+ +
+ +
+
+class draco.core.containers.FrequencyStackByPol(*args, **kwargs)[source]
+

Bases: FrequencyStack

+

Container for a frequency stack split by polarisation.

+
+
+property pol
+

Get the pol axis.

+
+ +
+ +
+
+class draco.core.containers.GainData(*args, **kwargs)[source]
+

Bases: FreqContainer, TODContainer, GainDataBase

+

Parallel container for holding gain data.

+
+
+property input
+

Get the input axis.

+
+ +
+
+property update_id
+

Get the update id dataset if it exists.

+
+ +
+ +
+
+class draco.core.containers.GainDataBase(*args, **kwargs)[source]
+

Bases: DataWeightContainer

+

A container interface for gain-like data.

+

To support the previous behaviour of gain type data the weight dataset is optional, +and returns None if it is not present.

+
+
+property gain: MemDataset
+

Get the gain dataset.

+
+ +
+
+property weight: MemDataset | None
+

The weights for each data point.

+

Returns None is no weight dataset exists.

+
+ +
+ +
+
+class draco.core.containers.GridBeam(coords='celestial', *args, **kwargs)[source]
+

Bases: FreqContainer, DataWeightContainer

+

Generic container for representing a 2D beam on a rectangular grid.

+
+
+property beam
+

Get the beam dataset.

+
+ +
+
+property coords
+

Get the coordinates attribute.

+
+ +
+
+property gain
+

Get the gain dataset.

+
+ +
+
+property input
+

Get the input axis.

+
+ +
+
+property phi
+

Get the phi axis.

+
+ +
+
+property pol
+

Get the pol axis.

+
+ +
+
+property quality
+

Get the quality dataset.

+
+ +
+
+property theta
+

Get the theta axis.

+
+ +
+ +
+
+class draco.core.containers.HEALPixBeam(coords='unknown', ordering='unknown', *args, **kwargs)[source]
+

Bases: FreqContainer, HealpixContainer, DataWeightContainer

+

Container for representing the spherical 2-d beam in a HEALPix grid.

+
+
Parameters:
+
    +
  • ordering ({"nested", "ring"}) – The HEALPix ordering scheme used for the beam map.

  • +
  • coords ({"celestial", "galactic", "telescope"}) – The coordinate system that the beam map is defined on.

  • +
+
+
+
+
+property beam
+

Get the beam dataset.

+
+ +
+
+property coords
+

Get the coordinate attribute.

+
+ +
+
+property input
+

Get the input axis.

+
+ +
+
+property nside
+

Get the nsides of the map.

+
+ +
+
+property ordering
+

Get the ordering attribute.

+
+ +
+
+property pol
+

Get the pol axis.

+
+ +
+ +
+
+class draco.core.containers.HealpixContainer(nside=None, *args, **kwargs)[source]
+

Bases: ContainerBase

+

Base class container for holding Healpix map data.

+
+
Parameters:
+

nside (int) – The nside of the Healpix maps.

+
+
+
+
+property nside
+

Get the nside of the map.

+
+ +
+ +
+
+class draco.core.containers.HybridVisMModes(mmax: int | None = None, oddra: bool | None = None, *args, **kwargs)[source]
+

Bases: FreqContainer, MContainer, VisBase

+

Visibilities beamformed in the NS direction and m-mode transformed in RA.

+

This container has visibilities beam formed only in the NS direction to give a +grid in elevation.

+
+ +
+
+class draco.core.containers.HybridVisStream(ra=None, *args, **kwargs)[source]
+

Bases: FreqContainer, SiderealContainer, VisBase

+

Visibilities beamformed only in the NS direction.

+

This container has visibilities beam formed only in the NS direction to give a +grid in elevation.

+
+
+property dirty_beam
+

Not useful at this stage, but it’s needed to propagate onward.

+
+ +
+ +
+
+class draco.core.containers.KLModes(mmax: int | None = None, oddra: bool | None = None, *args, **kwargs)[source]
+

Bases: SVDModes

+

Parallel container for holding KL filtered m-mode data.

+
+
Parameters:
+

mmax (integer, optional) – Largest m to be held.

+
+
+
+ +
+
+class draco.core.containers.MContainer(mmax: int | None = None, oddra: bool | None = None, *args, **kwargs)[source]
+

Bases: ContainerBase

+

Container for holding m-mode type data.

+

Note this container will have an msign axis even though not all m-mode based +data needs one. As always this is not an issue, datasets that don’t need it are +not required to list it in their axes list.

+
+
Parameters:
+
    +
  • mmax (integer, optional) – Largest m to be held.

  • +
  • oddra (bool, optional) – Does this MContainer come from an underlying odd number of RA points. This +determines if the largest negative m is filled or not (it is for odd=True, not +for odd=False). Default is odd=False.

  • +
+
+
+
+
+property mmax: int
+

The maximum m stored.

+
+ +
+
+property oddra: bool
+

Whether this represents an odd or even number of RA points.

+
+ +
+ +
+
+class draco.core.containers.MModes(*args, **kwargs)[source]
+

Bases: FreqContainer, VisContainer, MContainer

+

Parallel container for holding m-mode data.

+
+
+vis
+

Visibility array.

+
+
Type:
+

mpidataset.MPIArray

+
+
+
+ +
+
+weight
+

Array of weights for each point.

+
+
Type:
+

mpidataset.MPIArray

+
+
+
+ +
+ +
+
+class draco.core.containers.Map(polarisation=True, *args, **kwargs)[source]
+

Bases: FreqContainer, HealpixContainer

+

Container for holding multi-frequency sky maps.

+

The maps are packed in format [freq, pol, pixel] where the polarisations +are Stokes I, Q, U and V, and the pixel dimension stores a Healpix map.

+
+
Parameters:
+
    +
  • nside (int) – The nside of the Healpix maps.

  • +
  • polarisation (bool, optional) – If True all Stokes parameters are stored, if False only Stokes I is +stored.

  • +
+
+
+
+
+property map
+

Get the map dataset.

+
+ +
+ +
+
+class draco.core.containers.MockFrequencyStack(*args, **kwargs)[source]
+

Bases: FrequencyStack

+

Container for holding a frequency stack for multiple mock catalogs.

+

Adds a mock axis as the first dimension of each dataset.

+
+ +
+
+class draco.core.containers.MockFrequencyStackByPol(*args, **kwargs)[source]
+

Bases: FrequencyStackByPol

+

Container for holding a frequency stack split by pol for multiple mock catalogs.

+

Adds a mock axis as the first dimension of each dataset.

+
+ +
+
+class draco.core.containers.Powerspectrum2D(kperp_edges=None, kpar_edges=None, *args, **kwargs)[source]
+

Bases: ContainerBase

+

Container for a 2D cartesian power spectrum.

+

Generally you should set the standard attributes z_start and z_end with +the redshift range included in the power spectrum estimate, and the type +attribute with a description of the estimator type. Suggested valued for +type are:

+
+
unwindowed

The standard unbiased quadratic estimator.

+
+
minimum_variance

The minimum variance, but highly correlated, estimator. Just a rescaled +version of the q-estimator.

+
+
uncorrelated

The uncorrelated estimator using the root of the Fisher matrix.

+
+
+
+
Parameters:
+
    +
  • kpar_edges (np.ndarray) – Array of the power spectrum bin boundaries.

  • +
  • kperp_edges (np.ndarray) – Array of the power spectrum bin boundaries.

  • +
+
+
+
+
+property C_inv
+

Get the C inverse dataset.

+
+ +
+
+property powerspectrum
+

Get the powerspectrum dataset.

+
+ +
+ +
+
+class draco.core.containers.RFIMask(*args, **kwargs)[source]
+

Bases: FreqContainer, TODContainer

+

A container for holding an RFI mask for a timestream.

+

The mask is True for contaminated samples that should be excluded, and +False for clean samples.

+
+
+property mask
+

Get the mask dataset.

+
+ +
+ +
+
+class draco.core.containers.RingMap(ra=None, *args, **kwargs)[source]
+

Bases: FreqContainer, SiderealContainer, DataWeightContainer

+

Container for holding multifrequency ring maps.

+

The maps are packed in format [freq, pol, ra, EW beam, el] where +the polarisations are Stokes I, Q, U and V.

+
+
Parameters:
+
    +
  • nside (int) – The nside of the Healpix maps.

  • +
  • polarisation (bool, optional) – If True all Stokes parameters are stored, if False only Stokes I is +stored.

  • +
+
+
+
+
+property dirty_beam
+

Get the dirty beam dataset.

+
+ +
+
+property el
+

Get the el axis.

+
+ +
+
+property map
+

Get the map dataset.

+
+ +
+
+property pol
+

Get the pol axis.

+
+ +
+
+property rms
+

Get the rms dataset.

+
+ +
+ +
+
+class draco.core.containers.RingMapMask(ra=None, *args, **kwargs)[source]
+

Bases: FreqContainer, SiderealContainer

+

Mask bad ringmap pixels.

+
+
+property mask
+

Get the mask dataset.

+
+ +
+ +
+
+class draco.core.containers.SVDModes(mmax: int | None = None, oddra: bool | None = None, *args, **kwargs)[source]
+

Bases: MContainer, VisBase

+

Parallel container for holding SVD m-mode data.

+
+
Parameters:
+

mmax (integer, optional) – Largest m to be held.

+
+
+
+
+property nmode
+

Get the nmode dataset.

+
+ +
+ +
+
+class draco.core.containers.SVDSpectrum(*args, **kwargs)[source]
+

Bases: ContainerBase

+

Container for an m-mode SVD spectrum.

+
+
+property spectrum
+

Get the spectrum dataset.

+
+ +
+ +
+
+class draco.core.containers.SampleVarianceContainer(*args, **kwargs)[source]
+

Bases: ContainerBase

+

Base container for holding the sample variance over observations.

+

This works like ContainerBase but provides additional capabilities +for containers that may be used to hold the sample mean and variance over +complex-valued observations. These capabilities include automatic definition +of the component axis, properties for accessing standard datasets, properties +that rotate the sample variance into common bases, and a sample_weight property +that provides an equivalent to the weight dataset that is determined from the +sample variance over observations.

+

Subclasses must include a sample_variance and nsample dataset +in there _dataset_spec dictionary. They must also specify a +_mean property that returns the dataset containing the mean over observations.

+
+
+property component
+

Get the component axis.

+
+ +
+
+property nsample
+

Get the nsample dataset if it exists.

+
+ +
+
+property sample_variance
+

Convenience access to the sample variance dataset.

+
+
Returns:
+

C – The variance over the dimension that was stacked +(e.g., sidereal days, holographic observations) +in the default real-imaginary basis. The array is packed +into upper-triangle format such that the component axis +contains [(‘real’, ‘real’), (‘real’, ‘imag’), (‘imag’, ‘imag’)].

+
+
Return type:
+

np.ndarray[ncomponent, …]

+
+
+
+ +
+
+property sample_variance_amp_phase
+

Calculate the amplitude/phase covariance.

+

This interpretation is only valid if the fractional +variations in the amplitude and phase are small.

+
+
Returns:
+

C – The observed amplitude/phase covariance matrix, packed +into upper triangle format such that the component axis +contains [(‘amp’, ‘amp’), (‘amp’, ‘phase’), (‘phase’, ‘phase’)].

+
+
Return type:
+

np.ndarray[ncomponent, …]

+
+
+
+ +
+
+property sample_variance_iq
+

Rotate the sample variance to the in-phase/quadrature basis.

+
+
Returns:
+

C – The sample_variance dataset in the in-phase/quadrature basis, +packed into upper triangle format such that the component axis +contains [(‘I’, ‘I’), (‘I’, ‘Q’), (‘Q’, ‘Q’)].

+
+
Return type:
+

np.ndarray[ncomponent, …]

+
+
+
+ +
+
+property sample_weight
+

Calculate a weight from the sample variance.

+
+
Returns:
+

weight – The trace of the sample_variance dataset is used +as an estimate of the total variance and divided by the +nsample dataset to yield the uncertainty on the mean. +The inverse of this quantity is returned, and can be compared +directly to the weight dataset.

+
+
Return type:
+

np.ndarray[…]

+
+
+
+ +
+ +
+
+class draco.core.containers.SiderealBaselineMask(ra=None, *args, **kwargs)[source]
+

Bases: FreqContainer, SiderealContainer

+

A container for holding a baseline-dependent mask for a sidereal stream.

+

The mask is True for contaminated samples that should be excluded, and +False for clean samples.

+

Unlike SiderealRFIMask, this is distributed by default.

+
+
+property mask
+

Get the mask dataset.

+
+ +
+
+property stack
+

The stack definition as an index (and conjugation) of a member product.

+
+ +
+ +
+
+class draco.core.containers.SiderealContainer(ra=None, *args, **kwargs)[source]
+

Bases: ContainerBase

+

A pipeline container for data with an RA axis.

+

This works like a normal ContainerBase container, but already has an RA +axis defined, and specific properties for dealing with this axis.

+

Note that Right Ascension is a fairly ambiguous term. What is typically meant +here is the Local Stellar Angle, which is the transiting RA in CIRS coordinates. +This is similar to J2000/ICRS with the minimal amount of coordinate rotation to +account for the polar axis precession.

+
+
Parameters:
+

ra (array or int, optional) – Either the explicit locations of samples of the RA axis, or if passed an +integer interpret this as a number of samples dividing the full sidereal day +and create an axis accordingly.

+
+
+
+
+property ra
+

The RA in degrees associated with each sample of the RA axis.

+
+ +
+ +
+
+class draco.core.containers.SiderealGainData(ra=None, *args, **kwargs)[source]
+

Bases: FreqContainer, SiderealContainer, GainDataBase

+

Parallel container for holding sidereal gain data.

+
+
+property input
+

Get the input axis.

+
+ +
+ +
+
+class draco.core.containers.SiderealRFIMask(ra=None, *args, **kwargs)[source]
+

Bases: FreqContainer, SiderealContainer

+

A container for holding an RFI mask for a sidereal stream.

+

The mask is True for contaminated samples that should be excluded, and +False for clean samples.

+
+
+property mask
+

Get the mask dataset.

+
+ +
+ +
+
+class draco.core.containers.SiderealStream(*args, **kwargs)[source]
+

Bases: FreqContainer, VisContainer, SiderealContainer, SampleVarianceContainer

+

A container for holding a visibility dataset in sidereal time.

+
+
Parameters:
+

ra (int) – The number of points to divide the RA axis up into.

+
+
+
+
+property gain
+

Get the gain dataset.

+
+ +
+
+property input_flags
+

Get the input_flags dataset.

+
+ +
+ +
+
+class draco.core.containers.SourceCatalog(*args, **kwargs)[source]
+

Bases: TableBase

+

A basic container for holding astronomical source catalogs.

+

Notes

+

The ra and dec coordinates should be ICRS.

+
+ +
+
+class draco.core.containers.SpectroscopicCatalog(*args, **kwargs)[source]
+

Bases: SourceCatalog

+

A container for spectroscopic catalogs.

+
+ +
+
+class draco.core.containers.Stack3D(*args, **kwargs)[source]
+

Bases: FreqContainer, DataWeightContainer

+

Container for a 3D frequency stack.

+
+
+property stack
+

Get the stack dataset.

+
+ +
+ +
+
+class draco.core.containers.StaticGainData(*args, **kwargs)[source]
+

Bases: FreqContainer, GainDataBase

+

Parallel container for holding static gain data (i.e. non time varying).

+
+
+property input
+

Get the input axis.

+
+ +
+ +
+
+class draco.core.containers.SystemSensitivity(*args, **kwargs)[source]
+

Bases: FreqContainer, TODContainer

+

A container for holding the total system sensitivity.

+

This should be averaged/collapsed in the stack/prod axis +to provide an overall summary of the system sensitivity. +Two datasets are available: the measured noise from the +visibility weights and the radiometric estimate of the +noise from the autocorrelations.

+
+
+property frac_lost
+

Get the frac_lost dataset.

+
+ +
+
+property measured
+

Get the measured noise dataset.

+
+ +
+
+property pol
+

Get the pol axis.

+
+ +
+
+property radiometer
+

Get the radiometer estimate dataset.

+
+ +
+
+property weight
+

Get the weight dataset.

+
+ +
+ +
+
+class draco.core.containers.TODContainer(*args, **kwargs)[source]
+

Bases: ContainerBase, TOData

+

A pipeline container for time ordered data.

+

This works like a normal ContainerBase container, with the added +ability to be concatenated, and treated like a a tod.TOData +instance.

+
+ +
+
+class draco.core.containers.TableBase(*args, **kwargs)[source]
+

Bases: ContainerBase

+

A base class for containers holding tables of data.

+

Similar to the ContainerBase class, the container is defined through a +dictionary given as a _table_spec class attribute. The container may also +hold generic datasets by specifying _dataset_spec as with ContainerBase. +See Notes for details.

+
+
Parameters:
+
    +
  • axes_from (memh5.BasicCont, optional) – Another container to copy axis definitions from. Must be supplied as +keyword argument.

  • +
  • attrs_from (memh5.BasicCont, optional) – Another container to copy attributes from. Must be supplied as keyword +argument. This applies to attributes in default datasets too.

  • +
  • kwargs (dict) – Should contain definitions for all other table axes.

  • +
+
+
+

Notes

+

A _table_spec consists of a dictionary mapping table names into a +description of the table. That description is another dictionary containing +several entries.

+
    +
  • columns : the set of columns in the table. Given as a list of +(name, dtype) pairs.

  • +
  • axis : an optional name for the rows of the table. This is automatically +generated as ‘<tablename>_index’ if not explicitly set. This corresponds +to an index_map entry on the container.

  • +
  • initialise : whether to create the table by default.

  • +
  • distributed : whether the table is distributed, or common across all MPI ranks.

  • +
+

An example _table_spec entry is:

+
_table_spec = {
+    'quasars': {
+        'columns': [
+            ['ra': np.float64],
+            ['dec': np.float64],
+            ['z': np.float64]
+        ],
+        'distributed': False,
+        'axis': 'quasar_id'
+    }
+    'quasar_mask': {
+        'columns': [
+            ['mask', bool]
+        ],
+        'axis': 'quasar_id'
+    }
+}
+
+
+
+
+property table_spec
+

Return a copy of the fully resolved table specifiction as a dictionary.

+
+ +
+ +
+
+class draco.core.containers.TimeStream(*args, **kwargs)[source]
+

Bases: FreqContainer, VisContainer, TODContainer

+

A container for holding a visibility dataset in time.

+

This should look similar enough to the CHIME +CorrData container that they can be used +interchangably in most cases.

+
+
+property gain
+

Get the gain dataset.

+
+ +
+
+property input_flags
+

Get the input_flags dataset.

+
+ +
+ +
+
+class draco.core.containers.TrackBeam(theta=None, phi=None, coords='celestial', track_type='drift', *args, **kwargs)[source]
+

Bases: FreqContainer, SampleVarianceContainer, DataWeightContainer

+

Container for a sequence of beam samples at arbitrary locations on the sphere.

+

The axis of the beam samples is ‘pix’, defined by the numpy.dtype +[(‘theta’, np.float32), (‘phi’, np.float32)].

+
+
+property beam
+

Get the beam dataset.

+
+ +
+
+property coords
+

Get the coordinates attribute.

+
+ +
+
+property gain
+

Get the gain dataset.

+
+ +
+
+property input
+

Get the input axis.

+
+ +
+
+property pix
+

Get the pix axis.

+
+ +
+
+property pol
+

Get the pol axis.

+
+ +
+
+property track_type
+

Get the track type attribute.

+
+ +
+ +
+
+class draco.core.containers.VisBase(*args, **kwargs)[source]
+

Bases: DataWeightContainer

+

A very basic class for visibility data.

+

For better support for input/prod/stack structured data use VisContainer.

+
+
+property vis
+

The visibility like dataset.

+
+ +
+ +
+
+class draco.core.containers.VisContainer(*args, **kwargs)[source]
+

Bases: VisBase

+

A base container for holding a visibility dataset.

+

This works like a ContainerBase container, with the +ability to create visibility specific axes, if they are not +passed as a kwargs parameter.

+

Additionally this container has visibility specific defined properties +such as ‘vis’, ‘weight’, ‘freq’, ‘input’, ‘prod’, ‘stack’, +‘prodstack’, ‘conjugate’.

+
+
Parameters:
+
    +
  • axes_from (memh5.BasicCont, optional) – Another container to copy axis definitions from. Must be supplied as +keyword argument.

  • +
  • attrs_from (memh5.BasicCont, optional) – Another container to copy attributes from. Must be supplied as keyword +argument. This applies to attributes in default datasets too.

  • +
  • kwargs (dict) – Should contain entries for all other axes.

  • +
+
+
+
+
+property input
+

The correlated inputs.

+
+ +
+
+property is_stacked
+

Test if the data has been stacked or not.

+
+ +
+
+property prod
+

All the pairwise products that are represented in the data.

+
+ +
+
+property prodstack
+

A pair of input indices representative of those in the stack.

+

Note, these are correctly conjugated on return, and so calculations +of the baseline and polarisation can be done without additionally +looking up the stack conjugation.

+
+ +
+
+property stack
+

The stacks definition as an index (and conjugation) of a member product.

+
+ +
+ +
+
+class draco.core.containers.VisGridStream(ra=None, *args, **kwargs)[source]
+

Bases: FreqContainer, SiderealContainer, VisBase

+

Visibilities gridded into a 2D array.

+

Only makes sense for an array which is a cartesian grid.

+
+
+property redundancy
+

Get the redundancy dataset.

+
+ +
+ +
+
+class draco.core.containers.WaveletSpectrum(*args, **kwargs)[source]
+

Bases: FreqContainer, DelayContainer, DataWeightContainer

+

Container for a wavelet power spectrum.

+
+
+property spectrum
+

The wavelet spectrum.

+
+ +
+ +
+
+draco.core.containers.copy_datasets_filter(source: ContainerBase, dest: ContainerBase, axis: str | list | tuple = [], selection: ndarray | list | slice | dict = {}, exclude_axes: List[str] | None = None)[source]
+

Copy datasets while filtering a given axis.

+

Only datasets containing the axis to be filtered will be copied.

+
+
Parameters:
+
    +
  • source – Source container

  • +
  • dest – Destination container. The axes in this container should reflect the +selections being made to the source.

  • +
  • axis – Name of the axes to filter. These must match the axes in selection, +unless selection is a single item. This is partially here for legacy +reasons, as the selections can be fully specified by selection

  • +
  • selection – A filtering selection to be applied to each axis.

  • +
  • exclude_axes – An optional set of axes that if a dataset contains one means it will +not be copied.

  • +
+
+
+
+ +
+
+draco.core.containers.empty_like(obj, **kwargs)[source]
+

Create an empty container like obj.

+
+
Parameters:
+
    +
  • obj (ContainerBase) – Container to base this one off.

  • +
  • kwargs (optional) – Optional definitions of specific axes we want to override. Works in the +same way as the ContainerBase constructor, though axes_from=obj and +attrs_from=obj are implied.

  • +
+
+
Returns:
+

newobj – New data container.

+
+
Return type:
+

container.ContainerBase

+
+
+
+ +
+
+draco.core.containers.empty_timestream(**kwargs)[source]
+

Create a new timestream container.

+

This indirect call exists so it can be replaced to return custom timestream +types.

+
+
Parameters:
+

kwargs (optional) – Arguments to pass to the timestream constructor.

+
+
Returns:
+

ts

+
+
Return type:
+

TimeStream

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.core.html b/docs/_autosummary/draco.core.html new file mode 100644 index 000000000..a714249da --- /dev/null +++ b/docs/_autosummary/draco.core.html @@ -0,0 +1,147 @@ + + + + + + + draco.core — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.core

+ + + + + + + + + + + + + + + +

containers

Distributed containers for holding various types of analysis data.

io

Tasks for reading and writing data.

misc

Miscellaneous pipeline tasks with no where better to go.

task

An improved base task implementing easy (and explicit) saving of outputs.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.core.io.html b/docs/_autosummary/draco.core.io.html new file mode 100644 index 000000000..16cf0969c --- /dev/null +++ b/docs/_autosummary/draco.core.io.html @@ -0,0 +1,959 @@ + + + + + + + draco.core.io — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.core.io

+

Tasks for reading and writing data.

+
+

File Groups

+

Several tasks accept groups of files as arguments. These are specified in the YAML file as a dictionary like below.

+
list_of_file_groups:
+    -   tag: first_group  # An optional tag naming the group
+        files:
+            -   'file1.h5'
+            -   'file[3-4].h5'  # Globs are processed
+            -   'file7.h5'
+
+    -   files:  # No tag specified, implicitly gets the tag 'group_2'
+            -   'another_file1.h5'
+            -   'another_file2.h5'
+
+
+single_group:
+    files: ['file1.h5', 'file2.h5']
+
+
+
+

Functions

+ + + + + + + + + +

get_beamtransfer(obj)

Return a BeamTransfer object out of the input.

get_telescope(obj)

Return a telescope object out of the input.

+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

BaseLoadFiles()

Base class for loading containers from a file on disk.

FindFiles()

Take a glob or list of files and pass on to other tasks.

LoadBasicCont

alias of LoadFilesFromParams

LoadBeamTransfer()

Loads a beam transfer manager from disk.

LoadFITSCatalog()

Load an SDSS-style FITS source catalog.

LoadFiles()

Load data from files passed into the setup routine.

LoadFilesFromParams()

Load data from files given in the tasks parameters.

LoadMaps()

Load a series of maps from files given in the tasks parameters.

LoadProductManager()

Loads a driftscan product manager from disk.

Print()

Stupid module which just prints whatever it gets.

Save()

Save out the input, and pass it on.

SaveConfig()

Write pipeline config to a text file.

SaveModuleVersions()

Write module versions to a YAML file.

SaveZarrZip()

Save a container as a .zarr.zip file.

SelectionsMixin()

Mixin for parsing axis selections, typically from a yaml config.

Truncate()

Precision truncate data prior to saving with bitshuffle compression.

WaitZarrZip()

Collect Zarr-zipping jobs and wait for them to complete.

ZarrZipHandle(filename, handle)

A handle for keeping track of background Zarr-zipping job.

ZipZarrContainers()

Zip up a Zarr container into a single file.

+
+
+class draco.core.io.BaseLoadFiles[source]
+

Bases: SelectionsMixin, SingleTask

+

Base class for loading containers from a file on disk.

+

Provides the capability to make selections along axes.

+
+
+distributed
+

Whether the file should be loaded distributed across ranks.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+convert_strings
+

Convert strings to unicode when loading.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+redistribute
+

An optional axis name to redistribute the container over after it has +been read.

+
+
Type:
+

str, optional

+
+
+
+ +
+ +
+
+class draco.core.io.FindFiles[source]
+

Bases: TaskBase

+

Take a glob or list of files and pass on to other tasks.

+

Files are specified as a parameter in the configuration file.

+
+
Parameters:
+

files (list or glob)

+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup()[source]
+

Return list of files specified in the parameters.

+
+ +
+ +
+
+draco.core.io.LoadBasicCont
+

alias of LoadFilesFromParams

+
+ +
+
+class draco.core.io.LoadBeamTransfer[source]
+

Bases: TaskBase

+

Loads a beam transfer manager from disk.

+
+
+product_directory
+

Path to the saved Beam Transfer products.

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup()[source]
+

Load the beam transfer matrices.

+
+
Returns:
+

    +
  • tel (TransitTelescope) – Object describing the telescope.

  • +
  • bt (BeamTransfer) – BeamTransfer manager.

  • +
  • feed_info (list, optional) – Optional list providing additional information about each feed.

  • +
+

+
+
+
+ +
+ +
+
+class draco.core.io.LoadFITSCatalog[source]
+

Bases: SingleTask

+

Load an SDSS-style FITS source catalog.

+

Catalogs are given as one, or a list of File Groups (see +draco.core.io). Catalogs within the same group are combined together +before being passed on.

+
+
+catalogs
+

A dictionary specifying a file group, or a list of them.

+
+
Type:
+

list or dict

+
+
+
+ +
+
+z_range
+

Select only sources with a redshift within the given range.

+
+
Type:
+

list, optional

+
+
+
+ +
+
+freq_range
+

Select only sources with a 21cm line freq within the given range. Overrides +z_range.

+
+
Type:
+

list, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Load the groups of catalogs from disk, concatenate them and pass them on.

+
+
Returns:
+

catalog

+
+
Return type:
+

containers.SpectroscopicCatalog

+
+
+
+ +
+ +
+
+class draco.core.io.LoadFiles[source]
+

Bases: LoadFilesFromParams

+

Load data from files passed into the setup routine.

+

File must be a serialised subclass of memh5.BasicCont.

+
+
+setup(files)[source]
+

Set the list of files to load.

+
+
Parameters:
+

files (list) – Files to load

+
+
+
+ +
+ +
+
+class draco.core.io.LoadFilesFromParams[source]
+

Bases: BaseLoadFiles

+

Load data from files given in the tasks parameters.

+
+
+files
+

Can either be a glob pattern, or lists of actual files.

+
+
Type:
+

glob pattern, or list

+
+
+
+ +
+
+process()[source]
+

Load the given files in turn and pass on.

+
+
Returns:
+

cont

+
+
Return type:
+

subclass of memh5.BasicCont

+
+
+
+ +
+ +
+
+class draco.core.io.LoadMaps[source]
+

Bases: MPILoggedTask

+

Load a series of maps from files given in the tasks parameters.

+

Maps are given as one, or a list of File Groups (see +draco.core.io). Maps within the same group are added together +before being passed on.

+
+
+maps
+

A dictionary specifying a file group, or a list of them.

+
+
Type:
+

list or dict

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+next()[source]
+

Load the groups of maps from disk and pass them on.

+
+
Returns:
+

map

+
+
Return type:
+

containers.Map

+
+
+
+ +
+ +
+
+class draco.core.io.LoadProductManager[source]
+

Bases: TaskBase

+

Loads a driftscan product manager from disk.

+
+
+product_directory
+

Path to the root of the products. This is the same as the output +directory used by drift-makeproducts.

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup()[source]
+

Load the beam transfer matrices.

+
+
Returns:
+

manager – Object describing the telescope.

+
+
Return type:
+

ProductManager

+
+
+
+ +
+ +
+
+class draco.core.io.Print[source]
+

Bases: TaskBase

+

Stupid module which just prints whatever it gets. Good for debugging.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+next(input_)[source]
+

Print the input.

+
+ +
+ +
+
+class draco.core.io.Save[source]
+

Bases: TaskBase

+

Save out the input, and pass it on.

+

Assumes that the input has a to_hdf5 method. Appends a tag if there is +a tag entry in the attributes, otherwise just uses a count.

+
+
+root
+

Root of the file name to output to.

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+next(data)[source]
+

Write out the data file.

+

Assumes it has an MPIDataset interface.

+
+
Parameters:
+

data (mpidataset.MPIDataset) – Data to write out.

+
+
+
+ +
+ +
+
+class draco.core.io.SaveConfig[source]
+

Bases: SingleTask

+

Write pipeline config to a text file.

+

Yaml configuration document is written to a text file.

+
+
+root
+

Root of the file name to output to.

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Do nothing.

+
+ +
+
+setup()[source]
+

Save module versions.

+
+ +
+ +
+
+class draco.core.io.SaveModuleVersions[source]
+

Bases: SingleTask

+

Write module versions to a YAML file.

+

The list of modules should be added to the configuration under key ‘save_versions’. +The version strings are written to a YAML file.

+
+
+root
+

Root of the file name to output to.

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Do nothing.

+
+ +
+
+setup()[source]
+

Save module versions.

+
+ +
+ +
+
+class draco.core.io.SaveZarrZip[source]
+

Bases: ZipZarrContainers

+

Save a container as a .zarr.zip file.

+

This task saves the output first as a .zarr container, and then starts a background +job to start turning it into a zip file. It returns a handle to this job. All these +handles should be fed into a WaitZarrZip task to ensure the pipeline run does not +terminate before they are complete.

+

This accepts most parameters that a standard task would for saving, including +compression parameter overrides.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+next(container: BasicCont) ZarrZipHandle[source]
+

Take a container and save it out as a .zarr.zip file.

+
+
Parameters:
+

container – Container to save out.

+
+
Returns:
+

A handle to use to determine if the job has successfully completed. This +should be given to the WaitZarrZip task.

+
+
Return type:
+

handle

+
+
+
+ +
+
+setup()[source]
+

Check the parameters and determine the ranks to use.

+
+ +
+ +
+
+class draco.core.io.SelectionsMixin[source]
+

Bases: object

+

Mixin for parsing axis selections, typically from a yaml config.

+
+
+selections
+

A dictionary of axis selections. See below for details.

+
+
Type:
+

dict, optional

+
+
+
+ +
+
+Selections
+
+ +
+
+----------
+
+ +
+
+Selections can be given to limit the data read to specified subsets. They can be
+
+ +
+
+given for any named axis in the container.
+
+ +
+
+Selections can be given as a slice with an `<axis name>_range` key with either
+
+ +
+
+`[start, stop]` or `[start, stop, step]` as the value. Alternatively a list of
+
+ +
+
+explicit indices to extract can be given with the `<axis name>_index` key, and
+
+ +
+
+the value is a list of the indices. If both `<axis name>_range` and `<axis
+
+ +
+
+name>_index` keys are given the former will take precedence, but you should
+
+ +
+
+clearly avoid doing this.
+
+ +
+
+Additionally index based selections currently don't work for distributed reads.
+
+ +
+
+Here's an example in the YAML format that the pipeline uses
+
+ +
+
+.. code-block:: yaml
+
+
selections:

freq_range: [256, 512, 4] # A strided slice +stack_index: [1, 2, 4, 9, 16, 25, 36, 49, 64] # A sparse selection +stack_range: [1, 14] # Will override the selection above

+
+
+
+ +
+
+setup()[source]
+

Resolve the selections.

+
+ +
+ +
+
+class draco.core.io.Truncate[source]
+

Bases: SingleTask

+

Precision truncate data prior to saving with bitshuffle compression.

+

If no configuration is provided, will look for preset values for the +input container. Any properties defined in the config will override the +presets.

+

If available, each specified dataset will be truncated relative to a +(specified) weight dataset with the truncation increasing the variance up +to the specified maximum in variance_increase. If there is no specified +weight dataset then the truncation falls back to using the +fixed_precision.

+
+
+dataset
+

Datasets to be truncated as keys. Possible values are: +- bool : Whether or not to truncate, using default fixed precision. +- float : Truncate to this relative precision. +- dict : Specify values for weight_dataset, fixed_precision, variance_increase.

+
+
Type:
+

dict

+
+
+
+ +
+
+ensure_chunked
+

If True, ensure datasets are chunked according to their dataset_spec.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Truncate the incoming data.

+

The truncation is done in place.

+
+
Parameters:
+

data (containers.ContainerBase) – Data to truncate.

+
+
Returns:
+

truncated_data – Truncated data.

+
+
Return type:
+

containers.ContainerBase

+
+
Raises:
+
    +
  • caput.pipeline.PipelineRuntimeError – If input data has mismatching dataset and weight array shapes.

  • +
  • config.CaputConfigError – If the input data container has no preset values and fixed_precision or + variance_increase are not set in the config.

  • +
+
+
+
+ +
+ +
+
+class draco.core.io.WaitZarrZip[source]
+

Bases: MPILoggedTask

+

Collect Zarr-zipping jobs and wait for them to complete.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+finish()[source]
+

Wait for all Zarr zipping jobs to complete.

+
+ +
+
+next(handle: ZarrZipHandle)[source]
+

Receive the handles to wait on.

+
+
Parameters:
+

handle – The handle to wait on.

+
+
+
+ +
+ +
+
+class draco.core.io.ZarrZipHandle(filename: str, handle: Popen | None)[source]
+

Bases: object

+

A handle for keeping track of background Zarr-zipping job.

+
+ +
+
+class draco.core.io.ZipZarrContainers[source]
+

Bases: SingleTask

+

Zip up a Zarr container into a single file.

+

This is useful to save on file quota and speed up IO by combining the chunk +data into a single file. Note that the file cannot really be updated after +this process has been performed.

+

As this process is IO limited in most cases, it will attempt to parallelise +the compression across different distinct nodes. That means at most only +one rank per node will participate.

+
+
+containers
+

The names of the Zarr containers to compress. The zipped files will +have the same names with .zip appended.

+
+
Type:
+

list

+
+
+
+ +
+
+remove
+

Remove the original data when finished. Defaults to True.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Compress the listed zarr containers.

+

Only the lowest rank on each node will participate.

+
+ +
+
+setup(_=None)[source]
+

Setup the task.

+

This routine does nothing at all with the input, but it means the +process won’t run until the (optional) requirement is received. This +can be used to delay evaluation until you know that all the files are +available.

+
+ +
+ +
+
+draco.core.io.get_beamtransfer(obj)[source]
+

Return a BeamTransfer object out of the input.

+

Either ProductManager or BeamTransfer.

+
+ +
+
+draco.core.io.get_telescope(obj)[source]
+

Return a telescope object out of the input.

+

Either ProductManager, BeamTransfer, or TransitTelescope.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.core.misc.html b/docs/_autosummary/draco.core.misc.html new file mode 100644 index 000000000..21909180e --- /dev/null +++ b/docs/_autosummary/draco.core.misc.html @@ -0,0 +1,336 @@ + + + + + + + draco.core.misc — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.core.misc

+

Miscellaneous pipeline tasks with no where better to go.

+

Tasks should be proactively moved out of here when there is a thematically +appropriate module, or enough related tasks end up in here such that they can +all be moved out into their own module.

+

Classes

+ + + + + + + + + + + + + + + + + + + + + +

AccumulateList()

Accumulate the inputs into a list and return when the task finishes.

ApplyGain()

Apply a set of gains to a timestream or sidereal stack.

CheckMPIEnvironment()

Check that the current MPI environment can communicate across all nodes.

MakeCopy()

Make a copy of the passed container.

PassOn()

Unconditionally forward a tasks input.

WaitUntil()

Wait until the the requires before forwarding inputs.

+
+
+class draco.core.misc.AccumulateList[source]
+

Bases: MPILoggedTask

+

Accumulate the inputs into a list and return when the task finishes.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+finish()[source]
+

Remove the internal reference.

+

Prevents the items from hanging around after the task finishes.

+
+ +
+
+next(input_)[source]
+

Append an input to the list of inputs.

+
+ +
+ +
+
+class draco.core.misc.ApplyGain[source]
+

Bases: SingleTask

+

Apply a set of gains to a timestream or sidereal stack.

+
+
+inverse
+

Apply the gains directly, or their inverse.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+update_weight
+

Scale the weight array with the updated gains.

+
+
Type:
+

bool, optional

+
+
+
+ +
+
+smoothing_length
+

Smooth the gain timestream across the given number of seconds. +Not supported (ignored) for Sidereal Streams.

+
+
Type:
+

float, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(tstream, gain)[source]
+

Apply gains to the given timestream.

+

Smoothing the gains is not supported for SiderealStreams.

+
+
Parameters:
+
+
+
Returns:
+

tstream – The timestream with the gains applied.

+
+
Return type:
+

TimeStream or SiderealStream

+
+
+
+ +
+ +
+
+class draco.core.misc.CheckMPIEnvironment[source]
+

Bases: MPILoggedTask

+

Check that the current MPI environment can communicate across all nodes.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+setup()[source]
+

Send random messages between all ranks.

+

Tests to ensure that all messages are received within a specified amount +of time, and that the messages received are the same as those sent (i.e. +nothing was corrupted).

+
+ +
+ +
+
+class draco.core.misc.MakeCopy[source]
+

Bases: SingleTask

+

Make a copy of the passed container.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Return a copy of the given container.

+
+
Parameters:
+

data (containers.ContainerBase) – The container to copy.

+
+
+
+ +
+ +
+
+class draco.core.misc.PassOn[source]
+

Bases: MPILoggedTask

+

Unconditionally forward a tasks input.

+

While this seems like a pointless no-op it’s useful for connecting tasks in complex +topologies.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+next(input_)[source]
+

Immediately forward any input.

+
+ +
+ +
+
+class draco.core.misc.WaitUntil[source]
+

Bases: MPILoggedTask

+

Wait until the the requires before forwarding inputs.

+

This simple synchronization task will forward on whatever inputs it gets, however, it won’t do +this until it receives any requirement to it’s setup method. This allows certain parts of the +pipeline to be delayed until a piece of data further up has been generated.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+next(input_)[source]
+

Immediately forward any input.

+
+ +
+
+setup(input_)[source]
+

Accept, but don’t save any input.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.core.task.html b/docs/_autosummary/draco.core.task.html new file mode 100644 index 000000000..3351ba556 --- /dev/null +++ b/docs/_autosummary/draco.core.task.html @@ -0,0 +1,591 @@ + + + + + + + draco.core.task — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.core.task

+

An improved base task implementing easy (and explicit) saving of outputs.

+

Functions

+ + + + + + +

group_tasks(*tasks)

Create a Task that groups a bunch of tasks together.

+

Classes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Delete()

Delete pipeline products to free memory.

LoggedTask()

A task with logger support.

MPILogFilter([add_mpi_info, level_rank0, ...])

Filter log entries by MPI rank.

MPILoggedTask()

A task base that has MPI aware logging.

MPITask()

Base class for MPI using tasks.

ReturnFirstInputOnFinish()

Workaround for caput.pipeline issues.

ReturnLastInputOnFinish()

Workaround for caput.pipeline issues.

SetMPILogging()

A task used to configure MPI aware logging.

SingleTask()

Process a task with at most one input and output.

+
+
+class draco.core.task.Delete[source]
+

Bases: SingleTask

+

Delete pipeline products to free memory.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(x)[source]
+

Delete the input and collect garbage.

+
+
Parameters:
+

x (object) – The object to be deleted.

+
+
+
+ +
+ +
+
+class draco.core.task.LoggedTask[source]
+

Bases: TaskBase

+

A task with logger support.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+property log
+

The logger object for this task.

+
+ +
+ +
+
+class draco.core.task.MPILogFilter(add_mpi_info=True, level_rank0=20, level_all=30)[source]
+

Bases: Filter

+

Filter log entries by MPI rank.

+

Also this will optionally add MPI rank information, and add an elapsed time +entry.

+
+
Parameters:
+
    +
  • add_mpi_info (boolean, optional) – Add MPI rank/size info to log records that don’t already have it.

  • +
  • level_rank0 (int) – Log level for messages from rank=0.

  • +
  • level_all (int) – Log level for messages from all other ranks.

  • +
+
+
+

Initialize a filter.

+

Initialize with the name of the logger which, together with its +children, will have its events allowed through the filter. If no +name is specified, allow every event.

+
+
+filter(record)[source]
+

Add MPI info if desired.

+
+ +
+ +
+
+class draco.core.task.MPILoggedTask[source]
+

Bases: MPITask, LoggedTask

+

A task base that has MPI aware logging.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.core.task.MPITask[source]
+

Bases: TaskBase

+

Base class for MPI using tasks.

+

Just ensures that the task gets a comm attribute.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.core.task.ReturnFirstInputOnFinish[source]
+

Bases: SingleTask

+

Workaround for caput.pipeline issues.

+

This caches its input on the first call to process and +then returns it for a finish call.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(x)[source]
+

Take a reference to the input.

+
+
Parameters:
+

x (object) – Object to cache

+
+
+
+ +
+
+process_finish()[source]
+

Return the last input to process.

+
+
Returns:
+

x – Last input to process.

+
+
Return type:
+

object

+
+
+
+ +
+ +
+
+class draco.core.task.ReturnLastInputOnFinish[source]
+

Bases: SingleTask

+

Workaround for caput.pipeline issues.

+

This caches its input on every call to process and then returns +the last one for a finish call.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(x)[source]
+

Take a reference to the input.

+
+
Parameters:
+

x (object) – Object to cache

+
+
+
+ +
+
+process_finish()[source]
+

Return the last input to process.

+
+
Returns:
+

x – Last input to process.

+
+
Return type:
+

object

+
+
+
+ +
+ +
+
+class draco.core.task.SetMPILogging[source]
+

Bases: TaskBase

+

A task used to configure MPI aware logging.

+
+
+level_rank0, level_all
+

Log level for rank=0, and other ranks respectively.

+
+
Type:
+

int or str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.core.task.SingleTask[source]
+

Bases: MPILoggedTask, BasicContMixin

+

Process a task with at most one input and output.

+

Both input and output are expected to be memh5.BasicCont objects. +This class allows writing of the output when requested.

+

Tasks inheriting from this class should override process and optionally +setup() or finish(). They should not override next().

+

If the value of input_root is anything other than the string “None” +then the input will be read (using read_input()) from the file +self.input_root + self.input_filename. If the input is specified both as +a filename and as a product key in the pipeline configuration, an error +will be raised upon initialization.

+

If the value of output_root is anything other than the string +“None” then the output will be written (using write_output()) to the +file self.output_root + self.output_filename.

+
+
+save
+

Whether to save the output to disk or not.

+
+
Type:
+

bool

+
+
+
+ +
+
+attrs
+

A mapping of attribute names and values to set in the .attrs at the root of +the output container. String values will be formatted according to the standard +Python .format(…) rules, and can interpolate several other values into the +string. These are:

+
    +
  • count: an integer giving which iteration of the task is this.

  • +
  • +
    tag: a string identifier for the output derived from the

    containers tag attribute. If that attribute is not present +count is used instead.

    +
    +
    +
  • +
  • key: the name of the output key.

  • +
  • task: the (unqualified) name of the task.

  • +
  • input_tags: a list of the tags for each input argument for the task.

  • +
  • Any existing attribute in the container can be interpolated by the name of +its key. The specific values above will override any attribute with the same +name.

  • +
+

Incorrectly formatted values will cause an error to be thrown.

+
+
Type:
+

dict, optional

+
+
+
+ +
+
+tag
+

Set a format for the tag attached to the output. This is a Python format string +which can interpolate the variables listed under attrs above. For example a +tag of “cat{count}” will generate catalogs with the tags “cat1”, “cat2”, etc.

+
+
Type:
+

str, optional

+
+
+
+ +
+
+output_name
+

A python format string used to construct the filename. All variables given under +attrs above can be interpolated into the filename. +Valid identifiers are:

+
+
    +
  • count: an integer giving which iteration of the task is this.

  • +
  • +
    tag: a string identifier for the output derived from the

    containers tag attribute. If that attribute is not present +count is used instead.

    +
    +
    +
  • +
  • key: the name of the output key.

  • +
  • task: the (unqualified) name of the task.

  • +
  • +
    output_root: the value of the output root argument. This is deprecated

    and is just used for legacy support. The default value of +output_name means the previous behaviour works.

    +
    +
    +
  • +
+
+
+
Type:
+

string

+
+
+
+ +
+
+compression
+

Set compression options for each dataset. Provided as a dict with the dataset +names as keys and values for chunks, compression, and compression_opts. +Any datasets not included in the dict (including if the dict is empty), will use +the default parameters set in the dataset spec. If set to False (or anything +that evaluates to False, other than an empty dict) chunks and compression will +be disabled for all datasets. If no argument in provided, the default parameters +set in the dataset spec are used. Note that this will modify these parameters on +the container itself, such that if it is written out again downstream in the +pipeline these will be used.

+
+
Type:
+

dict or bool, optional

+
+
+
+ +
+
+output_root
+

Pipeline settable parameter giving the first part of the output path. +Deprecated in favour of output_name.

+
+
Type:
+

string

+
+
+
+ +
+
+nan_check
+

Check the output for NaNs (and infs) logging if they are present.

+
+
Type:
+

bool

+
+
+
+ +
+
+nan_dump
+

If NaN’s are found, dump the container to disk.

+
+
Type:
+

bool

+
+
+
+ +
+
+nan_skip
+

If NaN’s are found, don’t pass on the output.

+
+
Type:
+

bool

+
+
+
+ +
+
+versions
+

Keys are module names (str) and values are their version strings. This is +attached to output metadata.

+
+
Type:
+

dict

+
+
+
+ +
+
+pipeline_config
+

Global pipeline configuration. This is attached to output metadata.

+
+
Type:
+

dict

+
+
+
+ +
+
Raises:
+

caput.pipeline.PipelineRuntimeError – If this is used as a baseclass to a task overriding self.process with variable + length or optional arguments.

+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+finish()[source]
+

Should not need to override. Implement process_finish instead.

+
+ +
+
+next(*input)[source]
+

Should not need to override. Implement process instead.

+
+ +
+ +
+
+draco.core.task.group_tasks(*tasks)[source]
+

Create a Task that groups a bunch of tasks together.

+

This method creates a class that inherits from all the subtasks, and +calls each process method in sequence, passing the output of one to the +input of the next.

+

This should be used like:

+
>>> class SuperTask(group_tasks(SubTask1, SubTask2)):
+>>>     pass
+
+
+

At the moment if the ensemble has more than one setup method, the +SuperTask will need to implement an override that correctly calls each.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.synthesis.gain.html b/docs/_autosummary/draco.synthesis.gain.html new file mode 100644 index 000000000..6ae7fa925 --- /dev/null +++ b/docs/_autosummary/draco.synthesis.gain.html @@ -0,0 +1,476 @@ + + + + + + + draco.synthesis.gain — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.synthesis.gain

+

Tasks for generating random gain fluctuations in the data and stacking them.

+

Functions

+ + + + + + + + + + + + +

constrained_gaussian_realisation(x, ...[, rcond])

Generate a constrained Gaussian random field.

gaussian_realisation(x, corrfunc, n[, rcond])

Generate a Gaussian random field.

generate_fluctuations(x, corrfunc, n, ...)

Generate correlated random streams.

+

Classes

+ + + + + + + + + + + + + + + + + + +

BaseGains()

Rudimentary class to generate gain timestreams.

GainStacker()

Take sidereal gain data, make products and stack them up.

RandomGains()

Generate random gains.

RandomSiderealGains()

Generate random gains on a Sidereal grid.

SiderealGains()

Task for simulating sidereal gains.

+
+
+class draco.synthesis.gain.BaseGains[source]
+

Bases: SingleTask

+

Rudimentary class to generate gain timestreams.

+

The gains are drawn for times which match up to an input timestream file.

+
+
+amp
+

Generate gain amplitude fluctuations. Default is True.

+
+
Type:
+

bool

+
+
+
+ +
+
+phase
+

Generate gain phase fluctuations. Default is True.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Generate a gain timestream for the inputs and times in data.

+
+
Parameters:
+

data (containers.TimeStream) – Generate gain errors for data.

+
+
Returns:
+

gain

+
+
Return type:
+

containers.GainData

+
+
+
+ +
+ +
+
+class draco.synthesis.gain.GainStacker[source]
+

Bases: SingleTask

+

Take sidereal gain data, make products and stack them up.

+
+
+only_gains
+

Whether to return only the stacked gains or the stacked gains +mulitplied with the visibilites. Default: False.

+
+
Type:
+

bool

+
+
+
+ +

Notes

+

This task generates products of gain time streams for every sidereal day and +stacks them up over the number of days in the simulation.

+

More formally a gain stack can be described as

+
+\[G_{ij} = \sum_{a}^{Ndays} g_{i}(t)^{a} g_j(t)^{*a}\]
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(gain)[source]
+

Make sidereal gain products and stack them up.

+
+
Parameters:
+

gain (containers.SiderealGainData or containers.GainData) – Individual sidereal or time ordered gain data.

+
+
Returns:
+

gain_stack – Stacked products of gains.

+
+
Return type:
+

containers.TimeStream or containers.SiderealStream

+
+
+
+ +
+
+process_finish()[source]
+

Multiply summed gain with sidereal stream.

+
+
Returns:
+

data – Stack of sidereal data with gain applied.

+
+
Return type:
+

containers.SiderealStream or containers.TimeStream

+
+
+
+ +
+
+setup(stream)[source]
+

Get the sidereal stream onto which we stack the simulated gain data.

+
+
Parameters:
+

stream (containers.SiderealStream or containers.TimeStream) – The sidereal or time data to use.

+
+
+
+ +
+ +
+
+class draco.synthesis.gain.RandomGains[source]
+

Bases: BaseGains

+

Generate random gains.

+

Notes

+

The Random Gains class generates random fluctuations in gain amplitude and +phase.

+
+
+corr_length_amp, corr_length_phase
+

Correlation length for amplitude and phase fluctuations in seconds.

+
+
Type:
+

float

+
+
+
+ +
+
+sigma_amp, sigma_phase
+

Size of fluctuations for amplitude (fractional), and phase (radians).

+
+
Type:
+

float

+
+
+
+ +

Notes

+

This task generates gain time streams which are Gaussian distributed with +covariance

+
+\[C_{ij} = \sigma^2 \exp{\left(-\frac{1}{2 \xi^2}(t_i - t_j)^2\right)}\]
+

As the time stream is generated in separate pieces, to ensure that there is +consistency between them each gain time stream is drawn as a constrained +realisation against the previous file.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.synthesis.gain.RandomSiderealGains[source]
+

Bases: RandomGains, SiderealGains

+

Generate random gains on a Sidereal grid.

+

See the documentation for RandomGains and SiderealGains for more detail.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+ +
+
+class draco.synthesis.gain.SiderealGains[source]
+

Bases: BaseGains

+

Task for simulating sidereal gains.

+

This base class is useful for generating gain errors in sidereal time. +The simulation period is set by start_time and end_time and does not +need any input to process.

+
+
+start_time, end_time
+

Start and end times of the gain timestream to simulate. Needs to be either a +float (UNIX time) or a datetime objects in UTC. This determines the set +of LSDs to generate data for.

+
+
Type:
+

float or datetime

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Generate a gain timestream for the inputs and times in data.

+
+
Returns:
+

gain – Simulated gain errors in sidereal time.

+
+
Return type:
+

containers.SiderealGainData

+
+
+
+ +
+
+setup(bt, sstream)[source]
+

Set up an observer and the data to use for this simulation.

+
+
Parameters:
+
    +
  • bt (beamtransfer.BeamTransfer or manager.ProductManager) – Sets up an observer holding the geographic location of the telscope.

  • +
  • sstream (containers.SiderealStream) – The sidereal data to use for this gain simulation.

  • +
+
+
+
+ +
+ +
+
+draco.synthesis.gain.constrained_gaussian_realisation(x, corrfunc, n, x2, y2, rcond=1e-12)[source]
+

Generate a constrained Gaussian random field.

+

Given a correlation function generate a Gaussian random field that is +consistent with an existing set of values of parameter y2 located at +co-ordinates in parameter x2.

+
+
Parameters:
+
    +
  • x (np.ndarray[npoints] or np.ndarray[npoints, ndim]) – Co-ordinates of points to generate.

  • +
  • corrfunc (function(x) -> covariance matrix) – Function that take (vectorized) co-ordinates and returns their +covariance functions.

  • +
  • n (integer) – Number of realisations to generate.

  • +
  • x2 (np.ndarray[npoints] or np.ndarray[npoints, ndim]) – Co-ordinates of existing points.

  • +
  • y2 (np.ndarray[npoints] or np.ndarray[n, npoints]) – Existing values of the random field.

  • +
  • rcond (float, optional) – Ignore eigenmodes smaller than rcond times the largest eigenvalue.

  • +
+
+
Returns:
+

y – Realisations of the gaussian field.

+
+
Return type:
+

np.ndarray[n, npoints]

+
+
+
+ +
+
+draco.synthesis.gain.gaussian_realisation(x, corrfunc, n, rcond=1e-12)[source]
+

Generate a Gaussian random field.

+
+
Parameters:
+
    +
  • x (np.ndarray[npoints] or np.ndarray[npoints, ndim]) – Co-ordinates of points to generate.

  • +
  • corrfunc (function(x) -> covariance matrix) – Function that take (vectorized) co-ordinates and returns their +covariance functions.

  • +
  • n (integer) – Number of realisations to generate.

  • +
  • rcond (float, optional) – Ignore eigenmodes smaller than rcond times the largest eigenvalue.

  • +
+
+
Returns:
+

y – Realisations of the gaussian field.

+
+
Return type:
+

np.ndarray[n, npoints]

+
+
+
+ +
+
+draco.synthesis.gain.generate_fluctuations(x, corrfunc, n, prev_x, prev_fluc)[source]
+

Generate correlated random streams.

+

Generates a Gaussian field from the given correlation function and (potentially) +correlated with prior data.

+
+
Parameters:
+
    +
  • x (np.ndarray[npoints]) – Coordinates of samples in the new stream.

  • +
  • corrfunc (function) – See documentation of gaussian_realisation.

  • +
  • prev_x (np.ndarray[npoints]) – Coordinates of previous samples. Ignored if prev_fluc is None.

  • +
  • prev_fluc (np.ndarray[npoints]) – Values of previous samples. If None the stream is initialised.

  • +
  • n (int) – Number of realisations to generate.

  • +
+
+
Returns:
+

y – Realisations of the stream.

+
+
Return type:
+

np.ndarray[n, npoints]

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.synthesis.html b/docs/_autosummary/draco.synthesis.html new file mode 100644 index 000000000..1193bb91d --- /dev/null +++ b/docs/_autosummary/draco.synthesis.html @@ -0,0 +1,144 @@ + + + + + + + draco.synthesis — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.synthesis

+ + + + + + + + + + + + +

gain

Tasks for generating random gain fluctuations in the data and stacking them.

noise

Add the effects of instrumental noise into the simulation.

stream

Tasks for simulating sidereal and time stream data.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.synthesis.noise.html b/docs/_autosummary/draco.synthesis.noise.html new file mode 100644 index 000000000..9280b32ec --- /dev/null +++ b/docs/_autosummary/draco.synthesis.noise.html @@ -0,0 +1,381 @@ + + + + + + + draco.synthesis.noise — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.synthesis.noise

+

Add the effects of instrumental noise into the simulation.

+

This is separated out into multiple tasks. The first, ReceiverTemperature +adds in the effects of instrumental noise bias into the data. The second, +SampleNoise, takes a timestream which is assumed to be the expected (or +average) value and returns an observed time stream. The :class: GaussianNoise +adds in the effects of a Gaussian distributed noise into visibility data. +The :class: GaussianNoiseDataset replaces visibility data with Gaussian distributed noise, +using the variance of the noise estimate in the existing data.

+

Classes

+ + + + + + + + + + + + + + + +

GaussianNoise()

Add Gaussian distributed noise to a visibility dataset.

GaussianNoiseDataset()

Generates a Gaussian distributed noise dataset using the noise estimates of an existing dataset.

ReceiverTemperature()

Add a basic receiver temperature term into the data.

SampleNoise()

Add properly distributed noise to a visibility dataset.

+
+
+class draco.synthesis.noise.GaussianNoise[source]
+

Bases: SingleTask, RandomTask

+

Add Gaussian distributed noise to a visibility dataset.

+

Note that this is an approximation to the actual noise distribution good only +when T_recv >> T_sky and delta_time * delta_freq >> 1.

+
+
+ndays
+

Multiplies the number of samples in each measurement.

+
+
Type:
+

float

+
+
+
+ +
+
+set_weights
+

Set the weights to the appropriate values.

+
+
Type:
+

bool

+
+
+
+ +
+
+add_noise
+

Add Gaussian noise to the visibilities. By default this is True, but it may be +desirable to only set the weights.

+
+
Type:
+

bool

+
+
+
+ +
+
+recv_temp
+

The temperature of the noise to add.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Generate a noisy dataset.

+
+
Parameters:
+

data (containers.SiderealStream or containers.TimeStream) – The expected (i.e. noiseless) visibility dataset. Note the modification +is done in place.

+
+
Returns:
+

data_noise – The sampled (i.e. noisy) visibility dataset.

+
+
Return type:
+

same as parameter data

+
+
+
+ +
+
+setup(manager=None)[source]
+

Set the telescope instance if a manager object is given.

+

This is used to simulate noise for visibilities that are stacked +over redundant baselines.

+
+
Parameters:
+

manager (manager.ProductManager, optional) – The telescope/manager used to set the redundancy. If not set, +redundancy is derived from the data.

+
+
+
+ +
+ +
+
+class draco.synthesis.noise.GaussianNoiseDataset[source]
+

Bases: SingleTask, RandomTask

+

Generates a Gaussian distributed noise dataset using the noise estimates of an existing dataset.

+
+
+dataset
+

The dataset to fill with gaussian noise. If set to ‘vis’, will ensure +autos are real. If not set, will look for a default dataset in a list +of known containers.

+
+
Type:
+

string

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Generates a Gaussian distributed noise dataset given the provided dataset’s visibility weights.

+
+
Parameters:
+

data (VisContainer) – Any dataset which contains a vis and weight attribute. +Note the modification is done in place.

+
+
Returns:
+

data_noise – The previous dataset with the visibility replaced with +a Gaussian distributed noise realisation.

+
+
Return type:
+

same as parameter data

+
+
+
+ +
+ +
+
+class draco.synthesis.noise.ReceiverTemperature[source]
+

Bases: SingleTask

+

Add a basic receiver temperature term into the data.

+

This class adds in an uncorrelated, frequency and time independent receiver +noise temperature to the data. As it is uncorrelated this will only affect +the auto-correlations. Note this only adds in the offset to the visibility, +to add the corresponding random fluctuations to subsequently use the +SampleNoise task.

+
+
+recv_temp
+

The receiver temperature in Kelvin.

+
+
Type:
+

float

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data)[source]
+

Iterate over the products to find the auto-correlations and add the noise into them.

+
+ +
+ +
+
+class draco.synthesis.noise.SampleNoise[source]
+

Bases: SingleTask, RandomTask

+

Add properly distributed noise to a visibility dataset.

+

This task draws properly (complex Wishart) distributed samples from an input +visibility dataset which is assumed to represent the expectation.

+

See http://link.springer.com/article/10.1007%2Fs10440-010-9599-x for a +discussion of the Bartlett decomposition for complex Wishart distributed +quantities.

+
+
+sample_frac
+

Multiplies the number of samples in each measurement. For instance this +could be a duty cycle if the correlator was not keeping up, or could be +larger than one if multiple measurements were combined.

+
+
Type:
+

float

+
+
+
+ +
+
+set_weights
+

Set the weights to the appropriate values.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(data_exp)[source]
+

Generate a noisy dataset.

+
+
Parameters:
+

data_exp (containers.SiderealStream or containers.TimeStream) – The expected (i.e. noiseless) visibility dataset. Must be the full +triangle. Make sure you have added an instrumental noise bias if you +want instrumental noise.

+
+
Returns:
+

data_samp – The sampled (i.e. noisy) visibility dataset.

+
+
Return type:
+

same as parameter data_exp

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.synthesis.stream.html b/docs/_autosummary/draco.synthesis.stream.html new file mode 100644 index 000000000..fd3a81d23 --- /dev/null +++ b/docs/_autosummary/draco.synthesis.stream.html @@ -0,0 +1,354 @@ + + + + + + + draco.synthesis.stream — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.synthesis.stream

+

Tasks for simulating sidereal and time stream data.

+

A typical pattern would be to turn a map into a +containers.SiderealStream with the SimulateSidereal task, then +expand any redundant products with ExpandProducts and finally generate +a set of time stream files with MakeTimeStream.

+

Classes

+ + + + + + + + + + + + + + + +

ExpandProducts()

Un-wrap collated products to full triangle.

MakeSiderealDayStream()

Task for simulating a set of sidereal days from a given stream.

MakeTimeStream()

Generate a series of time streams files from a sidereal stream.

SimulateSidereal()

Create a simulated sidereal dataset from an input map.

+
+
+class draco.synthesis.stream.ExpandProducts[source]
+

Bases: SingleTask

+

Un-wrap collated products to full triangle.

+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(sstream)[source]
+

Transform a sidereal stream to having a full product matrix.

+
+
Parameters:
+

sstream (containers.SiderealStream) – Sidereal stream to unwrap.

+
+
Returns:
+

new_sstream – Unwrapped sidereal stream.

+
+
Return type:
+

containers.SiderealStream

+
+
+
+ +
+
+setup(telescope)[source]
+

Get a reference to the telescope class.

+
+
Parameters:
+

telescope (drift.core.TransitTelescope) – Telescope object to use

+
+
+
+ +
+ +
+
+class draco.synthesis.stream.MakeSiderealDayStream[source]
+

Bases: SingleTask

+

Task for simulating a set of sidereal days from a given stream.

+

This creates a copy of the base stream for every LSD within the provided time +range.

+
+
+start_time, end_time
+

Start and end times of the sidereal streams to simulate. Needs to be either a +float (UNIX time) or a datetime objects in UTC.

+
+
Type:
+

float or datetime

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Generate a sidereal stream for the specific sidereal day.

+
+
Returns:
+

ss – Simulated sidereal day stream.

+
+
Return type:
+

containers.SiderealStream

+
+
+
+ +
+
+setup(bt, sstream)[source]
+

Set up an observer and the data to use for this simulation.

+
+
Parameters:
+
    +
  • bt (beamtransfer.BeamTransfer or manager.ProductManager) – Sets up an observer holding the geographic location of the telscope.

  • +
  • sstream (containers.SiderealStream) – The base sidereal data to use for this simulation.

  • +
+
+
+
+ +
+ +
+
+class draco.synthesis.stream.MakeTimeStream[source]
+

Bases: SingleTask

+

Generate a series of time streams files from a sidereal stream.

+
+
Parameters:
+
    +
  • start_time (float or datetime) – Start and end times of the timestream to simulate. Needs to be either a +float (UNIX time) or a datetime objects in UTC.

  • +
  • end_time (float or datetime) – Start and end times of the timestream to simulate. Needs to be either a +float (UNIX time) or a datetime objects in UTC.

  • +
  • integration_time (float, optional) – Integration time in seconds. Takes precedence over integration_frame_exp.

  • +
  • integration_frame_exp (int, optional) – Specify the integration time in frames. The integration time is +2**integration_frame_exp * 2.56 us.

  • +
  • samples_per_file (int, optional) – Number of samples per file.

  • +
+
+
+

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Create a timestream file.

+
+
Returns:
+

tstream – Time stream object.

+
+
Return type:
+

containers.TimeStream

+
+
+
+ +
+
+setup(sstream, manager)[source]
+

Get the sidereal stream to turn into files.

+
+
Parameters:
+
    +
  • sstream (SiderealStream) – The sidereal data to use.

  • +
  • manager (ProductManager or BeamTransfer) – Beam Transfer and telescope manager

  • +
+
+
+
+ +
+ +
+
+class draco.synthesis.stream.SimulateSidereal[source]
+

Bases: SingleTask

+

Create a simulated sidereal dataset from an input map.

+
+
+stacked
+

When set, in the case that the beam transfer matrices are not full triangle +treat them as having been generated by collating the baselines of full triangle +data, and set appropriate index_map/stack and reverse_map/stack entries. If +not set, treat the entries as having been generated by down selecting the full +set of baselines, and thus don’t create the stack entries. Default is True.

+
+
Type:
+

bool

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process(map_)[source]
+

Simulate a SiderealStream.

+
+
Parameters:
+

map (containers.Map) – The sky map to process to into a sidereal stream. Frequencies in the +map, must match the Beam Transfer matrices.

+
+
Returns:
+

    +
  • ss (SiderealStream) – Stacked sidereal day.

  • +
  • feeds (list of CorrInput) – Description of the feeds simulated.

  • +
+

+
+
+
+ +
+
+setup(bt)[source]
+

Setup the simulation.

+
+
Parameters:
+

bt (ProductManager or BeamTransfer) – Beam Transfer maanger.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.util.exception.html b/docs/_autosummary/draco.util.exception.html new file mode 100644 index 000000000..1a206f85e --- /dev/null +++ b/docs/_autosummary/draco.util.exception.html @@ -0,0 +1,151 @@ + + + + + + + draco.util.exception — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.util.exception

+

Exceptions for draco.

+

Exceptions

+ + + + + + +

ConfigError

Raised due to an error with a task configuration.

+
+
+exception draco.util.exception.ConfigError[source]
+

Bases: Exception

+

Raised due to an error with a task configuration.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.util.html b/docs/_autosummary/draco.util.html new file mode 100644 index 000000000..e707f134a --- /dev/null +++ b/docs/_autosummary/draco.util.html @@ -0,0 +1,160 @@ + + + + + + + draco.util — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.util

+ + + + + + + + + + + + + + + + + + + + + + + + +

exception

Exceptions for draco.

random

Utilities for drawing random numbers.

regrid

Routines for regridding irregular data using a Lanczos/Wiener filtering approach.

rfi

Collection of routines for RFI excision.

testing

draco test utils.

tools

Collection of miscellaneous routines.

truncate

draco truncation utils

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.util.random.html b/docs/_autosummary/draco.util.random.html new file mode 100644 index 000000000..0ab19b18a --- /dev/null +++ b/docs/_autosummary/draco.util.random.html @@ -0,0 +1,391 @@ + + + + + + + draco.util.random — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.util.random

+

Utilities for drawing random numbers.

+

Functions

+ + + + + + + + + + + + + + + + + + + + + +

complex_normal([loc, scale, size, dtype, ...])

Get a set of complex normal variables.

complex_wishart(C, n[, rng])

Draw a complex Wishart matrix.

default_rng()

Returns an instance of the default random number generator to use.

mpi_random_seed(seed[, extra, gen])

Use a specific random seed and return to the original state on exit.

standard_complex_normal(shape[, dtype, rng])

Get a set of standard complex normal variables.

standard_complex_wishart(m, n[, rng])

Draw a standard Wishart matrix.

+

Classes

+ + + + + + + + + +

MultithreadedRNG([seed, threads, bitgen])

A multithreaded random number generator.

RandomTask()

A base class for MPI tasks that need to generate random numbers.

+
+
+class draco.util.random.MultithreadedRNG(seed: int | None = None, threads: int | None = None, bitgen: BitGenerator | None = None)[source]
+

Bases: Generator

+

A multithreaded random number generator.

+

This wraps specific methods to allow generation across multiple threads. See +PARALLEL_METHODS for the specific methods wrapped.

+
+
Parameters:
+
    +
  • seed – The seed to use.

  • +
  • nthreads – The number of threads to use. If not set, this tries to get the number from the +OMP_NUM_THREADS environment variable, or just uses 4 if that is also not set.

  • +
  • bitgen – The BitGenerator to use, if not set this uses _default_bitgen.

  • +
+
+
+
+ +
+
+class draco.util.random.RandomTask[source]
+

Bases: MPILoggedTask

+

A base class for MPI tasks that need to generate random numbers.

+
+
+seed
+

Set the seed for use in the task. If not set, a random seed is generated and +broadcast to all ranks. The seed being used is logged, to repeat a previous +run, simply set this as the seed parameter.

+
+
Type:
+

int, optional

+
+
+
+ +
+
+threads
+

Set the number of threads to use for the random number generator. If not +explicitly set this will use the value of the OMP_NUM_THREADS environment +variable, or fall back to four.

+
+
Type:
+

int, optional

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+property local_seed: int
+

Get the seed to be used on this rank.

+
+

Warning

+

Generating the seed is a collective operation if the seed is not set, +and so all ranks must participate in the first access of this property.

+
+
+ +
+
+property rng: Generator
+

A random number generator for this task.

+
+

Warning

+

Initialising the RNG is a collective operation if the seed is not set, +and so all ranks must participate in the first access of this property.

+
+
+
Returns:
+

rng – A deterministically seeded random number generator suitable for use in +MPI jobs.

+
+
Return type:
+

np.random.Generator

+
+
+
+ +
+ +
+
+draco.util.random.complex_normal(loc=0.0, scale=1.0, size=None, dtype=None, rng=None, out=None)[source]
+

Get a set of complex normal variables.

+

By default generate standard complex normal variables.

+
+
Parameters:
+
    +
  • size (tuple) – Shape of the array of variables.

  • +
  • loc (np.ndarray or complex float, optional) – The mean of the complex output. Can be any array which broadcasts against +an array of size.

  • +
  • scale (np.ndarray or float, optional) – The standard deviation of the complex output. Can be any array which +broadcasts against an array of size.

  • +
  • dtype ({np.complex64, np.complex128}, optional) – Output datatype.

  • +
  • rng (np.random.Generator, optional) – Generator object to use.

  • +
  • out (np.ndarray[shape], optional) – Array to place output directly into.

  • +
+
+
Returns:
+

out – Complex gaussian variates.

+
+
Return type:
+

np.ndarray[shape]

+
+
+
+ +
+
+draco.util.random.complex_wishart(C, n, rng=None)[source]
+

Draw a complex Wishart matrix.

+
+
Parameters:
+
    +
  • C (np.ndarray[:, :]) – Expected covaraince matrix.

  • +
  • n (integer) – Number of measurements the covariance matrix is estimated from.

  • +
  • rng (np.random.Generator, optional) – Random number generator to use.

  • +
+
+
Returns:
+

C_samp – Sample covariance matrix.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.util.random.default_rng()[source]
+

Returns an instance of the default random number generator to use.

+

This creates a randomly seeded generator using the fast SFC64 bit generator +underneath. This is only initialise on the first call, subsequent calls will +return the same Generator.

+
+
Returns:
+

rng

+
+
Return type:
+

np.random.Generator

+
+
+
+ +
+
+draco.util.random.mpi_random_seed(seed, extra=0, gen=None)[source]
+

Use a specific random seed and return to the original state on exit.

+

This is designed to work for MPI computations, incrementing the actual seed of +each process by the MPI rank. Overall each process gets the numpy seed: +numpy_seed = seed + mpi_rank + 4096 * extra. This can work for either the +global numpy.random context or for new np.random.Generator.

+
+
Parameters:
+
    +
  • seed (int) – Base seed to set. If seed is None, re-seed randomly.

  • +
  • extra (int, optional) – An extra part of the seed, which should be changed for calculations +using the same seed, but that want different random sequences.

  • +
  • gen – A RandomGen bit_generator whose internal seed state we are going to +influence.

  • +
+
+
Yields:
+
    +
  • If we are setting the numpy.random context, nothing is yielded.

  • +
  • class: Generator – If we are setting the RandomGen bit_generator, it will be returned.

  • +
+
+
+
+ +
+
+draco.util.random.standard_complex_normal(shape, dtype=None, rng=None)[source]
+

Get a set of standard complex normal variables.

+
+
Parameters:
+
    +
  • shape (tuple) – Shape of the array of variables.

  • +
  • dtype ({np.complex64, np.complex128}, optional) – Output datatype.

  • +
  • rng (np.random.Generator, optional) – Generator object to use.

  • +
+
+
Returns:
+

out – Complex gaussian variates.

+
+
Return type:
+

np.ndarray[shape]

+
+
+
+ +
+
+draco.util.random.standard_complex_wishart(m, n, rng=None)[source]
+

Draw a standard Wishart matrix.

+
+
Parameters:
+
    +
  • m (integer) – Number of variables (i.e. size of matrix).

  • +
  • n (integer) – Number of measurements the covariance matrix is estimated from.

  • +
  • rng (np.random.Generator, optional) – Random number generator to use.

  • +
+
+
Returns:
+

B

+
+
Return type:
+

np.ndarray[m, m]

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.util.regrid.html b/docs/_autosummary/draco.util.regrid.html new file mode 100644 index 000000000..1d0917420 --- /dev/null +++ b/docs/_autosummary/draco.util.regrid.html @@ -0,0 +1,251 @@ + + + + + + + draco.util.regrid — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.util.regrid

+

Routines for regridding irregular data using a Lanczos/Wiener filtering approach.

+

This is described in some detail in doclib:173.

+

Functions

+ + + + + + + + + + + + + + + +

band_wiener(R, Ni, Si, y, bw)

Calculate the Wiener filter assuming various bandedness properties.

lanczos_forward_matrix(x, y[, a, periodic])

Lanczos interpolation matrix.

lanczos_inverse_matrix(x, y[, a, cond])

Regrid data using a maximum likelihood inverse Lanczos.

lanczos_kernel(x, a)

Lanczos interpolation kernel.

+
+
+draco.util.regrid.band_wiener(R, Ni, Si, y, bw)[source]
+

Calculate the Wiener filter assuming various bandedness properties.

+

In particular this asserts that a particular element in the filtered +output will only couple to the nearest bw elements. Equivalently, this +is that the covariance matrix will be band diagonal. This allows us to use +fast routines to generate the solution.

+

Note that the inverse noise estimate returned is \(\mathrm{diag}(\mathbf{R}^T +\mathbf{N}^{-1} \mathbf{R})\) and not the full Bayesian estimate including a +contribution from the signal covariance.

+
+
Parameters:
+
    +
  • R (np.ndarray[m, n]) – Transfer matrix for the Wiener filter.

  • +
  • Ni (np.ndarray[k, n]) – Inverse noise matrix. Noise assumed to be uncorrelated (i.e. diagonal matrix).

  • +
  • Si (np.narray[m]) – Inverse signal matrix. Signal model assumed to be uncorrelated (i.e. diagonal +matrix).

  • +
  • y (np.ndarray[k, n]) – Data to apply to.

  • +
  • bw (int) – Bandwidth, i.e. how many elements couple together.

  • +
+
+
Returns:
+

    +
  • xhat (np.ndarray[k, m]) – Filtered data.

  • +
  • nw (np.ndarray[k, m]) – Estimate of variance of each element.

  • +
+

+
+
+
+ +
+
+draco.util.regrid.lanczos_forward_matrix(x, y, a=5, periodic=False)[source]
+

Lanczos interpolation matrix.

+
+
Parameters:
+
    +
  • x (np.ndarray[m]) – Points we have data at. Must be regularly spaced.

  • +
  • y (np.ndarray[n]) – Point we want to interpolate data onto.

  • +
  • a (integer, optional) – Lanczos width parameter.

  • +
  • periodic (boolean, optional) – Treat input points as periodic.

  • +
+
+
Returns:
+

matrix – Lanczos regridding matrix. Apply to data with np.dot(matrix, data).

+
+
Return type:
+

np.ndarray[m, n]

+
+
+
+ +
+
+draco.util.regrid.lanczos_inverse_matrix(x, y, a=5, cond=0.1)[source]
+

Regrid data using a maximum likelihood inverse Lanczos.

+
+
Parameters:
+
    +
  • x (np.ndarray[m]) – Points to regrid data onto. Must be regularly spaced.

  • +
  • y (np.ndarray[n]) – Points we have data at. Irregular spacing.

  • +
  • a (integer, optional) – Lanczos width parameter.

  • +
  • cond (float) – Relative condition number for pseudo-inverse.

  • +
+
+
Returns:
+

matrix – Lanczos regridding matrix. Apply to data with np.dot(matrix, data).

+
+
Return type:
+

np.ndarray[m, n]

+
+
+
+ +
+
+draco.util.regrid.lanczos_kernel(x, a)[source]
+

Lanczos interpolation kernel.

+
+
Parameters:
+
    +
  • x (array_like) – Point separation.

  • +
  • a (integer) – Lanczos kernel width.

  • +
+
+
Returns:
+

kernel

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.util.rfi.html b/docs/_autosummary/draco.util.rfi.html new file mode 100644 index 000000000..779a83273 --- /dev/null +++ b/docs/_autosummary/draco.util.rfi.html @@ -0,0 +1,260 @@ + + + + + + + draco.util.rfi — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.util.rfi

+

Collection of routines for RFI excision.

+

Functions

+ + + + + + + + + + + + + + + +

sir(basemask[, eta, only_freq, only_time])

Apply the SIR operator over the frequency and time axes for each product.

sir1d(basemask[, eta])

Numpy implementation of the scale-invariant rank (SIR) operator.

sumthreshold(data[, max_m, start_flag, ...])

SumThreshold outlier detection algorithm.

sumthreshold_py(data[, max_m, start_flag, ...])

SumThreshold outlier detection algorithm.

+
+
+draco.util.rfi.sir(basemask, eta=0.2, only_freq=False, only_time=False)[source]
+

Apply the SIR operator over the frequency and time axes for each product.

+

This is a wrapper for sir1d. It loops over times, applying sir1d +across the frequency axis. It then loops over frequencies, applying sir1d +across the time axis. It returns the logical OR of these two masks.

+
+
Parameters:
+
    +
  • basemask (np.ndarray[nfreq, nprod, ntime] of boolean type) – The previously generated threshold mask. +1 (True) for masked points, 0 (False) otherwise.

  • +
  • eta (float) – Aggressiveness of the method: with eta=0, no additional samples are +flagged and the function returns basemask. With eta=1, all samples +will be flagged.

  • +
  • only_freq (bool) – Only apply the SIR operator across the frequency axis.

  • +
  • only_time (bool) – Only apply the SIR operator across the time axis.

  • +
+
+
Returns:
+

mask – The mask after the application of the SIR operator.

+
+
Return type:
+

np.ndarray[nfreq, nprod, ntime] of boolean type

+
+
+
+ +
+
+draco.util.rfi.sir1d(basemask, eta=0.2)[source]
+

Numpy implementation of the scale-invariant rank (SIR) operator.

+

For more information, see arXiv:1201.3364v2.

+
+
Parameters:
+
    +
  • basemask (numpy 1D array of boolean type) – Array with the threshold mask previously generated. +1 (True) for flagged points, 0 (False) otherwise.

  • +
  • eta (float) – Aggressiveness of the method: with eta=0, no additional samples are +flagged and the function returns basemask. With eta=1, all samples +will be flagged. The authors in arXiv:1201.3364v2 seem to be convinced +that 0.2 is a mostly universally optimal value, but no optimization +has been done on CHIME data.

  • +
+
+
Returns:
+

mask – The mask after the application of the (SIR) operator. Same shape and +type as basemask.

+
+
Return type:
+

numpy 1D array of boolean type

+
+
+
+ +
+
+draco.util.rfi.sumthreshold(data, max_m=16, start_flag=None, threshold1=None, remove_median=True, correct_for_missing=True)
+

SumThreshold outlier detection algorithm.

+

See http://www.astro.rug.nl/~offringa/SumThreshold.pdf for description of +the algorithm.

+
+
Parameters:
+
    +
  • data (np.ndarray[:, :]) – The data to flag.

  • +
  • max_m (int, optional) – Maximum size to expand to.

  • +
  • start_flag (np.ndarray[:, :], optional) – A boolean array of the initially flagged data.

  • +
  • threshold1 (float, optional) – Initial threshold. By default use the 95 percentile.

  • +
  • remove_median (bool, optional) – Subtract the median of the full 2D dataset. Default is True.

  • +
  • correct_for_missing (bool, optional) – Correct for missing counts

  • +
+
+
Returns:
+

mask – Boolean array, with True entries marking outlier data.

+
+
Return type:
+

np.ndarray[:, :]

+
+
+
+ +
+
+draco.util.rfi.sumthreshold_py(data, max_m=16, start_flag=None, threshold1=None, remove_median=True, correct_for_missing=True)[source]
+

SumThreshold outlier detection algorithm.

+

See http://www.astro.rug.nl/~offringa/SumThreshold.pdf for description of +the algorithm.

+
+
Parameters:
+
    +
  • data (np.ndarray[:, :]) – The data to flag.

  • +
  • max_m (int, optional) – Maximum size to expand to.

  • +
  • start_flag (np.ndarray[:, :], optional) – A boolean array of the initially flagged data.

  • +
  • threshold1 (float, optional) – Initial threshold. By default use the 95 percentile.

  • +
  • remove_median (bool, optional) – Subtract the median of the full 2D dataset. Default is True.

  • +
  • correct_for_missing (bool, optional) – Correct for missing counts

  • +
+
+
Returns:
+

mask – Boolean array, with True entries marking outlier data.

+
+
Return type:
+

np.ndarray[:, :]

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.util.testing.html b/docs/_autosummary/draco.util.testing.html new file mode 100644 index 000000000..ee0e8cf2f --- /dev/null +++ b/docs/_autosummary/draco.util.testing.html @@ -0,0 +1,304 @@ + + + + + + + draco.util.testing — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.util.testing

+

draco test utils.

+

Functions

+ + + + + + +

mock_freq_data(freq, ntime, delaycut[, ...])

Make mock delay data with a constant delay spectrum up to a specified cut.

+

Classes

+ + + + + + + + + +

DummyTask()

Produce an empty data stream for testing.

RandomFreqData()

Generate a random sidereal stream with structure in delay.

+
+
+class draco.util.testing.DummyTask[source]
+

Bases: SingleTask

+

Produce an empty data stream for testing.

+
+
+total_len
+

Length of output data stream. Default: 1.

+
+
Type:
+

int

+
+
+
+ +
+
+tag
+

What to use as a tag for the produced data.

+
+
Type:
+

str

+
+
+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+process()[source]
+

Produce an empty stream and pass on.

+
+
Returns:
+

cont – Empty data stream.

+
+
Return type:
+

subclass of memh5.BasicCont

+
+
+
+ +
+ +
+
+class draco.util.testing.RandomFreqData[source]
+

Bases: RandomTask

+

Generate a random sidereal stream with structure in delay.

+
+
+num_realisation
+

How many to generate in subsequent process calls.

+
+ +
+
+num_correlated
+

The number of correlated realisations output per cycle.

+
+ +
+
+num_ra
+

The number of RA samples in the output.

+
+ +
+
+num_base
+

The number of baselines in the output.

+
+ +
+
+freq_start, freq_end
+

The start and end frequencies.

+
+ +
+
+num_freq
+

The number of frequency channels.

+
+ +
+
+delay_cut
+

The maximum delay in the data in us.

+
+ +
+
+noise
+

The RMS noise level.

+
+ +

Initialize pipeline task.

+

May be overridden with no arguments. Will be called after any +config.Property attributes are set and after ‘input’ and ‘requires’ +keys are set up.

+
+
+next() SiderealStream | List[SiderealStream][source]
+

Generate correlated sidereal streams.

+
+
Returns:
+

Either a single stream (if num_correlated=None), or a list of correlated +streams.

+
+
Return type:
+

streams

+
+
+
+ +
+ +
+
+draco.util.testing.mock_freq_data(freq: ndarray, ntime: int, delaycut: float, ndata: int | None = None, noise: float = 0.0, bad_freq: ndarray | None = None, rng: Generator | None = None) Tuple[ndarray, ndarray][source]
+

Make mock delay data with a constant delay spectrum up to a specified cut.

+
+
Parameters:
+
    +
  • freq – Frequencies of each channel (in MHz).

  • +
  • ntime – Number of independent time samples.

  • +
  • delaycut – Cutoff in us.

  • +
  • ndata – Number of correlated data sets. If not set (i.e. None) then do no add a +dataset axis.

  • +
  • noise – RMS noise level in the data.

  • +
  • bad_freq – A list of bad frequencies to mask out.

  • +
  • rng – The random number generator to use.

  • +
+
+
Returns:
+

    +
  • data – The 2D/3D data array [dataset, freq, time]. If ndata is None then the dataset +axis is dropped.

  • +
  • weights – The 2D weights data [freq, time].

  • +
+

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.util.tools.html b/docs/_autosummary/draco.util.tools.html new file mode 100644 index 000000000..2c3a6df93 --- /dev/null +++ b/docs/_autosummary/draco.util.tools.html @@ -0,0 +1,464 @@ + + + + + + + draco.util.tools — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.util.tools

+

Collection of miscellaneous routines.

+

Miscellaneous tasks should be placed in draco.core.misc.

+

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

apply_gain(vis, gain[, axis, out, prod_map])

Apply per input gains to a set of visibilities packed in upper triangular format.

baseline_vector(index_map, telescope)

Baseline vectors in meters.

calculate_redundancy(input_flags, prod_map, ...)

Calculates the number of redundant baselines that were stacked to form each unique baseline.

cmap(i, j, n)

Given a pair of feed indices, return the pair index.

extract_diagonal(utmat[, axis])

Extract the diagonal elements of an upper triangular array.

find_inputs(input_index, inputs[, require_match])

Find the indices of inputs into a list of inputs.

find_key(key_list, key)

Find the index of a key in a list of keys.

find_keys(key_list, keys[, require_match])

Find the indices of keys into a list of keys.

icmap(ix, n)

Inverse feed map.

polarization_map(index_map, telescope[, ...])

Map the visibilities corresponding to entries in pol = ['XX', 'XY', 'YX', 'YY'].

redefine_stack_index_map(telescope, inputs, ...)

Ensure baselines between unmasked inputs are used to represent each stack.

window_generalised(x[, window])

A generalised high-order window at arbitrary locations.

+
+
+draco.util.tools.apply_gain(vis, gain, axis=1, out=None, prod_map=None)[source]
+

Apply per input gains to a set of visibilities packed in upper triangular format.

+

This allows us to apply the gains while minimising the intermediate +products created.

+
+
Parameters:
+
    +
  • vis (np.ndarray[..., nprod, ...]) – Array of visibility products.

  • +
  • gain (np.ndarray[..., ninput, ...]) – Array of gains. One gain per input.

  • +
  • axis (integer, optional) – The axis along which the inputs (or visibilities) are +contained.

  • +
  • out (np.ndarray) – Array to place output in. If None create a new +array. This routine can safely use out = vis.

  • +
  • prod_map (ndarray of integer pairs) – Gives the mapping from product axis to input pairs. If not supplied, +icmap() is used.

  • +
+
+
Returns:
+

out – Visibility array with gains applied. Same shape as vis.

+
+
Return type:
+

np.ndarray

+
+
+
+ +
+
+draco.util.tools.baseline_vector(index_map, telescope)[source]
+

Baseline vectors in meters.

+
+
Parameters:
+
    +
  • index_map (h5py.group or dict) – Index map to map into polarizations. Must contain a stack +entry and an input entry.

  • +
  • telescope – Telescope object containing feed information.

  • +
+
+
Returns:
+

bvec_m – Array of shape (2, nstack). The 2D baseline vector +(in meters) for each visibility in index_map[‘stack’]

+
+
Return type:
+

array

+
+
+
+ +
+
+draco.util.tools.calculate_redundancy(input_flags, prod_map, stack_index, nstack)[source]
+

Calculates the number of redundant baselines that were stacked to form each unique baseline.

+

Accounts for the fact that some fraction of the inputs are flagged as bad at any given time.

+
+
Parameters:
+
    +
  • input_flags (np.ndarray [ninput, ntime]) – Array indicating which inputs were good at each time. +Non-zero value indicates that an input was good.

  • +
  • prod_map (np.ndarray[nprod]) – The products that were included in the stack. +Typically found in the index_map[‘prod’] attribute of the +containers.TimeStream or containers.SiderealStream object.

  • +
  • stack_index (np.ndarray[nprod]) – The index of the stack axis that each product went into. +Typically found in reverse_map[‘stack’][‘stack’] attribute +of the containers.Timestream or containers.SiderealStream object.

  • +
  • nstack (int) – Total number of unique baselines.

  • +
+
+
Returns:
+

redundancy – Array indicating the total number of redundant baselines +with good inputs that were stacked into each unique baseline.

+
+
Return type:
+

np.ndarray[nstack, ntime]

+
+
+
+ +
+
+draco.util.tools.cmap(i, j, n)[source]
+

Given a pair of feed indices, return the pair index.

+
+
Parameters:
+
    +
  • i (integer) – Feed index.

  • +
  • j (integer) – Feed index.

  • +
  • n (integer) – Total number of feeds.

  • +
+
+
Returns:
+

pi – Pair index.

+
+
Return type:
+

integer

+
+
+
+ +
+
+draco.util.tools.extract_diagonal(utmat, axis=1)[source]
+

Extract the diagonal elements of an upper triangular array.

+
+
Parameters:
+
    +
  • utmat (np.ndarray[..., nprod, ...]) – Upper triangular array.

  • +
  • axis (int, optional) – Axis of array that is upper triangular.

  • +
+
+
Returns:
+

diag – Diagonal of the array.

+
+
Return type:
+

np.ndarray[…, ninput, …]

+
+
+
+ +
+
+draco.util.tools.find_inputs(input_index, inputs, require_match=False)[source]
+

Find the indices of inputs into a list of inputs.

+

This behaves similarly to find_keys but will automatically choose the key to +match on.

+
+
Parameters:
+
    +
  • input_index (np.ndarray) – Inputs to search

  • +
  • inputs (np.ndarray) – Inputs to find

  • +
  • require_match (bool) – Require that input_index contain every element of inputs, +and if not, raise ValueError.

  • +
+
+
Returns:
+

indices – List of the same length as inputs containing +the indices of inputs in input_inswx. If require_match +is False, then this can also contain None for inputs +that are not contained in input_index.

+
+
Return type:
+

list of int or None

+
+
+
+ +
+
+draco.util.tools.find_key(key_list, key)[source]
+

Find the index of a key in a list of keys.

+

This is a wrapper for the list method index +that can search any interable (not just lists) +and will return None if the key is not found.

+
+
Parameters:
+
    +
  • key_list (iterable) – Iterable containing keys to search

  • +
  • key (object to be searched) – Keys to search for

  • +
+
+
Returns:
+

index – The index of key in key_list. +If key_list does not contain key, +then None is returned.

+
+
Return type:
+

int or None

+
+
+
+ +
+
+draco.util.tools.find_keys(key_list, keys, require_match=False)[source]
+

Find the indices of keys into a list of keys.

+
+
Parameters:
+
    +
  • key_list (iterable) – Iterable of keys to search

  • +
  • keys (iterable) – Keys to search for

  • +
  • require_match (bool) – Require that key_list contain every element of keys, +and if not, raise ValueError.

  • +
+
+
Returns:
+

indices – List of the same length as keys containing +the indices of keys in key_list. If require_match +is False, then this can also contain None for keys +that are not contained in key_list.

+
+
Return type:
+

list of int or None

+
+
+
+ +
+
+draco.util.tools.icmap(ix, n)[source]
+

Inverse feed map.

+
+
Parameters:
+
    +
  • ix (integer) – Pair index.

  • +
  • n (integer) – Total number of feeds.

  • +
+
+
Returns:
+

fi, fj – Feed indices.

+
+
Return type:
+

integer

+
+
+
+ +
+
+draco.util.tools.polarization_map(index_map, telescope, exclude_autos=True)[source]
+

Map the visibilities corresponding to entries in pol = [‘XX’, ‘XY’, ‘YX’, ‘YY’].

+
+
Parameters:
+
    +
  • index_map (h5py.group or dict) – Index map to map into polarizations. Must contain a stack +entry and an input entry.

  • +
  • telescope – Telescope object containing feed information.

  • +
  • exclude_autos (bool) – If True (default), auto-correlations are set to -1.

  • +
+
+
Returns:
+

polmap – Array of size nstack. Each entry is the index to the +corresponding polarization in pol = [‘XX’, ‘XY’, ‘YX’, ‘YY’]

+
+
Return type:
+

array of int

+
+
+
+ +
+
+draco.util.tools.redefine_stack_index_map(telescope, inputs, prod, stack, reverse_stack)[source]
+

Ensure baselines between unmasked inputs are used to represent each stack.

+
+
Parameters:
+
    +
  • telescope – Telescope object containing feed information.

  • +
  • inputs (np.ndarray[ninput,] of dtype=('correlator_input', 'chan_id')) – The ‘correlator_input’ or ‘chan_id’ of the inputs in the stack.

  • +
  • prod (np.ndarray[nprod,] of dtype=('input_a', 'input_b')) – The correlation products as pairs of inputs.

  • +
  • stack (np.ndarray[nstack,] of dtype=('prod', 'conjugate')) – The index into the prod axis of a characteristic baseline included in the stack.

  • +
  • reverse_stack (np.ndarray[nprod,] of dtype=('stack', 'conjugate')) – The index into the stack axis that each prod belongs.

  • +
+
+
Returns:
+

    +
  • stack_new (np.ndarray[nstack,] of dtype=(‘prod’, ‘conjugate’)) – The updated stack index map, where each element is an index to a product +consisting of a pair of unmasked inputs.

  • +
  • stack_flag (np.ndarray[nstack,] of dtype=bool) – Boolean flag that is True if this element of the stack index map is now valid, +and False if none of the baselines that were stacked contained unmasked inputs.

  • +
+

+
+
+
+ +
+
+draco.util.tools.window_generalised(x, window='nuttall')[source]
+

A generalised high-order window at arbitrary locations.

+
+
Parameters:
+
    +
  • x (np.ndarray[n]) – Location to evaluate at. Values outside the range 0 to 1 are zero.

  • +
  • window (one of {'nuttall', 'blackman_nuttall', 'blackman_harris'}) – Type of window function to return.

  • +
+
+
Returns:
+

w – Window function.

+
+
Return type:
+

np.ndarray[n]

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_autosummary/draco.util.truncate.html b/docs/_autosummary/draco.util.truncate.html new file mode 100644 index 000000000..4c32c3c62 --- /dev/null +++ b/docs/_autosummary/draco.util.truncate.html @@ -0,0 +1,150 @@ + + + + + + + draco.util.truncate — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco.util.truncate

+

draco truncation utils

+

Functions

+ + + + + + + + + + + + +

bit_truncate(val, err)

bit_truncate_fixed(val, prec)

bit_truncate_weights(val, wgt, fallback)

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/beamform.html b/docs/_modules/draco/analysis/beamform.html new file mode 100644 index 000000000..ce3f3b402 --- /dev/null +++ b/docs/_modules/draco/analysis/beamform.html @@ -0,0 +1,1498 @@ + + + + + + draco.analysis.beamform — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.beamform

+"""Beamform visibilities to the location of known sources."""
+
+from typing import Tuple
+
+import healpy
+import numpy as np
+import scipy.interpolate
+from caput import config
+from caput import time as ctime
+from cora.util import units
+from skyfield.api import Angle, Star
+
+from ..core import containers, io, task
+from ..util._fast_tools import beamform
+from ..util.tools import (
+    baseline_vector,
+    calculate_redundancy,
+    invert_no_zero,
+    polarization_map,
+)
+
+# Constants
+NU21 = units.nu21
+C = units.c
+
+
+
+[docs] +class BeamFormBase(task.SingleTask): + """Base class for beam forming tasks. + + Defines a few useful methods. Not to be used directly + but as parent class for BeamForm and BeamFormCat. + + Attributes + ---------- + collapse_ha : bool + Sum over hour-angle/time to complete the beamforming. Default is True. + polarization : string + Determines the polarizations that will be output: + - 'I' : Stokes I only. + - 'full' : 'XX', 'XY', 'YX' and 'YY' in this order. (default) + - 'copol' : 'XX' and 'YY' only. + - 'stokes' : 'I', 'Q', 'U' and 'V' in this order. Not implemented. + weight : string + How to weight the redundant baselines when adding: + - 'natural' : each baseline weighted by its redundancy (default) + - 'uniform' : each baseline given equal weight + - 'inverse_variance' : each baseline weighted by the weight attribute + no_beam_model : string + Do not include a primary beam factor in the beamforming + weights, i.e., use uniform weighting as a function of hour angle + and declination. + timetrack : float + How long (in seconds) to track sources at each side of transit. + Default is 900 seconds. Total transit time will be 2 * timetrack. + variable_timetrack : bool + Scale the total time to track each source by the secant of the + source declination, so that all sources are tracked through + the same angle on the sky. Default is False. + freqside : int + Number of frequencies to process at each side of the source. + Default (None) processes all frequencies. + """ + + collapse_ha = config.Property(proptype=bool, default=True) + polarization = config.enum(["I", "full", "copol", "stokes"], default="full") + weight = config.enum(["natural", "uniform", "inverse_variance"], default="natural") + no_beam_model = config.Property(proptype=bool, default=False) + timetrack = config.Property(proptype=float, default=900.0) + variable_timetrack = config.Property(proptype=bool, default=False) + freqside = config.Property(proptype=int, default=None) + data_available = True + +
+[docs] + def setup(self, manager): + """Generic setup method. + + To be complemented by specific setup methods in daughter tasks. + + Parameters + ---------- + manager : either `ProductManager`, `BeamTransfer` or `TransitTelescope` + Contains a TransitTelescope object describing the telescope. + """ + # Get the TransitTelescope object + self.telescope = io.get_telescope(manager) + self.latitude = np.deg2rad(self.telescope.latitude) + + # Polarizations. + if self.polarization == "I": + self.process_pol = ["XX", "YY"] + self.return_pol = ["I"] + elif self.polarization == "full": + self.process_pol = ["XX", "XY", "YX", "YY"] + self.return_pol = self.process_pol + elif self.polarization == "copol": + self.process_pol = ["XX", "YY"] + self.return_pol = self.process_pol + elif self.polarization == "stokes": + self.process_pol = ["XX", "XY", "YX", "YY"] + self.return_pol = ["I", "Q", "U", "V"] + msg = "Stokes parameters are not implemented" + raise RuntimeError(msg) + else: + # This should never happen. config.enum should bark first. + msg = "Invalid polarization parameter: {0}" + msg = msg.format(self.polarization) + raise ValueError(msg) + + self.npol = len(self.process_pol) + + self.map_pol_feed = { + pstr: list(self.telescope.polarisation).index(pstr) for pstr in ["X", "Y"] + } + + # Ensure that if we are using variable time tracking, + # then we are also collapsing over hour angle. + if self.variable_timetrack: + if self.collapse_ha: + self.log.info( + "Tracking source for declination dependent amount of time " + "[%d seconds at equator]" % self.timetrack + ) + else: + raise NotImplementedError( + "Must collapse over hour angle if tracking " + "sources for declination dependent " + "amount of time." + ) + + else: + self.log.info( + "Tracking source for fixed amount of time [%d seconds]" % self.timetrack + )
+ + +
+[docs] + def process(self): + """Generic process method. + + Performs all the beamforming, but not the data parsing. + To be complemented by specific process methods in daughter tasks. + + Returns + ------- + formed_beam : `containers.FormedBeam` or `containers.FormedBeamHA` + Formed beams at each source. Shape depends on parameter + `collapse_ha`. + """ + # Perform data dependent beam initialization + self._initialize_beam_with_data() + + # Contruct containers for formed beams + if self.collapse_ha: + # Container to hold the formed beams + formed_beam = containers.FormedBeam( + freq=self.freq, + object_id=self.source_cat.index_map["object_id"], + pol=np.array(self.return_pol), + distributed=True, + ) + else: + # Container to hold the formed beams + formed_beam = containers.FormedBeamHA( + freq=self.freq, + ha=np.arange(self.nha, dtype=np.int64), + object_id=self.source_cat.index_map["object_id"], + pol=np.array(self.return_pol), + distributed=True, + ) + # Initialize container to zeros. + formed_beam.ha[:] = 0.0 + + formed_beam.attrs["tag"] = "_".join( + [tag for tag in [self.tag_data, self.tag_catalog] if tag is not None] + ) + + # Initialize container to zeros. + formed_beam.beam[:] = 0.0 + formed_beam.weight[:] = 0.0 + + # Copy catalog information + formed_beam["position"][:] = self.source_cat["position"][:] + if "redshift" in self.source_cat: + formed_beam["redshift"][:] = self.source_cat["redshift"][:] + else: + # TODO: If there is not redshift information, + # should I have a different formed_beam container? + formed_beam["redshift"]["z"][:] = 0.0 + formed_beam["redshift"]["z_error"][:] = 0.0 + + # Ensure container is distributed in frequency + formed_beam.redistribute("freq") + + if self.freqside is None: + # Indices of local frequency axis. Full axis if freqside is None. + f_local_indices = np.arange(self.ls, dtype=np.int32) + f_mask = np.zeros(self.ls, dtype=bool) + + fbb = formed_beam.beam[:] + fbw = formed_beam.weight[:] + + # For each source, beamform and populate container. + for src in range(self.nsource): + if src % 1000 == 0: + self.log.info(f"Source {src}/{self.nsource}") + + # Declination of this source + dec = np.radians(self.sdec[src]) + + if self.freqside is not None: + # Get the frequency bin this source is closest to. + freq_diff = abs(self.freq["centre"] - self.sfreq[src]) + sfreq_index = np.argmin(freq_diff) + # Start and stop indices to process in global frequency axis + freq_idx0 = np.amax([0, sfreq_index - self.freqside]) + freq_idx1 = np.amin([self.nfreq, sfreq_index + self.freqside + 1]) + # Mask in full frequency axis + f_mask = np.ones(self.nfreq, dtype=bool) + f_mask[freq_idx0:freq_idx1] = False + # Restrict frequency mask to local range + f_mask = f_mask[self.lo : (self.lo + self.ls)] + + # TODO: In principle I should be able to skip + # sources that have no indices to be processed + # in this rank. I am getting a NaN error, however. + # I may need an mpiutil.barrier() call before the + # return statement. + if f_mask.all(): + # If there are no indices to be processed in + # the local frequency range, skip source. + continue + + # Frequency indices to process in local range + f_local_indices = np.arange(self.ls, dtype=np.int32)[np.invert(f_mask)] + + if self.is_sstream: + # Get RA bin this source is closest to. + # Phasing will actually be done at src position. + sra_index = np.searchsorted(self.ra, self.sra[src]) + else: + # Cannot use searchsorted, because RA might not be + # monotonically increasing. Slower. + # Notice: in case there is more than one transit, + # this will pick a single transit quasi-randomly! + transit_diff = abs(self.ra - self.sra[src]) + sra_index = np.argmin(transit_diff) + # For now, skip sources that do not transit in the data + ra_cadence = self.ra[1] - self.ra[0] + if transit_diff[sra_index] > 1.5 * ra_cadence: + continue + + if self.variable_timetrack: + ha_side = int(self.ha_side / np.cos(dec)) + else: + ha_side = int(self.ha_side) + + # Compute hour angle array + ha_array, ra_index_range, ha_mask = self._ha_array( + self.ra, sra_index, self.sra[src], ha_side, self.is_sstream + ) + + # Arrays to store beams and weights for this source + # for all polarizations prior to combining polarizations + if self.collapse_ha: + formed_beam_full = np.zeros((self.npol, self.ls), dtype=np.float64) + weight_full = np.zeros((self.npol, self.ls), dtype=np.float64) + else: + formed_beam_full = np.zeros( + (self.npol, self.ls, self.nha), dtype=np.float64 + ) + weight_full = np.zeros((self.npol, self.ls, self.nha), dtype=np.float64) + + # Loop over polarisations + for pol, pol_str in enumerate(self.process_pol): + primary_beam = self._beamfunc(pol_str, dec, ha_array) + + # Fringestop and sum over products + # 'beamform' does not normalize sum. + this_formed_beam = beamform( + self.vis[pol], + self.sumweight[pol], + dec, + self.latitude, + np.cos(ha_array), + np.sin(ha_array), + self.bvec[pol][0], + self.bvec[pol][1], + f_local_indices, + ra_index_range, + ) + + sumweight_inrange = self.sumweight[pol][:, ra_index_range, :] + visweight_inrange = self.visweight[pol][:, ra_index_range, :] + + if self.collapse_ha: + # Sum over RA. Does not multiply by weights because + # this_formed_beam was never normalized (this avoids + # re-work and makes code more efficient). + + this_sumweight = np.sum( + np.sum(sumweight_inrange, axis=-1) * primary_beam**2, axis=1 + ) + + formed_beam_full[pol] = np.sum( + this_formed_beam * primary_beam, axis=1 + ) * invert_no_zero(this_sumweight) + + if self.weight != "inverse_variance": + this_weight2 = np.sum( + np.sum( + sumweight_inrange**2 + * invert_no_zero(visweight_inrange), + axis=-1, + ) + * primary_beam**2, + axis=1, + ) + + weight_full[pol] = this_sumweight**2 * invert_no_zero( + this_weight2 + ) + + else: + weight_full[pol] = this_sumweight + + else: + # Need to divide by weight here for proper + # normalization because it is not done in + # beamform() + this_sumweight = np.sum(sumweight_inrange, axis=-1) + # Populate only where ha_mask is true. Zero otherwise. + formed_beam_full[pol][:, ha_mask] = ( + this_formed_beam * invert_no_zero(this_sumweight) + ) + if self.weight != "inverse_variance": + this_weight2 = np.sum( + sumweight_inrange**2 * invert_no_zero(visweight_inrange), + axis=-1, + ) + # Populate only where ha_mask is true. Zero otherwise. + weight_full[pol][:, ha_mask] = ( + this_sumweight**2 * invert_no_zero(this_weight2) + ) + else: + weight_full[pol][:, ha_mask] = this_sumweight + + # Ensure weights are zero for non-processed frequencies + weight_full[pol][f_mask] = 0.0 + + # Combine polarizations if needed. + # TODO: For now I am ignoring differences in the X and + # Y beams and just adding them as is. + if self.polarization == "I": + formed_beam_full = np.sum( + formed_beam_full * weight_full, axis=0 + ) * invert_no_zero(np.sum(weight_full, axis=0)) + weight_full = np.sum(weight_full, axis=0) + # Add an axis for the polarization + if self.collapse_ha: + formed_beam_full = np.reshape(formed_beam_full, (1, self.ls)) + weight_full = np.reshape(weight_full, (1, self.ls)) + else: + formed_beam_full = np.reshape( + formed_beam_full, (1, self.ls, self.nha) + ) + weight_full = np.reshape(weight_full, (1, self.ls, self.nha)) + elif self.polarization == "stokes": + # TODO: Not implemented + pass + + # Populate container. + fbb[src] = formed_beam_full + + # Scale the weights by a factor of 2 to account for the fact that we + # have taken the real-component of the fringestopped visibility, which + # has a variance that is 1/2 the variance of the complex visibility + # that was encoded in our original weight dataset. + fbw[src] = 2.0 * weight_full + + if not self.collapse_ha: + if self.is_sstream: + formed_beam.ha[src, :] = ha_array + else: + # Populate only where ha_mask is true. + formed_beam.ha[src, ha_mask] = ha_array + + return formed_beam
+ + +
+[docs] + def process_finish(self): + """Clear lists holding copies of data. + + These lists will persist beyond this task being done, so + the data stored there will continue to use memory. + """ + for attr in ["vis", "visweight", "bvec", "sumweight"]: + try: + delattr(self, attr) + except AttributeError: + pass
+ + + def _ha_array(self, ra, source_ra_index, source_ra, ha_side, is_sstream=True): + """Hour angle for each RA/time bin to be processed. + + Also return the indices of these bins in the full RA/time axis. + + Parameters + ---------- + ra : array + RA axis in the data + source_ra_index : int + Index in data.index_map['ra'] closest to source_ra + source_ra : float + RA of the quasar + ha_side : int + Number of RA/HA bins on each side of transit. + is_sstream : bool + True if data is sidereal stream. Flase if time stream + + Returns + ------- + ha_array : np.ndarray + Hour angle array in the range -180. to 180 + ra_index_range : np.ndarray of int + Indices (in data.index_map['ra']) corresponding + to ha_array. + """ + # RA range to track this quasar through the beam. + ra_index_range = np.arange( + source_ra_index - ha_side, source_ra_index + ha_side + 1, dtype=np.int32 + ) + # Number of RA bins in data. + nra = len(ra) + + if is_sstream: + # Wrap RA indices around edges. + ra_index_range[ra_index_range < 0] += nra + ra_index_range[ra_index_range >= nra] -= nra + # Hour angle array (convert to radians) + ha_array = np.deg2rad(ra[ra_index_range] - source_ra) + # For later convenience it is better if `ha_array` is + # in the range -pi to pi instead of 0 to 2pi. + ha_array = (ha_array + np.pi) % (2.0 * np.pi) - np.pi + # In this case the ha_mask is trivial + ha_mask = np.ones(len(ra_index_range), dtype=bool) + else: + # Mask-out indices out of range + ha_mask = (ra_index_range >= 0) & (ra_index_range < nra) + # Return smaller HA range, and mask. + ra_index_range = ra_index_range[ha_mask] + # Hour angle array (convert to radians) + ha_array = np.deg2rad(ra[ra_index_range] - source_ra) + # For later convenience it is better if `ha_array` is + # in the range -pi to pi instead of 0 to 2pi. + ha_array = (ha_array + np.pi) % (2.0 * np.pi) - np.pi + + return ha_array, ra_index_range, ha_mask + + def _initialize_beam_with_data(self): + """Beam initialization that requires data. + + This is called at the start of the process method + and can be overridden to perform any beam initialization + that requires the data and catalog to be parsed first. + """ + # Find the index of the local frequencies in + # the frequency axis of the telescope instance + if not self.no_beam_model: + self.freq_local_telescope_index = np.array( + [ + np.argmin(np.abs(nu - self.telescope.frequencies)) + for nu in self.freq_local + ] + ) + + def _beamfunc(self, pol, dec, ha): + """Calculate the primary beam at the location of a source as it transits. + + Uses the frequencies in the freq_local_telescope_index attribute. + + Parameters + ---------- + pol : str + String specifying the polarisation, + either 'XX', 'XY', 'YX', or 'YY'. + dec : float + The declination of the source in radians. + ha : np.ndarray[nha,] + The hour angle of the source in radians. + + Returns + ------- + primary_beam : np.ndarray[nfreq, nha] + The primary beam as a function of frequency and hour angle + at the sources declination for the requested polarisation. + """ + nfreq = self.freq_local.size + + if self.no_beam_model: + return np.ones((nfreq, ha.size), dtype=np.float64) + + angpos = np.array([(0.5 * np.pi - dec) * np.ones_like(ha), ha]).T + + primary_beam = np.zeros((nfreq, ha.size), dtype=np.float64) + + for ff, freq in enumerate(self.freq_local_telescope_index): + bii = self.telescope.beam(self.map_pol_feed[pol[0]], freq, angpos) + + if pol[0] != pol[1]: + bjj = self.telescope.beam(self.map_pol_feed[pol[1]], freq, angpos) + else: + bjj = bii + + primary_beam[ff] = np.sum(bii * bjj.conjugate(), axis=1) + + return primary_beam + + def _process_data(self, data): + """Store code for parsing and formating data prior to beamforming.""" + # Easy access to communicator + self.comm_ = data.comm + + self.tag_data = data.attrs["tag"] if "tag" in data.attrs else None + + # Extract data info + if "ra" in data.index_map: + self.is_sstream = True + self.ra = data.index_map["ra"] + + # Calculate the epoch for the data so we can calculate the correct + # CIRS coordinates + if "lsd" not in data.attrs: + raise ValueError( + "SiderealStream must have an LSD attribute to calculate the epoch." + ) + + lsd = np.mean(data.attrs["lsd"]) + self.epoch = self.telescope.lsd_to_unix(lsd) + + dt = 240.0 * ctime.SIDEREAL_S * np.median(np.abs(np.diff(self.ra))) + + else: + self.is_sstream = False + # Convert data timestamps into LSAs (degrees) + self.ra = self.telescope.unix_to_lsa(data.time) + self.epoch = data.time.mean() + + dt = np.median(np.abs(np.diff(data.time))) + + self.freq = data.index_map["freq"] + self.nfreq = len(self.freq) + # Ensure data is distributed in freq axis + data.redistribute(0) + + # Number of RA bins to track each source at each side of transit + self.ha_side = self.timetrack / dt + self.nha = 2 * int(self.ha_side) + 1 + + # polmap: indices of each vis product in + # polarization list: ['XX', 'XY', 'YX', 'YY'] + polmap = polarization_map(data.index_map, self.telescope) + # Baseline vectors in meters + bvec_m = baseline_vector(data.index_map, self.telescope) + + # MPI distribution values + self.lo = data.vis.local_offset[0] + self.ls = data.vis.local_shape[0] + self.freq_local = self.freq["centre"][self.lo : self.lo + self.ls] + + # These are to be used when gathering results in the end. + # Tuple (not list!) of number of frequencies in each rank + self.fsize = tuple(data.comm.allgather(self.ls)) + # Tuple (not list!) of displacements of each rank array in full array + self.foffset = tuple(data.comm.allgather(self.lo)) + + fullpol = ["XX", "XY", "YX", "YY"] + # Save subsets of the data for each polarization, changing + # the ordering to 'C' (needed for the cython part). + # This doubles the memory usage. + self.vis, self.visweight, self.bvec, self.sumweight = [], [], [], [] + for pol in self.process_pol: + pol = fullpol.index(pol) + polmask = polmap == pol + # Swap order of product(1) and RA(2) axes, to reduce striding + # through memory later on. + self.vis.append( + np.copy(np.moveaxis(data.vis[:, polmask, :], 1, 2), order="C") + ) + # Restrict visweight to the local frequencies + self.visweight.append( + np.copy( + np.moveaxis( + data.weight[self.lo : self.lo + self.ls][:, polmask, :], 1, 2 + ).astype(np.float64), + order="C", + ) + ) + # Multiply bvec_m by frequencies to get vector in wavelengths. + # Shape: (2, nfreq_local, nvis), for each pol. + self.bvec.append( + np.copy( + bvec_m[:, np.newaxis, polmask] + * self.freq_local[np.newaxis, :, np.newaxis] + * 1e6 + / C, + order="C", + ) + ) + if self.weight == "inverse_variance": + # Weights for sum are just the visibility weights + self.sumweight.append(self.visweight[-1]) + else: + # Ensure zero visweights result in zero sumweights + this_sumweight = (self.visweight[-1] > 0.0).astype(np.float64) + ssi = data.input_flags[:] + ssp = data.index_map["prod"][:] + sss = data.reverse_map["stack"]["stack"][:] + nstack = data.vis.shape[1] + # this redundancy takes into account input flags. + # It has shape (nstack, ntime) + redundancy = np.moveaxis( + calculate_redundancy(ssi, ssp, sss, nstack)[polmask].astype( + np.float64 + ), + 0, + 1, + )[np.newaxis, :, :] + # redundancy = (self.telescope.redundancy[polmask]. + # astype(np.float64)[np.newaxis, np.newaxis, :]) + this_sumweight *= redundancy + if self.weight == "uniform": + this_sumweight = (this_sumweight > 0.0).astype(np.float64) + self.sumweight.append(np.copy(this_sumweight, order="C")) + + def _process_catalog(self, catalog): + """Process the catalog to get CIRS coordinates at the correct epoch. + + Note that `self._process_data` must have been called before this. + """ + if "position" not in catalog: + raise ValueError("Input is missing a position table.") + + if not hasattr(self, "epoch"): + self.log.warning("Epoch not set. Was the requested data not available?") + self.data_available = False + return + + coord = catalog.attrs.get("coordinates", None) + if coord == "CIRS": + self.log.info("Catalog already in CIRS coordinates.") + self.sra = catalog["position"]["ra"] + self.sdec = catalog["position"]["dec"] + + else: + self.log.info("Converting catalog from ICRS to CIRS coordinates.") + self.sra, self.sdec = icrs_to_cirs( + catalog["position"]["ra"], catalog["position"]["dec"], self.epoch + ) + + if self.freqside is not None: + if "redshift" not in catalog: + raise ValueError("Input is missing a required redshift table.") + self.sfreq = NU21 / (catalog["redshift"]["z"][:] + 1.0) # MHz + + self.source_cat = catalog + self.nsource = len(self.sra) + + self.tag_catalog = catalog.attrs["tag"] if "tag" in catalog.attrs else None
+ + + +
+[docs] +class BeamForm(BeamFormBase): + """BeamForm for a single source catalog and multiple visibility datasets.""" + +
+[docs] + def setup(self, manager, source_cat): + """Parse the source catalog and performs the generic setup. + + Parameters + ---------- + manager : either `ProductManager`, `BeamTransfer` or `TransitTelescope` + Contains a TransitTelescope object describing the telescope. + + source_cat : :class:`containers.SourceCatalog` + Catalog of points to beamform at. + + """ + super().setup(manager) + self.catalog = source_cat
+ + +
+[docs] + def process(self, data): + """Parse the visibility data and beamforms all sources. + + Parameters + ---------- + data : `containers.SiderealStream` or `containers.TimeStream` + Data to beamform on. + + Returns + ------- + formed_beam : `containers.FormedBeam` or `containers.FormedBeamHA` + Formed beams at each source. + """ + # Process and make available various data + self._process_data(data) + self._process_catalog(self.catalog) + + if not self.data_available: + return None + + # Call generic process method. + return super().process()
+
+ + + +
+[docs] +class BeamFormCat(BeamFormBase): + """BeamForm for multiple source catalogs and a single visibility dataset.""" + +
+[docs] + def setup(self, manager, data): + """Parse the visibility data and performs the generic setup. + + Parameters + ---------- + manager : either `ProductManager`, `BeamTransfer` or `TransitTelescope` + Contains a TransitTelescope object describing the telescope. + + data : `containers.SiderealStream` or `containers.TimeStream` + Data to beamform on. + + """ + super().setup(manager) + + # Process and make available various data + self._process_data(data)
+ + +
+[docs] + def process(self, source_cat): + """Parse the source catalog and beamforms all sources. + + Parameters + ---------- + source_cat : :class:`containers.SourceCatalog` + Catalog of points to beamform at. + + Returns + ------- + formed_beam : `containers.FormedBeam` or `containers.FormedBeamHA` + Formed beams at each source. + """ + self._process_catalog(source_cat) + + if not self.data_available: + return None + + # Call generic process method. + return super().process()
+
+ + + +
+[docs] +class BeamFormExternalBase(BeamFormBase): + """Base class for tasks that beamform using an external model of the primary beam. + + The primary beam is provided to the task during setup. Do not use this class + directly, instead use BeamFormExternal and BeamFormExternalCat. + """ + +
+[docs] + def setup(self, beam, *args): + """Initialize the beam. + + Parameters + ---------- + beam : GridBeam + Model for the primary beam. + args : optional + Additional argument to pass to the super class + """ + super().setup(*args) + self._initialize_beam(beam)
+ + + def _initialize_beam(self, beam): + """Initialize based on the beam container type. + + Parameters + ---------- + beam : GridBeam + Container holding the model for the primary beam. + Currently only accepts GridBeam type containers. + """ + if isinstance(beam, containers.GridBeam): + self._initialize_grid_beam(beam) + self._beamfunc = self._grid_beam + + else: + raise ValueError(f"Do not recognize beam container: {beam.__class__}") + + def _initialize_beam_with_data(self): + """Ensure that the beam and visibilities have the same frequency axis.""" + if not np.array_equal(self.freq_local, self._beam_freq): + raise RuntimeError("Beam and visibility frequency axes do not match.") + + def _initialize_grid_beam(self, gbeam): + """Create an interpolator for a GridBeam. + + Parameters + ---------- + gbeam : GridBeam + Model for the primary beam on a celestial grid where + (theta, phi) = (declination, hour angle) in degrees. The beam + must be in power units and must have a length 1 input axis that + contains the "baseline averaged" beam, which will be applied to + all baselines of a given polarisation. + """ + # Make sure the beam is in celestial coordinates + if gbeam.coords != "celestial": + raise RuntimeError( + "GridBeam must be converted to celestial coordinates for beamforming." + ) + + # Make sure there is a single beam to use for all inputs + if gbeam.input.size > 1: + raise NotImplementedError( + "Do not support input-dependent beams at the moment." + ) + + # Distribute over frequencies, extract local frequencies + gbeam.redistribute("freq") + + lo = gbeam.beam.local_offset[0] + nfreq = gbeam.beam.local_shape[0] + self._beam_freq = gbeam.freq[lo : lo + nfreq] + + # Find the relevant indices into the polarisation axis + ipol = np.array([list(gbeam.pol).index(pstr) for pstr in self.process_pol]) + npol = ipol.size + self._beam_pol = [gbeam.pol[ip] for ip in ipol] + + # Extract beam + flag = gbeam.weight[:, :, 0][:, ipol] > 0.0 + beam = np.where(flag, gbeam.beam[:, :, 0][:, ipol].real, 0.0) + + # Convert the declination and hour angle axis to radians, make sure they are sorted + ha = (gbeam.phi + 180.0) % 360.0 - 180.0 + isort = np.argsort(ha) + ha = np.radians(ha[isort]) + + dec = np.radians(gbeam.theta) + + # Create a 2D interpolator for the beam at each frequency and polarisation + self._beam = [ + [ + scipy.interpolate.RectBivariateSpline(dec, ha, beam[ff, pp][:, isort]) + for pp in range(npol) + ] + for ff in range(nfreq) + ] + + # Create a similair interpolator for the flag array + self._beam_flag = [ + [ + scipy.interpolate.RectBivariateSpline( + dec, ha, flag[ff, pp][:, isort].astype(np.float32) + ) + for pp in range(npol) + ] + for ff in range(nfreq) + ] + + self.log.info("Grid beam initialized.") + + def _grid_beam(self, pol, dec, ha): + """Interpolate a GridBeam to the requested declination and hour angles. + + Parameters + ---------- + pol : str + String specifying the polarisation, + either 'XX', 'XY', 'YX', or 'YY'. + dec : float + The declination of the source in radians. + ha : np.ndarray[nha,] + The hour angle of the source in radians. + + Returns + ------- + primay_beam : np.ndarray[nfreq, nha] + The primary beam as a function of frequency and hour angle + at the sources declination for the requested polarisation. + """ + pp = self._beam_pol.index(pol) + + primay_beam = np.array( + [self._beam[ff][pp](dec, ha)[0] for ff in range(self._beam_freq.size)] + ) + + # If the interpolated flags deviate from 1.0, then we mask + # the interpolated beam, since some fraction the underlying + # data used to construct the interpolator was masked. + flag = np.array( + [ + np.abs(self._beam_flag[ff][pp](dec, ha)[0] - 1.0) < 0.01 + for ff in range(self._beam_freq.size) + ] + ) + + return np.where(flag, primay_beam, 0.0)
+ + + +
+[docs] +class BeamFormExternal(BeamFormExternalBase, BeamForm): + """Beamform a single catalog and multiple datasets using an external beam model. + + The setup method requires [beam, manager, source_cat] as arguments. + """
+ + + +
+[docs] +class BeamFormExternalCat(BeamFormExternalBase, BeamFormCat): + """Beamform multiple catalogs and a single dataset using an external beam model. + + The setup method requires [beam, manager, data] as arguments. + """
+ + + +
+[docs] +class RingMapBeamForm(task.SingleTask): + """Beamform by extracting the pixel containing each source form a RingMap. + + This is significantly faster than `Beamform` or `BeamformCat` with the caveat + that they can beamform exactly on a source whereas this task is at the mercy of + what was done to produce the `RingMap` (use `DeconvolveHybridM` for best + results). + + Unless it has an explicit `lsd` attribute, the ring map is assumed to be in the + same coordinate epoch as the catalog. If it does, the input catalog is assumed to be + in ICRS and then is precessed to the CIRS coordinates in the epoch of the map. + """ + +
+[docs] + def setup(self, telescope: io.TelescopeConvertible, ringmap: containers.RingMap): + """Set the telescope object. + + Parameters + ---------- + telescope + The telescope object to use. + ringmap + The ringmap to extract the sources from. See the class documentation for how + the epoch is determined. + """ + self.telescope = io.get_telescope(telescope) + self.ringmap = ringmap
+ + +
+[docs] + def process(self, catalog: containers.SourceCatalog) -> containers.FormedBeam: + """Extract sources from a ringmap. + + Parameters + ---------- + catalog + The catalog to extract sources from. + + Returns + ------- + sources + The source spectra. + """ + ringmap = self.ringmap + + src_ra, src_dec = self._process_catalog(catalog) + + # Container to hold the formed beams + formed_beam = containers.FormedBeam( + object_id=catalog.index_map["object_id"], + axes_from=ringmap, + attrs_from=catalog, + distributed=True, + ) + + # Initialize container to zeros. + formed_beam.beam[:] = 0.0 + formed_beam.weight[:] = 0.0 + + # Copy catalog information + formed_beam["position"][:] = catalog["position"][:] + if "redshift" in catalog: + formed_beam["redshift"][:] = catalog["redshift"][:] + + # Ensure containers are distributed in frequency + formed_beam.redistribute("freq") + ringmap.redistribute("freq") + + has_weight = "weight" in ringmap.datasets + + # Get the pixel indices + ra_ind, za_ind = self._source_ind(src_ra, src_dec) + + # Dereference the datasets + fbb = formed_beam.beam[:] + fbw = formed_beam.weight[:] + rmm = ringmap.map[:] + rmw = ringmap.weight[:] if has_weight else invert_no_zero(ringmap.rms[:]) ** 2 + + # Loop over sources and extract the polarised pencil beams containing them from + # the ringmaps + for si, (ri, zi) in enumerate(zip(ra_ind, za_ind)): + fbb[si] = rmm[0, :, :, ri, zi] + fbw[si] = rmw[:, :, ri, zi] if has_weight else rmw[:, :, ri] + + return formed_beam
+ + + def _process_catalog( + self, catalog: containers.SourceCatalog + ) -> Tuple[np.ndarray, np.ndarray]: + """Get the current epoch coordinates of the catalog.""" + if "position" not in catalog: + raise ValueError("Input is missing a position table.") + + # Calculate the epoch for the data so we can calculate the correct + # CIRS coordinates + if "lsd" not in self.ringmap.attrs: + self.log.info( + "Input map has no epoch set, assuming that it matches the catalog." + ) + src_ra, src_dec = catalog["position"]["ra"], catalog["position"]["dec"] + + else: + lsd = ( + self.ringmap.attrs["lsd"][0] + if isinstance(self.ringmap.attrs["lsd"], np.ndarray) + else self.ringmap.attrs["lsd"] + ) + epoch = self.telescope.lsd_to_unix(lsd) + + # Get the source positions at the current epoch + src_ra, src_dec = icrs_to_cirs( + catalog["position"]["ra"], catalog["position"]["dec"], epoch + ) + + return src_ra, src_dec + + def _source_ind( + self, src_ra: np.ndarray, src_dec: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray]: + """Get the RA/ZA ringmap pixel indices of the sources.""" + # Get the grid size of the map in RA and sin(ZA) + dra = np.median(np.abs(np.diff(self.ringmap.index_map["ra"]))) + dza = np.median(np.abs(np.diff(self.ringmap.index_map["el"]))) + za_min = self.ringmap.index_map["el"][:].min() + + # Get the source indices in RA + # NOTE: that we need to take into account that sources might be less than 360 + # deg, but still closer to ind=0 + max_ra_ind = len(self.ringmap.ra) - 1 + ra_ind = (np.rint(src_ra / dra) % max_ra_ind).astype(np.int64) + + # Get the indices for the ZA direction + za_ind = np.rint( + (np.sin(np.radians(src_dec - self.telescope.latitude)) - za_min) / dza + ).astype(np.int64) + + return ra_ind, za_ind
+ + + +
+[docs] +class RingMapStack2D(RingMapBeamForm): + """Stack RingMap's on sources directly. + + Parameters + ---------- + num_ra, num_dec : int + The number of RA and DEC pixels to stack either side of the source. + num_freq : int + Number of final frequency channels either side of the source redshift to + stack. + freq_width : float + Length of frequency interval either side of source to use in MHz. + weight : {"patch", "dec", "enum"} + How to weight the data. If `"input"` the data is weighted on a pixel by pixel + basis according to the input data. If `"patch"` then the inverse of the + variance of the extracted patch is used. If `"dec"` then the inverse variance + of each declination strip is used. + """ + + num_ra = config.Property(proptype=int, default=10) + num_dec = config.Property(proptype=int, default=10) + num_freq = config.Property(proptype=int, default=256) + freq_width = config.Property(proptype=float, default=100.0) + weight = config.enum(["patch", "dec", "input"], default="input") + +
+[docs] + def process(self, catalog: containers.SourceCatalog) -> containers.FormedBeam: + """Extract sources from a ringmap. + + Parameters + ---------- + catalog + The catalog to extract sources from. + + Returns + ------- + sources + The source spectra. + """ + from mpi4py import MPI + + ringmap = self.ringmap + + # Get the current epoch catalog position + src_ra, src_dec = self._process_catalog(catalog) + src_z = catalog["redshift"]["z"] + + # Get the pixel indices + ra_ind, za_ind = self._source_ind(src_ra, src_dec) + + # Ensure containers are distributed in frequency + ringmap.redistribute("freq") + + # Get the frequencies on this rank + fs = ringmap.map.local_offset[2] + fe = fs + ringmap.map.local_shape[2] + local_freq = ringmap.freq[fs:fe] + + # Dereference the datasets + rmm = ringmap.map[:] + rmw = ( + ringmap.weight[:] + if "weight" in ringmap.datasets + else invert_no_zero(ringmap.rms[:]) ** 2 + ) + + # Calculate the frequencies bins to use + nbins = 2 * self.num_freq + 1 + bin_edges = np.linspace( + -self.freq_width, self.freq_width, nbins + 1, endpoint=True + ) + + # Calculate the edges of the frequency distribution, sources outside this range + # will be dropped + global_fmin = ringmap.freq.min() + global_fmax = ringmap.freq.max() + + # Create temporary array to accumulate into + wstack = np.zeros( + (nbins + 2, len(ringmap.pol), 2 * self.num_ra + 1, 2 * self.num_dec + 1) + ) + weight = np.zeros( + (nbins + 2, len(ringmap.pol), 2 * self.num_ra + 1, 2 * self.num_dec + 1) + ) + + rmvar = rmm[0].var(axis=2) + w_global = invert_no_zero(np.where(rmvar < 3e-7, 0.0, rmvar)) + + # Loop over sources and extract the polarised pencil beams containing them from + # the ringmaps + for si, (ri, zi, z) in enumerate(zip(ra_ind, za_ind, src_z)): + source_freq = 1420.406 / (1 + z) + + if source_freq > global_fmax or source_freq < global_fmin: + continue + + # Get bin indices + bin_ind = np.digitize(local_freq - source_freq, bin_edges) + + # Get the slices to extract the enclosing angular region + ri_slice = slice(ri - self.num_ra, ri + self.num_ra + 1) + zi_slice = slice(zi - self.num_dec, zi + self.num_dec + 1) + + b = rmm[0, :, :, ri_slice, zi_slice] + w = rmw[:, :, ri_slice, np.newaxis] + + if self.weight == "patch": + # Replace the weights with the variance of the patch + w = (w != 0) * invert_no_zero(b.var(axis=(2, 3)))[ + :, :, np.newaxis, np.newaxis + ] + elif self.weight == "dec": + # w = (w != 0) * invert_no_zero(b.var(axis=2))[:, :, np.newaxis, :] + w = (w != 0) * w_global[:, :, np.newaxis, zi_slice] + + bw = b * w + + # TODO: this is probably slow so should be moved into Cython + for lfi, bi in enumerate(bin_ind): + wstack[bi] += bw[:, lfi] + weight[bi] += w[:, lfi] + + # Arrays to reduce the data into + wstack_all = np.zeros_like(wstack) + weight_all = np.zeros_like(weight) + + self.comm.Allreduce(wstack, wstack_all, op=MPI.SUM) + self.comm.Allreduce(weight, weight_all, op=MPI.SUM) + + stack_all = wstack_all * invert_no_zero(weight_all) + + # Create the container to store the data in + bin_centres = 0.5 * (bin_edges[1:] + bin_edges[:-1]) + stack = containers.Stack3D( + freq=bin_centres, + delta_ra=np.arange(-self.num_ra, self.num_ra + 1), + delta_dec=np.arange(-self.num_dec, self.num_dec + 1), + axes_from=ringmap, + attrs_from=ringmap, + ) + stack.attrs["tag"] = catalog.attrs["tag"] + stack.stack[:] = stack_all[1:-1].transpose((1, 2, 3, 0)) + + return stack
+
+ + + +
+[docs] +class HealpixBeamForm(task.SingleTask): + """Beamform by extracting the pixel containing each source form a Healpix map. + + Unless it has an explicit `epoch` attribute, the Healpix map is assumed to be in the + same coordinate epoch as the catalog. If it does, the input catalog is assumed to be + in ICRS and then is precessed to the CIRS coordinates in the epoch of the map. + + Attributes + ---------- + fwhm : float + Smooth the map with a Gaussian with the specified FWHM in degrees. If `None` + (default), leave at native map resolution. This will modify the input map in + place. + """ + + fwhm = config.Property(proptype=float, default=None) + +
+[docs] + def setup(self, hpmap: containers.Map): + """Set the map to extract beams from at each catalog location. + + Parameters + ---------- + hpmap + The Healpix map to extract the sources from. + """ + self.map = hpmap + mv = self.map.map[:] + self.map.redistribute("freq") + + self.log.info("Smoothing input Healpix map.") + for lfi, _ in mv.enumerate(axis=0): + for pi in range(mv.shape[1]): + mv[lfi, pi] = healpy.smoothing( + mv[lfi, pi], fwhm=np.radians(self.fwhm), verbose=False + )
+ + +
+[docs] + def process(self, catalog: containers.SourceCatalog) -> containers.FormedBeam: + """Extract sources from a ringmap. + + Parameters + ---------- + catalog + The catalog to extract sources from. + + Returns + ------- + formed_beam + The source spectra. + """ + if "position" not in catalog: + raise ValueError("Input is missing a position table.") + + # Container to hold the formed beams + formed_beam = containers.FormedBeam( + object_id=catalog.index_map["object_id"], + axes_from=self.map, + distributed=True, + ) + + # Initialize container to zeros. + formed_beam.beam[:] = 0.0 + formed_beam.weight[:] = 0.0 + + # Copy catalog information + formed_beam["position"][:] = catalog["position"][:] + if "redshift" in catalog: + formed_beam["redshift"][:] = catalog["redshift"][:] + + # Get the source positions at the epoch of the input map + epoch = self.map.attrs.get("epoch", None) + epoch = ctime.ensure_unix(epoch) if epoch is not None else None + if epoch: + src_ra, src_dec = icrs_to_cirs( + catalog["position"]["ra"], catalog["position"]["dec"], epoch + ) + else: + self.log.info( + "Input map has no epoch set, assuming that it matches the catalog." + ) + src_ra = catalog["position"]["ra"] + src_dec = catalog["position"]["dec"] + + # Use Healpix to get the pixels containing the sources + pix_ind = healpy.ang2pix(self.map.nside, src_ra, src_dec, lonlat=True) + + # Ensure containers are distributed in frequency + formed_beam.redistribute("freq") + self.map.redistribute("freq") + + formed_beam.beam[:] = self.map.map[:, :, pix_ind].transpose(2, 1, 0) + # Set to some non-zero value as the Map container doesn't have a weight + formed_beam.weight[:] = 1.0 + + return formed_beam
+
+ + + +
+[docs] +def icrs_to_cirs(ra, dec, epoch, apparent=True): + """Convert a set of positions from ICRS to CIRS at a given data. + + Parameters + ---------- + ra, dec : float or np.ndarray + Positions of source in ICRS coordinates including an optional + redshift position. + epoch : time_like + Time to convert the positions to. Can be any type convertible to a + time using `caput.time.ensure_unix`. + apparent : bool + Calculate the apparent position (includes abberation and deflection). + + Returns + ------- + ra_cirs, dec_cirs : float or np.ndarray + Arrays of the positions in *CIRS* coordiantes. + """ + positions = Star(ra=Angle(degrees=ra), dec=Angle(degrees=dec)) + + epoch = ctime.unix_to_skyfield_time(ctime.ensure_unix(epoch)) + + earth = ctime.skyfield_wrapper.ephemeris["earth"] + positions = earth.at(epoch).observe(positions) + + if apparent: + positions = positions.apparent() + + ra_cirs, dec_cirs, _ = positions.cirs_radec(epoch) + + return ra_cirs._degrees, dec_cirs._degrees
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/delay.html b/docs/_modules/draco/analysis/delay.html new file mode 100644 index 000000000..364f10f64 --- /dev/null +++ b/docs/_modules/draco/analysis/delay.html @@ -0,0 +1,2224 @@ + + + + + + draco.analysis.delay — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.delay

+"""Delay space spectrum estimation and filtering."""
+
+from typing import List, Optional, Tuple, TypeVar, Union
+
+import numpy as np
+import scipy.linalg as la
+from caput import config, memh5, mpiarray
+from cora.util import units
+from numpy.lib.recfunctions import structured_to_unstructured
+
+from draco.core.containers import ContainerBase, FreqContainer
+
+from ..core import containers, io, task
+from ..util import random, tools
+
+
+
+[docs] +class DelayFilter(task.SingleTask): + """Remove delays less than a given threshold. + + This is performed by projecting the data onto the null space that is orthogonal + to any mode at low delays. + + Note that for this task to work best the zero entries in the weights dataset + should factorize in frequency-time for each baseline. A mostly optimal masking + can be generated using the `draco.analysis.flagging.MaskFreq` task. + + Attributes + ---------- + delay_cut : float + Delay value to filter at in seconds. + za_cut : float + Sine of the maximum zenith angle included in baseline-dependent delay + filtering. Default is 1 which corresponds to the horizon (ie: filters out all + zenith angles). Setting to zero turns off baseline dependent cut. + extra_cut : float + Increase the delay threshold beyond the baseline dependent term. + weight_tol : float + Maximum weight kept in the masked data, as a fraction of the largest weight + in the original dataset. + telescope_orientation : one of ('NS', 'EW', 'none') + Determines if the baseline-dependent delay cut is based on the north-south + component, the east-west component or the full baseline length. For + cylindrical telescopes oriented in the NS direction (like CHIME) use 'NS'. + The default is 'NS'. + window : bool + Apply the window function to the data when applying the filter. + + Notes + ----- + The delay cut applied is `max(za_cut * baseline / c + extra_cut, delay_cut)`. + """ + + delay_cut = config.Property(proptype=float, default=0.1) + za_cut = config.Property(proptype=float, default=1.0) + extra_cut = config.Property(proptype=float, default=0.0) + weight_tol = config.Property(proptype=float, default=1e-4) + telescope_orientation = config.enum(["NS", "EW", "none"], default="NS") + window = config.Property(proptype=bool, default=False) + +
+[docs] + def setup(self, telescope): + """Set the telescope needed to obtain baselines. + + Parameters + ---------- + telescope : TransitTelescope + The telescope object to use + """ + self.telescope = io.get_telescope(telescope)
+ + +
+[docs] + def process(self, ss): + """Filter out delays from a SiderealStream or TimeStream. + + Parameters + ---------- + ss : containers.SiderealStream + Data to filter. + + Returns + ------- + ss_filt : containers.SiderealStream + Filtered dataset. + """ + tel = self.telescope + + ss.redistribute(["input", "prod", "stack"]) + + freq = ss.freq[:] + bandwidth = np.ptp(freq) + + ssv = ss.vis[:].view(np.ndarray) + ssw = ss.weight[:].view(np.ndarray) + + ia, ib = structured_to_unstructured(ss.prodstack, dtype=np.int16).T + baselines = tel.feedpositions[ia] - tel.feedpositions[ib] + + for lbi, bi in ss.vis[:].enumerate(axis=1): + # Select the baseline length to use + baseline = baselines[bi] + if self.telescope_orientation == "NS": + baseline = abs(baseline[1]) # Y baseline + elif self.telescope_orientation == "EW": + baseline = abs(baseline[0]) # X baseline + else: + baseline = np.linalg.norm(baseline) # Norm + + # In micro seconds + baseline_delay_cut = self.za_cut * baseline / units.c * 1e6 + self.extra_cut + delay_cut = np.amax([baseline_delay_cut, self.delay_cut]) + + # Calculate the number of samples needed to construct the delay null space. + # `4 * tau_max * bandwidth` is the amount recommended in the DAYENU paper + # and seems to work well here + number_cut = int(4.0 * bandwidth * delay_cut + 0.5) + + # Flag frequencies and times with zero weight. This works much better if the + # incoming weight can be factorized + f_samp = (ssw[:, lbi] > 0.0).sum(axis=1) + f_mask = (f_samp == f_samp.max()).astype(np.float64) + + t_samp = (ssw[:, lbi] > 0.0).sum(axis=0) + t_mask = (t_samp == t_samp.max()).astype(np.float64) + + try: + NF = null_delay_filter( + freq, + delay_cut, + f_mask, + num_delay=number_cut, + window=self.window, + ) + except la.LinAlgError as e: + raise RuntimeError( + f"Failed to converge while processing baseline {bi}" + ) from e + + ssv[:, lbi] = np.dot(NF, ssv[:, lbi]) + ssw[:, lbi] *= f_mask[:, np.newaxis] * t_mask[np.newaxis, :] + + return ss
+
+ + + +# A specific subclass of a FreqContainer +FreqContainerType = TypeVar("FreqContainerType", bound=containers.FreqContainer) + + +
+[docs] +class DelayFilterBase(task.SingleTask): + """Remove delays less than a given threshold. + + This is performed by projecting the data onto the null space that is orthogonal + to any mode at low delays. + + Note that for this task to work best the zero entries in the weights dataset + should factorize in frequency-time for each baseline. A mostly optimal masking + can be generated using the `draco.analysis.flagging.MaskFreq` task. + + Attributes + ---------- + delay_cut : float + Delay value to filter at in seconds. + window : bool + Apply the window function to the data when applying the filter. + axis : str + The main axis to iterate over. The delay cut can be varied for each element + of this axis. If not set, a suitable default is picked for the container + type. + dataset : str + Apply the delay filter to this dataset. If not set, a suitable default + is picked for the container type. + + Notes + ----- + The delay cut applied is `max(za_cut * baseline / c + extra_cut, delay_cut)`. + """ + + delay_cut = config.Property(proptype=float, default=0.1) + window = config.Property(proptype=bool, default=False) + axis = config.Property(proptype=str, default=None) + dataset = config.Property(proptype=str, default=None) + +
+[docs] + def setup(self, telescope: io.TelescopeConvertible): + """Set the telescope needed to obtain baselines. + + Parameters + ---------- + telescope + The telescope object to use + """ + self.telescope = io.get_telescope(telescope)
+ + + def _delay_cut(self, ss: FreqContainerType, axis: str, ind: int) -> float: + """Return the delay cut to use for this entry in microseconds. + + Parameters + ---------- + ss + The container we are processing. + axis + The axis we are looping over. + ind : int + The (global) index along that axis. + + Returns + ------- + float + The delay cut in microseconds. + """ + return self.delay_cut + +
+[docs] + def process(self, ss: FreqContainerType) -> FreqContainerType: + """Filter out delays from a SiderealStream or TimeStream. + + Parameters + ---------- + ss + Data to filter. + + Returns + ------- + ss_filt + Filtered dataset. + """ + if not isinstance(ss, containers.FreqContainer): + raise TypeError( + f"Can only process FreqContainer instances. Got {type(ss)}." + ) + + _default_axis = { + containers.SiderealStream: "stack", + containers.HybridVisMModes: "m", + containers.RingMap: "el", + containers.GridBeam: "theta", + } + + _default_dataset = { + containers.SiderealStream: "vis", + containers.HybridVisMModes: "vis", + containers.RingMap: "map", + containers.GridBeam: "beam", + } + + axis = self.axis + + if self.axis is None: + for cls, ax in _default_axis.items(): + if isinstance(ss, cls): + axis = ax + break + else: + raise ValueError(f"No default axis know for {type(ss)} container.") + + dset = self.dataset + + if self.dataset is None: + for cls, dataset in _default_dataset.items(): + if isinstance(ss, cls): + dset = dataset + break + else: + raise ValueError(f"No default dataset know for {type(ss)} container.") + + ss.redistribute(axis) + + freq = ss.freq[:] + bandwidth = np.ptp(freq) + + # Get views of the relevant datasets, but make sure that the weights have the + # same number of axes as the visibilities (inserting length-1 axes as needed) + ssv = ss.datasets[dset][:].view(np.ndarray) + ssw = match_axes(ss.datasets[dset], ss.weight).view(np.ndarray) + + dist_axis_pos = list(ss.datasets[dset].attrs["axis"]).index(axis) + freq_axis_pos = list(ss.datasets[dset].attrs["axis"]).index("freq") + + # Once we have selected elements of dist_axis the location of freq_axis_pos may + # be one lower + sel_freq_axis_pos = ( + freq_axis_pos if freq_axis_pos < dist_axis_pos else freq_axis_pos - 1 + ) + + for lbi, bi in ss.datasets[dset][:].enumerate(axis=dist_axis_pos): + # Extract the part of the array that we are processing, and + # transpose/reshape to make a 2D array with frequency as axis=0 + vis_local = _take_view(ssv, lbi, dist_axis_pos) + vis_2D = _move_front(vis_local, sel_freq_axis_pos, vis_local.shape) + + weight_local = _take_view(ssw, lbi, dist_axis_pos) + weight_2D = _move_front(weight_local, sel_freq_axis_pos, weight_local.shape) + + # In micro seconds + delay_cut = self._delay_cut(ss, axis, bi) + + # Calculate the number of samples needed to construct the delay null space. + # `4 * tau_max * bandwidth` is the amount recommended in the DAYENU paper + # and seems to work well here + number_cut = int(4.0 * bandwidth * delay_cut + 0.5) + + # Flag frequencies and times (or all other axes) with zero weight. This + # works much better if the incoming weight can be factorized + f_samp = (weight_2D > 0.0).sum(axis=1) + f_mask = (f_samp == f_samp.max()).astype(np.float64) + + t_samp = (weight_2D > 0.0).sum(axis=0) + t_mask = (t_samp == t_samp.max()).astype(np.float64) + + # This has occasionally failed to converge, catch this and output enough + # info to debug after the fact + try: + NF = null_delay_filter( + freq, + delay_cut, + f_mask, + num_delay=number_cut, + window=self.window, + ) + except la.LinAlgError as e: + raise RuntimeError( + f"Failed to converge while processing baseline {bi}" + ) from e + + vis_local[:] = _inv_move_front( + np.dot(NF, vis_2D), sel_freq_axis_pos, vis_local.shape + ) + weight_local[:] *= _inv_move_front( + f_mask[:, np.newaxis] * t_mask[np.newaxis, :], + sel_freq_axis_pos, + weight_local.shape, + ) + + return ss
+
+ + + +
+[docs] +class DelayTransformBase(task.SingleTask): + """Base class for transforming from frequency to delay (non-functional). + + Attributes + ---------- + freq_zero : float, optional + The physical frequency (in MHz) of the *zero* channel. That is the DC + channel coming out of the F-engine. If not specified, use the first + frequency channel of the stream. + freq_spacing : float, optional + The spacing between the underlying channels (in MHz). This is conjugate + to the length of a frame of time samples that is transformed. If not + set, then use the smallest gap found between channels in the dataset. + nfreq : int, optional + The number of frequency channels in the full set produced by the + F-engine. If not set, assume the last included frequency is the last of + the full set (or is the penultimate if `skip_nyquist` is set). + skip_nyquist : bool, optional + Whether the Nyquist frequency is included in the data. This is `True` by + default to align with the output of CASPER PFBs. + apply_window : bool, optional + Whether to apply apodisation to frequency axis. Default: True. + window : window available in :func:`draco.util.tools.window_generalised()`, optional + Apodisation to perform on frequency axis. Default: 'nuttall'. + complex_timedomain : bool, optional + Whether to assume the original time samples that were channelized into a + frequency spectrum were purely real (False) or complex (True). If True, + `freq_zero`, `nfreq`, and `skip_nyquist` are ignored. Default: False. + weight_boost : float, optional + Multiply weights in the input container by this factor. This causes the task to + assume the noise power in the data is `weight_boost` times lower, which is + useful if you want the "true" noise to not be downweighted by the Wiener filter, + or have it included in the Gibbs sampler. Default: 1.0. + """ + + freq_zero = config.Property(proptype=float, default=None) + freq_spacing = config.Property(proptype=float, default=None) + nfreq = config.Property(proptype=int, default=None) + skip_nyquist = config.Property(proptype=bool, default=True) + apply_window = config.Property(proptype=bool, default=True) + window = config.enum( + [ + "uniform", + "hann", + "hanning", + "hamming", + "blackman", + "nuttall", + "blackman_nuttall", + "blackman_harris", + ], + default="nuttall", + ) + complex_timedomain = config.Property(proptype=bool, default=False) + weight_boost = config.Property(proptype=float, default=1.0) + +
+[docs] + def process(self, ss): + """Estimate the delay spectrum or power spectrum. + + Parameters + ---------- + ss : `containers.FreqContainer` + Data to transform. Must have a frequency axis. + + Returns + ------- + out_cont : `containers.DelayTransform` or `containers.DelaySpectrum` + Output delay spectrum or delay power spectrum. + """ + delays, channel_ind = self._calculate_delays(ss) + + # Get views of data and weights appropriate for the type of processing we're + # doing. + data_view, weight_view, coord_axes = self._process_data(ss) + + # Create the right output container + out_cont = self._create_output(ss, delays, coord_axes) + + # Evaluate frequency->delay transform. (self._evaluate take the empty output + # container, fills it, and returns it) + return self._evaluate(data_view, weight_view, out_cont, delays, channel_ind)
+ + + def _process_data( + self, ss: containers.FreqContainer + ) -> tuple[mpiarray.MPIArray, mpiarray.MPIArray, list[str]]: + """Get relevant views of data and weights, and create output container. + + This implementation is blank, and must be overridden. The function must take + a `containers.FreqContainer` and rearrange the data into an `MPIArray` packed as + ``[coord, freq, sample]``, where we want to compute a separate delay spectrum + for each value of `coord` (e.g. baseline, ringmap elevation), and `sample` is + averaged over if we are computing a delay power spectrum. The weights must also + be rearranged in the same way. Finally, the function must also return an empty + container suitable to hold the final delay spectrum or power spectrum. + + Parameters + ---------- + ss + Data to transform. Must have a frequency axis. + + Returns + ------- + data_view + Data to transform, reshaped as described above. + weight_view + Weights, reshaped in same way as data. + coord_axes + List of string names of the axes folded into `coord`. + """ + raise NotImplementedError() + + def _evaluate(self, data_view, weight_view, out_cont, delays, channel_ind): + """Estimate the delay spectrum or power spectrum. + + This implementation is blank, and must be overridden. The function must take + the outputs of `_process_data`, evaluate the delay spectrum or power spectrum, + and return the appropriate container. + + Parameters + ---------- + data_view : `caput.mpiarray.MPIArray` + Data to transform. + weight_view : `caput.mpiarray.MPIArray` + Weights corresponding to `data_view`. + out_cont : `containers.DelayTransform` or `containers.DelaySpectrum` + Container for output delay spectrum or power spectrum. + delays + The delays to evaluate at. + channel_ind + The indices of the available frequency channels in the full set of channels. + + Returns + ------- + out_cont : `containers.DelayTransform` or `containers.DelaySpectrum` + Output delay spectrum or delay power spectrum. + """ + raise NotImplementedError() + + def _create_output( + self, + ss: containers.FreqContainer, + delays: np.ndarray, + coord_axes: list[str], + ) -> containers.ContainerBase: + """Create a suitable output container. + + Parameters + ---------- + ss + The input container that we are using. + delays + The delays that we will calculate at. + coord_axes + The list of axes folded into the coord axis. + """ + raise NotImplementedError() + + def _calculate_delays( + self, ss: Union[FreqContainer, list[FreqContainer]] + ) -> tuple[np.ndarray, np.ndarray]: + """Calculate the grid of delays. + + Parameters + ---------- + ss + A FreqContainer to determine the delays from. + + Returns + ------- + delays + The delays that will be calculated. + channel_ind + The effective channel indices of the data. + """ + if isinstance(ss, FreqContainer): + freq = ss.freq + elif len(ss) > 0: + freq = ss[0].freq + else: + raise TypeError("Could not find a frequency axis in the input.") + + freq_zero = freq[0] if self.freq_zero is None else self.freq_zero + + freq_spacing = self.freq_spacing + if freq_spacing is None: + freq_spacing = np.abs(np.diff(freq)).min() + + nfreq = self.nfreq + + if self.complex_timedomain: + nfreq = len(freq) + channel_ind = np.arange(nfreq) + ndelay = nfreq + else: + channel_ind = (np.abs(freq - freq_zero) / freq_spacing).astype(np.int64) + + if nfreq is None: + nfreq = channel_ind[-1] + 1 + + if self.skip_nyquist: + nfreq += 1 + + # Assume each transformed frame was an even number of samples long + ndelay = 2 * (nfreq - 1) + + # Compute delays corresponding to output delay power spectrum (in us) + delays = np.fft.fftshift(np.fft.fftfreq(ndelay, d=freq_spacing)) + + return delays, channel_ind + + # NOTE: this not obviously the right level for this, but it's the only baseclass in + # common to where it's used + def _cut_data( + self, data: np.ndarray, weight: np.ndarray, channel_ind: np.ndarray + ) -> Optional[tuple[np.ndarray, np.ndarray, np.ndarray]]: + """Apply cuts on the data and weights and returned modified versions. + + Parameters + ---------- + data + An n-d array of the data. Frequency is the last axis, and the average axis + the second last. + weight + A n-d array of the weights. Axes the same as the data. + channel_ind + The indices of the frequency channels. + + Returns + ------- + new_data + The new data with cuts applied and all-zero channels removed. + new_weight + The new weights with cuts applied and averaged over the `average_axis` (i.e + second last). + new_channel_ind + The indices of the remaining channels after cuts. + """ + # Mask out data with completely zero'd weights and generate time + # averaged weights + weight_cut = ( + 1e-4 * weight.mean() + ) # Use approx threshold to ignore small weights + data = data * (weight > weight_cut) + weight = np.mean(weight, axis=-2) + + if (data == 0.0).all(): + return None + + # If there are no non-zero weighted entries skip + non_zero = (weight > 0).reshape(-1, weight.shape[-1]).all(axis=0) + if not non_zero.any(): + return None + + # Remove any frequency channel which is entirely zero, this is just to + # reduce the computational cost, it should make no difference to the result + data = data[..., non_zero] + weight = weight[..., non_zero] + non_zero_channel = channel_ind[non_zero] + + # Increase the weights by a specified amount + weight *= self.weight_boost + + return data, weight, non_zero_channel
+ + + +
+[docs] +class DelayGibbsSamplerBase(DelayTransformBase, random.RandomTask): + """Base class for delay power spectrum estimation via Gibbs sampling (non-functional). + + The spectrum is calculated by Gibbs sampling. The spectrum returned is the median + of the final half of the samples calculated. + + Attributes + ---------- + nsamp : int, optional + The number of Gibbs samples to draw. + initial_amplitude : float, optional + The Gibbs sampler will be initialized with a flat power spectrum with + this amplitude. Default: 10. + save_samples : bool, optional. + The entire chain of samples will be saved rather than just the final + result. Default: False + initial_sample_path : str, optional + File path to load an initial power spectrum sample. If no file is given, + start with a flat power spectrum. Default: None + """ + + nsamp = config.Property(proptype=int, default=20) + initial_amplitude = config.Property(proptype=float, default=10.0) + save_samples = config.Property(proptype=bool, default=False) + initial_sample_path = config.Property(proptype=str, default=None) + + def _create_output( + self, + ss: FreqContainer, + delays: np.ndarray, + coord_axes: Union[list[str], np.ndarray], + ) -> ContainerBase: + """Create the output container for the delay power spectrum. + + If `coord_axes` is a list of strings then it is assumed to be a list of the + names of the folded axes. If it's an array then assume it is the actual axis + definition. + """ + # If only one axis is being collapsed, use that as the baseline axis definition, + # otherwise just use integer indices + if isinstance(coord_axes, np.ndarray): + bl = coord_axes + elif len(coord_axes) == 1: + bl = ss.index_map[coord_axes[0]] + else: + bl = np.prod([len(ss.index_map[ax]) for ax in coord_axes]) + + # Initialise the spectrum container + delay_spec = containers.DelaySpectrum( + baseline=bl, + delay=delays, + sample=self.nsamp, + attrs_from=ss, + ) + + delay_spec.redistribute("baseline") + delay_spec.spectrum[:] = 0.0 + + # Copy the index maps for all the flattened axes into the output container, and + # write out their order into an attribute so we can reconstruct this easily + # when loading in the spectrum + if isinstance(coord_axes, list): + for ax in coord_axes: + delay_spec.create_index_map(ax, ss.index_map[ax]) + delay_spec.attrs["baseline_axes"] = coord_axes + + if self.save_samples: + delay_spec.add_dataset("spectrum_samples") + + # Save the frequency axis of the input data as an attribute in the output + # container + delay_spec.attrs["freq"] = ss.freq + + return delay_spec + + def _get_initial_S(self, nbase, ndelay, dtype): + """Load the initial spectrum estimate if a file path exists. + + Parameters + ---------- + nbase : int + Number of baselines + ndelay : int + Number of delay samples + dtype : type | np.dtype | str + Datatype for the sample if no path is given + """ + if self.initial_sample_path is not None: + cont = ContainerBase.from_file(self.initial_sample_path, distributed=True) + + cont.redistribute("baseline") + + # Extract the spectrum and ove the baseline axis to the front + initial_S = cont.spectrum[:].local_array + bl_ax = cont.spectrum.attrs["axis"].tolist().index("baseline") + initial_S = np.moveaxis(initial_S, bl_ax, 0) + else: + initial_S = np.ones((nbase, ndelay), dtype=dtype) * self.initial_amplitude + + return initial_S + + def _evaluate(self, data_view, weight_view, out_cont, delays, channel_ind): + """Estimate the delay spectrum or power spectrum. + + Parameters + ---------- + data_view : `caput.mpiarray.MPIArray` + Data to transform. + weight_view : `caput.mpiarray.MPIArray` + Weights corresponding to `data_view`. + out_cont : `containers.DelayTransform` or `containers.DelaySpectrum` + Container for output delay spectrum or power spectrum. + delays + The delays to evaluate at. + channel_ind + The indices of the available frequency channels in the full set of channels. + + Returns + ------- + out_cont : `containers.DelayTransform` or `containers.DelaySpectrum` + Output delay spectrum or delay power spectrum. + """ + nbase = out_cont.spectrum.global_shape[0] + ndelay = len(delays) + + # Set initial conditions for delay power spectrum + initial_S = self._get_initial_S(nbase, ndelay, delays.dtype) + + # Initialize the random number generator we'll use + rng = self.rng + + # Iterate over all baselines and use the Gibbs sampler to estimate the spectrum + for lbi, bi in out_cont.spectrum[:].enumerate(axis=0): + self.log.debug(f"Delay transforming baseline {bi}/{nbase}") + + # Get the local selections + data = data_view.local_array[lbi] + weight = weight_view.local_array[lbi] + + # Apply the cuts to the data + t = self._cut_data(data, weight, channel_ind) + if t is None: + continue + data, weight, non_zero_channel = t + + spec = delay_power_spectrum_gibbs( + data, + ndelay, + weight, + initial_S[lbi], + window=self.window if self.apply_window else None, + fsel=non_zero_channel, + niter=self.nsamp, + rng=rng, + complex_timedomain=self.complex_timedomain, + ) + + # Take an average over the last half of the delay spectrum samples + # (presuming that removes the burn-in) + spec_av = np.median(spec[-(self.nsamp // 2) :], axis=0) + out_cont.spectrum[bi] = np.fft.fftshift(spec_av) + + if self.save_samples: + out_cont.datasets["spectrum_samples"][:, bi] = spec + + return out_cont
+ + + +
+[docs] +class DelayGeneralContainerBase(DelayTransformBase): + """Base class for freq->delay transforms that collapse over several dataset axes. + + The delay spectrum or power spectrum output is indexed by a `baseline` axis. This + axis is the composite axis of all the axes in the container except the frequency + axis or the `average_axis`. These constituent axes are included in the index map, + and their order is given by the `baseline_axes` attribute. + + Attributes + ---------- + dataset : str, optional + Calculate the delay spectrum of this dataset (e.g., "vis", "map", "beam"). If + not set, assume the input is a `DataWeightContainer` and use the main data + dataset. + average_axis : str + Name of the axis to take the average over. + """ + + dataset = config.Property(proptype=str, default=None) + average_axis = config.Property(proptype=str) + + def _process_data(self, ss): + """Get relevant views of data and weights, and create output container. + + Parameters + ---------- + ss : `FreqContainer` and `DataWeightContainer` subclass. + Data to transform. Must have a frequency axis, and a weight dataset. + + Returns + ------- + data_view : `caput.mpiarray.MPIArray` + Data to transform, reshaped such that all axes other than frequency and + `average_axis` are compressed into the baseline axis. + weight_view : `caput.mpiarray.MPIArray` + Weights, reshaped in same way as data. + out_cont : `containers.DelayTransform` or `containers.DelaySpectrum` + Container for output delay spectrum or power spectrum. + """ + ss.redistribute("freq") + + if self.dataset is not None: + if self.dataset not in ss.datasets: + raise ValueError( + f"Specified dataset to delay transform ({self.dataset}) not in " + f"container of type {type(ss)}." + ) + data_dset = ss[self.dataset] + else: + data_dset = ss.data + + if ( + self.average_axis not in ss.axes + or self.average_axis not in data_dset.attrs["axis"] + ): + raise ValueError( + f"Specified axis to average over ({self.average_axis}) not in " + f"container of type {type(ss)}." + ) + + # Find the relevant axis positions + data_view, bl_axes = flatten_axes(data_dset, [self.average_axis, "freq"]) + weight_view, _ = flatten_axes( + ss.weight, [self.average_axis, "freq"], match_dset=data_dset + ) + + return data_view, weight_view, bl_axes
+ + + +
+[docs] +class DelayPowerSpectrumStokesIEstimator(DelayGibbsSamplerBase): + """Class to measure delay power spectrum of Stokes-I visibilities via Gibbs sampling.""" + +
+[docs] + def setup(self, telescope): + """Set the telescope needed to generate Stokes I. + + Parameters + ---------- + telescope : TransitTelescope + Telescope object we'll use for baseline and polarization information. + """ + self.telescope = io.get_telescope(telescope)
+ + + def _process_data(self, ss): + """Get Stokes-I data and weights, and create output container. + + Parameters + ---------- + ss : `containers.FreqContainer` + Data to transform. Must have a frequency axis. + + Returns + ------- + data_view : `caput.mpiarray.MPIArray` + Data to transform. + weight_view : `caput.mpiarray.MPIArray` + Weights corresponding to `data_view`. + out_cont : `containers.DelayTransform` or `containers.DelaySpectrum` + Container for output delay spectrum or power spectrum. + """ + ss.redistribute("freq") + + tel = self.telescope + + # Construct the Stokes I vis, and transpose from [baseline, freq, ra] to + # [baseline, ra, freq]. + data_view, weight_view, baselines = stokes_I(ss, tel) + data_view = data_view.transpose(0, 2, 1) + weight_view = weight_view.transpose(0, 2, 1) + + return data_view, weight_view, baselines
+ + + +
+[docs] +class DelayPowerSpectrumGeneralEstimator( + DelayGibbsSamplerBase, DelayGeneralContainerBase +): + """Class to measure delay power spectrum of general container via Gibbs sampling."""
+ + + +
+[docs] +class DelaySpectrumWienerEstimator(DelayGeneralContainerBase): + """Class to measure delay spectrum of general container via Wiener filtering. + + The spectrum is calculated by applying a Wiener filter to the input frequency + spectrum, assuming an input model for the delay power spectrum of the signal and + that the noise power is described by the weights of the input container. See + https://arxiv.org/abs/2202.01242, Eq. A6 for details. + """ + +
+[docs] + def setup(self, dps: containers.DelaySpectrum): + """Set the delay power spectrum to use as the signal covariance. + + Parameters + ---------- + dps : `containers.DelaySpectrum` + Delay power spectrum for signal part of Wiener filter. + """ + self.dps = dps
+ + + def _create_output( + self, ss: FreqContainer, delays: np.ndarray, coord_axes: list[str] + ) -> ContainerBase: + """Create the output container for the Wiener filtered data.""" + # Initialise the spectrum container + nbase = np.prod([len(ss.index_map[ax]) for ax in coord_axes]) + delay_spec = containers.DelayTransform( + baseline=nbase, + sample=ss.index_map[self.average_axis], + delay=delays, + attrs_from=ss, + weight_boost=self.weight_boost, + ) + delay_spec.redistribute("baseline") + delay_spec.spectrum[:] = 0.0 + + # Copy the index maps for all the flattened axes into the output container, and + # write out their order into an attribute so we can reconstruct this easily + # when loading in the spectrum + for ax in coord_axes: + delay_spec.create_index_map(ax, ss.index_map[ax]) + delay_spec.attrs["baseline_axes"] = coord_axes + + # Save the frequency axis of the input data as an attribute in the output + # container + delay_spec.attrs["freq"] = ss.freq + + return delay_spec + + def _evaluate(self, data_view, weight_view, out_cont, delays, channel_ind): + """Estimate the delay spectrum by Wiener filtering. + + Parameters + ---------- + data_view : `caput.mpiarray.MPIArray` + Data to transform. + weight_view : `caput.mpiarray.MPIArray` + Weights corresponding to `data_view`. + out_cont : `containers.DelayTransform` or `containers.DelaySpectrum` + Container for output delay spectrum or power spectrum. + delays + The delays to evaluate at. + channel_ind + The indices of the available frequency channels in the full set of channels. + + Returns + ------- + out_cont : `containers.DelaySpectrum` + Output delay spectrum. + """ + nbase = out_cont.spectrum.global_shape[0] + ndelay = len(delays) + + # Read the delay power spectrum to use as the signal covariance + delay_ps = self.dps.spectrum[:].local_array + + # Iterate over combined baseline axis, and use the Wiener filter to estimate + # the delay spectrum for each element of `average_axis` (e.g. each RA) + for lbi, bi in out_cont.spectrum[:].enumerate(axis=0): + self.log.debug( + f"Estimating the delay spectrum of each baseline {bi}/{nbase} using " + "Wiener filter" + ) + + # Get the local selections + data = data_view.local_array[lbi] + weight = weight_view.local_array[lbi] + + # Apply the cuts to the data + t = self._cut_data(data, weight, channel_ind) + if t is None: + continue + data, weight, non_zero_channel = t + + # Pass the delay power spectrum and frequency spectrum for each "baseline" + # to the Wiener filtering routine.The delay power spectrum has been + # fftshifted in the DelaySpectrumEstimatorBase task, so need to do another + # fftshift. + y_spec = delay_spectrum_wiener_filter( + np.fft.fftshift(delay_ps[lbi, :]), + data, + ndelay, + weight, + window=self.window if self.apply_window else None, + fsel=non_zero_channel, + complex_timedomain=self.complex_timedomain, + ) + # FFT-shift along the last axis + out_cont.spectrum[bi] = np.fft.fftshift(y_spec, axes=1) + + return out_cont
+ + + +# Aliases to support old names +DelaySpectrumEstimator = DelayPowerSpectrumStokesIEstimator +DelaySpectrumEstimatorBase = DelayPowerSpectrumGeneralEstimator +DelaySpectrumWienerBase = DelaySpectrumWienerEstimator + + +
+[docs] +class DelayCrossPowerSpectrumEstimator( + DelayGeneralContainerBase, DelayGibbsSamplerBase +): + """A delay cross power spectrum estimator. + + This takes multiple compatible `FreqContainer`s as inputs and will return a + `DelayCrossSpectrum` container with the full pair-wise cross power spectrum. + """ + + def _process_data( + self, sslist: list[FreqContainer] + ) -> tuple[list[mpiarray.MPIArray], list[mpiarray.MPIArray], list[str]]: + if len(sslist) == 0: + raise ValueError("No datasets passed.") + + freq_ref = sslist[0].freq + + data_views = [] + weight_views = [] + coord_axes = None + + for ss in sslist: + ss.redistribute("freq") + + if (ss.freq != freq_ref).all(): + raise ValueError("Input containers must have the same frequencies.") + dv, wv, ca = DelayGeneralContainerBase._process_data(self, ss) + + if coord_axes is not None and not coord_axes == ca: + raise ValueError("Different axes found for the input containers.") + + data_views.append(dv) + weight_views.append(wv) + coord_axes = ca + + return data_views, weight_views, coord_axes + + def _create_output( + self, ss: list[FreqContainer], delays: np.ndarray, coord_axes: list[str] + ) -> ContainerBase: + """Create the output container for the delay power spectrum. + + If `coord_axes` is a list of strings then it is assumed to be a list of the + names of the folded axes. If it's an array then assume it is the actual axis + definition. + """ + ssref = ss[0] + ndata = len(ss) + + # If only one axis is being collapsed, use that as the baseline axis definition, + # otherwise just use integer indices + if len(coord_axes) == 1: + bl = ssref.index_map[coord_axes[0]] + else: + bl = np.prod([len(ssref.index_map[ax]) for ax in coord_axes]) + + # Initialise the spectrum container + delay_spec = containers.DelayCrossSpectrum( + baseline=bl, + dataset=ndata, + delay=delays, + sample=self.nsamp, + attrs_from=ssref, + ) + + delay_spec.redistribute("baseline") + delay_spec.spectrum[:] = 0.0 + + # Copy the index maps for all the flattened axes into the output container, and + # write out their order into an attribute so we can reconstruct this easily + # when loading in the spectrum + if isinstance(coord_axes, list): + for ax in coord_axes: + delay_spec.create_index_map(ax, ssref.index_map[ax]) + delay_spec.attrs["baseline_axes"] = coord_axes + + if self.save_samples: + delay_spec.add_dataset("spectrum_samples") + + # Save the frequency axis of the input data as an attribute in the output + # container + delay_spec.attrs["freq"] = ssref.freq + + return delay_spec + + def _evaluate(self, data_view, weight_view, out_cont, delays, channel_ind): + ndata = len(data_view) + ndelay = len(delays) + nbase = out_cont.spectrum.shape[-2] + + initial_S = self._get_initial_S(nbase, ndelay, delays.dtype) + + if initial_S.ndim == 2: + # Expand the sample shape to match the number of datasets + initial_S = ( + np.identity(ndata)[np.newaxis, ..., np.newaxis] + * initial_S[:, np.newaxis, np.newaxis] + ) + elif (initial_S.ndim != 4) or (initial_S.shape[1] != ndata): + raise ValueError( + f"Expected an initial sample with dimension 4 and {ndata} datasets. " + f"Got sample with dimension {initial_S.ndim} and shape {initial_S.shape}." + ) + + # Initialize the random number generator we'll use + rng = self.rng + + # Iterate over all baselines and use the Gibbs sampler to estimate the spectrum + for lbi, bi in out_cont.spectrum[:].enumerate(axis=-2): + self.log.debug(f"Delay transforming baseline {bi}/{nbase}") + + # Get the local selections for all datasets and combine into a single array + data = np.array([d.local_array[lbi] for d in data_view]) + weight = np.array([w.local_array[lbi] for w in weight_view]) + + # Apply the cuts to the data + t = self._cut_data(data, weight, channel_ind) + if t is None: + continue + data, weight, non_zero_channel = t + + spec = delay_spectrum_gibbs_cross( + data, + ndelay, + weight, + initial_S[lbi], + window=self.window if self.apply_window else None, + fsel=non_zero_channel, + niter=self.nsamp, + rng=rng, + ) + + # Take an average over the last half of the delay spectrum samples + # (presuming that removes the burn-in) + spec_av = np.median(spec[-(self.nsamp // 2) :], axis=0) + out_cont.spectrum[..., bi, :] = np.fft.fftshift(spec_av) + + if self.save_samples: + out_cont.datasets["spectrum_samples"][..., bi, :] = spec + + return out_cont
+ + + +
+[docs] +def stokes_I(sstream, tel): + """Extract instrumental Stokes I from a time/sidereal stream. + + Parameters + ---------- + sstream : containers.SiderealStream, container.TimeStream + Stream of correlation data. + tel : TransitTelescope + Instance describing the telescope. + + Returns + ------- + vis_I : mpiarray.MPIArray[nbase, nfreq, ntime] + The instrumental Stokes I visibilities, distributed over baselines. + vis_weight : mpiarray.MPIArray[nbase, nfreq, ntime] + The weights for each visibility, distributed over baselines. + ubase : np.ndarray[nbase, 2] + Baseline vectors corresponding to output. + """ + # Construct a complex number representing each baseline (used for determining + # unique baselines). + # NOTE: due to floating point precision, some baselines don't get matched as having + # the same lengths. To get around this, round all separations to 0.1 mm precision + bl_round = np.around(tel.baselines[:, 0] + 1.0j * tel.baselines[:, 1], 4) + + # ==== Unpack into Stokes I + ubase, uinv, ucount = np.unique(bl_round, return_inverse=True, return_counts=True) + ubase = ubase.astype(np.complex128, copy=False).view(np.float64).reshape(-1, 2) + nbase = ubase.shape[0] + + vis_shape = (nbase, sstream.vis.global_shape[0], sstream.vis.global_shape[2]) + vis_I = mpiarray.zeros(vis_shape, dtype=sstream.vis.dtype, axis=1) + vis_weight = mpiarray.zeros(vis_shape, dtype=sstream.weight.dtype, axis=1) + + # Iterate over products to construct the Stokes I vis + # TODO: this should be updated when driftscan gains a concept of polarisation + ssv = sstream.vis[:] + ssw = sstream.weight[:] + + # Cache beamclass as it's regenerated every call + beamclass = tel.beamclass[:] + for ii, ui in enumerate(uinv): + # Skip if not all polarisations were included + if ucount[ui] < 4: + continue + + fi, fj = tel.uniquepairs[ii] + bi, bj = beamclass[fi], beamclass[fj] + + upi = tel.feedmap[fi, fj] + + if upi == -1: + continue + + if bi == bj: + vis_I[ui] += ssv[:, ii] + vis_weight[ui] += ssw[:, ii] + + vis_I = vis_I.redistribute(axis=0) + vis_weight = vis_weight.redistribute(axis=0) + + return vis_I, vis_weight, ubase
+ + + +
+[docs] +def fourier_matrix_r2c(N, fsel=None): + """Generate a Fourier matrix to represent a real to complex FFT. + + Parameters + ---------- + N : integer + Length of timestream that we are transforming to. Must be even. + fsel : array_like, optional + Indexes of the frequency channels to include in the transformation + matrix. By default, assume all channels. + + Returns + ------- + Fr : np.ndarray + An array performing the Fourier transform from a real time series to + frequencies packed as alternating real and imaginary elements, + """ + if fsel is None: + fa = np.arange(N // 2 + 1) + else: + fa = np.array(fsel) + + fa = fa[:, np.newaxis] + ta = np.arange(N)[np.newaxis, :] + + Fr = np.zeros((2 * fa.shape[0], N), dtype=np.float64) + + Fr[0::2] = np.cos(2 * np.pi * ta * fa / N) + Fr[1::2] = -np.sin(2 * np.pi * ta * fa / N) + + return Fr
+ + + +
+[docs] +def fourier_matrix_c2r(N, fsel=None): + """Generate a Fourier matrix to represent a complex to real FFT. + + Parameters + ---------- + N : integer + Length of timestream that we are transforming to. Must be even. + fsel : array_like, optional + Indexes of the frequency channels to include in the transformation + matrix. By default, assume all channels. + + Returns + ------- + Fr : np.ndarray + An array performing the Fourier transform from frequencies packed as + alternating real and imaginary elements, to the real time series. + """ + if fsel is None: + fa = np.arange(N // 2 + 1) + else: + fa = np.array(fsel) + + fa = fa[np.newaxis, :] + + mul = np.where((fa == 0) | (fa == N // 2), 1.0, 2.0) / N + + ta = np.arange(N)[:, np.newaxis] + + Fr = np.zeros((N, 2 * fa.shape[1]), dtype=np.float64) + + Fr[:, 0::2] = np.cos(2 * np.pi * ta * fa / N) * mul + Fr[:, 1::2] = -np.sin(2 * np.pi * ta * fa / N) * mul + + return Fr
+ + + +
+[docs] +def fourier_matrix_c2c(N, fsel=None): + """Generate a Fourier matrix to represent a complex to complex FFT. + + These Fourier conventions match `numpy.fft.fft()`. + + Parameters + ---------- + N : integer + Length of timestream that we are transforming to. + fsel : array_like, optional + Indices of the frequency channels to include in the transformation + matrix. By default, assume all channels. + + Returns + ------- + F : np.ndarray + An array performing the Fourier transform from a complex time series to + frequencies, with both input and output packed as alternating real and + imaginary elements. + """ + if fsel is None: + fa = np.arange(N) + else: + fa = np.array(fsel) + + fa = fa[:, np.newaxis] + ta = np.arange(N)[np.newaxis, :] + + F = np.zeros((2 * fa.shape[0], 2 * N), dtype=np.float64) + + arg = 2 * np.pi * ta * fa / N + F[0::2, 0::2] = np.cos(arg) + F[0::2, 1::2] = np.sin(arg) + F[1::2, 0::2] = -np.sin(arg) + F[1::2, 1::2] = np.cos(arg) + + return F
+ + + +
+[docs] +def fourier_matrix(N: int, fsel: Optional[np.ndarray] = None) -> np.ndarray: + """Generate a Fourier matrix to represent a real to complex FFT. + + Parameters + ---------- + N : integer + Length of timestream that we are transforming to. Must be even. + fsel : array_like, optional + Indexes of the frequency channels to include in the transformation + matrix. By default, assume all channels. + + Returns + ------- + Fr : np.ndarray + An array performing the Fourier transform from a real time series to + frequencies packed as alternating real and imaginary elements, + """ + if fsel is None: + fa = np.arange(N) + else: + fa = np.array(fsel) + + fa = fa[:, np.newaxis] + ta = np.arange(N)[np.newaxis, :] + + return np.exp(-2.0j * np.pi * ta * fa / N)
+ + + +def _complex_to_alternating_real(array): + """View complex numbers as an array with alternating real and imaginary components. + + Parameters + ---------- + array : array_like + Input array of complex numbers. + + Returns + ------- + out : array_like + Output array of alternating real and imaginary components. These components are + expanded along the last axis, such that if `array` has `N` complex elements in + its last axis, `out` will have `2N` real elements. + """ + return array.astype(np.complex128, order="C").view(np.float64) + + +def _alternating_real_to_complex(array): + """View real numbers as complex, interpreted as alternating real and imag. components. + + Parameters + ---------- + array : array_like + Input array of real numbers. Last axis must have even number of elements. + + Returns + ------- + out : array_like + Output array of complex numbers, derived from compressing the last axis (if + `array` has `N` real elements in the last axis, `out` will have `N/2` complex + elements). + """ + return array.astype(np.float64, order="C").view(np.complex128) + + +def _compute_delay_spectrum_inputs(data, N, Ni, fsel, window, complex_timedomain): + """Compute quantities needed for Gibbs sampling and/or Wiener filtering. + + These quantities are needed by both :func:`delay_power_spectrum_gibbs` and + :func:`delay_spectrum_wiener_filter`, so we compute them in this separate routine. + """ + total_freq = N if complex_timedomain else N // 2 + 1 + + if fsel is None: + fsel = np.arange(total_freq) + + # Construct the Fourier matrix + F = ( + fourier_matrix_c2c(N, fsel) + if complex_timedomain + else fourier_matrix_r2c(N, fsel) + ) + + # Construct a view of the data with alternating real and imaginary parts + data = _complex_to_alternating_real(data).T.copy() + + # Window the frequency data + if window is not None: + # Construct the window function + x = fsel * 1.0 / total_freq + w = tools.window_generalised(x, window=window) + w = np.repeat(w, 2) + + # Apply to the projection matrix and the data + F *= w[:, np.newaxis] + data *= w[:, np.newaxis] + + if complex_timedomain: + is_real_freq = np.zeros_like(fsel).astype(bool) + else: + is_real_freq = (fsel == 0) | (fsel == N // 2) + + # Construct the Noise inverse array for the real and imaginary parts of the + # frequency spectrum (taking into account that the zero and Nyquist frequencies are + # strictly real if the delay spectrum is assumed to be real) + Ni_r = np.zeros(2 * Ni.shape[0]) + Ni_r[0::2] = np.where(is_real_freq, Ni, Ni * 2) + Ni_r[1::2] = np.where(is_real_freq, 0.0, Ni * 2) + + # Create the transpose of the Fourier matrix weighted by the noise + # (this is used multiple times) + FTNih = F.T * Ni_r[np.newaxis, :] ** 0.5 + FTNiF = np.dot(FTNih, FTNih.T) + + # Pre-whiten the data to save doing it repeatedly + data = data * Ni_r[:, np.newaxis] ** 0.5 + + # Return data and inverse-noise-weighted Fourier matrices + return data, FTNih, FTNiF + + +
+[docs] +def delay_power_spectrum_gibbs( + data, + N, + Ni, + initial_S, + window="nuttall", + fsel=None, + niter=20, + rng=None, + complex_timedomain=False, +): + """Estimate the delay power spectrum by Gibbs sampling. + + This routine estimates the spectrum at the `N` delay samples conjugate to + an input frequency spectrum with ``N/2 + 1`` channels (if the delay spectrum is + assumed real) or `N` channels (if the delay spectrum is assumed complex). + A subset of these channels can be specified using the `fsel` argument. + + Parameters + ---------- + data : np.ndarray[:, freq] + Data to estimate the delay spectrum of. + N : int + The length of the output delay spectrum. There are assumed to be `N/2 + 1` + total frequency channels if assuming a real delay spectrum, or `N` channels + for a complex delay spectrum. + Ni : np.ndarray[freq] + Inverse noise variance. + initial_S : np.ndarray[delay] + The initial delay power spectrum guess. + window : one of {'nuttall', 'blackman_nuttall', 'blackman_harris', None}, optional + Apply an apodisation function. Default: 'nuttall'. + fsel : np.ndarray[freq], optional + Indices of channels that we have data at. By default assume all channels. + niter : int, optional + Number of Gibbs samples to generate. + rng : np.random.Generator, optional + A generator to use to produce the random samples. + complex_timedomain : bool, optional + If True, assume input data arose from a complex timestream. If False, assume + input data arose from a real timestream, such that the first and last frequency + channels have purely real values. Default: False. + + Returns + ------- + spec : list + List of spectrum samples. + """ + # Get reference to RNG + if rng is None: + rng = random.default_rng() + + spec = [] + + # Pre-whiten and apply frequency window to data, and compute F^dagger N^{-1/2} + # and F^dagger N^{-1} F + data, FTNih, FTNiF = _compute_delay_spectrum_inputs( + data, N, Ni, fsel, window, complex_timedomain + ) + + # Set the initial guess for the delay power spectrum. + S_samp = initial_S + + def _draw_signal_sample_f(S): + # Draw a random sample of the signal (delay spectrum) assuming a Gaussian model + # with a given delay power spectrum `S`. Do this using the perturbed Wiener + # filter approach + + # This method is fastest if the number of frequencies is larger than the number + # of delays we are solving for. Typically this isn't true, so we probably want + # `_draw_signal_sample_t` + + # Construct the Wiener covariance + if complex_timedomain: + # If delay spectrum is complex, extend S to correspond to the individual + # real and imaginary components of the delay spectrum, each of which have + # power spectrum equal to 0.5 times the power spectrum of the complex + # delay spectrum, if the statistics are circularly symmetric + S = 0.5 * np.repeat(S, 2) + Si = 1.0 / S + Ci = np.diag(Si) + FTNiF + + # Draw random vectors that form the perturbations + if complex_timedomain: + # If delay spectrum is complex, draw for real and imaginary components + # separately + w1 = rng.standard_normal((2 * N, data.shape[1])) + else: + w1 = rng.standard_normal((N, data.shape[1])) + w2 = rng.standard_normal(data.shape) + + # Construct the random signal sample by forming a perturbed vector and + # then doing a matrix solve + y = np.dot(FTNih, data + w2) + Si[:, np.newaxis] ** 0.5 * w1 + + return la.solve(Ci, y, assume_a="pos") + + def _draw_signal_sample_t(S): + # This method is fastest if the number of delays is larger than the number of + # frequencies. This is usually the regime we are in. + + # Construct various dependent matrices + if complex_timedomain: + # If delay spectrum is complex, extend S to correspond to the individual + # real and imaginary components of the delay spectrum, each of which have + # power spectrum equal to 0.5 times the power spectrum of the complex + # delay spectrum, if the statistics are circularly symmetric + S = 0.5 * np.repeat(S, 2) + Sh = S**0.5 + Rt = Sh[:, np.newaxis] * FTNih + R = Rt.T.conj() + + # Draw random vectors that form the perturbations + if complex_timedomain: + # If delay spectrum is complex, draw for real and imaginary components + # separately + w1 = rng.standard_normal((2 * N, data.shape[1])) + else: + w1 = rng.standard_normal((N, data.shape[1])) + w2 = rng.standard_normal(data.shape) + + # Perform the solve step (rather than explicitly using the inverse) + y = data + w2 - np.dot(R, w1) + Ci = np.identity(2 * Ni.shape[0]) + np.dot(R, Rt) + x = la.solve(Ci, y, assume_a="pos") + + return Sh[:, np.newaxis] * (np.dot(Rt, x) + w1) + + def _draw_ps_sample(d): + # Draw a random delay power spectrum sample assuming the signal is Gaussian and + # we have a flat prior on the power spectrum. + # This means drawing from a inverse chi^2. + + if complex_timedomain: + # If delay spectrum is complex, combine real and imaginary components + # stored in d, such that variance below is variance of complex spectrum + d = d[0::2] + 1.0j * d[1::2] + S_hat = d.var(axis=1) + + df = d.shape[1] + chi2 = rng.chisquare(df, size=d.shape[0]) + + return S_hat * df / chi2 + + # Select the method to use for the signal sample based on how many frequencies + # versus delays there are + _draw_signal_sample = ( + _draw_signal_sample_f if (len(fsel) > 0.25 * N) else _draw_signal_sample_t + ) + + # Perform the Gibbs sampling iteration for a given number of loops and + # return the power spectrum output of them. + for ii in range(niter): + d_samp = _draw_signal_sample(S_samp) + S_samp = _draw_ps_sample(d_samp) + + spec.append(S_samp) + + return spec
+ + + +
+[docs] +def delay_spectrum_gibbs_cross( + data: np.ndarray, + N: int, + Ni: np.ndarray, + initial_S: np.ndarray, + window: str = "nuttall", + fsel: Optional[np.ndarray] = None, + niter: int = 20, + rng: Optional[np.random.Generator] = None, +) -> list[np.ndarray]: + """Estimate the delay power spectrum by Gibbs sampling. + + This routine estimates the spectrum at the `N` delay samples conjugate to + an input frequency spectrum with ``N/2 + 1`` channels (if the delay spectrum is + assumed real) or `N` channels (if the delay spectrum is assumed complex). + A subset of these channels can be specified using the `fsel` argument. + + Parameters + ---------- + data + A 3D array of [dataset, sample, freq]. The delay cross-power spectrum of these + will be calculated. + N + The length of the output delay spectrum. There are assumed to be `N/2 + 1` + total frequency channels if assuming a real delay spectrum, or `N` channels + for a complex delay spectrum. + Ni + Inverse noise variance as a 3D [dataset, sample, freq] array. + initial_S + The initial delay cross-power spectrum guess. A 3D array of [data1, data2, + delay]. + window : one of {'nuttall', 'blackman_nuttall', 'blackman_harris', None}, optional + Apply an apodisation function. Default: 'nuttall'. + fsel + Indices of channels that we have data at. By default assume all channels. + niter + Number of Gibbs samples to generate. + rng + A generator to use to produce the random samples. + + Returns + ------- + spec : list + List of cross-power spectrum samples. + """ + # Get reference to RNG + + if rng is None: + rng = random.default_rng() + + spec = [] + + nd, nsamp, Nf = data.shape + + if fsel is None: + fsel = np.arange(Nf) + elif len(fsel) != Nf: + raise ValueError( + "Length of frequency selection must match frequencies passed. " + f"{len(fsel)} != {data.shape[-1]}" + ) + + # Construct the Fourier matrix + F = fourier_matrix(N, fsel) + + if nd == 0: + raise ValueError("Need at least one set of data") + + # We want the sample axis to be last + data = data.transpose(0, 2, 1) + + # Window the frequency data + if window is not None: + # Construct the window function + x = fsel * 1.0 / N + w = tools.window_generalised(x, window=window) + + # Apply to the projection matrix and the data + F *= w[:, np.newaxis] + data *= w[:, np.newaxis] + + # Create the transpose of the Fourier matrix weighted by the noise + # (this is used multiple times) + # This is packed as a single freq -> delay projection per dataset + FTNih = F.T[np.newaxis, :, :] * Ni[:, np.newaxis, :] ** 0.5 + + # This should be an array for each dataset i of F_i^H N_i^{-1} F_i + FTNiF = np.zeros((nd, N, nd, N), dtype=np.complex128) + for ii in range(nd): + FTNiF[ii, :, ii] = FTNih[ii] @ FTNih[ii].T.conj() + + # Pre-whiten the data to save doing it repeatedly + data *= Ni[:, :, np.newaxis] ** 0.5 + + # Set the initial guess for the delay power spectrum. + S_samp = initial_S + + def _draw_signal_sample_f(S): + # Draw a random sample of the signal (delay spectrum) assuming a Gaussian model + # with a given delay power spectrum `S`. Do this using the perturbed Wiener + # filter approach + + # This method is fastest if the number of frequencies is larger than the number + # of delays we are solving for. Typically this isn't true, so we probably want + # `_draw_signal_sample_t` + + Si = np.empty_like(S) + Sh = np.empty((N, nd, nd), dtype=S.dtype) + + for ii in range(N): + inv = la.inv(S[:, :, ii]) + Si[:, :, ii] = inv + Sh[ii, :, :] = la.cholesky(S[:, :, ii], lower=False) + + Ci = FTNiF.copy() + for ii in range(nd): + for jj in range(nd): + Ci[ii, :, jj] += np.diag(Si[ii, jj]) + + w1 = random.standard_complex_normal((N, nd, nsamp), rng=rng) + w2 = random.standard_complex_normal(data.shape, rng=rng) + + # Construct the random signal sample by forming a perturbed vector and + # then doing a matrix solve + y = FTNih @ (data + w2) + + for ii in range(N): + w1s = la.solve_triangular( + Sh[ii], + w1[ii], + overwrite_b=True, + lower=False, + check_finite=False, + ) + y[:, ii] += w1s + # NOTE: Other combinations that you might think would work don't appear to + # be stable. Don't try these: + # y[:, ii] += Si[:, :, ii] @ Sh[:, :, ii] @ w1[:, ii] + # y[:, ii] += Shi[:, :, ii] @ w1[:, ii] + + cf = la.cho_factor( + Ci.reshape(nd * N, nd * N), + overwrite_a=True, + check_finite=False, + ) + + return la.cho_solve( + cf, + y.reshape(nd * N, nsamp), + overwrite_b=True, + check_finite=False, + ).reshape(nd, N, nsamp) + + def _draw_signal_sample_t(S): + # This method is fastest if the number of delays is larger than the number of + # frequencies. This is usually the regime we are in. + raise NotImplementedError("Drawing samples in the time basis not yet written.") + + def _draw_ps_sample(d): + # Draw a random delay power spectrum sample assuming the signal is Gaussian and + # we have a flat prior on the power spectrum. + # This means drawing from a inverse chi^2. + + # Estimate the sample covariance + S = np.empty((nd, nd, N), dtype=np.complex128) + for ii in range(N): + S[:, :, ii] = np.cov(d[:, ii], bias=True) + + # Then in place draw a sample of the true covariance from the posterior which + # is an inverse Wishart + for ii in range(N): + Si = la.inv(S[:, :, ii]) + Si_samp = random.complex_wishart(Si, nsamp, rng=rng) / nsamp + S[:, :, ii] = la.inv(Si_samp) + + return S + + # Select the method to use for the signal sample based on how many frequencies + # versus delays there are. At the moment only the _f method is implemented. + _draw_signal_sample = _draw_signal_sample_f + + # Perform the Gibbs sampling iteration for a given number of loops and + # return the power spectrum output of them. + try: + for ii in range(niter): + d_samp = _draw_signal_sample(S_samp) + S_samp = _draw_ps_sample(d_samp) + + spec.append(S_samp) + except la.LinAlgError as e: + raise RuntimeError("Exiting earlier as singular") from e + + return spec
+ + + +# Alias delay_spectrum_gibbs to delay_power_spectrum_gibbs, for backwards compatibility +delay_spectrum_gibbs = delay_power_spectrum_gibbs + + +
+[docs] +def delay_spectrum_wiener_filter( + delay_PS, data, N, Ni, window="nuttall", fsel=None, complex_timedomain=False +): + """Estimate the delay spectrum from an input frequency spectrum by Wiener filtering. + + This routine estimates the spectrum at the `N` delay samples conjugate to + an input frequency spectrum with ``N/2 + 1`` channels (if the delay spectrum is + assumed real) or `N` channels (if the delay spectrum is assumed complex). + A subset of these channels can be specified using the `fsel` argument. + + Parameters + ---------- + delay_PS : np.ndarray[ndelay] + Delay power spectrum to use for the signal covariance in the Wiener filter. + data : np.ndarray[nsample, freq] + Data to estimate the delay spectrum of. + N : int + The length of the output delay spectrum. There are assumed to be `N/2 + 1` + total frequency channels if assuming a real delay spectrum, or `N` channels + for a complex delay spectrum. + Ni : np.ndarray[freq] + Inverse noise variance. + fsel : np.ndarray[freq], optional + Indices of channels that we have data at. By default assume all channels. + window : one of {'nuttall', 'blackman_nuttall', 'blackman_harris', None}, optional + Apply an apodisation function. Default: 'nuttall'. + complex_timedomain : bool, optional + If True, assume input data arose from a complex timestream. If False, assume + input data arose from a real timestream, such that the first and last frequency + channels have purely real values. Default: False. + + Returns + ------- + y_spec : np.ndarray[nsample, ndelay] + Delay spectrum for each element of the `sample` axis. + """ + # Pre-whiten and apply frequency window to data, and compute F^dagger N^{-1/2} + # and F^dagger N^{-1} F + data, FTNih, FTNiF = _compute_delay_spectrum_inputs( + data, N, Ni, fsel, window, complex_timedomain + ) + + # Apply F^dagger N^{-1/2} to input frequency spectrum + y = np.dot(FTNih, data) + + # Construct the Wiener covariance + if complex_timedomain: + # If delay spectrum is complex, extend delay_PS to correspond to the individual + # real and imaginary components of the delay spectrum, each of which have + # power spectrum equal to 0.5 times the power spectrum of the complex + # delay spectrum, if the statistics are circularly symmetric + S = 0.5 * np.repeat(delay_PS, 2) + Si = 1.0 / S + else: + Si = 1.0 / delay_PS + + Ci = np.diag(Si) + FTNiF + + # Solve the linear equation for the Wiener-filtered spectrum, and transpose to + # [average_axis, delay] + y_spec = la.solve(Ci, y, assume_a="pos").T + + if complex_timedomain: + y_spec = _alternating_real_to_complex(y_spec) + + return y_spec
+ + + +
+[docs] +def null_delay_filter(freq, max_delay, mask, num_delay=200, tol=1e-8, window=True): + """Take frequency data and null out any delays below some value. + + Parameters + ---------- + freq : np.ndarray[freq] + Frequencies we have data at. + max_delay : float + Maximum delay to keep. + mask : np.ndarray[freq] + Frequencies to mask out. + num_delay : int, optional + Number of delay values to use. + tol : float, optional + Cut off value for singular values. + window : bool, optional + Apply a window function to the data while filtering. + + Returns + ------- + filter : np.ndarray[freq, freq] + The filter as a 2D matrix. + """ + # Construct the window function + x = (freq - freq.min()) / freq.ptp() + w = tools.window_generalised(x, window="nuttall") + + delay = np.linspace(-max_delay, max_delay, num_delay) + + # Construct the Fourier matrix + F = mask[:, np.newaxis] * np.exp( + 2.0j * np.pi * delay[np.newaxis, :] * freq[:, np.newaxis] + ) + + if window: + F *= w[:, np.newaxis] + + # Use an SVD to figure out the set of significant modes spanning the delays + # we are wanting to get rid of. + # NOTE: we've experienced some convergence failures in here which ultimately seem + # to be the fault of MKL (see https://github.com/scipy/scipy/issues/10032 and links + # therein). This seems to be limited to the `gesdd` LAPACK routine, so we can get + # around it by switching to `gesvd`. + u, sig, vh = la.svd(F, lapack_driver="gesvd") + nmodes = np.sum(sig > tol * sig.max()) + p = u[:, :nmodes] + + # Construct a projection matrix for the filter + proj = np.identity(len(freq)) - np.dot(p, p.T.conj()) + proj *= mask[np.newaxis, :] + + if window: + proj *= w[np.newaxis, :] + + return proj
+ + + +
+[docs] +def match_axes(dset1, dset2): + """Make sure that dset2 has the same set of axes as dset1. + + Sometimes the weights are missing axes (usually where the entries would all be + the same), we need to map these into one another and expand the weights to the + same size as the visibilities. This assumes that the vis/weight axes are in the + same order when present + + Parameters + ---------- + dset1 + The dataset with more axes. + dset2 + The dataset with a subset of axes. For the moment these are assumed to be in + the same order. + + Returns + ------- + dset2_view + A view of dset2 with length-1 axes inserted to match the axes missing from + dset1. + """ + axes1 = dset1.attrs["axis"] + axes2 = dset2.attrs["axis"] + bcast_slice = tuple(slice(None) if ax in axes2 else np.newaxis for ax in axes1) + + return dset2[:][bcast_slice]
+ + + +
+[docs] +def flatten_axes( + dset: memh5.MemDatasetDistributed, + axes_to_keep: List[str], + match_dset: Optional[memh5.MemDatasetDistributed] = None, +) -> Tuple[mpiarray.MPIArray, List[str]]: + """Move the specified axes of the dataset to the back, and flatten all others. + + Optionally this will add length-1 axes to match the axes of another dataset. + + Parameters + ---------- + dset + The dataset to reshape. + axes_to_keep + The names of the axes to keep. + match_dset + An optional dataset to match the shape of. + + Returns + ------- + flat_array + The MPIArray representing the re-arranged dataset. Distributed along the + flattened axis. + flat_axes + The names of the flattened axes from slowest to fastest varying. + """ + # Find the relevant axis positions + data_axes = list(dset.attrs["axis"]) + + # Check that the requested datasets actually exist + for axis in axes_to_keep: + if axis not in data_axes: + raise ValueError(f"Specified {axis=} not present in dataset.") + + # If specified, add extra axes to match the shape of the given dataset + if match_dset and tuple(dset.attrs["axis"]) != tuple(match_dset.attrs["axis"]): + dset_full = np.empty_like(match_dset[:]) + dset_full[:] = match_axes(match_dset, dset) + + axes_ind = [data_axes.index(axis) for axis in axes_to_keep] + + # Get an MPIArray and make sure it is distributed along one of the preserved axes + data_array = dset[:] + if data_array.axis not in axes_ind: + data_array = data_array.redistribute(axes_ind[0]) + + # Create a view of the dataset with the relevant axes at the back, + # and all others moved to the front (retaining their relative order) + other_axes = [ax for ax in range(len(data_axes)) if ax not in axes_ind] + data_array = data_array.transpose(other_axes + axes_ind) + + # Get the explicit shape of the axes that will remain, but set the distributed one + # to None (as will be needed for MPIArray.reshape) + remaining_shape = list(data_array.shape) + remaining_shape[data_array.axis] = None + new_ax_len = np.prod(remaining_shape[: -len(axes_ind)]) + remaining_shape = remaining_shape[-len(axes_ind) :] + + # Reshape the MPIArray, and redistribute over the flattened axis + data_array = data_array.reshape((new_ax_len, *remaining_shape)) + data_array = data_array.redistribute(axis=0) + + other_axes_names = [data_axes[ax] for ax in other_axes] + + return data_array, other_axes_names
+ + + +def _move_front(arr: np.ndarray, axis: int, shape: tuple) -> np.ndarray: + # Move the specified axis to the front and flatten to give a 2D array + new_arr = np.moveaxis(arr, axis, 0) + return new_arr.reshape(shape[axis], -1) + + +def _inv_move_front(arr: np.ndarray, axis: int, shape: tuple) -> np.ndarray: + # Move the first axis back to it's original position and return the original shape, + # i.e. reverse the above operation + rshape = (shape[axis],) + shape[:axis] + shape[(axis + 1) :] + new_arr = arr.reshape(rshape) + new_arr = np.moveaxis(new_arr, 0, axis) + return new_arr.reshape(shape) + + +def _take_view(arr: np.ndarray, ind: int, axis: int) -> np.ndarray: + # Like np.take but returns a view (instead of a copy), but only supports a scalar + # index + sl = (slice(None),) * axis + return arr[(*sl, ind)] +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/fgfilter.html b/docs/_modules/draco/analysis/fgfilter.html new file mode 100644 index 000000000..fac9f0b3a --- /dev/null +++ b/docs/_modules/draco/analysis/fgfilter.html @@ -0,0 +1,357 @@ + + + + + + draco.analysis.fgfilter — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.fgfilter

+"""Tasks for foreground filtering data."""
+
+import numpy as np
+from caput import config
+
+from ..core import containers, io, task
+
+
+class _ProjectFilterBase(task.SingleTask):
+    """A base class for projecting data to/from a different basis.
+
+    Attributes
+    ----------
+    mode : string
+        Which projection to perform. Into the new basis (forward), out of the
+        new basis (backward), and forward then backward in order to filter the
+        data through the basis (filter).
+    """
+
+    mode = config.enum(["forward", "backward", "filter"], default="forward")
+
+    def process(self, inp):
+        """Project or filter the input data.
+
+        Parameters
+        ----------
+        inp : memh5.BasicCont
+            Data to process.
+
+        Returns
+        -------
+        output : memh5.BasicCont
+        """
+        if self.mode == "forward":
+            return self._forward(inp)
+
+        if self.mode == "backward":
+            return self._backward(inp)
+
+        if self.mode == "filter":
+            return self._backward(self._forward(inp))
+
+        return None
+
+    def _forward(self, inp):
+        pass
+
+    def _backward(self, inp):
+        pass
+
+
+
+[docs] +class SVDModeProject(_ProjectFilterBase): + """SVD projection between the raw m-modes and the reduced degrees of freedom. + + Note that this produces the packed SVD modes, with the modes from each + frequency concatenated. + """ + +
+[docs] + def setup(self, bt): + """Set the beamtransfer instance. + + Parameters + ---------- + bt : BeamTransfer + This can also take a ProductManager instance. + """ + self.beamtransfer = io.get_beamtransfer(bt)
+ + + def _forward(self, mmodes): + # Forward transform into SVD basis + + bt = self.beamtransfer + tel = bt.telescope + + svdmodes = containers.SVDModes( + mode=bt.ndofmax, axes_from=mmodes, attrs_from=mmodes + ) + svdmodes.vis[:] = 0.0 + + mmodes.redistribute("m") + svdmodes.redistribute("m") + + # Iterate over local m's, project mode and save to disk. + for lm, mi in mmodes.vis[:].enumerate(axis=0): + tm = mmodes.vis[mi].transpose((1, 0, 2)).reshape(tel.nfreq, 2 * tel.npairs) + svdm = bt.project_vector_telescope_to_svd(mi, tm) + + svdmodes.nmode[mi] = len(svdm) + svdmodes.vis[mi, : svdmodes.nmode[mi]] = svdm + + # TODO: apply transform correctly to weights. For now just crudely + # transfer over the weights, only really good for determining + # whether an m-mode should be masked comoletely + svdmodes.weight[mi] = np.median(mmodes.weight[mi]) + + return svdmodes + + def _backward(self, svdmodes): + # Backward transform from SVD basis into the m-modes + + bt = self.beamtransfer + tel = bt.telescope + + # Try and fetch out the feed index and info from the telescope object. + try: + feed_index = tel.input_index + except AttributeError: + feed_index = tel.nfeed + + # Construct frequency index map + freqmap = np.zeros( + len(tel.frequencies), dtype=[("centre", np.float64), ("width", np.float64)] + ) + freqmap["centre"][:] = tel.frequencies + freqmap["width"][:] = np.abs(np.diff(tel.frequencies)[0]) + + # Construct the new m-mode container + mmodes = containers.MModes( + freq=freqmap, + prod=tel.uniquepairs, + input=feed_index, + attrs_from=svdmodes, + axes_from=svdmodes, + ) + mmodes.redistribute("m") + svdmodes.redistribute("m") + + # Iterate over local m's, project mode and save to disk. + for lm, mi in mmodes.vis[:].enumerate(axis=0): + svdm = svdmodes.vis[mi] + tm = bt.project_vector_svd_to_telescope(mi, svdm) + + svdmodes.nmode[mi] = len(svdm) + mmodes.vis[mi] = tm.transpose((1, 0, 2)) + + # TODO: apply transform correctly to weights. For now just crudely + # transfer over the weights, only really good for determining + # whether an m-mode should be masked comoletely + mmodes.weight[mi] = np.median(svdmodes.weight[mi]) + + return mmodes
+ + + +
+[docs] +class KLModeProject(_ProjectFilterBase): + """Project between the SVD and KL basis. + + Attributes + ---------- + threshold : float, optional + KL mode threshold. + klname : str + Name of filter to use. + mode : string + Which projection to perform. Into the KL basis (forward), out of the + KL basis (backward), and forward then backward in order to KL filter the + data through the basis (filter). + """ + + threshold = config.Property(proptype=float, default=None) + klname = config.Property(proptype=str) + +
+[docs] + def setup(self, manager): + """Set the product manager that holds the saved KL modes.""" + self.product_manager = manager
+ + + def _forward(self, svdmodes): + # Forward transform into the KL modes + + bt = self.product_manager.beamtransfer + + # Check and set the KL basis we are using + if self.klname not in self.product_manager.kltransforms: + raise RuntimeError( + f"Requested KL basis {self.kname} not available (options " + f"are {list(self.product_manager.kltransforms.items())!r})" + ) + kl = self.product_manager.kltransforms[self.klname] + + # Construct the container and redistribute + klmodes = containers.KLModes( + mode=bt.ndofmax, axes_from=svdmodes, attrs_from=svdmodes + ) + + klmodes.vis[:] = 0.0 + + klmodes.redistribute("m") + svdmodes.redistribute("m") + + # Iterate over local m's and project mode into KL basis + for lm, mi in svdmodes.vis[:].enumerate(axis=0): + sm = svdmodes.vis[mi][: svdmodes.nmode[mi]] + klm = kl.project_vector_svd_to_kl(mi, sm, threshold=self.threshold) + + klmodes.nmode[mi] = len(klm) + klmodes.vis[mi, : klmodes.nmode[mi]] = klm + + # TODO: apply transform correctly to weights. For now just crudely + # transfer over the weights, only really good for determining + # whether an m-mode should be masked comoletely + klmodes.weight[mi] = np.median(svdmodes.weight[mi]) + + return klmodes + + def _backward(self, klmodes): + # Backward transform from the KL modes into the SVD modes + + bt = self.product_manager.beamtransfer + + # Check and set the KL basis we are using + if self.klname not in self.product_manager.kltransforms: + raise RuntimeError( + f"Requested KL basis {self.klname} not available (options " + f"are {list(self.product_manager.kltransforms.items())!r})" + ) + kl = self.product_manager.kltransforms[self.klname] + + # Construct the container and redistribute + + svdmodes = containers.SVDModes( + mode=bt.ndofmax, axes_from=klmodes, attrs_from=klmodes + ) + klmodes.redistribute("m") + svdmodes.redistribute("m") + + # Iterate over local m's and project mode into KL basis + for lm, mi in klmodes.vis[:].enumerate(axis=0): + klm = klmodes.vis[mi][: klmodes.nmode[mi]] + sm = kl.project_vector_kl_to_svd(mi, klm, threshold=self.threshold) + + svdmodes.nmode[mi] = len(sm) + svdmodes.vis[mi, : svdmodes.nmode[mi]] = sm + + # TODO: apply transform correctly to weights. For now just crudely + # transfer over the weights, only really good for determining + # whether an m-mode should be masked comoletely + svdmodes.weight[mi] = np.median(klmodes.weight[mi]) + + return svdmodes
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/flagging.html b/docs/_modules/draco/analysis/flagging.html new file mode 100644 index 000000000..1daf7b1aa --- /dev/null +++ b/docs/_modules/draco/analysis/flagging.html @@ -0,0 +1,2339 @@ + + + + + + draco.analysis.flagging — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.flagging

+"""Tasks for flagging out bad or unwanted data.
+
+This includes data quality flagging on timestream data; sun excision on sidereal
+data; and pre-map making flagging on m-modes.
+
+The convention for flagging/masking is `True` for contaminated samples that should
+be excluded and `False` for clean samples.
+"""
+
+import warnings
+from typing import Union, overload
+
+import numpy as np
+import scipy.signal
+from caput import config, mpiarray, weighted_median
+
+from ..core import containers, io, task
+from ..util import rfi, tools
+
+
+
+[docs] +class DayMask(task.SingleTask): + """Crudely simulate a masking out of the daytime data. + + Attributes + ---------- + start, end : float + Start and end of masked out region. + width : float + Use a smooth transition of given width between the fully masked and + unmasked data. This is interior to the region marked by start and end. + zero_data : bool, optional + Zero the data in addition to modifying the noise weights + (default is True). + remove_average : bool, optional + Estimate and remove the mean level from each visibilty. This estimate + does not use data from the masked region. + """ + + start = config.Property(proptype=float, default=90.0) + end = config.Property(proptype=float, default=270.0) + + width = config.Property(proptype=float, default=60.0) + + zero_data = config.Property(proptype=bool, default=True) + remove_average = config.Property(proptype=bool, default=True) + +
+[docs] + def process(self, sstream): + """Apply a day time mask. + + Parameters + ---------- + sstream : containers.SiderealStream + Unmasked sidereal stack. + + Returns + ------- + mstream : containers.SiderealStream + Masked sidereal stream. + """ + sstream.redistribute("freq") + + ra_shift = (sstream.ra[:] - self.start) % 360.0 + end_shift = (self.end - self.start) % 360.0 + + # Crudely mask the on and off regions + mask_bool = ra_shift > end_shift + + # Put in the transition at the start of the day + mask = np.where( + ra_shift < self.width, + 0.5 * (1 + np.cos(np.pi * (ra_shift / self.width))), + mask_bool, + ) + + # Put the transition at the end of the day + mask = np.where( + np.logical_and(ra_shift > end_shift - self.width, ra_shift <= end_shift), + 0.5 * (1 + np.cos(np.pi * ((ra_shift - end_shift) / self.width))), + mask, + ) + + if self.remove_average: + # Estimate the mean level from unmasked data + import scipy.stats + + nanvis = ( + sstream.vis[:] + * np.where(mask_bool, 1.0, np.nan)[np.newaxis, np.newaxis, :] + ) + average = scipy.stats.nanmedian(nanvis, axis=-1)[:, :, np.newaxis] + sstream.vis[:] -= average + + # Apply the mask to the data + if self.zero_data: + sstream.vis[:] *= mask + + # Modify the noise weights + sstream.weight[:] *= mask**2 + + return sstream
+
+ + + +
+[docs] +class MaskMModeData(task.SingleTask): + """Mask out mmode data ahead of map making. + + Attributes + ---------- + auto_correlations : bool + Exclude auto correlations if set (default=False). + m_zero : bool + Ignore the m=0 mode (default=False). + positive_m : bool + Include positive m-modes (default=True). + negative_m : bool + Include negative m-modes (default=True). + mask_low_m : int, optional + If set, mask out m's lower than this threshold. + """ + + auto_correlations = config.Property(proptype=bool, default=False) + m_zero = config.Property(proptype=bool, default=False) + positive_m = config.Property(proptype=bool, default=True) + negative_m = config.Property(proptype=bool, default=True) + + mask_low_m = config.Property(proptype=int, default=None) + +
+[docs] + def process(self, mmodes): + """Mask out unwanted datain the m-modes. + + Parameters + ---------- + mmodes : containers.MModes + Mmode container to mask + + Returns + ------- + mmodes : containers.MModes + Same object as input with masking applied + """ + mmodes.redistribute("freq") + + mw = mmodes.weight[:] + + # Exclude auto correlations if set + if not self.auto_correlations: + for pi, (fi, fj) in enumerate(mmodes.prodstack): + if fi == fj: + mw[..., pi] = 0.0 + + # Apply m based masks + if not self.m_zero: + mw[0] = 0.0 + + if not self.positive_m: + mw[1:, 0] = 0.0 + + if not self.negative_m: + mw[1:, 1] = 0.0 + + if self.mask_low_m: + mw[: self.mask_low_m] = 0.0 + + return mmodes
+
+ + + +
+[docs] +class MaskBaselines(task.SingleTask): + """Mask out baselines from a dataset. + + This task may produce output with shared datasets. Be warned that + this can produce unexpected outputs if not properly taken into + account. + + Attributes + ---------- + mask_long_ns : float, optional + Mask out baselines longer than a given distance in the N/S direction. + mask_short : float, optional + Mask out baselines shorter than a given distance. + mask_short_ew : float, optional + Mask out baselines shorter then a given distance in the East-West + direction. Useful for masking out intra-cylinder baselines for + North-South oriented cylindrical telescopes. + mask_short_ns : float, optional + Mask out baselines shorter then a given distance in the North-South + direction. + missing_threshold : float, optional + Mask any baseline that is missing more than this fraction of samples. This is + measured relative to other baselines. + zero_data : bool, optional + Zero the data in addition to modifying the noise weights + (default is False). + share : {"all", "none", "vis"} + Which datasets should we share with the input. If "none" we create a + full copy of the data, if "vis" we create a copy only of the modified + weight dataset and the unmodified vis dataset is shared, if "all" we + modify in place and return the input container. + """ + + mask_long_ns = config.Property(proptype=float, default=None) + mask_short = config.Property(proptype=float, default=None) + mask_short_ew = config.Property(proptype=float, default=None) + mask_short_ns = config.Property(proptype=float, default=None) + + weight_threshold = config.Property(proptype=float, default=None) + missing_threshold = config.Property(proptype=float, default=None) + + zero_data = config.Property(proptype=bool, default=False) + + share = config.enum(["none", "vis", "all"], default="all") + +
+[docs] + def setup(self, telescope): + """Set the telescope model. + + Parameters + ---------- + telescope : TransitTelescope + The telescope object to use + """ + self.telescope = io.get_telescope(telescope) + + if self.zero_data and self.share == "vis": + self.log.warn( + "Setting `zero_data = True` and `share = vis` doesn't make much sense." + )
+ + +
+[docs] + def process(self, ss): + """Apply the mask to data. + + Parameters + ---------- + ss : SiderealStream or TimeStream + Data to mask. Applied in place. + """ + from mpi4py import MPI + + ss.redistribute("freq") + + baselines = self.telescope.baselines + + # The masking array. True will *retain* a sample + mask = np.zeros_like(ss.weight[:], dtype=bool) + + if self.mask_long_ns is not None: + long_ns_mask = np.abs(baselines[:, 1]) > self.mask_long_ns + mask |= long_ns_mask[np.newaxis, :, np.newaxis] + + if self.mask_short is not None: + short_mask = np.sum(baselines**2, axis=1) ** 0.5 < self.mask_short + mask |= short_mask[np.newaxis, :, np.newaxis] + + if self.mask_short_ew is not None: + short_ew_mask = np.abs(baselines[:, 0]) < self.mask_short_ew + mask |= short_ew_mask[np.newaxis, :, np.newaxis] + + if self.mask_short_ns is not None: + short_ns_mask = np.abs(baselines[:, 1]) < self.mask_short_ns + mask |= short_ns_mask[np.newaxis, :, np.newaxis] + + if self.weight_threshold is not None: + # Get the sum of the weights over frequencies + weight_sum_local = ss.weight[:].local_array.sum(axis=0) + weight_sum_tot = np.zeros_like(weight_sum_local) + self.comm.Allreduce(weight_sum_local, weight_sum_tot, op=MPI.SUM) + + # Retain only baselines with average weights larger than the threshold + mask |= weight_sum_tot[np.newaxis, :, :] < self.weight_threshold * len( + ss.freq + ) + + if self.missing_threshold is not None: + # Get the total number of samples for each baseline accumulated onto each + # rank + nsamp_local = (ss.weight[:].local_array > 0).sum(axis=-1).sum(axis=0) + nsamp_tot = np.zeros_like(nsamp_local) + self.comm.Allreduce(nsamp_local, nsamp_tot, op=MPI.SUM) + + # Mask out baselines with more that `missing_threshold` samples missing + baseline_missing_ratio = 1 - nsamp_tot / nsamp_tot.max() + mask |= ( + baseline_missing_ratio[np.newaxis, :, np.newaxis] + > self.missing_threshold + ) + + if self.share == "all": + ssc = ss + elif self.share == "vis": + ssc = ss.copy(shared=("vis",)) + else: # self.share == "none" + ssc = ss.copy() + + # Apply the mask to the weight + np.multiply( + ssc.weight[:].local_array, 0.0, where=mask, out=ssc.weight[:].local_array + ) + + # Apply the mask to the data + if self.zero_data: + np.multiply( + ssc.vis[:].local_array, 0.0, where=mask, out=ssc.vis[:].local_array + ) + + return ssc
+
+ + + +
+[docs] +class FindBeamformedOutliers(task.SingleTask): + """Identify beamformed visibilities that deviate from our expectation for noise. + + Attributes + ---------- + nsigma : float + Beamformed visibilities whose magnitude is greater than nsigma times + the expected standard deviation of the noise, given by sqrt(1 / weight), + will be masked. + window : list of int + If provided, the outlier mask will be extended to cover neighboring pixels. + This list provides the number of pixels in each dimension that a single + outlier will mask. Only supported for RingMap containers, where the list + should be length 2 with [nra, nel], and FormedBeamHA containers, where the list + should be length 1 with [nha,]. + """ + + nsigma = config.Property(proptype=float, default=3.0) + window = config.Property(proptype=list, default=None) + +
+[docs] + def process(self, data): + """Create a mask that indicates outlier beamformed visibilities. + + Parameters + ---------- + data : FormedBeam, FormedBeamHA, or RingMap + Beamformed visibilities. + + Returns + ------- + out : FormedBeamMask, FormedBeamHAMask, or RingMapMask + Container with a boolean mask where True indicates + outlier beamformed visibilities. + """ + class_dict = { + containers.FormedBeam: ("beam", containers.FormedBeamMask), + containers.FormedBeamHA: ("beam", containers.FormedBeamHAMask), + containers.RingMap: ("map", containers.RingMapMask), + } + + dataset, out_cont = class_dict[data.__class__] + + # Redistribute data over frequency + data.redistribute("freq") + + # Make sure the weight dataset has the same + # number of dimensions as the visibility dataset. + axes1 = data[dataset].attrs["axis"] + axes2 = data.weight.attrs["axis"] + + bcast_slice = tuple(slice(None) if ax in axes2 else np.newaxis for ax in axes1) + axes_collapse = tuple(ii for ii, ax in enumerate(axes1) if ax not in axes2) + + # Calculate the expected standard deviation based on weights dataset + inv_sigma = np.sqrt(data.weight[:][bcast_slice].view(np.ndarray)) + + # Standardize the beamformed visibilities + ratio = np.abs(data[dataset][:].view(np.ndarray) * inv_sigma) + + # Mask outliers + mask = ratio > self.nsigma + + if axes_collapse: + mask = np.any(mask, axis=axes_collapse) + + # Apply a smoothing operation + if self.window is not None: + ndim_smooth = len(self.window) + ndim_iter = mask.ndim - ndim_smooth + shp = mask.shape[0:ndim_iter] + + msg = ", ".join( + [ + f"{axes2[ndim_iter + ww]} [{win}]" + for ww, win in enumerate(self.window) + ] + ) + self.log.info(f"Extending mask along: axis [num extended] = {msg}") + + kernel = np.ones(tuple(self.window), dtype=np.float32) + th = 0.5 / kernel.size + + # Loop over the dimensions that are not being convolved + # to prevent memory errors due to intermediate products + # created by scipy's convolve. + mask_extended = np.zeros_like(mask) + for ind in np.ndindex(*shp): + mask_extended[ind] = ( + scipy.signal.convolve( + mask[ind].astype(np.float32), + kernel, + mode="same", + method="auto", + ) + > th + ) + + mask = mask_extended + + # Save the mask to a separate container + out = out_cont( + axes_from=data, + attrs_from=data, + distributed=data.distributed, + comm=data.comm, + ) + out.redistribute("freq") + out.mask[:] = mask + + return out
+
+ + + +
+[docs] +class MaskBadGains(task.SingleTask): + """Get a mask of regions with bad gain. + + Assumes that bad gains are set to 1. + """ + + threshold = config.Property(proptype=float, default=1.0) + threshold_tol = config.Property(proptype=float, default=1e-5) + +
+[docs] + def process(self, data): + """Generate a time-freq mask. + + Parameters + ---------- + data : :class:`andata.Corrdata` or :class:`container.ContainerBase` with a `gain` dataset + Data containing the gains to be flagged. Must have a `gain` dataset. + + Returns + ------- + mask : RFIMask container + Time-freq mask + """ + # Ensure data is distributed in frequency + data.redistribute("freq") + + # Boolean mask where gains are bad across all baselines. + mask = np.all( + data.gain[:] <= self.threshold + self.threshold_tol, axis=1 + ).allgather() + + mask_cont = containers.RFIMask(axes_from=data) + mask_cont.mask[:] = mask + + return mask_cont
+
+ + + +
+[docs] +class MaskBeamformedOutliers(task.SingleTask): + """Mask beamformed visibilities that deviate from our expectation for noise. + + This is operating under the assumption that, after proper foreground filtering, + the beamformed visibilities should be consistent with noise. + """ + +
+[docs] + def process(self, data, mask): + """Mask outlier beamformed visibilities. + + Parameters + ---------- + data : FormedBeam, FormedBeamHA, or RingMap + Beamformed visibilities. + + mask : FormedBeamMask, FormedBeamHAMask, or RingMapMask + Container with a boolean mask where True indicates + a beamformed visibility that should be ignored. + + Returns + ------- + data : FormedBeam or RingMap + The input container with the weight dataset set to zero + for samples that were identified as outliers. + """ + # Redistribute data over frequency + data.redistribute("freq") + mask.redistribute("freq") + + # Multiply the weights by the inverse of the mask + flag = ~mask.mask[:].view(np.ndarray) + + data.weight[:] *= flag.astype(np.float32) + + return data
+
+ + + +
+[docs] +class MaskBeamformedWeights(task.SingleTask): + """Mask beamformed visibilities with anomalously large weights before stacking. + + Attributes + ---------- + nmed : float + Any weight that is more than `nmed` times the median weight + over all objects and frequencies will be set to zero. + Default is 8.0. + """ + + nmed = config.Property(proptype=float, default=8.0) + +
+[docs] + def process(self, data): + """Mask large weights. + + Parameters + ---------- + data : FormedBeam + Beamformed visibilities. + + Returns + ------- + data : FormedBeam + The input container with the weight dataset set to zero + if the weights exceed the threshold. + """ + from caput import mpiutil + + data.redistribute("object_id") + + npol = data.pol.size + med_weight = np.zeros(npol, dtype=np.float32) + + for pp in range(npol): + wlocal = data.weight[:, pp] + wglobal = np.zeros(wlocal.global_shape, dtype=wlocal.dtype) + + mpiutil.gather_local( + wglobal, wlocal, wlocal.local_offset, root=0, comm=data.comm + ) + + if data.comm.rank == 0: + med_weight[pp] = np.median(wglobal[wglobal > 0]) + self.log.info( + f"Median weight for Pol {data.pol[pp]}: {med_weight[pp]:0.2e}" + ) + + # Broadcast the median weight to all ranks + data.comm.Bcast(med_weight, root=0) + + w = data.weight[:].view(np.ndarray) + flag = w < (self.nmed * med_weight[np.newaxis, :, np.newaxis]) + + data.weight[:] *= flag.astype(np.float32) + + return data
+
+ + + +
+[docs] +class RadiometerWeight(task.SingleTask): + r"""Update vis_weight according to the radiometer equation. + + .. math:: + + \text{weight}_{ij} = N_\text{samp} / V_{ii} V_{jj} + + Attributes + ---------- + replace : bool, optional + Replace any existing weights (default). If `False` then we multiply the + existing weights by the radiometer values. + """ + + replace = config.Property(proptype=bool, default=True) + +
+[docs] + def process(self, stream): + """Change the vis weight. + + Parameters + ---------- + stream : SiderealStream or TimeStream + Data to be weighted. This is done in place. + + Returns + ------- + stream : SiderealStream or TimeStream + """ + from caput.time import STELLAR_S + + # Redistribute over the frequency direction + stream.redistribute("freq") + + ninput = len(stream.index_map["input"]) + nprod = len(stream.index_map["prod"]) + + if nprod != (ninput * (ninput + 1) // 2): + raise RuntimeError( + "Must have a input stream with the full correlation triangle." + ) + + freq_width = np.median(stream.index_map["freq"]["width"]) + + if isinstance(stream, containers.SiderealStream): + RA_S = 240 * STELLAR_S # SI seconds in 1 deg of RA change + int_time = np.median(np.abs(np.diff(stream.ra))) / RA_S + else: + int_time = np.median(np.abs(np.diff(stream.index_map["time"]))) + + if self.replace: + stream.weight[:] = 1.0 + + # Construct and set the correct weights in place + nsamp = 1e6 * freq_width * int_time + autos = tools.extract_diagonal(stream.vis[:]).real + weight_fac = nsamp**0.5 / autos + tools.apply_gain(stream.weight[:], weight_fac, out=stream.weight[:]) + + # Return timestream with updated weights + return stream
+
+ + + +
+[docs] +class SanitizeWeights(task.SingleTask): + """Flags weights outside of a valid range. + + Flags any weights above a max threshold and below a minimum threshold. + Baseline dependent, so only some baselines may be flagged. + + Attributes + ---------- + max_thresh : float + largest value to keep + min_thresh : float + smallest value to keep + """ + + max_thresh = config.Property(proptype=np.float32, default=1e30) + min_thresh = config.Property(proptype=np.float32, default=1e-30) + +
+[docs] + def setup(self): + """Validate the max and min values. + + Raises + ------ + ValueError + if min_thresh is larger than max_thresh + """ + if self.min_thresh >= self.max_thresh: + raise ValueError("Minimum threshold is larger than maximum threshold.")
+ + +
+[docs] + def process(self, data): + """Mask any weights outside of the threshold range. + + Parameters + ---------- + data : :class:`andata.CorrData` or :class:`containers.VisContainer` object + Data containing the weights to be flagged + + Returns + ------- + data : same object as data + Data object with high/low weights masked in-place + """ + # Ensure data is distributed in frequency + data.redistribute("freq") + + weight = data.weight[:].local_array + + weight[weight > self.max_thresh] = 0.0 + weight[weight < self.min_thresh] = 0.0 + + return data
+
+ + + +
+[docs] +class SmoothVisWeight(task.SingleTask): + """Smooth the visibility weights with a median filter. + + This is done in-place. + + Attributes + ---------- + kernel_size : int, optional + Size of the kernel for the median filter in time points. + Default is 31, corresponding to ~5 minutes window for 10s cadence data. + mask_zeros : bool, optional + Mask out zero-weight entries when taking the moving weighted median. + """ + + # 31 time points correspond to ~ 5min in 10s cadence + kernel_size = config.Property(proptype=int, default=31) + mask_zeros = config.Property(proptype=bool, default=False) + +
+[docs] + def process(self, data: containers.TimeStream) -> containers.TimeStream: + """Smooth the weights with a median filter. + + Parameters + ---------- + data + Data containing the weights to be smoothed + + Returns + ------- + data + Data object containing the same data as the input, but with the + weights substituted by the smoothed ones. + """ + # Ensure data is distributed in frequency, + # so a frequency loop will not be too large. + data.redistribute("freq") + + weight_local = data.weight[:].local_array + + for i in range(weight_local.shape[0]): + # Find values equal to zero to preserve them in final weights + zeromask = weight_local[i] == 0.0 + + # moving_weighted_median wants float64-type weights + if self.mask_zeros: + mask = (weight_local[i] > 0.0).astype(np.float64) + else: + mask = np.ones_like(weight_local[i], dtype=np.float64) + + weight_local[i] = weighted_median.moving_weighted_median( + data=weight_local[i], + weights=mask, + size=(1, self.kernel_size), + method="split", + ) + + # Ensure zero values are zero + weight_local[i][zeromask] = 0.0 + + return data
+
+ + + +
+[docs] +class ThresholdVisWeightFrequency(task.SingleTask): + """Create a mask to remove all weights below a per-frequency threshold. + + A single relative threshold is set for each frequency along with an absolute + minimum weight threshold. Masking is done relative to the mean baseline. + + Parameters + ---------- + absolute_threshold : float + Any weights with values less than this number will be set to zero. + relative_threshold : float + Any weights with values less than this number times the average weight + will be set to zero. + """ + + absolute_threshold = config.Property(proptype=float, default=1e-7) + relative_threshold = config.Property(proptype=float, default=0.9) + +
+[docs] + def process(self, stream): + """Make a baseline-independent mask. + + Parameters + ---------- + stream : `.core.container` with `weight` attribute + Container to mask + + Returns + ------- + out : RFIMask container + RFIMask container with mask set + """ + stream.redistribute("freq") + + # Make the output container depending on what 'stream' is + if "ra" in stream.axes: + mask_cont = containers.SiderealRFIMask(axes_from=stream, attrs_from=stream) + elif "time" in stream.axes: + mask_cont = containers.RFIMask(axes_from=stream, attrs_from=stream) + else: + raise TypeError(f"Require Timestream or SiderealStream. Got {type(stream)}") + + weight = stream.weight[:].local_array + # Average over the baselines (stack) axis + mean_baseline = np.mean(weight, axis=1, keepdims=True) + # Cut out any values below fixed threhold. Return a np.ndarray + threshold = np.where( + mean_baseline > self.absolute_threshold, mean_baseline, np.nan + ) + # Average across the time (ra) axis to get per-frequency thresholds, + # ignoring any nans. np.nanmean will give a warning if an entire band is + # nan, which we expect to happen in some cases. + with warnings.catch_warnings(): + warnings.filterwarnings(action="ignore", message="Mean of empty slice") + threshold = np.nanmean(threshold, axis=2, keepdims=True) + # Create a 2D baseline-independent mask. + mask = ~( + mean_baseline + > np.fmax(threshold * self.relative_threshold, self.absolute_threshold) + )[:, 0, :] + # Collect all parts of the mask. Method .allgather() returns a np.ndarray + mask = mpiarray.MPIArray.wrap(mask, axis=0).allgather() + # Log the percent of data masked + drop_frac = np.sum(mask) / np.prod(mask.shape) + self.log.info( + "%0.5f%% of data is below the weight threshold" % (100.0 * (drop_frac)) + ) + + mask_cont.mask[:] = mask + + return mask_cont
+
+ + + +
+[docs] +class ThresholdVisWeightBaseline(task.SingleTask): + """Form a mask corresponding to weights that are below some threshold. + + The threshold is determined as `maximum(absolute_threshold, + relative_threshold * average(weight))` and is evaluated per product/stack + entry. The user can specify whether to use a mean or median as the average, + but note that the mean is much more likely to be biased by anomalously + high- or low-weight samples (both of which are present in raw CHIME data). + The user can also specify that weights below some threshold should not be + considered when taking the average and constructing the mask (the default + is to only ignore zero-weight samples). + + The task outputs a BaselineMask or SiderealBaselineMask depending on the + input container. + + Parameters + ---------- + average_type : string, optional + Type of average to use ("median" or "mean"). Default: "median". + absolute_threshold : float, optional + Any weights with values less than this number will be set to zero. + Default: 1e-7. + relative_threshold : float, optional + Any weights with values less than this number times the average weight + will be set to zero. Default: 1e-6. + ignore_absolute_threshold : float, optional + Any weights with values less than this number will be ignored when + taking averages and constructing the mask. Default: 0.0. + pols_to_flag : string, optional + Which polarizations to flag. "copol" only flags XX and YY baselines, + while "all" flags everything. Default: "all". + """ + + average_type = config.enum(["median", "mean"], default="median") + absolute_threshold = config.Property(proptype=float, default=1e-7) + relative_threshold = config.Property(proptype=float, default=1e-6) + ignore_absolute_threshold = config.Property(proptype=float, default=0.0) + pols_to_flag = config.enum(["all", "copol"], default="all") + +
+[docs] + def setup(self, telescope): + """Set the telescope model. + + Parameters + ---------- + telescope : TransitTelescope + The telescope object to use + """ + self.telescope = io.get_telescope(telescope)
+ + +
+[docs] + def process( + self, + stream, + ) -> Union[containers.BaselineMask, containers.SiderealBaselineMask]: + """Construct baseline-dependent mask. + + Parameters + ---------- + stream : `.core.container` with `weight` attribute + Input container whose weights are used to construct the mask. + + Returns + ------- + out : `BaselineMask` or `SiderealBaselineMask` + The output baseline-dependent mask. + """ + from mpi4py import MPI + + # Only redistribute the weight dataset, because CorrData containers will have + # other parallel datasets without a stack axis + stream.weight.redistribute(axis=1) + + # Make the output container, depending on input type + if "ra" in stream.axes: + mask_cont = containers.SiderealBaselineMask( + axes_from=stream, attrs_from=stream + ) + elif "time" in stream.axes: + mask_cont = containers.BaselineMask(axes_from=stream, attrs_from=stream) + else: + raise TypeError( + f"Task requires TimeStream, SiderealStream, or CorrData. Got {type(stream)}" + ) + + # Redistribute output container along stack axis + mask_cont.redistribute("stack") + + # Get local section of weights + local_weight = stream.weight[:].local_array + + # For each baseline (axis=1), take average over non-ignored time/freq samples + average_func = np.ma.median if self.average_type == "median" else np.ma.mean + average_weight = average_func( + np.ma.array( + local_weight, mask=(local_weight <= self.ignore_absolute_threshold) + ), + axis=(0, 2), + ).data + + # Figure out which entries to keep + threshold = np.maximum( + self.absolute_threshold, self.relative_threshold * average_weight + ) + + # Compute the mask, excluding samples that we want to ignore + local_mask = (local_weight < threshold[np.newaxis, :, np.newaxis]) & ( + local_weight > self.ignore_absolute_threshold + ) + + # If only flagging co-pol baselines, make separate mask to select those, + # and multiply into low-weight mask + if self.pols_to_flag == "copol": + # Get local section of stack axis + local_stack = stream.stack[stream.weight[:].local_bounds] + + # Get product map from input stream + prod = stream.prod[:] + + # Get polarisation of each input for each element of local stack axis + pol_a = self.telescope.polarisation[prod[local_stack["prod"]]["input_a"]] + pol_b = self.telescope.polarisation[prod[local_stack["prod"]]["input_b"]] + + # Make mask to select co-pol baselines + local_pol_mask = (pol_a == pol_b)[np.newaxis, :, np.newaxis] + + # Apply pol mask to low-weight mask + local_mask *= local_pol_mask + + # Compute the fraction of data that will be masked + local_mask_sum = np.sum(local_mask) + global_mask_total = np.zeros_like(local_mask_sum) + stream.comm.Allreduce(local_mask_sum, global_mask_total, op=MPI.SUM) + mask_frac = global_mask_total / float(np.prod(stream.weight.global_shape)) + + self.log.info( + "%0.5f%% of data is below the weight threshold" % (100.0 * mask_frac) + ) + + # Save mask to output container + mask_cont.mask[:] = mpiarray.MPIArray.wrap(local_mask, axis=1) + + # Distribute back across frequency + mask_cont.redistribute("freq") + stream.redistribute("freq") + + return mask_cont
+
+ + + +
+[docs] +class CollapseBaselineMask(task.SingleTask): + """Collapse a baseline-dependent mask along the baseline axis. + + The output is a frequency/time mask that is True for any freq/time sample + for which any baseline is masked in the input mask. + """ + +
+[docs] + def process( + self, + baseline_mask: Union[containers.BaselineMask, containers.SiderealBaselineMask], + ) -> Union[containers.RFIMask, containers.SiderealRFIMask]: + """Collapse input mask over baseline axis. + + Parameters + ---------- + baseline_mask : `BaselineMask` or `SiderealBaselineMask` + Input baseline-dependent mask + + Returns + ------- + mask_cont : `RFIMask` or `SiderealRFIMask` + Output baseline-independent mask. + """ + # Redistribute input mask along freq axis + baseline_mask.redistribute("freq") + + # Make container for output mask. Remember that this will not be distributed. + if isinstance(baseline_mask, containers.BaselineMask): + mask_cont = containers.RFIMask( + axes_from=baseline_mask, attrs_from=baseline_mask + ) + elif isinstance(baseline_mask, containers.SiderealBaselineMask): + mask_cont = containers.SiderealRFIMask( + axes_from=baseline_mask, attrs_from=baseline_mask + ) + + # Get local section of baseline-dependent mask + local_mask = baseline_mask.mask[:].local_array + + # Collapse along stack axis + local_mask = np.any(local_mask, axis=1) + + # Gather full mask on each rank + full_mask = mpiarray.MPIArray.wrap(local_mask, axis=0).allgather() + + # Log the percent of freq/time samples masked + drop_frac = np.sum(full_mask) / np.prod(full_mask.shape) + self.log.info( + f"After baseline collapse: {100.0 * drop_frac:.1f}%% of data" + " is below the weight threshold" + ) + + mask_cont.mask[:] = full_mask + + return mask_cont
+
+ + + +
+[docs] +class RFISensitivityMask(task.SingleTask): + """Slightly less crappy RFI masking. + + Attributes + ---------- + mask_type : string, optional + One of 'mad', 'sumthreshold' or 'combine'. + Default is combine, which uses the sumthreshold everywhere + except around the transits of the Sun, CasA and CygA where it + applies the MAD mask to avoid masking out the transits. + include_pol : list of strings, optional + The list of polarisations to include. Default is to use all + polarisations. + remove_median : bool, optional + Remove median accross times for each frequency? + Recommended. Default: True. + sir : bool, optional + Apply scale invariant rank (SIR) operator on top of final mask? + We find that this is advisable while we still haven't flagged + out all the static bands properly. Default: True. + sigma : float, optional + The false positive rate of the flagger given as sigma value assuming + the non-RFI samples are Gaussian. + Used for the MAD and TV station flaggers. + max_m : int, optional + Maximum size of the SumThreshold window to use. + The default (8) seems to work well with sensitivity data. + start_threshold_sigma : float, optional + The desired threshold for the SumThreshold algorithm at the + final window size (determined by max m) given as a + number of standard deviations (to be estimated from the + sensitivity map excluding weight and static masks). + The default (8) seems to work well with sensitivity data + using the default max_m. + tv_fraction : float, optional + Number of bad samples in a digital TV channel that cause the whole + channel to be flagged. + tv_base_size : [int, int] + The size of the region used to estimate the baseline for the TV channel + detection. + tv_mad_size : [int, int] + The size of the region used to estimate the MAD for the TV channel detection. + """ + + mask_type = config.enum(["mad", "sumthreshold", "combine"], default="combine") + include_pol = config.list_type(str, default=None) + remove_median = config.Property(proptype=bool, default=True) + sir = config.Property(proptype=bool, default=True) + + sigma = config.Property(proptype=float, default=5.0) + max_m = config.Property(proptype=int, default=8) + start_threshold_sigma = config.Property(proptype=float, default=8) + + tv_fraction = config.Property(proptype=float, default=0.5) + tv_base_size = config.list_type(int, length=2, default=(11, 3)) + tv_mad_size = config.list_type(int, length=2, default=(201, 51)) + +
+[docs] + def process(self, sensitivity): + """Derive an RFI mask from sensitivity data. + + Parameters + ---------- + sensitivity : containers.SystemSensitivity + Sensitivity data to derive the RFI mask from. + + Returns + ------- + rfimask : containers.RFIMask + RFI mask derived from sensitivity. + """ + ## Constants + # Convert MAD to RMS + MAD_TO_RMS = 1.4826 + + # The difference between the exponents in the usual + # scaling of the RMS (n**0.5) and the scaling used + # in the sumthreshold algorithm (n**log2(1.5)) + RMS_SCALING_DIFF = np.log2(1.5) - 0.5 + + # Distribute over polarisation as we need all times and frequencies + # available simultaneously + sensitivity.redistribute("pol") + + # Divide sensitivity to get a radiometer test + radiometer = sensitivity.measured[:] * tools.invert_no_zero( + sensitivity.radiometer[:] + ) + radiometer = mpiarray.MPIArray.wrap(radiometer, axis=1) + + freq = sensitivity.freq + npol = len(sensitivity.pol) + nfreq = len(freq) + + static_flag = ~self._static_rfi_mask_hook(freq, sensitivity.time[0]) + + madmask = mpiarray.MPIArray( + (npol, nfreq, len(sensitivity.time)), axis=0, dtype=bool + ) + madmask[:] = False + stmask = mpiarray.MPIArray( + (npol, nfreq, len(sensitivity.time)), axis=0, dtype=bool + ) + stmask[:] = False + + for li, ii in madmask.enumerate(axis=0): + # Only process this polarisation if we should be including it, + # otherwise skip and let it be implicitly set to False (i.e. not + # masked) + if self.include_pol and sensitivity.pol[ii] not in self.include_pol: + continue + + # Initial flag on weights equal to zero. + origflag = sensitivity.weight[:, ii] == 0.0 + + # Remove median at each frequency, if asked. + if self.remove_median: + for ff in range(nfreq): + radiometer[ff, li] -= np.median( + radiometer[ff, li][~origflag[ff]].view(np.ndarray) + ) + + # Combine weights with static flag + start_flag = origflag | static_flag[:, None] + + # Obtain MAD and TV masks + this_madmask, tvmask = self._mad_tv_mask( + radiometer[:, li], start_flag, freq + ) + + # combine MAD and TV masks + madmask[li] = this_madmask | tvmask + + # Add TV channels to ST start flag. + start_flag = start_flag | tvmask + + # Determine initial threshold + med = np.median(radiometer[:, li][~start_flag].view(np.ndarray)) + mad = np.median(abs(radiometer[:, li][~start_flag].view(np.ndarray) - med)) + threshold1 = ( + mad + * MAD_TO_RMS + * self.start_threshold_sigma + * self.max_m**RMS_SCALING_DIFF + ) + + # SumThreshold mask + stmask[li] = rfi.sumthreshold( + radiometer[:, li], + self.max_m, + start_flag=start_flag, + threshold1=threshold1, + correct_for_missing=True, + ) + + # Perform an OR (.any) along the pol axis and reform into an MPIArray + # along the freq axis + madmask = mpiarray.MPIArray.wrap(madmask.redistribute(1).any(0), 0) + stmask = mpiarray.MPIArray.wrap(stmask.redistribute(1).any(0), 0) + + # Pick which of the MAD or SumThreshold mask to use (or blend them) + if self.mask_type == "mad": + finalmask = madmask + + elif self.mask_type == "sumthreshold": + finalmask = stmask + + else: + # Combine ST and MAD masks + madtimes = self._combine_st_mad_hook(sensitivity.time) + finalmask = stmask + finalmask[:, madtimes] = madmask[:, madtimes] + + # Collect all parts of the mask onto rank 1 and then broadcast to all ranks + finalmask = mpiarray.MPIArray.wrap(finalmask, 0).allgather() + + # Apply scale invariant rank (SIR) operator, if asked for. + if self.sir: + finalmask = self._apply_sir(finalmask, static_flag) + + # Create container to hold mask + rfimask = containers.RFIMask(axes_from=sensitivity) + rfimask.mask[:] = finalmask + + return rfimask
+ + + def _combine_st_mad_hook(self, times): + """Override to add a custom blending mask between the SumThreshold and MAD flagged data. + + This is useful to use the MAD algorithm around bright source + transits, where the SumThreshold begins to remove real signal. + + Parameters + ---------- + times : np.ndarray[ntime] + Times of the data at floating point UNIX time. + + Returns + ------- + combine : np.ndarray[ntime] + Mixing array as a function of time. If `True` that sample will be + filled from the MAD, if `False` use the SumThreshold algorithm. + """ + return np.ones_like(times, dtype=bool) + + def _static_rfi_mask_hook(self, freq, timestamp=None): + """Override this function to apply a static RFI mask to the data. + + Parameters + ---------- + freq : np.ndarray[nfreq] + 1D array of frequencies in the data (in MHz). + timestamp : float or np.ndarray[ntimes] + timestamps to use when determining the static mask for this datatset + + Returns + ------- + mask : np.ndarray[nfreq] + Mask array. True will include a frequency channel, False masks it out. + """ + return np.ones_like(freq, dtype=bool) + + def _apply_sir(self, mask, baseflag, eta=0.2): + """Expand the mask with SIR.""" + # Remove baseflag from mask and run SIR + nobaseflag = np.copy(mask) + nobaseflag[baseflag] = False + nobaseflagsir = rfi.sir(nobaseflag[:, np.newaxis, :], eta=eta)[:, 0, :] + + # Make sure the original mask (including baseflag) is still masked + return nobaseflagsir | mask + + def _mad_tv_mask(self, data, start_flag, freq): + """Use the specific scattered TV channel flagging.""" + # Make copy of data + data = np.copy(data) + + # Calculate the scaled deviations + data[start_flag] = 0.0 + maddev = mad( + data, start_flag, base_size=self.tv_base_size, mad_size=self.tv_mad_size + ) + + # Replace any NaNs (where too much data is missing) with a + # large enough value to always be flagged + maddev = np.where(np.isnan(maddev), 2 * self.sigma, maddev) + + # Reflag for scattered TV emission + tvmask = tv_channels_flag(maddev, freq, sigma=self.sigma, f=self.tv_fraction) + + # Create MAD mask + madmask = maddev > self.sigma + + # Ensure start flag is masked + madmask = madmask | start_flag + + return madmask, tvmask
+ + + +
+[docs] +class RFIMask(task.SingleTask): + """Crappy RFI masking. + + Attributes + ---------- + sigma : float, optional + The false positive rate of the flagger given as sigma value assuming + the non-RFI samples are Gaussian. + tv_fraction : float, optional + Number of bad samples in a digital TV channel that cause the whole + channel to be flagged. + stack_ind : int + Which stack to process to derive flags for the whole dataset. + """ + + sigma = config.Property(proptype=float, default=5.0) + tv_fraction = config.Property(proptype=float, default=0.5) + stack_ind = config.Property(proptype=int) + + @overload + def process( + self, sstream: containers.SiderealStream + ) -> containers.SiderealRFIMask: ... + + @overload + def process(self, sstream: containers.TimeStream) -> containers.RFIMask: ... + +
+[docs] + def process( + self, sstream: Union[containers.TimeStream, containers.SiderealStream] + ) -> Union[containers.RFIMask, containers.SiderealRFIMask]: + """Apply a day time mask. + + Parameters + ---------- + sstream + Unmasked sidereal or time stream visibility data. + + Returns + ------- + mask + The derived RFI mask. + """ + # Select the correct mask type depending on if we have sidereal data or not + output_type = ( + containers.SiderealRFIMask + if "ra" in sstream.index_map + else containers.RFIMask + ) + + sstream.redistribute(["stack", "prod"]) + + ssv = sstream.vis[:] + ssw = sstream.weight[:] + + # Figure out which rank actually has the requested index + lstart = ssv.local_offset[1] + lstop = lstart + ssv.local_shape[1] + has_ind = (self.stack_ind >= lstart) and (self.stack_ind < lstop) + has_ind_list = sstream.comm.allgather(has_ind) + rank_with_ind = has_ind_list.index(True) + self.log.debug( + "Rank %i has the requested index %i", rank_with_ind, self.stack_ind + ) + + mask_cont = output_type(copy_from=sstream) + mask = mask_cont.mask[:] + + # Get the rank with stack to create the new mask + if sstream.comm.rank == rank_with_ind: + # Cut out the right section + wf = ssv.local_array[:, self.stack_ind - lstart] + ww = ssw.local_array[:, self.stack_ind - lstart] + + # Generate an initial mask and calculate the scaled deviations + # TODO: replace this magic threshold + weight_cut = 1e-4 * ww.mean() # Ignore samples with small weights + wm = ww < weight_cut + maddev = mad(wf, wm) + + # Replace any NaNs (where too much data is missing) with a large enough + # value to always be flagged + maddev = np.where(np.isnan(maddev), 2 * self.sigma, maddev) + + # Reflag for scattered TV emission + tvmask = tv_channels_flag( + maddev, sstream.freq, sigma=self.sigma, f=self.tv_fraction + ) + + # Construct the new mask + mask[:] = tvmask | (maddev > self.sigma) + + # Broadcast the new flags to all ranks and then apply + sstream.comm.Bcast(mask, root=rank_with_ind) + + self.log.info( + "Flagging %0.2f%% of data due to RFI." + % (100.0 * np.sum(mask) / float(mask.size)) + ) + + return mask_cont
+
+ + + +
+[docs] +class ApplyTimeFreqMask(task.SingleTask): + """Apply a time-frequency mask to the data. + + Typically this is used to ask out all inputs at times and + frequencies contaminated by RFI. + + This task may produce output with shared datasets. Be warned that + this can produce unexpected outputs if not properly taken into + account. + + Attributes + ---------- + share : {"all", "none", "vis", "map"} + Which datasets should we share with the input. If "none" we create a + full copy of the data, if "vis" or "map" we create a copy only of the modified + weight dataset and the unmodified vis dataset is shared, if "all" we + modify in place and return the input container. + """ + + share = config.enum(["none", "vis", "map", "all"], default="all") + +
+[docs] + def process(self, tstream, rfimask): + """Apply the mask by zeroing the weights. + + Parameters + ---------- + tstream : timestream or sidereal stream + A timestream or sidereal stream like container. For example, + `containers.TimeStream`, `andata.CorrData` or + `containers.SiderealStream`. + rfimask : containers.RFIMask + An RFI mask for the same period of time. + + Returns + ------- + tstream : timestream or sidereal stream + The masked timestream. Note that the masking is done in place. + """ + if isinstance(rfimask, containers.RFIMask): + if not hasattr(tstream, "time"): + raise TypeError( + f"Expected a timestream like type. Got {type(tstream)}." + ) + # Validate the time axes match + if not np.array_equal(tstream.time, rfimask.time): + raise ValueError("timestream and mask data have different time axes.") + + elif isinstance(rfimask, containers.SiderealRFIMask): + if not hasattr(tstream, "ra"): + raise TypeError( + f"Expected a sidereal stream like type. Got {type(tstream)}." + ) + # Validate the RA axes match + if not np.array_equal(tstream.ra, rfimask.ra): + raise ValueError("timestream and mask data have different RA axes.") + + else: + raise TypeError( + f"Require a RFIMask or SiderealRFIMask. Got {type(rfimask)}." + ) + + # Validate the frequency axis + if not np.array_equal(tstream.freq, rfimask.freq): + raise ValueError("timestream and mask data have different freq axes.") + + # Ensure we are frequency distributed + tstream.redistribute("freq") + + # Create a slice that broadcasts the mask to the final shape + t_axes = tstream.weight.attrs["axis"] + m_axes = rfimask.mask.attrs["axis"] + bcast_slice = tuple( + slice(None) if ax in m_axes else np.newaxis for ax in t_axes + ) + + # RFI Mask is not distributed, so we need to cut out the frequencies + # that are local for the tstream + ax = list(t_axes).index("freq") + sf = tstream.weight.local_offset[ax] + ef = sf + tstream.weight.local_shape[ax] + + if self.share == "all": + tsc = tstream + elif self.share == "vis": + tsc = tstream.copy(shared=("vis",)) + elif self.share == "map": + tsc = tstream.copy(shared=("map",)) + else: # self.share == "none" + tsc = tstream.copy() + + # Mask the data. + tsc.weight[:].local_array[:] *= (~rfimask.mask[sf:ef][bcast_slice]).astype( + np.float32 + ) + + return tsc
+
+ + + +
+[docs] +class ApplyBaselineMask(task.SingleTask): + """Apply a distributed mask that varies across baselines. + + No broadcasting is done, so the data and mask should have the same + axes. This shouldn't be used for non-distributed time-freq masks. + + This task may produce output with shared datasets. Be warned that + this can produce unexpected outputs if not properly taken into + account. + + Attributes + ---------- + share : {"all", "none", "vis", "map"} + Which datasets should we share with the input. If "none" we create a + full copy of the data, if "vis" or "map" we create a copy only of the modified + weight dataset and the unmodified vis dataset is shared, if "all" we + modify in place and return the input container. + """ + + share = config.enum(["none", "vis", "map", "all"], default="all") + + @overload + def process( + self, data: containers.TimeStream, mask: containers.BaselineMask + ) -> containers.TimeStream: ... + + @overload + def process( + self, data: containers.SiderealStream, mask: containers.SiderealBaselineMask + ) -> containers.SiderealStream: ... + +
+[docs] + def process(self, data, mask): + """Flag data by zeroing the weights. + + Parameters + ---------- + data + Data to apply mask to. Must have a stack axis + mask + A baseline-dependent mask + + Returns + ------- + data + The masked data. Masking is done in place. + """ + if isinstance(mask, containers.BaselineMask): + if not hasattr(data, "time"): + raise TypeError(f"Expected a timestream-like type. Got {type(data)}.") + + if not (data.time.shape == mask.time.shape) and np.allclose( + data.time, mask.time + ): + raise ValueError("timestream and mask have different time axes.") + + elif isinstance(mask, containers.SiderealBaselineMask): + if not hasattr(data, "ra"): + raise TypeError( + f"Expected a sidereal stream like type. Got {type(data)}." + ) + + if not (data.ra.shape == mask.ra.shape) and np.allclose(data.ra, mask.ra): + raise ValueError("sidereal stream and mask have different RA axes.") + + else: + raise TypeError( + f"Require a BaselineMask or SiderealBaselineMask. Got {type(mask)}." + ) + + # Validate remaining axes + if not np.array_equal(data.stack, mask.stack): + raise ValueError("data and mask have different baseline axes.") + + if not (data.freq.shape == mask.freq.shape) and np.allclose( + data.freq, mask.freq + ): + raise ValueError("data and mask have different freq axes.") + + if self.share == "all": + tsc = data + elif self.share == "vis": + tsc = data.copy(shared=("vis",)) + elif self.share == "map": + tsc = data.copy(shared=("map",)) + else: + tsc = data.copy() + + tsc.weight[:] *= (~mask.mask[:]).astype(np.float32) + + return tsc
+
+ + + +
+[docs] +class MaskFreq(task.SingleTask): + """Make a mask for certain frequencies. + + Attributes + ---------- + bad_freq_ind : list, optional + A list containing frequencies to flag out. Each entry can either be an + integer giving an individual frequency index to remove, or 2-tuples giving + start and end indices of a range to flag (as with a standard slice, the end + is *not* included.) + factorize : bool, optional + Find the smallest factorizable mask of the time-frequency axis that covers all + samples already flagged in the data. + all_time : bool, optional + Only include frequencies where all time samples are present. + mask_missing_data : bool, optional + Mask time-freq samples where some baselines (for visibily data) or + polarisations/elevations (for ring map data) are missing. + """ + + bad_freq_ind = config.Property(proptype=list, default=None) + factorize = config.Property(proptype=bool, default=False) + all_time = config.Property(proptype=bool, default=False) + mask_missing_data = config.Property(proptype=bool, default=False) + +
+[docs] + def process( + self, data: Union[containers.VisContainer, containers.RingMap] + ) -> Union[containers.RFIMask, containers.SiderealRFIMask]: + """Make the mask. + + Parameters + ---------- + data + The data to mask. + + Returns + ------- + mask_cont + Frequency mask container + """ + data.redistribute("freq") + + maskcls = ( + containers.SiderealRFIMask + if isinstance(data, containers.SiderealContainer) + else containers.RFIMask + ) + maskcont = maskcls(axes_from=data, attrs_from=data) + mask = maskcont.mask[:] + + # Get the total number of amount of data for each freq-time. This is used to + # create an initial mask. For visibilities find the number of baselines + # present... + if isinstance(data, containers.VisContainer): + present_data = mpiarray.MPIArray.wrap( + (data.weight[:] > 0).sum(axis=1), comm=data.weight.comm, axis=0 + ) + # ... for ringmaps find the number of polarisations/elevations present + elif isinstance(data, containers.RingMap): + present_data = mpiarray.MPIArray.wrap( + (data.weight[:] > 0).sum(axis=3).sum(axis=0), + comm=data.weight.comm, + axis=0, + ) + else: + raise ValueError( + f"Received data of type {data._class__}. " + "Only visibility type data and ringmaps are supported." + ) + + all_present_data = present_data.allgather() + mask[:] = all_present_data == 0 + + self.log.info(f"Input data: {100.0 * mask.mean():.2f}% flagged.") + + # Create an initial mask of the freq-time space, where bad samples are + # True. If `mask_missing_data` is set this masks any sample where the amount + # of present data is less than the maximum, otherwise it is where all + # data is missing + if self.mask_missing_data: + mask = all_present_data < all_present_data.max() + self.log.info( + f"Requiring all baselines: {100.0 * mask.mean():.2f}% flagged." + ) + + if self.bad_freq_ind is not None: + nfreq = len(data.freq) + mask |= self._bad_freq_mask(nfreq)[:, np.newaxis] + self.log.info(f"Frequency mask: {100.0 * mask.mean():.2f}% flagged.") + + if self.all_time: + mask |= mask.any(axis=1)[:, np.newaxis] + self.log.info(f"All time mask: {100.0 * mask.mean():.2f}% flagged.") + elif self.factorize: + mask[:] = self._optimal_mask(mask) + self.log.info(f"Factorizable mask: {100.0 * mask.mean():.2f}% flagged.") + + return maskcont
+ + + def _bad_freq_mask(self, nfreq: int) -> np.ndarray: + # Parse the bad frequency list to create a per frequency mask + + mask = np.zeros(nfreq, dtype=bool) + + for s in self.bad_freq_ind: + if isinstance(s, int): + if s < nfreq: + mask[s] = True + elif isinstance(s, (tuple, list)) and len(s) == 2: + mask[s[0] : s[1]] = True + else: + raise ValueError( + "Elements of `bad_freq_ind` must be integers or 2-tuples. " + f"Got {type(s)}." + ) + + return mask + + def _optimal_mask(self, mask: np.ndarray) -> np.ndarray: + # From the freq-time input mask, create the smallest factorizable mask that + # covers all the original masked samples + + from scipy.optimize import minimize_scalar + + def genmask(f): + # Calculate a factorisable mask given the time masking threshold f + time_mask = mask.mean(axis=0) > f + freq_mask = mask[:, ~time_mask].any(axis=1) + return time_mask[np.newaxis, :] | freq_mask[:, np.newaxis] + + def fmask(f): + # Calculate the total area masked given f + m = genmask(f).mean() + self.log.info(f"Current value: {m}") + return m + + # Solve to find a value of f that minimises the amount of data masked + res = minimize_scalar( + fmask, method="golden", options={"maxiter": 20, "xtol": 1e-2} + ) + + if not res.success: + self.log.info("Optimisation did not converge, but this isn't unexpected.") + + return genmask(res.x)
+ + + +
+[docs] +class BlendStack(task.SingleTask): + """Mix a small amount of a stack into data to regularise RFI gaps. + + This is designed to mix in a small amount of a stack into a day of data (which + will have RFI masked gaps) to attempt to regularise operations which struggle to + deal with time variable masks, e.g. `DelaySpectrumEstimator`. + + Attributes + ---------- + frac : float, optional + The relative weight to give the stack in the average. This multiplies the + weights already in the stack, and so it should be remembered that these may + already be significantly higher than the single day weights. + match_median : bool, optional + Estimate the median in the time/RA direction from the common samples and use + this to match any quasi time-independent bias of the data (e.g. cross talk). + subtract : bool, optional + Rather than taking an average, instead subtract out the blending stack + from the input data in the common samples to calculate the difference + between them. The interpretation of `frac` is a scaling of the inverse + variance of the stack to an inverse variance of a prior on the + difference, e.g. a `frac = 1e-4` means that we expect the standard + deviation of the difference between the data and the stacked data to be + 100x larger than the noise of the stacked data. + """ + + frac = config.Property(proptype=float, default=1e-4) + match_median = config.Property(proptype=bool, default=True) + subtract = config.Property(proptype=bool, default=False) + +
+[docs] + def setup(self, data_stack): + """Set the stacked data. + + Parameters + ---------- + data_stack : VisContainer + Data stack to blend + """ + self.data_stack = data_stack
+ + +
+[docs] + def process(self, data): + """Blend a small amount of the stack into the incoming data. + + Parameters + ---------- + data : VisContainer + The data to be blended into. This is modified in place. + + Returns + ------- + data_blend : VisContainer + The modified data. This is the same object as the input, and it has been + modified in place. + """ + if type(self.data_stack) != type(data): + raise TypeError( + f"type(data) (={type(data)}) must match" + f"type(data_stack) (={type(self.type)}" + ) + + # Try and get both the stack and the incoming data to have the same + # distribution + self.data_stack.redistribute(["freq", "time", "ra"]) + data.redistribute(["freq", "time", "ra"]) + + if isinstance(data, containers.SiderealStream): + dset_stack = self.data_stack.vis[:] + dset = data.vis[:] + else: + raise TypeError( + "Only SiderealStream's are currently supported. " + f"Got type(data) = {type(data)}" + ) + + if dset_stack.shape != dset.shape: + raise ValueError( + f"Size of data ({dset.shape}) must match " + f"data_stack ({dset_stack.shape})" + ) + + weight_stack = self.data_stack.weight[:] + weight = data.weight[:] + + # Find the median offset between the stack and the daily data + if self.match_median: + # Find the parts of the both the stack and the daily data that are both + # measured + mask = ( + ((weight[:] > 0) & (weight_stack[:] > 0)) + .astype(np.float32) + .view(np.ndarray) + ) + + # ... get the median of the stack in this common subset + stack_med_real = weighted_median.weighted_median( + dset_stack.real.view(np.ndarray).copy(), mask + ) + stack_med_imag = weighted_median.weighted_median( + dset_stack.imag.view(np.ndarray).copy(), mask + ) + + # ... get the median of the data in the common subset + data_med_real = weighted_median.weighted_median( + dset.real.view(np.ndarray).copy(), mask + ) + data_med_imag = weighted_median.weighted_median( + dset.imag.view(np.ndarray).copy(), mask + ) + + # ... construct an offset to match the medians in the time/RA direction + stack_offset = ( + (data_med_real - stack_med_real) + + 1.0j * (data_med_imag - stack_med_imag) + )[..., np.newaxis] + stack_offset = mpiarray.MPIArray.wrap( + stack_offset, + axis=dset_stack.axis, + comm=dset_stack.comm, + ) + + else: + stack_offset = 0 + + if self.subtract: + # Subtract the base stack where data is present, otherwise give zeros + + dset -= dset_stack + stack_offset + dset *= (weight > 0).astype(np.float32) + + # This sets the weights where weight == 0 to frac * weight_stack, + # otherwise weight is the sum of the variances. It's a bit obscure + # because it attempts to do the operations in place rather than + # building many temporaries + weight *= tools.invert_no_zero(weight + weight_stack) + weight += (weight == 0) * self.frac + weight *= weight_stack + + else: + # Perform a weighted average of the data to fill in missing samples + dset *= weight + dset += weight_stack * self.frac * (dset_stack + stack_offset) + weight += weight_stack * self.frac + + dset *= tools.invert_no_zero(weight) + + return data
+
+ + + +# This is here for compatibility +ApplyRFIMask = ApplyTimeFreqMask +MaskData = MaskMModeData + + +
+[docs] +def medfilt(x, mask, size, *args): + """Apply a moving median filter to masked data. + + The application is done by iterative filling to + overcome the fact we don't have an actual implementation + of a nanmedian. + + Parameters + ---------- + x : np.ndarray + Data to filter. + mask : np.ndarray + Mask of data to filter out. + size : tuple + Size of the window in each dimension. + args : optional + Additional arguments to pass to the moving weighted median + + Returns + ------- + y : np.ndarray + The masked data. Data within the mask is undefined. + """ + if np.iscomplexobj(x): + return medfilt(x.real, mask, size) + 1.0j * medfilt(x.imag, mask, size) + + # Copy and do initial masking + x = np.ascontiguousarray(x.astype(np.float64)) + w = np.ascontiguousarray((~mask).astype(np.float64)) + + return weighted_median.moving_weighted_median(x, w, size, *args)
+ + + +
+[docs] +def mad(x, mask, base_size=(11, 3), mad_size=(21, 21), debug=False, sigma=True): + """Calculate the MAD of freq-time data. + + Parameters + ---------- + x : np.ndarray + Data to filter. + mask : np.ndarray + Initial mask. + base_size : tuple + Size of the window to use in (freq, time) when + estimating the baseline. + mad_size : tuple + Size of the window to use in (freq, time) when + estimating the MAD. + debug : bool, optional + If True, return deviation and mad arrays as well + sigma : bool, optional + Rescale the output into units of Gaussian sigmas. + + Returns + ------- + mad : np.ndarray + Size of deviation at each point in MAD units. This output may contain + NaN's for regions of missing data. + """ + xs = medfilt(x, mask, size=base_size) + dev = np.abs(x - xs) + + mad = medfilt(dev, mask, size=mad_size) + + if sigma: + mad *= 1.4826 # apply the conversion from MAD->sigma + + # Suppress warnings about NaNs produced during the division + with np.errstate(divide="ignore", invalid="ignore"): + r = dev / mad + + if debug: + return r, dev, mad + return r
+ + + +
+[docs] +def inverse_binom_cdf_prob(k, N, F): + """Calculate the trial probability that gives the CDF. + + This gets the trial probability that gives an overall cumulative + probability for Pr(X <= k; N, p) = F + + Parameters + ---------- + k : int + Maximum number of successes. + N : int + Total number of trials. + F : float + The cumulative probability for (k, N). + + Returns + ------- + p : float + The trial probability. + """ + # This uses the result that we can write the cumulative probability of a + # binomial in terms of an incomplete beta function + + import scipy.special as sp + + return sp.betaincinv(k + 1, N - k, 1 - F)
+ + + +
+[docs] +def sigma_to_p(sigma): + """Get the probability of an excursion larger than sigma for a Gaussian.""" + import scipy.stats as ss + + return 2 * ss.norm.sf(sigma)
+ + + +
+[docs] +def p_to_sigma(p): + """Get the sigma exceeded by the tails of a Gaussian with probability p.""" + import scipy.stats as ss + + return ss.norm.isf(p / 2)
+ + + +
+[docs] +def tv_channels_flag(x, freq, sigma=5, f=0.5, debug=False): + """Perform a higher sensitivity flagging for the TV stations. + + This flags a whole TV station band if more than fraction f of the samples + within a station band exceed a given threshold. The threshold is calculated + by wanting a fixed false positive rate (as described by sigma) for fraction + f of samples exceeding the threshold + + Parameters + ---------- + x : np.ndarray[freq, time] + Deviations of data in sigma units. + freq : np.ndarray[freq] + Frequency of samples in MHz. + sigma : float, optional + The probability of a false positive given as a sigma of a Gaussian. + f : float, optional + Fraction of bad samples within each channel before flagging the whole + thing. + debug : bool, optional + Returns (mask, fraction) instead to give extra debugging info. + + Returns + ------- + mask : np.ndarray[bool] + Mask of the input data. + """ + p_false = sigma_to_p(sigma) + frac = np.ones_like(x, dtype=np.float32) + + tvstart_freq = 398 + tvwidth_freq = 6 + + # Calculate the boundaries of each frequency channel + df = np.median(np.abs(np.diff(freq))) + freq_start = freq - 0.5 * df + freq_end = freq + 0.5 * df + + for i in range(67): + # Find all frequencies that lie wholly or partially within the TV channel + fs = tvstart_freq + i * tvwidth_freq + fe = fs + tvwidth_freq + sel = (freq_end >= fs) & (freq_start <= fe) + + # Don't continue processing channels for which we don't have + # frequencies in the incoming data + if not sel.any(): + continue + + # Calculate the threshold to apply + N = sel.sum() + k = int(f * N) + + # This is the Gaussian threshold required for there to be at most a p_false + # chance of more than k trials exceeding the threshold. This is the correct + # expression, and has been double checked by numerical trials. + t = p_to_sigma(inverse_binom_cdf_prob(k, N, 1 - p_false)) + + frac[sel] = (x[sel] > t).mean(axis=0)[np.newaxis, :] + + mask = frac > f + + if debug: + return mask, frac + + return mask
+ + + +
+[docs] +def complex_med(x, *args, **kwargs): + """Complex median, done by applying to the real/imag parts individually. + + Parameters + ---------- + x : np.ndarray + Array to apply to. + *args, **kwargs : list, dict + Passed straight through to `np.nanmedian` + + Returns + ------- + m : np.ndarray + Median. + """ + return np.nanmedian(x.real, *args, **kwargs) + 1j * np.nanmedian( + x.imag, *args, **kwargs + )
+ + + +
+[docs] +def destripe(x, w, axis=1): + """Subtract the median along a specified axis. + + Parameters + ---------- + x : np.ndarray + Array to destripe. + w : np.ndarray + Mask array for points to include (True) or ignore (False). + axis : int, optional + Axis to apply destriping along. + + Returns + ------- + y : np.ndarray + Destriped array. + """ + # Calculate the average along the axis + stripe = complex_med(np.where(w, x, np.nan), axis=axis) + stripe = np.nan_to_num(stripe) + + # Construct a slice to broadcast back along the axis + bsel = [slice(None)] * x.ndim + bsel[axis] = None + bsel = tuple(bsel) + + return x - stripe[bsel]
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/mapmaker.html b/docs/_modules/draco/analysis/mapmaker.html new file mode 100644 index 000000000..447a30be3 --- /dev/null +++ b/docs/_modules/draco/analysis/mapmaker.html @@ -0,0 +1,427 @@ + + + + + + draco.analysis.mapmaker — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.mapmaker

+"""Map making from driftscan data using the m-mode formalism."""
+
+import numpy as np
+from caput import config, mpiarray
+
+from ..core import containers, io, task
+from ..util import tools
+
+
+
+[docs] +class BaseMapMaker(task.SingleTask): + """Rudimetary m-mode map maker. + + Attributes + ---------- + nside : int + Resolution of output Healpix map. + """ + + nside = config.Property(proptype=int, default=256) + + bt_cache = None + +
+[docs] + def setup(self, bt): + """Set the beamtransfer matrices to use. + + Parameters + ---------- + bt : beamtransfer.BeamTransfer or manager.ProductManager + Beam transfer manager object (or ProductManager) containing all the + pre-generated beam transfer matrices. + """ + self.beamtransfer = io.get_beamtransfer(bt)
+ + +
+[docs] + def process(self, mmodes): + """Make a map from the given m-modes. + + Parameters + ---------- + mmodes : containers.MModes + Data to map + + Returns + ------- + map : containers.Map + """ + from cora.util import hputil + + # Fetch various properties + bt = self.beamtransfer + lmax = bt.telescope.lmax + mmax = min(bt.telescope.mmax, len(mmodes.index_map["m"]) - 1) + nfreq = len(mmodes.index_map["freq"]) # bt.telescope.nfreq + + # Figure out mapping between the frequencies + bt_freq = self.beamtransfer.telescope.frequencies + mm_freq = mmodes.index_map["freq"]["centre"] + + freq_ind = tools.find_keys(bt_freq, mm_freq, require_match=True) + + # Trim off excess m-modes + mmodes.redistribute("freq") + m_array = mmodes.vis[: (mmax + 1)] + m_array = m_array.redistribute(axis=0) + + m_weight = mmodes.weight[: (mmax + 1)] + m_weight = m_weight.redistribute(axis=0) + + # Create array to store alms in. + alm = mpiarray.MPIArray( + (nfreq, 4, lmax + 1, mmax + 1), + axis=3, + dtype=np.complex128, + comm=mmodes.comm, + ) + alm[:] = 0.0 + + # Loop over all m's and solve from m-mode visibilities to alms. + for mi, m in m_array.enumerate(axis=0): + self.log.debug( + "Processing m=%i (local %i/%i)", m, mi + 1, m_array.local_shape[0] + ) + + # Get and cache the beam transfer matrix, but trim off any l < m. + # if self.bt_cache is None: + # self.bt_cache = (m, bt.beam_m(m)) + # self.log.debug("Cached beamtransfer for m=%i", m) + + for fi in range(nfreq): + v = m_array[mi, :, fi].view(np.ndarray) + a = alm[fi, ..., mi].view(np.ndarray) + Ni = m_weight[mi, :, fi].view(np.ndarray) + + a[:] = self._solve_m(m, freq_ind[fi], v, Ni) + + self.bt_cache = None + + # Redistribute back over frequency + alm = alm.redistribute(axis=0) + + # Copy into square alm array for transform + almt = mpiarray.MPIArray( + (nfreq, 4, lmax + 1, lmax + 1), + dtype=np.complex128, + axis=0, + comm=mmodes.comm, + ) + almt[..., : (mmax + 1)] = alm + alm = almt + + # Perform spherical harmonic transform to map space + maps = hputil.sphtrans_inv_sky(alm, self.nside) + maps = mpiarray.MPIArray.wrap(maps, axis=0) + + m = containers.Map(nside=self.nside, axes_from=mmodes, comm=mmodes.comm) + m.map[:] = maps + + return m
+ + + def _solve_m(self, m, f, v, Ni): + """Solve for the a_lm's. + + This implementation is blank. Must be overriden. + + Parameters + ---------- + m : int + Which m-mode are we solving for. + f : int + Frequency we are solving for. This is the index for the beam transfers. + v : np.ndarray[2, nbase] + Visibility data. + Ni : np.ndarray[2, nbase] + Inverse of noise variance. Used as the noise matrix for the solve. + + Returns + ------- + a : np.ndarray[npol, lmax+1] + """ + pass
+ + + +
+[docs] +class DirtyMapMaker(BaseMapMaker): + r"""Generate a dirty map. + + Notes + ----- + The dirty map is produced by generating a set of :math:`a_{lm}` coefficients + using + + .. math:: \hat{\mathbf{a}} = \mathbf{B}^\dagger \mathbf{N}^{-1} \mathbf{v} + + and then performing the spherical harmonic transform to get the sky intensity. + """ + + def _solve_m(self, m, f, v, Ni): + bt = self.beamtransfer + + # Massage the arrays into shape + v = v.reshape(bt.ntel) + Ni = Ni.reshape(bt.ntel) + bm = bt.beam_m(m, fi=f).reshape(bt.ntel, bt.nsky) + + # Solve for the dirty map alms + a = np.dot(bm.T.conj(), Ni * v) + + # Reshape to the correct output + return a.reshape(bt.telescope.num_pol_sky, bt.telescope.lmax + 1)
+ + + +
+[docs] +class MaximumLikelihoodMapMaker(BaseMapMaker): + r"""Generate a Maximum Likelihood map using the Moore-Penrose pseudo-inverse. + + Notes + ----- + The dirty map is produced by generating a set of :math:`a_{lm}` coefficients + using + + .. math:: \hat{\mathbf{a}} = \left( \mathbf{N}^{-1/2 }\mathbf{B} \right) ^+ \mathbf{N}^{-1/2} \mathbf{v} + + where the superscript :math:`+` denotes the pseudo-inverse. + """ + + def _solve_m(self, m, f, v, Ni): + bt = self.beamtransfer + + # Massage the arrays into shape + v = v.reshape(bt.ntel) + Ni = Ni.reshape(bt.ntel) + bm = bt.beam_m(m, fi=f).reshape(bt.ntel, bt.nsky) + + Nh = Ni**0.5 + + # Construct the beam pseudo inverse + ib = pinv_svd(bm * Nh[:, np.newaxis]) + + # Solve for the ML map alms + a = np.dot(ib, Nh * v) + + # Reshape to the correct output + return a.reshape(bt.telescope.num_pol_sky, bt.telescope.lmax + 1)
+ + + +
+[docs] +class WienerMapMaker(BaseMapMaker): + r"""Generate a Wiener filtered map. + + Assumes that the signal is a Gaussian random field described by + a power-law power spectum. + + Attributes + ---------- + prior_amp : float + An amplitude prior to use for the map maker. In Kelvin. + prior_tilt : float + Power law index prior for the power spectrum. + + Notes + ----- + The Wiener map is produced by generating a set of :math:`a_{lm}` coefficients + using + + .. math:: + \hat{\mathbf{a}} = \left( \mathbf{S}^{-1} + \mathbf{B}^\dagger + \mathbf{N}^{-1} \mathbf{B} \right)^{-1} \mathbf{B}^\dagger \mathbf{N}^{-1} \mathbf{v} + + where the signal covariance matrix :math:`\mathbf{S}` is assumed to be + governed by a power law power spectrum for each polarisation component. + """ + + prior_amp = config.Property(proptype=float, default=1.0) + prior_tilt = config.Property(proptype=float, default=0.5) + + bt_cache = None + + def _solve_m(self, m, f, v, Ni): + import scipy.linalg as la + + bt = self.beamtransfer + + # Get transfer for this m and f + if self.bt_cache is not None and self.bt_cache[0] == m: + bm = self.bt_cache[1][f] + else: + bm = bt.beam_m(m, fi=f) + bm = bm[..., m:].reshape(bt.ntel, -1) + + # Massage the arrays into shape + v = v.reshape(bt.ntel) + Ni = Ni.reshape(bt.ntel) + Nh = Ni**0.5 + + # Construct pre-wightened beam and beam-conjugated matrices + bmt = bm * Nh[:, np.newaxis] + bth = bmt.T.conj() + + # Pre-wighten the visibilities + vt = Nh * v + + # Construct the signal covariance matrix + l = np.arange(bt.telescope.lmax + 1) + l[0] = 1 # Change l=0 to get around singularity + l = l[m:] # Trim off any l < m + cl_TT = self.prior_amp**2 * l ** (-self.prior_tilt) + S_diag = np.concatenate([cl_TT] * 4) + + # For large ntel it's quickest to solve in the standard Wiener filter way + if bt.ntel > bt.nsky: + Ci = np.diag(1.0 / S_diag) + np.dot( + bth, bmt + ) # Construct the inverse covariance + a_dirty = np.dot(bth, vt) # Find the dirty map + a_wiener = la.solve(Ci, a_dirty, sym_pos=True) # Solve to find C vt + + # If not it's better to rearrange using the results for blockwise matrix inversion + else: + pCi = np.identity(bt.ntel) + np.dot(bmt * S_diag[np.newaxis, :], bth) + v_int = la.solve(pCi, vt, sym_pos=True) + a_wiener = S_diag * np.dot(bth, v_int) + + # Copy the solution into a correctly shaped array output + a = np.zeros((bt.telescope.num_pol_sky, bt.telescope.lmax + 1), dtype=v.dtype) + a[:, m:] = a_wiener.reshape(bt.telescope.num_pol_sky, -1) + + return a
+ + + +
+[docs] +def pinv_svd(M, acond=1e-4, rcond=1e-3): + """Generate the pseudo-inverse from an svd. + + Not really clear why I'm not just using la.pinv2 instead + """ + import scipy.linalg as la + + u, sig, vh = la.svd(M, full_matrices=False) + + rank = np.sum(np.logical_and(sig > rcond * sig.max(), sig > acond)) + + psigma_diag = 1.0 / sig[:rank] + + return np.transpose(np.conjugate(np.dot(u[:, :rank] * psigma_diag, vh[:rank])))
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/powerspectrum.html b/docs/_modules/draco/analysis/powerspectrum.html new file mode 100644 index 000000000..94c32014f --- /dev/null +++ b/docs/_modules/draco/analysis/powerspectrum.html @@ -0,0 +1,210 @@ + + + + + + draco.analysis.powerspectrum — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.powerspectrum

+"""Power spectrum estimation code."""
+
+import numpy as np
+from caput import config
+
+from ..core import containers, task
+
+
+
+[docs] +class QuadraticPSEstimation(task.SingleTask): + """Estimate a power spectrum from a set of KLModes. + + Attributes + ---------- + psname : str + Name of power spectrum to use. Must be precalculated in the driftscan + products. + pstype : str + Type of power spectrum estimate to calculate. One of 'unwindowed', + 'minimum_variance' or 'uncorrelated'. + """ + + psname = config.Property(proptype=str) + + pstype = config.enum( + ["unwindowed", "minimum_variance", "uncorrelated"], default="unwindowed" + ) + +
+[docs] + def setup(self, manager): + """Set the ProductManager instance to use. + + Parameters + ---------- + manager : ProductManager + Manager object to use + """ + self.manager = manager
+ + +
+[docs] + def process(self, klmodes): + """Estimate the power spectrum from the given data. + + Parameters + ---------- + klmodes : containers.KLModes + KLModes for which to estimate the power spectrum + + Returns + ------- + ps : containers.PowerSpectrum + """ + import scipy.linalg as la + + if not isinstance(klmodes, containers.KLModes): + raise ValueError( + "Input container must be instance of " + f"KLModes (received {klmodes.__class__!s})" + ) + + klmodes.redistribute("m") + + pse = self.manager.psestimators[self.psname] + pse.genbands() + + q_list = [] + + for mi, m in klmodes.vis[:].enumerate(axis=0): + ps_single = pse.q_estimator(m, klmodes.vis[m, : klmodes.nmode[m]]) + q_list.append(ps_single) + + q = klmodes.comm.allgather(np.array(q_list).sum(axis=0)) + q = np.array(q).sum(axis=0) + + # reading from directory + fisher, bias = pse.fisher_bias() + + ps = containers.Powerspectrum2D( + kperp_edges=pse.kperp_bands, kpar_edges=pse.kpar_bands + ) + + npar = len(ps.index_map["kpar"]) + nperp = len(ps.index_map["kperp"]) + + # Calculate the right unmixing matrix for each ps type + if self.pstype == "unwindowed": + M = la.pinv(fisher, rcond=1e-8) + elif self.pstype == "uncorrelated": + Fh = la.cholesky(fisher) + M = la.inv(Fh) / Fh.sum(axis=1)[:, np.newaxis] + elif self.pstype == "minimum_variance": + M = np.diag(fisher.sum(axis=1) ** -1) + + ps.powerspectrum[:] = np.dot(M, q - bias).reshape(nperp, npar) + ps.C_inv[:] = fisher.reshape(nperp, npar, nperp, npar) + + return ps
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/sensitivity.html b/docs/_modules/draco/analysis/sensitivity.html new file mode 100644 index 000000000..f67f68e84 --- /dev/null +++ b/docs/_modules/draco/analysis/sensitivity.html @@ -0,0 +1,369 @@ + + + + + + draco.analysis.sensitivity — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.sensitivity

+"""Sensitivity Analysis Tasks."""
+
+import numpy as np
+from caput import config
+
+from ..core import containers, io, task
+from ..util import tools
+
+
+
+[docs] +class ComputeSystemSensitivity(task.SingleTask): + """Compute the sensitivity of beamformed visibilities. + + Parameters + ---------- + exclude_intracyl : bool + Exclude the intracylinder baselines in the sensitivity estimate. + Default is to use all baselines. Note that a RuntimeError + will be raised if exclude_intracyl is True and the visibilities + have already been stacked over cylinder. + """ + + exclude_intracyl = config.Property(proptype=bool, default=False) + +
+[docs] + def setup(self, telescope): + """Save the telescope model. + + Parameters + ---------- + telescope : TransitTelescope + Telescope object to use + """ + self.telescope = io.get_telescope(telescope)
+ + +
+[docs] + def process(self, data): + """Estimate the sensitivity of the input data. + + Parameters + ---------- + data : TODContainer + Must have a weight property that contains an + estimate of the inverse variance of the noise + in each visibility. The visibilities can be + stacked to any level of redundancy. + + Returns + ------- + metrics : SystemSensitivity + Contains the measured and radiometric estimates of + the noise in the beamformed visibilities. + """ + # Ensure we are distributed over frequency. Get shape of visibilities. + data.redistribute("freq") + + nfreq, nstack, ntime = data.vis.local_shape + + # Extract the input flags. If container has a gain dataset, + # then also check for the default gain 1.0 + 0.0j as this indicates + # that an input was masked for a particular time and frequency. + inpflg = data.input_flags[:].view(np.ndarray).astype(bool) + niff = 1 + + if "gain" in data.datasets: + # Derive frequency dependent flags from gains + gainflg = data.gain[:].view(np.ndarray) != (1.0 + 0.0j) + inpflg = np.swapaxes(inpflg[np.newaxis, :, :] & gainflg, 0, 1) + # Flatten frequency and time axis so we can use numpy's unique + inpflg = inpflg.reshape(inpflg.shape[0], -1) + niff = nfreq + + # Find unique sets of input flags + uniq_inpflg, index_cnt = np.unique(inpflg, return_inverse=True, axis=1) + + # Calculate redundancy for each unique set of input flags + cnt = tools.calculate_redundancy( + uniq_inpflg.astype(np.float32), + data.prod, + data.reverse_map["stack"]["stack"], + data.stack.size, + ) + + # Determine stack axis + stack_new, stack_flag = tools.redefine_stack_index_map( + self.telescope, data.input, data.prod, data.stack, data.reverse_map["stack"] + ) + + if not np.all(stack_flag): + self.log.warning( + "There are %d stacked baselines that are masked " + "in the telescope instance." % np.sum(~stack_flag) + ) + + ps = data.prod[stack_new["prod"]] + conj = stack_new["conjugate"] + + prodstack = ps.copy() + prodstack["input_a"] = np.where(conj, ps["input_b"], ps["input_a"]) + prodstack["input_b"] = np.where(conj, ps["input_a"], ps["input_b"]) + + # Figure out mapping between inputs in data file and inputs in telescope + tel_index = tools.find_inputs( + self.telescope.input_index, data.input, require_match=False + ) + + # Use the mapping to extract polarisation and EW position of each input + input_pol = np.array( + [ + self.telescope.polarisation[ti] if ti is not None else "N" + for ti in tel_index + ] + ) + + ew_position = np.array( + [ + self.telescope.feedpositions[ti, 0] if ti is not None else 0.0 + for ti in tel_index + ] + ) + + # Next we determine indices into the stack axis for each polarisation product + # The next three lines result in XY and YX being + # combined into a single polarisation product + pa, pb = input_pol[prodstack["input_a"]], input_pol[prodstack["input_b"]] + pol_a = np.where(pa <= pb, pa, pb) + pol_b = np.where(pa <= pb, pb, pa) + + baseline_pol = np.core.defchararray.add(pol_a, pol_b) + + if self.exclude_intracyl: + baseline_flag = ( + ew_position[prodstack["input_a"]] != ew_position[prodstack["input_b"]] + ) + else: + baseline_flag = np.ones(prodstack.size, dtype=bool) + + pol_uniq = [bp for bp in np.unique(baseline_pol) if "N" not in bp] + pol_index = [ + np.flatnonzero((baseline_pol == up) & baseline_flag) for up in pol_uniq + ] + npol = len(pol_uniq) + + auto_flag = (prodstack["input_a"] == prodstack["input_b"]).astype(np.float32) + + if self.exclude_intracyl and (np.sum(auto_flag) == npol): + raise ValueError( + "You have requested the exclusion of " + "intracylinder baselines, however it appears " + "that the visibilities have already been stacked " + "over cylinder, preventing calculation of the " + "radiometric estimate." + ) + + # Dereference the weight dataset + bweight = data.weight[:].view(np.ndarray) + bflag = bweight > 0.0 + + # Initialize arrays + var = np.zeros((nfreq, npol, ntime), dtype=np.float32) + counter = np.zeros((nfreq, npol, ntime), dtype=np.float32) + + # Average over selected baseline per polarization + for pp, ipol in enumerate(pol_index): + pcnt = cnt[ipol, :] + pscale = 2.0 - auto_flag[ipol, np.newaxis] + + # Loop over frequencies to reduce memory usage + for ff in range(nfreq): + fslc = slice((ff % niff) * ntime, ((ff % niff) + 1) * ntime) + pfcnt = pcnt[:, index_cnt[fslc]] + + pvar = tools.invert_no_zero(bweight[ff, ipol, :]) + pflag = bflag[ff, ipol, :].astype(np.float32) + + var[ff, pp, :] = np.sum(pfcnt**2 * pscale * pflag * pvar, axis=0) + + counter[ff, pp, :] = np.sum(pfcnt * pscale * pflag, axis=0) + + # Normalize + var *= tools.invert_no_zero(counter**2) + + # Determine which of the stack indices correspond to autocorrelations + auto_stack_id = np.flatnonzero(auto_flag) + auto_input = prodstack["input_a"][auto_stack_id] + auto_pol = input_pol[auto_input] + + auto_cnt = cnt[auto_stack_id, :][:, index_cnt] + auto_cnt = np.swapaxes(auto_cnt.reshape(-1, niff, ntime), 0, 1) + num_feed = auto_cnt * bflag[:, auto_stack_id, :].astype(np.float32) + + auto = data.vis[:, auto_stack_id, :].local_array.real + + # Construct the radiometric estimate of the noise by taking the sum + # of the product of pairs of (possibly stacked) autocorrelations. + radiometer = np.zeros((nfreq, npol, ntime), dtype=np.float32) + radiometer_counter = np.zeros((nfreq, npol, ntime), dtype=np.float32) + + for ii, (ai, pi) in enumerate(zip(auto_input, auto_pol)): + for jj, (aj, pj) in enumerate(zip(auto_input, auto_pol)): + if self.exclude_intracyl and (ew_position[ai] == ew_position[aj]): + # Exclude intracylinder baselines + continue + + # Combine XY and YX into single polarisation product + pp = pol_uniq.index(pi + pj) if pi <= pj else pol_uniq.index(pj + pi) + + # Weight by the number of feeds that were averaged + # together to obtain each stacked autocorrelation + nsq = num_feed[:, ii, :] * num_feed[:, jj, :] + + radiometer[:, pp, :] += nsq * auto[:, ii, :] * auto[:, jj, :] + + radiometer_counter[:, pp, :] += nsq + + # Calculate number of independent samples from the + # integration time, frequency resolution, and fraction of packets lost + tint = np.median(np.abs(np.diff(data.time))) + dnu = np.median(data.index_map["freq"]["width"]) * 1e6 + + if ("flags" in data) and ("frac_lost" in data["flags"]): + frac_lost = data["flags"]["frac_lost"][:].local_array + else: + frac_lost = np.zeros((1, 1), dtype=np.float32) + + nint = dnu * tint * (1.0 - frac_lost[:, np.newaxis, :]) + + # Normalize by the number of independent samples + # and the total number of baselines squared + radiometer *= tools.invert_no_zero(nint * radiometer_counter**2) + + # Create output container + metrics = containers.SystemSensitivity( + pol=np.array(pol_uniq, dtype="<U2"), + axes_from=data, + attrs_from=data, + comm=data.comm, + distributed=data.distributed, + ) + + metrics.redistribute("freq") + + # In order to write generic code for generating the radiometric + # estimate of the sensitivity, we had to sum over the upper and lower triangle + # of the visibility matrix. Below we multiply by sqrt(2) in order to + # obtain the sensitivity of the real component. + metrics.radiometer[:] = np.sqrt(2.0 * radiometer) + metrics.measured[:] = np.sqrt(2.0 * var) + + # Save the total number of baselines that were averaged in the weight dataset + metrics.weight[:] = counter + + # Save the fraction of missing samples + metrics.frac_lost[:] = frac_lost + + return metrics
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/sidereal.html b/docs/_modules/draco/analysis/sidereal.html new file mode 100644 index 000000000..bbd9d8627 --- /dev/null +++ b/docs/_modules/draco/analysis/sidereal.html @@ -0,0 +1,965 @@ + + + + + + draco.analysis.sidereal — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.sidereal

+"""Take timestream data and regridding it into sidereal days which can be stacked.
+
+Usage
+=====
+
+Generally you would want to use these tasks together. Sending time stream data
+into  :class:`SiderealGrouper`, then feeding that into
+:class:`SiderealRegridder` to grid onto each sidereal day, and then into
+:class:`SiderealStacker` if you want to combine the different days.
+"""
+
+import numpy as np
+import scipy.linalg as la
+from caput import config, mpiarray, tod
+from cora.util import units
+
+from ..core import containers, io, task
+from ..util import tools
+from .transform import Regridder
+
+
+
+[docs] +class SiderealGrouper(task.SingleTask): + """Group individual timestreams together into whole Sidereal days. + + Attributes + ---------- + padding : float + Extra amount of a sidereal day to pad each timestream by. Useful for + getting rid of interpolation artifacts. + offset : float + Time in seconds to subtract before determining the LSD. Useful if the + desired output is not a full sideral stream, but rather a narrow window + around source transits on different sideral days. In that case, one + should set this quantity to `240 * (source_ra - 180)`. + min_day_length : float + Require at least this fraction of a full sidereal day to process. + """ + + padding = config.Property(proptype=float, default=0.0) + offset = config.Property(proptype=float, default=0.0) + min_day_length = config.Property(proptype=float, default=0.10) + + def __init__(self): + super().__init__() + + self._timestream_list = [] + self._current_lsd = None + +
+[docs] + def setup(self, manager): + """Set the local observers position. + + Parameters + ---------- + manager : :class:`~caput.time.Observer` + An Observer object holding the geographic location of the telescope. + Note that :class:`~drift.core.TransitTelescope` instances are also + Observers. + """ + # Need an observer object holding the geographic location of the telescope. + self.observer = io.get_telescope(manager)
+ + +
+[docs] + def process(self, tstream): + """Load in each sidereal day. + + Parameters + ---------- + tstream : containers.TimeStream + Timestream to group together. + + Returns + ------- + ts : containers.TimeStream or None + Returns the timestream of each sidereal day when we have received + the last file, otherwise returns :obj:`None`. + """ + # This is the start and the end of the LSDs of the file only if padding + # is chosen to be 0 (default). If padding is set to some value then 'lsd_start' + # will actually correspond to the start of of the requested time frame (incl + # padding) + lsd_start = int( + self.observer.unix_to_lsd(tstream.time[0] - self.padding - self.offset) + ) + lsd_end = int( + self.observer.unix_to_lsd(tstream.time[-1] + self.padding - self.offset) + ) + + # If current_lsd is None then this is the first time we've run + if self._current_lsd is None: + self._current_lsd = lsd_start + + # If this file started during the current lsd add it onto the list + if self._current_lsd == lsd_start: + self._timestream_list.append(tstream) + + self.log.info("Adding file into group for LSD:%i", lsd_start) + + # If this file ends during a later LSD then we need to process the + # current list and restart the system + if self._current_lsd < lsd_end: + self.log.info("Concatenating files for LSD:%i", self._current_lsd) + + # Combine timestreams into a single container for the whole day this + # could get returned as None if there wasn't enough data + tstream_all = self._process_current_lsd() + + # Reset list and current LSD for the new file + self._timestream_list = [tstream] + self._current_lsd = lsd_end + + return tstream_all + + return None
+ + +
+[docs] + def process_finish(self): + """Return the final sidereal day. + + Returns + ------- + ts : containers.TimeStream or None + Returns the timestream of the final sidereal day if it's long + enough, otherwise returns :obj:`None`. + """ + # If we are here there is no more data coming, we just need to process any remaining data + return self._process_current_lsd() if self._timestream_list else None
+ + + def _process_current_lsd(self): + # Combine the current set of files into a timestream + + lsd = self._current_lsd + + # Calculate the length of data in this current LSD + start = self.observer.unix_to_lsd(self._timestream_list[0].time[0]) + end = self.observer.unix_to_lsd(self._timestream_list[-1].time[-1]) + day_length = min(end, lsd + 1) - max(start, lsd) + + # If the amount of data for this day is too small, then just skip + if day_length < self.min_day_length: + return None + + self.log.info("Constructing LSD:%i [%i files]", lsd, len(self._timestream_list)) + + # Construct the combined timestream + ts = tod.concatenate(self._timestream_list) + + # Add attributes for the LSD and a tag for labelling saved files + ts.attrs["tag"] = "lsd_%i" % lsd + ts.attrs["lsd"] = lsd + + # Clear the timestream list since these days have already been processed + self._timestream_list = [] + + return ts
+ + + +
+[docs] +class SiderealRegridder(Regridder): + """Take a sidereal days worth of data, and put onto a regular grid. + + Uses a maximum-likelihood inverse of a Lanczos interpolation to do the + regridding. This gives a reasonably local regridding, that is pretty well + behaved in m-space. + + Attributes + ---------- + samples : int + Number of samples across the sidereal day. + lanczos_width : int + Width of the Lanczos interpolation kernel. + snr_cov: float + Ratio of signal covariance to noise covariance (used for Wiener filter). + down_mix: bool + Down mix the visibility prior to interpolation using the fringe rate + of a source at zenith. This is un-done after the interpolation. + """ + + down_mix = config.Property(proptype=bool, default=False) + +
+[docs] + def setup(self, manager): + """Set the local observers position. + + Parameters + ---------- + manager : :class:`~caput.time.Observer` + An Observer object holding the geographic location of the telescope. + Note that :class:`~drift.core.TransitTelescope` instances are also + Observers. + """ + # Need an Observer object holding the geographic location of the telescope. + self.observer = io.get_telescope(manager)
+ + +
+[docs] + def process(self, data): + """Regrid the sidereal day. + + Parameters + ---------- + data : containers.TimeStream + Timestream data for the day (must have a `LSD` attribute). + + Returns + ------- + sdata : containers.SiderealStream + The regularly gridded sidereal timestream. + """ + self.log.info("Regridding LSD:%i", data.attrs["lsd"]) + + # Redistribute if needed too + data.redistribute("freq") + + sfreq = data.vis.local_offset[0] + efreq = sfreq + data.vis.local_shape[0] + freq = data.freq[sfreq:efreq] + + # Convert data timestamps into LSDs + timestamp_lsd = self.observer.unix_to_lsd(data.time) + + # Fetch which LSD this is to set bounds + self.start = data.attrs["lsd"] + self.end = self.start + 1 + + # Get view of data + weight = data.weight[:].view(np.ndarray) + vis_data = data.vis[:].view(np.ndarray) + + # Mix down + if self.down_mix: + self.log.info("Downmixing before regridding.") + phase = self._get_phase(freq, data.prodstack, timestamp_lsd) + vis_data *= phase + + # perform regridding + new_grid, sts, ni = self._regrid(vis_data, weight, timestamp_lsd) + + # Mix back up + if self.down_mix: + phase = self._get_phase(freq, data.prodstack, new_grid).conj() + sts *= phase + ni *= (np.abs(phase) > 0.0).astype(ni.dtype) + + # Wrap to produce MPIArray + sts = mpiarray.MPIArray.wrap(sts, axis=0) + ni = mpiarray.MPIArray.wrap(ni, axis=0) + + # FYI this whole process creates an extra copy of the sidereal stack. + # This could probably be optimised out with a little work. + sdata = containers.SiderealStream(axes_from=data, ra=self.samples) + sdata.redistribute("freq") + sdata.vis[:] = sts + sdata.weight[:] = ni + sdata.attrs["lsd"] = self.start + sdata.attrs["tag"] = "lsd_%i" % self.start + + return sdata
+ + + def _get_phase(self, freq, prod, lsd): + # Determine if any baselines contains masked feeds + # These baselines will be flagged since they do not + # have valid baseline distances. + aa, bb = prod["input_a"], prod["input_b"] + + mask = self.observer.feedmask[(aa, bb)].astype(np.float32)[ + np.newaxis, :, np.newaxis + ] + + # Calculate the fringe rate assuming that ha = 0.0 and dec = lat + lmbda = units.c / (freq * 1e6) + u = self.observer.baselines[np.newaxis, :, 0] / lmbda[:, np.newaxis] + + omega = -2.0 * np.pi * u * np.cos(np.radians(self.observer.latitude)) + + # Calculate the local sidereal angle + dphi = 2.0 * np.pi * (lsd - np.floor(lsd)) + + # Construct a complex sinusoid whose frequency + # is equal to each baselines fringe rate + return mask * np.exp( + -1.0j * omega[:, :, np.newaxis] * dphi[np.newaxis, np.newaxis, :] + )
+ + + +def _search_nearest(x, xeval): + index_next = np.searchsorted(x, xeval, side="left") + + index_previous = np.maximum(0, index_next - 1) + index_next = np.minimum(x.size - 1, index_next) + + return np.where( + np.abs(xeval - x[index_previous]) < np.abs(xeval - x[index_next]), + index_previous, + index_next, + ) + + +
+[docs] +class SiderealRegridderNearest(SiderealRegridder): + """Regrid onto the sidereal day using nearest neighbor interpolation.""" + + def _regrid(self, vis, weight, lsd): + # Create a regular grid + interp_grid = np.arange(0, self.samples, dtype=np.float64) / self.samples + interp_grid = interp_grid * (self.end - self.start) + self.start + + # Find the data points that are closest to the fixed points on the grid + index = _search_nearest(lsd, interp_grid) + + interp_vis = vis[..., index] + interp_weight = weight[..., index] + + # Flag the re-gridded data if the nearest neighbor was more than one + # sample spacing away. This can occur if the input data does not have + # complete sidereal coverage. + delta = np.median(np.abs(np.diff(lsd))) + distant = np.flatnonzero(np.abs(lsd[index] - interp_grid) > delta) + interp_weight[..., distant] = 0.0 + + return interp_grid, interp_vis, interp_weight
+ + + +
+[docs] +class SiderealRegridderLinear(SiderealRegridder): + """Regrid onto the sidereal day using linear interpolation.""" + + def _regrid(self, vis, weight, lsd): + # Create a regular grid + interp_grid = np.arange(0, self.samples, dtype=np.float64) / self.samples + interp_grid = interp_grid * (self.end - self.start) + self.start + + # Find the data points that lie on either side of each point in the fixed grid + index = np.searchsorted(lsd, interp_grid, side="left") + + ind1 = index - 1 + ind2 = index + + # If the fixed grid is outside the range covered by the data, + # then we will extrapolate and later flag as bad. + below = np.flatnonzero(ind1 == -1) + if below.size > 0: + ind1[below] = 0 + ind2[below] = 1 + + above = np.flatnonzero(ind2 == lsd.size) + if above.size > 0: + ind1[above] = lsd.size - 2 + ind2[above] = lsd.size - 1 + + # If the closest data points to the fixed grid point are more than one + # sample spacing away, then we will later flag that data as bad. + # This will occur if the input data does not cover the full sidereal day. + delta = np.median(np.abs(np.diff(lsd))) + distant = np.flatnonzero( + (np.abs(lsd[ind1] - interp_grid) > delta) + | (np.abs(lsd[ind2] - interp_grid) > delta) + ) + + # Calculate the coefficients for the linear interpolation + dx1 = interp_grid - lsd[ind1] + dx2 = lsd[ind2] - interp_grid + + norm = tools.invert_no_zero(dx1 + dx2) + coeff1 = dx2 * norm + coeff2 = dx1 * norm + + # Initialize the output arrays + shp = vis.shape[:-1] + (self.samples,) + + interp_vis = np.zeros(shp, dtype=vis.dtype) + interp_weight = np.zeros(shp, dtype=weight.dtype) + + # Loop over frequencies to reduce memory usage + for ff in range(shp[0]): + fvis = vis[ff] + fweight = weight[ff] + + # Consider the data valid if it has nonzero weight + fflag = fweight > 0.0 + + # Determine the variance from the inverse weight + fvar = tools.invert_no_zero(fweight) + + # Require both data points to be valid for the interpolated value to be valid + finterp_flag = fflag[:, ind1] & fflag[:, ind2] + + # Interpolate the visibilities and propagate the weights + interp_vis[ff] = coeff1 * fvis[:, ind1] + coeff2 * fvis[:, ind2] + + interp_weight[ff] = tools.invert_no_zero( + coeff1**2 * fvar[:, ind1] + coeff2**2 * fvar[:, ind2] + ) * finterp_flag.astype(np.float32) + + # Flag as bad any values that were extrapolated or that used distant points + interp_weight[..., below] = 0.0 + interp_weight[..., above] = 0.0 + interp_weight[..., distant] = 0.0 + + return interp_grid, interp_vis, interp_weight
+ + + +
+[docs] +class SiderealRegridderCubic(SiderealRegridder): + """Regrid onto the sidereal day using cubic Hermite spline interpolation.""" + + def _regrid(self, vis, weight, lsd): + # Create a regular grid + interp_grid = np.arange(0, self.samples, dtype=np.float64) / self.samples + interp_grid = interp_grid * (self.end - self.start) + self.start + + # Find the data point just after each point on the fixed grid + index = np.searchsorted(lsd, interp_grid, side="left") + + # Find the 4 data points that will be used to interpolate + # each point on the fixed grid + index = np.vstack([index + i for i in range(-2, 2)]) + + # If the fixed grid is outside the range covered by the data, + # then we will extrapolate and later flag as bad + below = np.flatnonzero(np.any(index < 0, axis=0)) + if below.size > 0: + index = np.maximum(index, 0) + + above = np.flatnonzero(np.any(index >= lsd.size, axis=0)) + if above.size > 0: + index = np.minimum(index, lsd.size - 1) + + # If the closest data points to the fixed grid point are more than one + # sample spacing away, then we will later flag that data as bad. + # This will occur if the input data does not cover the full sidereal day. + delta = np.median(np.abs(np.diff(lsd))) + distant = np.flatnonzero( + np.any(np.abs(interp_grid - lsd[index]) > (2.0 * delta), axis=0) + ) + + # Calculate the coefficients for the interpolation + u = (interp_grid - lsd[index[1]]) * tools.invert_no_zero( + lsd[index[2]] - lsd[index[1]] + ) + + coeff = np.zeros((4, u.size), dtype=np.float64) + coeff[0] = u * ((2 - u) * u - 1) + coeff[1] = u**2 * (3 * u - 5) + 2 + coeff[2] = u * ((4 - 3 * u) * u + 1) + coeff[3] = u**2 * (u - 1) + coeff *= 0.5 + + # Initialize the output arrays + shp = vis.shape[:-1] + (self.samples,) + + interp_vis = np.zeros(shp, dtype=vis.dtype) + interp_weight = np.zeros(shp, dtype=weight.dtype) + + # Loop over frequencies to reduce memory usage + for ff in range(shp[0]): + fvis = vis[ff] + fweight = weight[ff] + + # Consider the data valid if it has nonzero weight + fflag = fweight > 0.0 + + # Determine the variance from the inverse weight + fvar = tools.invert_no_zero(fweight) + + # Interpolate the visibilities and propagate the weights + finterp_flag = np.ones(shp[1:], dtype=bool) + finterp_var = np.zeros(shp[1:], dtype=weight.dtype) + + for ii, cc in zip(index, coeff): + finterp_flag &= fflag[:, ii] + finterp_var += cc**2 * fvar[:, ii] + + interp_vis[ff] += cc * fvis[:, ii] + + # Invert the accumulated variances to get the weight + # Require all data points are valid for the interpolated value to be valid + interp_weight[ff] = tools.invert_no_zero(finterp_var) * finterp_flag.astype( + np.float32 + ) + + # Flag as bad any values that were extrapolated or that used distant points + interp_weight[..., below] = 0.0 + interp_weight[..., above] = 0.0 + interp_weight[..., distant] = 0.0 + + return interp_grid, interp_vis, interp_weight
+ + + +
+[docs] +class SiderealStacker(task.SingleTask): + """Take in a set of sidereal days, and stack them up. + + Also computes the variance over sideral days using an + algorithm that updates the sum of square differences from + the current mean, which is less prone to numerical issues. + See West, D.H.D. (1979). https://doi.org/10.1145/359146.359153. + + Attributes + ---------- + tag : str (default: "stack") + The tag to give the stack. + weight: str (default: "inverse_variance") + The weighting to use in the stack. + Either `uniform` or `inverse_variance`. + with_sample_variance : bool (default: False) + Add a dataset containing the sample variance + of the visibilities over sidereal days to the + sidereal stack. + """ + + stack = None + + tag = config.Property(proptype=str, default="stack") + weight = config.enum(["uniform", "inverse_variance"], default="inverse_variance") + with_sample_variance = config.Property(proptype=bool, default=False) + +
+[docs] + def process(self, sdata): + """Stack up sidereal days. + + Parameters + ---------- + sdata : containers.SiderealStream + Individual sidereal day to add to stack. + """ + sdata.redistribute("freq") + + # Get the LSD (or CSD) label out of the input's attributes. + # If there is no label, use a placeholder. + if "lsd" in sdata.attrs: + input_lsd = sdata.attrs["lsd"] + elif "csd" in sdata.attrs: + input_lsd = sdata.attrs["csd"] + else: + input_lsd = -1 + + input_lsd = _ensure_list(input_lsd) + + # If this is our first sidereal day, then initialize the + # container that will hold the stack. + if self.stack is None: + self.stack = containers.empty_like(sdata) + + # Add stack-specific datasets + if "nsample" not in self.stack.datasets: + self.stack.add_dataset("nsample") + + if self.with_sample_variance and ( + "sample_variance" not in self.stack.datasets + ): + self.stack.add_dataset("sample_variance") + + self.stack.redistribute("freq") + + # Initialize all datasets to zero. + for data in self.stack.datasets.values(): + data[:] = 0 + + self.lsd_list = [] + + # Keep track of the sum of squared weights + # to perform Bessel's correction at the end. + if self.with_sample_variance: + self.sum_coeff_sq = np.zeros_like(self.stack.weight[:].view(np.ndarray)) + + # Accumulate + self.log.info(f"Adding to stack LSD(s): {input_lsd!s}") + + self.lsd_list += input_lsd + + if "nsample" in sdata.datasets: + # The input sidereal stream is already a stack + # over multiple sidereal days. Use the nsample + # dataset as the weight for the uniform case. + count = sdata.nsample[:] + else: + # The input sidereal stream contains a single + # sidereal day. Use a boolean array that + # indicates a non-zero weight dataset as + # the weight for the uniform case. + dtype = self.stack.nsample.dtype + count = (sdata.weight[:] > 0.0).astype(dtype) + + # Determine the weights to be used in the average. + if self.weight == "uniform": + coeff = count.astype(np.float32) + # Accumulate the variances in the stack.weight dataset. + self.stack.weight[:] += (coeff**2) * tools.invert_no_zero(sdata.weight[:]) + else: + coeff = sdata.weight[:] + # We are using inverse variance weights. In this case, + # we accumulate the inverse variances in the stack.weight + # dataset. Do that directly to avoid an unneccessary + # division in the more general expression above. + self.stack.weight[:] += sdata.weight[:] + + # Accumulate the total number of samples. + self.stack.nsample[:] += count + + # Below we will need to normalize by the current sum of coefficients. + # Can be found in the stack.nsample dataset for uniform case or + # the stack.weight dataset for inverse variance case. + if self.weight == "uniform": + sum_coeff = self.stack.nsample[:].astype(np.float32) + else: + sum_coeff = self.stack.weight[:] + + # Calculate weighted difference between the new data and the current mean. + delta_before = coeff * (sdata.vis[:] - self.stack.vis[:]) + + # Update the mean. + self.stack.vis[:] += delta_before * tools.invert_no_zero(sum_coeff) + + # The calculations below are only required if the sample variance was requested + if self.with_sample_variance: + # Accumulate the sum of squared coefficients. + self.sum_coeff_sq += coeff**2 + + # Calculate the difference between the new data and the updated mean. + delta_after = sdata.vis[:] - self.stack.vis[:] + + # Update the sample variance. + self.stack.sample_variance[0] += delta_before.real * delta_after.real + self.stack.sample_variance[1] += delta_before.real * delta_after.imag + self.stack.sample_variance[2] += delta_before.imag * delta_after.imag
+ + +
+[docs] + def process_finish(self): + """Normalize the stack and return the result. + + Returns + ------- + stack : containers.SiderealStream + Stack of sidereal days. + """ + self.stack.attrs["tag"] = self.tag + self.stack.attrs["lsd"] = np.array(self.lsd_list) + + # For uniform weighting, normalize the accumulated variances by the total + # number of samples squared and then invert to finalize stack.weight. + if self.weight == "uniform": + norm = self.stack.nsample[:].astype(np.float32) + self.stack.weight[:] = tools.invert_no_zero(self.stack.weight[:]) * norm**2 + + # We need to normalize the sample variance by the sum of coefficients. + # Can be found in the stack.nsample dataset for uniform case + # or the stack.weight dataset for inverse variance case. + if self.with_sample_variance: + if self.weight != "uniform": + norm = self.stack.weight[:] + + # Perform Bessel's correction. In the case of + # uniform weighting, norm will be equal to nsample - 1. + norm = norm - self.sum_coeff_sq * tools.invert_no_zero(norm) + + # Normalize the sample variance. + self.stack.sample_variance[:] *= np.where( + self.stack.nsample[:] > 1, tools.invert_no_zero(norm), 0.0 + )[np.newaxis, :] + + return self.stack
+
+ + + +
+[docs] +class SiderealStackerMatch(task.SingleTask): + """Take in a set of sidereal days, and stack them up. + + This treats the time average of each input sidereal stream as an extra source of + noise and uses a Wiener filter approach to consistent stack the individual streams + together while accounting for their distinct coverage in RA. In practice this is + used for co-adding stacks with different sidereal coverage while marginalising out + the effects of the different cross talk contributions that each input stream may + have. + + There is no uniquely correct solution for the sidereal average (or m=0 mode) of the + output stream. This task fixes this unknown mode by setting the *median* of each 24 + hour period to zero. Note this is not the same as setting the m=0 mode to be zero. + + Parameters + ---------- + tag : str + The tag to give the stack. + """ + + stack = None + lsd_list = None + + tag = config.Property(proptype=str, default="stack") + + count = 0 + +
+[docs] + def process(self, sdata): + """Stack up sidereal days. + + Parameters + ---------- + sdata : containers.SiderealStream + Individual sidereal day to stack up. + """ + sdata.redistribute("freq") + + if self.stack is None: + self.log.info("Starting new stack.") + + self.stack = containers.empty_like(sdata) + self.stack.redistribute("freq") + self.stack.vis[:] = 0.0 + self.stack.weight[:] = 0.0 + + self.count = 0 + self.Ni_s = mpiarray.zeros( + (sdata.weight.shape[0], sdata.weight.shape[2]), + axis=0, + comm=sdata.comm, + dtype=np.float64, + ) + self.Vm = [] + self.lsd_list = [] + + label = sdata.attrs.get("tag", f"stream_{self.count}") + self.log.info(f"Adding {label} to stack.") + + # Get an estimate of the noise inverse for each time and freq in the file. + # Average over baselines as we don't have the memory + Ni_d = sdata.weight[:].mean(axis=1) + + # Calculate the trace of the inverse noise covariance for each frequency + tr_Ni = Ni_d.sum(axis=1) + + # Calculate the projection vector v + v = Ni_d * tools.invert_no_zero(tr_Ni[:, np.newaxis]) ** 0.5 + + d = sdata.vis[:] + + # Calculate and store the dirty map in the stack container + self.stack.vis[:] += ( + d * Ni_d[:, np.newaxis, :] + # - v[:, np.newaxis, :] * np.dot(sdata.vis[:], v.T)[..., np.newaxis] + - v[:, np.newaxis, :] + * np.matmul(v[:, np.newaxis, np.newaxis, :], d[..., np.newaxis])[..., 0] + ) + + # Propagate the transformation into the weights, but for the moment we need to + # store the variance. We don't propagate the change coming from matching the + # means, as it is small, and the effects are primarily on the off-diagonal + # covariance entries that we don't store anyway + self.stack.weight[:] += ( + tools.invert_no_zero(sdata.weight[:]) * Ni_d[:, np.newaxis, :] ** 2 + ) + + # Accumulate the total inverse noise + self.Ni_s += Ni_d + + # We need to keep the projection vector until the end + self.Vm.append(v) + + # Get the LSD label out of the data (resort to using a CSD if it's + # present). If there's no label just use a place holder and stack + # anyway. + if "lsd" in sdata.attrs: + input_lsd = sdata.attrs["lsd"] + elif "csd" in sdata.attrs: + input_lsd = sdata.attrs["csd"] + else: + input_lsd = -1 + self.lsd_list += _ensure_list(input_lsd) + + self.count += 1
+ + +
+[docs] + def process_finish(self): + """Construct and emit sidereal stack. + + Returns + ------- + stack : containers.SiderealStream + Stack of sidereal days. + """ + self.stack.attrs["tag"] = self.tag + + Va = np.array(self.Vm).transpose(1, 2, 0) + + # Dereference for efficiency to avoid MPI calls in the loop + sv = self.stack.vis[:].local_array + sw = self.stack.weight[:].local_array + + # Loop over all frequencies to do the deconvolution. The loop is done because + # of the difficulty mapping the operations we would want to do into what numpy + # allows + for lfi in range(self.stack.vis[:].local_shape[0]): + Ni_s = self.Ni_s.local_array[lfi] + N_s = tools.invert_no_zero(Ni_s) + V = Va[lfi] * N_s[:, np.newaxis] + + # Note, need to use a pseudo-inverse in here as there is a singular mode + A = la.pinv( + np.identity(self.count) - np.dot(V.T, Ni_s[:, np.newaxis] * V), + rcond=1e-8, + ) + + # Perform the deconvolution step + sv[lfi] = sv[lfi] * N_s + np.dot(V, np.dot(A, np.dot(sv[lfi], V).T)).T + + # Normalise the weights + sw[lfi] = tools.invert_no_zero(sw[lfi]) * Ni_s**2 + + # Remove the full day median to set a well defined normalisation, otherwise the + # mean is undefined + stack_median = np.median(sv.real, axis=2) + np.median(sv.imag, axis=2) * 1.0j + sv -= stack_median[:, :, np.newaxis] + + # Set the full LSD list + self.stack.attrs["lsd"] = np.array(self.lsd_list) + + return self.stack
+
+ + + +def _ensure_list(x): + if hasattr(x, "__iter__"): + y = list(x) + else: + y = [x] + + return y +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/sourcestack.html b/docs/_modules/draco/analysis/sourcestack.html new file mode 100644 index 000000000..f1ed5c3bb --- /dev/null +++ b/docs/_modules/draco/analysis/sourcestack.html @@ -0,0 +1,586 @@ + + + + + + draco.analysis.sourcestack — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.sourcestack

+"""Source Stack Analysis Tasks."""
+
+import numpy as np
+from caput import config, pipeline
+from cora.util import units
+from mpi4py import MPI
+
+from ..core import containers, task
+from ..util.random import RandomTask
+from ..util.tools import invert_no_zero
+
+# Constants
+NU21 = units.nu21
+C = units.c
+
+
+
+[docs] +class SourceStack(task.SingleTask): + """Stack the product of `draco.analysis.BeamForm` accross sources. + + For this to work BeamForm must have been run with `collapse_ha = True` (default). + + Attributes + ---------- + freqside : int + Number of frequency bins to keep on each side of source bin + when stacking. Default: 50. + single_source_bin_index : int, optional + Only stack on sources in frequency bin with this index. + Useful for isolating stacking signal from a narrow frequency range. + Default: None. + """ + + # Number of frequencies to keep on each side of source RA + freqside = config.Property(proptype=int, default=50) + + # Only consider sources within frequency channel with this index + single_source_bin_index = config.Property(proptype=int, default=None) + +
+[docs] + def process(self, formed_beam): + """Receives a formed beam object and stack across sources. + + Parameters + ---------- + formed_beam : `containers.FormedBeam` object + Formed beams to stack over sources. + + Returns + ------- + stack : `containers.FrequencyStack` object + The stack of sources. + """ + # Get communicator + comm = formed_beam.comm + + # Ensure formed_beam is distributed in sources + formed_beam.redistribute("object_id") + + # Local shape and offset + loff = formed_beam.beam.local_offset[0] + lshape = formed_beam.beam.local_shape[0] + + # Frequency axis + freq = formed_beam.freq + nfreq = len(freq) + + # Polarisation axis + pol = formed_beam.pol + npol = len(pol) + + # Frequency of sources in MHz + source_freq = NU21 / (formed_beam["redshift"]["z"][loff : loff + lshape] + 1.0) + + # Size of source stack array + self.nstack = 2 * self.freqside + 1 + + # Construct frequency offset axis (for stack container) + self.stack_axis = np.copy( + formed_beam.frequency[ + int(nfreq / 2) - self.freqside : int(nfreq / 2) + self.freqside + 1 + ] + ) + self.stack_axis["centre"] = ( + self.stack_axis["centre"] - self.stack_axis["centre"][self.freqside] + ) + + # Get f_mask and source_indices + freqdiff = freq[np.newaxis, :] - source_freq[:, np.newaxis] + + # Stack axis bin edges to digitize each source at, in either increasing + # or decreasing order depending on order of frequencies + if self.stack_axis["centre"][0] > self.stack_axis["centre"][-1]: + stackbins = self.stack_axis["centre"] + 0.5 * self.stack_axis["width"] + stackbins = np.append( + stackbins, + self.stack_axis["centre"][-1] - 0.5 * self.stack_axis["width"][-1], + ) + else: + stackbins = self.stack_axis["centre"] - 0.5 * self.stack_axis["width"] + stackbins = np.append( + stackbins, + self.stack_axis["centre"][-1] + 0.5 * self.stack_axis["width"][-1], + ) + + # Index of each frequency in stack axis, for each source + source_indices = np.digitize(freqdiff, stackbins) - 1 + + # Indices to be processed in full frequency axis for each source + f_mask = (source_indices >= 0) & (source_indices < self.nstack) + + # Only sources in the frequency range of the data. + source_mask = (np.sum(f_mask, axis=1) > 0).astype(bool) + + # If desired, also restrict to sources within a specific channel. + # This works because the frequency axis is not distributed between + # ranks. + if self.single_source_bin_index is not None: + fs = formed_beam.index_map["freq"][self.single_source_bin_index] + restricted_chan_mask = np.abs(source_freq - fs["centre"]) < ( + 0.5 * fs["width"] + ) + source_mask *= restricted_chan_mask + + # Container to hold the stack + if npol > 1: + stack = containers.FrequencyStackByPol( + freq=self.stack_axis, pol=pol, attrs_from=formed_beam + ) + else: + stack = containers.FrequencyStack( + freq=self.stack_axis, attrs_from=formed_beam + ) + + # Loop over polarisations + for pp, pstr in enumerate(pol): + fb = formed_beam.beam[:, pp].view(np.ndarray) + fw = formed_beam.weight[:, pp].view(np.ndarray) + + # Source stack array. + source_stack = np.zeros(self.nstack, dtype=np.float64) + source_weight = np.zeros(self.nstack, dtype=np.float64) + + count = 0 # Source counter + # For each source in the range of this process + for lq in range(lshape): + if not source_mask[lq]: + # Source not in the data redshift range + continue + + count += 1 + + # Indices and slice for frequencies included in the stack. + f_indices = np.arange(nfreq, dtype=np.int32)[f_mask[lq]] + f_slice = np.s_[f_indices[0] : f_indices[-1] + 1] + + source_stack += np.bincount( + source_indices[lq, f_slice], + weights=fw[lq, f_slice] * fb[lq, f_slice], + minlength=self.nstack, + ) + + source_weight += np.bincount( + source_indices[lq, f_slice], + weights=fw[lq, f_slice], + minlength=self.nstack, + ) + + # Gather source stack for all ranks. Each contains the sum + # over a different subset of sources. + + source_stack_full = np.zeros( + comm.size * self.nstack, dtype=source_stack.dtype + ) + source_weight_full = np.zeros( + comm.size * self.nstack, dtype=source_weight.dtype + ) + # Gather all ranks + comm.Allgather(source_stack, source_stack_full) + comm.Allgather(source_weight, source_weight_full) + + # Determine the index for the output container + oslc = (pp, slice(None)) if npol > 1 else slice(None) + + # Sum across ranks + stack.weight[oslc] = np.sum( + source_weight_full.reshape(comm.size, self.nstack), axis=0 + ) + stack.stack[oslc] = np.sum( + source_stack_full.reshape(comm.size, self.nstack), axis=0 + ) * invert_no_zero(stack.weight[oslc]) + + # Gather all ranks of count. Report number of sources stacked + full_count = comm.reduce(count, op=MPI.SUM, root=0) + if comm.rank == 0: + self.log.info(f"Number of sources stacked for pol {pstr}: {full_count}") + + return stack
+
+ + + +
+[docs] +class RandomSubset(task.SingleTask, RandomTask): + """Take a large mock catalog and draw `number` catalogs of a given `size`. + + Attributes + ---------- + number : int + Number of catalogs to construct. + size : int + Number of objects in each catalog. + """ + + number = config.Property(proptype=int) + size = config.Property(proptype=int) + + def __init__(self): + super().__init__() + self.catalog_ind = 0 + +
+[docs] + def setup(self, catalog): + """Set the full mock catalog. + + Parameters + ---------- + catalog : containers.SourceCatalog or containers.FormedBeam + The mock catalog to draw from. + """ + # If the catalog is distributed, then we need to make sure that it + # is distributed over an axis other than the object_id axis. + if catalog.distributed: + axis_size = { + key: len(val) + for key, val in catalog.index_map.items() + if key != "object_id" + } + + if len(axis_size) > 0: + self.distributed_axis = max(axis_size, key=axis_size.get) + + self.log.info( + f"Distributing over the {self.distributed_axis} axis " + "to take random subsets of objects." + ) + catalog.redistribute(self.distributed_axis) + + else: + raise ValueError( + "The catalog that was provided is distributed " + "over the object_id axis. Unable to take a " + "random subset over object_id." + ) + else: + self.distributed_axis = None + + if "tag" in catalog.attrs: + self.base_tag = f"{catalog.attrs['tag']}_mock_{{:05d}}" + else: + self.base_tag = "mock_{{:05d}}" + + self.catalog = catalog
+ + +
+[docs] + def process(self): + """Draw a new random catalog. + + Returns + ------- + new_catalog : containers.SourceCatalog or containers.FormedBeam + A catalog of the same type as the input catalog, with a random set of + objects. + """ + if self.catalog_ind >= self.number: + raise pipeline.PipelineStopIteration + + objects = self.catalog.index_map["object_id"] + num_cat = len(objects) + + # NOTE: We need to be very careful here, the RNG is initialised at first access + # and this is a collective operation. So we need to ensure all ranks do it even + # though only rank=0 is going to use the RNG in this task + rng = self.rng + + # Generate a random selection of objects on rank=0 and broadcast to all other + # ranks + if self.comm.rank == 0: + ind = np.sort(rng.choice(num_cat, self.size, replace=False)) + else: + ind = np.zeros(self.size, dtype=np.int64) + self.comm.Bcast(ind, root=0) + + # Create new container + new_catalog = self.catalog.__class__( + object_id=objects[ind], + attrs_from=self.catalog, + axes_from=self.catalog, + comm=self.catalog.comm, + ) + + for name in self.catalog.datasets.keys(): + if name not in new_catalog.datasets: + new_catalog.add_dataset(name) + + if self.distributed_axis is not None: + new_catalog.redistribute(self.distributed_axis) + + new_catalog.attrs["tag"] = self.base_tag.format(self.catalog_ind) + + # Loop over all datasets and if they have an object_id axis, select the + # relevant objects along that axis + for name, dset in self.catalog.datasets.items(): + if dset.attrs["axis"][0] == "object_id": + new_catalog.datasets[name][:] = dset[:][ind] + else: + new_catalog.datasets[name][:] = dset[:] + + self.catalog_ind += 1 + + return new_catalog
+
+ + + +
+[docs] +class GroupSourceStacks(task.SingleTask): + """Accumulate many frequency stacks into a single container. + + Attributes + ---------- + ngroup : int + The number of frequency stacks to accumulate into a + single container. + """ + + ngroup = config.Property(proptype=int, default=100) + +
+[docs] + def setup(self): + """Create a list to be populated by the process method.""" + self.stack = [] + self.nmock = 0 + self.counter = 0 + + self._container_lookup = { + containers.FrequencyStack: containers.MockFrequencyStack, + containers.FrequencyStackByPol: containers.MockFrequencyStackByPol, + containers.MockFrequencyStack: containers.MockFrequencyStack, + containers.MockFrequencyStackByPol: containers.MockFrequencyStackByPol, + }
+ + +
+[docs] + def process(self, stack): + """Add a FrequencyStack to the list. + + As soon as list contains `ngroup` items, they will be collapsed + into a single container and output by the task. + + Parameters + ---------- + stack : containers.FrequencyStack, containers.FrequencyStackByPol, + containers.MockFrequencyStack, containers.MockFrequencyStackByPol + + Returns + ------- + out : containers.MockFrequencyStack, containers.MockFrequencyStackByPol + The previous `ngroup` FrequencyStacks accumulated into a single container. + """ + self.stack.append(stack) + if "mock" in stack.index_map: + self.nmock += stack.index_map["mock"].size + else: + self.nmock += 1 + + self.log.info( + "Collected frequency stack. Current size is %d." % len(self.stack) + ) + + if (len(self.stack) % self.ngroup) == 0: + return self._reset() + + return None
+ + +
+[docs] + def process_finish(self): + """Return whatever FrequencyStacks are currently in the list. + + Returns + ------- + out : containers.MockFrequencyStack, containers.MockFrequencyStackByPol + The remaining frequency stacks accumulated into a single container. + """ + if len(self.stack) > 0: + return self._reset() + + return None
+ + + def _reset(self): + """Combine all frequency stacks currently in the list into new container. + + Then, empty the list, reset the stack counter, and increment the group counter. + """ + self.log.info( + "We have accumulated %d mock realizations. Saving to file. [group %03d]" + % (self.nmock, self.counter) + ) + + mock = np.arange(self.nmock, dtype=np.int64) + + # Create the output container + OutputContainer = self._container_lookup[self.stack[0].__class__] + + out = OutputContainer( + mock=mock, axes_from=self.stack[0], attrs_from=self.stack[0] + ) + + counter_str = f"{self.counter:03d}" + + # Update tag using the hierarchy that a group contains multiple mocks, + # and a supergroup contains multiple groups. + if "tag" in out.attrs: + tag = out.attrs["tag"].split("_") + if "group" in tag: + ig = max(ii for ii, tt in enumerate(tag) if tt == "group") + tag[ig] = "supergroup" + tag[ig + 1] = counter_str + + elif "mock" in tag: + im = max(ii for ii, tt in enumerate(tag) if tt == "mock") + tag[im] = "group" + tag[im + 1] = counter_str + + else: + tag.append(f"group_{counter_str}") + + out.attrs["tag"] = "_".join(tag) + else: + out.attrs["tag"] = f"group_{counter_str}" + + for name in self.stack[0].datasets.keys(): + if name not in out.datasets: + out.add_dataset(name) + + # Loop over mock stacks and save to output container + for name, odset in out.datasets.items(): + mock_count = 0 + + for ss, stack in enumerate(self.stack): + dset = stack.datasets[name] + if dset.attrs["axis"][0] == "mock": + data = dset[:] + else: + data = dset[np.newaxis, ...] + + for mdata in data: + odset[mock_count] = mdata[:] + mock_count += 1 + + # Reset the class attributes + self.stack = [] + self.nmock = 0 + self.counter += 1 + + return out
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/svdfilter.html b/docs/_modules/draco/analysis/svdfilter.html new file mode 100644 index 000000000..9b3d29362 --- /dev/null +++ b/docs/_modules/draco/analysis/svdfilter.html @@ -0,0 +1,308 @@ + + + + + + draco.analysis.svdfilter — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.svdfilter

+"""A set of tasks for SVD filtering the m-modes."""
+
+import numpy as np
+import scipy.linalg as la
+from caput import config
+
+from draco.core import containers, task
+
+
+
+[docs] +class SVDSpectrumEstimator(task.SingleTask): + """Calculate the SVD spectrum of a set of m-modes. + + Attributes + ---------- + niter : int + Number of iterations of EM to perform. + """ + + niter = config.Property(proptype=int, default=5) + +
+[docs] + def process(self, mmodes): + """Calculate the spectrum. + + Parameters + ---------- + mmodes : containers.MModes + MModes to find the spectrum of. + + Returns + ------- + spectrum : containers.SVDSpectrum + """ + mmodes.redistribute("m") + + vis = mmodes.vis[:] + weight = mmodes.weight[:] + + nmode = min(vis.shape[1] * vis.shape[3], vis.shape[2]) + + spec = containers.SVDSpectrum(singularvalue=nmode, axes_from=mmodes) + spec.spectrum[:] = 0.0 + + for mi, m in vis.enumerate(axis=0): + self.log.debug("Calculating SVD spectrum of m=%i", m) + + vis_m = vis.local_array[mi].transpose((1, 0, 2)).reshape(vis.shape[2], -1) + weight_m = ( + weight.local_array[mi].transpose((1, 0, 2)).reshape(vis.shape[2], -1) + ) + mask_m = weight_m == 0.0 + + u, sig, vh = svd_em(vis_m, mask_m, niter=self.niter) + + spec.spectrum[m] = sig + + return spec
+
+ + + +
+[docs] +class SVDFilter(task.SingleTask): + """SVD filter the m-modes to remove the most correlated components. + + Attributes + ---------- + niter : int + Number of iterations of EM to perform. + local_threshold : float + Cut out modes with singular value higher than `local_threshold` times the + largest mode on each m. + global_threshold : float + Remove modes with singular value higher than `global_threshold` times the + largest mode on any m + """ + + niter = config.Property(proptype=int, default=5) + global_threshold = config.Property(proptype=float, default=1e-3) + local_threshold = config.Property(proptype=float, default=1e-2) + +
+[docs] + def process(self, mmodes): + """Filter MModes using an SVD. + + Parameters + ---------- + mmodes : container.MModes + MModes to process + + Returns + ------- + mmodes : container.MModes + """ + from mpi4py import MPI + + mmodes.redistribute("m") + + vis = mmodes.vis[:] + weight = mmodes.weight[:] + + sv_max = 0.0 + + # TODO: this should be changed such that it does all the computation in + # a single SVD pass. + + # Do a quick first pass calculation of all the singular values to get the max on this rank. + for mi, m in vis.enumerate(axis=0): + vis_m = vis.local_array[mi].transpose((1, 0, 2)).reshape(vis.shape[2], -1) + weight_m = ( + weight.local_array[mi].transpose((1, 0, 2)).reshape(vis.shape[2], -1) + ) + mask_m = weight_m == 0.0 + + u, sig, vh = svd_em(vis_m, mask_m, niter=self.niter) + + sv_max = max(sig[0], sv_max) + + # Reduce to get the global max. + global_max = mmodes.comm.allreduce(sv_max, op=MPI.MAX) + + self.log.debug("Global maximum singular value=%.2g", global_max) + import sys + + sys.stdout.flush() + + # Loop over all m's and remove modes below the combined cut + for mi, m in vis.enumerate(axis=0): + vis_m = vis.local_array[mi].transpose((1, 0, 2)).reshape(vis.shape[2], -1) + weight_m = ( + weight.local_array[mi].transpose((1, 0, 2)).reshape(vis.shape[2], -1) + ) + mask_m = weight_m == 0.0 + + u, sig, vh = svd_em(vis_m, mask_m, niter=self.niter) + + # Zero out singular values below the combined mode cut + global_cut = (sig > self.global_threshold * global_max).sum() + local_cut = (sig > self.local_threshold * sig[0]).sum() + cut = max(global_cut, local_cut) + sig[:cut] = 0.0 + + # Recombine the matrix + vis_m = np.dot(u, sig[:, np.newaxis] * vh) + + # Reshape and write back into the mmodes container + vis[mi] = vis_m.reshape(vis.shape[2], 2, -1).transpose((1, 0, 2)) + + return mmodes
+
+ + + +
+[docs] +def svd_em(A, mask, niter=5, rank=5, full_matrices=False): + """Perform an SVD with missing entries using Expectation-Maximisation. + + This assumes that the matrix is well approximated by only a few modes in + order fill the missing entries. This is probably not a proper EM scheme, but + is not far off. + + Parameters + ---------- + A : np.ndarray + Matrix to SVD. + mask : np.ndarray + Boolean array of masked values. Missing values are `True`. + niter : int, optional + Number of iterations to perform. + rank : int, optional + Set the rank of the approximation used to fill the missing values. + full_matrices : bool, optional + Return the full span of eigenvectors and values (see `scipy.linalg.svd` + for a fuller description). + + Returns + ------- + u, sig, vh : np.ndarray + The singular values and vectors. + """ + # Do an initial fill of the missing entries + A = A.copy() + A[mask] = np.median(A[~mask]) + + # Perform cycles of calculating the SVD with the current guess for the + # missing values, then forming a new estimate of the missing values using a + # low rank approximation. + for i in range(niter): + u, sig, vh = la.svd(A, full_matrices=full_matrices, overwrite_a=False) + + low_rank_A = np.dot(u[:, :rank] * sig[:rank], vh[:rank]) + A[mask] = low_rank_A[mask] + + return u, sig, vh
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/analysis/transform.html b/docs/_modules/draco/analysis/transform.html new file mode 100644 index 000000000..e63c7a271 --- /dev/null +++ b/docs/_modules/draco/analysis/transform.html @@ -0,0 +1,1800 @@ + + + + + + draco.analysis.transform — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.analysis.transform

+"""Miscellaneous transformations to do on data.
+
+This includes grouping frequencies and products to performing the m-mode transform.
+"""
+
+from typing import Optional, Tuple, Union, overload
+
+import numpy as np
+import scipy.linalg as la
+from caput import config, mpiarray
+from caput.tools import invert_no_zero
+from numpy.lib.recfunctions import structured_to_unstructured
+
+from ..core import containers, io, task
+from ..util import regrid, tools
+
+
+
+[docs] +class FrequencyRebin(task.SingleTask): + """Rebin neighbouring frequency channels. + + Parameters + ---------- + channel_bin : int + Number of channels to in together. + """ + + channel_bin = config.Property(proptype=int, default=1) + +
+[docs] + def process(self, ss): + """Take the input dataset and rebin the frequencies. + + Parameters + ---------- + ss : containers.SiderealStream or containers.TimeStream + Input data to rebin. Can also be an `andata.CorrData` instance, + however the output will be a `containers.TimeStream` instance. + + Returns + ------- + sb : containers.SiderealStream or containers.TimeStream + Rebinned data. Type should match the input. + """ + if "freq" not in ss.index_map: + raise RuntimeError("Data does not have a frequency axis.") + + if len(ss.freq) % self.channel_bin != 0: + raise RuntimeError("Binning must exactly divide the number of channels.") + + # Get all frequencies onto same node + ss.redistribute(["time", "ra"]) + + # Calculate the new frequency centres and widths + fc = ss.index_map["freq"]["centre"].reshape(-1, self.channel_bin).mean(axis=-1) + fw = ss.index_map["freq"]["width"].reshape(-1, self.channel_bin).sum(axis=-1) + + freq_map = np.empty(fc.shape[0], dtype=ss.index_map["freq"].dtype) + freq_map["centre"] = fc + freq_map["width"] = fw + + # Create new container for rebinned stream + sb = containers.empty_like(ss, freq=freq_map) + + # Get all frequencies onto same node + sb.redistribute(["time", "ra"]) + + # Rebin the arrays, do this with a loop to save memory + for fi in range(len(ss.freq)): + # Calculate rebinned index + ri = fi // self.channel_bin + + sb.vis[ri] += ss.vis[fi] * ss.weight[fi] + + if "gain" in ss.datasets: + sb.gain[ri] += ( + ss.gain[fi] / self.channel_bin + ) # Don't do weighted average for the moment + + sb.weight[ri] += ss.weight[fi] + + # If we are on the final sub-channel then divide the arrays through + if (fi + 1) % self.channel_bin == 0: + sb.vis[ri] *= tools.invert_no_zero(sb.weight[ri]) + + sb.redistribute("freq") + + return sb
+
+ + + +
+[docs] +class CollateProducts(task.SingleTask): + """Extract and order the correlation products for map-making. + + The task will take a sidereal task and format the products that are needed + or the map-making. It uses a BeamTransfer instance to figure out what these + products are, and how they should be ordered. It similarly selects only the + required frequencies. + + It is important to note that while the input + :class:`~containers.SiderealStream` can contain more feeds and frequencies + than are contained in the BeamTransfers, the converse is not true. That is, + all the frequencies and feeds that are in the BeamTransfers must be found in + the timestream object. + + Parameters + ---------- + weight : string ('natural', 'uniform', or 'inverse_variance') + How to weight the redundant baselines when stacking: + 'natural' - each baseline weighted by its redundancy (default) + 'uniform' - each baseline given equal weight + 'inverse_variance' - each baseline weighted by the weight attribute + """ + + weight = config.Property(proptype=str, default="natural") + +
+[docs] + def setup(self, tel): + """Set the Telescope instance to use. + + Parameters + ---------- + tel : TransitTelescope + Telescope object to use + """ + if self.weight not in ["natural", "uniform", "inverse_variance"]: + KeyError(f"Do not recognize weight = {self.weight!s}") + + self.telescope = io.get_telescope(tel) + + # Precalculate the stack properties + self.bt_stack = np.array( + [ + ( + (tools.cmap(upp[0], upp[1], self.telescope.nfeed), 0) + if upp[0] <= upp[1] + else (tools.cmap(upp[1], upp[0], self.telescope.nfeed), 1) + ) + for upp in self.telescope.uniquepairs + ], + dtype=[("prod", "<u4"), ("conjugate", "u1")], + ) + + # Construct the equivalent prod and stack index_map for the telescope instance + triu = np.triu_indices(self.telescope.nfeed) + dt_prod = np.dtype([("input_a", "<u2"), ("input_b", "<u2")]) + self.bt_prod = np.array(triu).astype("<u2").T.copy().view(dt_prod).reshape(-1) + + # Construct the equivalent reverse_map stack for the telescope instance. + # Note that we identify invalid products here using an index that is the + # size of the stack axis. + feedmask = self.telescope.feedmask[triu] + + self.bt_rev = np.empty( + feedmask.size, dtype=[("stack", "<u4"), ("conjugate", "u1")] + ) + self.bt_rev["stack"] = np.where( + feedmask, self.telescope.feedmap[triu], self.telescope.npairs + ) + self.bt_rev["conjugate"] = np.where(feedmask, self.telescope.feedconj[triu], 0)
+ + + @overload + def process(self, ss: containers.SiderealStream) -> containers.SiderealStream: ... + + @overload + def process(self, ss: containers.TimeStream) -> containers.TimeStream: ... + +
+[docs] + def process(self, ss): + """Select and reorder the products. + + Parameters + ---------- + ss + Data with products + + Returns + ------- + sp + Dataset containing only the required products. + """ + # For each input in the file, find the corresponding index in the telescope instance + input_ind = tools.find_inputs( + self.telescope.input_index, ss.input, require_match=False + ) + + # Figure out the reverse mapping (i.e., for each input in the telescope instance, + # find the corresponding index in file) + rev_input_ind = tools.find_inputs( + ss.input, self.telescope.input_index, require_match=True + ) + + # Figure out mapping between the frequencies + freq_ind = tools.find_keys( + ss.freq[:], self.telescope.frequencies, require_match=True + ) + + bt_freq = ss.index_map["freq"][freq_ind] + # Determine the input product map and conjugation. + # If the input timestream is already stacked, then attempt to redefine + # its representative products so that they contain only feeds that exist + # and are not masked in the telescope instance. + if ss.is_stacked: + stack_new, stack_flag = tools.redefine_stack_index_map( + self.telescope, ss.input, ss.prod, ss.stack, ss.reverse_map["stack"] + ) + + if not np.all(stack_flag): + self.log.warning( + "There are %d stacked baselines that are masked " + "in the telescope instance." % np.sum(~stack_flag) + ) + + ss_prod = ss.prod[stack_new["prod"]] + ss_conj = stack_new["conjugate"] + + else: + ss_prod = ss.prod + ss_conj = np.zeros(ss_prod.size, dtype=bool) + + # Create output container + sp = ss.__class__( + freq=bt_freq, + input=self.telescope.input_index, + prod=self.bt_prod, + stack=self.bt_stack, + reverse_map_stack=self.bt_rev, + copy_from=ss, + distributed=True, + comm=ss.comm, + ) + + # Check if frequencies are already ordered + no_redistribute = freq_ind == list(range(len(ss.freq[:]))) + + # If frequencies are mapped across ranks, we have to redistribute so all + # frequencies and products are on each rank + raxis = "freq" if no_redistribute else ["ra", "time"] + self.log.debug(f"Distributing across '{raxis}' axis") + ss.redistribute(raxis) + sp.redistribute(raxis) + + # Initialize datasets in output container + sp.vis[:] = 0.0 + sp.weight[:] = 0.0 + sp.input_flags[:] = ss.input_flags[rev_input_ind, :] + + # Infer number of products that went into each stack + if self.weight != "inverse_variance": + ssi = ss.input_flags[:] + ssp = ss.index_map["prod"][:] + sss = ss.reverse_map["stack"]["stack"][:] + nstack = ss.vis.shape[1] + + nprod_in_stack = tools.calculate_redundancy(ssi, ssp, sss, nstack) + + if self.weight == "uniform": + nprod_in_stack = (nprod_in_stack > 0).astype(np.float32) + + # Create counter to increment during the stacking. + # This will be used to normalize at the end. + counter = np.zeros_like(sp.weight[:]) + + # Dereference the global slices, there's a hidden MPI call in the [:] operation. + spv = sp.vis[:] + ssv = ss.vis[:] + spw = sp.weight[:] + ssw = ss.weight[:] + + # Get the local frequency and time slice/mapping + freq_ind = slice(None) if no_redistribute else freq_ind + time_ind = slice(None) if no_redistribute else ssv.local_bounds + + # Iterate over products (stacked) in the sidereal stream + for ss_pi, ((ii, ij), conj) in enumerate(zip(ss_prod, ss_conj)): + # Map the feed indices into ones for the Telescope class + bi, bj = input_ind[ii], input_ind[ij] + + # If either feed is not in the telescope class, skip it. + if bi is None or bj is None: + continue + + sp_pi = self.telescope.feedmap[bi, bj] + feedconj = self.telescope.feedconj[bi, bj] + + # Skip if product index is not valid + if sp_pi < 0: + continue + + # Generate weight + if self.weight == "inverse_variance": + wss = ssw.local_array[freq_ind, ss_pi] + + else: + wss = (ssw.local_array[freq_ind, ss_pi] > 0.0).astype(np.float32) + wss[:] *= nprod_in_stack[np.newaxis, ss_pi, time_ind] + + # Accumulate visibilities, conjugating if required + if feedconj == conj: + spv.local_array[:, sp_pi] += wss * ssv.local_array[freq_ind, ss_pi] + else: + spv.local_array[:, sp_pi] += ( + wss * ssv.local_array[freq_ind, ss_pi].conj() + ) + + # Accumulate variances in quadrature. Save in the weight dataset. + spw.local_array[:, sp_pi] += wss**2 * tools.invert_no_zero( + ssw.local_array[freq_ind, ss_pi] + ) + + # Increment counter + counter.local_array[:, sp_pi] += wss + + # Divide through by counter to get properly weighted visibility average + sp.vis[:] *= tools.invert_no_zero(counter) + sp.weight[:] = counter**2 * tools.invert_no_zero(sp.weight[:]) + + # Copy over any additional datasets that need to be frequency filtered + containers.copy_datasets_filter( + ss, sp, "freq", freq_ind, ["input", "prod", "stack"] + ) + + # Switch back to frequency distribution. This will have minimal + # cost if we are already distributed in frequency + ss.redistribute("freq") + sp.redistribute("freq") + + return sp
+
+ + + +
+[docs] +class SelectFreq(task.SingleTask): + """Select a subset of frequencies from a container. + + Attributes + ---------- + freq_physical : list + List of physical frequencies in MHz. + Given first priority. + channel_range : list + Range of frequency channel indices, either + [start, stop, step], [start, stop], or [stop] + is acceptable. Given second priority. + channel_index : list + List of frequency channel indices. + Given third priority. + freq_physical_range : list + Range of physical frequencies to include given as (low_freq, high_freq). + Given fourth priority. + """ + + freq_physical = config.Property(proptype=list, default=[]) + freq_physical_range = config.Property(proptype=list, default=[]) + channel_range = config.Property(proptype=list, default=[]) + channel_index = config.Property(proptype=list, default=[]) + +
+[docs] + def process(self, data): + """Selet a subset of the frequencies. + + Parameters + ---------- + data : containers.ContainerBase + A data container with a frequency axis. + + Returns + ------- + newdata : containers.ContainerBase + New container with trimmed frequencies. + """ + # Set up frequency selection. + freq_map = data.index_map["freq"] + + # Construct the frequency channel selection + if self.freq_physical: + newindex = sorted( + { + np.argmin(np.abs(freq_map["centre"] - freq)) + for freq in self.freq_physical + } + ) + + elif self.channel_range and (len(self.channel_range) <= 3): + newindex = slice(*self.channel_range) + + elif self.channel_index: + newindex = self.channel_index + + elif self.freq_physical_range: + low, high = sorted(self.freq_physical_range) + newindex = np.where( + (freq_map["centre"] >= low) & (freq_map["centre"] < high) + )[0] + + else: + raise ValueError( + "Must specify either freq_physical, channel_range, or channel_index." + ) + + freq_map = freq_map[newindex] + + # Destribute input container over ra or time. + data.redistribute(["ra", "time", "pixel"]) + + # Create new container with subset of frequencies. + newdata = containers.empty_like(data, freq=freq_map) + + # Make sure all datasets are initialised + for name in data.datasets.keys(): + if name not in newdata.datasets: + newdata.add_dataset(name) + + # Redistribute new container over ra or time. + newdata.redistribute(["ra", "time", "pixel"]) + + # Copy over datasets. If the dataset has a frequency axis, + # then we only copy over the subset. + if isinstance(data, containers.ContainerBase): + containers.copy_datasets_filter(data, newdata, "freq", newindex) + else: + newdata.vis[:] = data.vis[newindex] + newdata.weight[:] = data.weight[newindex] + newdata.gain[:] = data.gain[newindex] + + newdata.input_flags[:] = data.input_flags[:] + + # Switch back to frequency distribution + data.redistribute("freq") + newdata.redistribute("freq") + + return newdata
+
+ + + +
+[docs] +class MModeTransform(task.SingleTask): + """Transform a sidereal stream to m-modes. + + Currently ignores any noise weighting. + + The maximum m used in the container is derived from the number of + time samples, or if a manager is supplied `telescope.mmax` is used. + + Attributes + ---------- + remove_integration_window : bool + Deconvolve the effect of the finite width of the RA integration (presuming it + was a rectangular integration window). This is applied to both the visibilities + and the weights. + """ + + remove_integration_window = config.Property(proptype=bool, default=False) + +
+[docs] + def setup(self, manager: Optional[io.TelescopeConvertible] = None): + """Set the telescope instance if a manager object is given. + + This is used to set the `mmax` used in the transform. + + Parameters + ---------- + manager : manager.ProductManager, optional + The telescope/manager used to set the `mmax`. If not set, `mmax` + is derived from the timestream. + """ + if manager is not None: + self.telescope = io.get_telescope(manager) + else: + self.telescope = None
+ + +
+[docs] + def process(self, sstream: containers.SiderealContainer) -> containers.MContainer: + """Perform the m-mode transform. + + Parameters + ---------- + sstream : containers.SiderealStream or containers.HybridVisStream + The input sidereal stream. + + Returns + ------- + mmodes : containers.MModes + """ + contmap = { + containers.SiderealStream: containers.MModes, + containers.HybridVisStream: containers.HybridVisMModes, + } + + # Get the output container and figure out at which position is it's + # frequency axis + out_cont = contmap[sstream.__class__] + + sstream.redistribute("freq") + + # Sum the noise variance over time samples, this will become the noise + # variance for the m-modes + nra = sstream.weight.shape[-1] + weight_sum = nra**2 * tools.invert_no_zero( + tools.invert_no_zero(sstream.weight[:]).sum(axis=-1) + ) + + if self.telescope is not None: + mmax = self.telescope.mmax + else: + mmax = sstream.vis.shape[-1] // 2 + + # Create the container to store the modes in + ma = out_cont( + mmax=mmax, + oddra=bool(nra % 2), + axes_from=sstream, + attrs_from=sstream, + comm=sstream.comm, + ) + ma.redistribute("freq") + + # Generate the m-mode transform directly into the output container + # NOTE: Need to zero fill as not every element gets set within _make_marray + ma.vis[:] = 0.0 + _make_marray(sstream.vis[:].local_array, ma.vis[:].local_array) + + # Assign the weights into the container + ma.weight[:] = weight_sum[np.newaxis, np.newaxis, :, :] + + # Divide out the m-mode sinc-suppression caused by the rectangular integration window + if self.remove_integration_window: + m = np.arange(mmax + 1) + w = np.sinc(m / nra) + inv_w = tools.invert_no_zero(w) + + sl_vis = (slice(None),) + (np.newaxis,) * (len(ma.vis.shape) - 1) + ma.vis[:] *= inv_w[sl_vis] + + sl_weight = (slice(None),) + (np.newaxis,) * (len(ma.weight.shape) - 1) + ma.weight[:] *= w[sl_weight] ** 2 + + return ma
+
+ + + +def _make_marray(ts, mmodes=None, mmax=None, dtype=None): + """Make an m-mode array from a sidereal stream. + + This will loop over the first axis of `ts` to avoid needing a lot of memory for + intermediate arrays. + + It can also write the m-mode output directly into a passed `mmodes` array. + """ + if dtype is None: + dtype = np.complex64 + + if mmodes is None and mmax is None: + raise ValueError("One of `mmodes` or `mmax` must be set.") + + if mmodes is not None and mmax is not None: + raise ValueError("If mmodes is set, mmax must be None.") + + if mmodes is not None and mmodes.shape[2:] != ts.shape[:-1]: + raise ValueError( + "ts and mmodes have incompatible shapes: " + f"{mmodes.shape[2:]} != {ts.shape[:-1]}" + ) + + if mmodes is None: + mmodes = np.zeros((mmax + 1, 2) + ts.shape[:-1], dtype=dtype) + + if mmax is None: + mmax = mmodes.shape[0] - 1 + + # Total number of modes + N = ts.shape[-1] + # Calculate the max m to use for both positive and negative m. This is a little + # tricky to get correct as we need to account for the number of negative + # frequencies produced by the FFT + mlim = min(N // 2, mmax) + mlim_neg = N // 2 - 1 + N % 2 if mmax >= N // 2 else mmax + + for i in range(ts.shape[0]): + m_fft = np.fft.fft(ts[i], axis=-1) / ts.shape[-1] + + # Loop and copy over positive and negative m's + # NOTE: this is done as a loop to try and save memory + for mi in range(mlim + 1): + mmodes[mi, 0, i] = m_fft[..., mi] + + for mi in range(1, mlim_neg + 1): + mmodes[mi, 1, i] = m_fft[..., -mi].conj() + + return mmodes + + +
+[docs] +class MModeInverseTransform(task.SingleTask): + """Transform m-modes to sidereal stream. + + Currently ignores any noise weighting. + + .. warning:: + Using `apply_integration_window` will modify the input mmodes. + + Attributes + ---------- + nra : int + Number of RA bins in the output. Note that if the number of samples does not + Nyquist sample the maximum m, information may be lost. If not set, then try to + get from an `original_nra` attribute on the incoming MModes, otherwise determine + an appropriate number of RA bins from the mmax. + apply_integration_window : bool + Apply the effect of the finite width of the RA integration (presuming a + rectangular integration window). This is applied to both the visibilities and + the weights. If this is true, as a side effect the input data will be modified + in place. + """ + + nra = config.Property(proptype=int, default=None) + apply_integration_window = config.Property(proptype=bool, default=False) + +
+[docs] + def process(self, mmodes: containers.MContainer) -> containers.SiderealContainer: + """Perform the m-mode inverse transform. + + Parameters + ---------- + mmodes : containers.MModes + The input m-modes. + + Returns + ------- + sstream : containers.SiderealStream + The output sidereal stream. + """ + # NOTE: If n_time is smaller than Nyquist sampling the m-mode axis then + # the m-modes get clipped. If it is larger, they get zero padded. This + # is NOT passed directly as parameter 'n' to `numpy.fft.ifft`, as this + # would give unwanted behaviour (https://github.com/numpy/numpy/pull/7593). + + # Ensure m-modes are distributed in frequency + mmodes.redistribute("freq") + + # Use the nra property if set otherwise use the natural nra from the incoming + # container + nra_cont = 2 * mmodes.mmax + (1 if mmodes.oddra else 0) + nra = self.nra if self.nra is not None else nra_cont + + # Apply the m-mode sinc-suppression caused by the rectangular integration window + if self.apply_integration_window: + m = np.arange(mmodes.mmax + 1) + w = np.sinc(m / nra) + inv_w = tools.invert_no_zero(w) + + sl_vis = (slice(None),) + (np.newaxis,) * (len(mmodes.vis.shape) - 1) + mmodes.vis[:] *= w[sl_vis] + + sl_weight = (slice(None),) + (np.newaxis,) * (len(mmodes.weight.shape) - 1) + mmodes.weight[:] *= inv_w[sl_weight] ** 2 + + # Re-construct array of S-streams + ssarray = _make_ssarray(mmodes.vis[:], n=nra) + nra = ssarray.shape[-1] # Get the actual nra used + ssarray = mpiarray.MPIArray.wrap(ssarray[:], axis=0, comm=mmodes.comm) + + # Construct container and set visibility data + sstream = containers.SiderealStream( + ra=nra, + axes_from=mmodes, + attrs_from=mmodes, + distributed=True, + comm=mmodes.comm, + ) + sstream.redistribute("freq") + + # Assign the visibilities and weights into the container + sstream.vis[:] = ssarray + # There is no way to recover time information for the weights. + # Just assign the time average to each baseline and frequency. + sstream.weight[:] = mmodes.weight[0, 0, :, :][:, :, np.newaxis] / nra + + return sstream
+
+ + + +
+[docs] +class SiderealMModeResample(task.group_tasks(MModeTransform, MModeInverseTransform)): + """Resample a sidereal stream by FFT. + + This performs a forward and inverse m-mode transform to resample a sidereal stream. + + Attributes + ---------- + nra : int + The number of RA bins for the output stream. + remove_integration_window, apply_integration_window : bool + Remove the integration window from the incoming data, and/or apply it to the + output sidereal stream. + """ + + pass
+ + + +def _make_ssarray(mmodes, n=None): + # Construct an array of sidereal time streams from m-modes + marray = _unpack_marray(mmodes, n=n) + return np.fft.ifft(marray * marray.shape[-1], axis=-1) + + +def _unpack_marray(mmodes, n=None): + # Unpack m-modes into the correct format for an FFT + # (i.e. from [m, +/-, freq, baseline] to [freq, baseline, time-FFT]) + + shape = mmodes.shape[2:] + mmax_plus = mmodes.shape[0] - 1 + if (mmodes[mmax_plus, 1, ...].flatten() == 0).all(): + mmax_minus = mmax_plus - 1 + else: + mmax_minus = mmax_plus + + if n is None: + ntimes = mmax_plus + mmax_minus + 1 + else: + ntimes = n + mmax_plus = np.amin((ntimes // 2, mmax_plus)) + mmax_minus = np.amin(((ntimes - 1) // 2, mmax_minus)) + + # Create array to contain mmodes + marray = np.zeros((*shape, ntimes), dtype=np.complex128) + # Add the DC bin + marray[..., 0] = mmodes[0, 0] + # Add all m-modes up to mmax_minus + for mi in range(1, mmax_minus + 1): + marray[..., mi] = mmodes[mi, 0] + marray[..., -mi] = mmodes[mi, 1].conj() + + if mmax_plus != mmax_minus: + # In case of even number of samples. Add the Nyquist frequency. + marray[..., mmax_plus] = mmodes[mmax_plus, 0] + + return marray + + +
+[docs] +class Regridder(task.SingleTask): + """Interpolate time-ordered data onto a regular grid. + + Uses a maximum-likelihood inverse of a Lanczos interpolation to do the + regridding. This gives a reasonably local regridding, that is pretty well + behaved in m-space. + + Attributes + ---------- + samples : int + Number of samples to interpolate onto. + start: float + Start of the interpolated samples. + end: float + End of the interpolated samples. + lanczos_width : int + Width of the Lanczos interpolation kernel. + snr_cov: float + Ratio of signal covariance to noise covariance (used for Wiener filter). + mask_zero_weight: bool + Mask the output noise weights at frequencies where the weights were + zero for all time samples. + """ + + samples = config.Property(proptype=int, default=1024) + start = config.Property(proptype=float) + end = config.Property(proptype=float) + lanczos_width = config.Property(proptype=int, default=5) + snr_cov = config.Property(proptype=float, default=1e-8) + mask_zero_weight = config.Property(proptype=bool, default=False) + +
+[docs] + def setup(self, observer): + """Set the local observers position. + + Parameters + ---------- + observer : :class:`~caput.time.Observer` + An Observer object holding the geographic location of the telescope. + Note that :class:`~drift.core.TransitTelescope` instances are also + Observers. + """ + self.observer = observer
+ + +
+[docs] + def process(self, data): + """Regrid visibility data in the time direction. + + Parameters + ---------- + data : containers.TODContainer + Time-ordered data. + + Returns + ------- + new_data : containers.TODContainer + The regularly gridded interpolated timestream. + """ + # Redistribute if needed + data.redistribute("freq") + + # View of data + weight = data.weight[:].view(np.ndarray) + vis_data = data.vis[:].view(np.ndarray) + + # Get input time grid + timelike_axis = data.vis.attrs["axis"][-1] + times = data.index_map[timelike_axis][:] + + # check bounds + if self.start is None: + self.start = times[0] + if self.end is None: + self.end = times[-1] + if self.start < times[0] or self.end > times[-1]: + msg = "Start or end points for regridder fall outside bounds of input data." + self.log.error(msg) + raise RuntimeError(msg) + + # perform regridding + new_grid, new_vis, ni = self._regrid(vis_data, weight, times) + + # Wrap to produce MPIArray + new_vis = mpiarray.MPIArray.wrap(new_vis, axis=data.vis.distributed_axis) + ni = mpiarray.MPIArray.wrap(ni, axis=data.vis.distributed_axis) + + # Create new container for output + cont_type = data.__class__ + new_data = cont_type(axes_from=data, **{timelike_axis: new_grid}) + new_data.redistribute("freq") + new_data.vis[:] = new_vis + new_data.weight[:] = ni + + return new_data
+ + + def _regrid(self, vis_data, weight, times): + # Create a regular grid, padded at either end to supress interpolation issues + pad = 5 * self.lanczos_width + interp_grid = ( + np.arange(-pad, self.samples + pad, dtype=np.float64) / self.samples + ) + # scale to specified range + interp_grid = interp_grid * (self.end - self.start) + self.start + + # Construct regridding matrix for reverse problem + lzf = regrid.lanczos_forward_matrix( + interp_grid, times, self.lanczos_width + ).T.copy() + + # Reshape data + vr = vis_data.reshape(-1, vis_data.shape[-1]) + nr = weight.reshape(-1, vis_data.shape[-1]) + + # Construct a signal 'covariance' + Si = np.ones_like(interp_grid) * self.snr_cov + + # Calculate the interpolated data and a noise weight at the points in the padded grid + sts, ni = regrid.band_wiener(lzf, nr, Si, vr, 2 * self.lanczos_width - 1) + + # Throw away the padded ends + sts = sts[:, pad:-pad].copy() + ni = ni[:, pad:-pad].copy() + interp_grid = interp_grid[pad:-pad].copy() + + # Reshape to the correct shape + sts = sts.reshape(vis_data.shape[:-1] + (self.samples,)) + ni = ni.reshape(vis_data.shape[:-1] + (self.samples,)) + + if self.mask_zero_weight: + # set weights to zero where there is no data + w_mask = weight.sum(axis=-1) != 0.0 + ni *= w_mask[..., np.newaxis] + + return interp_grid, sts, ni
+ + + +
+[docs] +class ShiftRA(task.SingleTask): + """Add a shift to the RA axis. + + This is useful for fixing a bug in earlier revisions of CHIME processing. + + Parameters + ---------- + delta : float + The shift to *add* to the RA axis. + periodic : bool, optional + If True, wrap any time sample that is shifted to RA > 360 deg around to its + 360-degree-periodic counterpart, and likewise for any sample that is shifted + to RA < 0 deg. This wrapping is applied to the RA index_map along with any + dataset with an `ra` axis. Default: False. + """ + + delta = config.Property(proptype=float) + periodic = config.Property(proptype=bool, default=False) + +
+[docs] + def process( + self, sscont: containers.SiderealContainer + ) -> containers.SiderealContainer: + """Add a shift to the input sidereal container. + + Parameters + ---------- + sscont + The container to shift. The input is modified in place. + + Returns + ------- + sscont + The shifted container. + """ + if not isinstance(sscont, containers.SiderealContainer): + raise TypeError( + f"Expected a SiderealContainer, got {type(sscont)} instead." + ) + + # Shift RA coordinates by delta + sscont.ra[:] += self.delta + + if self.periodic: + # If shift is positive, subtract 360 deg from any sample shifted to + # > 360 deg. Same idea if shift is negative, for samples shifted to < 0 deg + if self.delta > 0: + sscont.ra[sscont.ra[:] >= 360] -= 360 + else: + sscont.ra[sscont.ra[:] < 0] += 360 + + # Get indices that sort shifted RA axis in ascending order, and apply sort + ascending_ra_idx = np.argsort(sscont.ra[:]) + sscont.ra[:] = sscont.ra[ascending_ra_idx] + + # Loop over datasets in container + for name, dset in sscont.datasets.items(): + if "ra" in dset.attrs["axis"]: + # If dataset has RA axis, identify which axis it is + ra_axis_idx = np.where(dset.attrs["axis"] == "ra")[0][0] + + # Make sure dataset isn't distributed in RA. If it is, redistribute + # along another (somewhat arbitrarily chosen) axis. (This should + # usually not be necessary.) + if dset.distributed and dset.distributed_axis == ra_axis_idx: + redist_axis = max(ra_axis_idx - 1, 0) + dset.redistribute(redist_axis) + + # Apply RA-sorting from earlier to the appropriate axis + slc = [slice(None)] * len(dset.attrs["axis"]) + slc[ra_axis_idx] = ascending_ra_idx + dset[:] = dset[:][tuple(slc)] + + return sscont
+
+ + + +
+[docs] +class SelectPol(task.SingleTask): + """Extract a subset of polarisations, including Stokes parameters. + + This currently only extracts Stokes I. + + Attributes + ---------- + pol : list + Polarisations to extract. Only Stokes I extraction is supported (i.e. `pol = + ["I"]`). + """ + + pol = config.Property(proptype=list) + +
+[docs] + def process(self, polcont): + """Extract the specified polarisation from the input. + + This will combine polarisation pairs to get instrumental Stokes polarisations if + requested. + + Parameters + ---------- + polcont : ContainerBase + A container with a polarisation axis. + + Returns + ------- + selectedpolcont : same as polcont + A new container with the selected polarisation. + """ + polcont.redistribute("freq") + + if "pol" not in polcont.axes: + raise ValueError( + f"Container of type {type(polcont)} does not have a pol axis." + ) + + if len(self.pol) != 1 or self.pol[0] != "I": + raise NotImplementedError("Only selecting stokes I is currently working.") + + outcont = containers.empty_like(polcont, pol=np.array(self.pol)) + outcont.redistribute("freq") + + # Get the locations of the XX and YY components + XX_ind = list(polcont.index_map["pol"]).index("XX") + YY_ind = list(polcont.index_map["pol"]).index("YY") + + for name, dset in polcont.datasets.items(): + out_dset = outcont.datasets[name] + if "pol" not in dset.attrs["axis"]: + out_dset[:] = dset[:] + else: + pol_axis_pos = list(dset.attrs["axis"]).index("pol") + + sl = tuple([slice(None)] * pol_axis_pos) + out_dset[(*sl, 0)] = dset[(*sl, XX_ind)] + out_dset[(*sl, 0)] += dset[(*sl, YY_ind)] + + if np.issubdtype(out_dset.dtype, np.integer): + out_dset[:] //= 2 + else: + out_dset[:] *= 0.5 + + return outcont
+
+ + + +
+[docs] +class TransformJanskyToKelvin(task.SingleTask): + """Task to convert from Jy to Kelvin and vice-versa. + + This integrates over the primary beams in the telescope class to derive the + brightness temperature to flux conversion. + + Attributes + ---------- + convert_Jy_to_K : bool + If True, apply a Jansky to Kelvin conversion factor. If False apply a Kelvin to + Jansky conversion. + reference_declination : float, optional + The declination to set the flux reference for. A source transiting at this + declination will produce a visibility signal equal to its flux. If `None` + (default) use the zenith. + share : {"none", "all"} + Which datasets should the output share with the input. Default is "all". + nside : int + The NSIDE to use for the primary beam area calculation. This may need to be + increased for beams with intricate small scale structure. Default is 256. + """ + + convert_Jy_to_K = config.Property(proptype=bool, default=True) + reference_declination = config.Property(proptype=float, default=None) + share = config.enum(["none", "all"], default="all") + + nside = config.Property(proptype=int, default=256) + +
+[docs] + def setup(self, telescope: io.TelescopeConvertible): + """Set the telescope object. + + Parameters + ---------- + telescope + An object we can get a telescope object from. This telescope must be able to + calculate the beams at all incoming frequencies. + """ + self.telescope = io.get_telescope(telescope) + self.telescope._init_trans(self.nside) + + # If not explicitly set, use the zenith as the reference declination + if self.reference_declination is None: + self.reference_declination = self.telescope.latitude + + self._omega_cache = {}
+ + + def _beam_area(self, feed, freq): + """Calculate the primary beam solid angle.""" + import healpy + + beam = self.telescope.beam(feed, freq) + horizon = self.telescope._horizon[:, np.newaxis] + beam_pow = np.sum(np.abs(beam) ** 2 * horizon, axis=1) + + pxarea = 4 * np.pi / beam.shape[0] + omega = beam_pow.sum() * pxarea + + # Normalise omega by the squared magnitude of the beam at the reference position + # NOTE: this is slightly less accurate than the previous approach of reseting + # the internal `_angpos` property to force evaluation of the beam at the exact + # coordinates, but is more generically applicable, and works (for instance) with + # the CHIMEExternalBeam class. + # + # Also, for a reason I don't fully understand it's more accurate to use the + # value of the pixel including the reference position, and not do an + # interpolation using it's neighbours... + beam_ref = beam_pow[ + healpy.ang2pix(self.nside, 0.0, self.reference_declination, lonlat=True) + ] + omega *= tools.invert_no_zero(beam_ref) + + return omega + +
+[docs] + def process(self, sstream: containers.SiderealStream) -> containers.SiderealStream: + """Apply the brightness temperature to flux conversion to the data. + + Parameters + ---------- + sstream + The visibilities to apply the conversion to. They are converted to/from + brightness temperature units depending on the setting of `convert_Jy_to_K`. + + Returns + ------- + new_sstream + Visibilities with the conversion applied. This may be the same as the input + container if `share == "all"`. + """ + import scipy.constants as c + + sstream.redistribute("freq") + + # Get the local frequencies in the sidereal stream + sfreq = sstream.vis.local_offset[0] + efreq = sfreq + sstream.vis.local_shape[0] + local_freq = sstream.freq[sfreq:efreq] + + # Get the indices of the incoming frequencies as far as the telescope class is + # concerned + local_freq_inds = [] + for freq in local_freq: + local_freq_inds.append(np.argmin(np.abs(self.telescope.frequencies - freq))) + + # Get the feedpairs we have data for and their beamclass (usually this maps to + # polarisation) + feedpairs = structured_to_unstructured(sstream.prodstack) + beamclass_pairs = self.telescope.beamclass[feedpairs] + + # Calculate all the unique beams that we need to calculate areas for + unique_beamclass, bc_index = np.unique(beamclass_pairs, return_index=True) + + # Calculate any missing beam areas and to the cache + for beamclass, bc_ind in zip(unique_beamclass, bc_index): + feed_ind = feedpairs.ravel()[bc_ind] + + for freq, freq_ind in zip(local_freq, local_freq_inds): + key = (beamclass, freq) + + if key not in self._omega_cache: + self._omega_cache[key] = self._beam_area(feed_ind, freq_ind) + + # Loop over all frequencies and visibilities and get the effective primary + # beam area for each + om_ij = np.zeros((len(local_freq), sstream.vis.shape[1])) + for fi, freq in enumerate(local_freq): + for bi, (bci, bcj) in enumerate(beamclass_pairs): + om_i = self._omega_cache[(bci, freq)] + om_j = self._omega_cache[(bcj, freq)] + om_ij[fi, bi] = (om_i * om_j) ** 0.5 + + # Calculate the Jy to K conversion + wavelength = (c.c / (local_freq * 10**6))[:, np.newaxis, np.newaxis] + K_to_Jy = 2 * 1e26 * c.k * om_ij[:, :, np.newaxis] / wavelength**2 + Jy_to_K = tools.invert_no_zero(K_to_Jy) + + # Get the container we will apply the conversion to (either the input, or a + # copy) + if self.share == "all": + new_stream = sstream + else: # self.share == "none" + new_stream = sstream.copy() + + # Apply the conversion to the data and the weights + if self.convert_Jy_to_K: + new_stream.vis[:] *= Jy_to_K + new_stream.weight[:] *= K_to_Jy**2 + else: + new_stream.vis[:] *= K_to_Jy + new_stream.weight[:] *= Jy_to_K**2 + + return new_stream
+
+ + + +
+[docs] +class MixData(task.SingleTask): + """Mix together pieces of data with specified weights. + + This can generate arbitrary linear combinations of the data and weights for both + `SiderealStream` and `RingMap` objects, and can be used for many purposes such as: + adding together simulated timestreams, injecting signal into data, replacing weights + in simulated data with those from real data, etc. + + All coefficients are applied naively to generate the final combinations, i.e. no + normalisations or weighted summation is performed. + + Attributes + ---------- + data_coeff : list + A list of coefficients to apply to the data dataset of each input containter to + produce the final output. These are applied to either the `vis` or `map` dataset + depending on the the type of the input container. + weight_coeff : list + Coefficient to be applied to each input containers weights to generate the + output. + """ + + data_coeff = config.list_type(type_=float) + weight_coeff = config.list_type(type_=float) + + mixed_data = None + +
+[docs] + def setup(self): + """Check the lists have the same length.""" + if len(self.data_coeff) != len(self.weight_coeff): + raise config.CaputConfigError( + "data and weight coefficient lists must be the same length" + ) + + self._data_ind = 0
+ + +
+[docs] + def process(self, data: Union[containers.SiderealStream, containers.RingMap]): + """Add the input data into the mixed data output. + + Parameters + ---------- + data + The data to be added into the mix. + """ + + def _get_dset(data): + # Helpful routine to get the data dset depending on the type + if isinstance(data, containers.SiderealStream): + return data.vis + + if isinstance(data, containers.RingMap): + return data.map + + return None + + if self._data_ind >= len(self.data_coeff): + raise RuntimeError( + "This task cannot accept more items than there are coefficents set." + ) + + if self.mixed_data is None: + self.mixed_data = containers.empty_like(data) + self.mixed_data.redistribute("freq") + + # Zero out data and weights + _get_dset(self.mixed_data)[:] = 0.0 + self.mixed_data.weight[:] = 0.0 + + # Validate the types are the same + if type(self.mixed_data) != type(data): + raise TypeError( + f"type(data) (={type(data)}) must match " + f"type(data_stack) (={type(self.type)}" + ) + + data.redistribute("freq") + + mixed_dset = _get_dset(self.mixed_data)[:] + data_dset = _get_dset(data)[:] + + # Validate the shapes match + if mixed_dset.shape != data_dset.shape: + raise ValueError( + f"Size of data ({data_dset.shape}) must match " + f"data_stack ({mixed_dset.shape})" + ) + + # Mix in the data and weights + mixed_dset[:] += self.data_coeff[self._data_ind] * data_dset[:] + self.mixed_data.weight[:] += self.weight_coeff[self._data_ind] * data.weight[:] + + self._data_ind += 1
+ + +
+[docs] + def process_finish(self) -> Union[containers.SiderealStream, containers.RingMap]: + """Return the container with the mixed inputs. + + Returns + ------- + mixed_data + The mixed data. + """ + if self._data_ind != len(self.data_coeff): + raise RuntimeError( + "Did not receive enough inputs. " + f"Got {self._data_ind}, expected {len(self.data_coeff)}." + ) + + # Get an ephemeral reference to the mixed data and remove the task reference so + # the object can be eventually deleted + data = self.mixed_data + self.mixed_data = None + + return data
+
+ + + +
+[docs] +class Downselect(io.SelectionsMixin, task.SingleTask): + """Apply axis selections to a container. + + Apply slice or `np.take` operations across multiple axes of a container. + The selections are applied to every dataset. + + If a dataset is distributed, there must be at least one axis not included + in the selections. + """ + +
+[docs] + def process(self, data: containers.ContainerBase) -> containers.ContainerBase: + """Apply downselections to the container. + + Parameters + ---------- + data + Container to process + + Returns + ------- + out + Container of same type as the input with specific axis selections. + Any datasets not included in the selections will not be initialized. + """ + # Re-format selections to only use axis name + for ax_sel in list(self._sel): + ax = ax_sel.replace("_sel", "") + self._sel[ax] = self._sel.pop(ax_sel) + + # Figure out the axes for the new container and + # Apply the downselections to each axis index_map + output_axes = { + ax: mpiarray._apply_sel(data.index_map[ax], sel, 0) + for ax, sel in self._sel.items() + } + # Create the output container without initializing any datasets. + out = data.__class__( + axes_from=data, attrs_from=data, skip_datasets=True, **output_axes + ) + containers.copy_datasets_filter(data, out, selection=self._sel) + + return out
+
+ + + +
+[docs] +class ReduceBase(task.SingleTask): + """Apply a weighted reduction operation across specific axes. + + This is non-functional without overriding the `reduction` method. + + There must be at least one axis not included in the reduction. + + Attributes + ---------- + axes : list + Axis names to apply the reduction to + dataset : str + Dataset name to reduce. + weighting : str + Which type of weighting to use, if applicable. Options are "none", + "masked", or "weighted" + """ + + axes = config.Property(proptype=list) + dataset = config.Property(proptype=str) + weighting = config.enum(["none", "masked", "weighted"], default="none") + + _op = None + +
+[docs] + def process(self, data: containers.ContainerBase) -> containers.ContainerBase: + """Downselect and apply the reduction operation to the data. + + Parameters + ---------- + data + Dataset to process. + + Returns + ------- + out + Dataset of same type as input with axes reduced. Any datasets + which are not included in the reduction list will not be initialized, + other than weights. + """ + out = self._make_output_container(data) + out.add_dataset(self.dataset) + + # Get the dataset + ds = data.datasets[self.dataset] + original_ax_id = ds.distributed_axis + + # Get the axis indices to apply the operation over + ds_axes = list(ds.attrs["axis"]) + + # Get the new axis to distribute over + if ds_axes[original_ax_id] not in self.axes: + new_ax_id = original_ax_id + else: + ax_priority = [ + x for _, x in sorted(zip(ds.shape, ds_axes)) if x not in self.axes + ] + if not ax_priority: + raise ValueError( + "Could not find a valid axis to redistribute. At least one " + "axis must be omitted from filtering." + ) + # Get the longest axis + new_ax_id = ds_axes.index(ax_priority[-1]) + + new_ax_name = ds_axes[new_ax_id] + + # Redistribute the dataset to the target axis + ds.redistribute(new_ax_id) + # Redistribute the output container (group) to the target axis + # Since this is a container, distribute based on axis name + # rather than index + out.redistribute(new_ax_name) + + # Get the weights + if hasattr(data, "weight"): + weight = data.weight[:] + # The weights should be distributed over the same axis as the array, + # even if they don't share all the same axes + new_weight_ax = list(data.weight.attrs["axis"]).index(new_ax_name) + weight = weight.redistribute(new_weight_ax) + else: + self.log.info("No weights available. Using equal weighting.") + weight = np.ones(ds.local_shape, ds.dtype) + + # Apply the reduction, ensuring that the weights have the correct dimension + weight = np.broadcast_to(weight, ds.local_shape, subok=False) + apply_over = tuple([ds_axes.index(ax) for ax in self.axes if ax in ds_axes]) + + reduced, reduced_weight = self.reduction( + ds[:].local_array[:], weight, apply_over + ) + + # Add the reduced data and redistribute the container back to the + # original axis + out[self.dataset][:] = reduced[:] + + if hasattr(out, "weight"): + out.weight[:] = reduced_weight[:] + + # Redistribute bcak to the original axis, again using the axis name + out.redistribute(ds_axes[original_ax_id]) + + return out
+ + + def _make_output_container( + self, data: containers.ContainerBase + ) -> containers.ContainerBase: + """Create the output container.""" + # For a collapsed axis, the meaning of the index map will depend on + # the reduction being done, and can be meaningless. The first value + # of the relevant index map is chosen as the default to provide + # some meaning to the index map regardless of the reduction operation + # or reduction axis involved + output_axes = {ax: np.array([data.index_map[ax][0]]) for ax in self.axes} + + # Create the output container without initializing any datasets. + # Add some extra metadata about which axes were reduced and which + # datasets are meaningful + out = data.__class__( + axes_from=data, attrs_from=data, skip_datasets=True, **output_axes + ) + out.attrs["reduced"] = True + out.attrs["reduction_axes"] = np.array(self.axes) + out.attrs["reduced_dataset"] = self.dataset + out.attrs["reduction_op"] = self._op + + # Initialize the weight dataset + if "weight" in data.datasets: + out.add_dataset("weight") + elif "vis_weight" in data.datasets: + out.add_dataset("vis_weight") + + return out + +
+[docs] + def reduction( + self, arr: np.ndarray, weight: np.ndarray, axis: tuple + ) -> Tuple[np.ndarray, np.ndarray]: + """Overwrite to implement the reductino operation.""" + raise NotImplementedError
+
+ + + +
+[docs] +class ReduceVar(ReduceBase): + """Take the weighted variance of a container.""" + + _op = "variance" + +
+[docs] + def reduction(self, arr, weight, axis): + """Apply a weighted variance.""" + if self.weighting == "none": + v = np.var(arr, axis=axis, keepdims=True) + + return v, np.ones_like(v) + + if self.weighting == "masked": + weight = (weight > 0).astype(weight.dtype) + + # Calculate the inverted sum of the weights. This is used + # more than once + ws = invert_no_zero(np.sum(weight, axis=axis, keepdims=True)) + # Get the weighted mean + mu = np.sum(weight * arr, axis=axis, keepdims=True) * ws + # Get the weighted variance + v = np.sum(weight * (arr - mu) ** 2, axis=axis, keepdims=True) * ws + + return v, np.ones_like(v)
+
+ + + +
+[docs] +class HPFTimeStream(task.SingleTask): + """High pass filter a timestream. + + This is done by solving for a low-pass filtered version of the timestream and then + subtracting it from the original. + + Parameters + ---------- + tau + Timescale in seconds to filter out fluctuations below. + pad + Implicitly pad the timestream with this many multiples of tau worth of zeros. + This is used to mitigate edge effects. The default is 2. + window + Use a Blackman window when determining the low-pass filtered timestream. When + applied this approximately doubles the length of the timescale, which is only + crudely corrected for. + prior + This should be approximately the size of the large scale fluctuations that we + will use as a regulariser. + """ + + tau = config.Property(proptype=float) + pad = config.Property(proptype=float, default=2) + window = config.Property(proptype=bool, default=True) + + prior = config.Property(proptype=float, default=1e2) + +
+[docs] + def process(self, tstream: containers.TODContainer) -> containers.TODContainer: + """High pass filter a time stream. + + Parameters + ---------- + tstream + A TOD container that also implements DataWeightContainer. + + Returns + ------- + filtered_tstream + The high-pass filtered time stream. + """ + if not isinstance(tstream, containers.DataWeightContainer): + # NOTE: no python intersection type so need to do this for now + raise TypeError("Need a DataWeightContainers") + + if "time" != tstream.data.attrs["axis"][-1]: + raise TypeError("'time' is not the last axis of the dataset.") + + if tstream.data.shape != tstream.weight.shape: + raise ValueError("Data and weights must have the same shape.") + + # Distribute over the first axis + tstream.redistribute(tstream.data.attrs["axis"][0]) + + tau = 2 * self.tau if self.window else self.tau + + dt = np.diff(tstream.time) + if not np.allclose(dt, dt[0], atol=1e-4): + self.log.warn( + "Samples are not regularly spaced. This might not work super well." + ) + + total_T = tstream.time[-1] - tstream.time[0] + 2 * tau + + # Calculate the nearest integer multiple of modes based on the total length and + # the timescale + nmodes = int(np.ceil(total_T / tau)) + + # Calculate the conjugate fourier frequencies to use, we don't need to be in the + # canonical order as we're going to calculate this exactly via matrices + t_freq = np.arange(-nmodes, nmodes) / total_T + + F = np.exp(2.0j * np.pi * tstream.time[:, np.newaxis] * t_freq[np.newaxis, :]) + + if self.window: + F *= np.blackman(2 * nmodes)[np.newaxis, :] + + Fh = F.T.conj().copy() + + dflat = tstream.data[:].view(np.ndarray).reshape(-1, len(tstream.time)) + wflat = tstream.weight[:].view(np.ndarray).reshape(-1, len(tstream.time)) + + Si = np.identity(2 * nmodes) * self.prior**-2 + + for ii in range(dflat.shape[0]): + d, w = dflat[ii], wflat[ii] + + wsum = w.sum() + if wsum == 0: + continue + + m = np.sum(d * w) / wsum + + # dirty = Fh @ ((d - m) * w) + # Ci = Fh @ (w[:, np.newaxis] * F) + d -= m + dirty = np.dot(Fh, (d * w)) + Ci = np.dot(Fh, w[:, np.newaxis] * F) + Ci += Si + + f_lpf = la.solve(Ci, dirty, assume_a="pos") + + # As we know the result will be real, split up the matrix multiplication to + # guarantee this + t_lpf = np.dot(F.real, f_lpf.real) - np.dot(F.imag, f_lpf.imag) + d -= t_lpf + + return tstream
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/core/containers.html b/docs/_modules/draco/core/containers.html new file mode 100644 index 000000000..c3b3700e6 --- /dev/null +++ b/docs/_modules/draco/core/containers.html @@ -0,0 +1,3300 @@ + + + + + + draco.core.containers — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.core.containers

+"""Distributed containers for holding various types of analysis data.
+
+Containers
+==========
+- :py:class:`Map`
+- :py:class:`SiderealStream`
+- :py:class:`SystemSensitivity`
+- :py:class:`RFIMask`
+- :py:class:`TimeStream`
+- :py:class:`GridBeam`
+- :py:class:`TrackBeam`
+- :py:class:`MModes`
+- :py:class:`SVDModes`
+- :py:class:`KLModes`
+- :py:class:`VisGridStream`
+- :py:class:`HybridVisStream`
+- :py:class:`HybridVisMModes`
+- :py:class:`RingMap`
+- :py:class:`RingMapMask`
+- :py:class:`CommonModeGainData`
+- :py:class:`CommonModeSiderealGainData`
+- :py:class:`GainData`
+- :py:class:`SiderealGainData`
+- :py:class:`StaticGainData`
+- :py:class:`DelayCutoff`
+- :py:class:`DelaySpectrum`
+- :py:class:`Powerspectrum2D`
+- :py:class:`SVDSpectrum`
+- :py:class:`FrequencyStack`
+- :py:class:`FrequencyStackByPol`
+- :py:class:`MockFrequencyStack`
+- :py:class:`MockFrequencyStackByPol`
+- :py:class:`SourceCatalog`
+- :py:class:`SpectroscopicCatalog`
+- :py:class:`FormedBeam`
+- :py:class:`FormedBeamHA`
+- :py:class:`FormedBeamMask`
+- :py:class:`FormedBeamHAMask`
+
+Container Base Classes
+----------------------
+- :py:class:`ContainerBase`
+- :py:class:`TableBase`
+- :py:class:`TODContainer`
+- :py:class:`VisContainer`
+- :py:class:`SampleVarianceContainer`
+- :py:class:`FreqContainer`
+- :py:class:`SiderealContainer`
+- :py:class:`MContainer`
+
+Helper Routines
+---------------
+These routines are designed to be replaced by other packages trying to insert
+their own custom container types.
+
+- :py:meth:`empty_like`
+- :py:meth:`empty_timestream`
+"""
+
+import inspect
+from typing import ClassVar, List, Optional, Union
+
+import numpy as np
+from caput import memh5, mpiarray, tod
+
+from ..util import tools
+
+# Try to import bitshuffle to set the default compression options
+try:
+    import bitshuffle.h5
+
+    COMPRESSION = bitshuffle.h5.H5FILTER
+    COMPRESSION_OPTS = (0, bitshuffle.h5.H5_COMPRESS_LZ4)
+except ImportError:
+    COMPRESSION = None
+    COMPRESSION_OPTS = None
+
+
+
+[docs] +class ContainerBase(memh5.BasicCont): + """A base class for pipeline containers. + + This class is designed to do much of the work of setting up pipeline + containers. It should be derived from, and two variables set `_axes` and + `_dataset_spec`. See the :ref:`Notes <containerbase_notes>` section for details. + + Parameters + ---------- + data_group : `memh5.MemDiskGroup` + A container to pass through for making a shallow copy. This is used by + routine like `caput.tod.concatenate` and generally shouldn't be used + directly. Either a keyword argument, or the first positional argument. + axes_from : `memh5.BasicCont`, optional + Another container to copy axis definitions from. Must be supplied as + keyword argument. + attrs_from : `memh5.BasicCont`, optional + Another container to copy attributes from. Must be supplied as keyword + argument. This applies to attributes in default datasets too. + dsets_from : `memh5.BasicCont`, optional + A container to copy datasets from. Any dataset which an axis whose definition + has been explicitly set (i.e. does not come from `axes_from`) will not be + copied. + copy_from : `memh5.BasicCont`, optional + Set `axes_from`, `attrs_from` and `dsets_from` to this instance if they are + not set explicitly. + skip_datasets : bool, optional + Skip creating datasets. They must all be added manually with + `.add_dataset` regardless of the entry in `.dataset_spec`. Default is False. + distributed : bool, optional + Should this be a distributed container. Defaults to True. + comm : mpi4py.MPI.Comm, optional + The MPI communicator to distribute over. Use COMM_WORLD if not set. + allow_chunked : bool, optional + Allow the datasets to be chunked. Default is True. + + kwargs : dict + Should contain entries for all other axes. + + Notes + ----- + .. _containerbase_notes: + + Inheritance from other `ContainerBase` subclasses should work as expected, + with datasets defined in super classes appearing as expected, and being + overridden where they are redefined in the derived class. + + The variable `_axes` should be a tuple containing the names of axes that + datasets in this container will use. + + The variable `_dataset_spec` should define the datasets. It's a dictionary + with the name of the dataset as key. Each entry should be another + dictionary, the entry 'axes' is mandatory and should be a list of the axes + the dataset has (these should correspond to entries in `_axes`), as is + `dtype` which should be a datatype understood by numpy. Other possible + entries are: + + - `initialise` : if set to `True` the dataset will be created as the + container is initialised. + + - `distributed` : the dataset will be distributed if the entry is `True`, if + `False` it won't be, and if not set it will be distributed if the + container is set to be. + + - `distributed_axis` : the axis to distribute over. Should be a name given + in the `axes` entry. + """ + + _axes = () + + _dataset_spec: ClassVar = {} + + convert_attribute_strings = True + convert_dataset_strings = True + + def __init__(self, *args, **kwargs): + # Arguments for pulling in definitions from other containers + copy_from = kwargs.pop("copy_from", None) + axes_from = kwargs.pop("axes_from", copy_from) + attrs_from = kwargs.pop("attrs_from", copy_from) + dsets_from = kwargs.pop("dsets_from", copy_from) + + # MPI distribution arguments + dist = kwargs.pop("distributed", True) + comm = kwargs.pop("comm", None) + + # Extract misc options + self.allow_chunked = kwargs.pop("allow_chunked", True) + skip_datasets = kwargs.pop("skip_datasets", False) + + # Handle the data_group argument. We need to identify if the argument + # was actually supplied or not (both as a positional or keyword + # argument), and infer what its value should be, or None if not + # provided + if args and "data_group" in kwargs: + raise ValueError( + "Received conflicting definitions of `data_group`, as both the first " + "positional and a keyword argument." + ) + has_data_group = args or ("data_group" in kwargs) + data_group = args[0] if args else kwargs.get("data_group", None) + + # Run base initialiser, and exit early if data_group was provided + super().__init__(data_group=data_group, distributed=dist, comm=comm) + + # If data_group was provided we need to exit early to behave like + # memh5.MemDiskGroup would have. In this case we're probably trying to + # create a bare container, or a shallow clone, so don't initialise any + # datasets. This behaviour is needed to support tod.concatenate + if has_data_group: + return + + # Create axis entries + for axis in self.axes: + axis_map = None + + # Check if axis is specified in initialiser + if axis in kwargs: + axis_map = kwargs[axis] + copy_axis_attrs = False + + # If axis is an integer, turn into an arange as a default definition + if isinstance(axis_map, (int, np.integer)): + axis_map = np.arange(axis_map) + + # If no valid map provided in arguments copy from another object if set + elif axes_from is not None: + axis_map = axes_from.index_map.get(axis, None) + copy_axis_attrs = True + + # Set the index_map[axis] if we have a definition, otherwise throw an error + if axis_map is None: + raise RuntimeError(f"No definition of axis {axis} supplied.") + + self.create_index_map(axis, axis_map) + + if copy_axis_attrs: + # Copy over axis attributes if we're copying the axis from another dataset + memh5.copyattrs(axes_from.index_attrs[axis], self.index_attrs[axis]) + + # Iterate over datasets and initialise any that specify it + if not skip_datasets: + for name, spec in self.dataset_spec.items(): + if spec.get("initialise"): + self.add_dataset(name) + + # Copy over datasets that have compatible axes + if dsets_from is not None: + # Get the list of axes names that have been overriden + changed_axes = {ax for ax in self.axes if ax in kwargs} + + for name in self.dataset_spec.keys(): + if name not in dsets_from: + continue + + source_dset = dsets_from[name] + source_axes = set(source_dset.attrs["axis"]) + + # Check if any of the axes of this dataset have been changed, if that's + # the case then we can't copy the data over + if not source_axes.isdisjoint(changed_axes): + continue + + # The dataset may not have been initialised by default, if not, create + # it + if name not in self: + self.add_dataset(name) + + self[name][:] = source_dset[:] + + # Copy over attributes + if attrs_from is not None: + # Copy attributes from container root + memh5.copyattrs(attrs_from.attrs, self.attrs) + + # Copy attributes over from any common datasets + for name in self.dataset_spec.keys(): + if name in self.datasets and name in attrs_from.datasets: + attrs_no_axis = { + k: v + for k, v in attrs_from.datasets[name].attrs.items() + if k != "axis" + } + memh5.copyattrs(attrs_no_axis, self.datasets[name].attrs) + + # Make sure that the __memh5_subclass attribute is accurate + clspath = self.__class__.__module__ + "." + self.__class__.__name__ + clsattr = self.attrs.get("__memh5_subclass", None) + if clsattr and (clsattr != clspath): + self.attrs["__memh5_subclass"] = clspath + +
+[docs] + def add_dataset(self, name): + """Create an empty dataset. + + The dataset must be defined in the specification for the container. + + Parameters + ---------- + name : string + Name of the dataset to create. + + Returns + ------- + dset : `memh5.MemDataset` + """ + # Normalise name + name = name.strip("/") + + # Dataset must be specified + if name not in self.dataset_spec: + raise RuntimeError(f"Dataset {name} not known.") + + dspec = self.dataset_spec[name] + + # Fetch dataset properties + axes = dspec["axes"] + dtype = dspec["dtype"] + chunks, compression, compression_opts = None, None, None + if self.allow_chunked: + chunks = dspec.get("chunks", None) + compression = dspec.get("compression", None) + compression_opts = dspec.get("compression_opts", None) + + # Get distribution properties + dist = self.distributed and dspec.get("distributed", True) + shape = () + + # Check that all the specified axes are defined, and fetch their lengths + for axis in axes: + if axis not in self.index_map: + if isinstance(axis, int): + l = axis + else: + raise RuntimeError(f"Axis {axis} not defined in index_map") + else: + l = len(self.index_map[axis]) + + shape += (l,) + + # Fetch distributed axis, and turn into axis index + dist_axis = ( + dspec["distributed_axis"] if "distributed_axis" in dspec else axes[0] + ) + dist_axis = list(axes).index(dist_axis) + + # Check chunk dimensions are consistent with axis + if chunks is not None: + final_chunks = () + for i, l in enumerate(shape): + final_chunks += (min(chunks[i], l),) + chunks = final_chunks + + # Create dataset + dset = self.create_dataset( + name, + shape=shape, + dtype=dtype, + distributed=dist, + distributed_axis=dist_axis, + chunks=chunks, + compression=compression, + compression_opts=compression_opts, + ) + + dset.attrs["axis"] = np.array(axes) + + return dset
+ + + def _ensure_chunked(self): + """Ensure datasets that have chunk/compression specs are chunked. + + For every dataset, check if chunks and compression are set, and + if not set them to dataset_spec values. + """ + for dset in self.dataset_spec: + if dset not in self: + continue + if "chunks" in self.dataset_spec[dset] and self[dset].chunks is None: + # ensure chunks aren't larger than dataset shape + chunks = () + for i, l in enumerate(self[dset].shape): + chunks += (min(self.dataset_spec[dset]["chunks"][i], l),) + self._data._storage_root[dset].chunks = chunks + if ( + "compression" in self.dataset_spec[dset] + and self[dset].compression is None + ): + self._data._storage_root[dset].compression = self.dataset_spec[dset][ + "compression" + ] + if ( + "compression_opts" in self.dataset_spec[dset] + and self[dset].compression_opts is None + ): + self._data._storage_root[dset].compression_opts = self.dataset_spec[ + dset + ]["compression_opts"] + + @property + def datasets(self): + """Return the datasets in this container. + + Do not try to add a new dataset by assigning to an item of this + property. Use `create_dataset` instead. + + Returns + ------- + datasets : read only dictionary + Entries are :mod:`caput.memh5` datasets. + + """ + out = {} + for name, value in self._data.items(): + if not memh5.is_group(value): + out[name] = value + return memh5.ro_dict(out) + + @classmethod + def _class_dataset_spec(cls): + """Get the inherited set of dataset spec entries.""" + ddict = {} + + # Iterate over the reversed MRO and look for _dataset_spec attributes + # which get added to a temporary dict. We go over the reversed MRO so + # that the `ddict.update` overrides datasets in base classes.` + for cls in inspect.getmro(cls)[::-1]: + try: + # NOTE: this is a little ugly as the following line will drop + # down to base classes if dataset_spec isn't present, and thus + # try and `update` with the same values again. + ddict.update(cls._dataset_spec) + except AttributeError: + pass + + # Ensure that the dataset_spec is the same order on all ranks + return {k: ddict[k] for k in sorted(ddict)} + + @property + def dataset_spec(self): + """Return a copy of the fully resolved dataset specifiction as a dictionary.""" + ddict = self.__class__._class_dataset_spec() + + # Add in any _dataset_spec found on the instance + ddict.update(self.__dict__.get("_dataset_spec", {})) + + # Ensure that the dataset_spec is the same order on all ranks + return {k: ddict[k] for k in sorted(ddict)} + + @classmethod + def _class_axes(cls): + """Get the set of axes defined by the container and it's base classes.""" + axes = set() + + # Iterate over the reversed MRO and look for _table_spec attributes + # which get added to a temporary dict. We go over the reversed MRO so + # that the `tdict.update` overrides tables in base classes. + for c in inspect.getmro(cls)[::-1]: + try: + axes |= set(c._axes) + except AttributeError: + pass + + # This must be the same order on all ranks, so we need to explicitly sort to get around the + # hash randomization + return tuple(sorted(axes)) + + @property + def axes(self): + """The set of axes for this container including any defined on the instance.""" + axes = set(self._class_axes()) + + # Add in any axes found on the instance (this is needed to support the table + # classes where the axes get added at run time) + axes |= set(self.__dict__.get("_axes", [])) + + # This must be the same order on all ranks, so we need to explicitly sort to + # get around the hash randomization + return tuple(sorted(axes)) + + @classmethod + def _make_selections(cls, sel_args): + """Match down-selection arguments to axes of datasets. + + Parses sel_* argument and returns dict mapping dataset names to selections. + + Parameters + ---------- + sel_args : dict + Should contain valid numpy indexes as values and axis names (str) as keys. + + Returns + ------- + dict + Mapping of dataset names to numpy indexes for downselection of the data. + Also includes another dict under the key "index_map" that includes + the selections for those. + """ + # Check if all those axes exist + for axis in sel_args.keys(): + if axis not in cls._class_axes(): + raise RuntimeError(f"No '{axis}' axis found to select from.") + + # Build selections dict + selections = {} + for name, dataset in cls._class_dataset_spec().items(): + ds_axes = dataset["axes"] + sel = [] + ds_relevant = False + for axis in ds_axes: + if axis in sel_args: + sel.append(sel_args[axis]) + ds_relevant = True + else: + sel.append(slice(None)) + if ds_relevant: + selections["/" + name] = tuple(sel) + + # add index maps selections + for axis, sel in sel_args.items(): + selections["/index_map/" + axis] = sel + + return selections + +
+[docs] + def copy(self, shared=None): + """Copy this container, optionally sharing the source datasets. + + This routine will create a copy of the container. By default this is + as full copy with the contents fully independent. However, a set of + dataset names can be given that will share the same data as the + source to save memory for large datasets. These will just view the + same memory, so any modification to either the original or the copy + will be visible to the other. This includes all write operations, + addition and removal of attributes, redistribution etc. This + functionality should be used with caution and clearly documented. + + Parameters + ---------- + shared : list, optional + A list of datasets whose content will be shared with the original. + + Returns + ------- + copy : subclass of ContainerBase + The copied container. + """ + new_cont = self.__class__( + attrs_from=self, + axes_from=self, + skip_datasets=True, + distributed=self.distributed, + comm=self.comm, + ) + + # Loop over datasets that exist in the source and either add a view of + # the source dataset, or perform a full copy + for name, data in self.datasets.items(): + if shared and name in shared: + # TODO: find a way to do this that doesn't depend on the + # internal implementation of BasicCont and MemGroup + # NOTE: we don't use `.view()` on the RHS here as we want to + # preserve the shared data through redistributions + new_cont._data._get_storage()[name] = self._data._get_storage()[name] + else: + dset = new_cont.add_dataset(name) + + # Ensure that we have exactly the same distribution + if dset.distributed: + dset.redistribute(data.distributed_axis) + + # Copy over the data and attributes + dset[:] = data[:] + memh5.copyattrs(data.attrs, dset.attrs) + # TODO Is there a case where these properties don't exist? + dset.chunks = data.chunks + dset.compression = data.compression + dset.compression_opts = data.compression_opts + + return new_cont
+
+ + + +
+[docs] +class TableBase(ContainerBase): + """A base class for containers holding tables of data. + + Similar to the `ContainerBase` class, the container is defined through a + dictionary given as a `_table_spec` class attribute. The container may also + hold generic datasets by specifying `_dataset_spec` as with `ContainerBase`. + See :ref:`Notes <tablebase_notes>` for details. + + Parameters + ---------- + axes_from : `memh5.BasicCont`, optional + Another container to copy axis definitions from. Must be supplied as + keyword argument. + attrs_from : `memh5.BasicCont`, optional + Another container to copy attributes from. Must be supplied as keyword + argument. This applies to attributes in default datasets too. + kwargs : dict + Should contain definitions for all other table axes. + + Notes + ----- + .. _tablebase_notes: + + A `_table_spec` consists of a dictionary mapping table names into a + description of the table. That description is another dictionary containing + several entries. + + - `columns` : the set of columns in the table. Given as a list of + `(name, dtype)` pairs. + + - `axis` : an optional name for the rows of the table. This is automatically + generated as `'<tablename>_index'` if not explicitly set. This corresponds + to an `index_map` entry on the container. + + - `initialise` : whether to create the table by default. + + - `distributed` : whether the table is distributed, or common across all MPI ranks. + + An example `_table_spec` entry is:: + + _table_spec = { + 'quasars': { + 'columns': [ + ['ra': np.float64], + ['dec': np.float64], + ['z': np.float64] + ], + 'distributed': False, + 'axis': 'quasar_id' + } + 'quasar_mask': { + 'columns': [ + ['mask', bool] + ], + 'axis': 'quasar_id' + } + } + """ + + _table_spec: ClassVar = {} + + def __init__(self, *args, **kwargs): + # Get the dataset specifiction for this class (not any base classes), or + # an empty dictionary if it does not exist. Do the same for the axes entry.. + dspec = self.__class__.__dict__.get("_dataset_spec", {}) + axes = self.__class__.__dict__.get("_axes", ()) + + # Iterate over all table_spec entries and construct dataset specifications for + # them. + for name, spec in self.table_spec.items(): + # Get the specifieid axis or if not present create a unique one for + # this table entry + axis = spec.get("axis", name + "_index") + + dtype = self._create_dtype(spec["columns"]) + + _dataset = { + "axes": [axis], + "dtype": dtype, + "initialise": spec.get("initialise", True), + "distributed": spec.get("distributed", False), + "distributed_axis": axis, + } + + dspec[name] = _dataset + + if axis not in axes: + axes += (axis,) + + self._dataset_spec = dspec + self._axes = axes + + super().__init__(*args, **kwargs) + + def _create_dtype(self, columns): + """Take a dictionary of columns and turn into the appropriate compound data type.""" + dt = [] + for ci, (name, dtype) in enumerate(columns): + if not isinstance(name, str): + raise ValueError("Column %i is invalid" % ci) + dt.append((name, dtype)) + + return dt + + @property + def table_spec(self): + """Return a copy of the fully resolved table specifiction as a dictionary.""" + import inspect + + tdict = {} + + for cls in inspect.getmro(self.__class__)[::-1]: + try: + tdict.update(cls._table_spec) + except AttributeError: + pass + + return tdict
+ + + +
+[docs] +class TODContainer(ContainerBase, tod.TOData): + """A pipeline container for time ordered data. + + This works like a normal :class:`ContainerBase` container, with the added + ability to be concatenated, and treated like a a :class:`tod.TOData` + instance. + """ + + _axes = ("time",)
+ + + +
+[docs] +class DataWeightContainer(ContainerBase): + """A base class for containers with generic data/weight datasets. + + This is meant such that tasks can operate generically over containers with this + common structure. The data and weight datasets are expected to have the same size, + though this isn't checked. Subclasses must define `_data_dset_name` and + `_weight_dset_name`. + """ + + _data_dset_name: Optional[str] = None + _weight_dset_name: Optional[str] = None + + @property + def data(self) -> memh5.MemDataset: + """The main dataset.""" + if self._data_dset_name is None: + raise RuntimeError(f"Type {type(self)} has not defined `_data_dset_name`.") + + dset = self[self._data_dset_name] + + if not isinstance(dset, memh5.MemDataset): + raise TypeError(f"/{self._data_dset_name} is not a dataset") + + return dset + + @property + def weight(self) -> memh5.MemDataset: + """The weights for each data point.""" + if not self._weight_dset_name: + raise RuntimeError( + f"Type {type(self)} has not defined `_weight_dset_name`." + ) + + dset = self[self._weight_dset_name] + + if not isinstance(dset, memh5.MemDataset): + raise TypeError(f"/{self._weight_dset_name} is not a dataset") + + return dset
+ + + +
+[docs] +class VisBase(DataWeightContainer): + """A very basic class for visibility data. + + For better support for input/prod/stack structured data use `VisContainer`. + """ + + _data_dset_name = "vis" + _weight_dset_name = "vis_weight" + + @property + def vis(self): + """The visibility like dataset.""" + return self.datasets["vis"]
+ + + +
+[docs] +class VisContainer(VisBase): + """A base container for holding a visibility dataset. + + This works like a :class:`ContainerBase` container, with the + ability to create visibility specific axes, if they are not + passed as a kwargs parameter. + + Additionally this container has visibility specific defined properties + such as 'vis', 'weight', 'freq', 'input', 'prod', 'stack', + 'prodstack', 'conjugate'. + + Parameters + ---------- + axes_from : `memh5.BasicCont`, optional + Another container to copy axis definitions from. Must be supplied as + keyword argument. + attrs_from : `memh5.BasicCont`, optional + Another container to copy attributes from. Must be supplied as keyword + argument. This applies to attributes in default datasets too. + kwargs : dict + Should contain entries for all other axes. + """ + + _axes = ("input", "prod", "stack") + + def __init__(self, *args, **kwargs): + # Resolve product map + prod = None + if "prod" in kwargs: + prod = kwargs["prod"] + elif ("axes_from" in kwargs) and ("prod" in kwargs["axes_from"].index_map): + prod = kwargs["axes_from"].index_map["prod"] + + # Resolve input map + inputs = None + if "input" in kwargs: + inputs = kwargs["input"] + elif ("axes_from" in kwargs) and ("input" in kwargs["axes_from"].index_map): + inputs = kwargs["axes_from"].index_map["input"] + + # Resolve stack map + stack = None + if "stack" in kwargs: + stack = kwargs["stack"] + elif ("axes_from" in kwargs) and ("stack" in kwargs["axes_from"].index_map): + stack = kwargs["axes_from"].index_map["stack"] + + # Automatically construct product map from inputs if not given + if prod is None and inputs is not None: + nfeed = inputs if isinstance(inputs, int) else len(inputs) + kwargs["prod"] = np.array( + [[fi, fj] for fi in range(nfeed) for fj in range(fi, nfeed)] + ) + + if stack is None and prod is not None: + stack = np.empty_like(prod, dtype=[("prod", "<u4"), ("conjugate", "u1")]) + stack["prod"][:] = np.arange(len(prod)) + stack["conjugate"] = 0 + kwargs["stack"] = stack + + # Call initializer from `ContainerBase` + super().__init__(*args, **kwargs) + + reverse_map_stack = None + # Create reverse map + if "reverse_map_stack" in kwargs: + # If axis is an integer, turn into an arange as a default definition + if isinstance(kwargs["reverse_map_stack"], int): + reverse_map_stack = np.arange(kwargs["reverse_map_stack"]) + else: + reverse_map_stack = kwargs["reverse_map_stack"] + # If not set in the arguments copy from another object if set + elif ("axes_from" in kwargs) and ("stack" in kwargs["axes_from"].reverse_map): + reverse_map_stack = kwargs["axes_from"].reverse_map["stack"] + + # Set the reverse_map['stack'] if we have a definition, + # otherwise do NOT throw an error, errors are thrown in + # classes that actually need a reverse stack + if reverse_map_stack is not None: + self.create_reverse_map("stack", reverse_map_stack) + + @property + def input(self): + """The correlated inputs.""" + return self.index_map["input"] + + @property + def prod(self): + """All the pairwise products that are represented in the data.""" + return self.index_map["prod"] + + @property + def stack(self): + """The stacks definition as an index (and conjugation) of a member product.""" + return self.index_map["stack"] + + @property + def prodstack(self): + """A pair of input indices representative of those in the stack. + + Note, these are correctly conjugated on return, and so calculations + of the baseline and polarisation can be done without additionally + looking up the stack conjugation. + """ + if not self.is_stacked: + return self.prod + + t = self.index_map["prod"][:][self.index_map["stack"]["prod"]] + + prodmap = t.copy() + conj = self.stack["conjugate"] + prodmap["input_a"] = np.where(conj, t["input_b"], t["input_a"]) + prodmap["input_b"] = np.where(conj, t["input_a"], t["input_b"]) + + return prodmap + + @property + def is_stacked(self): + """Test if the data has been stacked or not.""" + return len(self.stack) != len(self.prod)
+ + + +
+[docs] +class SampleVarianceContainer(ContainerBase): + """Base container for holding the sample variance over observations. + + This works like :class:`ContainerBase` but provides additional capabilities + for containers that may be used to hold the sample mean and variance over + complex-valued observations. These capabilities include automatic definition + of the component axis, properties for accessing standard datasets, properties + that rotate the sample variance into common bases, and a `sample_weight` property + that provides an equivalent to the `weight` dataset that is determined from the + sample variance over observations. + + Subclasses must include a `sample_variance` and `nsample` dataset + in there `_dataset_spec` dictionary. They must also specify a + `_mean` property that returns the dataset containing the mean over observations. + """ + + _axes = ("component",) + + def __init__(self, *args, **kwargs): + # Set component axis to default real-imaginary basis if not already provided + if "component" not in kwargs: + kwargs["component"] = np.array( + [("real", "real"), ("real", "imag"), ("imag", "imag")], + dtype=[("component_a", "<U8"), ("component_b", "<U8")], + ) + + super().__init__(*args, **kwargs) + + @property + def component(self): + """Get the component axis.""" + return self.index_map["component"] + + @property + def sample_variance(self): + """Convenience access to the sample variance dataset. + + Returns + ------- + C: np.ndarray[ncomponent, ...] + The variance over the dimension that was stacked + (e.g., sidereal days, holographic observations) + in the default real-imaginary basis. The array is packed + into upper-triangle format such that the component axis + contains [('real', 'real'), ('real', 'imag'), ('imag', 'imag')]. + """ + if "sample_variance" in self.datasets: + return self.datasets["sample_variance"] + + raise KeyError("Dataset 'sample_variance' not initialised.") + + @property + def sample_variance_iq(self): + """Rotate the sample variance to the in-phase/quadrature basis. + + Returns + ------- + C: np.ndarray[ncomponent, ...] + The `sample_variance` dataset in the in-phase/quadrature basis, + packed into upper triangle format such that the component axis + contains [('I', 'I'), ('I', 'Q'), ('Q', 'Q')]. + """ + C = self.sample_variance[:].view(np.ndarray) + + # Construct rotation coefficients from average vis angle + phi = np.angle(self._mean[:].view(np.ndarray)) + cc = np.cos(phi) ** 2 + cs = np.cos(phi) * np.sin(phi) + ss = np.sin(phi) ** 2 + + # Rotate the covariance matrix from real-imag to in-phase/quadrature + Cphi = np.zeros_like(C) + Cphi[0] = cc * C[0] + 2 * cs * C[1] + ss * C[2] + Cphi[1] = -cs * C[0] + (cc - ss) * C[1] + cs * C[2] + Cphi[2] = ss * C[0] - 2 * cs * C[1] + cc * C[2] + + return Cphi + + @property + def sample_variance_amp_phase(self): + """Calculate the amplitude/phase covariance. + + This interpretation is only valid if the fractional + variations in the amplitude and phase are small. + + Returns + ------- + C: np.ndarray[ncomponent, ...] + The observed amplitude/phase covariance matrix, packed + into upper triangle format such that the component axis + contains [('amp', 'amp'), ('amp', 'phase'), ('phase', 'phase')]. + """ + # Rotate to in-phase/quadrature basis and then + # normalize by squared amplitude to convert to + # fractional units (amplitude) and radians (phase). + return self.sample_variance_iq * tools.invert_no_zero( + np.abs(self._mean[:][np.newaxis, ...]) ** 2 + ) + + @property + def nsample(self): + """Get the nsample dataset if it exists.""" + if "nsample" in self.datasets: + return self.datasets["nsample"] + + raise KeyError("Dataset 'nsample' not initialised.") + + @property + def sample_weight(self): + """Calculate a weight from the sample variance. + + Returns + ------- + weight: np.ndarray[...] + The trace of the `sample_variance` dataset is used + as an estimate of the total variance and divided by the + `nsample` dataset to yield the uncertainty on the mean. + The inverse of this quantity is returned, and can be compared + directly to the `weight` dataset. + """ + C = self.sample_variance[:].view(np.ndarray) + nsample = self.nsample[:].view(np.ndarray) + + return nsample * tools.invert_no_zero(C[0] + C[2])
+ + + +
+[docs] +class FreqContainer(ContainerBase): + """A pipeline container for data with a frequency axis. + + This works like a normal :class:`ContainerBase` container, but already has a freq + axis defined, and specific properties for dealing with frequencies. + """ + + _axes = ("freq",) + + @property + def freq(self): + """The physical frequency associated with each entry of the time axis. + + By convention this property should return the frequency in MHz at the centre + of each of frequency channel. + """ + try: + return self.index_map["freq"][:]["centre"] + # Need to check for both types as different numpy versions return + # different exceptions. + except (IndexError, ValueError): + return self.index_map["freq"][:]
+ + + +
+[docs] +class SiderealContainer(ContainerBase): + """A pipeline container for data with an RA axis. + + This works like a normal :class:`ContainerBase` container, but already has an RA + axis defined, and specific properties for dealing with this axis. + + Note that Right Ascension is a fairly ambiguous term. What is typically meant + here is the Local Stellar Angle, which is the transiting RA in CIRS coordinates. + This is similar to J2000/ICRS with the minimal amount of coordinate rotation to + account for the polar axis precession. + + Parameters + ---------- + ra : array or int, optional + Either the explicit locations of samples of the RA axis, or if passed an + integer interpret this as a number of samples dividing the full sidereal day + and create an axis accordingly. + """ + + _axes = ("ra",) + + def __init__(self, ra=None, *args, **kwargs): + # Allow the passing of a number of samples for the RA axis + if ra is not None: + if isinstance(ra, int): + ra = np.linspace(0.0, 360.0, ra, endpoint=False) + kwargs["ra"] = ra + + super().__init__(*args, **kwargs) + + @property + def ra(self): + """The RA in degrees associated with each sample of the RA axis.""" + return self.index_map["ra"][:]
+ + + +
+[docs] +class MContainer(ContainerBase): + """Container for holding m-mode type data. + + Note this container will have an `msign` axis even though not all m-mode based + data needs one. As always this is not an issue, datasets that don't need it are + not required to list it in their `axes` list. + + Parameters + ---------- + mmax : integer, optional + Largest m to be held. + oddra : bool, optional + Does this MContainer come from an underlying odd number of RA points. This + determines if the largest negative m is filled or not (it is for odd=True, not + for odd=False). Default is odd=False. + """ + + _axes = ("m", "msign") + + def __init__( + self, mmax: Optional[int] = None, oddra: Optional[bool] = None, *args, **kwargs + ): + # Set up axes from passed arguments + if mmax is not None: + kwargs["m"] = mmax + 1 + + # Ensure the sign axis is set correctly + kwargs["msign"] = np.array(["+", "-"]) + + super().__init__(*args, **kwargs) + + # Set oddra, prioritising an explicit keyword argument over anything else + if oddra is not None: + self.attrs["oddra"] = oddra + elif "oddra" not in self.attrs: + self.attrs["oddra"] = False + + @property + def mmax(self) -> int: + """The maximum m stored.""" + return int(self.index_map["m"][-1]) + + @property + def oddra(self) -> bool: + """Whether this represents an odd or even number of RA points.""" + return self.attrs["oddra"]
+ + + +
+[docs] +class HealpixContainer(ContainerBase): + """Base class container for holding Healpix map data. + + Parameters + ---------- + nside : int + The nside of the Healpix maps. + """ + + _axes = ("pixel",) + + def __init__(self, nside=None, *args, **kwargs): + # Set up axes from passed arguments + if nside is not None: + kwargs["pixel"] = 12 * nside**2 + + super().__init__(*args, **kwargs) + + @property + def nside(self): + """Get the nside of the map.""" + return int((len(self.index_map["pixel"]) // 12) ** 0.5)
+ + + +
+[docs] +class Map(FreqContainer, HealpixContainer): + """Container for holding multi-frequency sky maps. + + The maps are packed in format `[freq, pol, pixel]` where the polarisations + are Stokes I, Q, U and V, and the pixel dimension stores a Healpix map. + + Parameters + ---------- + nside : int + The nside of the Healpix maps. + polarisation : bool, optional + If `True` all Stokes parameters are stored, if `False` only Stokes I is + stored. + """ + + _axes = ("pol",) + + _dataset_spec: ClassVar = { + "map": { + "axes": ["freq", "pol", "pixel"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + } + } + + def __init__(self, polarisation=True, *args, **kwargs): + # Set up axes from passed arguments + kwargs["pol"] = ( + np.array(["I", "Q", "U", "V"]) if polarisation else np.array(["I"]) + ) + + super().__init__(*args, **kwargs) + + @property + def map(self): + """Get the map dataset.""" + return self.datasets["map"]
+ + + +
+[docs] +class SiderealStream( + FreqContainer, VisContainer, SiderealContainer, SampleVarianceContainer +): + """A container for holding a visibility dataset in sidereal time. + + Parameters + ---------- + ra : int + The number of points to divide the RA axis up into. + """ + + _dataset_spec: ClassVar = { + "vis": { + "axes": ["freq", "stack", "ra"], + "dtype": np.complex64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (64, 128, 128), + "truncate": { + "weight_dataset": "vis_weight", + }, + }, + "vis_weight": { + "axes": ["freq", "stack", "ra"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (64, 128, 128), + "truncate": True, + }, + "input_flags": { + "axes": ["input", "ra"], + "dtype": np.float32, + "initialise": True, + "distributed": False, + }, + "gain": { + "axes": ["freq", "input", "ra"], + "dtype": np.complex64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + "sample_variance": { + "axes": ["component", "freq", "stack", "ra"], + "dtype": np.float32, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (3, 64, 128, 128), + "truncate": True, + }, + "nsample": { + "axes": ["freq", "stack", "ra"], + "dtype": np.uint16, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (64, 128, 128), + }, + } + + @property + def gain(self): + """Get the gain dataset.""" + return self.datasets["gain"] + + @property + def input_flags(self): + """Get the input_flags dataset.""" + return self.datasets["input_flags"] + + @property + def _mean(self): + """Get the vis dataset.""" + return self.datasets["vis"]
+ + + +
+[docs] +class SystemSensitivity(FreqContainer, TODContainer): + """A container for holding the total system sensitivity. + + This should be averaged/collapsed in the stack/prod axis + to provide an overall summary of the system sensitivity. + Two datasets are available: the measured noise from the + visibility weights and the radiometric estimate of the + noise from the autocorrelations. + """ + + _axes = ("pol",) + + _dataset_spec: ClassVar = { + "measured": { + "axes": ["freq", "pol", "time"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + }, + "radiometer": { + "axes": ["freq", "pol", "time"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + }, + "weight": { + "axes": ["freq", "pol", "time"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + }, + "frac_lost": { + "axes": ["freq", "time"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + }, + } + + @property + def measured(self): + """Get the measured noise dataset.""" + return self.datasets["measured"] + + @property + def radiometer(self): + """Get the radiometer estimate dataset.""" + return self.datasets["radiometer"] + + @property + def weight(self): + """Get the weight dataset.""" + return self.datasets["weight"] + + @property + def frac_lost(self): + """Get the frac_lost dataset.""" + return self.datasets["frac_lost"] + + @property + def pol(self): + """Get the pol axis.""" + return self.index_map["pol"]
+ + + +
+[docs] +class RFIMask(FreqContainer, TODContainer): + """A container for holding an RFI mask for a timestream. + + The mask is `True` for contaminated samples that should be excluded, and + `False` for clean samples. + """ + + _dataset_spec: ClassVar = { + "mask": { + "axes": ["freq", "time"], + "dtype": bool, + "initialise": True, + "distributed": False, + "distributed_axis": "freq", + } + } + + @property + def mask(self): + """Get the mask dataset.""" + return self.datasets["mask"]
+ + + +
+[docs] +class SiderealRFIMask(FreqContainer, SiderealContainer): + """A container for holding an RFI mask for a sidereal stream. + + The mask is `True` for contaminated samples that should be excluded, and + `False` for clean samples. + """ + + _dataset_spec: ClassVar = { + "mask": { + "axes": ["freq", "ra"], + "dtype": bool, + "initialise": True, + "distributed": False, + "distributed_axis": "freq", + } + } + + @property + def mask(self): + """Get the mask dataset.""" + return self.datasets["mask"]
+ + + +
+[docs] +class BaselineMask(FreqContainer, TODContainer): + """A container for holding a baseline-dependent mask for a timestream. + + The mask is `True` for contaminated samples that should be excluded, and + `False` for clean samples. + + Unlike RFIMask, this is distributed by default. + """ + + _axes = ("stack",) + + _dataset_spec: ClassVar = { + "mask": { + "axes": ["freq", "stack", "time"], + "dtype": bool, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + } + } + + @property + def mask(self): + """Get the mask dataset.""" + return self.datasets["mask"] + + @property + def stack(self): + """The stack definition as an index (and conjugation) of a member product.""" + return self.index_map["stack"]
+ + + +
+[docs] +class SiderealBaselineMask(FreqContainer, SiderealContainer): + """A container for holding a baseline-dependent mask for a sidereal stream. + + The mask is `True` for contaminated samples that should be excluded, and + `False` for clean samples. + + Unlike SiderealRFIMask, this is distributed by default. + """ + + _axes = ("stack",) + + _dataset_spec: ClassVar = { + "mask": { + "axes": ["freq", "stack", "ra"], + "dtype": bool, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + } + } + + @property + def mask(self): + """Get the mask dataset.""" + return self.datasets["mask"] + + @property + def stack(self): + """The stack definition as an index (and conjugation) of a member product.""" + return self.index_map["stack"]
+ + + +
+[docs] +class TimeStream(FreqContainer, VisContainer, TODContainer): + """A container for holding a visibility dataset in time. + + This should look similar enough to the CHIME + :class:`~ch_util.andata.CorrData` container that they can be used + interchangably in most cases. + """ + + _dataset_spec: ClassVar = { + "vis": { + "axes": ["freq", "stack", "time"], + "dtype": np.complex64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (64, 128, 128), + "truncate": { + "weight_dataset": "vis_weight", + }, + }, + "vis_weight": { + "axes": ["freq", "stack", "time"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (64, 128, 128), + "truncate": True, + }, + "input_flags": { + "axes": ["input", "time"], + "dtype": np.float32, + "initialise": True, + "distributed": False, + }, + "gain": { + "axes": ["freq", "input", "time"], + "dtype": np.complex64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + } + + @property + def gain(self): + """Get the gain dataset.""" + return self.datasets["gain"] + + @property + def input_flags(self): + """Get the input_flags dataset.""" + return self.datasets["input_flags"]
+ + + +
+[docs] +class GridBeam(FreqContainer, DataWeightContainer): + """Generic container for representing a 2D beam on a rectangular grid.""" + + _axes = ("pol", "input", "theta", "phi") + + _dataset_spec: ClassVar = { + "beam": { + "axes": ["freq", "pol", "input", "theta", "phi"], + "dtype": np.complex64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["freq", "pol", "input", "theta", "phi"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "quality": { + "axes": ["freq", "pol", "input", "theta", "phi"], + "dtype": np.uint8, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + "gain": { + "axes": ["freq", "input"], + "dtype": np.complex64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + } + + _data_dset_name = "beam" + _weight_dset_name = "weight" + + def __init__(self, coords="celestial", *args, **kwargs): + super().__init__(*args, **kwargs) + self.attrs["coords"] = coords + + @property + def beam(self): + """Get the beam dataset.""" + return self.datasets["beam"] + + @property + def quality(self): + """Get the quality dataset.""" + return self.datasets["quality"] + + @property + def gain(self): + """Get the gain dataset.""" + return self.datasets["gain"] + + @property + def coords(self): + """Get the coordinates attribute.""" + return self.attrs["coords"] + + @property + def pol(self): + """Get the pol axis.""" + return self.index_map["pol"] + + @property + def input(self): + """Get the input axis.""" + return self.index_map["input"] + + @property + def theta(self): + """Get the theta axis.""" + return self.index_map["theta"] + + @property + def phi(self): + """Get the phi axis.""" + return self.index_map["phi"]
+ + + +
+[docs] +class HEALPixBeam(FreqContainer, HealpixContainer, DataWeightContainer): + """Container for representing the spherical 2-d beam in a HEALPix grid. + + Parameters + ---------- + ordering : {"nested", "ring"} + The HEALPix ordering scheme used for the beam map. + coords : {"celestial", "galactic", "telescope"} + The coordinate system that the beam map is defined on. + """ + + _axes = ("pol", "input") + + _dataset_spec: ClassVar = { + "beam": { + "axes": ["freq", "pol", "input", "pixel"], + "dtype": [("Et", np.complex64), ("Ep", np.complex64)], + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["freq", "pol", "input", "pixel"], + "dtype": [("Et", np.float32), ("Ep", np.float32)], + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + } + + _data_dset_name = "beam" + _weight_dset_name = "weight" + + def __init__(self, coords="unknown", ordering="unknown", *args, **kwargs): + super().__init__(*args, **kwargs) + self.attrs["coords"] = coords + self.attrs["ordering"] = ordering + + @property + def beam(self): + """Get the beam dataset.""" + return self.datasets["beam"] + + @property + def ordering(self): + """Get the ordering attribute.""" + return self.attrs["ordering"] + + @property + def coords(self): + """Get the coordinate attribute.""" + return self.attrs["coords"] + + @property + def pol(self): + """Get the pol axis.""" + return self.index_map["pol"] + + @property + def input(self): + """Get the input axis.""" + return self.index_map["input"] + + @property + def nside(self): + """Get the nsides of the map.""" + return int(np.sqrt(len(self.index_map["pixel"]) / 12))
+ + + +
+[docs] +class TrackBeam(FreqContainer, SampleVarianceContainer, DataWeightContainer): + """Container for a sequence of beam samples at arbitrary locations on the sphere. + + The axis of the beam samples is 'pix', defined by the numpy.dtype + [('theta', np.float32), ('phi', np.float32)]. + """ + + _axes = ("pol", "input", "pix") + + _dataset_spec: ClassVar = { + "beam": { + "axes": ["freq", "pol", "input", "pix"], + "dtype": np.complex64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (64, 2, 64, 128), + "truncate": { + "weight_dataset": "weight", + }, + }, + "weight": { + "axes": ["freq", "pol", "input", "pix"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (64, 2, 64, 128), + "truncate": True, + }, + "sample_variance": { + "axes": ["component", "freq", "pol", "input", "pix"], + "dtype": np.float32, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (3, 64, 2, 64, 128), + "truncate": True, + }, + "nsample": { + "axes": ["freq", "pol", "input", "pix"], + "dtype": np.uint8, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "chunks": (64, 2, 64, 128), + }, + } + + _data_dset_name = "beam" + _weight_dset_name = "weight" + + def __init__( + self, + theta=None, + phi=None, + coords="celestial", + track_type="drift", + *args, + **kwargs, + ): + if theta is not None and phi is not None: + if len(theta) != len(phi): + raise RuntimeError( + "theta and phi axes must have same length: " + f"({len(theta)} != {len(phi)})" + ) + + pix = np.zeros( + len(theta), dtype=[("theta", np.float32), ("phi", np.float32)] + ) + pix["theta"] = theta + pix["phi"] = phi + kwargs["pix"] = pix + elif (theta is None) != (phi is None): + raise RuntimeError("Both theta and phi coordinates must be specified.") + + super().__init__(*args, **kwargs) + + self.attrs["coords"] = coords + self.attrs["track_type"] = track_type + + @property + def beam(self): + """Get the beam dataset.""" + return self.datasets["beam"] + + @property + def gain(self): + """Get the gain dataset.""" + return self.datasets["gain"] + + @property + def coords(self): + """Get the coordinates attribute.""" + return self.attrs["coords"] + + @property + def track_type(self): + """Get the track type attribute.""" + return self.attrs["track_type"] + + @property + def pol(self): + """Get the pol axis.""" + return self.index_map["pol"] + + @property + def input(self): + """Get the input axis.""" + return self.index_map["input"] + + @property + def pix(self): + """Get the pix axis.""" + return self.index_map["pix"] + + @property + def _mean(self): + """Get the beam dataset.""" + return self.datasets["beam"]
+ + + +
+[docs] +class MModes(FreqContainer, VisContainer, MContainer): + """Parallel container for holding m-mode data. + + Attributes + ---------- + vis : mpidataset.MPIArray + Visibility array. + weight : mpidataset.MPIArray + Array of weights for each point. + """ + + _dataset_spec: ClassVar = { + "vis": { + "axes": ["m", "msign", "freq", "stack"], + "dtype": np.complex128, + "initialise": True, + "distributed": True, + "distributed_axis": "m", + }, + "vis_weight": { + "axes": ["m", "msign", "freq", "stack"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "m", + }, + }
+ + + +
+[docs] +class SVDModes(MContainer, VisBase): + """Parallel container for holding SVD m-mode data. + + Parameters + ---------- + mmax : integer, optional + Largest m to be held. + """ + + _axes = ("mode",) + + _dataset_spec: ClassVar = { + "vis": { + "axes": ["m", "mode"], + "dtype": np.complex128, + "initialise": True, + "distributed": True, + "distributed_axis": "m", + }, + "vis_weight": { + "axes": ["m", "mode"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "m", + }, + "nmode": { + "axes": ["m"], + "dtype": np.int32, + "initialise": True, + "distributed": True, + "distributed_axis": "m", + }, + } + + @property + def nmode(self): + """Get the nmode dataset.""" + return self.datasets["nmode"]
+ + + +
+[docs] +class KLModes(SVDModes): + """Parallel container for holding KL filtered m-mode data. + + Parameters + ---------- + mmax : integer, optional + Largest m to be held. + """ + + pass
+ + + +
+[docs] +class VisGridStream(FreqContainer, SiderealContainer, VisBase): + """Visibilities gridded into a 2D array. + + Only makes sense for an array which is a cartesian grid. + """ + + _axes = ("pol", "ew", "ns") + + _dataset_spec: ClassVar = { + "vis": { + "axes": ["pol", "freq", "ew", "ns", "ra"], + "dtype": np.complex64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "chunks": (1, 64, 1, 64, 128), + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "truncate": { + "weight_dataset": "weight", + }, + }, + "vis_weight": { + "axes": ["pol", "freq", "ew", "ns", "ra"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "chunks": (1, 64, 1, 64, 128), + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "truncate": True, + }, + "redundancy": { + "axes": ["pol", "ew", "ns", "ra"], + "dtype": np.int32, + "initialise": True, + "distributed": False, + "chunks": (1, 64, 1, 64, 128), + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + }, + } + + @property + def redundancy(self): + """Get the redundancy dataset.""" + return self.datasets["redundancy"]
+ + + +
+[docs] +class HybridVisStream(FreqContainer, SiderealContainer, VisBase): + """Visibilities beamformed only in the NS direction. + + This container has visibilities beam formed only in the NS direction to give a + grid in elevation. + """ + + _axes = ("pol", "ew", "el") + + _dataset_spec: ClassVar = { + "vis": { + "axes": ["pol", "freq", "ew", "el", "ra"], + "dtype": np.complex64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "dirty_beam": { + "axes": ["pol", "freq", "ew", "el", "ra"], + "dtype": np.float32, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + "vis_weight": { + "axes": ["pol", "freq", "ew", "ra"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + } + + @property + def dirty_beam(self): + """Not useful at this stage, but it's needed to propagate onward.""" + return self.datasets["dirty_beam"]
+ + + +
+[docs] +class HybridVisMModes(FreqContainer, MContainer, VisBase): + """Visibilities beamformed in the NS direction and m-mode transformed in RA. + + This container has visibilities beam formed only in the NS direction to give a + grid in elevation. + """ + + _axes = ("pol", "ew", "el") + + _dataset_spec: ClassVar = { + "vis": { + "axes": ["m", "msign", "pol", "freq", "ew", "el"], + "dtype": np.complex64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "vis_weight": { + "axes": ["m", "msign", "pol", "freq", "ew"], + "dtype": np.float32, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + }
+ + + +
+[docs] +class RingMap(FreqContainer, SiderealContainer, DataWeightContainer): + """Container for holding multifrequency ring maps. + + The maps are packed in format `[freq, pol, ra, EW beam, el]` where + the polarisations are Stokes I, Q, U and V. + + Parameters + ---------- + nside : int + The nside of the Healpix maps. + polarisation : bool, optional + If `True` all Stokes parameters are stored, if `False` only Stokes I is + stored. + """ + + _axes = ("pol", "beam", "el") + + _dataset_spec: ClassVar = { + "map": { + "axes": ["beam", "pol", "freq", "ra", "el"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "chunks": (1, 1, 64, 128, 128), + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "truncate": { + "weight_dataset": "weight", + }, + }, + "weight": { + "axes": ["pol", "freq", "ra", "el"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + "chunks": (1, 64, 128, 128), + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "truncate": True, + }, + "dirty_beam": { + "axes": ["beam", "pol", "freq", "ra", "el"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + "chunks": (1, 1, 64, 128, 128), + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "truncate": True, + }, + "rms": { + "axes": ["pol", "freq", "ra"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + "chunks": (4, 512, 512), + "compression": COMPRESSION, + "compression_opts": COMPRESSION_OPTS, + "truncate": True, + }, + } + + _data_dset_name = "map" + _weight_dset_name = "weight" + + @property + def pol(self): + """Get the pol axis.""" + return self.index_map["pol"] + + @property + def el(self): + """Get the el axis.""" + return self.index_map["el"] + + @property + def map(self): + """Get the map dataset.""" + return self.datasets["map"] + + @property + def rms(self): + """Get the rms dataset.""" + return self.datasets["rms"] + + @property + def dirty_beam(self): + """Get the dirty beam dataset.""" + return self.datasets["dirty_beam"]
+ + + +
+[docs] +class RingMapMask(FreqContainer, SiderealContainer): + """Mask bad ringmap pixels.""" + + _axes = ("pol", "el") + + _dataset_spec: ClassVar = { + "mask": { + "axes": ["pol", "freq", "ra", "el"], + "dtype": bool, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + } + } + + @property + def mask(self): + """Get the mask dataset.""" + return self.datasets["mask"]
+ + + +
+[docs] +class GainDataBase(DataWeightContainer): + """A container interface for gain-like data. + + To support the previous behaviour of gain type data the weight dataset is optional, + and returns None if it is not present. + """ + + _data_dset_name = "gain" + _weight_dset_name = "weight" + + @property + def gain(self) -> memh5.MemDataset: + """Get the gain dataset.""" + return self.datasets["gain"] + + @property + def weight(self) -> Optional[memh5.MemDataset]: + """The weights for each data point. + + Returns None is no weight dataset exists. + """ + try: + return super().weight + except KeyError: + return None
+ + + +
+[docs] +class CommonModeGainData(FreqContainer, TODContainer, GainDataBase): + """Parallel container for holding gain data common to all inputs.""" + + _dataset_spec: ClassVar = { + "gain": { + "axes": ["freq", "time"], + "dtype": np.complex128, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["freq", "time"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + }
+ + + +
+[docs] +class CommonModeSiderealGainData(FreqContainer, SiderealContainer, GainDataBase): + """Parallel container for holding sidereal gain data common to all inputs.""" + + _dataset_spec: ClassVar = { + "gain": { + "axes": ["freq", "ra"], + "dtype": np.complex128, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["freq", "ra"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + }
+ + + +
+[docs] +class GainData(FreqContainer, TODContainer, GainDataBase): + """Parallel container for holding gain data.""" + + _axes = ("input",) + + _dataset_spec: ClassVar = { + "gain": { + "axes": ["freq", "input", "time"], + "dtype": np.complex128, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["freq", "input", "time"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + "update_id": { + "axes": ["time"], + "dtype": np.dtype("<U64"), + "initialise": False, + "distributed": False, + }, + } + + @property + def update_id(self): + """Get the update id dataset if it exists.""" + try: + return self.datasets["update_id"] + except KeyError: + return None + + @property + def input(self): + """Get the input axis.""" + return self.index_map["input"]
+ + + +
+[docs] +class SiderealGainData(FreqContainer, SiderealContainer, GainDataBase): + """Parallel container for holding sidereal gain data.""" + + _axes = ("input",) + + _dataset_spec: ClassVar = { + "gain": { + "axes": ["freq", "input", "ra"], + "dtype": np.complex128, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["freq", "input", "ra"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + } + + @property + def input(self): + """Get the input axis.""" + return self.index_map["input"]
+ + + +
+[docs] +class StaticGainData(FreqContainer, GainDataBase): + """Parallel container for holding static gain data (i.e. non time varying).""" + + _axes = ("input",) + + _dataset_spec: ClassVar = { + "gain": { + "axes": ["freq", "input"], + "dtype": np.complex128, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["freq", "input"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "freq", + }, + } + + @property + def input(self): + """Get the input axis.""" + return self.index_map["input"]
+ + + +
+[docs] +class DelayCutoff(ContainerBase): + """Container for a delay cutoff.""" + + _axes = ("pol", "el") + + _dataset_spec: ClassVar = { + "cutoff": { + "axes": ["pol", "el"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + "distributed_axis": "el", + } + } + + @property + def cutoff(self): + """Get the cutoff dataset.""" + return self.datasets["cutoff"] + + @property + def pol(self): + """Get the pol axis.""" + return self.index_map["pol"] + + @property + def el(self): + """Get the el axis.""" + return self.index_map["el"]
+ + + +
+[docs] +class DelayContainer(ContainerBase): + """A container with a delay axis.""" + + _axes = ("delay",) + + @property + def delay(self) -> np.ndarray: + """The delay axis in microseconds.""" + return self.index_map["delay"]
+ + + +
+[docs] +class DelaySpectrum(DelayContainer): + """Container for a delay power spectrum. + + Notes + ----- + A note about definitions: for a dataset with a frequency axis, the corresponding + delay spectrum is the result of Fourier transforming in frequency, while the delay + power spectrum is obtained by taking the squared magnitude of each element of the + delay spectrum, and then usually averaging over some other axis. Our unfortunate + convention is to store a delay power spectrum in a `DelaySpectrum` container, and + store a delay spectrum in a :py:class:`~draco.core.containers.DelayTransform` + container. + """ + + _axes = ("baseline", "sample") + + _dataset_spec: ClassVar = { + "spectrum": { + "axes": ["baseline", "delay"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "baseline", + }, + "spectrum_samples": { + "axes": ["sample", "baseline", "delay"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "baseline", + }, + } + + def __init__(self, *args, weight_boost=1.0, sample=1, **kwargs): + super().__init__(*args, sample=sample, **kwargs) + self.attrs["weight_boost"] = weight_boost + + @property + def spectrum(self): + """Get the spectrum dataset.""" + return self.datasets["spectrum"] + + @property + def weight_boost(self): + """Get the weight boost factor. + + If set, this factor was used to set the assumed noise when computing the + spectrum. + """ + return self.attrs["weight_boost"] + + @property + def freq(self): + """Get the frequency axis of the input data.""" + return self.attrs["freq"]
+ + + +
+[docs] +class DelayTransform(DelayContainer): + """Container for a delay spectrum. + + Notes + ----- + See the docstring for :py:class:`~draco.core.containers.DelaySpectrum` for a + description of the difference between `DelayTransform` and `DelaySpectrum`. + """ + + _axes = ("baseline", "sample") + + _dataset_spec: ClassVar = { + "spectrum": { + "axes": ["baseline", "sample", "delay"], + "dtype": np.complex128, + "initialise": True, + "distributed": True, + "distributed_axis": "baseline", + } + } + + def __init__(self, weight_boost=1.0, *args, **kwargs): + super().__init__(*args, **kwargs) + self.attrs["weight_boost"] = weight_boost + + @property + def spectrum(self): + """Get the spectrum dataset.""" + return self.datasets["spectrum"] + + @property + def weight_boost(self): + """Get the weight boost factor. + + If set, this factor was used to set the assumed noise when computing the + spectrum. + """ + return self.attrs["weight_boost"] + + @property + def freq(self): + """Get the frequency axis of the input data.""" + return self.attrs["freq"]
+ + + +
+[docs] +class WaveletSpectrum(FreqContainer, DelayContainer, DataWeightContainer): + """Container for a wavelet power spectrum.""" + + _axes = ("baseline",) + + _dataset_spec: ClassVar = { + "spectrum": { + "axes": ["baseline", "delay", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "baseline", + }, + "weight": { + "axes": ["baseline", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "baseline", + }, + } + _data_dset_name = "spectrum" + _weight_dset_name = "weight" + + @property + def spectrum(self): + """The wavelet spectrum.""" + return self.datasets["spectrum"]
+ + + +
+[docs] +class DelayCrossSpectrum(DelaySpectrum): + """Container for a delay cross power spectra.""" + + _axes = ("dataset",) + + _dataset_spec: ClassVar = { + "spectrum": { + "axes": ["dataset", "dataset", "baseline", "delay"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "baseline", + }, + "spectrum_samples": { + "axes": ["sample", "dataset", "dataset", "baseline", "delay"], + "dtype": np.float64, + "initialise": False, + "distributed": True, + "distributed_axis": "baseline", + }, + } + + @property + def spectrum(self): + """Get the spectrum dataset.""" + return self.datasets["spectrum"]
+ + + +
+[docs] +class Powerspectrum2D(ContainerBase): + """Container for a 2D cartesian power spectrum. + + Generally you should set the standard attributes `z_start` and `z_end` with + the redshift range included in the power spectrum estimate, and the `type` + attribute with a description of the estimator type. Suggested valued for + `type` are: + + `unwindowed` + The standard unbiased quadratic estimator. + + `minimum_variance` + The minimum variance, but highly correlated, estimator. Just a rescaled + version of the q-estimator. + + `uncorrelated` + The uncorrelated estimator using the root of the Fisher matrix. + + Parameters + ---------- + kpar_edges, kperp_edges : np.ndarray + Array of the power spectrum bin boundaries. + """ + + _axes = ("kperp", "kpar") + + _dataset_spec: ClassVar = { + "powerspectrum": { + "axes": ["kperp", "kpar"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + "C_inv": { + "axes": ["kperp", "kpar", "kperp", "kpar"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + } + + def __init__(self, kperp_edges=None, kpar_edges=None, *args, **kwargs): + # Construct the kperp axis from the bin edges + if kperp_edges is not None: + centre = 0.5 * (kperp_edges[1:] + kperp_edges[:-1]) + width = kperp_edges[1:] - kperp_edges[:-1] + + kwargs["kperp"] = np.rec.fromarrays( + [centre, width], names=["centre", "width"] + ).view(np.ndarray) + + # Construct the kpar axis from the bin edges + if kpar_edges is not None: + centre = 0.5 * (kpar_edges[1:] + kpar_edges[:-1]) + width = kpar_edges[1:] - kpar_edges[:-1] + + kwargs["kpar"] = np.rec.fromarrays( + [centre, width], names=["centre", "width"] + ).view(np.ndarray) + + super().__init__(*args, **kwargs) + + @property + def powerspectrum(self): + """Get the powerspectrum dataset.""" + return self.datasets["powerspectrum"] + + @property + def C_inv(self): + """Get the C inverse dataset.""" + return self.datasets["C_inv"]
+ + + +
+[docs] +class SVDSpectrum(ContainerBase): + """Container for an m-mode SVD spectrum.""" + + _axes = ("m", "singularvalue") + + _dataset_spec: ClassVar = { + "spectrum": { + "axes": ["m", "singularvalue"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "m", + } + } + + @property + def spectrum(self): + """Get the spectrum dataset.""" + return self.datasets["spectrum"]
+ + + +
+[docs] +class FrequencyStack(FreqContainer, DataWeightContainer): + """Container for a frequency stack. + + In general used to hold the product of `draco.analysis.SourceStack` + The stacked signal of frequency slices of the data in the direction + of sources of interest. + """ + + _dataset_spec: ClassVar = { + "stack": { + "axes": ["freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + "weight": { + "axes": ["freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + } + + _data_dset_name = "stack" + _weight_dset_name = "weight" + + @property + def stack(self): + """Get the stack dataset.""" + return self.datasets["stack"]
+ + + +
+[docs] +class FrequencyStackByPol(FrequencyStack): + """Container for a frequency stack split by polarisation.""" + + _axes = ("pol",) + + _dataset_spec: ClassVar = { + "stack": { + "axes": ["pol", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + "weight": { + "axes": ["pol", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + } + + @property + def pol(self): + """Get the pol axis.""" + return self.index_map["pol"]
+ + + +
+[docs] +class MockFrequencyStack(FrequencyStack): + """Container for holding a frequency stack for multiple mock catalogs. + + Adds a `mock` axis as the first dimension of each dataset. + """ + + _axes = ("mock",) + + _dataset_spec: ClassVar = { + "stack": { + "axes": ["mock", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + "weight": { + "axes": ["mock", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + }
+ + + +
+[docs] +class MockFrequencyStackByPol(FrequencyStackByPol): + """Container for holding a frequency stack split by pol for multiple mock catalogs. + + Adds a `mock` axis as the first dimension of each dataset. + """ + + _axes = ("mock",) + + _dataset_spec: ClassVar = { + "stack": { + "axes": ["mock", "pol", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + "weight": { + "axes": ["mock", "pol", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + }
+ + + +
+[docs] +class Stack3D(FreqContainer, DataWeightContainer): + """Container for a 3D frequency stack.""" + + _axes = ("pol", "delta_ra", "delta_dec") + + _dataset_spec: ClassVar = { + "stack": { + "axes": ["pol", "delta_ra", "delta_dec", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + "weight": { + "axes": ["pol", "delta_ra", "delta_dec", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + } + + _data_dset_name = "stack" + _weight_dset_name = "weight" + + @property + def stack(self): + """Get the stack dataset.""" + return self.datasets["stack"]
+ + + +
+[docs] +class SourceCatalog(TableBase): + """A basic container for holding astronomical source catalogs. + + Notes + ----- + The `ra` and `dec` coordinates should be ICRS. + """ + + _table_spec: ClassVar = { + "position": { + "columns": [["ra", np.float64], ["dec", np.float64]], + "axis": "object_id", + } + }
+ + + +
+[docs] +class SpectroscopicCatalog(SourceCatalog): + """A container for spectroscopic catalogs.""" + + _table_spec: ClassVar = { + "redshift": { + "columns": [["z", np.float64], ["z_error", np.float64]], + "axis": "object_id", + } + }
+ + + +
+[docs] +class FormedBeam(FreqContainer, DataWeightContainer): + """Container for formed beams.""" + + _axes = ("object_id", "pol") + + _dataset_spec: ClassVar = { + "beam": { + "axes": ["object_id", "pol", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["object_id", "pol", "freq"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "position": { + "axes": ["object_id"], + "dtype": np.dtype([("ra", np.float64), ("dec", np.float64)]), + "initialise": True, + "distributed": False, + }, + "redshift": { + "axes": ["object_id"], + "dtype": np.dtype([("z", np.float64), ("z_error", np.float64)]), + "initialise": True, + "distributed": False, + }, + } + + _data_dset_name = "beam" + _weight_dset_name = "weight" + + @property + def beam(self): + """Get the beam dataset.""" + return self.datasets["beam"] + + @property + def frequency(self): + """Get the frequency axis.""" + return self.index_map["freq"] + + @property + def id(self): + """Get the object id axis.""" + return self.index_map["object_id"] + + @property + def pol(self): + """Get the pol axis.""" + return self.index_map["pol"]
+ + + +
+[docs] +class FormedBeamHA(FormedBeam): + """Container for formed beams. + + These have not been collapsed in the hour angle (HA) axis. + """ + + _axes = ("ha",) + + _dataset_spec: ClassVar = { + "beam": { + "axes": ["object_id", "pol", "freq", "ha"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "weight": { + "axes": ["object_id", "pol", "freq", "ha"], + "dtype": np.float64, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + }, + "object_ha": { + "axes": ["object_id", "ha"], + "dtype": np.float64, + "initialise": True, + "distributed": False, + }, + } + + @property + def ha(self): + """Get the hour angle dataset.""" + return self.datasets["object_ha"]
+ + + +
+[docs] +class FormedBeamMask(FreqContainer): + """Mask bad formed beams.""" + + _axes = ("object_id", "pol") + + _dataset_spec: ClassVar = { + "mask": { + "axes": ["object_id", "pol", "freq"], + "dtype": bool, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + } + } + + @property + def mask(self): + """Get the mask dataset.""" + return self.datasets["mask"]
+ + + +
+[docs] +class FormedBeamHAMask(FormedBeamMask): + """Mask bad formed beams as a function of hour angle.""" + + _axes = ("ha",) + + _dataset_spec: ClassVar = { + "mask": { + "axes": ["object_id", "pol", "freq", "ha"], + "dtype": bool, + "initialise": True, + "distributed": True, + "distributed_axis": "freq", + } + }
+ + + +
+[docs] +def empty_like(obj, **kwargs): + """Create an empty container like `obj`. + + Parameters + ---------- + obj : ContainerBase + Container to base this one off. + kwargs : optional + Optional definitions of specific axes we want to override. Works in the + same way as the `ContainerBase` constructor, though `axes_from=obj` and + `attrs_from=obj` are implied. + + Returns + ------- + newobj : container.ContainerBase + New data container. + """ + if isinstance(obj, ContainerBase): + return obj.__class__(axes_from=obj, attrs_from=obj, **kwargs) + + raise RuntimeError( + f"I don't know how to deal with data type {obj.__class__.__name__}" + )
+ + + +
+[docs] +def empty_timestream(**kwargs): + """Create a new timestream container. + + This indirect call exists so it can be replaced to return custom timestream + types. + + Parameters + ---------- + kwargs : optional + Arguments to pass to the timestream constructor. + + Returns + ------- + ts : TimeStream + """ + return TimeStream(**kwargs)
+ + + +
+[docs] +def copy_datasets_filter( + source: ContainerBase, + dest: ContainerBase, + axis: Union[str, list, tuple] = [], + selection: Union[np.ndarray, list, slice, dict] = {}, + exclude_axes: Optional[List[str]] = None, +): + """Copy datasets while filtering a given axis. + + Only datasets containing the axis to be filtered will be copied. + + Parameters + ---------- + source + Source container + dest + Destination container. The axes in this container should reflect the + selections being made to the source. + axis + Name of the axes to filter. These must match the axes in `selection`, + unless selection is a single item. This is partially here for legacy + reasons, as the selections can be fully specified by `selection` + selection + A filtering selection to be applied to each axis. + exclude_axes + An optional set of axes that if a dataset contains one means it will + not be copied. + """ + exclude_axes_set = set(exclude_axes) if exclude_axes else set() + if isinstance(axis, str): + axis = [axis] + axis = set(axis) + + # Resolve the selections and axes, removing any that aren't needed + if not isinstance(selection, dict): + # Assume we just want to apply this selection to all listed axes + selection = {ax: selection for ax in axis} + + if not axis: + axis = set(selection.keys()) + # Make sure that all axis keys are present in selection + elif not all(ax in selection for ax in axis): + raise ValueError( + f"Mismatch between axis and selection. Got {axis} " + f"but selections for {list(selection.keys())}." + ) + + # Try to clean up selections + for ax in list(selection): + sel = selection[ax] + # Remove any unnecessary slices + if sel == slice(None): + del selection[ax] + # Convert any indexed selections to slices where possible + elif type(sel) in {list, tuple, np.ndarray}: + if list(sel) == list(range(sel[0], sel[-1])): + selection[ax] = slice(sel[0], sel[-1]) + + stack = [source] + + while stack: + item = stack.pop() + + if memh5.is_group(item): + stack += list(item.values()) + continue + + item_axes = list(item.attrs.get("axis", ())) + + # Only copy if at least one of the axes we are filtering + # are present, and there are no excluded axes in the dataset + if not ( + axis.intersection(item_axes) and exclude_axes_set.isdisjoint(item_axes) + ): + continue + + if item.name not in dest: + dest.add_dataset(item.name) + + dest_dset = dest[item.name] + + # Make sure that both datasets are distributed to the same axis + if isinstance(item, memh5.MemDatasetDistributed): + if not isinstance(dest_dset, memh5.MemDatasetDistributed): + raise ValueError( + "Cannot filter a distributed dataset into a non-distributed " + "dataset using this method." + ) + + # Choose the best possible axis to distribute over. Try + # to avoid redistributing if possible + original_ax_id = item.distributed_axis + # If no selections are being made or the slection is not over the + # current axis, so no need to redistribute + if not selection or item_axes[original_ax_id] not in selection: + new_ax_id = original_ax_id + else: + # Find the largest axis available + ax_priority = [ + x for _, x in sorted(zip(item.shape, item_axes)) if x not in axis + ] + if not ax_priority: + raise ValueError( + "Could not find a valid axis to redistribute. At least one " + "axis must be omitted from filtering." + ) + new_ax_id = item_axes.index(ax_priority[-1]) + + # Make sure both datasets are distributed to the same axis. + item.redistribute(new_ax_id) + dest_dset.redistribute(new_ax_id) + + # Apply the selections + arr = item[:].view(np.ndarray) + + for ax, sel in selection.items(): + try: + ax_ind = item_axes.index(ax) + except ValueError: + continue + arr = mpiarray._apply_sel(arr, sel, ax_ind) + + dest_dset[:] = arr[:] + + if isinstance(dest_dset, memh5.MemDatasetDistributed): + # Redistribute back to the original axis + dest_dset.redistribute(original_ax_id)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/core/io.html b/docs/_modules/draco/core/io.html new file mode 100644 index 000000000..aacaee989 --- /dev/null +++ b/docs/_modules/draco/core/io.html @@ -0,0 +1,1411 @@ + + + + + + draco.core.io — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.core.io

+"""Tasks for reading and writing data.
+
+File Groups
+===========
+
+Several tasks accept groups of files as arguments. These are specified in the YAML file as a dictionary like below.
+
+.. code-block:: yaml
+
+    list_of_file_groups:
+        -   tag: first_group  # An optional tag naming the group
+            files:
+                -   'file1.h5'
+                -   'file[3-4].h5'  # Globs are processed
+                -   'file7.h5'
+
+        -   files:  # No tag specified, implicitly gets the tag 'group_2'
+                -   'another_file1.h5'
+                -   'another_file2.h5'
+
+
+    single_group:
+        files: ['file1.h5', 'file2.h5']
+"""
+
+import os.path
+import shutil
+import subprocess
+from typing import ClassVar, Dict, List, Optional, Union
+
+import numpy as np
+from caput import config, fileformats, memh5, pipeline, truncate
+from cora.util import units
+from drift.core import beamtransfer, manager, telescope
+from yaml import dump as yamldump
+
+from ..util.exception import ConfigError
+from . import task
+
+
+def _list_of_filelists(files: Union[List[str], List[List[str]]]) -> List[List[str]]:
+    """Take in a list of lists/glob patterns of filenames.
+
+    Parameters
+    ----------
+    files
+        A path or glob pattern (e.g. /my/data/*.h5) or a list of those (or a list of
+        lists of those).
+
+    Raises
+    ------
+    ConfigError
+        If files has the wrong format or refers to a file that doesn't exist.
+
+    Returns
+    -------
+    The input file list list. Any glob patterns will be flattened to file path string
+    lists.
+    """
+    import glob
+
+    f2 = []
+
+    for filelist in files:
+        if isinstance(filelist, str):
+            if "*" not in filelist and not os.path.isfile(filelist):
+                raise ConfigError(f"File not found: {filelist!s}")
+            filelist = glob.glob(filelist)
+        elif isinstance(filelist, list):
+            for i in range(len(filelist)):
+                filelist[i] = _list_or_glob(filelist[i])
+        else:
+            raise ConfigError("Must be list or glob pattern.")
+        f2 = f2 + filelist
+
+    return f2
+
+
+def _list_or_glob(files: Union[str, List[str]]) -> List[str]:
+    """Take in a list of lists/glob patterns of filenames.
+
+    Parameters
+    ----------
+    files
+        A path or glob pattern (e.g. /my/data/*.h5) or a list of those
+
+    Returns
+    -------
+    The input file list. Any glob patterns will be flattened to file path string lists.
+
+    Raises
+    ------
+    ConfigError
+        If files has the wrong type or if it refers to a file that doesn't exist.
+    """
+    import glob
+
+    # If the input was a list, process each element and return as a single flat list
+    if isinstance(files, list):
+        parsed_files = []
+        for f in files:
+            parsed_files = parsed_files + _list_or_glob(f)
+        return parsed_files
+
+    # If it's a glob we need to expand the glob and then call again
+    if isinstance(files, str) and any(c in files for c in "*?["):
+        return _list_or_glob(sorted(glob.glob(files)))
+
+    # We presume a string is an actual path...
+    if isinstance(files, str):
+        # Check that it exists and is a file (or dir if zarr format)
+        if files.endswith(".zarr"):
+            if not os.path.isdir(files):
+                raise ConfigError(
+                    f"Expected a zarr directory store, but directory not found: {files}"
+                )
+        else:
+            if not os.path.isfile(files):
+                raise ConfigError(f"File not found: {files!s}")
+
+        return [files]
+
+    raise ConfigError(
+        f"Argument must be list, glob pattern, or file path, got {files!r}"
+    )
+
+
+def _list_of_filegroups(groups: Union[List[Dict], Dict]) -> List[Dict]:
+    """Process a file group/groups.
+
+    Parameters
+    ----------
+    groups
+        Dicts should contain keys 'files': An iterable with file path or glob pattern
+        strings, 'tag': the group tag str
+
+    Returns
+    -------
+    The input groups. Any glob patterns in the 'files' list will be flattened to file
+    path strings.
+
+    Raises
+    ------
+    ConfigError
+        If groups has the wrong format.
+    """
+    import glob
+
+    # Convert to list if the group was not included in a list
+    if not isinstance(groups, list):
+        groups = [groups]
+
+    # Iterate over groups, set the tag if needed, and process the file list
+    # through glob
+    for gi, group in enumerate(groups):
+        try:
+            files = group["files"]
+        except KeyError:
+            raise ConfigError("File group is missing key 'files'.")
+        except TypeError:
+            raise ConfigError(f"Expected type dict in file groups (got {type(group)}).")
+
+        if "tag" not in group:
+            group["tag"] = "group_%i" % gi
+
+        flist = []
+
+        for fname in files:
+            if "*" not in fname and not os.path.isfile(fname):
+                raise ConfigError(f"File not found: {fname!s}")
+            flist += glob.glob(fname)
+
+        if not len(flist):
+            raise ConfigError(f"No files in group exist ({files!s}).")
+
+        group["files"] = flist
+
+    return groups
+
+
+
+[docs] +class LoadMaps(task.MPILoggedTask): + """Load a series of maps from files given in the tasks parameters. + + Maps are given as one, or a list of `File Groups` (see + :mod:`draco.core.io`). Maps within the same group are added together + before being passed on. + + Attributes + ---------- + maps : list or dict + A dictionary specifying a file group, or a list of them. + """ + + maps = config.Property(proptype=_list_of_filegroups) + +
+[docs] + def next(self): + """Load the groups of maps from disk and pass them on. + + Returns + ------- + map : :class:`containers.Map` + """ + from . import containers + + # Exit this task if we have eaten all the file groups + if len(self.maps) == 0: + raise pipeline.PipelineStopIteration + + group = self.maps.pop(0) + + map_stack = None + + # Iterate over all the files in the group, load them into a Map + # container and add them all together + for mfile in group["files"]: + self.log.debug("Loading file %s", mfile) + + current_map = containers.Map.from_file(mfile, distributed=True) + current_map.redistribute("freq") + + # Start the stack if needed + if map_stack is None: + map_stack = current_map + + # Otherwise, check that the new map has consistent frequencies, + # nside and pol and stack up. + else: + if (current_map.freq != map_stack.freq).all(): + raise RuntimeError("Maps do not have consistent frequencies.") + + if (current_map.index_map["pol"] != map_stack.index_map["pol"]).all(): + raise RuntimeError("Maps do not have the same polarisations.") + + if ( + current_map.index_map["pixel"] != map_stack.index_map["pixel"] + ).all(): + raise RuntimeError("Maps do not have the same pixelisation.") + + map_stack.map[:] += current_map.map[:] + + # Assign a tag to the stack of maps + map_stack.attrs["tag"] = group["tag"] + + return map_stack
+
+ + + +
+[docs] +class LoadFITSCatalog(task.SingleTask): + """Load an SDSS-style FITS source catalog. + + Catalogs are given as one, or a list of `File Groups` (see + :mod:`draco.core.io`). Catalogs within the same group are combined together + before being passed on. + + Attributes + ---------- + catalogs : list or dict + A dictionary specifying a file group, or a list of them. + z_range : list, optional + Select only sources with a redshift within the given range. + freq_range : list, optional + Select only sources with a 21cm line freq within the given range. Overrides + `z_range`. + """ + + catalogs = config.Property(proptype=_list_of_filegroups) + z_range = config.list_type(type_=float, length=2, default=None) + freq_range = config.list_type(type_=float, length=2, default=None) + +
+[docs] + def process(self): + """Load the groups of catalogs from disk, concatenate them and pass them on. + + Returns + ------- + catalog : :class:`containers.SpectroscopicCatalog` + """ + from astropy.io import fits + + from . import containers + + # Exit this task if we have eaten all the file groups + if len(self.catalogs) == 0: + raise pipeline.PipelineStopIteration + + group = self.catalogs.pop(0) + + # Set the redshift selection + if self.freq_range: + zl = units.nu21 / self.freq_range[1] - 1 + zh = units.nu21 / self.freq_range[0] - 1 + self.z_range = (zl, zh) + + if self.z_range: + zl, zh = self.z_range + self.log.info(f"Applying redshift selection {zl:.2f} <= z <= {zh:.2f}") + + # Load the data only on rank=0 and then broadcast + if self.comm.rank == 0: + # Iterate over all the files in the group, load them into a Map + # container and add them all together + catalog_stack = [] + for cfile in group["files"]: + self.log.debug("Loading file %s", cfile) + + # TODO: read out the weights from the catalogs + with fits.open(cfile, mode="readonly") as cat: + pos = np.array([cat[1].data[col] for col in ["RA", "DEC", "Z"]]) + + # Apply any redshift selection to the objects + if self.z_range: + zsel = (pos[2] >= self.z_range[0]) & (pos[2] <= self.z_range[1]) + pos = pos[:, zsel] + + catalog_stack.append(pos) + + # NOTE: this one is tricky, for some reason the concatenate in here + # produces a non C contiguous array, so we need to ensure that otherwise + # the broadcasting will get very confused + catalog_array = np.concatenate(catalog_stack, axis=-1).astype(np.float64) + catalog_array = np.ascontiguousarray(catalog_array) + num_objects = catalog_array.shape[-1] + else: + num_objects = None + catalog_array = None + + # Broadcast the size of the catalog to all ranks, create the target array and + # broadcast into it + num_objects = self.comm.bcast(num_objects, root=0) + self.log.debug(f"Constructing catalog with {num_objects} objects.") + + if self.comm.rank != 0: + catalog_array = np.zeros((3, num_objects), dtype=np.float64) + self.comm.Bcast(catalog_array, root=0) + + catalog = containers.SpectroscopicCatalog(object_id=num_objects) + catalog["position"]["ra"] = catalog_array[0] + catalog["position"]["dec"] = catalog_array[1] + catalog["redshift"]["z"] = catalog_array[2] + catalog["redshift"]["z_error"] = 0 + + # Assign a tag to the stack of maps + catalog.attrs["tag"] = group["tag"] + + return catalog
+
+ + + +
+[docs] +class SelectionsMixin: + """Mixin for parsing axis selections, typically from a yaml config. + + Attributes + ---------- + selections : dict, optional + A dictionary of axis selections. See below for details. + + Selections + ---------- + Selections can be given to limit the data read to specified subsets. They can be + given for any named axis in the container. + + Selections can be given as a slice with an `<axis name>_range` key with either + `[start, stop]` or `[start, stop, step]` as the value. Alternatively a list of + explicit indices to extract can be given with the `<axis name>_index` key, and + the value is a list of the indices. If both `<axis name>_range` and `<axis + name>_index` keys are given the former will take precedence, but you should + clearly avoid doing this. + + Additionally index based selections currently don't work for distributed reads. + + Here's an example in the YAML format that the pipeline uses: + + .. code-block:: yaml + + selections: + freq_range: [256, 512, 4] # A strided slice + stack_index: [1, 2, 4, 9, 16, 25, 36, 49, 64] # A sparse selection + stack_range: [1, 14] # Will override the selection above + """ + + selections = config.Property(proptype=dict, default=None) + +
+[docs] + def setup(self): + """Resolve the selections.""" + self._sel = self._resolve_sel()
+ + + def _resolve_sel(self): + # Turn the selection parameters into actual selectable types + + sel = {} + + sel_parsers = {"range": self._parse_range, "index": self._parse_index} + + # To enforce the precedence of range vs index selections, we rely on the fact + # that a sort will place the axis_range keys after axis_index keys + for k in sorted(self.selections or []): + # Parse the key to get the axis name and type, accounting for the fact the + # axis name may contain an underscore + *axis, type_ = k.split("_") + axis_name = "_".join(axis) + + if type_ not in sel_parsers: + raise ValueError( + f'Unsupported selection type "{type_}", or invalid key "{k}"' + ) + + sel[f"{axis_name}_sel"] = sel_parsers[type_](self.selections[k]) + + return sel + + def _parse_range(self, x): + # Parse and validate a range type selection + + if not isinstance(x, (list, tuple)) or len(x) > 3 or len(x) < 2: + raise ValueError( + f"Range spec must be a length 2 or 3 list or tuple. Got {x}." + ) + + for v in x: + if not isinstance(v, int): + raise ValueError(f"All elements of range spec must be ints. Got {x}") + + return slice(*x) + + def _parse_index(self, x): + # Parse and validate an index type selection + + if not isinstance(x, (list, tuple)) or len(x) == 0: + raise ValueError(f"Index spec must be a non-empty list or tuple. Got {x}.") + + for v in x: + if not isinstance(v, int): + raise ValueError(f"All elements of index spec must be ints. Got {x}") + + return list(x)
+ + + +
+[docs] +class BaseLoadFiles(SelectionsMixin, task.SingleTask): + """Base class for loading containers from a file on disk. + + Provides the capability to make selections along axes. + + Attributes + ---------- + distributed : bool, optional + Whether the file should be loaded distributed across ranks. + convert_strings : bool, optional + Convert strings to unicode when loading. + redistribute : str, optional + An optional axis name to redistribute the container over after it has + been read. + """ + + distributed = config.Property(proptype=bool, default=True) + convert_strings = config.Property(proptype=bool, default=True) + redistribute = config.Property(proptype=str, default=None) + + def _load_file(self, filename, extra_message=""): + # Load the file into the relevant container + + if not os.path.exists(filename): + raise RuntimeError(f"File does not exist: {filename}") + + self.log.info(f"Loading file {filename} {extra_message}") + self.log.debug(f"Reading with selections: {self._sel}") + + # If we are applying selections we need to dispatch the `from_file` via the + # correct subclass, rather than relying on the internal detection of the + # subclass. To minimise the number of files being opened this is only done on + # rank=0 and is then broadcast + if self._sel: + if self.comm.rank == 0: + with fileformats.guess_file_format(filename).open(filename, "r") as fh: + clspath = memh5.MemDiskGroup._detect_subclass_path(fh) + else: + clspath = None + clspath = self.comm.bcast(clspath, root=0) + new_cls = memh5.MemDiskGroup._resolve_subclass(clspath) + else: + new_cls = memh5.BasicCont + + cont = new_cls.from_file( + filename, + distributed=self.distributed, + comm=self.comm, + convert_attribute_strings=self.convert_strings, + convert_dataset_strings=self.convert_strings, + **self._sel, + ) + + if self.redistribute is not None: + cont.redistribute(self.redistribute) + + return cont
+ + + +
+[docs] +class LoadFilesFromParams(BaseLoadFiles): + """Load data from files given in the tasks parameters. + + Attributes + ---------- + files : glob pattern, or list + Can either be a glob pattern, or lists of actual files. + """ + + files = config.Property(proptype=_list_or_glob) + + _file_ind = 0 + +
+[docs] + def process(self): + """Load the given files in turn and pass on. + + Returns + ------- + cont : subclass of `memh5.BasicCont` + """ + # Garbage collect to workaround leaking memory from containers. + # TODO: find actual source of leak + import gc + + gc.collect() + + if self._file_ind == len(self.files): + raise pipeline.PipelineStopIteration + + # Fetch and remove the first item in the list + file_ = self.files[self._file_ind] + + # Load into a container + nfiles_str = str(len(self.files)) + message = f"[{self._file_ind + 1: {len(nfiles_str)}}/{nfiles_str}]" + cont = self._load_file(file_, extra_message=message) + + if "tag" not in cont.attrs: + # Get the first part of the actual filename and use it as the tag + tag = os.path.splitext(os.path.basename(file_))[0] + + cont.attrs["tag"] = tag + + self._file_ind += 1 + + return cont
+
+ + + +# Define alias for old code +LoadBasicCont = LoadFilesFromParams + + +
+[docs] +class FindFiles(pipeline.TaskBase): + """Take a glob or list of files and pass on to other tasks. + + Files are specified as a parameter in the configuration file. + + Parameters + ---------- + files : list or glob + """ + + files = config.Property(proptype=_list_or_glob) + +
+[docs] + def setup(self): + """Return list of files specified in the parameters.""" + if not isinstance(self.files, (list, tuple)): + raise RuntimeError("Argument must be list of files.") + + return self.files
+
+ + + +
+[docs] +class LoadFiles(LoadFilesFromParams): + """Load data from files passed into the setup routine. + + File must be a serialised subclass of :class:`memh5.BasicCont`. + """ + + files = None + +
+[docs] + def setup(self, files): + """Set the list of files to load. + + Parameters + ---------- + files : list + Files to load + """ + # Call the baseclass setup to resolve any selections + super().setup() + + if not isinstance(files, (list, tuple)): + raise RuntimeError(f'Argument must be list of files. Got "{files}"') + + self.files = files
+
+ + + +
+[docs] +class Save(pipeline.TaskBase): + """Save out the input, and pass it on. + + Assumes that the input has a `to_hdf5` method. Appends a *tag* if there is + a `tag` entry in the attributes, otherwise just uses a count. + + Attributes + ---------- + root : str + Root of the file name to output to. + """ + + root = config.Property(proptype=str) + + count = 0 + +
+[docs] + def next(self, data): + """Write out the data file. + + Assumes it has an MPIDataset interface. + + Parameters + ---------- + data : mpidataset.MPIDataset + Data to write out. + """ + if "tag" not in data.attrs: + tag = self.count + self.count += 1 + else: + tag = data.attrs["tag"] + + fname = f"{self.root}_{tag!s}.h5" + + data.to_hdf5(fname) + + return data
+
+ + + +
+[docs] +class Print(pipeline.TaskBase): + """Stupid module which just prints whatever it gets. Good for debugging.""" + +
+[docs] + def next(self, input_): + """Print the input.""" + print(input_) + + return input_
+
+ + + +
+[docs] +class LoadBeamTransfer(pipeline.TaskBase): + """Loads a beam transfer manager from disk. + + Attributes + ---------- + product_directory : str + Path to the saved Beam Transfer products. + """ + + product_directory = config.Property(proptype=str) + +
+[docs] + def setup(self): + """Load the beam transfer matrices. + + Returns + ------- + tel : TransitTelescope + Object describing the telescope. + bt : BeamTransfer + BeamTransfer manager. + feed_info : list, optional + Optional list providing additional information about each feed. + """ + import os + + from drift.core import beamtransfer + + if not os.path.exists(self.product_directory): + raise RuntimeError("BeamTransfers do not exist.") + + bt = beamtransfer.BeamTransfer(self.product_directory) + + tel = bt.telescope + + try: + return tel, bt, tel.feeds + except AttributeError: + return tel, bt
+
+ + + +
+[docs] +class LoadProductManager(pipeline.TaskBase): + """Loads a driftscan product manager from disk. + + Attributes + ---------- + product_directory : str + Path to the root of the products. This is the same as the output + directory used by ``drift-makeproducts``. + """ + + product_directory = config.Property(proptype=str) + +
+[docs] + def setup(self): + """Load the beam transfer matrices. + + Returns + ------- + manager : ProductManager + Object describing the telescope. + """ + import os + + from drift.core import manager + + if not os.path.exists(self.product_directory): + raise RuntimeError("Products do not exist.") + + # Load ProductManager and Timestream + return manager.ProductManager.from_config(self.product_directory)
+
+ + + +
+[docs] +class Truncate(task.SingleTask): + """Precision truncate data prior to saving with bitshuffle compression. + + If no configuration is provided, will look for preset values for the + input container. Any properties defined in the config will override the + presets. + + If available, each specified dataset will be truncated relative to a + (specified) weight dataset with the truncation increasing the variance up + to the specified maximum in `variance_increase`. If there is no specified + weight dataset then the truncation falls back to using the + `fixed_precision`. + + Attributes + ---------- + dataset : dict + Datasets to be truncated as keys. Possible values are: + - bool : Whether or not to truncate, using default fixed precision. + - float : Truncate to this relative precision. + - dict : Specify values for `weight_dataset`, `fixed_precision`, `variance_increase`. + ensure_chunked : bool + If True, ensure datasets are chunked according to their dataset_spec. + """ + + dataset = config.Property(proptype=dict, default=None) + ensure_chunked = config.Property(proptype=bool, default=True) + + default_params: ClassVar = { + "weight_dataset": None, + "fixed_precision": 1e-4, + "variance_increase": 1e-3, + } + + def _get_params(self, container, dset): + """Load truncation parameters for a dataset from config or container defaults. + + Parameters + ---------- + container + Container class. + dset : str + Dataset name + + Returns + ------- + Dict or None + Returns `None` if the dataset shouldn't get truncated. + """ + # Check if dataset should get truncated at all + if (self.dataset is None) or (dset not in self.dataset): + cdspec = container._class_dataset_spec() + if dset not in cdspec or not cdspec[dset].get("truncate", False): + self.log.debug(f"Not truncating dataset '{dset}' in {container}.") + return None + # Use the dataset spec if nothing specified in config + given_params = cdspec[dset].get("truncate", False) + else: + given_params = self.dataset[dset] + + # Parse config parameters + params = self.default_params.copy() + if isinstance(given_params, dict): + params.update(given_params) + elif isinstance(given_params, float): + params["fixed_precision"] = given_params + elif not given_params: + self.log.debug(f"Not truncating dataset '{dset}' in {container}.") + return None + + # Factor of 3 for variance over uniform distribution of truncation errors + if params["variance_increase"] is not None: + params["variance_increase"] *= 3 + + return params + +
+[docs] + def process(self, data): + """Truncate the incoming data. + + The truncation is done *in place*. + + Parameters + ---------- + data : containers.ContainerBase + Data to truncate. + + Returns + ------- + truncated_data : containers.ContainerBase + Truncated data. + + Raises + ------ + `caput.pipeline.PipelineRuntimeError` + If input data has mismatching dataset and weight array shapes. + `config.CaputConfigError` + If the input data container has no preset values and `fixed_precision` or + `variance_increase` are not set in the config. + """ + if self.ensure_chunked: + data._ensure_chunked() + + for dset in data.dataset_spec: + # get truncation parameters from config or container defaults + specs = self._get_params(type(data), dset) + + if (specs is None) or (dset not in data): + # Don't truncate this dataset + continue + + self.log.debug(f"Truncating {dset}") + + old_shape = data[dset][:].shape + # np.ndarray.reshape must be used with ndarrays + # MPIArrays use MPIArray.reshape() + val = np.ndarray.reshape(data[dset][:].view(np.ndarray), data[dset][:].size) + if specs["weight_dataset"] is None: + if np.iscomplexobj(data[dset]): + data[dset][:].real = truncate.bit_truncate_relative( + val.real, specs["fixed_precision"] + ).reshape(old_shape) + data[dset][:].imag = truncate.bit_truncate_relative( + val.imag, specs["fixed_precision"] + ).reshape(old_shape) + else: + data[dset][:] = truncate.bit_truncate_relative( + val, specs["fixed_precision"] + ).reshape(old_shape) + else: + invvar = ( + np.broadcast_to( + data[specs["weight_dataset"]][:], data[dset][:].shape + ) + .copy() + .reshape(-1) + ) + invvar *= (2.0 if np.iscomplexobj(data[dset]) else 1.0) / specs[ + "variance_increase" + ] + if np.iscomplexobj(data[dset]): + data[dset][:].real = truncate.bit_truncate_weights( + val.real, + invvar, + specs["fixed_precision"], + ).reshape(old_shape) + data[dset][:].imag = truncate.bit_truncate_weights( + val.imag, + invvar, + specs["fixed_precision"], + ).reshape(old_shape) + else: + data[dset][:] = truncate.bit_truncate_weights( + val, + invvar, + specs["fixed_precision"], + ).reshape(old_shape) + + return data
+
+ + + +
+[docs] +class ZipZarrContainers(task.SingleTask): + """Zip up a Zarr container into a single file. + + This is useful to save on file quota and speed up IO by combining the chunk + data into a single file. Note that the file cannot really be updated after + this process has been performed. + + As this process is IO limited in most cases, it will attempt to parallelise + the compression across different distinct nodes. That means at most only + one rank per node will participate. + + Attributes + ---------- + containers : list + The names of the Zarr containers to compress. The zipped files will + have the same names with `.zip` appended. + remove : bool + Remove the original data when finished. Defaults to True. + """ + + containers = config.Property(proptype=list) + remove = config.Property(proptype=bool, default=True) + + _host_rank = None + +
+[docs] + def setup(self, _=None): + """Setup the task. + + This routine does nothing at all with the input, but it means the + process won't run until the (optional) requirement is received. This + can be used to delay evaluation until you know that all the files are + available. + """ + import socket + + # See if we can find 7z + path_7z = shutil.which("7z") + if path_7z is None: + raise RuntimeError("Could not find 7z on the PATH") + self._path_7z = path_7z + + # Get the rank -> hostname mapping for all ranks + my_host = socket.gethostname() + my_rank = self.comm.rank + all_host_ranks = self.comm.allgather((my_host, my_rank)) + + # Identify the lowest rank running on each node + unique_hosts = {} + for host, rank in all_host_ranks: + if host not in unique_hosts: + unique_hosts[host] = rank + else: + if unique_hosts[host] > rank: + unique_hosts[host] = rank + + self._num_hosts = len(unique_hosts) + + # Figure out if this rank is one that needs to do anything + if unique_hosts[my_host] != my_rank: + # This is not the lowest rank on the host, so we don't do anything + self._host_rank = None + else: + # This is the lowest rank, so find where we are in the sorted list of all hosts + self._host_rank = sorted(unique_hosts).index(my_host) + self.log.debug(f"Lowest rank on {my_host}")
+ + +
+[docs] + def process(self): + """Compress the listed zarr containers. + + Only the lowest rank on each node will participate. + """ + if self._host_rank is not None: + # Get the set of containers this rank is responsible for compressing + my_containers = self.containers[self._host_rank :: self._num_hosts] + + for container in my_containers: + self.log.info(f"Zipping {container}") + + if not container.endswith(".zarr") or not os.path.isdir(container): + raise ValueError(f"{container} is not a valid .zarr directory") + + # Run 7z to zip up the file + dest_file = container + ".zip" + src_dir = container + "/." + command = [self._path_7z, "a", "-tzip", "-mx=0", dest_file, src_dir] + status = subprocess.run(command, capture_output=True) + + if status.returncode != 0: + self.log.debug("Error occurred while zipping. Debug logs follow...") + self.log.debug(f"stdout={status.stdout}") + self.log.debug(f"stderr={status.stderr}") + raise RuntimeError(f"Error occurred while zipping {container}.") + + self.log.info(f"Done zipping. Generated {dest_file}.") + + if self.remove: + shutil.rmtree(container) + self.log.info(f"Removed original container {container}.") + + self.comm.Barrier() + + raise pipeline.PipelineStopIteration
+
+ + + +
+[docs] +class ZarrZipHandle: + """A handle for keeping track of background Zarr-zipping job.""" + + def __init__(self, filename: str, handle: Optional[subprocess.Popen]): + self.filename = filename + self.handle = handle
+ + + +
+[docs] +class SaveZarrZip(ZipZarrContainers): + """Save a container as a .zarr.zip file. + + This task saves the output first as a .zarr container, and then starts a background + job to start turning it into a zip file. It returns a handle to this job. All these + handles should be fed into a `WaitZarrZip` task to ensure the pipeline run does not + terminate before they are complete. + + This accepts most parameters that a standard task would for saving, including + compression parameter overrides. + """ + + # This keeps track of the global number of operations run such that we can dispatch + # the background jobs to different ranks + _operation_counter = 0 + +
+[docs] + def setup(self): + """Check the parameters and determine the ranks to use.""" + if not self.output_name.endswith(".zarr.zip"): + raise ConfigError("File output name must end in `.zarr.zip`.") + + # Trim off the .zip suffix and fix the file format + self.output_name = self.output_name[:-4] + self.output_format = fileformats.Zarr + self.save = True + + # Call the baseclass to determine which ranks will do the work + super().setup()
+ + + # Override next as we don't want the usual mechanism +
+[docs] + def next(self, container: memh5.BasicCont) -> ZarrZipHandle: + """Take a container and save it out as a .zarr.zip file. + + Parameters + ---------- + container + Container to save out. + + Returns + ------- + handle + A handle to use to determine if the job has successfully completed. This + should be given to the `WaitZarrZip` task. + """ + outfile = self._save_output(container) + dest_file = outfile + ".zip" + self.comm.Barrier() + + bg_process = None + + host_rank_to_use = self._operation_counter % self._num_hosts + + if self._host_rank == host_rank_to_use: + self.log.info(f"Starting background job to zip {outfile}") + + # Run 7z to zip up the file + dest_file = outfile + ".zip" + src_dir = outfile + "/." + command = f"{self._path_7z} a -tzip -mx=0 {dest_file} {src_dir}" + + # If we are to remove the file get the background job to do it immediately + # after zipping succeeds + if self.remove: + command += f" && rm -r {outfile}" + + bg_process = subprocess.Popen( + command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + # Increment the global operations counter + self.__class__._operation_counter += 1 + + return ZarrZipHandle(dest_file, bg_process)
+
+ + + +
+[docs] +class WaitZarrZip(task.MPILoggedTask): + """Collect Zarr-zipping jobs and wait for them to complete.""" + + _handles: Optional[List[ZarrZipHandle]] = None + +
+[docs] + def next(self, handle: ZarrZipHandle): + """Receive the handles to wait on. + + Parameters + ---------- + handle + The handle to wait on. + """ + if self._handles is None: + self._handles = [] + + self._handles.append(handle)
+ + +
+[docs] + def finish(self): + """Wait for all Zarr zipping jobs to complete.""" + for h in self._handles: + self.log.debug(f"Waiting on job processing {h.filename}") + + if h.handle is not None: + returncode = h.handle.wait() + + if returncode != 0 or not os.path.exists(h.filename): + self.log.debug("Error occurred while zipping. Debug logs follow...") + self.log.debug(f"stdout={h.handle.stdout}") + self.log.debug(f"stderr={h.handle.stderr}") + raise RuntimeError(f"Error occurred while zipping {h.filename}.") + + self.comm.Barrier() + self.log.info(f"Processing job for {h.filename} successful.")
+
+ + + +
+[docs] +class SaveModuleVersions(task.SingleTask): + """Write module versions to a YAML file. + + The list of modules should be added to the configuration under key 'save_versions'. + The version strings are written to a YAML file. + + Attributes + ---------- + root : str + Root of the file name to output to. + """ + + root = config.Property(proptype=str) + + done = True + +
+[docs] + def setup(self): + """Save module versions.""" + fname = f"{self.root}_versions.yml" + f = open(fname, "w") + f.write(yamldump(self.versions)) + f.close() + self.done = True
+ + +
+[docs] + def process(self): + """Do nothing.""" + self.done = True + return
+
+ + + +
+[docs] +class SaveConfig(task.SingleTask): + """Write pipeline config to a text file. + + Yaml configuration document is written to a text file. + + Attributes + ---------- + root : str + Root of the file name to output to. + """ + + root = config.Property(proptype=str) + done = True + +
+[docs] + def setup(self): + """Save module versions.""" + fname = f"{self.root}_config.yml" + f = open(fname, "w") + f.write(yamldump(self.pipeline_config)) + f.close() + self.done = True
+ + +
+[docs] + def process(self): + """Do nothing.""" + self.done = True + return
+
+ + + +# Python types for objects convertible to beamtransfers or telescope instances +BeamTransferConvertible = Union[manager.ProductManager, beamtransfer.BeamTransfer] +TelescopeConvertible = Union[BeamTransferConvertible, telescope.TransitTelescope] + + +
+[docs] +def get_telescope(obj): + """Return a telescope object out of the input. + + Either `ProductManager`, `BeamTransfer`, or `TransitTelescope`. + """ + try: + return get_beamtransfer(obj).telescope + except RuntimeError: + if isinstance(obj, telescope.TransitTelescope): + return obj + + raise RuntimeError(f"Could not get telescope instance out of {obj!r}")
+ + + +
+[docs] +def get_beamtransfer(obj): + """Return a BeamTransfer object out of the input. + + Either `ProductManager` or `BeamTransfer`. + """ + if isinstance(obj, beamtransfer.BeamTransfer): + return obj + + if isinstance(obj, manager.ProductManager): + return obj.beamtransfer + + raise RuntimeError(f"Could not get BeamTransfer instance out of {obj!r}")
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/core/misc.html b/docs/_modules/draco/core/misc.html new file mode 100644 index 000000000..ad4c8e24e --- /dev/null +++ b/docs/_modules/draco/core/misc.html @@ -0,0 +1,461 @@ + + + + + + draco.core.misc — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.core.misc

+"""Miscellaneous pipeline tasks with no where better to go.
+
+Tasks should be proactively moved out of here when there is a thematically
+appropriate module, or enough related tasks end up in here such that they can
+all be moved out into their own module.
+"""
+
+import numpy as np
+from caput import config
+
+from ..core import containers, task
+from ..util import tools
+
+
+
+[docs] +class ApplyGain(task.SingleTask): + """Apply a set of gains to a timestream or sidereal stack. + + Attributes + ---------- + inverse : bool, optional + Apply the gains directly, or their inverse. + update_weight : bool, optional + Scale the weight array with the updated gains. + smoothing_length : float, optional + Smooth the gain timestream across the given number of seconds. + Not supported (ignored) for Sidereal Streams. + """ + + inverse = config.Property(proptype=bool, default=True) + update_weight = config.Property(proptype=bool, default=False) + smoothing_length = config.Property(proptype=float, default=None) + +
+[docs] + def process(self, tstream, gain): + """Apply gains to the given timestream. + + Smoothing the gains is not supported for SiderealStreams. + + Parameters + ---------- + tstream : TimeStream like or SiderealStream + Time stream to apply gains to. The gains are applied in place. + gain : StaticGainData, GainData, SiderealGainData, CommonModeGainData + or CommonModeSiderealGainData. Gains to apply. + + Returns + ------- + tstream : TimeStream or SiderealStream + The timestream with the gains applied. + """ + tstream.redistribute("freq") + gain.redistribute("freq") + + if tstream.is_stacked and not isinstance( + gain, (containers.CommonModeGainData, containers.CommonModeSiderealGainData) + ): + raise ValueError( + f"Cannot apply input-dependent gains to stacked data: {tstream!s}" + ) + + if isinstance(gain, containers.StaticGainData): + # Extract gain array and add in a time axis + gain_arr = gain.gain[:][..., np.newaxis] + + # Get the weight array if it's there + weight_arr = ( + gain.weight[:][..., np.newaxis] if gain.weight is not None else None + ) + + elif isinstance( + gain, + ( + containers.GainData, + containers.SiderealGainData, + containers.CommonModeGainData, + containers.CommonModeSiderealGainData, + ), + ): + # Extract gain array + gain_arr = gain.gain[:] + + # Regularise any crazy entries + gain_arr = np.nan_to_num(gain_arr) + + # Get the weight array if it's there + weight_arr = gain.weight[:] if gain.weight is not None else None + + if isinstance( + gain, + (containers.SiderealGainData, containers.CommonModeSiderealGainData), + ): + # Check that we are defined at the same RA samples + if (gain.ra != tstream.ra).any(): + raise RuntimeError( + "Gain data and sidereal stream defined at different RA samples." + ) + + else: + # We are using a time stream + + # Check that we are defined at the same time samples + if (gain.time != tstream.time).any(): + raise RuntimeError( + "Gain data and timestream defined at different time samples." + ) + + # Smooth the gain data if required + if self.smoothing_length is not None: + import scipy.signal as ss + + # Turn smoothing length into a number of samples + tdiff = gain.time[1] - gain.time[0] + samp = int(np.ceil(self.smoothing_length / tdiff)) + + # Ensure smoothing length is odd + l = 2 * (samp // 2) + 1 + + # Turn into 2D array (required by smoothing routines) + gain_r = gain_arr.reshape(-1, gain_arr.shape[-1]) + + # Smooth amplitude and phase separately + smooth_amp = ss.medfilt2d(np.abs(gain_r), kernel_size=[1, l]) + smooth_phase = ss.medfilt2d(np.angle(gain_r), kernel_size=[1, l]) + + # Recombine and reshape back to original shape + gain_arr = smooth_amp * np.exp(1.0j * smooth_phase) + gain_arr = gain_arr.reshape(gain.gain[:].shape) + + # Smooth weight array if it exists + if weight_arr is not None: + shp = weight_arr.shape + weight_arr = ss.medfilt2d( + weight_arr.reshape(-1, shp[-1]), kernel_size=[1, l] + ).reshape(shp) + + else: + raise RuntimeError("Format of `gain` argument is unknown.") + + # Regularise any crazy entries + gain_arr = np.nan_to_num(gain_arr) + + # Invert the gains as we need both the gains and the inverse to update + # the visibilities and the weights + inverse_gain_arr = tools.invert_no_zero(gain_arr) + + # Apply gains to visibility matrix + self.log.info("Applying inverse gain." if self.inverse else "Applying gain.") + gvis = inverse_gain_arr if self.inverse else gain_arr + if isinstance(gain, containers.SiderealGainData): + # Need a prod_map for sidereal streams + tools.apply_gain( + tstream.vis[:], gvis, out=tstream.vis[:], prod_map=tstream.prod + ) + elif isinstance( + gain, (containers.CommonModeGainData, containers.CommonModeSiderealGainData) + ): + # Apply the gains to all 'prods/stacks' directly: + tstream.vis[:] *= np.abs(gvis[:, np.newaxis, :]) ** 2 + else: + tools.apply_gain(tstream.vis[:], gvis, out=tstream.vis[:]) + + # Apply gains to the weights + if self.update_weight: + self.log.info("Applying gain to weight.") + gweight = np.abs(gain_arr if self.inverse else inverse_gain_arr) ** 2 + else: + gweight = np.ones_like(gain_arr, dtype=np.float64) + + if weight_arr is not None: + gweight *= (weight_arr[:] > 0.0).astype(np.float64) + + if isinstance(gain, containers.SiderealGainData): + # Need a prod_map for sidereal streams + tools.apply_gain( + tstream.weight[:], gweight, out=tstream.weight[:], prod_map=tstream.prod + ) + elif isinstance( + gain, (containers.CommonModeGainData, containers.CommonModeSiderealGainData) + ): + # Apply the gains to all 'prods/stacks' directly: + tstream.weight[:] *= gweight[:, np.newaxis, :] ** 2 + else: + tools.apply_gain(tstream.weight[:], gweight, out=tstream.weight[:]) + + # Update units if they were specified + convert_units_to = gain.gain.attrs.get("convert_units_to") + if convert_units_to is not None: + tstream.vis.attrs["units"] = convert_units_to + + return tstream
+
+ + + +
+[docs] +class AccumulateList(task.MPILoggedTask): + """Accumulate the inputs into a list and return when the task *finishes*.""" + + def __init__(self): + super().__init__() + self._items = [] + +
+[docs] + def next(self, input_): + """Append an input to the list of inputs.""" + self._items.append(input_)
+ + +
+[docs] + def finish(self): + """Remove the internal reference. + + Prevents the items from hanging around after the task finishes. + """ + items = self._items + del self._items + + return items
+
+ + + +
+[docs] +class CheckMPIEnvironment(task.MPILoggedTask): + """Check that the current MPI environment can communicate across all nodes.""" + + timeout = config.Property(proptype=int, default=240) + +
+[docs] + def setup(self): + """Send random messages between all ranks. + + Tests to ensure that all messages are received within a specified amount + of time, and that the messages received are the same as those sent (i.e. + nothing was corrupted). + """ + import time + + comm = self.comm + n = 500000 # Corresponds to a 4 MB buffer + results = [] + + sends = np.arange(comm.size * n, dtype=np.float64).reshape(comm.size, n) + recvs = np.empty_like(sends) + + # Send and receive across all ranks + for i in range(comm.size): + send = (comm.rank + i) % comm.size + recv = (comm.rank - i) % comm.size + + results.append(comm.Irecv(recvs[recv, :], recv)) + comm.Isend(sends[comm.rank, :], send) + + start_time = time.time() + + while time.time() - start_time < self.timeout: + success = all(r.get_status() for r in results) + + if success: + break + + time.sleep(5) + + if not success: + self.log.critical( + f"MPI test failed to respond in {self.timeout} seconds. Aborting..." + ) + comm.Abort() + + if not (recvs == sends).all(): + self.log.critical("MPI test did not receive the correct data. Aborting...") + comm.Abort() + + # Stop successful processes from finshing if any task has failed + comm.Barrier() + + self.log.debug( + f"MPI test successful after {time.time() - start_time:.1f} seconds" + )
+
+ + + +
+[docs] +class MakeCopy(task.SingleTask): + """Make a copy of the passed container.""" + +
+[docs] + def process(self, data): + """Return a copy of the given container. + + Parameters + ---------- + data : containers.ContainerBase + The container to copy. + """ + return data.copy()
+
+ + + +
+[docs] +class WaitUntil(task.MPILoggedTask): + """Wait until the the requires before forwarding inputs. + + This simple synchronization task will forward on whatever inputs it gets, however, it won't do + this until it receives any requirement to it's setup method. This allows certain parts of the + pipeline to be delayed until a piece of data further up has been generated. + """ + +
+[docs] + def setup(self, input_): + """Accept, but don't save any input.""" + self.log.info("Received the requirement, starting to forward inputs") + pass
+ + +
+[docs] + def next(self, input_): + """Immediately forward any input.""" + return input_
+
+ + + +
+[docs] +class PassOn(task.MPILoggedTask): + """Unconditionally forward a tasks input. + + While this seems like a pointless no-op it's useful for connecting tasks in complex + topologies. + """ + +
+[docs] + def next(self, input_): + """Immediately forward any input.""" + return input_
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/core/task.html b/docs/_modules/draco/core/task.html new file mode 100644 index 000000000..ff13bf0e8 --- /dev/null +++ b/docs/_modules/draco/core/task.html @@ -0,0 +1,874 @@ + + + + + + draco.core.task — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.core.task

+"""An improved base task implementing easy (and explicit) saving of outputs."""
+
+import logging
+import os
+from inspect import getfullargspec
+from typing import Optional
+
+import numpy as np
+from caput import config, fileformats, memh5, pipeline
+
+
+
+[docs] +class MPILogFilter(logging.Filter): + """Filter log entries by MPI rank. + + Also this will optionally add MPI rank information, and add an elapsed time + entry. + + Parameters + ---------- + add_mpi_info : boolean, optional + Add MPI rank/size info to log records that don't already have it. + level_rank0 : int + Log level for messages from rank=0. + level_all : int + Log level for messages from all other ranks. + """ + + def __init__( + self, add_mpi_info=True, level_rank0=logging.INFO, level_all=logging.WARN + ): + from mpi4py import MPI + + self.add_mpi_info = add_mpi_info + + self.level_rank0 = level_rank0 + self.level_all = level_all + + self.comm = MPI.COMM_WORLD + +
+[docs] + def filter(self, record): + """Add MPI info if desired.""" + try: + record.mpi_rank + except AttributeError: + if self.add_mpi_info: + record.mpi_rank = self.comm.rank + record.mpi_size = self.comm.size + + # Add a new field with the elapsed time in seconds (as a float) + record.elapsedTime = record.relativeCreated * 1e-3 + + # Return whether we should filter the record or not. + return (record.mpi_rank == 0 and record.levelno >= self.level_rank0) or ( + record.mpi_rank > 0 and record.levelno >= self.level_all + )
+
+ + + +def _log_level(x): + """Interpret the input as a logging level. + + Parameters + ---------- + x : int or str + Explicit integer logging level or one of 'DEBUG', 'INFO', 'WARN', + 'ERROR' or 'CRITICAL'. + + Returns + ------- + level : int + """ + level_dict = { + "DEBUG": logging.DEBUG, + "INFO": logging.INFO, + "WARN": logging.WARN, + "WARNING": logging.WARN, + "ERROR": logging.ERROR, + "CRITICAL": logging.CRITICAL, + } + + if isinstance(x, int): + return x + + if isinstance(x, str) and x in level_dict: + return level_dict[x.upper()] + + raise ValueError(f"Logging level {x!r} not understood") + + +
+[docs] +class SetMPILogging(pipeline.TaskBase): + """A task used to configure MPI aware logging. + + Attributes + ---------- + level_rank0, level_all : int or str + Log level for rank=0, and other ranks respectively. + """ + + level_rank0 = config.Property(proptype=_log_level, default=logging.INFO) + level_all = config.Property(proptype=_log_level, default=logging.WARN) + + def __init__(self): + import math + + from mpi4py import MPI + + logging.captureWarnings(True) + + rank_length = int(math.log10(MPI.COMM_WORLD.size)) + 1 + + mpi_fmt = "[MPI %%(mpi_rank)%id/%%(mpi_size)%id]" % (rank_length, rank_length) + filt = MPILogFilter(level_all=self.level_all, level_rank0=self.level_rank0) + + # This uses the fact that caput.pipeline.Manager has already + # attempted to set up the logging. We just insert our custom filter + root_logger = logging.getLogger() + ch = root_logger.handlers[0] + ch.addFilter(filt) + + formatter = logging.Formatter( + "%(elapsedTime)8.1fs " + + mpi_fmt + + " - %(levelname)-8s %(name)s: %(message)s" + ) + + ch.setFormatter(formatter)
+ + + +
+[docs] +class LoggedTask(pipeline.TaskBase): + """A task with logger support.""" + + log_level = config.Property(proptype=_log_level, default=None) + + def __init__(self): + # Get the logger for this task + self._log = logging.getLogger(f"{self.__module__}.{self.__class__.__name__}") + + # Set the log level for this task if specified + if self.log_level is not None: + self.log.setLevel(self.log_level) + + @property + def log(self): + """The logger object for this task.""" + return self._log
+ + + +
+[docs] +class MPITask(pipeline.TaskBase): + """Base class for MPI using tasks. + + Just ensures that the task gets a `comm` attribute. + """ + + comm = None + + def __init__(self): + from mpi4py import MPI + + # Set default communicator + self.comm = MPI.COMM_WORLD
+ + + +class _AddRankLogAdapter(logging.LoggerAdapter): + """Add the rank of the logging process to a log message. + + Attributes + ---------- + calling_obj : object + An object with a `comm` property that will be queried for the rank. + """ + + calling_obj = None + + def process(self, msg, kwargs): + if "extra" not in kwargs: + kwargs["extra"] = {} + + kwargs["extra"]["mpi_rank"] = self.calling_obj.comm.rank + kwargs["extra"]["mpi_size"] = self.calling_obj.comm.size + + return msg, kwargs + + +
+[docs] +class MPILoggedTask(MPITask, LoggedTask): + """A task base that has MPI aware logging.""" + + def __init__(self): + # Initialise the base classes + MPITask.__init__(self) + LoggedTask.__init__(self) + + # Replace the logger with a LogAdapter instance that adds MPI process + # information + logadapter = _AddRankLogAdapter(self._log, None) + logadapter.calling_obj = self + self._log = logadapter
+ + + +
+[docs] +class SingleTask(MPILoggedTask, pipeline.BasicContMixin): + """Process a task with at most one input and output. + + Both input and output are expected to be :class:`memh5.BasicCont` objects. + This class allows writing of the output when requested. + + Tasks inheriting from this class should override `process` and optionally + :meth:`setup` or :meth:`finish`. They should not override :meth:`next`. + + If the value of :attr:`input_root` is anything other than the string "None" + then the input will be read (using :meth:`read_input`) from the file + ``self.input_root + self.input_filename``. If the input is specified both as + a filename and as a product key in the pipeline configuration, an error + will be raised upon initialization. + + + If the value of :attr:`output_root` is anything other than the string + "None" then the output will be written (using :meth:`write_output`) to the + file ``self.output_root + self.output_filename``. + + Attributes + ---------- + save : bool + Whether to save the output to disk or not. + attrs : dict, optional + A mapping of attribute names and values to set in the `.attrs` at the root of + the output container. String values will be formatted according to the standard + Python `.format(...)` rules, and can interpolate several other values into the + string. These are: + + - `count`: an integer giving which iteration of the task is this. + - `tag`: a string identifier for the output derived from the + containers `tag` attribute. If that attribute is not present + `count` is used instead. + - `key`: the name of the output key. + - `task`: the (unqualified) name of the task. + - `input_tags`: a list of the tags for each input argument for the task. + - Any existing attribute in the container can be interpolated by the name of + its key. The specific values above will override any attribute with the same + name. + + Incorrectly formatted values will cause an error to be thrown. + tag : str, optional + Set a format for the tag attached to the output. This is a Python format string + which can interpolate the variables listed under `attrs` above. For example a + tag of "cat{count}" will generate catalogs with the tags "cat1", "cat2", etc. + output_name : string + A python format string used to construct the filename. All variables given under + `attrs` above can be interpolated into the filename. + Valid identifiers are: + + - `count`: an integer giving which iteration of the task is this. + - `tag`: a string identifier for the output derived from the + containers `tag` attribute. If that attribute is not present + `count` is used instead. + - `key`: the name of the output key. + - `task`: the (unqualified) name of the task. + - `output_root`: the value of the output root argument. This is deprecated + and is just used for legacy support. The default value of + `output_name` means the previous behaviour works. + + compression : dict or bool, optional + Set compression options for each dataset. Provided as a dict with the dataset + names as keys and values for `chunks`, `compression`, and `compression_opts`. + Any datasets not included in the dict (including if the dict is empty), will use + the default parameters set in the dataset spec. If set to `False` (or anything + that evaluates to `False`, other than an empty dict) chunks and compression will + be disabled for all datasets. If no argument in provided, the default parameters + set in the dataset spec are used. Note that this will modify these parameters on + the container itself, such that if it is written out again downstream in the + pipeline these will be used. + output_root : string + Pipeline settable parameter giving the first part of the output path. + Deprecated in favour of `output_name`. + nan_check : bool + Check the output for NaNs (and infs) logging if they are present. + nan_dump : bool + If NaN's are found, dump the container to disk. + nan_skip : bool + If NaN's are found, don't pass on the output. + versions : dict + Keys are module names (str) and values are their version strings. This is + attached to output metadata. + pipeline_config : dict + Global pipeline configuration. This is attached to output metadata. + + Raises + ------ + `caput.pipeline.PipelineRuntimeError` + If this is used as a baseclass to a task overriding `self.process` with variable + length or optional arguments. + """ + + save = config.Property(default=False, proptype=bool) + + output_root = config.Property(default="", proptype=str) + output_name = config.Property(default="{output_root}{tag}.h5", proptype=str) + output_format = config.file_format() + compression = config.Property( + default=True, proptype=lambda x: x if isinstance(x, dict) else bool(x) + ) + + nan_check = config.Property(default=True, proptype=bool) + nan_skip = config.Property(default=True, proptype=bool) + nan_dump = config.Property(default=True, proptype=bool) + + # Metadata to get attached to the output + versions = config.Property(default={}, proptype=dict) + pipeline_config = config.Property(default={}, proptype=dict) + + tag = config.Property(proptype=str, default="{tag}") + attrs = config.Property(proptype=dict, default=None) + + _count = 0 + + done = False + _no_input = False + + def __init__(self): + super().__init__() + + # Inspect the `process` method to see how many arguments it takes. + pro_argspec = getfullargspec(self.process) + n_args = len(pro_argspec.args) - 1 + + if pro_argspec.varargs or pro_argspec.varkw or pro_argspec.defaults: + msg = ( + "`process` method may not have variable length or optional" + " arguments." + ) + raise pipeline.PipelineRuntimeError(msg) + + if n_args == 0: + self._no_input = True + else: + self._no_input = False + +
+[docs] + def next(self, *input): + """Should not need to override. Implement `process` instead.""" + self.log.info(f"Starting next for task {self.__class__.__name__}") + + self.comm.Barrier() + + # This should only be called once. + try: + if self.done: + raise pipeline.PipelineStopIteration() + except AttributeError: + self.done = True + + # Extract a list of the tags for all input arguments + input_tags = [ + ( + str(icont.attrs.get("tag")) + if isinstance(icont, memh5.MemDiskGroup) + else "" + ) + for icont in input + ] + + # Process input and fetch output + if self._no_input: + if len(input) > 0: + # This should never happen. Just here to catch bugs. + raise RuntimeError("Somehow `input` was set.") + output = self.process() + else: + output = self.process(*input) + + # Return immediately if output is None to skip writing phase. + if output is None: + return None + + # Insert the input tags into the output container + output.attrs["input_tags"] = input_tags + + output = self._process_output(output) + + # Increment internal counter + self._count = self._count + 1 + + self.log.info(f"Leaving next for task {self.__class__.__name__}") + + # Return the output for the next task + return output
+ + +
+[docs] + def finish(self): + """Should not need to override. Implement `process_finish` instead.""" + class_name = self.__class__.__name__ + + self.log.info(f"Starting finish for task {class_name}") + + if not hasattr(self, "process_finish"): + self.log.info(f"No finish for task {class_name}") + return None + + output = self.process_finish() + + # Return immediately if output is None to skip writing phase. + if output is None: + self.log.info(f"Leaving finish for task {class_name}") + return None + + output = self._process_output(output) + + self.log.info(f"Leaving finish for task {class_name}") + + return output
+ + + def _process_output(self, output): + if not isinstance(output, memh5.MemDiskGroup): + raise pipeline.PipelineRuntimeError( + f"Task must output a valid memh5 container; given {type(output)}" + ) + + # Set the tag according to the format + idict = self._interpolation_dict(output) + + # Set the attributes in the output container (including from the `tag` config + # option) + attrs_to_set = {} if self.attrs is None else self.attrs.copy() + attrs_to_set["tag"] = self.tag + for attrname, attrval in attrs_to_set.items(): + if isinstance(attrval, str): + attrval = attrval.format(**idict) + output.attrs[attrname] = attrval + + # Check for NaN's etc + output = self._nan_process_output(output) + + # Write the output if needed + self._save_output(output) + + return output + + def _save_output(self, output: memh5.MemDiskGroup) -> Optional[str]: + """Save the output and return the file path if it was saved.""" + if output is None: + return None + + # Parse compression/chunks options + def walk_dset_tree(grp, root=""): + # won't find forbidden datasets like index_map but we are not compressing those + datasets = [] + for key in grp: + if isinstance(grp[key], memh5.MemGroup): + datasets += walk_dset_tree(grp[key], f"{root}{key}/") + else: + datasets.append(root + key) + return datasets + + if isinstance(self.compression, dict): + # We want to overwrite some compression settings + datasets = walk_dset_tree(output) + for ds in self.compression: + if ds in datasets: + for key, val in self.compression[ds].items(): + self.log.debug( + f"Overriding default compression setting on dataset {ds}: {key}={val}." + ) + setattr(output._data._storage_root[ds], key, val) + # shorthand for bitshuffle + if output[ds].compression in ( + "bitshuffle", + fileformats.H5FILTER, + ): + output[ds].compression = fileformats.H5FILTER + if output[ds].compression_opts is None: + output._data._storage_root[ds].compression_opts = ( + 0, + fileformats.H5_COMPRESS_LZ4, + ) + else: + self.log.warning( + f"Ignoring config entry in `compression` for non-existing dataset `{ds}`" + ) + elif not self.compression: + # Disable compression + for ds in walk_dset_tree(output): + output._data._storage_root[ds].chunks = None + output._data._storage_root[ds].compression = None + output._data._storage_root[ds].compression_opts = None + + # Routine to write output if needed. + if self.save: + # add metadata to output + metadata = {"versions": self.versions, "config": self.pipeline_config} + for key, value in metadata.items(): + output.add_history(key, value) + + # Construct the filename + name_parts = self._interpolation_dict(output) + if self.output_root != "": + self.log.warn("Use of `output_root` is deprecated.") + name_parts["output_root"] = self.output_root + outfile = self.output_name.format(**name_parts) + + # Expand any variables in the path + outfile = os.path.expanduser(outfile) + outfile = os.path.expandvars(outfile) + + self.log.debug("Writing output %s to disk.", outfile) + self.write_output( + outfile, + output, + file_format=self.output_format, + ) + return outfile + + return None + + def _nan_process_output(self, output): + # Process the output to check for NaN's + # Returns the output or, None if it should be skipped + + if not isinstance(output, memh5.MemDiskGroup): + raise pipeline.PipelineRuntimeError( + f"Task must output a valid memh5 container; given {type(output)}" + ) + + if self.nan_check: + nan_found = self._nan_check_walk(output) + + if nan_found and self.nan_dump: + # Construct the filename + tag = output.attrs["tag"] if "tag" in output.attrs else self._count + outfile = "nandump_" + self.__class__.__name__ + "_" + str(tag) + ".h5" + self.log.debug("NaN found. Dumping %s", outfile) + self.write_output(outfile, output) + + if nan_found and self.nan_skip: + self.log.debug("NaN found. Skipping output.") + return None + + return output + + def _interpolation_dict(self, output): + # Get the set of variables the can be interpolated into the various strings + idict = dict(output.attrs) + if "tag" in output.attrs: + tag = output.attrs["tag"] + elif "input_tags" in output.attrs and len(output.attrs["input_tags"]): + tag = output.attrs["input_tags"][0] + else: + tag = self._count + + idict.update( + tag=tag, + count=self._count, + task=self.__class__.__name__, + key=( + self._out_keys[0] + if hasattr(self, "_out_keys") and self._out_keys + else "" + ), + output_root=self.output_root, + ) + return idict + + def _nan_check_walk(self, cont): + # Walk through a memh5 container and check for NaN's and Inf's. + # Logs any issues found and returns True if there were any found. + from mpi4py import MPI + + if isinstance(cont, memh5.MemDiskGroup): + cont = cont._data + + stack = [cont] + found = False + + # Walk over the container tree... + while stack: + n = stack.pop() + + # Check the dataset for non-finite numbers + if isinstance(n, memh5.MemDataset): + # Try to test for NaN's and infs. This will fail for compound datatypes... + # casting to ndarray, bc MPI ranks may fall out of sync, if a nan or inf are found + arr = n[:].view(np.ndarray) + try: + all_finite = np.isfinite(arr).all() + except TypeError: + continue + + if not all_finite: + nans = np.isnan(arr).sum() + if nans > 0: + self.log.info( + f"NaN's found in dataset {n.name} [{nans} of {arr.size} elements]" + ) + found = True + break + + infs = np.isinf(arr).sum() + if infs > 0: + self.log.info( + f"Inf's found in dataset {n.name} [{infs} of {arr.size} elements]" + ) + found = True + break + + elif isinstance(n, (memh5.MemGroup, memh5.MemDiskGroup)): + for item in n.values(): + stack.append(item) + + # All ranks need to know if any rank found a NaN/Inf + return self.comm.allreduce(found, op=MPI.MAX)
+ + + +
+[docs] +class ReturnLastInputOnFinish(SingleTask): + """Workaround for `caput.pipeline` issues. + + This caches its input on every call to `process` and then returns + the last one for a finish call. + """ + + x = None + +
+[docs] + def process(self, x): + """Take a reference to the input. + + Parameters + ---------- + x : object + Object to cache + """ + self.x = x
+ + +
+[docs] + def process_finish(self): + """Return the last input to process. + + Returns + ------- + x : object + Last input to process. + """ + return self.x
+
+ + + +
+[docs] +class ReturnFirstInputOnFinish(SingleTask): + """Workaround for `caput.pipeline` issues. + + This caches its input on the first call to `process` and + then returns it for a finish call. + """ + + x = None + +
+[docs] + def process(self, x): + """Take a reference to the input. + + Parameters + ---------- + x : object + Object to cache + """ + if self.x is None: + self.x = x
+ + +
+[docs] + def process_finish(self): + """Return the last input to process. + + Returns + ------- + x : object + Last input to process. + """ + return self.x
+
+ + + +
+[docs] +class Delete(SingleTask): + """Delete pipeline products to free memory.""" + +
+[docs] + def process(self, x): + """Delete the input and collect garbage. + + Parameters + ---------- + x : object + The object to be deleted. + """ + import gc + + self.log.info(f"Deleting {type(x)!s}") + del x + gc.collect()
+
+ + + +
+[docs] +def group_tasks(*tasks): + """Create a Task that groups a bunch of tasks together. + + This method creates a class that inherits from all the subtasks, and + calls each `process` method in sequence, passing the output of one to the + input of the next. + + This should be used like: + + >>> class SuperTask(group_tasks(SubTask1, SubTask2)): + >>> pass + + At the moment if the ensemble has more than one setup method, the + SuperTask will need to implement an override that correctly calls each. + """ + + class TaskGroup(*tasks): + # TODO: figure out how to make the setup work at the moment it just picks the first in MRO + # def setup(self, x): pass + + def process(self, x): + for t in tasks: + self.log.debug("Calling process for subtask %s", t.__name__) + x = t.process(self, x) + + return x + + return TaskGroup
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/synthesis/gain.html b/docs/_modules/draco/synthesis/gain.html new file mode 100644 index 000000000..9659de17c --- /dev/null +++ b/docs/_modules/draco/synthesis/gain.html @@ -0,0 +1,743 @@ + + + + + + draco.synthesis.gain — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.synthesis.gain

+"""Tasks for generating random gain fluctuations in the data and stacking them."""
+
+import numpy as np
+from caput import config, mpiarray, pipeline
+
+from ..core import containers, io, task
+
+
+
+[docs] +class BaseGains(task.SingleTask): + """Rudimentary class to generate gain timestreams. + + The gains are drawn for times which match up to an input timestream file. + + Attributes + ---------- + amp: bool + Generate gain amplitude fluctuations. Default is True. + phase: bool + Generate gain phase fluctuations. Default is True. + """ + + amp = config.Property(default=True, proptype=bool) + phase = config.Property(default=True, proptype=bool) + + _prev_time = None + +
+[docs] + def process(self, data): + """Generate a gain timestream for the inputs and times in `data`. + + Parameters + ---------- + data : :class:`containers.TimeStream` + Generate gain errors for `data`. + + Returns + ------- + gain : :class:`containers.GainData` + """ + data.redistribute("freq") + + time = data.time + + gain_data = containers.GainData(axes_from=data, comm=data.comm) + gain_data.redistribute("input") + + # Save some useful attributes + self.ninput_local = gain_data.gain.local_shape[1] + self.ninput_global = gain_data.gain.global_shape[1] + self.freq = data.index_map["freq"]["centre"][:] + + gain_amp = 1.0 + gain_phase = 0.0 + + if self.amp: + gain_amp = self._generate_amp(time) + + if self.phase: + gain_phase = self._generate_phase(time) + + # Combine into an overall gain fluctuation + gain_comb = gain_amp * np.exp(1.0j * gain_phase) + + # Copy the gain entries into the output container + gain = mpiarray.MPIArray.wrap(gain_comb, axis=1, comm=data.comm) + gain_data.gain[:] = gain + + # Keep a reference to time around for the next round + self._prev_time = time + + return gain_data
+ + + def _corr_func(self, zeta, amp): + """Generate the correlation function. + + Parameters + ---------- + zeta: float + Correlation length + amp : float + Amplitude (given as standard deviation) of fluctuations. + """ + + def _cf(x): + dij = x[:, np.newaxis] - x[np.newaxis, :] + return amp**2 * np.exp(-0.5 * (dij / zeta) ** 2) + + return _cf + + def _generate_amp(self, time): + """Generate phase gain errors. + + This implementation is blank. Must be overriden. + + Parameters + ---------- + time : np.ndarray + Generate amplitude fluctuations for this time period. + """ + raise NotImplementedError + + def _generate_phase(self, time): + """Generate phase gain errors. + + This implementation is blank. Must be overriden. + + Parameters + ---------- + time : np.ndarray + Generate phase fluctuations for this time period. + """ + raise NotImplementedError
+ + + +
+[docs] +class SiderealGains(BaseGains): + """Task for simulating sidereal gains. + + This base class is useful for generating gain errors in sidereal time. + The simulation period is set by `start_time` and `end_time` and does not + need any input to `process`. + + Attributes + ---------- + start_time, end_time : float or datetime + Start and end times of the gain timestream to simulate. Needs to be either a + `float` (UNIX time) or a `datetime` objects in UTC. This determines the set + of LSDs to generate data for. + """ + + start_time = config.utc_time() + end_time = config.utc_time() + +
+[docs] + def setup(self, bt, sstream): + """Set up an observer and the data to use for this simulation. + + Parameters + ---------- + bt : beamtransfer.BeamTransfer or manager.ProductManager + Sets up an observer holding the geographic location of the telscope. + sstream : containers.SiderealStream + The sidereal data to use for this gain simulation. + """ + self.observer = io.get_telescope(bt) + self.lsd_start = self.observer.unix_to_lsd(self.start_time) + self.lsd_end = self.observer.unix_to_lsd(self.end_time) + + self.log.info( + "Sidereal period requested: LSD=%i to LSD=%i", + int(self.lsd_start), + int(self.lsd_end), + ) + + # Initialize the current lsd time + self._current_lsd = None + self.sstream = sstream
+ + +
+[docs] + def process(self): + """Generate a gain timestream for the inputs and times in `data`. + + Returns + ------- + gain : :class:`containers.SiderealGainData` + Simulated gain errors in sidereal time. + """ + # If current_lsd is None then this is the first time we've run + if self._current_lsd is None: + # Check if lsd is an integer, if not add an lsd + if isinstance(self.lsd_start, int): + self._current_lsd = int(self.lsd_start) + else: + self._current_lsd = int(self.lsd_start + 1) + + # Check if we have reached the end of the requested time + if self._current_lsd >= self.lsd_end: + raise pipeline.PipelineStopIteration + + # Convert the current lsd day to unix time + unix_start = self.observer.lsd_to_unix(self._current_lsd) + unix_end = self.observer.lsd_to_unix(self._current_lsd + 1) + + # Distribute the sidereal data and create a time array + data = self.sstream + data.redistribute("freq") + self.freq = data.index_map["freq"]["centre"][:] + nra = len(data.ra) + time = np.linspace(unix_start, unix_end, nra, endpoint=False) + + # Make a sidereal gain data container + gain_data = containers.SiderealGainData(axes_from=data, comm=data.comm) + gain_data.redistribute("input") + + self.ninput_local = gain_data.gain.local_shape[1] + self.ninput_global = gain_data.gain.global_shape[1] + + gain_amp = 1.0 + gain_phase = 0.0 + + if self.amp: + gain_amp = self._generate_amp(time) + + if self.phase: + gain_phase = self._generate_phase(time) + + # Combine into an overall gain fluctuation + gain_comb = gain_amp * np.exp(1.0j * gain_phase) + + # Copy the gain entries into the output container + gain = mpiarray.MPIArray.wrap(gain_comb, axis=1, comm=data.comm) + gain_data.gain[:] = gain + gain_data.attrs["lsd"] = self._current_lsd + gain_data.attrs["tag"] = "lsd_%i" % self._current_lsd + + # Increment current lsd + self._current_lsd += 1 + + # Keep a reference to time around for the next round + self._prev_time = time + + return gain_data
+
+ + + +
+[docs] +class RandomGains(BaseGains): + r"""Generate random gains. + + Notes + ----- + The Random Gains class generates random fluctuations in gain amplitude and + phase. + + Attributes + ---------- + corr_length_amp, corr_length_phase : float + Correlation length for amplitude and phase fluctuations in seconds. + sigma_amp, sigma_phase : float + Size of fluctuations for amplitude (fractional), and phase (radians). + + Notes + ----- + This task generates gain time streams which are Gaussian distributed with + covariance + + .. math:: + C_{ij} = \sigma^2 \exp{\left(-\frac{1}{2 \xi^2}(t_i - t_j)^2\right)} + + As the time stream is generated in separate pieces, to ensure that there is + consistency between them each gain time stream is drawn as a constrained + realisation against the previous file. + """ + + corr_length_amp = config.Property(default=3600.0, proptype=float) + corr_length_phase = config.Property(default=3600.0, proptype=float) + + sigma_amp = config.Property(default=0.02, proptype=float) + sigma_phase = config.Property(default=0.1, proptype=float) + + _prev_amp = None + _prev_phase = None + + def _generate_amp(self, time): + # Generate the correlation function + cf_amp = self._corr_func(self.corr_length_amp, self.sigma_amp) + ninput = self.ninput_local + num_realisations = len(self.freq) * ninput + ntime = len(time) + + # Generate amplitude fluctuations + gain_amp = generate_fluctuations( + time, cf_amp, num_realisations, self._prev_time, self._prev_amp + ) + + # Save amplitude fluctuations to instannce + self._prev_amp = gain_amp + + gain_amp = gain_amp.reshape((len(self.freq), ninput, ntime)) + return 1.0 + gain_amp + + def _generate_phase(self, time): + # Generate the correlation function + cf_phase = self._corr_func(self.corr_length_phase, self.sigma_phase) + ninput = self.ninput_local + num_realisations = len(self.freq) * ninput + ntime = len(time) + + # Generate phase fluctuations + gain_phase_fluc = generate_fluctuations( + time, cf_phase, num_realisations, self._prev_time, self._prev_phase + ) + + # Save phase fluctuations to instannce + self._prev_phase = gain_phase_fluc + # Reshape to correct size + return gain_phase_fluc.reshape((len(self.freq), ninput, ntime))
+ + + +
+[docs] +class RandomSiderealGains(RandomGains, SiderealGains): + """Generate random gains on a Sidereal grid. + + See the documentation for `RandomGains` and `SiderealGains` for more detail. + """ + + pass
+ + + +
+[docs] +class GainStacker(task.SingleTask): + r"""Take sidereal gain data, make products and stack them up. + + Attributes + ---------- + only_gains : bool + Whether to return only the stacked gains or the stacked gains + mulitplied with the visibilites. Default: False. + + Notes + ----- + This task generates products of gain time streams for every sidereal day and + stacks them up over the number of days in the simulation. + + More formally a gain stack can be described as + + .. math:: + G_{ij} = \sum_{a}^{Ndays} g_{i}(t)^{a} g_j(t)^{*a} + """ + + only_gains = config.Property(default=False, proptype=bool) + + gain_stack = None + lsd_list = None + +
+[docs] + def setup(self, stream): + """Get the sidereal stream onto which we stack the simulated gain data. + + Parameters + ---------- + stream : containers.SiderealStream or containers.TimeStream + The sidereal or time data to use. + """ + self.stream = stream
+ + +
+[docs] + def process(self, gain): + """Make sidereal gain products and stack them up. + + Parameters + ---------- + gain : containers.SiderealGainData or containers.GainData + Individual sidereal or time ordered gain data. + + Returns + ------- + gain_stack : containers.TimeStream or containers.SiderealStream + Stacked products of gains. + """ + stream = self.stream + + prod = stream.index_map["prod"] + + if "lsd" in gain.attrs: + input_lsd = gain.attrs["lsd"] + else: + input_lsd = -1 + + input_lsd = _ensure_list(input_lsd) + + # If gain_stack is None create an MPIArray to hold the product expanded + # gain data and redistribute over all freq + if self.gain_stack is None: + self.gain_stack = containers.empty_like(stream) + self.gain_stack.redistribute("freq") + gain.redistribute("freq") + + gsv = self.gain_stack.vis[:] + g = gain.gain[:] + + for pi, (ii, jj) in enumerate(prod): + gsv[:, pi, :] = g[:, ii] * np.conjugate(g[:, jj]) + + self.gain_stack.weight[:] = np.ones(self.gain_stack.vis.local_shape) + + self.lsd_list = input_lsd + + self.log.info("Starting gain stack with LSD:%i", input_lsd[0]) + + return + + # Keep gains around for next round, save current lsd to list, log + self.log.info("Adding LSD:%i to gain stack", gain.attrs["lsd"]) + + gain.redistribute("freq") + gsv = self.gain_stack.vis[:] + g = gain.gain[:] + + # Calculate the gain products + for pi, (ii, jj) in enumerate(prod): + gsv[:, pi] += g[:, ii] * np.conjugate(g[:, jj]) + + self.gain_stack.weight[:] += np.ones(self.gain_stack.vis.local_shape) + + self.lsd_list += input_lsd
+ + +
+[docs] + def process_finish(self): + """Multiply summed gain with sidereal stream. + + Returns + ------- + data : containers.SiderealStream or containers.TimeStream + Stack of sidereal data with gain applied. + """ + # If requested, or shapes of visibilties and gain stack don't match then just return stack. + if ( + self.stream.vis[:].shape[-1] != self.gain_stack.vis[:].shape[-1] + ) or self.only_gains: + self.log.info("Saving only gain stack") + self.log.info( + "Either requested or shapes of visibilites and gain stack do not match" + ) + + self.gain_stack.vis[:] = self.gain_stack.vis[:] / self.gain_stack.weight[:] + + return self.gain_stack + + data = containers.empty_like(self.stream) + data.redistribute("freq") + + self.gain_stack.vis[:] = self.gain_stack.vis[:] / self.gain_stack.weight[:] + data.vis[:] = self.stream.vis[:] * self.gain_stack.vis[:] + data.weight[:] = self.stream.weight[:] + + data.attrs["tag"] = "gain_stack" + + return data
+
+ + + +def _ensure_list(x): + if hasattr(x, "__iter__"): + y = list(x) + else: + y = [x] + + return y + + +
+[docs] +def generate_fluctuations(x, corrfunc, n, prev_x, prev_fluc): + """Generate correlated random streams. + + Generates a Gaussian field from the given correlation function and (potentially) + correlated with prior data. + + Parameters + ---------- + x : np.ndarray[npoints] + Coordinates of samples in the new stream. + corrfunc : function + See documentation of `gaussian_realisation`. + prev_x : np.ndarray[npoints] + Coordinates of previous samples. Ignored if `prev_fluc` is None. + prev_fluc : np.ndarray[npoints] + Values of previous samples. If `None` the stream is initialised. + n : int + Number of realisations to generate. + + Returns + ------- + y : np.ndarray[n, npoints] + Realisations of the stream. + """ + nx = len(x) + + if prev_fluc is None: + fluctuations = gaussian_realisation(x, corrfunc, n).reshape(n, nx) + + else: + fluctuations = constrained_gaussian_realisation( + x, corrfunc, n, prev_x, prev_fluc + ).reshape(n, nx) + + return fluctuations
+ + + +
+[docs] +def gaussian_realisation(x, corrfunc, n, rcond=1e-12): + """Generate a Gaussian random field. + + Parameters + ---------- + x : np.ndarray[npoints] or np.ndarray[npoints, ndim] + Co-ordinates of points to generate. + corrfunc : function(x) -> covariance matrix + Function that take (vectorized) co-ordinates and returns their + covariance functions. + n : integer + Number of realisations to generate. + rcond : float, optional + Ignore eigenmodes smaller than `rcond` times the largest eigenvalue. + + Returns + ------- + y : np.ndarray[n, npoints] + Realisations of the gaussian field. + """ + return _realisation(corrfunc(x), n, rcond)
+ + + +def _realisation(C, n, rcond): + """Create a realisation of the given covariance matrix. + + Regularise by throwing away small eigenvalues. + """ + import scipy.linalg as la + + # Find the eigendecomposition, truncate small modes, and use this to + # construct a matrix projecting from the non-singular space + evals, evecs = la.eigh(C) + num = np.sum(evals > rcond * evals[-1]) + R = evecs[:, -num:] * evals[np.newaxis, -num:] ** 0.5 + + # Generate independent gaussian variables + w = np.random.standard_normal((n, num)) + + # Apply projection to get random field + return np.dot(w, R.T) + + +
+[docs] +def constrained_gaussian_realisation(x, corrfunc, n, x2, y2, rcond=1e-12): + """Generate a constrained Gaussian random field. + + Given a correlation function generate a Gaussian random field that is + consistent with an existing set of values of parameter `y2` located at + co-ordinates in parameter `x2`. + + Parameters + ---------- + x : np.ndarray[npoints] or np.ndarray[npoints, ndim] + Co-ordinates of points to generate. + corrfunc : function(x) -> covariance matrix + Function that take (vectorized) co-ordinates and returns their + covariance functions. + n : integer + Number of realisations to generate. + x2 : np.ndarray[npoints] or np.ndarray[npoints, ndim] + Co-ordinates of existing points. + y2 : np.ndarray[npoints] or np.ndarray[n, npoints] + Existing values of the random field. + rcond : float, optional + Ignore eigenmodes smaller than `rcond` times the largest eigenvalue. + + Returns + ------- + y : np.ndarray[n, npoints] + Realisations of the gaussian field. + """ + import scipy.linalg as la + + if (y2.ndim >= 2) and (n != y2.shape[0]): + raise ValueError("Array y2 of existing data has the wrong shape.") + + # Calculate the covariance matrix for the full dataset + xc = np.concatenate([x, x2]) + M = corrfunc(xc) + + # Select out the different blocks + l = len(x) + A = M[:l, :l] + B = M[:l, l:] + C = M[l:, l:] + + # This method tends to be unstable when there are singular modes in the + # covariance matrix (i.e. modes with zero variance). We can remove these by + # projecting onto the non-singular modes. + + # Find the eigendecomposition and construct projection matrices onto the + # non-singular space + evals_A, evecs_A = la.eigh(A) + evals_C, evecs_C = la.eigh(C) + + num_A = np.sum(evals_A > rcond * evals_A.max()) + num_C = np.sum(evals_C > rcond * evals_C.max()) + + R_A = evecs_A[:, -num_A:] + R_C = evecs_C[:, -num_C:] + + # Construct the covariance blocks in the reduced basis + A_r = np.diag(evals_A[-num_A:]) + B_r = np.dot(R_A.T, np.dot(B, R_C)) + Ci_r = np.diag(1.0 / evals_C[-num_C:]) + + # Project the existing data into the new basis + y2_r = np.dot(y2, R_C) + + # Calculate the mean of the new variables + z_r = np.dot(y2_r, np.dot(Ci_r, B_r.T)) + + # Generate fluctuations for the new variables (in the reduced basis) + Ap_r = A_r - np.dot(B_r, np.dot(Ci_r, B_r.T)) + y_r = _realisation(Ap_r, n, rcond) + + # Project into the original basis for A + return np.dot(z_r + y_r, R_A.T)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/synthesis/noise.html b/docs/_modules/draco/synthesis/noise.html new file mode 100644 index 000000000..93e6b76b4 --- /dev/null +++ b/docs/_modules/draco/synthesis/noise.html @@ -0,0 +1,459 @@ + + + + + + draco.synthesis.noise — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.synthesis.noise

+"""Add the effects of instrumental noise into the simulation.
+
+This is separated out into multiple tasks. The first, :class:`ReceiverTemperature`
+adds in the effects of instrumental noise bias into the data. The second,
+:class:`SampleNoise`, takes a timestream which is assumed to be the expected (or
+average) value and returns an observed time stream. The :class: `GaussianNoise`
+adds in the effects of a Gaussian distributed noise into visibility data.
+The :class: `GaussianNoiseDataset` replaces visibility data with Gaussian distributed noise,
+using the variance of the noise estimate in the existing data.
+"""
+
+import numpy as np
+from caput import config
+from caput.time import STELLAR_S
+
+from ..core import containers, io, task
+from ..util import random, tools
+
+
+
+[docs] +class ReceiverTemperature(task.SingleTask): + """Add a basic receiver temperature term into the data. + + This class adds in an uncorrelated, frequency and time independent receiver + noise temperature to the data. As it is uncorrelated this will only affect + the auto-correlations. Note this only adds in the offset to the visibility, + to add the corresponding random fluctuations to subsequently use the + :class:`SampleNoise` task. + + Attributes + ---------- + recv_temp : float + The receiver temperature in Kelvin. + """ + + recv_temp = config.Property(proptype=float, default=0.0) + +
+[docs] + def process(self, data): + """Iterate over the products to find the auto-correlations and add the noise into them.""" + for pi, prod in enumerate(data.prodstack): + # Great an auto! + if prod[0] == prod[1]: + data.vis[:, pi] += self.recv_temp + + return data
+
+ + + +
+[docs] +class GaussianNoiseDataset(task.SingleTask, random.RandomTask): + """Generates a Gaussian distributed noise dataset using the noise estimates of an existing dataset. + + Attributes + ---------- + dataset : string + The dataset to fill with gaussian noise. If set to 'vis', will ensure + autos are real. If not set, will look for a default dataset in a list + of known containers. + """ + + dataset = config.Property(proptype=str, default=None) + +
+[docs] + def process(self, data): + """Generates a Gaussian distributed noise dataset given the provided dataset's visibility weights. + + Parameters + ---------- + data : :class:`VisContainer` + Any dataset which contains a vis and weight attribute. + Note the modification is done in place. + + Returns + ------- + data_noise : same as parameter `data` + The previous dataset with the visibility replaced with + a Gaussian distributed noise realisation. + + """ + _default_dataset = { + containers.TimeStream: "vis", + containers.SiderealStream: "vis", + containers.HybridVisMModes: "vis", + containers.RingMap: "map", + containers.GridBeam: "beam", + containers.TrackBeam: "beam", + } + if self.dataset is None: + for cls, dataset in _default_dataset.items(): + if isinstance(data, cls): + dataset_name = dataset + break + else: + raise ValueError( + f"No default dataset known for {type(data)} container." + ) + else: + dataset_name = self.dataset + + if dataset_name not in data: + raise config.CaputConfigError( + f"Dataset '{dataset_name}' does not exist in container {type(data)}." + ) + + # Distribute in something other than `stack` + data.redistribute("freq") + + # Replace visibilities with noise + dset = data[dataset_name][:] + if np.iscomplexobj(dset): + random.complex_normal( + scale=tools.invert_no_zero(data.weight[:]) ** 0.5, + out=dset, + rng=self.rng, + ) + else: + self.rng.standard_normal(out=dset) + dset *= tools.invert_no_zero(data.weight[:]) ** 0.5 + + # We need to loop to ensure the autos are real and have the correct variance + if dataset_name == "vis": + for si, prod in enumerate(data.prodstack): + if prod[0] == prod[1]: + # This is an auto-correlation + dset[:, si].real *= 2**0.5 + dset[:, si].imag = 0.0 + + return data
+
+ + + +
+[docs] +class GaussianNoise(task.SingleTask, random.RandomTask): + """Add Gaussian distributed noise to a visibility dataset. + + Note that this is an approximation to the actual noise distribution good only + when T_recv >> T_sky and delta_time * delta_freq >> 1. + + Attributes + ---------- + ndays : float + Multiplies the number of samples in each measurement. + set_weights : bool + Set the weights to the appropriate values. + add_noise : bool + Add Gaussian noise to the visibilities. By default this is True, but it may be + desirable to only set the weights. + recv_temp : bool + The temperature of the noise to add. + """ + + recv_temp = config.Property(proptype=float, default=50.0) + ndays = config.Property(proptype=float, default=733.0) + set_weights = config.Property(proptype=bool, default=True) + add_noise = config.Property(proptype=bool, default=True) + +
+[docs] + def setup(self, manager=None): + """Set the telescope instance if a manager object is given. + + This is used to simulate noise for visibilities that are stacked + over redundant baselines. + + Parameters + ---------- + manager : manager.ProductManager, optional + The telescope/manager used to set the `redundancy`. If not set, + `redundancy` is derived from the data. + """ + if manager is not None: + self.telescope = io.get_telescope(manager) + else: + self.telescope = None
+ + +
+[docs] + def process(self, data): + """Generate a noisy dataset. + + Parameters + ---------- + data : :class:`containers.SiderealStream` or :class:`containers.TimeStream` + The expected (i.e. noiseless) visibility dataset. Note the modification + is done in place. + + Returns + ------- + data_noise : same as parameter `data` + The sampled (i.e. noisy) visibility dataset. + """ + data.redistribute("freq") + + visdata = data.vis[:] + + # Get the time interval + if isinstance(data, containers.SiderealStream): + dt = 240 * (data.ra[1] - data.ra[0]) * STELLAR_S + ntime = len(data.ra) + else: + dt = data.time[1] - data.time[0] + ntime = len(data.time) + + # TODO: this assumes uniform channels + df = data.index_map["freq"]["width"][0] * 1e6 + nfreq = data.vis.local_shape[0] + nprod = len(data.prodstack) + ninput = len(data.index_map["input"]) + + # Consider if this data is stacked over redundant baselines or not. + if (self.telescope is not None) and (nprod == self.telescope.nbase): + redundancy = self.telescope.redundancy + elif nprod == ninput * (ninput + 1) / 2: + redundancy = np.ones(nprod) + else: + raise ValueError("Unexpected number of products") + + # Calculate the number of samples, this is a 1D array for the prod axis. + nsamp = int(self.ndays * dt * df) * redundancy + std = self.recv_temp / np.sqrt(nsamp) + + if self.add_noise: + noise = random.complex_normal( + size=(nfreq, nprod, ntime), + scale=std[np.newaxis, :, np.newaxis], + rng=self.rng, + ) + + # Iterate over the products to find the auto-correlations and add the noise + for pi, prod in enumerate(data.prodstack): + # Auto: multiply by sqrt(2) because auto has twice the variance + if prod[0] == prod[1]: + visdata[:, pi].real += np.sqrt(2) * noise[:, pi].real + + else: + visdata[:, pi] += noise[:, pi] + + # Construct and set the correct weights in place + if self.set_weights: + for lfi, fi in visdata.enumerate(0): + data.weight[fi] = 1.0 / std[:, np.newaxis] ** 2 + + return data
+
+ + + +
+[docs] +class SampleNoise(task.SingleTask, random.RandomTask): + """Add properly distributed noise to a visibility dataset. + + This task draws properly (complex Wishart) distributed samples from an input + visibility dataset which is assumed to represent the expectation. + + See http://link.springer.com/article/10.1007%2Fs10440-010-9599-x for a + discussion of the Bartlett decomposition for complex Wishart distributed + quantities. + + Attributes + ---------- + sample_frac : float + Multiplies the number of samples in each measurement. For instance this + could be a duty cycle if the correlator was not keeping up, or could be + larger than one if multiple measurements were combined. + set_weights : bool + Set the weights to the appropriate values. + """ + + sample_frac = config.Property(proptype=float, default=1.0) + seed = config.Property(proptype=int, default=None) + set_weights = config.Property(proptype=bool, default=True) + +
+[docs] + def process(self, data_exp): + """Generate a noisy dataset. + + Parameters + ---------- + data_exp : :class:`containers.SiderealStream` or :class:`containers.TimeStream` + The expected (i.e. noiseless) visibility dataset. Must be the full + triangle. Make sure you have added an instrumental noise bias if you + want instrumental noise. + + Returns + ------- + data_samp : same as parameter `data_exp` + The sampled (i.e. noisy) visibility dataset. + """ + from caput.time import STELLAR_S + + from ..util import _fast_tools + + data_exp.redistribute("freq") + + nfeed = len(data_exp.index_map["input"]) + + # Get a reference to the base MPIArray. Attempting to do this in the + # loop fails if not all ranks enter the loop (as there is an implied MPI + # Barrier) + vis_data = data_exp.vis[:] + + # Get the time interval + if isinstance(data_exp, containers.SiderealStream): + dt = 240 * (data_exp.ra[1] - data_exp.ra[0]) * STELLAR_S + else: + dt = data_exp.time[1] - data_exp.time[0] + + # Iterate over frequencies + for lfi, fi in vis_data.enumerate(0): + # Get the frequency interval + df = data_exp.index_map["freq"]["width"][fi] * 1e6 + + # Calculate the number of samples + nsamp = int(self.sample_frac * dt * df) + + # Iterate over time + for lti, ti in vis_data.enumerate(2): + # Unpack visibilites into full matrix + vis_utv = vis_data[lfi, :, lti].view(np.ndarray).copy() + vis_mat = np.zeros((nfeed, nfeed), dtype=vis_utv.dtype) + _fast_tools._unpack_product_array_fast( + vis_utv, vis_mat, np.arange(nfeed), nfeed + ) + + vis_samp = random.complex_wishart(vis_mat, nsamp, rng=self.rng) / nsamp + + vis_data[lfi, :, lti] = vis_samp[np.triu_indices(nfeed)] + + # Construct and set the correct weights in place + if self.set_weights: + autos = tools.extract_diagonal(vis_data[lfi], axis=0).real + weight_fac = nsamp**0.5 / autos + tools.apply_gain( + data_exp.weight[fi][np.newaxis, ...], + weight_fac[np.newaxis, ...], + out=data_exp.weight[fi][np.newaxis, ...], + ) + + return data_exp
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/synthesis/stream.html b/docs/_modules/draco/synthesis/stream.html new file mode 100644 index 000000000..b8c196c0f --- /dev/null +++ b/docs/_modules/draco/synthesis/stream.html @@ -0,0 +1,567 @@ + + + + + + draco.synthesis.stream — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.synthesis.stream

+"""Tasks for simulating sidereal and time stream data.
+
+A typical pattern would be to turn a map into a
+:class:`containers.SiderealStream` with the :class:`SimulateSidereal` task, then
+expand any redundant products with :class:`ExpandProducts` and finally generate
+a set of time stream files with :class:`MakeTimeStream`.
+"""
+
+import numpy as np
+from caput import config, mpiarray, mpiutil, pipeline
+from cora.util import hputil
+
+from ..core import containers, io, task
+
+
+
+[docs] +class SimulateSidereal(task.SingleTask): + """Create a simulated sidereal dataset from an input map. + + Attributes + ---------- + stacked : bool + When set, in the case that the beam transfer matrices are not full triangle + treat them as having been generated by collating the baselines of full triangle + data, and set appropriate `index_map/stack` and `reverse_map/stack` entries. If + not set, treat the entries as having been generated by down selecting the full + set of baselines, and thus don't create the `stack` entries. Default is `True`. + """ + + stacked = config.Property(proptype=bool, default=True) + +
+[docs] + def setup(self, bt): + """Setup the simulation. + + Parameters + ---------- + bt : ProductManager or BeamTransfer + Beam Transfer maanger. + """ + self.beamtransfer = io.get_beamtransfer(bt) + self.telescope = io.get_telescope(bt)
+ + +
+[docs] + def process(self, map_): + """Simulate a SiderealStream. + + Parameters + ---------- + map_ : :class:`containers.Map` + The sky map to process to into a sidereal stream. Frequencies in the + map, must match the Beam Transfer matrices. + + Returns + ------- + ss : SiderealStream + Stacked sidereal day. + feeds : list of CorrInput + Description of the feeds simulated. + """ + # Read in telescope system + bt = self.beamtransfer + tel = self.telescope + + lmax = tel.lmax + mmax = tel.mmax + nfreq = tel.nfreq + npol = tel.num_pol_sky + + lfreq, sfreq, efreq = mpiutil.split_local(nfreq) + + lm, sm, em = mpiutil.split_local(mmax + 1) + + # Set the minimum resolution required for the sky. + ntime = 2 * mmax + 1 + + freqmap = map_.index_map["freq"][:] + row_map = map_.map[:] + + if (tel.frequencies != freqmap["centre"]).any(): + raise ValueError("Frequencies in map do not match those in Beam Transfers.") + + # Calculate the alm's for the local sections + row_alm = hputil.sphtrans_sky(row_map, lmax=lmax).reshape( + (lfreq, npol * (lmax + 1), lmax + 1) + ) + + # Trim off excess m's and wrap into MPIArray + row_alm = row_alm[..., : (mmax + 1)] + row_alm = mpiarray.MPIArray.wrap(row_alm, axis=0) + + # Perform the transposition to distribute different m's across processes. Neat + # tip, putting a shorter value for the number of columns, trims the array at + # the same time + col_alm = row_alm.redistribute(axis=2) + + # Transpose and reshape to shift m index first. + col_alm = col_alm.transpose((2, 0, 1)).reshape((None, nfreq, npol, lmax + 1)) + + # Create storage for visibility data + vis_data = mpiarray.MPIArray( + (mmax + 1, nfreq, bt.ntel), axis=0, dtype=np.complex128 + ) + vis_data[:] = 0.0 + + # Iterate over m's local to this process and generate the corresponding + # visibilities + for mp, mi in vis_data.enumerate(axis=0): + vis_data[mp] = bt.project_vector_sky_to_telescope( + mi, col_alm[mp].view(np.ndarray) + ) + + # Rearrange axes such that frequency is last (as we want to divide + # frequencies across processors) + row_vis = vis_data.transpose((0, 2, 1)) + + # Parallel transpose to get all m's back onto the same processor + col_vis_tmp = row_vis.redistribute(axis=2) + col_vis_tmp = col_vis_tmp.reshape((mmax + 1, 2, tel.npairs, None)) + + # Transpose the local section to make the m's the last axis and unwrap the + # positive and negative m at the same time. + col_vis = mpiarray.MPIArray( + (tel.npairs, nfreq, ntime), axis=1, dtype=np.complex128 + ) + col_vis[:] = 0.0 + col_vis[..., 0] = col_vis_tmp[0, 0] + for mi in range(1, mmax + 1): + col_vis[..., mi] = col_vis_tmp[mi, 0] + col_vis[..., -mi] = col_vis_tmp[ + mi, 1 + ].conj() # Conjugate only (not (-1)**m - see paper) + + del col_vis_tmp + + # Fourier transform m-modes back to get final timestream. + vis_stream = np.fft.ifft(col_vis, axis=-1) * ntime + vis_stream = vis_stream.reshape((tel.npairs, lfreq, ntime)) + vis_stream = vis_stream.transpose((1, 0, 2)).copy() + + # Try and fetch out the feed index and info from the telescope object. + try: + feed_index = tel.input_index + except AttributeError: + feed_index = tel.nfeed + + kwargs = {} + + if tel.npairs != (tel.nfeed + 1) * tel.nfeed // 2 and self.stacked: + # If we should treat this as stacked, then pull the information straight + # from the telescope class + kwargs["prod"] = tel.index_map_prod + kwargs["stack"] = tel.index_map_stack + kwargs["reverse_map_stack"] = tel.reverse_map_stack + else: + # Construct a product map as if this was a down selection + prod_map = np.zeros( + tel.uniquepairs.shape[0], dtype=[("input_a", int), ("input_b", int)] + ) + prod_map["input_a"] = tel.uniquepairs[:, 0] + prod_map["input_b"] = tel.uniquepairs[:, 1] + + kwargs["prod"] = prod_map + + # Construct container and set visibility data + sstream = containers.SiderealStream( + freq=freqmap, + ra=ntime, + input=feed_index, + distributed=True, + comm=map_.comm, + **kwargs, + ) + sstream.vis[:] = mpiarray.MPIArray.wrap(vis_stream, axis=0) + sstream.weight[:] = 1.0 + + return sstream
+
+ + + +
+[docs] +class ExpandProducts(task.SingleTask): + """Un-wrap collated products to full triangle.""" + +
+[docs] + def setup(self, telescope): + """Get a reference to the telescope class. + + Parameters + ---------- + telescope : :class:`drift.core.TransitTelescope` + Telescope object to use + """ + self.telescope = io.get_telescope(telescope)
+ + +
+[docs] + def process(self, sstream): + """Transform a sidereal stream to having a full product matrix. + + Parameters + ---------- + sstream : :class:`containers.SiderealStream` + Sidereal stream to unwrap. + + Returns + ------- + new_sstream : :class:`containers.SiderealStream` + Unwrapped sidereal stream. + """ + sstream.redistribute("freq") + + ninput = len(sstream.input) + prod = np.array( + [(fi, fj) for fi in range(ninput) for fj in range(fi, ninput)], + dtype=[("input_a", int), ("input_b", int)], + ) + nprod = len(prod) + + new_stream = containers.SiderealStream(prod=prod, stack=None, axes_from=sstream) + new_stream.redistribute("freq") + new_stream.vis[:] = 0.0 + new_stream.weight[:] = 0.0 + + # Create dummpy index and reverse map for the stack axis to match the behaviour + # of reading in an N^2 file through andata + fwd_stack = np.empty(nprod, dtype=[("prod", "<u4"), ("conjugate", "u1")]) + fwd_stack["prod"] = np.arange(nprod) + fwd_stack["conjugate"] = False + new_stream.create_index_map("stack", fwd_stack) + rev_stack = np.empty(nprod, dtype=[("stack", "<u4"), ("conjugate", "u1")]) + rev_stack["stack"] = np.arange(nprod) + rev_stack["conjugate"] = False + new_stream.create_reverse_map("stack", rev_stack) + + # Iterate over all feed pairs and work out which is the correct index in the sidereal stack. + for pi, (fi, fj) in enumerate(prod): + unique_ind = self.telescope.feedmap[fi, fj] + conj = self.telescope.feedconj[fi, fj] + + # unique_ind is less than zero it has masked out + if unique_ind < 0: + continue + + prod_stream = sstream.vis[:, unique_ind] + new_stream.vis[:, pi] = prod_stream.conj() if conj else prod_stream + + new_stream.weight[:, pi] = 1.0 + + return new_stream
+
+ + + +
+[docs] +class MakeTimeStream(task.SingleTask): + """Generate a series of time streams files from a sidereal stream. + + Parameters + ---------- + start_time, end_time : float or datetime + Start and end times of the timestream to simulate. Needs to be either a + `float` (UNIX time) or a `datetime` objects in UTC. + integration_time : float, optional + Integration time in seconds. Takes precedence over `integration_frame_exp`. + integration_frame_exp: int, optional + Specify the integration time in frames. The integration time is + `2**integration_frame_exp * 2.56 us`. + samples_per_file : int, optional + Number of samples per file. + """ + + start_time = config.utc_time() + end_time = config.utc_time() + + integration_time = config.Property(proptype=float, default=None) + integration_frame_exp = config.Property(proptype=int, default=23) + + samples_per_file = config.Property(proptype=int, default=1024) + + _cur_time = 0.0 # Hold the current file start time + +
+[docs] + def setup(self, sstream, manager): + """Get the sidereal stream to turn into files. + + Parameters + ---------- + sstream : SiderealStream + The sidereal data to use. + manager : ProductManager or BeamTransfer + Beam Transfer and telescope manager + """ + self.sstream = sstream + # Need an Observer object holding the geographic location of the telescope. + self.observer = io.get_telescope(manager) + # Initialise the current start time + self._cur_time = self.start_time
+ + +
+[docs] + def process(self): + """Create a timestream file. + + Returns + ------- + tstream : :class:`containers.TimeStream` + Time stream object. + """ + from ..util import regrid + + # First check to see if we have reached the end of the requested time, + # and if so stop the iteration. + if self._cur_time >= self.end_time: + raise pipeline.PipelineStopIteration + + time = self._next_time_axis() + + # Make the timestream container + tstream = containers.empty_timestream(axes_from=self.sstream, time=time) + + # Make the interpolation array + ra = self.observer.unix_to_lsa(tstream.time) + lza = regrid.lanczos_forward_matrix(self.sstream.ra, ra, periodic=True) + lza = lza.T.astype(np.complex64) + + # Apply the interpolation matrix to construct the new timestream, place + # the output directly into the container + np.dot(self.sstream.vis[:], lza, out=tstream.vis[:]) + + # Set the weights array to the maximum value for CHIME + tstream.weight[:] = 1.0 + + # Output the timestream + return tstream
+ + + def _next_time_axis(self): + # Calculate the integration time + if self.integration_time is not None: + int_time = self.integration_time + else: + int_time = 2.56e-6 * 2**self.integration_frame_exp + + # Calculate number of samples in file and timestamps + nsamp = min( + int(np.ceil((self.end_time - self._cur_time) / int_time)), + self.samples_per_file, + ) + timestamps = ( + self._cur_time + (np.arange(nsamp) + 1) * int_time + ) # +1 as timestamps are at the end of each sample + + # Construct the time axis index map + if self.integration_time is not None: + time = timestamps + else: + _time_dtype = [("fpga_count", np.uint64), ("ctime", np.float64)] + time = np.zeros(nsamp, _time_dtype) + time["ctime"] = timestamps + time["fpga_count"] = ( + (timestamps - self.start_time) + / int_time + * 2**self.integration_frame_exp + ).astype(np.uint64) + + # Increment the current start time for the next iteration + self._cur_time += nsamp * int_time + + return time
+ + + +
+[docs] +class MakeSiderealDayStream(task.SingleTask): + """Task for simulating a set of sidereal days from a given stream. + + This creates a copy of the base stream for every LSD within the provided time + range. + + Attributes + ---------- + start_time, end_time : float or datetime + Start and end times of the sidereal streams to simulate. Needs to be either a + `float` (UNIX time) or a `datetime` objects in UTC. + """ + + start_time = config.utc_time() + end_time = config.utc_time() + +
+[docs] + def setup(self, bt, sstream): + """Set up an observer and the data to use for this simulation. + + Parameters + ---------- + bt : beamtransfer.BeamTransfer or manager.ProductManager + Sets up an observer holding the geographic location of the telscope. + sstream : containers.SiderealStream + The base sidereal data to use for this simulation. + """ + self.observer = io.get_telescope(bt) + self.lsd_start = self.observer.unix_to_lsd(self.start_time) + self.lsd_end = self.observer.unix_to_lsd(self.end_time) + + self.log.info( + "Sidereal period requested: LSD=%i to LSD=%i", + int(self.lsd_start), + int(self.lsd_end), + ) + + # Initialize the current lsd time + self._current_lsd = None + self.sstream = sstream
+ + +
+[docs] + def process(self): + """Generate a sidereal stream for the specific sidereal day. + + Returns + ------- + ss : :class:`containers.SiderealStream` + Simulated sidereal day stream. + """ + # If current_lsd is None then this is the first time we've run + if self._current_lsd is None: + # Check if lsd is an integer, if not add an lsd + if isinstance(self.lsd_start, int): + self._current_lsd = int(self.lsd_start) + else: + self._current_lsd = int(self.lsd_start + 1) + + # Check if we have reached the end of the requested time + if self._current_lsd >= self.lsd_end: + raise pipeline.PipelineStopIteration + + ss = self.sstream.copy() + ss.attrs["tag"] = f"lsd_{self._current_lsd}" + ss.attrs["lsd"] = self._current_lsd + + self._current_lsd += 1 + + return ss
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/util/exception.html b/docs/_modules/draco/util/exception.html new file mode 100644 index 000000000..b2f369224 --- /dev/null +++ b/docs/_modules/draco/util/exception.html @@ -0,0 +1,117 @@ + + + + + + draco.util.exception — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.util.exception

+"""Exceptions for draco."""
+
+
+
+[docs] +class ConfigError(Exception): + """Raised due to an error with a task configuration.""" + + pass
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/util/random.html b/docs/_modules/draco/util/random.html new file mode 100644 index 000000000..22320521a --- /dev/null +++ b/docs/_modules/draco/util/random.html @@ -0,0 +1,660 @@ + + + + + + draco.util.random — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.util.random

+"""Utilities for drawing random numbers."""
+
+import concurrent.futures
+import contextlib
+import os
+import zlib
+from typing import Callable, ClassVar, Optional
+
+import numpy as np
+from caput import config
+
+from ..core import task
+
+_rng = None
+_default_bitgen = np.random.SFC64
+
+
+
+[docs] +def default_rng(): + """Returns an instance of the default random number generator to use. + + This creates a randomly seeded generator using the fast SFC64 bit generator + underneath. This is only initialise on the first call, subsequent calls will + return the same Generator. + + Returns + ------- + rng : np.random.Generator + """ + global _rng + + if _rng is None: + _rng = np.random.Generator(_default_bitgen()) + + return _rng
+ + + +
+[docs] +def complex_normal(loc=0.0, scale=1.0, size=None, dtype=None, rng=None, out=None): + """Get a set of complex normal variables. + + By default generate standard complex normal variables. + + Parameters + ---------- + size : tuple + Shape of the array of variables. + loc : np.ndarray or complex float, optional + The mean of the complex output. Can be any array which broadcasts against + an array of `size`. + scale : np.ndarray or float, optional + The standard deviation of the complex output. Can be any array which + broadcasts against an array of `size`. + dtype : {np.complex64, np.complex128}, optional + Output datatype. + rng : np.random.Generator, optional + Generator object to use. + out : np.ndarray[shape], optional + Array to place output directly into. + + Returns + ------- + out : np.ndarray[shape] + Complex gaussian variates. + """ + # Validate/set size argument + if size is None and out is None: + size = (1,) + elif out is not None and size is None: + size = out.shape + elif out is not None and size is not None and out.shape != size: + raise ValueError( + f"Shape of output array ({out.shape}) != size argument ({size}" + ) + + # Validate/set dtype argument + if dtype is None and out is None: + dtype = np.complex128 + elif dtype is None and out is not None: + dtype = out.dtype.type + elif out is not None and dtype is not None and out.dtype.type != dtype: + raise ValueError( + f"Dtype of output array ({out.dtype.type}) != dtype argument ({dtype}" + ) + + if rng is None: + rng = default_rng() + + _type_map = { + np.complex64: np.float32, + np.complex128: np.float64, + } + + if dtype not in _type_map: + raise ValueError( + f"Only dtype must be complex64 or complex128. Got dtype={dtype}." + ) + + if out is None: + out = np.ndarray(size, dtype=dtype) + + # Fill the complex array by creating a real type view of it + rtype = _type_map[dtype] + rsize = size[:-1] + (size[-1] * 2,) + rng.standard_normal(rsize, dtype=rtype, out=out.view(rtype)) + + # Use inplace ops for scaling and adding to avoid intermediate arrays + rscale = scale / 2**0.5 + out *= rscale + + # Don't bother with the additions if not needed + if np.any(loc != 0.0): + out += loc + + return out
+ + + +
+[docs] +def standard_complex_normal(shape, dtype=None, rng=None): + """Get a set of standard complex normal variables. + + Parameters + ---------- + shape : tuple + Shape of the array of variables. + dtype : {np.complex64, np.complex128}, optional + Output datatype. + rng : np.random.Generator, optional + Generator object to use. + + Returns + ------- + out : np.ndarray[shape] + Complex gaussian variates. + """ + return complex_normal(size=shape, dtype=dtype, rng=rng)
+ + + +
+[docs] +def standard_complex_wishart(m, n, rng=None): + """Draw a standard Wishart matrix. + + Parameters + ---------- + m : integer + Number of variables (i.e. size of matrix). + n : integer + Number of measurements the covariance matrix is estimated from. + rng : np.random.Generator, optional + Random number generator to use. + + Returns + ------- + B : np.ndarray[m, m] + """ + if rng is None: + rng = default_rng() + + # Fill in normal variables in the lower triangle + T = np.zeros((m, m), dtype=np.complex128) + T[np.tril_indices(m, k=-1)] = ( + rng.standard_normal(m * (m - 1) // 2) + + 1.0j * rng.standard_normal(m * (m - 1) // 2) + ) / 2**0.5 + + # Gamma variables on the diagonal + for i in range(m): + T[i, i] = rng.gamma(n - i) ** 0.5 + + # Return the square to get the Wishart matrix + return np.dot(T, T.T.conj())
+ + + +
+[docs] +def complex_wishart(C, n, rng=None): + """Draw a complex Wishart matrix. + + Parameters + ---------- + C : np.ndarray[:, :] + Expected covaraince matrix. + n : integer + Number of measurements the covariance matrix is estimated from. + rng : np.random.Generator, optional + Random number generator to use. + + Returns + ------- + C_samp : np.ndarray + Sample covariance matrix. + """ + import scipy.linalg as la + + # Find Cholesky of C + L = la.cholesky(C, lower=True) + + # Generate a standard Wishart + A = standard_complex_wishart(C.shape[0], n, rng=rng) + + # Transform to get the Wishart variable + return np.dot(L, np.dot(A, L.T.conj()))
+ + + +
+[docs] +@contextlib.contextmanager +def mpi_random_seed(seed, extra=0, gen=None): + """Use a specific random seed and return to the original state on exit. + + This is designed to work for MPI computations, incrementing the actual seed of + each process by the MPI rank. Overall each process gets the numpy seed: + `numpy_seed = seed + mpi_rank + 4096 * extra`. This can work for either the + global numpy.random context or for new np.random.Generator. + + + Parameters + ---------- + seed : int + Base seed to set. If seed is :obj:`None`, re-seed randomly. + extra : int, optional + An extra part of the seed, which should be changed for calculations + using the same seed, but that want different random sequences. + gen: :class: `Generator` + A RandomGen bit_generator whose internal seed state we are going to + influence. + + Yields + ------ + If we are setting the numpy.random context, nothing is yielded. + + :class: `Generator` + If we are setting the RandomGen bit_generator, it will be returned. + """ + import warnings + + from caput import mpiutil + + warnings.warn( + "This routine has fatal flaws. Try using `RandomTask` instead", + category=DeprecationWarning, + ) + + # Just choose a random number per process as the seed if nothing was set. + if seed is None: + seed = np.random.randint(2**30) + + # Construct the new process specific seed + new_seed = seed + mpiutil.rank + 4096 * extra + np.random.seed(new_seed) + + # we will be setting the numpy.random context + if gen is None: + # Copy the old state for restoration later. + old_state = np.random.get_state() + + # Enter the context block, and reset the state on exit. + try: + yield + finally: + np.random.set_state(old_state) + + # we will be setting the randomgen context + else: + # Copy the old state for restoration later. + old_state = gen.state + + # Enter the context block, and reset the state on exit. + try: + yield gen + finally: + gen.state = old_state
+ + + +
+[docs] +class MultithreadedRNG(np.random.Generator): + """A multithreaded random number generator. + + This wraps specific methods to allow generation across multiple threads. See + `PARALLEL_METHODS` for the specific methods wrapped. + + Parameters + ---------- + seed + The seed to use. + nthreads + The number of threads to use. If not set, this tries to get the number from the + `OMP_NUM_THREADS` environment variable, or just uses 4 if that is also not set. + bitgen + The BitGenerator to use, if not set this uses `_default_bitgen`. + """ + + _parallel_threshold = 1000 + + # The methods to generate parallel versions for. This table is: + # method name, number of initial parameter arguments, default data type, if there is + # a dtype argument, and if there is an out argument. See `_build_method` for + # details. + PARALLEL_METHODS: ClassVar = { + "random": (0, np.float64, True, True), + "integers": (2, np.int64, True, False), + "uniform": (2, np.float64, False, False), + "normal": (2, np.float64, False, False), + "standard_normal": (0, np.float64, True, True), + "poisson": (1, np.float64, False, False), + "power": (1, np.float64, False, False), + } + + def __init__( + self, + seed: Optional[int] = None, + threads: Optional[int] = None, + bitgen: Optional[np.random.BitGenerator] = None, + ): + if bitgen is None: + bitgen = _default_bitgen + + # Initialise this object with the given seed. This allows methods that don't + # have multithreaded support to work + super().__init__(bitgen(seed)) + + if threads is None: + threads = int(os.environ.get("OMP_NUM_THREADS", 4)) + + # Initialise the parallel thread pool + self._threads = threads + self._random_generators = [ + np.random.Generator(bitgen(seed=s)) + for s in np.random.SeedSequence(seed).spawn(threads) + ] + self._executor = concurrent.futures.ThreadPoolExecutor(threads) + + # Create the methods and attach them to this instance. + for method, spec in self.PARALLEL_METHODS.items(): + setattr(self, method, self._build_method(method, *spec)) + + def _build_method( + self, + name: str, + nparam: int, + defdtype: np.dtype, + has_dtype: bool, + has_out: bool, + ) -> Callable: + """Build a method for generating random numbers from a given distribution. + + As the underlying methods are in Cython they can't be adequately introspected + and so we need to provide information about the signature. + + Parameters + ---------- + name + The name of the generation method in `np.random.Generator`. + nparam + The number of distribution parameters that come before the `size` argument. + defdtype + The default datatype used if non is explicitly supplied. + has_dtype + Does the underlying method have a dtype argument? + has_out + Does the underlying method have an `out` parameter for directly filling an + array. + + Returns + ------- + parallel_method + A method for generating in parallel. + """ + method = getattr(np.random.Generator, name) + + def _call(*args, **kwargs): + orig_args = list(args) + orig_kwargs = dict(kwargs) + + # Try and get the size + if len(args) > nparam: + size = args[nparam] + elif "size" in kwargs: + size = kwargs.pop("size") + else: + size = None + + # Try and get an out argument + if has_out and "out" in kwargs: + out = kwargs.pop("out") + size = out.shape + else: + out = None + + # Try to figure out the dtype so we can pre-allocate the array for filling + if has_dtype and len(args) > nparam + 1: + dtype = args[nparam + 1] + elif has_dtype and "dtype" in kwargs: + dtype = kwargs.pop("dtype") + else: + dtype = defdtype + + # Trim any excess positional arguments + args = args[:nparam] + + # Check that all the parameters are scalar + all_scalar = all(np.isscalar(arg) for arg in args) + + # Check that any remaining kwargs (assumed to be parameters are also scalar) + all_scalar &= all(np.isscalar(arg) for arg in kwargs.values()) + + # If neither size nor out is set we can't parallelise this so just call + # directly. + # Additionally if the distribution arguments are not scalars there may be + # some complex broadcasting required, so we also drop out if that is true. + if (size is None and out is None) or not all_scalar: + return method(self, *orig_args, **orig_kwargs) + + flatsize = np.prod(size) + + # If the total size is too small, then just call directly + if flatsize < self._parallel_threshold: + return method(self, *orig_args, **orig_kwargs) + + # Create the output array if required + if out is None: + out = np.empty(size, dtype) + + # Figure out how to split up the array + step = int(np.ceil(flatsize / self._threads)) + + # A worker method for each thread to fill its part of the array with the + # random numbers + def _fill(gen: np.random.Generator, local_array: np.ndarray) -> None: + if has_out: + method(gen, *args, **kwargs, out=local_array) + else: + if has_dtype: + kwargs["dtype"] = dtype + local_array[:] = method( + gen, + *args, + **kwargs, + size=len(local_array), + ) + + # Generate the numbers with each worker thread + futures = [ + self._executor.submit( + _fill, + self._random_generators[i], + out.ravel()[(i * step) : ((i + 1) * step)], + ) + for i in range(self._threads) + ] + concurrent.futures.wait(futures) + + for ii, future in enumerate(futures): + if (e := future.exception()) is not None: + raise RuntimeError( + f"An exception occurred in thread {ii} (and maybe others)." + ) from e + + return out + + # Copy over the docstring for the method + _call.__doc__ = "Multithreaded version.\n" + method.__doc__ + + return _call + + def __del__(self): + self._executor.shutdown(False)
+ + + +
+[docs] +class RandomTask(task.MPILoggedTask): + """A base class for MPI tasks that need to generate random numbers. + + Attributes + ---------- + seed : int, optional + Set the seed for use in the task. If not set, a random seed is generated and + broadcast to all ranks. The seed being used is logged, to repeat a previous + run, simply set this as the seed parameter. + threads : int, optional + Set the number of threads to use for the random number generator. If not + explicitly set this will use the value of the `OMP_NUM_THREADS` environment + variable, or fall back to four. + """ + + seed = config.Property(proptype=int, default=None) + threads = config.Property(proptype=int, default=None) + + _rng = None + + @property + def rng(self) -> np.random.Generator: + """A random number generator for this task. + + .. warning:: + Initialising the RNG is a collective operation if the seed is not set, + and so all ranks must participate in the first access of this property. + + Returns + ------- + rng : np.random.Generator + A deterministically seeded random number generator suitable for use in + MPI jobs. + """ + if self._rng is None: + self._rng = MultithreadedRNG(self.local_seed, threads=self.threads) + + return self._rng + + _local_seed = None + + @property + def local_seed(self) -> int: + """Get the seed to be used on this rank. + + .. warning:: + Generating the seed is a collective operation if the seed is not set, + and so all ranks must participate in the first access of this property. + """ + if self._local_seed is None: + if self.seed is None: + # Use seed sequence to generate a random seed + seed = np.random.SeedSequence().entropy + seed = self.comm.bcast(seed, root=0) + else: + seed = self.seed + + self.log.info("Using random seed: %i", seed) + + # Construct the new MPI-process and task specific seed. This mixes an + # integer checksum of the class name with the MPI-rank to generate a new + # hash. + # NOTE: the slightly odd (rank + 1) is to ensure that even rank=0 mixes in + # the class seed + cls_name = f"{self.__module__}.{self.__class__.__name__}" + cls_seed = zlib.adler32(cls_name.encode()) + self._local_seed = seed + (self.comm.rank + 1) * cls_seed + + return self._local_seed
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/util/regrid.html b/docs/_modules/draco/util/regrid.html new file mode 100644 index 000000000..de931da41 --- /dev/null +++ b/docs/_modules/draco/util/regrid.html @@ -0,0 +1,276 @@ + + + + + + draco.util.regrid — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.util.regrid

+"""Routines for regridding irregular data using a Lanczos/Wiener filtering approach.
+
+This is described in some detail in `doclib:173
+<http://bao.chimenet.ca/doc/cgi-bin/general/documents/display?Id=173>`_.
+"""
+
+import numpy as np
+import scipy.linalg as la
+
+from ..util import _fast_tools
+
+
+
+[docs] +def band_wiener(R, Ni, Si, y, bw): + r"""Calculate the Wiener filter assuming various bandedness properties. + + In particular this asserts that a particular element in the filtered + output will only couple to the nearest `bw` elements. Equivalently, this + is that the covariance matrix will be band diagonal. This allows us to use + fast routines to generate the solution. + + Note that the inverse noise estimate returned is :math:`\mathrm{diag}(\mathbf{R}^T + \mathbf{N}^{-1} \mathbf{R})` and not the full Bayesian estimate including a + contribution from the signal covariance. + + Parameters + ---------- + R : np.ndarray[m, n] + Transfer matrix for the Wiener filter. + Ni : np.ndarray[k, n] + Inverse noise matrix. Noise assumed to be uncorrelated (i.e. diagonal matrix). + Si : np.narray[m] + Inverse signal matrix. Signal model assumed to be uncorrelated (i.e. diagonal + matrix). + y : np.ndarray[k, n] + Data to apply to. + bw : int + Bandwidth, i.e. how many elements couple together. + + Returns + ------- + xhat : np.ndarray[k, m] + Filtered data. + nw : np.ndarray[k, m] + Estimate of variance of each element. + """ + Ni = np.atleast_2d(Ni) + y = np.atleast_2d(y) + + k = Ni.shape[0] + m = R.shape[0] + + # Initialise arrays + xh = np.zeros((k, m), dtype=y.dtype) + nw = np.zeros((k, m), dtype=np.float32) + + # Multiply by noise weights inplace to reduce memory usage (destroys original) + y *= Ni + + # Calculate dirty estimate (and output straight into xh) + R_s = R.astype(np.float32) + np.dot(y, R_s.T, out=xh) + + # Calculate the start and end indices of the summation + start_ind = (R != 0).argmax(axis=-1).astype(np.int32) + end_ind = R.shape[-1] - (R[..., ::-1] != 0).argmax(axis=-1) + end_ind = np.where((R == 0).all(axis=-1), 0, end_ind).astype(np.int32) + + # Iterate through and solve noise + for ki in range(k): + # Upcast noise weights to float type + Ni_ki = Ni[ki].astype(np.float64) + + # Calculate the Wiener noise weighting (i.e. inverse covariance) + Ci = _fast_tools._band_wiener_covariance(R, Ni_ki, start_ind, end_ind, bw) + + # Set the noise estimate before adding in the signal contribution. This avoids + # the issue that the inverse-noise estimate becomes non-zero even when the data + # was entirely missing + nw[ki] = Ci[-1] + + # Add on the signal covariance part + Ci[-1] += Si + + # Solve for the Wiener estimate + xh[ki] = la.solveh_banded(Ci, xh[ki]) + + return xh, nw
+ + + +
+[docs] +def lanczos_kernel(x, a): + """Lanczos interpolation kernel. + + Parameters + ---------- + x : array_like + Point separation. + a : integer + Lanczos kernel width. + + Returns + ------- + kernel : np.ndarray + """ + return np.where(np.abs(x) < a, np.sinc(x) * np.sinc(x / a), np.zeros_like(x))
+ + + +
+[docs] +def lanczos_forward_matrix(x, y, a=5, periodic=False): + """Lanczos interpolation matrix. + + Parameters + ---------- + x : np.ndarray[m] + Points we have data at. Must be regularly spaced. + y : np.ndarray[n] + Point we want to interpolate data onto. + a : integer, optional + Lanczos width parameter. + periodic : boolean, optional + Treat input points as periodic. + + Returns + ------- + matrix : np.ndarray[m, n] + Lanczos regridding matrix. Apply to data with `np.dot(matrix, data)`. + """ + dx = x[1] - x[0] + + sep = (x[np.newaxis, :] - y[:, np.newaxis]) / dx + + if periodic: + n = len(x) + sep = np.where(np.abs(sep) > n // 2, n - np.abs(sep), sep) + + return lanczos_kernel(sep, a)
+ + + +
+[docs] +def lanczos_inverse_matrix(x, y, a=5, cond=1e-1): + """Regrid data using a maximum likelihood inverse Lanczos. + + Parameters + ---------- + x : np.ndarray[m] + Points to regrid data onto. Must be regularly spaced. + y : np.ndarray[n] + Points we have data at. Irregular spacing. + a : integer, optional + Lanczos width parameter. + cond : float + Relative condition number for pseudo-inverse. + + Returns + ------- + matrix : np.ndarray[m, n] + Lanczos regridding matrix. Apply to data with `np.dot(matrix, data)`. + """ + lz_forward = lanczos_forward_matrix(x, y, a) + return la.pinv(lz_forward, rcond=cond)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/util/rfi.html b/docs/_modules/draco/util/rfi.html new file mode 100644 index 000000000..ac234e04c --- /dev/null +++ b/docs/_modules/draco/util/rfi.html @@ -0,0 +1,311 @@ + + + + + + draco.util.rfi — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.util.rfi

+"""Collection of routines for RFI excision."""
+
+import numpy as np
+from scipy.ndimage import convolve1d
+
+
+
+[docs] +def sumthreshold_py( + data, + max_m=16, + start_flag=None, + threshold1=None, + remove_median=True, + correct_for_missing=True, +): + """SumThreshold outlier detection algorithm. + + See http://www.astro.rug.nl/~offringa/SumThreshold.pdf for description of + the algorithm. + + Parameters + ---------- + data : np.ndarray[:, :] + The data to flag. + max_m : int, optional + Maximum size to expand to. + start_flag : np.ndarray[:, :], optional + A boolean array of the initially flagged data. + threshold1 : float, optional + Initial threshold. By default use the 95 percentile. + remove_median : bool, optional + Subtract the median of the full 2D dataset. Default is True. + correct_for_missing : bool, optional + Correct for missing counts + + Returns + ------- + mask : np.ndarray[:, :] + Boolean array, with `True` entries marking outlier data. + """ + data = np.copy(data) + (ny, nx) = data.shape + + if start_flag is None: + start_flag = np.isnan(data) + flag = np.copy(start_flag) + + if remove_median: + data -= np.median(data[~flag]) + + if threshold1 is None: + threshold1 = np.percentile(data[~flag], 95.0) + + m = 1 + while m <= max_m: + if m == 1: + threshold = threshold1 + else: + threshold = threshold1 / 1.5 ** (np.log2(m)) + + # The centre of the window for even windows is the bin right to the left of + # centre. I want the origin at the leftmost bin + if m == 1: + centre = 0 + else: + centre = m // 2 - 1 + + ## X-axis + + data[flag] = 0.0 + count = (~flag).astype(np.float64) + + # Convolution of the data + dconv = convolve1d( + data, weights=np.ones(m, dtype=float), origin=-centre, axis=1 + )[:, : (nx - m + 1)] + + # Convolution of the counts + cconv = convolve1d( + count, weights=np.ones(m, dtype=float), origin=-centre, axis=1 + )[:, : (nx - m + 1)] + if correct_for_missing: + cconv = m**0.5 * cconv**0.5 + flag_temp = dconv > cconv * threshold + flag_temp += dconv < -cconv * threshold + for ii in range(flag_temp.shape[1]): + flag[:, ii : (ii + m)] += flag_temp[:, ii][:, np.newaxis] + + ## Y-axis + + data[flag] = 0.0 + count = (~flag).astype(np.float64) + # Convolution of the data + dconv = convolve1d( + data, weights=np.ones(m, dtype=float), origin=-centre, axis=0 + )[: (ny - m + 1), :] + # Convolution of the counts + cconv = convolve1d( + count, weights=np.ones(m, dtype=float), origin=-centre, axis=0 + )[: (ny - m + 1), :] + if correct_for_missing: + cconv = m**0.5 * cconv**0.5 + flag_temp = dconv > cconv * threshold + flag_temp += dconv < -cconv * threshold + + for ii in range(flag_temp.shape[0]): + flag[ii : ii + m, :] += flag_temp[ii, :][np.newaxis, :] + + m *= 2 + + return flag
+ + + +# This routine might be substituted by a faster one later +sumthreshold = sumthreshold_py + + +# Scale-invariant rank (SIR) functions +
+[docs] +def sir1d(basemask, eta=0.2): + """Numpy implementation of the scale-invariant rank (SIR) operator. + + For more information, see arXiv:1201.3364v2. + + Parameters + ---------- + basemask : numpy 1D array of boolean type + Array with the threshold mask previously generated. + 1 (True) for flagged points, 0 (False) otherwise. + eta : float + Aggressiveness of the method: with eta=0, no additional samples are + flagged and the function returns basemask. With eta=1, all samples + will be flagged. The authors in arXiv:1201.3364v2 seem to be convinced + that 0.2 is a mostly universally optimal value, but no optimization + has been done on CHIME data. + + Returns + ------- + mask : numpy 1D array of boolean type + The mask after the application of the (SIR) operator. Same shape and + type as basemask. + """ + n = basemask.size + psi = basemask.astype(np.float64) - 1.0 + eta + + M = np.zeros(n + 1, dtype=np.float64) + M[1:] = np.cumsum(psi) + + MP = np.minimum.accumulate(M)[:-1] + MQ = np.concatenate((np.maximum.accumulate(M[-2::-1])[-2::-1], M[-1, np.newaxis])) + + return (MQ - MP) >= 0.0
+ + + +
+[docs] +def sir(basemask, eta=0.2, only_freq=False, only_time=False): + """Apply the SIR operator over the frequency and time axes for each product. + + This is a wrapper for `sir1d`. It loops over times, applying `sir1d` + across the frequency axis. It then loops over frequencies, applying `sir1d` + across the time axis. It returns the logical OR of these two masks. + + Parameters + ---------- + basemask : np.ndarray[nfreq, nprod, ntime] of boolean type + The previously generated threshold mask. + 1 (True) for masked points, 0 (False) otherwise. + eta : float + Aggressiveness of the method: with eta=0, no additional samples are + flagged and the function returns basemask. With eta=1, all samples + will be flagged. + only_freq : bool + Only apply the SIR operator across the frequency axis. + only_time : bool + Only apply the SIR operator across the time axis. + + Returns + ------- + mask : np.ndarray[nfreq, nprod, ntime] of boolean type + The mask after the application of the SIR operator. + """ + if only_freq and only_time: + raise ValueError("Only one of only_freq and only_time can be True.") + + nfreq, nprod, ntime = basemask.shape + + newmask = basemask.astype(bool).copy() + + for pp in range(nprod): + if not only_time: + for tt in range(ntime): + newmask[:, pp, tt] |= sir1d(basemask[:, pp, tt], eta=eta) + + if not only_freq: + for ff in range(nfreq): + newmask[ff, pp, :] |= sir1d(basemask[ff, pp, :], eta=eta) + + return newmask
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/util/testing.html b/docs/_modules/draco/util/testing.html new file mode 100644 index 000000000..d203e8d91 --- /dev/null +++ b/docs/_modules/draco/util/testing.html @@ -0,0 +1,332 @@ + + + + + + draco.util.testing — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.util.testing

+"""draco test utils."""
+
+from typing import List, Optional, Tuple, Union
+
+import numpy as np
+from caput import config, memh5, pipeline
+
+from ..core.containers import SiderealStream
+from ..core.task import SingleTask
+from . import random
+
+
+
+[docs] +class DummyTask(SingleTask): + """Produce an empty data stream for testing. + + Attributes + ---------- + total_len : int + Length of output data stream. Default: 1. + tag : str + What to use as a tag for the produced data. + """ + + total_len = config.Property(default=1, proptype=int) + tag = config.Property(proptype=str) + +
+[docs] + def process(self): + """Produce an empty stream and pass on. + + Returns + ------- + cont : subclass of `memh5.BasicCont` + Empty data stream. + """ + if self.total_len == 0: + raise pipeline.PipelineStopIteration + + self.log.debug(f"Producing test data '{self.tag}'...") + + cont = memh5.BasicCont() + + if "tag" not in cont.attrs: + cont.attrs["tag"] = self.tag + + self.total_len -= 1 + return cont
+
+ + + +
+[docs] +def mock_freq_data( + freq: np.ndarray, + ntime: int, + delaycut: float, + ndata: Optional[int] = None, + noise: float = 0.0, + bad_freq: Optional[np.ndarray] = None, + rng: Optional[np.random.Generator] = None, +) -> Tuple[np.ndarray, np.ndarray]: + """Make mock delay data with a constant delay spectrum up to a specified cut. + + Parameters + ---------- + freq + Frequencies of each channel (in MHz). + ntime + Number of independent time samples. + delaycut + Cutoff in us. + ndata + Number of correlated data sets. If not set (i.e. `None`) then do no add a + dataset axis. + noise + RMS noise level in the data. + bad_freq + A list of bad frequencies to mask out. + rng + The random number generator to use. + + Return + ------ + data + The 2D/3D data array [dataset, freq, time]. If ndata is `None` then the dataset + axis is dropped. + weights + The 2D weights data [freq, time]. + """ + nfreq = len(freq) + ndelay = nfreq + + df = np.abs(freq[1] - freq[0]) + + delays = np.fft.fftfreq(ndelay, df) + dspec = np.where(np.abs(delays) < delaycut, 1.0, 0.0) + + # Construct a set of delay spectra + delay_spectra = random.complex_normal(size=(ntime, ndelay), rng=rng) + delay_spectra *= dspec**0.5 + + # Generate the noise realisation + outshape = (nfreq, ntime) + if ndata is not None: + outshape = (ndata, *outshape) + data = noise * random.complex_normal(size=outshape, rng=rng) + + # Transform to get frequency spectra + data += np.fft.fft(delay_spectra, axis=-1).T + + weights = np.empty(data.shape, dtype=np.float64) + weights[:] = 1.0 / noise**2 + + if bad_freq: + data[..., bad_freq, :] = 0.0 + weights[..., bad_freq, :] = 0.0 + + return data, weights
+ + + +
+[docs] +class RandomFreqData(random.RandomTask): + """Generate a random sidereal stream with structure in delay. + + Attributes + ---------- + num_realisation + How many to generate in subsequent process calls. + num_correlated + The number of correlated realisations output per cycle. + num_ra + The number of RA samples in the output. + num_base + The number of baselines in the output. + freq_start, freq_end + The start and end frequencies. + num_freq + The number of frequency channels. + delay_cut + The maximum delay in the data in us. + noise + The RMS noise level. + """ + + num_realisation = config.Property(proptype=int, default=1) + num_correlated = config.Property(proptype=int, default=None) + + num_ra = config.Property(proptype=int) + num_base = config.Property(proptype=int) + + freq_start = config.Property(proptype=float, default=800.0) + freq_end = config.Property(proptype=float, default=400.0) + num_freq = config.Property(proptype=int, default=1024) + + delay_cut = config.Property(proptype=float, default=0.2) + noise = config.Property(proptype=float, default=1e-5) + +
+[docs] + def next(self) -> Union[SiderealStream, List[SiderealStream]]: + """Generate correlated sidereal streams. + + Returns + ------- + streams + Either a single stream (if num_correlated=None), or a list of correlated + streams. + """ + if self.num_realisation == 0: + raise pipeline.PipelineStopIteration() + + # Construct the frequency axis + freq = np.linspace( + self.freq_start, + self.freq_end, + self.num_freq, + endpoint=False, + ) + + streams = [] + + # Construct all the sidereal streams + for ii in range(self.num_correlated or 1): + stream = SiderealStream( + input=5, # Probably should be something smarter + freq=freq, + ra=self.num_ra, + stack=self.num_base, + ) + stream.redistribute("stack") + ssv = stream.vis[:].local_array + ssw = stream.weight[:].local_array + + streams.append((stream, ssv, ssw)) + + # Iterate over baselines and construct correlated realisations for each, and + # then insert them into each of the sidereal streams + for ii in range(ssv.shape[1]): + d, w = mock_freq_data( + freq, + self.num_ra, + self.delay_cut, + ndata=(self.num_correlated or 1), + noise=self.noise, + ) + + for jj, (_, ssv, ssw) in enumerate(streams): + ssv[:, ii] = d[jj] + ssw[:, ii] = w[jj] + + self.num_realisation -= 1 + + # Don't return a list of streams if num_correlated is None + if self.num_correlated is None: + return streams[0][0] + + return [s for s, *_ in streams]
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/draco/util/tools.html b/docs/_modules/draco/util/tools.html new file mode 100644 index 000000000..85dcb2ad0 --- /dev/null +++ b/docs/_modules/draco/util/tools.html @@ -0,0 +1,679 @@ + + + + + + draco.util.tools — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for draco.util.tools

+"""Collection of miscellaneous routines.
+
+Miscellaneous tasks should be placed in :py:mod:`draco.core.misc`.
+"""
+
+import numpy as np
+
+# Keep this here for compatibility
+from caput.tools import invert_no_zero  # noqa: F401
+from numpy.lib.recfunctions import structured_to_unstructured
+
+from ._fast_tools import _calc_redundancy
+
+
+
+[docs] +def cmap(i, j, n): + """Given a pair of feed indices, return the pair index. + + Parameters + ---------- + i, j : integer + Feed index. + n : integer + Total number of feeds. + + Returns + ------- + pi : integer + Pair index. + """ + if i <= j: + return (n * (n + 1) // 2) - ((n - i) * (n - i + 1) // 2) + (j - i) + + return cmap(j, i, n)
+ + + +
+[docs] +def icmap(ix, n): + """Inverse feed map. + + Parameters + ---------- + ix : integer + Pair index. + n : integer + Total number of feeds. + + Returns + ------- + fi, fj : integer + Feed indices. + """ + for ii in range(n): + if cmap(ii, n - 1, n) >= ix: + break + + i = ii + j = ix - cmap(i, i, n) + i + return i, j
+ + + +
+[docs] +def find_key(key_list, key): + """Find the index of a key in a list of keys. + + This is a wrapper for the list method `index` + that can search any interable (not just lists) + and will return None if the key is not found. + + Parameters + ---------- + key_list : iterable + Iterable containing keys to search + key : object to be searched + Keys to search for + + Returns + ------- + index : int or None + The index of `key` in `key_list`. + If `key_list` does not contain `key`, + then None is returned. + """ + try: + return [tuple(x) for x in key_list].index(tuple(key)) + except TypeError: + return list(key_list).index(key) + except ValueError: + return None
+ + + +
+[docs] +def find_keys(key_list, keys, require_match=False): + """Find the indices of keys into a list of keys. + + Parameters + ---------- + key_list : iterable + Iterable of keys to search + keys : iterable + Keys to search for + require_match : bool + Require that `key_list` contain every element of `keys`, + and if not, raise ValueError. + + Returns + ------- + indices : list of int or None + List of the same length as `keys` containing + the indices of `keys` in `key_list`. If `require_match` + is False, then this can also contain None for keys + that are not contained in `key_list`. + """ + # Significantly faster than repeated calls to find_key + try: + dct = {tuple(kk): ii for ii, kk in enumerate(key_list)} + index = [dct.get(tuple(key)) for key in keys] + except TypeError: + dct = {kk: ii for ii, kk in enumerate(key_list)} + index = [dct.get(key) for key in keys] + + if require_match and any(ind is None for ind in index): + raise ValueError("Could not find all of the keys.") + + return index
+ + + +
+[docs] +def find_inputs(input_index, inputs, require_match=False): + """Find the indices of inputs into a list of inputs. + + This behaves similarly to `find_keys` but will automatically choose the key to + match on. + + Parameters + ---------- + input_index : np.ndarray + Inputs to search + inputs : np.ndarray + Inputs to find + require_match : bool + Require that `input_index` contain every element of `inputs`, + and if not, raise ValueError. + + Returns + ------- + indices : list of int or None + List of the same length as `inputs` containing + the indices of `inputs` in `input_inswx`. If `require_match` + is False, then this can also contain None for inputs + that are not contained in `input_index`. + """ + # Significantly faster than repeated calls to find_key + + if "correlator_input" in input_index.dtype.fields: + field_to_match = "correlator_input" + elif "chan_id" in input_index.dtype.fields: + field_to_match = "chan_id" + else: + raise ValueError( + "`input_index` must have either a `chan_id` or `correlator_input` field." + ) + + if field_to_match not in inputs.dtype.fields: + raise ValueError(f"`inputs` array does not have a `{field_to_match!s}` field.") + + return find_keys( + input_index[field_to_match], inputs[field_to_match], require_match=require_match + )
+ + + +
+[docs] +def apply_gain(vis, gain, axis=1, out=None, prod_map=None): + """Apply per input gains to a set of visibilities packed in upper triangular format. + + This allows us to apply the gains while minimising the intermediate + products created. + + Parameters + ---------- + vis : np.ndarray[..., nprod, ...] + Array of visibility products. + gain : np.ndarray[..., ninput, ...] + Array of gains. One gain per input. + axis : integer, optional + The axis along which the inputs (or visibilities) are + contained. + out : np.ndarray + Array to place output in. If :obj:`None` create a new + array. This routine can safely use `out = vis`. + prod_map : ndarray of integer pairs + Gives the mapping from product axis to input pairs. If not supplied, + :func:`icmap` is used. + + Returns + ------- + out : np.ndarray + Visibility array with gains applied. Same shape as :obj:`vis`. + """ + nprod = vis.shape[axis] + ninput = gain.shape[axis] + + if prod_map is None and nprod != (ninput * (ninput + 1) // 2): + raise Exception("Number of inputs does not match the number of products.") + + if prod_map is not None: + if len(prod_map) != nprod: + msg = "Length of *prod_map* does not match number of input" " products." + raise ValueError(msg) + # Could check prod_map contents as well, but the loop should give a + # sensible error if this is wrong, and checking is expensive. + else: + prod_map = [icmap(pp, ninput) for pp in range(nprod)] + + if out is None: + out = np.empty_like(vis) + elif out.shape != vis.shape: + raise Exception("Output array is wrong shape.") + + # Define slices for use in gain & vis selection & combination + gain_vis_slice = tuple(slice(None) for i in range(axis)) + + # Iterate over input pairs and set gains + for pp in range(nprod): + # Determine the inputs. + ii, ij = prod_map[pp] + + # Fetch the gains + gi = gain[(*gain_vis_slice, ii)] + gj = gain[(*gain_vis_slice, ij)].conj() + + # Apply the gains and save into the output array. + out[(*gain_vis_slice, pp)] = vis[(*gain_vis_slice, pp)] * gi * gj + + return out
+ + + +
+[docs] +def extract_diagonal(utmat, axis=1): + """Extract the diagonal elements of an upper triangular array. + + Parameters + ---------- + utmat : np.ndarray[..., nprod, ...] + Upper triangular array. + axis : int, optional + Axis of array that is upper triangular. + + Returns + ------- + diag : np.ndarray[..., ninput, ...] + Diagonal of the array. + """ + # Estimate nside from the array shape + nside = int((2 * utmat.shape[axis]) ** 0.5) + + # Check that this nside is correct + if utmat.shape[axis] != (nside * (nside + 1) // 2): + msg = ( + "Array length (%i) of axis %i does not correspond upper triangle\ + of square matrix" + % (utmat.shape[axis], axis) + ) + raise RuntimeError(msg) + + # Find indices of the diagonal + diag_ind = [cmap(ii, ii, nside) for ii in range(nside)] + + # Construct slice objects representing the axes before and after the product axis + slice0 = (np.s_[:],) * axis + slice1 = (np.s_[:],) * (len(utmat.shape) - axis - 1) + + # Extract wanted elements with a giant slice + sl = (*slice0, diag_ind, *slice1) + return utmat[sl]
+ + + +
+[docs] +def calculate_redundancy(input_flags, prod_map, stack_index, nstack): + """Calculates the number of redundant baselines that were stacked to form each unique baseline. + + Accounts for the fact that some fraction of the inputs are flagged as bad at any given time. + + Parameters + ---------- + input_flags : np.ndarray [ninput, ntime] + Array indicating which inputs were good at each time. + Non-zero value indicates that an input was good. + + prod_map: np.ndarray[nprod] + The products that were included in the stack. + Typically found in the `index_map['prod']` attribute of the + `containers.TimeStream` or `containers.SiderealStream` object. + + stack_index: np.ndarray[nprod] + The index of the stack axis that each product went into. + Typically found in `reverse_map['stack']['stack']` attribute + of the `containers.Timestream` or `containers.SiderealStream` object. + + nstack: int + Total number of unique baselines. + + Returns + ------- + redundancy : np.ndarray[nstack, ntime] + Array indicating the total number of redundant baselines + with good inputs that were stacked into each unique baseline. + """ + ninput, ntime = input_flags.shape + redundancy = np.zeros((nstack, ntime), dtype=np.float32) + + if not np.any(input_flags): + input_flags = np.ones_like(input_flags) + + input_flags = np.ascontiguousarray(input_flags.astype(np.float32, copy=False)) + pm = structured_to_unstructured(prod_map, dtype=np.int16) + stack_index = np.ascontiguousarray(stack_index.astype(np.int32, copy=False)) + + # Call fast cython function to do calculation + _calc_redundancy(input_flags, pm, stack_index, nstack, redundancy) + + return redundancy
+ + + +
+[docs] +def redefine_stack_index_map(telescope, inputs, prod, stack, reverse_stack): + """Ensure baselines between unmasked inputs are used to represent each stack. + + Parameters + ---------- + telescope : :class: `drift.core.telescope` + Telescope object containing feed information. + inputs : np.ndarray[ninput,] of dtype=('correlator_input', 'chan_id') + The 'correlator_input' or 'chan_id' of the inputs in the stack. + prod : np.ndarray[nprod,] of dtype=('input_a', 'input_b') + The correlation products as pairs of inputs. + stack : np.ndarray[nstack,] of dtype=('prod', 'conjugate') + The index into the `prod` axis of a characteristic baseline included in the stack. + reverse_stack : np.ndarray[nprod,] of dtype=('stack', 'conjugate') + The index into the `stack` axis that each `prod` belongs. + + Returns + ------- + stack_new : np.ndarray[nstack,] of dtype=('prod', 'conjugate') + The updated `stack` index map, where each element is an index to a product + consisting of a pair of unmasked inputs. + stack_flag : np.ndarray[nstack,] of dtype=bool + Boolean flag that is True if this element of the stack index map is now valid, + and False if none of the baselines that were stacked contained unmasked inputs. + """ + # Determine mapping between inputs in the index_map and + # inputs in the telescope instance + tel_index = find_inputs(telescope.input_index, inputs, require_match=False) + + # Create a copy of the stack axis + stack_new = stack.copy() + stack_flag = np.zeros(stack_new.size, dtype=bool) + + # Loop over the stacked baselines + for sind, (ii, jj) in enumerate(prod[stack["prod"]]): + bi, bj = tel_index[ii], tel_index[jj] + + # Check that the represenative pair of inputs are present + # in the telescope instance and not masked. + if (bi is None) or (bj is None) or not telescope.feedmask[bi, bj]: + # Find alternative pairs of inputs using the reverse map + this_stack = np.flatnonzero(reverse_stack["stack"] == sind) + + # Loop over alternatives until we find an acceptable pair of inputs + for ts in this_stack: + tp = prod[ts] + ti, tj = tel_index[tp[0]], tel_index[tp[1]] + if (ti is not None) and (tj is not None) and telescope.feedmask[ti, tj]: + stack_new[sind]["prod"] = ts + stack_new[sind]["conjugate"] = reverse_stack[ts]["conjugate"] + stack_flag[sind] = True + break + else: + stack_flag[sind] = True + + return stack_new, stack_flag
+ + + +
+[docs] +def polarization_map(index_map, telescope, exclude_autos=True): + """Map the visibilities corresponding to entries in pol = ['XX', 'XY', 'YX', 'YY']. + + Parameters + ---------- + index_map : h5py.group or dict + Index map to map into polarizations. Must contain a `stack` + entry and an `input` entry. + telescope : :class: `drift.core.telescope` + Telescope object containing feed information. + exclude_autos: bool + If True (default), auto-correlations are set to -1. + + Returns + ------- + polmap : array of int + Array of size `nstack`. Each entry is the index to the + corresponding polarization in pol = ['XX', 'XY', 'YX', 'YY'] + """ + # Old versions of telescope object don't have the `stack_type` + # attribute. Assume those are of type `redundant`. + try: + teltype = telescope.stack_type + except AttributeError: + teltype = None + msg = ( + "Telescope object does not have a `stack_type` attribute.\n" + + "Assuming it is of type `redundant`" + ) + + if teltype is not None: + if not (teltype == "redundant"): + msg = "Telescope stack type needs to be 'redundant'. Is {0}" + raise RuntimeError(msg.format(telescope.stack_type)) + + # Older data's input map has a simpler dtype + try: + input_map = index_map["input"]["chan_id"][:] + except IndexError: + input_map = index_map["input"][:] + + pol = ["XX", "XY", "YX", "YY"] + nstack = len(index_map["stack"]) + # polmap: indices of each vis product in + # polarization list: ['XX', 'YY', 'XY', 'YX'] + polmap = np.zeros(nstack, dtype=int) + # For each entry in stack + for vi in range(nstack): + # Product index + pi = index_map["stack"][vi][0] + # Inputs that go into this product + ipt0 = input_map[index_map["prod"][pi][0]] + ipt1 = input_map[index_map["prod"][pi][1]] + + # Exclude autos if exclude_autos == True + if exclude_autos and (ipt0 == ipt1): + polmap[vi] = -1 + continue + + # Find polarization of first input + if telescope.beamclass[ipt0] == 0: + polstring = "X" + elif telescope.beamclass[ipt0] == 1: + polstring = "Y" + else: + # Not a CHIME feed or not On. Ignore. + polmap[vi] = -1 + continue + # Find polarization of second input and add it to polstring + if telescope.beamclass[ipt1] == 0: + polstring += "X" + elif telescope.beamclass[ipt1] == 1: + polstring += "Y" + else: + # Not a CHIME feed or not On. Ignore. + polmap[vi] = -1 + continue + # If conjugate, flip polstring ('XY -> 'YX) + if telescope.feedconj[ipt0, ipt1]: + polstring = polstring[::-1] + # Populate polmap + polmap[vi] = pol.index(polstring) + + return polmap
+ + + +
+[docs] +def baseline_vector(index_map, telescope): + """Baseline vectors in meters. + + Parameters + ---------- + index_map : h5py.group or dict + Index map to map into polarizations. Must contain a `stack` + entry and an `input` entry. + telescope : :class: `drift.core.telescope` + Telescope object containing feed information. + + Returns + ------- + bvec_m : array + Array of shape (2, nstack). The 2D baseline vector + (in meters) for each visibility in index_map['stack'] + """ + nstack = len(index_map["stack"]) + # Baseline vectors in meters. + bvec_m = np.zeros((2, nstack), dtype=np.float64) + # Older data's input map has a simpler dtype + try: + input_map = index_map["input"]["chan_id"][:] + except IndexError: + input_map = index_map["input"][:] + + # Compute all baseline vectors. + for vi in range(nstack): + # Product index + pi = index_map["stack"][vi][0] + # Inputs that go into this product + ipt0 = input_map[index_map["prod"][pi][0]] + ipt1 = input_map[index_map["prod"][pi][1]] + + # Beseline vector in meters + unique_index = telescope.feedmap[ipt0, ipt1] + bvec_m[:, vi] = telescope.baselines[unique_index] + # No need to conjugate. Already done in telescope.baselines. + # if telescope.feedconj[ipt0, ipt1]: + # bvec_m[:, vi] *= -1. + + return bvec_m
+ + + +
+[docs] +def window_generalised(x, window="nuttall"): + """A generalised high-order window at arbitrary locations. + + Parameters + ---------- + x : np.ndarray[n] + Location to evaluate at. Values outside the range 0 to 1 are zero. + window : one of {'nuttall', 'blackman_nuttall', 'blackman_harris'} + Type of window function to return. + + Returns + ------- + w : np.ndarray[n] + Window function. + """ + a_table = { + "uniform": np.array([1, 0, 0, 0]), + "hann": np.array([0.5, -0.5, 0, 0]), + "hanning": np.array([0.5, -0.5, 0, 0]), + "hamming": np.array([0.53836, -0.46164, 0, 0]), + "blackman": np.array([0.42, -0.5, 0.08, 0]), + "nuttall": np.array([0.355768, -0.487396, 0.144232, -0.012604]), + "blackman_nuttall": np.array([0.3635819, -0.4891775, 0.1365995, -0.0106411]), + "blackman_harris": np.array([0.35875, -0.48829, 0.14128, -0.01168]), + } + + a = a_table[window] + + t = 2 * np.pi * np.arange(4)[:, np.newaxis] * x[np.newaxis, :] + + w = (a[:, np.newaxis] * np.cos(t)).sum(axis=0) + return np.where((x >= 0) & (x <= 1), w, 0)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/index.html b/docs/_modules/index.html new file mode 100644 index 000000000..e0c60f385 --- /dev/null +++ b/docs/_modules/index.html @@ -0,0 +1,130 @@ + + + + + + Overview: module code — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/docs/_sources/_autosummary/draco.analysis.beamform.rst.txt b/docs/_sources/_autosummary/draco.analysis.beamform.rst.txt new file mode 100644 index 000000000..7c20adc23 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.beamform.rst.txt @@ -0,0 +1,43 @@ +draco.analysis.beamform +======================= + +.. automodule:: draco.analysis.beamform + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + icrs_to_cirs + + + + + + .. rubric:: Classes + + .. autosummary:: + + BeamForm + BeamFormBase + BeamFormCat + BeamFormExternal + BeamFormExternalBase + BeamFormExternalCat + HealpixBeamForm + RingMapBeamForm + RingMapStack2D + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.delay.rst.txt b/docs/_sources/_autosummary/draco.analysis.delay.rst.txt new file mode 100644 index 000000000..76d635d42 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.delay.rst.txt @@ -0,0 +1,57 @@ +draco.analysis.delay +==================== + +.. automodule:: draco.analysis.delay + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + delay_power_spectrum_gibbs + delay_spectrum_gibbs + delay_spectrum_gibbs_cross + delay_spectrum_wiener_filter + flatten_axes + fourier_matrix + fourier_matrix_c2c + fourier_matrix_c2r + fourier_matrix_r2c + match_axes + null_delay_filter + stokes_I + + + + + + .. rubric:: Classes + + .. autosummary:: + + DelayCrossPowerSpectrumEstimator + DelayFilter + DelayFilterBase + DelayGeneralContainerBase + DelayGibbsSamplerBase + DelayPowerSpectrumGeneralEstimator + DelayPowerSpectrumStokesIEstimator + DelaySpectrumEstimator + DelaySpectrumEstimatorBase + DelaySpectrumWienerBase + DelaySpectrumWienerEstimator + DelayTransformBase + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.fgfilter.rst.txt b/docs/_sources/_autosummary/draco.analysis.fgfilter.rst.txt new file mode 100644 index 000000000..741421e7a --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.fgfilter.rst.txt @@ -0,0 +1,30 @@ +draco.analysis.fgfilter +======================= + +.. automodule:: draco.analysis.fgfilter + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + KLModeProject + SVDModeProject + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.flagging.rst.txt b/docs/_sources/_autosummary/draco.analysis.flagging.rst.txt new file mode 100644 index 000000000..b5ac56271 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.flagging.rst.txt @@ -0,0 +1,62 @@ +draco.analysis.flagging +======================= + +.. automodule:: draco.analysis.flagging + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + complex_med + destripe + inverse_binom_cdf_prob + mad + medfilt + p_to_sigma + sigma_to_p + tv_channels_flag + + + + + + .. rubric:: Classes + + .. autosummary:: + + ApplyBaselineMask + ApplyRFIMask + ApplyTimeFreqMask + BlendStack + CollapseBaselineMask + DayMask + FindBeamformedOutliers + MaskBadGains + MaskBaselines + MaskBeamformedOutliers + MaskBeamformedWeights + MaskData + MaskFreq + MaskMModeData + RFIMask + RFISensitivityMask + RadiometerWeight + SanitizeWeights + SmoothVisWeight + ThresholdVisWeightBaseline + ThresholdVisWeightFrequency + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.mapmaker.rst.txt b/docs/_sources/_autosummary/draco.analysis.mapmaker.rst.txt new file mode 100644 index 000000000..6bf8b7565 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.mapmaker.rst.txt @@ -0,0 +1,38 @@ +draco.analysis.mapmaker +======================= + +.. automodule:: draco.analysis.mapmaker + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + pinv_svd + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaseMapMaker + DirtyMapMaker + MaximumLikelihoodMapMaker + WienerMapMaker + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.powerspectrum.rst.txt b/docs/_sources/_autosummary/draco.analysis.powerspectrum.rst.txt new file mode 100644 index 000000000..e50d3f770 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.powerspectrum.rst.txt @@ -0,0 +1,29 @@ +draco.analysis.powerspectrum +============================ + +.. automodule:: draco.analysis.powerspectrum + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + QuadraticPSEstimation + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.rst.txt b/docs/_sources/_autosummary/draco.analysis.rst.txt new file mode 100644 index 000000000..cc7419ba8 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.rst.txt @@ -0,0 +1,23 @@ +draco.analysis +============== + +.. automodule:: draco.analysis + + + + + + + + + + + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.sensitivity.rst.txt b/docs/_sources/_autosummary/draco.analysis.sensitivity.rst.txt new file mode 100644 index 000000000..36eb8b492 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.sensitivity.rst.txt @@ -0,0 +1,29 @@ +draco.analysis.sensitivity +========================== + +.. automodule:: draco.analysis.sensitivity + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + ComputeSystemSensitivity + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.sidereal.rst.txt b/docs/_sources/_autosummary/draco.analysis.sidereal.rst.txt new file mode 100644 index 000000000..5f7b8fab8 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.sidereal.rst.txt @@ -0,0 +1,35 @@ +draco.analysis.sidereal +======================= + +.. automodule:: draco.analysis.sidereal + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + SiderealGrouper + SiderealRegridder + SiderealRegridderCubic + SiderealRegridderLinear + SiderealRegridderNearest + SiderealStacker + SiderealStackerMatch + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.sourcestack.rst.txt b/docs/_sources/_autosummary/draco.analysis.sourcestack.rst.txt new file mode 100644 index 000000000..c93bf3889 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.sourcestack.rst.txt @@ -0,0 +1,31 @@ +draco.analysis.sourcestack +========================== + +.. automodule:: draco.analysis.sourcestack + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + GroupSourceStacks + RandomSubset + SourceStack + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.svdfilter.rst.txt b/docs/_sources/_autosummary/draco.analysis.svdfilter.rst.txt new file mode 100644 index 000000000..052e9e8c5 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.svdfilter.rst.txt @@ -0,0 +1,36 @@ +draco.analysis.svdfilter +======================== + +.. automodule:: draco.analysis.svdfilter + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + svd_em + + + + + + .. rubric:: Classes + + .. autosummary:: + + SVDFilter + SVDSpectrumEstimator + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.analysis.transform.rst.txt b/docs/_sources/_autosummary/draco.analysis.transform.rst.txt new file mode 100644 index 000000000..35c549912 --- /dev/null +++ b/docs/_sources/_autosummary/draco.analysis.transform.rst.txt @@ -0,0 +1,43 @@ +draco.analysis.transform +======================== + +.. automodule:: draco.analysis.transform + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + CollateProducts + Downselect + FrequencyRebin + HPFTimeStream + MModeInverseTransform + MModeTransform + MixData + ReduceBase + ReduceVar + Regridder + SelectFreq + SelectPol + ShiftRA + SiderealMModeResample + TransformJanskyToKelvin + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.core.containers.rst.txt b/docs/_sources/_autosummary/draco.core.containers.rst.txt new file mode 100644 index 000000000..5538547b7 --- /dev/null +++ b/docs/_sources/_autosummary/draco.core.containers.rst.txt @@ -0,0 +1,91 @@ +draco.core.containers +===================== + +.. automodule:: draco.core.containers + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + copy_datasets_filter + empty_like + empty_timestream + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaselineMask + CommonModeGainData + CommonModeSiderealGainData + ContainerBase + DataWeightContainer + DelayContainer + DelayCrossSpectrum + DelayCutoff + DelaySpectrum + DelayTransform + FormedBeam + FormedBeamHA + FormedBeamHAMask + FormedBeamMask + FreqContainer + FrequencyStack + FrequencyStackByPol + GainData + GainDataBase + GridBeam + HEALPixBeam + HealpixContainer + HybridVisMModes + HybridVisStream + KLModes + MContainer + MModes + Map + MockFrequencyStack + MockFrequencyStackByPol + Powerspectrum2D + RFIMask + RingMap + RingMapMask + SVDModes + SVDSpectrum + SampleVarianceContainer + SiderealBaselineMask + SiderealContainer + SiderealGainData + SiderealRFIMask + SiderealStream + SourceCatalog + SpectroscopicCatalog + Stack3D + StaticGainData + SystemSensitivity + TODContainer + TableBase + TimeStream + TrackBeam + VisBase + VisContainer + VisGridStream + WaveletSpectrum + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.core.io.rst.txt b/docs/_sources/_autosummary/draco.core.io.rst.txt new file mode 100644 index 000000000..298b1626a --- /dev/null +++ b/docs/_sources/_autosummary/draco.core.io.rst.txt @@ -0,0 +1,54 @@ +draco.core.io +============= + +.. automodule:: draco.core.io + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + get_beamtransfer + get_telescope + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaseLoadFiles + FindFiles + LoadBasicCont + LoadBeamTransfer + LoadFITSCatalog + LoadFiles + LoadFilesFromParams + LoadMaps + LoadProductManager + Print + Save + SaveConfig + SaveModuleVersions + SaveZarrZip + SelectionsMixin + Truncate + WaitZarrZip + ZarrZipHandle + ZipZarrContainers + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.core.misc.rst.txt b/docs/_sources/_autosummary/draco.core.misc.rst.txt new file mode 100644 index 000000000..cafaef194 --- /dev/null +++ b/docs/_sources/_autosummary/draco.core.misc.rst.txt @@ -0,0 +1,34 @@ +draco.core.misc +=============== + +.. automodule:: draco.core.misc + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + AccumulateList + ApplyGain + CheckMPIEnvironment + MakeCopy + PassOn + WaitUntil + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.core.rst.txt b/docs/_sources/_autosummary/draco.core.rst.txt new file mode 100644 index 000000000..ebf85019d --- /dev/null +++ b/docs/_sources/_autosummary/draco.core.rst.txt @@ -0,0 +1,23 @@ +draco.core +========== + +.. automodule:: draco.core + + + + + + + + + + + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.core.task.rst.txt b/docs/_sources/_autosummary/draco.core.task.rst.txt new file mode 100644 index 000000000..8717307e7 --- /dev/null +++ b/docs/_sources/_autosummary/draco.core.task.rst.txt @@ -0,0 +1,43 @@ +draco.core.task +=============== + +.. automodule:: draco.core.task + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + group_tasks + + + + + + .. rubric:: Classes + + .. autosummary:: + + Delete + LoggedTask + MPILogFilter + MPILoggedTask + MPITask + ReturnFirstInputOnFinish + ReturnLastInputOnFinish + SetMPILogging + SingleTask + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.synthesis.gain.rst.txt b/docs/_sources/_autosummary/draco.synthesis.gain.rst.txt new file mode 100644 index 000000000..bbf0ac0ee --- /dev/null +++ b/docs/_sources/_autosummary/draco.synthesis.gain.rst.txt @@ -0,0 +1,41 @@ +draco.synthesis.gain +==================== + +.. automodule:: draco.synthesis.gain + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + constrained_gaussian_realisation + gaussian_realisation + generate_fluctuations + + + + + + .. rubric:: Classes + + .. autosummary:: + + BaseGains + GainStacker + RandomGains + RandomSiderealGains + SiderealGains + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.synthesis.noise.rst.txt b/docs/_sources/_autosummary/draco.synthesis.noise.rst.txt new file mode 100644 index 000000000..195c3810f --- /dev/null +++ b/docs/_sources/_autosummary/draco.synthesis.noise.rst.txt @@ -0,0 +1,32 @@ +draco.synthesis.noise +===================== + +.. automodule:: draco.synthesis.noise + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + GaussianNoise + GaussianNoiseDataset + ReceiverTemperature + SampleNoise + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.synthesis.rst.txt b/docs/_sources/_autosummary/draco.synthesis.rst.txt new file mode 100644 index 000000000..c6b8dc2b6 --- /dev/null +++ b/docs/_sources/_autosummary/draco.synthesis.rst.txt @@ -0,0 +1,23 @@ +draco.synthesis +=============== + +.. automodule:: draco.synthesis + + + + + + + + + + + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.synthesis.stream.rst.txt b/docs/_sources/_autosummary/draco.synthesis.stream.rst.txt new file mode 100644 index 000000000..ab16dd0a3 --- /dev/null +++ b/docs/_sources/_autosummary/draco.synthesis.stream.rst.txt @@ -0,0 +1,32 @@ +draco.synthesis.stream +====================== + +.. automodule:: draco.synthesis.stream + + + + + + + + + + + + .. rubric:: Classes + + .. autosummary:: + + ExpandProducts + MakeSiderealDayStream + MakeTimeStream + SimulateSidereal + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.util.exception.rst.txt b/docs/_sources/_autosummary/draco.util.exception.rst.txt new file mode 100644 index 000000000..68c0a1e2c --- /dev/null +++ b/docs/_sources/_autosummary/draco.util.exception.rst.txt @@ -0,0 +1,29 @@ +draco.util.exception +==================== + +.. automodule:: draco.util.exception + + + + + + + + + + + + + + + + .. rubric:: Exceptions + + .. autosummary:: + + ConfigError + + + + + diff --git a/docs/_sources/_autosummary/draco.util.random.rst.txt b/docs/_sources/_autosummary/draco.util.random.rst.txt new file mode 100644 index 000000000..ba95d0cfb --- /dev/null +++ b/docs/_sources/_autosummary/draco.util.random.rst.txt @@ -0,0 +1,41 @@ +draco.util.random +================= + +.. automodule:: draco.util.random + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + complex_normal + complex_wishart + default_rng + mpi_random_seed + standard_complex_normal + standard_complex_wishart + + + + + + .. rubric:: Classes + + .. autosummary:: + + MultithreadedRNG + RandomTask + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.util.regrid.rst.txt b/docs/_sources/_autosummary/draco.util.regrid.rst.txt new file mode 100644 index 000000000..657fa7859 --- /dev/null +++ b/docs/_sources/_autosummary/draco.util.regrid.rst.txt @@ -0,0 +1,32 @@ +draco.util.regrid +================= + +.. automodule:: draco.util.regrid + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + band_wiener + lanczos_forward_matrix + lanczos_inverse_matrix + lanczos_kernel + + + + + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.util.rfi.rst.txt b/docs/_sources/_autosummary/draco.util.rfi.rst.txt new file mode 100644 index 000000000..bbab95a48 --- /dev/null +++ b/docs/_sources/_autosummary/draco.util.rfi.rst.txt @@ -0,0 +1,32 @@ +draco.util.rfi +============== + +.. automodule:: draco.util.rfi + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + sir + sir1d + sumthreshold + sumthreshold_py + + + + + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.util.rst.txt b/docs/_sources/_autosummary/draco.util.rst.txt new file mode 100644 index 000000000..25892cf49 --- /dev/null +++ b/docs/_sources/_autosummary/draco.util.rst.txt @@ -0,0 +1,23 @@ +draco.util +========== + +.. automodule:: draco.util + + + + + + + + + + + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.util.testing.rst.txt b/docs/_sources/_autosummary/draco.util.testing.rst.txt new file mode 100644 index 000000000..bf1f03027 --- /dev/null +++ b/docs/_sources/_autosummary/draco.util.testing.rst.txt @@ -0,0 +1,36 @@ +draco.util.testing +================== + +.. automodule:: draco.util.testing + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + mock_freq_data + + + + + + .. rubric:: Classes + + .. autosummary:: + + DummyTask + RandomFreqData + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.util.tools.rst.txt b/docs/_sources/_autosummary/draco.util.tools.rst.txt new file mode 100644 index 000000000..0ad7cdce8 --- /dev/null +++ b/docs/_sources/_autosummary/draco.util.tools.rst.txt @@ -0,0 +1,40 @@ +draco.util.tools +================ + +.. automodule:: draco.util.tools + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + apply_gain + baseline_vector + calculate_redundancy + cmap + extract_diagonal + find_inputs + find_key + find_keys + icmap + polarization_map + redefine_stack_index_map + window_generalised + + + + + + + + + + + + + diff --git a/docs/_sources/_autosummary/draco.util.truncate.rst.txt b/docs/_sources/_autosummary/draco.util.truncate.rst.txt new file mode 100644 index 000000000..446a4ffbe --- /dev/null +++ b/docs/_sources/_autosummary/draco.util.truncate.rst.txt @@ -0,0 +1,31 @@ +draco.util.truncate +=================== + +.. automodule:: draco.util.truncate + + + + + + + + .. rubric:: Functions + + .. autosummary:: + + bit_truncate + bit_truncate_fixed + bit_truncate_weights + + + + + + + + + + + + + diff --git a/docs/_sources/dev.rst.txt b/docs/_sources/dev.rst.txt new file mode 100644 index 000000000..e7e7837a4 --- /dev/null +++ b/docs/_sources/dev.rst.txt @@ -0,0 +1,54 @@ +Development Guidelines +---------------------- + +The idea behind this repository is to keep track of the CHIME pipeline +development, such that the union of the input data and this repository always +gives the same output. This requires that we keep track of not only the code and +scripts in this repository, but also any dependencies (discussed below). + +As far as possible the pipeline code should be using Kiyo's pipeline task module `caput.pipeline` ([doc](http://bao.phas.ubc.ca/codedoc/caput/)). + +Structure +^^^^^^^^^ + +Tasks should go into `draco`. + +Branches +^^^^^^^^ + +Development should be done in `passX` branches, or in feature branches that are +merged back in. + +Dependencies +^^^^^^^^^^^^ + +Dependencies should be installable python packages. This means that they must +have a ``setup.py`` script in their root directory, and should be installable +using `python setup.py install`. They are kept track of in a ``requirements.txt`` +file. This file can contain references to exact versions of dependencies, using +both version tags, and commit hashes. An example ``requirements.txt`` file is +given below:: + + -e git+https://github.com/radiocosmology/caput@ee1c55ea4cf8cb7857af2ef3adcb2439d876768d#egg=caput-master + -e git+ssh://git@bitbucket.org/chime/ch_util.git@e33b174696509b158c15cf0bfc27f4cb2b0c6406#egg=ch_util-e + -e git+https://github.com/radiocosmology/cora@v1.0.0#egg=cora + -e git+https://github.com/radiocosmology/driftscan@v1.0.0#egg=driftscan + +Here, the first two requirements specify an exact git hash, whereas the second +two use git tags as a shorthand. + +These dependencies can be installed using:: + + $ pip install -r requirements.txt + +This is automatically done by the ``mkvenv.sh`` script. + +Virtualenv +^^^^^^^^^^ + +The script `mkvenv.sh` will automatically install a `virtualenv +` containing all the pipeline dependencies from the +``requirements.txt`` file. This gives a fresh, self-contained installation of the +pipeline to work with. Before use, you should activate it using:: + + $ source venv/bin/activate diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt new file mode 100644 index 000000000..656b62965 --- /dev/null +++ b/docs/_sources/index.rst.txt @@ -0,0 +1,36 @@ +.. draco documentation master file, created by + sphinx-quickstart on Mon Jan 4 14:16:37 2021. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + + +draco +===== + +A basic pipeline for the analysis and simulation of drift scan radio data. + +The purpose of the CHIME pipeline is to analyse timestream data, turning it into +maps and power spectra, and to do the reverse, take (synthetic) maps and turn +them into realistic simulated timestream data. This is intended as quick +tutorial for how to install, set-up and run the pipeline, as well as a reference +to the API documentation. + + +Contents: + +.. toctree:: + :maxdepth: 3 + :hidden: + + tutorial + reference + dev + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_sources/reference.rst.txt b/docs/_sources/reference.rst.txt new file mode 100644 index 000000000..62d4240ee --- /dev/null +++ b/docs/_sources/reference.rst.txt @@ -0,0 +1,4 @@ +API Reference +------------- + +.. automodule:: draco diff --git a/docs/_sources/tutorial.rst.txt b/docs/_sources/tutorial.rst.txt new file mode 100644 index 000000000..efdf81ae2 --- /dev/null +++ b/docs/_sources/tutorial.rst.txt @@ -0,0 +1,191 @@ +Tutorial +-------- + +This tutorial is going to go through the process of generating BAO constraints +from the Pathfinder data. Just kidding! We're actually just going to generate +some simulated data and the turn it into maps. + +Setting up the Pipeline +^^^^^^^^^^^^^^^^^^^^^^^ + +Before you start, make sure you have access to the CHIME bitbucket organisation, +and have set up your ssh keys for access to the bitbucket repositories from the +machine you want to run the pipeline on. Unless you are working on `Scinet`, +you'll also want to ensure you have an account on `niedermayer` and your ssh +keys are set up to allow password-less login, to ensure the database connection +can be set up. + +There are a few software pre-requesites to ensure you have installed. Obviously +python is one of them, with `numpy` and `scipy` installed, but you also need to +have `virtualenv`, allowing us to install the pipeline and it's dependencies +without messing up the base python installation. To check you have it installed +try running:: + + $ virtualenv --help + +if you get an error, it's not installed properly so you'll need to fix it. + +With that all sorted, we're ready to start. First step, download the pipeline +repository to wherever you want it installed:: + + $ git clone git@github.com/radiocosmology/draco.git + +Then change into that directory, and run the script `mkvenv.sh`:: + + $ cd draco + $ ./mkvenv.sh + +The script will do three things. First it will create a python virtual +environment to isolate the CHIME pipeline installation. Second it will fetch the +python pre-requesites for the pipeline and install them into the virtualenv. +Finally, it will install itself into the new virtualenv. Look carefully through +the messages output for errors to make sure it completed successfully. You'll +need to activate the environment whenever you want to use the pipeline. To do +that, simply do:: + + $ source /venv/bin/activate + +You can check that it's installed correctly by firing up python, and attempting +to import some of the packages. For example:: + + >>> from drift.core import telescope + >>> print telescope.__file__ + /Users/richard/code/draco/venv/src/driftscan/drift/core/telescope.pyc + >>> from draco import containers + >>> print containers.__file__ + /Users/richard/code/draco/draco/containers.pyc + + +External Products +^^^^^^^^^^^^^^^^^ + +If you are here, you've got the pipeline successfully installed. Congratulations. + +There are a few data products we'll need to run the pipeline that must be +generated externally. Fortunately installing the pipeline has already setup all +the tools we need to do this. + +We'll start with the beam transfer matrices, which describes how the sky gets +mapped into our measured visibilities. These are used both for simulating +observations given a sky map, and for making maps from visibilities (real or +simulated). To generate them we use the `driftscan` package, telling it what +exactly to generate with a `YAML` configuration file such as the one below. + +.. literalinclude:: product_params.yaml + :linenos: + :language: YAML + +This file is run with the command:: + + $ drift-makeproducts run product_params.yaml + +To simulate the timestreams we also need a sky map to base it on. The ``cora`` +package contains several different sky models we can use to produce a sky map. +The easiest method is to use the `cora-makesky` command, e.g.:: + + $ cora-makesky foreground 64 401.0 411.0 5 foreground_map.h5 + +which will generate an ``HDF5`` file containing simulated foreground maps at each +polarisation (Stokes I, Q, U and V) with five frequency channels between 401.0 +and 411.0 MHz. Each map is in Healpix format with ``NSIDE=16``. There are options +to produce 21cm signal simulations as well as point source only, and galactic +synchrotron maps. + +Map-making with the Pipeline +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The CHIME pipeline is built using the infrastructure developed by Kiyo in the +``caput.pipeline`` module. Python classes are written to perform task on the +data, and a YAML configuration file describes how these should be configured and +connected together. Below I've put the configuration file we are going to use to +make maps from simulated data: + +.. literalinclude:: pipeline_params.yaml + :linenos: + :language: YAML + +Before we jump into making the maps, let's briefly go over what this all means. +For further details you can consult the ``caput`` documentation on the pipeline. + +The bulk of this configuration file is a list of tasks being configured. There +is a ``type`` field where the class is specified by its fully qualified python +name (for example, the first task ``draco.io.LoadBeamTransfer``). To +connect one task to another, you simply specify a label for the ``output`` of +one task, and give the same label to the ``input`` or ``requires`` of the other +task. The labels themselves are dummy variables, any string will do, provided it +does not clash with the name of another label. The distinction between ``input`` +and ``requires`` is that the first is for an input which is passed every cycle +of the pipeline, and the second is for something required only at initialisation +of the task. + +Often we might want to configure a task from the YAML file itself. This is done +with the ``params`` section of each task. The named items within this section +are passed to the pipeline class when it is created. Each entry corresponds to a +``config.Property`` attribute on the class. For example the ``SimulateSidereal`` +class has parameters that can be specified:: + + class SimulateSidereal(task.SingleTask): + """Create a simulated timestream. + + Attributes + ---------- + maps : list + List of map filenames. The sum of these form the simulated sky. + ndays : float, optional + Number of days of observation. Setting `ndays = None` (default) uses + the default stored in the telescope object; `ndays = 0`, assumes the + observation time is infinite so that the noise is zero. This allows a + fractional number to account for higher noise. + seed : integer, optional + Set the random seed used for the noise simulations. Default (None) is + to choose a random seed. + """ + maps = config.Property(proptype=list) + ndays = config.Property(proptype=float, default=0.0) + seed = config.Property(proptype=int, default=None) + + ... + +In the YAML file we configured the task as follows: + +.. code-block:: YAML + + - type: draco.synthesis.stream.SimulateSidereal + requires: [tel, bt] + out: sstream + params: + maps: [ "testfg.h5" ] + save: Yes + output_root: teststream_ + +Of the three properties available from the definition of ``SimulateSidereal`` we +have only configured one of them, the list of maps to process. The remaining two +entries of the ``params`` section are inherited from the pipeline base task. +These simply tell the pipeline to save the output of the task, with a base name +given by ``output_root``. + +The pipeline is run with the `caput-pipeline` script:: + + $ caput-pipeline run pipeline_params.yaml + +What has it actually done? Let's just quickly go through the tasks in order: + +#. Load the beam transfer manager from disk. This just gives the pipeline access + to all the beam transfer matrices produced by the driftscan code. + +#. Load a map from disk, use the beam transfers to transform it into a sidereal timestream. + +#. Select the products from the timestream that are understood by the given beam + transfer manager. In this case it won't change anything, but this task can + subset frequencies and products as well as average over redundant baselines. + +#. Perform the m-mode transform on the sidereal timestream. + +#. Apply the map maker to the m-modes to produce a dirty map. + +#. Apply the map maker to the generate a Wiener filtered map. + +Ninja Techniques +^^^^^^^^^^^^^^^^ + +Running on a cluster. Coming soon.... diff --git a/docs/_static/_sphinx_javascript_frameworks_compat.js b/docs/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 000000000..81415803e --- /dev/null +++ b/docs/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/docs/_static/basic.css b/docs/_static/basic.css new file mode 100644 index 000000000..f316efcb4 --- /dev/null +++ b/docs/_static/basic.css @@ -0,0 +1,925 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_static/css/badge_only.css b/docs/_static/css/badge_only.css new file mode 100644 index 000000000..c718cee44 --- /dev/null +++ b/docs/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 000000000..6cb600001 Binary files /dev/null and b/docs/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 000000000..7059e2314 Binary files /dev/null and b/docs/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 000000000..f815f63f9 Binary files /dev/null and b/docs/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 000000000..f2c76e5bd Binary files /dev/null and b/docs/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.eot b/docs/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..e9f60ca95 Binary files /dev/null and b/docs/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.svg b/docs/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..855c845e5 --- /dev/null +++ b/docs/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/css/fonts/fontawesome-webfont.ttf b/docs/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..35acda2fa Binary files /dev/null and b/docs/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.woff b/docs/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..400014a4b Binary files /dev/null and b/docs/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/_static/css/fonts/fontawesome-webfont.woff2 b/docs/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 000000000..4d13fc604 Binary files /dev/null and b/docs/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/_static/css/fonts/lato-bold-italic.woff b/docs/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 000000000..88ad05b9f Binary files /dev/null and b/docs/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/_static/css/fonts/lato-bold-italic.woff2 b/docs/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 000000000..c4e3d804b Binary files /dev/null and b/docs/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/_static/css/fonts/lato-bold.woff b/docs/_static/css/fonts/lato-bold.woff new file mode 100644 index 000000000..c6dff51f0 Binary files /dev/null and b/docs/_static/css/fonts/lato-bold.woff differ diff --git a/docs/_static/css/fonts/lato-bold.woff2 b/docs/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 000000000..bb195043c Binary files /dev/null and b/docs/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/_static/css/fonts/lato-normal-italic.woff b/docs/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 000000000..76114bc03 Binary files /dev/null and b/docs/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/_static/css/fonts/lato-normal-italic.woff2 b/docs/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 000000000..3404f37e2 Binary files /dev/null and b/docs/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/_static/css/fonts/lato-normal.woff b/docs/_static/css/fonts/lato-normal.woff new file mode 100644 index 000000000..ae1307ff5 Binary files /dev/null and b/docs/_static/css/fonts/lato-normal.woff differ diff --git a/docs/_static/css/fonts/lato-normal.woff2 b/docs/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 000000000..3bf984332 Binary files /dev/null and b/docs/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/_static/css/theme.css b/docs/_static/css/theme.css new file mode 100644 index 000000000..19a446a0e --- /dev/null +++ b/docs/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js new file mode 100644 index 000000000..4d67807d1 --- /dev/null +++ b/docs/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js new file mode 100644 index 000000000..a04b15378 --- /dev/null +++ b/docs/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '0+untagged.1.g9cf3e8b', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/docs/_static/file.png b/docs/_static/file.png new file mode 100644 index 000000000..a858a410e Binary files /dev/null and b/docs/_static/file.png differ diff --git a/docs/_static/jquery.js b/docs/_static/jquery.js new file mode 100644 index 000000000..c4c6022f2 --- /dev/null +++ b/docs/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/_static/js/html5shiv.min.js b/docs/_static/js/html5shiv.min.js new file mode 100644 index 000000000..cd1c674f5 --- /dev/null +++ b/docs/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/_static/js/theme.js b/docs/_static/js/theme.js new file mode 100644 index 000000000..1fddb6ee4 --- /dev/null +++ b/docs/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/docs/_static/minus.png b/docs/_static/minus.png new file mode 100644 index 000000000..d96755fda Binary files /dev/null and b/docs/_static/minus.png differ diff --git a/docs/_static/plus.png b/docs/_static/plus.png new file mode 100644 index 000000000..7107cec93 Binary files /dev/null and b/docs/_static/plus.png differ diff --git a/docs/_static/pygments.css b/docs/_static/pygments.css new file mode 100644 index 000000000..84ab3030a --- /dev/null +++ b/docs/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +.highlight .no { color: #880000 } /* Name.Constant */ +.highlight .nd { color: #AA22FF } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #0000FF } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #666666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666666 } /* Literal.Number.Float */ +.highlight .mh { color: #666666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #0000FF } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_static/searchtools.js b/docs/_static/searchtools.js new file mode 100644 index 000000000..92da3f8b2 --- /dev/null +++ b/docs/_static/searchtools.js @@ -0,0 +1,619 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor) + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + "Search finished, found ${resultCount} page(s) matching the search query." + ).replace('${resultCount}', resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + for (const removalQuery of [".headerlinks", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/docs/_static/sphinx_highlight.js b/docs/_static/sphinx_highlight.js new file mode 100644 index 000000000..8a96c69a1 --- /dev/null +++ b/docs/_static/sphinx_highlight.js @@ -0,0 +1,154 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore( + span, + parent.insertBefore( + rest, + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/docs/dev.html b/docs/dev.html new file mode 100644 index 000000000..dd92c2b8f --- /dev/null +++ b/docs/dev.html @@ -0,0 +1,165 @@ + + + + + + + Development Guidelines — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Development Guidelines

+

The idea behind this repository is to keep track of the CHIME pipeline +development, such that the union of the input data and this repository always +gives the same output. This requires that we keep track of not only the code and +scripts in this repository, but also any dependencies (discussed below).

+

As far as possible the pipeline code should be using Kiyo’s pipeline task module caput.pipeline ([doc](http://bao.phas.ubc.ca/codedoc/caput/)).

+
+

Structure

+

Tasks should go into draco.

+
+
+

Branches

+

Development should be done in passX branches, or in feature branches that are +merged back in.

+
+
+

Dependencies

+

Dependencies should be installable python packages. This means that they must +have a setup.py script in their root directory, and should be installable +using python setup.py install. They are kept track of in a requirements.txt +file. This file can contain references to exact versions of dependencies, using +both version tags, and commit hashes. An example requirements.txt file is +given below:

+
-e git+https://github.com/radiocosmology/caput@ee1c55ea4cf8cb7857af2ef3adcb2439d876768d#egg=caput-master
+-e git+ssh://git@bitbucket.org/chime/ch_util.git@e33b174696509b158c15cf0bfc27f4cb2b0c6406#egg=ch_util-e
+-e git+https://github.com/radiocosmology/cora@v1.0.0#egg=cora
+-e git+https://github.com/radiocosmology/driftscan@v1.0.0#egg=driftscan
+
+
+

Here, the first two requirements specify an exact git hash, whereas the second +two use git tags as a shorthand.

+

These dependencies can be installed using:

+
$ pip install -r requirements.txt
+
+
+

This is automatically done by the mkvenv.sh script.

+
+
+

Virtualenv

+

The script mkvenv.sh will automatically install a virtualenv +<http://www.virtualenv.org/> containing all the pipeline dependencies from the +requirements.txt file. This gives a fresh, self-contained installation of the +pipeline to work with. Before use, you should activate it using:

+
$ source venv/bin/activate
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/genindex.html b/docs/genindex.html new file mode 100644 index 000000000..1a0b6ea09 --- /dev/null +++ b/docs/genindex.html @@ -0,0 +1,2039 @@ + + + + + + Index — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | H + | I + | K + | L + | M + | N + | O + | P + | Q + | R + | S + | T + | U + | V + | W + | Z + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
    +
  • + draco.analysis.fgfilter + +
  • +
  • + draco.analysis.flagging + +
  • +
  • + draco.analysis.mapmaker + +
  • +
  • + draco.analysis.powerspectrum + +
  • +
  • + draco.analysis.sensitivity + +
  • +
  • + draco.analysis.sidereal + +
  • +
  • + draco.analysis.sourcestack + +
  • +
  • + draco.analysis.svdfilter + +
  • +
  • + draco.analysis.transform + +
  • +
  • + draco.core + +
  • +
  • + draco.core.containers + +
  • +
  • + draco.core.io + +
  • +
  • + draco.core.misc + +
  • +
  • + draco.core.task + +
  • +
  • + draco.synthesis + +
  • +
  • + draco.synthesis.gain + +
  • +
  • + draco.synthesis.noise + +
  • +
  • + draco.synthesis.stream + +
  • +
  • + draco.util + +
  • +
  • + draco.util.exception + +
  • +
  • + draco.util.random + +
  • +
  • + draco.util.regrid + +
  • +
  • + draco.util.rfi + +
  • +
  • + draco.util.testing + +
  • +
  • + draco.util.tools + +
  • +
  • + draco.util.truncate + +
  • +
  • DummyTask (class in draco.util.testing) +
  • +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

K

+ + + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

Q

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ +

Z

+ + + +
+ + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..453690bb9 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,131 @@ + + + + + + + draco — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

draco

+

A basic pipeline for the analysis and simulation of drift scan radio data.

+

The purpose of the CHIME pipeline is to analyse timestream data, turning it into +maps and power spectra, and to do the reverse, take (synthetic) maps and turn +them into realistic simulated timestream data. This is intended as quick +tutorial for how to install, set-up and run the pipeline, as well as a reference +to the API documentation.

+

Contents:

+
+
+
+
+

Indices and tables

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/objects.inv b/docs/objects.inv new file mode 100644 index 000000000..b82e66be4 Binary files /dev/null and b/docs/objects.inv differ diff --git a/docs/py-modindex.html b/docs/py-modindex.html new file mode 100644 index 000000000..bddc0a2c8 --- /dev/null +++ b/docs/py-modindex.html @@ -0,0 +1,271 @@ + + + + + + Python Module Index — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ d +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ d
+ draco +
    + draco.analysis +
    + draco.analysis.beamform +
    + draco.analysis.delay +
    + draco.analysis.fgfilter +
    + draco.analysis.flagging +
    + draco.analysis.mapmaker +
    + draco.analysis.powerspectrum +
    + draco.analysis.sensitivity +
    + draco.analysis.sidereal +
    + draco.analysis.sourcestack +
    + draco.analysis.svdfilter +
    + draco.analysis.transform +
    + draco.core +
    + draco.core.containers +
    + draco.core.io +
    + draco.core.misc +
    + draco.core.task +
    + draco.synthesis +
    + draco.synthesis.gain +
    + draco.synthesis.noise +
    + draco.synthesis.stream +
    + draco.util +
    + draco.util.exception +
    + draco.util.random +
    + draco.util.regrid +
    + draco.util.rfi +
    + draco.util.testing +
    + draco.util.tools +
    + draco.util.truncate +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/reference.html b/docs/reference.html new file mode 100644 index 000000000..a1201f5f6 --- /dev/null +++ b/docs/reference.html @@ -0,0 +1,145 @@ + + + + + + + API Reference — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

API Reference

+

draco.

+
+

Submodules

+ + + + + + + + + + + + + + + +

analysis

core

synthesis

util

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/search.html b/docs/search.html new file mode 100644 index 000000000..358948069 --- /dev/null +++ b/docs/search.html @@ -0,0 +1,126 @@ + + + + + + Search — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+ +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/searchindex.js b/docs/searchindex.js new file mode 100644 index 000000000..7766d13ce --- /dev/null +++ b/docs/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"API Reference": [[31, "module-draco"]], "Branches": [[29, "branches"]], "Container Base Classes": [[13, "container-base-classes"]], "Containers": [[13, "containers"]], "Dependencies": [[29, "dependencies"]], "Development Guidelines": [[29, "development-guidelines"]], "External Products": [[32, "external-products"]], "File Groups": [[14, "file-groups"]], "Helper Routines": [[13, "helper-routines"]], "Indices and tables": [[30, "indices-and-tables"]], "Map-making with the Pipeline": [[32, "map-making-with-the-pipeline"]], "Ninja Techniques": [[32, "ninja-techniques"]], "Setting up the Pipeline": [[32, "setting-up-the-pipeline"]], "Structure": [[29, "structure"]], "Submodules": [[31, "submodules"]], "Tutorial": [[32, "tutorial"]], "Usage": [[8, "usage"]], "Virtualenv": [[29, "virtualenv"]], "draco": [[30, "draco"]], "draco.analysis": [[0, "module-draco.analysis"]], "draco.analysis.beamform": [[1, "module-draco.analysis.beamform"]], "draco.analysis.delay": [[2, "module-draco.analysis.delay"]], "draco.analysis.fgfilter": [[3, "module-draco.analysis.fgfilter"]], "draco.analysis.flagging": [[4, "module-draco.analysis.flagging"]], "draco.analysis.mapmaker": [[5, "module-draco.analysis.mapmaker"]], "draco.analysis.powerspectrum": [[6, "module-draco.analysis.powerspectrum"]], "draco.analysis.sensitivity": [[7, "module-draco.analysis.sensitivity"]], "draco.analysis.sidereal": [[8, "module-draco.analysis.sidereal"]], "draco.analysis.sourcestack": [[9, "module-draco.analysis.sourcestack"]], "draco.analysis.svdfilter": [[10, "module-draco.analysis.svdfilter"]], "draco.analysis.transform": [[11, "module-draco.analysis.transform"]], "draco.core": [[12, "module-draco.core"]], "draco.core.containers": [[13, "module-draco.core.containers"]], "draco.core.io": [[14, "module-draco.core.io"]], "draco.core.misc": [[15, "module-draco.core.misc"]], "draco.core.task": [[16, "module-draco.core.task"]], "draco.synthesis": [[17, "module-draco.synthesis"]], "draco.synthesis.gain": [[18, "module-draco.synthesis.gain"]], "draco.synthesis.noise": [[19, "module-draco.synthesis.noise"]], "draco.synthesis.stream": [[20, "module-draco.synthesis.stream"]], "draco.util": [[21, "module-draco.util"]], "draco.util.exception": [[22, "module-draco.util.exception"]], "draco.util.random": [[23, "module-draco.util.random"]], "draco.util.regrid": [[24, "module-draco.util.regrid"]], "draco.util.rfi": [[25, "module-draco.util.rfi"]], "draco.util.testing": [[26, "module-draco.util.testing"]], "draco.util.tools": [[27, "module-draco.util.tools"]], "draco.util.truncate": [[28, "module-draco.util.truncate"]]}, "docnames": ["_autosummary/draco.analysis", "_autosummary/draco.analysis.beamform", "_autosummary/draco.analysis.delay", "_autosummary/draco.analysis.fgfilter", "_autosummary/draco.analysis.flagging", "_autosummary/draco.analysis.mapmaker", "_autosummary/draco.analysis.powerspectrum", "_autosummary/draco.analysis.sensitivity", "_autosummary/draco.analysis.sidereal", "_autosummary/draco.analysis.sourcestack", "_autosummary/draco.analysis.svdfilter", "_autosummary/draco.analysis.transform", "_autosummary/draco.core", "_autosummary/draco.core.containers", "_autosummary/draco.core.io", "_autosummary/draco.core.misc", "_autosummary/draco.core.task", "_autosummary/draco.synthesis", "_autosummary/draco.synthesis.gain", "_autosummary/draco.synthesis.noise", "_autosummary/draco.synthesis.stream", "_autosummary/draco.util", "_autosummary/draco.util.exception", "_autosummary/draco.util.random", "_autosummary/draco.util.regrid", "_autosummary/draco.util.rfi", "_autosummary/draco.util.testing", "_autosummary/draco.util.tools", "_autosummary/draco.util.truncate", "dev", "index", "reference", "tutorial"], "envversion": {"sphinx": 61, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1}, "filenames": ["_autosummary/draco.analysis.rst", "_autosummary/draco.analysis.beamform.rst", "_autosummary/draco.analysis.delay.rst", "_autosummary/draco.analysis.fgfilter.rst", "_autosummary/draco.analysis.flagging.rst", "_autosummary/draco.analysis.mapmaker.rst", "_autosummary/draco.analysis.powerspectrum.rst", "_autosummary/draco.analysis.sensitivity.rst", "_autosummary/draco.analysis.sidereal.rst", "_autosummary/draco.analysis.sourcestack.rst", "_autosummary/draco.analysis.svdfilter.rst", "_autosummary/draco.analysis.transform.rst", "_autosummary/draco.core.rst", "_autosummary/draco.core.containers.rst", "_autosummary/draco.core.io.rst", "_autosummary/draco.core.misc.rst", "_autosummary/draco.core.task.rst", "_autosummary/draco.synthesis.rst", "_autosummary/draco.synthesis.gain.rst", "_autosummary/draco.synthesis.noise.rst", "_autosummary/draco.synthesis.stream.rst", "_autosummary/draco.util.rst", "_autosummary/draco.util.exception.rst", "_autosummary/draco.util.random.rst", "_autosummary/draco.util.regrid.rst", "_autosummary/draco.util.rfi.rst", "_autosummary/draco.util.testing.rst", "_autosummary/draco.util.tools.rst", "_autosummary/draco.util.truncate.rst", "dev.rst", "index.rst", "reference.rst", "tutorial.rst"], "indexentries": {"accumulatelist (class in draco.core.misc)": [[15, "draco.core.misc.AccumulateList", false]], "add_dataset() (draco.core.containers.containerbase method)": [[13, "draco.core.containers.ContainerBase.add_dataset", false]], "add_noise (draco.synthesis.noise.gaussiannoise attribute)": [[19, "draco.synthesis.noise.GaussianNoise.add_noise", false]], "all_time (draco.analysis.flagging.maskfreq attribute)": [[4, "draco.analysis.flagging.MaskFreq.all_time", false]], "amp (draco.synthesis.gain.basegains attribute)": [[18, "draco.synthesis.gain.BaseGains.amp", false]], "apply_gain() (in module draco.util.tools)": [[27, "draco.util.tools.apply_gain", false]], "apply_integration_window (draco.analysis.transform.mmodeinversetransform attribute)": [[11, "draco.analysis.transform.MModeInverseTransform.apply_integration_window", false]], "apply_window (draco.analysis.delay.delaytransformbase attribute)": [[2, "draco.analysis.delay.DelayTransformBase.apply_window", false]], "applybaselinemask (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.ApplyBaselineMask", false]], "applygain (class in draco.core.misc)": [[15, "draco.core.misc.ApplyGain", false]], "applyrfimask (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.ApplyRFIMask", false]], "applytimefreqmask (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.ApplyTimeFreqMask", false]], "attrs (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.attrs", false]], "auto_correlations (draco.analysis.flagging.maskmmodedata attribute)": [[4, "draco.analysis.flagging.MaskMModeData.auto_correlations", false]], "average_axis (draco.analysis.delay.delaygeneralcontainerbase attribute)": [[2, "draco.analysis.delay.DelayGeneralContainerBase.average_axis", false]], "axes (draco.analysis.transform.reducebase attribute)": [[11, "draco.analysis.transform.ReduceBase.axes", false]], "axes (draco.core.containers.containerbase property)": [[13, "draco.core.containers.ContainerBase.axes", false]], "axis (draco.analysis.delay.delayfilterbase attribute)": [[2, "draco.analysis.delay.DelayFilterBase.axis", false]], "bad_freq_ind (draco.analysis.flagging.maskfreq attribute)": [[4, "draco.analysis.flagging.MaskFreq.bad_freq_ind", false]], "band_wiener() (in module draco.util.regrid)": [[24, "draco.util.regrid.band_wiener", false]], "basegains (class in draco.synthesis.gain)": [[18, "draco.synthesis.gain.BaseGains", false]], "baseline_vector() (in module draco.util.tools)": [[27, "draco.util.tools.baseline_vector", false]], "baselinemask (class in draco.core.containers)": [[13, "draco.core.containers.BaselineMask", false]], "baseloadfiles (class in draco.core.io)": [[14, "draco.core.io.BaseLoadFiles", false]], "basemapmaker (class in draco.analysis.mapmaker)": [[5, "draco.analysis.mapmaker.BaseMapMaker", false]], "beam (draco.core.containers.formedbeam property)": [[13, "draco.core.containers.FormedBeam.beam", false]], "beam (draco.core.containers.gridbeam property)": [[13, "draco.core.containers.GridBeam.beam", false]], "beam (draco.core.containers.healpixbeam property)": [[13, "draco.core.containers.HEALPixBeam.beam", false]], "beam (draco.core.containers.trackbeam property)": [[13, "draco.core.containers.TrackBeam.beam", false]], "beamform (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.BeamForm", false]], "beamformbase (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.BeamFormBase", false]], "beamformcat (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.BeamFormCat", false]], "beamformexternal (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.BeamFormExternal", false]], "beamformexternalbase (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.BeamFormExternalBase", false]], "beamformexternalcat (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.BeamFormExternalCat", false]], "blendstack (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.BlendStack", false]], "c_inv (draco.core.containers.powerspectrum2d property)": [[13, "draco.core.containers.Powerspectrum2D.C_inv", false]], "calculate_redundancy() (in module draco.util.tools)": [[27, "draco.util.tools.calculate_redundancy", false]], "catalogs (draco.core.io.loadfitscatalog attribute)": [[14, "draco.core.io.LoadFITSCatalog.catalogs", false]], "channel_index (draco.analysis.transform.selectfreq attribute)": [[11, "draco.analysis.transform.SelectFreq.channel_index", false]], "channel_range (draco.analysis.transform.selectfreq attribute)": [[11, "draco.analysis.transform.SelectFreq.channel_range", false]], "checkmpienvironment (class in draco.core.misc)": [[15, "draco.core.misc.CheckMPIEnvironment", false]], "cmap() (in module draco.util.tools)": [[27, "draco.util.tools.cmap", false]], "collapse_ha (draco.analysis.beamform.beamformbase attribute)": [[1, "draco.analysis.beamform.BeamFormBase.collapse_ha", false]], "collapsebaselinemask (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.CollapseBaselineMask", false]], "collateproducts (class in draco.analysis.transform)": [[11, "draco.analysis.transform.CollateProducts", false]], "commonmodegaindata (class in draco.core.containers)": [[13, "draco.core.containers.CommonModeGainData", false]], "commonmodesiderealgaindata (class in draco.core.containers)": [[13, "draco.core.containers.CommonModeSiderealGainData", false]], "complex_med() (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.complex_med", false]], "complex_normal() (in module draco.util.random)": [[23, "draco.util.random.complex_normal", false]], "complex_timedomain (draco.analysis.delay.delaytransformbase attribute)": [[2, "draco.analysis.delay.DelayTransformBase.complex_timedomain", false]], "complex_wishart() (in module draco.util.random)": [[23, "draco.util.random.complex_wishart", false]], "component (draco.core.containers.samplevariancecontainer property)": [[13, "draco.core.containers.SampleVarianceContainer.component", false]], "compression (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.compression", false]], "computesystemsensitivity (class in draco.analysis.sensitivity)": [[7, "draco.analysis.sensitivity.ComputeSystemSensitivity", false]], "configerror": [[22, "draco.util.exception.ConfigError", false]], "constrained_gaussian_realisation() (in module draco.synthesis.gain)": [[18, "draco.synthesis.gain.constrained_gaussian_realisation", false]], "containerbase (class in draco.core.containers)": [[13, "draco.core.containers.ContainerBase", false]], "containers (draco.core.io.zipzarrcontainers attribute)": [[14, "draco.core.io.ZipZarrContainers.containers", false]], "convert_jy_to_k (draco.analysis.transform.transformjanskytokelvin attribute)": [[11, "draco.analysis.transform.TransformJanskyToKelvin.convert_Jy_to_K", false]], "convert_strings (draco.core.io.baseloadfiles attribute)": [[14, "draco.core.io.BaseLoadFiles.convert_strings", false]], "coords (draco.core.containers.gridbeam property)": [[13, "draco.core.containers.GridBeam.coords", false]], "coords (draco.core.containers.healpixbeam property)": [[13, "draco.core.containers.HEALPixBeam.coords", false]], "coords (draco.core.containers.trackbeam property)": [[13, "draco.core.containers.TrackBeam.coords", false]], "copy() (draco.core.containers.containerbase method)": [[13, "draco.core.containers.ContainerBase.copy", false]], "copy_datasets_filter() (in module draco.core.containers)": [[13, "draco.core.containers.copy_datasets_filter", false]], "cutoff (draco.core.containers.delaycutoff property)": [[13, "draco.core.containers.DelayCutoff.cutoff", false]], "data (draco.core.containers.dataweightcontainer property)": [[13, "draco.core.containers.DataWeightContainer.data", false]], "data_coeff (draco.analysis.transform.mixdata attribute)": [[11, "draco.analysis.transform.MixData.data_coeff", false]], "dataset (draco.analysis.delay.delayfilterbase attribute)": [[2, "draco.analysis.delay.DelayFilterBase.dataset", false]], "dataset (draco.analysis.delay.delaygeneralcontainerbase attribute)": [[2, "draco.analysis.delay.DelayGeneralContainerBase.dataset", false]], "dataset (draco.analysis.transform.reducebase attribute)": [[11, "draco.analysis.transform.ReduceBase.dataset", false]], "dataset (draco.core.io.truncate attribute)": [[14, "draco.core.io.Truncate.dataset", false]], "dataset (draco.synthesis.noise.gaussiannoisedataset attribute)": [[19, "draco.synthesis.noise.GaussianNoiseDataset.dataset", false]], "dataset_spec (draco.core.containers.containerbase property)": [[13, "draco.core.containers.ContainerBase.dataset_spec", false]], "datasets (draco.core.containers.containerbase property)": [[13, "draco.core.containers.ContainerBase.datasets", false]], "dataweightcontainer (class in draco.core.containers)": [[13, "draco.core.containers.DataWeightContainer", false]], "daymask (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.DayMask", false]], "default_rng() (in module draco.util.random)": [[23, "draco.util.random.default_rng", false]], "delay (draco.core.containers.delaycontainer property)": [[13, "draco.core.containers.DelayContainer.delay", false]], "delay_cut (draco.analysis.delay.delayfilter attribute)": [[2, "draco.analysis.delay.DelayFilter.delay_cut", false]], "delay_cut (draco.analysis.delay.delayfilterbase attribute)": [[2, "draco.analysis.delay.DelayFilterBase.delay_cut", false]], "delay_cut (draco.util.testing.randomfreqdata attribute)": [[26, "draco.util.testing.RandomFreqData.delay_cut", false]], "delay_power_spectrum_gibbs() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.delay_power_spectrum_gibbs", false]], "delay_spectrum_gibbs() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.delay_spectrum_gibbs", false]], "delay_spectrum_gibbs_cross() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.delay_spectrum_gibbs_cross", false]], "delay_spectrum_wiener_filter() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.delay_spectrum_wiener_filter", false]], "delaycontainer (class in draco.core.containers)": [[13, "draco.core.containers.DelayContainer", false]], "delaycrosspowerspectrumestimator (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelayCrossPowerSpectrumEstimator", false]], "delaycrossspectrum (class in draco.core.containers)": [[13, "draco.core.containers.DelayCrossSpectrum", false]], "delaycutoff (class in draco.core.containers)": [[13, "draco.core.containers.DelayCutoff", false]], "delayfilter (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelayFilter", false]], "delayfilterbase (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelayFilterBase", false]], "delaygeneralcontainerbase (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelayGeneralContainerBase", false]], "delaygibbssamplerbase (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelayGibbsSamplerBase", false]], "delaypowerspectrumgeneralestimator (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelayPowerSpectrumGeneralEstimator", false]], "delaypowerspectrumstokesiestimator (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelayPowerSpectrumStokesIEstimator", false]], "delayspectrum (class in draco.core.containers)": [[13, "draco.core.containers.DelaySpectrum", false]], "delayspectrumestimator (in module draco.analysis.delay)": [[2, "draco.analysis.delay.DelaySpectrumEstimator", false]], "delayspectrumestimatorbase (in module draco.analysis.delay)": [[2, "draco.analysis.delay.DelaySpectrumEstimatorBase", false]], "delayspectrumwienerbase (in module draco.analysis.delay)": [[2, "draco.analysis.delay.DelaySpectrumWienerBase", false]], "delayspectrumwienerestimator (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelaySpectrumWienerEstimator", false]], "delaytransform (class in draco.core.containers)": [[13, "draco.core.containers.DelayTransform", false]], "delaytransformbase (class in draco.analysis.delay)": [[2, "draco.analysis.delay.DelayTransformBase", false]], "delete (class in draco.core.task)": [[16, "draco.core.task.Delete", false]], "destripe() (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.destripe", false]], "dirty_beam (draco.core.containers.hybridvisstream property)": [[13, "draco.core.containers.HybridVisStream.dirty_beam", false]], "dirty_beam (draco.core.containers.ringmap property)": [[13, "draco.core.containers.RingMap.dirty_beam", false]], "dirtymapmaker (class in draco.analysis.mapmaker)": [[5, "draco.analysis.mapmaker.DirtyMapMaker", false]], "distributed (draco.core.io.baseloadfiles attribute)": [[14, "draco.core.io.BaseLoadFiles.distributed", false]], "down_mix (draco.analysis.sidereal.siderealregridder attribute)": [[8, "draco.analysis.sidereal.SiderealRegridder.down_mix", false]], "downselect (class in draco.analysis.transform)": [[11, "draco.analysis.transform.Downselect", false]], "draco": [[31, "module-draco", false]], "draco.analysis": [[0, "module-draco.analysis", false]], "draco.analysis.beamform": [[1, "module-draco.analysis.beamform", false]], "draco.analysis.delay": [[2, "module-draco.analysis.delay", false]], "draco.analysis.fgfilter": [[3, "module-draco.analysis.fgfilter", false]], "draco.analysis.flagging": [[4, "module-draco.analysis.flagging", false]], "draco.analysis.mapmaker": [[5, "module-draco.analysis.mapmaker", false]], "draco.analysis.powerspectrum": [[6, "module-draco.analysis.powerspectrum", false]], "draco.analysis.sensitivity": [[7, "module-draco.analysis.sensitivity", false]], "draco.analysis.sidereal": [[8, "module-draco.analysis.sidereal", false]], "draco.analysis.sourcestack": [[9, "module-draco.analysis.sourcestack", false]], "draco.analysis.svdfilter": [[10, "module-draco.analysis.svdfilter", false]], "draco.analysis.transform": [[11, "module-draco.analysis.transform", false]], "draco.core": [[12, "module-draco.core", false]], "draco.core.containers": [[13, "module-draco.core.containers", false]], "draco.core.io": [[14, "module-draco.core.io", false]], "draco.core.misc": [[15, "module-draco.core.misc", false]], "draco.core.task": [[16, "module-draco.core.task", false]], "draco.synthesis": [[17, "module-draco.synthesis", false]], "draco.synthesis.gain": [[18, "module-draco.synthesis.gain", false]], "draco.synthesis.noise": [[19, "module-draco.synthesis.noise", false]], "draco.synthesis.stream": [[20, "module-draco.synthesis.stream", false]], "draco.util": [[21, "module-draco.util", false]], "draco.util.exception": [[22, "module-draco.util.exception", false]], "draco.util.random": [[23, "module-draco.util.random", false]], "draco.util.regrid": [[24, "module-draco.util.regrid", false]], "draco.util.rfi": [[25, "module-draco.util.rfi", false]], "draco.util.testing": [[26, "module-draco.util.testing", false]], "draco.util.tools": [[27, "module-draco.util.tools", false]], "draco.util.truncate": [[28, "module-draco.util.truncate", false]], "dummytask (class in draco.util.testing)": [[26, "draco.util.testing.DummyTask", false]], "el (draco.core.containers.delaycutoff property)": [[13, "draco.core.containers.DelayCutoff.el", false]], "el (draco.core.containers.ringmap property)": [[13, "draco.core.containers.RingMap.el", false]], "empty_like() (in module draco.core.containers)": [[13, "draco.core.containers.empty_like", false]], "empty_timestream() (in module draco.core.containers)": [[13, "draco.core.containers.empty_timestream", false]], "end (draco.analysis.transform.regridder attribute)": [[11, "draco.analysis.transform.Regridder.end", false]], "ensure_chunked (draco.core.io.truncate attribute)": [[14, "draco.core.io.Truncate.ensure_chunked", false]], "expandproducts (class in draco.synthesis.stream)": [[20, "draco.synthesis.stream.ExpandProducts", false]], "extra_cut (draco.analysis.delay.delayfilter attribute)": [[2, "draco.analysis.delay.DelayFilter.extra_cut", false]], "extract_diagonal() (in module draco.util.tools)": [[27, "draco.util.tools.extract_diagonal", false]], "factorize (draco.analysis.flagging.maskfreq attribute)": [[4, "draco.analysis.flagging.MaskFreq.factorize", false]], "files (draco.core.io.loadfilesfromparams attribute)": [[14, "draco.core.io.LoadFilesFromParams.files", false]], "filter() (draco.core.task.mpilogfilter method)": [[16, "draco.core.task.MPILogFilter.filter", false]], "find_inputs() (in module draco.util.tools)": [[27, "draco.util.tools.find_inputs", false]], "find_key() (in module draco.util.tools)": [[27, "draco.util.tools.find_key", false]], "find_keys() (in module draco.util.tools)": [[27, "draco.util.tools.find_keys", false]], "findbeamformedoutliers (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.FindBeamformedOutliers", false]], "findfiles (class in draco.core.io)": [[14, "draco.core.io.FindFiles", false]], "finish() (draco.core.io.waitzarrzip method)": [[14, "draco.core.io.WaitZarrZip.finish", false]], "finish() (draco.core.misc.accumulatelist method)": [[15, "draco.core.misc.AccumulateList.finish", false]], "finish() (draco.core.task.singletask method)": [[16, "draco.core.task.SingleTask.finish", false]], "flatten_axes() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.flatten_axes", false]], "formedbeam (class in draco.core.containers)": [[13, "draco.core.containers.FormedBeam", false]], "formedbeamha (class in draco.core.containers)": [[13, "draco.core.containers.FormedBeamHA", false]], "formedbeamhamask (class in draco.core.containers)": [[13, "draco.core.containers.FormedBeamHAMask", false]], "formedbeammask (class in draco.core.containers)": [[13, "draco.core.containers.FormedBeamMask", false]], "fourier_matrix() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.fourier_matrix", false]], "fourier_matrix_c2c() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.fourier_matrix_c2c", false]], "fourier_matrix_c2r() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.fourier_matrix_c2r", false]], "fourier_matrix_r2c() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.fourier_matrix_r2c", false]], "frac (draco.analysis.flagging.blendstack attribute)": [[4, "draco.analysis.flagging.BlendStack.frac", false]], "frac_lost (draco.core.containers.systemsensitivity property)": [[13, "draco.core.containers.SystemSensitivity.frac_lost", false]], "freq (draco.core.containers.delayspectrum property)": [[13, "draco.core.containers.DelaySpectrum.freq", false]], "freq (draco.core.containers.delaytransform property)": [[13, "draco.core.containers.DelayTransform.freq", false]], "freq (draco.core.containers.freqcontainer property)": [[13, "draco.core.containers.FreqContainer.freq", false]], "freq_physical (draco.analysis.transform.selectfreq attribute)": [[11, "draco.analysis.transform.SelectFreq.freq_physical", false]], "freq_physical_range (draco.analysis.transform.selectfreq attribute)": [[11, "draco.analysis.transform.SelectFreq.freq_physical_range", false]], "freq_range (draco.core.io.loadfitscatalog attribute)": [[14, "draco.core.io.LoadFITSCatalog.freq_range", false]], "freq_spacing (draco.analysis.delay.delaytransformbase attribute)": [[2, "draco.analysis.delay.DelayTransformBase.freq_spacing", false]], "freq_zero (draco.analysis.delay.delaytransformbase attribute)": [[2, "draco.analysis.delay.DelayTransformBase.freq_zero", false]], "freqcontainer (class in draco.core.containers)": [[13, "draco.core.containers.FreqContainer", false]], "freqside (draco.analysis.beamform.beamformbase attribute)": [[1, "draco.analysis.beamform.BeamFormBase.freqside", false]], "freqside (draco.analysis.sourcestack.sourcestack attribute)": [[9, "draco.analysis.sourcestack.SourceStack.freqside", false]], "frequency (draco.core.containers.formedbeam property)": [[13, "draco.core.containers.FormedBeam.frequency", false]], "frequencyrebin (class in draco.analysis.transform)": [[11, "draco.analysis.transform.FrequencyRebin", false]], "frequencystack (class in draco.core.containers)": [[13, "draco.core.containers.FrequencyStack", false]], "frequencystackbypol (class in draco.core.containers)": [[13, "draco.core.containers.FrequencyStackByPol", false]], "fwhm (draco.analysis.beamform.healpixbeamform attribute)": [[1, "draco.analysis.beamform.HealpixBeamForm.fwhm", false]], "gain (draco.core.containers.gaindatabase property)": [[13, "draco.core.containers.GainDataBase.gain", false]], "gain (draco.core.containers.gridbeam property)": [[13, "draco.core.containers.GridBeam.gain", false]], "gain (draco.core.containers.siderealstream property)": [[13, "draco.core.containers.SiderealStream.gain", false]], "gain (draco.core.containers.timestream property)": [[13, "draco.core.containers.TimeStream.gain", false]], "gain (draco.core.containers.trackbeam property)": [[13, "draco.core.containers.TrackBeam.gain", false]], "gaindata (class in draco.core.containers)": [[13, "draco.core.containers.GainData", false]], "gaindatabase (class in draco.core.containers)": [[13, "draco.core.containers.GainDataBase", false]], "gainstacker (class in draco.synthesis.gain)": [[18, "draco.synthesis.gain.GainStacker", false]], "gaussian_realisation() (in module draco.synthesis.gain)": [[18, "draco.synthesis.gain.gaussian_realisation", false]], "gaussiannoise (class in draco.synthesis.noise)": [[19, "draco.synthesis.noise.GaussianNoise", false]], "gaussiannoisedataset (class in draco.synthesis.noise)": [[19, "draco.synthesis.noise.GaussianNoiseDataset", false]], "generate_fluctuations() (in module draco.synthesis.gain)": [[18, "draco.synthesis.gain.generate_fluctuations", false]], "get_beamtransfer() (in module draco.core.io)": [[14, "draco.core.io.get_beamtransfer", false]], "get_telescope() (in module draco.core.io)": [[14, "draco.core.io.get_telescope", false]], "global_threshold (draco.analysis.svdfilter.svdfilter attribute)": [[10, "draco.analysis.svdfilter.SVDFilter.global_threshold", false]], "gridbeam (class in draco.core.containers)": [[13, "draco.core.containers.GridBeam", false]], "group_tasks() (in module draco.core.task)": [[16, "draco.core.task.group_tasks", false]], "groupsourcestacks (class in draco.analysis.sourcestack)": [[9, "draco.analysis.sourcestack.GroupSourceStacks", false]], "ha (draco.core.containers.formedbeamha property)": [[13, "draco.core.containers.FormedBeamHA.ha", false]], "healpixbeam (class in draco.core.containers)": [[13, "draco.core.containers.HEALPixBeam", false]], "healpixbeamform (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.HealpixBeamForm", false]], "healpixcontainer (class in draco.core.containers)": [[13, "draco.core.containers.HealpixContainer", false]], "hpftimestream (class in draco.analysis.transform)": [[11, "draco.analysis.transform.HPFTimeStream", false]], "hybridvismmodes (class in draco.core.containers)": [[13, "draco.core.containers.HybridVisMModes", false]], "hybridvisstream (class in draco.core.containers)": [[13, "draco.core.containers.HybridVisStream", false]], "icmap() (in module draco.util.tools)": [[27, "draco.util.tools.icmap", false]], "icrs_to_cirs() (in module draco.analysis.beamform)": [[1, "draco.analysis.beamform.icrs_to_cirs", false]], "id (draco.core.containers.formedbeam property)": [[13, "draco.core.containers.FormedBeam.id", false]], "include_pol (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.include_pol", false]], "initial_amplitude (draco.analysis.delay.delaygibbssamplerbase attribute)": [[2, "draco.analysis.delay.DelayGibbsSamplerBase.initial_amplitude", false]], "initial_sample_path (draco.analysis.delay.delaygibbssamplerbase attribute)": [[2, "draco.analysis.delay.DelayGibbsSamplerBase.initial_sample_path", false]], "input (draco.core.containers.gaindata property)": [[13, "draco.core.containers.GainData.input", false]], "input (draco.core.containers.gridbeam property)": [[13, "draco.core.containers.GridBeam.input", false]], "input (draco.core.containers.healpixbeam property)": [[13, "draco.core.containers.HEALPixBeam.input", false]], "input (draco.core.containers.siderealgaindata property)": [[13, "draco.core.containers.SiderealGainData.input", false]], "input (draco.core.containers.staticgaindata property)": [[13, "draco.core.containers.StaticGainData.input", false]], "input (draco.core.containers.trackbeam property)": [[13, "draco.core.containers.TrackBeam.input", false]], "input (draco.core.containers.viscontainer property)": [[13, "draco.core.containers.VisContainer.input", false]], "input_flags (draco.core.containers.siderealstream property)": [[13, "draco.core.containers.SiderealStream.input_flags", false]], "input_flags (draco.core.containers.timestream property)": [[13, "draco.core.containers.TimeStream.input_flags", false]], "inverse (draco.core.misc.applygain attribute)": [[15, "draco.core.misc.ApplyGain.inverse", false]], "inverse_binom_cdf_prob() (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.inverse_binom_cdf_prob", false]], "is_stacked (draco.core.containers.viscontainer property)": [[13, "draco.core.containers.VisContainer.is_stacked", false]], "kernel_size (draco.analysis.flagging.smoothvisweight attribute)": [[4, "draco.analysis.flagging.SmoothVisWeight.kernel_size", false]], "klmodeproject (class in draco.analysis.fgfilter)": [[3, "draco.analysis.fgfilter.KLModeProject", false]], "klmodes (class in draco.core.containers)": [[13, "draco.core.containers.KLModes", false]], "klname (draco.analysis.fgfilter.klmodeproject attribute)": [[3, "draco.analysis.fgfilter.KLModeProject.klname", false]], "lanczos_forward_matrix() (in module draco.util.regrid)": [[24, "draco.util.regrid.lanczos_forward_matrix", false]], "lanczos_inverse_matrix() (in module draco.util.regrid)": [[24, "draco.util.regrid.lanczos_inverse_matrix", false]], "lanczos_kernel() (in module draco.util.regrid)": [[24, "draco.util.regrid.lanczos_kernel", false]], "lanczos_width (draco.analysis.sidereal.siderealregridder attribute)": [[8, "draco.analysis.sidereal.SiderealRegridder.lanczos_width", false]], "lanczos_width (draco.analysis.transform.regridder attribute)": [[11, "draco.analysis.transform.Regridder.lanczos_width", false]], "loadbasiccont (in module draco.core.io)": [[14, "draco.core.io.LoadBasicCont", false]], "loadbeamtransfer (class in draco.core.io)": [[14, "draco.core.io.LoadBeamTransfer", false]], "loadfiles (class in draco.core.io)": [[14, "draco.core.io.LoadFiles", false]], "loadfilesfromparams (class in draco.core.io)": [[14, "draco.core.io.LoadFilesFromParams", false]], "loadfitscatalog (class in draco.core.io)": [[14, "draco.core.io.LoadFITSCatalog", false]], "loadmaps (class in draco.core.io)": [[14, "draco.core.io.LoadMaps", false]], "loadproductmanager (class in draco.core.io)": [[14, "draco.core.io.LoadProductManager", false]], "local_seed (draco.util.random.randomtask property)": [[23, "draco.util.random.RandomTask.local_seed", false]], "local_threshold (draco.analysis.svdfilter.svdfilter attribute)": [[10, "draco.analysis.svdfilter.SVDFilter.local_threshold", false]], "log (draco.core.task.loggedtask property)": [[16, "draco.core.task.LoggedTask.log", false]], "loggedtask (class in draco.core.task)": [[16, "draco.core.task.LoggedTask", false]], "m_zero (draco.analysis.flagging.maskmmodedata attribute)": [[4, "draco.analysis.flagging.MaskMModeData.m_zero", false]], "mad() (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.mad", false]], "makecopy (class in draco.core.misc)": [[15, "draco.core.misc.MakeCopy", false]], "makesiderealdaystream (class in draco.synthesis.stream)": [[20, "draco.synthesis.stream.MakeSiderealDayStream", false]], "maketimestream (class in draco.synthesis.stream)": [[20, "draco.synthesis.stream.MakeTimeStream", false]], "map (class in draco.core.containers)": [[13, "draco.core.containers.Map", false]], "map (draco.core.containers.map property)": [[13, "draco.core.containers.Map.map", false]], "map (draco.core.containers.ringmap property)": [[13, "draco.core.containers.RingMap.map", false]], "maps (draco.core.io.loadmaps attribute)": [[14, "draco.core.io.LoadMaps.maps", false]], "mask (draco.core.containers.baselinemask property)": [[13, "draco.core.containers.BaselineMask.mask", false]], "mask (draco.core.containers.formedbeammask property)": [[13, "draco.core.containers.FormedBeamMask.mask", false]], "mask (draco.core.containers.rfimask property)": [[13, "draco.core.containers.RFIMask.mask", false]], "mask (draco.core.containers.ringmapmask property)": [[13, "draco.core.containers.RingMapMask.mask", false]], "mask (draco.core.containers.siderealbaselinemask property)": [[13, "draco.core.containers.SiderealBaselineMask.mask", false]], "mask (draco.core.containers.siderealrfimask property)": [[13, "draco.core.containers.SiderealRFIMask.mask", false]], "mask_long_ns (draco.analysis.flagging.maskbaselines attribute)": [[4, "draco.analysis.flagging.MaskBaselines.mask_long_ns", false]], "mask_low_m (draco.analysis.flagging.maskmmodedata attribute)": [[4, "draco.analysis.flagging.MaskMModeData.mask_low_m", false]], "mask_missing_data (draco.analysis.flagging.maskfreq attribute)": [[4, "draco.analysis.flagging.MaskFreq.mask_missing_data", false]], "mask_short (draco.analysis.flagging.maskbaselines attribute)": [[4, "draco.analysis.flagging.MaskBaselines.mask_short", false]], "mask_short_ew (draco.analysis.flagging.maskbaselines attribute)": [[4, "draco.analysis.flagging.MaskBaselines.mask_short_ew", false]], "mask_short_ns (draco.analysis.flagging.maskbaselines attribute)": [[4, "draco.analysis.flagging.MaskBaselines.mask_short_ns", false]], "mask_type (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.mask_type", false]], "mask_zero_weight (draco.analysis.transform.regridder attribute)": [[11, "draco.analysis.transform.Regridder.mask_zero_weight", false]], "mask_zeros (draco.analysis.flagging.smoothvisweight attribute)": [[4, "draco.analysis.flagging.SmoothVisWeight.mask_zeros", false]], "maskbadgains (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.MaskBadGains", false]], "maskbaselines (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.MaskBaselines", false]], "maskbeamformedoutliers (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.MaskBeamformedOutliers", false]], "maskbeamformedweights (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.MaskBeamformedWeights", false]], "maskdata (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.MaskData", false]], "maskfreq (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.MaskFreq", false]], "maskmmodedata (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.MaskMModeData", false]], "match_axes() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.match_axes", false]], "match_median (draco.analysis.flagging.blendstack attribute)": [[4, "draco.analysis.flagging.BlendStack.match_median", false]], "max_m (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.max_m", false]], "max_thresh (draco.analysis.flagging.sanitizeweights attribute)": [[4, "draco.analysis.flagging.SanitizeWeights.max_thresh", false]], "maximumlikelihoodmapmaker (class in draco.analysis.mapmaker)": [[5, "draco.analysis.mapmaker.MaximumLikelihoodMapMaker", false]], "mcontainer (class in draco.core.containers)": [[13, "draco.core.containers.MContainer", false]], "measured (draco.core.containers.systemsensitivity property)": [[13, "draco.core.containers.SystemSensitivity.measured", false]], "medfilt() (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.medfilt", false]], "min_day_length (draco.analysis.sidereal.siderealgrouper attribute)": [[8, "draco.analysis.sidereal.SiderealGrouper.min_day_length", false]], "min_thresh (draco.analysis.flagging.sanitizeweights attribute)": [[4, "draco.analysis.flagging.SanitizeWeights.min_thresh", false]], "missing_threshold (draco.analysis.flagging.maskbaselines attribute)": [[4, "draco.analysis.flagging.MaskBaselines.missing_threshold", false]], "mixdata (class in draco.analysis.transform)": [[11, "draco.analysis.transform.MixData", false]], "mmax (draco.core.containers.mcontainer property)": [[13, "draco.core.containers.MContainer.mmax", false]], "mmodeinversetransform (class in draco.analysis.transform)": [[11, "draco.analysis.transform.MModeInverseTransform", false]], "mmodes (class in draco.core.containers)": [[13, "draco.core.containers.MModes", false]], "mmodetransform (class in draco.analysis.transform)": [[11, "draco.analysis.transform.MModeTransform", false]], "mock_freq_data() (in module draco.util.testing)": [[26, "draco.util.testing.mock_freq_data", false]], "mockfrequencystack (class in draco.core.containers)": [[13, "draco.core.containers.MockFrequencyStack", false]], "mockfrequencystackbypol (class in draco.core.containers)": [[13, "draco.core.containers.MockFrequencyStackByPol", false]], "mode (draco.analysis.fgfilter.klmodeproject attribute)": [[3, "draco.analysis.fgfilter.KLModeProject.mode", false]], "module": [[0, "module-draco.analysis", false], [1, "module-draco.analysis.beamform", false], [2, "module-draco.analysis.delay", false], [3, "module-draco.analysis.fgfilter", false], [4, "module-draco.analysis.flagging", false], [5, "module-draco.analysis.mapmaker", false], [6, "module-draco.analysis.powerspectrum", false], [7, "module-draco.analysis.sensitivity", false], [8, "module-draco.analysis.sidereal", false], [9, "module-draco.analysis.sourcestack", false], [10, "module-draco.analysis.svdfilter", false], [11, "module-draco.analysis.transform", false], [12, "module-draco.core", false], [13, "module-draco.core.containers", false], [14, "module-draco.core.io", false], [15, "module-draco.core.misc", false], [16, "module-draco.core.task", false], [17, "module-draco.synthesis", false], [18, "module-draco.synthesis.gain", false], [19, "module-draco.synthesis.noise", false], [20, "module-draco.synthesis.stream", false], [21, "module-draco.util", false], [22, "module-draco.util.exception", false], [23, "module-draco.util.random", false], [24, "module-draco.util.regrid", false], [25, "module-draco.util.rfi", false], [26, "module-draco.util.testing", false], [27, "module-draco.util.tools", false], [28, "module-draco.util.truncate", false], [31, "module-draco", false]], "mpi_random_seed() (in module draco.util.random)": [[23, "draco.util.random.mpi_random_seed", false]], "mpilogfilter (class in draco.core.task)": [[16, "draco.core.task.MPILogFilter", false]], "mpiloggedtask (class in draco.core.task)": [[16, "draco.core.task.MPILoggedTask", false]], "mpitask (class in draco.core.task)": [[16, "draco.core.task.MPITask", false]], "multithreadedrng (class in draco.util.random)": [[23, "draco.util.random.MultithreadedRNG", false]], "nan_check (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.nan_check", false]], "nan_dump (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.nan_dump", false]], "nan_skip (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.nan_skip", false]], "ndays (draco.synthesis.noise.gaussiannoise attribute)": [[19, "draco.synthesis.noise.GaussianNoise.ndays", false]], "negative_m (draco.analysis.flagging.maskmmodedata attribute)": [[4, "draco.analysis.flagging.MaskMModeData.negative_m", false]], "next() (draco.core.io.loadmaps method)": [[14, "draco.core.io.LoadMaps.next", false]], "next() (draco.core.io.print method)": [[14, "draco.core.io.Print.next", false]], "next() (draco.core.io.save method)": [[14, "draco.core.io.Save.next", false]], "next() (draco.core.io.savezarrzip method)": [[14, "draco.core.io.SaveZarrZip.next", false]], "next() (draco.core.io.waitzarrzip method)": [[14, "draco.core.io.WaitZarrZip.next", false]], "next() (draco.core.misc.accumulatelist method)": [[15, "draco.core.misc.AccumulateList.next", false]], "next() (draco.core.misc.passon method)": [[15, "draco.core.misc.PassOn.next", false]], "next() (draco.core.misc.waituntil method)": [[15, "draco.core.misc.WaitUntil.next", false]], "next() (draco.core.task.singletask method)": [[16, "draco.core.task.SingleTask.next", false]], "next() (draco.util.testing.randomfreqdata method)": [[26, "draco.util.testing.RandomFreqData.next", false]], "nfreq (draco.analysis.delay.delaytransformbase attribute)": [[2, "draco.analysis.delay.DelayTransformBase.nfreq", false]], "ngroup (draco.analysis.sourcestack.groupsourcestacks attribute)": [[9, "draco.analysis.sourcestack.GroupSourceStacks.ngroup", false]], "niter (draco.analysis.svdfilter.svdfilter attribute)": [[10, "draco.analysis.svdfilter.SVDFilter.niter", false]], "niter (draco.analysis.svdfilter.svdspectrumestimator attribute)": [[10, "draco.analysis.svdfilter.SVDSpectrumEstimator.niter", false]], "nmed (draco.analysis.flagging.maskbeamformedweights attribute)": [[4, "draco.analysis.flagging.MaskBeamformedWeights.nmed", false]], "nmode (draco.core.containers.svdmodes property)": [[13, "draco.core.containers.SVDModes.nmode", false]], "no_beam_model (draco.analysis.beamform.beamformbase attribute)": [[1, "draco.analysis.beamform.BeamFormBase.no_beam_model", false]], "noise (draco.util.testing.randomfreqdata attribute)": [[26, "draco.util.testing.RandomFreqData.noise", false]], "nra (draco.analysis.transform.mmodeinversetransform attribute)": [[11, "draco.analysis.transform.MModeInverseTransform.nra", false]], "nra (draco.analysis.transform.siderealmmoderesample attribute)": [[11, "draco.analysis.transform.SiderealMModeResample.nra", false]], "nsamp (draco.analysis.delay.delaygibbssamplerbase attribute)": [[2, "draco.analysis.delay.DelayGibbsSamplerBase.nsamp", false]], "nsample (draco.core.containers.samplevariancecontainer property)": [[13, "draco.core.containers.SampleVarianceContainer.nsample", false]], "nside (draco.analysis.mapmaker.basemapmaker attribute)": [[5, "draco.analysis.mapmaker.BaseMapMaker.nside", false]], "nside (draco.analysis.transform.transformjanskytokelvin attribute)": [[11, "draco.analysis.transform.TransformJanskyToKelvin.nside", false]], "nside (draco.core.containers.healpixbeam property)": [[13, "draco.core.containers.HEALPixBeam.nside", false]], "nside (draco.core.containers.healpixcontainer property)": [[13, "draco.core.containers.HealpixContainer.nside", false]], "nsigma (draco.analysis.flagging.findbeamformedoutliers attribute)": [[4, "draco.analysis.flagging.FindBeamformedOutliers.nsigma", false]], "null_delay_filter() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.null_delay_filter", false]], "num_base (draco.util.testing.randomfreqdata attribute)": [[26, "draco.util.testing.RandomFreqData.num_base", false]], "num_correlated (draco.util.testing.randomfreqdata attribute)": [[26, "draco.util.testing.RandomFreqData.num_correlated", false]], "num_freq (draco.util.testing.randomfreqdata attribute)": [[26, "draco.util.testing.RandomFreqData.num_freq", false]], "num_ra (draco.util.testing.randomfreqdata attribute)": [[26, "draco.util.testing.RandomFreqData.num_ra", false]], "num_realisation (draco.util.testing.randomfreqdata attribute)": [[26, "draco.util.testing.RandomFreqData.num_realisation", false]], "number (draco.analysis.sourcestack.randomsubset attribute)": [[9, "draco.analysis.sourcestack.RandomSubset.number", false]], "oddra (draco.core.containers.mcontainer property)": [[13, "draco.core.containers.MContainer.oddra", false]], "offset (draco.analysis.sidereal.siderealgrouper attribute)": [[8, "draco.analysis.sidereal.SiderealGrouper.offset", false]], "only_gains (draco.synthesis.gain.gainstacker attribute)": [[18, "draco.synthesis.gain.GainStacker.only_gains", false]], "ordering (draco.core.containers.healpixbeam property)": [[13, "draco.core.containers.HEALPixBeam.ordering", false]], "output_name (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.output_name", false]], "output_root (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.output_root", false]], "p_to_sigma() (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.p_to_sigma", false]], "padding (draco.analysis.sidereal.siderealgrouper attribute)": [[8, "draco.analysis.sidereal.SiderealGrouper.padding", false]], "passon (class in draco.core.misc)": [[15, "draco.core.misc.PassOn", false]], "phase (draco.synthesis.gain.basegains attribute)": [[18, "draco.synthesis.gain.BaseGains.phase", false]], "phi (draco.core.containers.gridbeam property)": [[13, "draco.core.containers.GridBeam.phi", false]], "pinv_svd() (in module draco.analysis.mapmaker)": [[5, "draco.analysis.mapmaker.pinv_svd", false]], "pipeline_config (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.pipeline_config", false]], "pix (draco.core.containers.trackbeam property)": [[13, "draco.core.containers.TrackBeam.pix", false]], "pol (draco.analysis.transform.selectpol attribute)": [[11, "draco.analysis.transform.SelectPol.pol", false]], "pol (draco.core.containers.delaycutoff property)": [[13, "draco.core.containers.DelayCutoff.pol", false]], "pol (draco.core.containers.formedbeam property)": [[13, "draco.core.containers.FormedBeam.pol", false]], "pol (draco.core.containers.frequencystackbypol property)": [[13, "draco.core.containers.FrequencyStackByPol.pol", false]], "pol (draco.core.containers.gridbeam property)": [[13, "draco.core.containers.GridBeam.pol", false]], "pol (draco.core.containers.healpixbeam property)": [[13, "draco.core.containers.HEALPixBeam.pol", false]], "pol (draco.core.containers.ringmap property)": [[13, "draco.core.containers.RingMap.pol", false]], "pol (draco.core.containers.systemsensitivity property)": [[13, "draco.core.containers.SystemSensitivity.pol", false]], "pol (draco.core.containers.trackbeam property)": [[13, "draco.core.containers.TrackBeam.pol", false]], "polarization (draco.analysis.beamform.beamformbase attribute)": [[1, "draco.analysis.beamform.BeamFormBase.polarization", false]], "polarization_map() (in module draco.util.tools)": [[27, "draco.util.tools.polarization_map", false]], "positive_m (draco.analysis.flagging.maskmmodedata attribute)": [[4, "draco.analysis.flagging.MaskMModeData.positive_m", false]], "powerspectrum (draco.core.containers.powerspectrum2d property)": [[13, "draco.core.containers.Powerspectrum2D.powerspectrum", false]], "powerspectrum2d (class in draco.core.containers)": [[13, "draco.core.containers.Powerspectrum2D", false]], "print (class in draco.core.io)": [[14, "draco.core.io.Print", false]], "prior_amp (draco.analysis.mapmaker.wienermapmaker attribute)": [[5, "draco.analysis.mapmaker.WienerMapMaker.prior_amp", false]], "prior_tilt (draco.analysis.mapmaker.wienermapmaker attribute)": [[5, "draco.analysis.mapmaker.WienerMapMaker.prior_tilt", false]], "process() (draco.analysis.beamform.beamform method)": [[1, "draco.analysis.beamform.BeamForm.process", false]], "process() (draco.analysis.beamform.beamformbase method)": [[1, "draco.analysis.beamform.BeamFormBase.process", false]], "process() (draco.analysis.beamform.beamformcat method)": [[1, "draco.analysis.beamform.BeamFormCat.process", false]], "process() (draco.analysis.beamform.healpixbeamform method)": [[1, "draco.analysis.beamform.HealpixBeamForm.process", false]], "process() (draco.analysis.beamform.ringmapbeamform method)": [[1, "draco.analysis.beamform.RingMapBeamForm.process", false]], "process() (draco.analysis.beamform.ringmapstack2d method)": [[1, "draco.analysis.beamform.RingMapStack2D.process", false]], "process() (draco.analysis.delay.delayfilter method)": [[2, "draco.analysis.delay.DelayFilter.process", false]], "process() (draco.analysis.delay.delayfilterbase method)": [[2, "draco.analysis.delay.DelayFilterBase.process", false]], "process() (draco.analysis.delay.delaytransformbase method)": [[2, "draco.analysis.delay.DelayTransformBase.process", false]], "process() (draco.analysis.flagging.applybaselinemask method)": [[4, "draco.analysis.flagging.ApplyBaselineMask.process", false]], "process() (draco.analysis.flagging.applytimefreqmask method)": [[4, "draco.analysis.flagging.ApplyTimeFreqMask.process", false]], "process() (draco.analysis.flagging.blendstack method)": [[4, "draco.analysis.flagging.BlendStack.process", false]], "process() (draco.analysis.flagging.collapsebaselinemask method)": [[4, "draco.analysis.flagging.CollapseBaselineMask.process", false]], "process() (draco.analysis.flagging.daymask method)": [[4, "draco.analysis.flagging.DayMask.process", false]], "process() (draco.analysis.flagging.findbeamformedoutliers method)": [[4, "draco.analysis.flagging.FindBeamformedOutliers.process", false]], "process() (draco.analysis.flagging.maskbadgains method)": [[4, "draco.analysis.flagging.MaskBadGains.process", false]], "process() (draco.analysis.flagging.maskbaselines method)": [[4, "draco.analysis.flagging.MaskBaselines.process", false]], "process() (draco.analysis.flagging.maskbeamformedoutliers method)": [[4, "draco.analysis.flagging.MaskBeamformedOutliers.process", false]], "process() (draco.analysis.flagging.maskbeamformedweights method)": [[4, "draco.analysis.flagging.MaskBeamformedWeights.process", false]], "process() (draco.analysis.flagging.maskfreq method)": [[4, "draco.analysis.flagging.MaskFreq.process", false]], "process() (draco.analysis.flagging.maskmmodedata method)": [[4, "draco.analysis.flagging.MaskMModeData.process", false]], "process() (draco.analysis.flagging.radiometerweight method)": [[4, "draco.analysis.flagging.RadiometerWeight.process", false]], "process() (draco.analysis.flagging.rfimask method)": [[4, "draco.analysis.flagging.RFIMask.process", false]], "process() (draco.analysis.flagging.rfisensitivitymask method)": [[4, "draco.analysis.flagging.RFISensitivityMask.process", false]], "process() (draco.analysis.flagging.sanitizeweights method)": [[4, "draco.analysis.flagging.SanitizeWeights.process", false]], "process() (draco.analysis.flagging.smoothvisweight method)": [[4, "draco.analysis.flagging.SmoothVisWeight.process", false]], "process() (draco.analysis.flagging.thresholdvisweightbaseline method)": [[4, "draco.analysis.flagging.ThresholdVisWeightBaseline.process", false]], "process() (draco.analysis.flagging.thresholdvisweightfrequency method)": [[4, "draco.analysis.flagging.ThresholdVisWeightFrequency.process", false]], "process() (draco.analysis.mapmaker.basemapmaker method)": [[5, "draco.analysis.mapmaker.BaseMapMaker.process", false]], "process() (draco.analysis.powerspectrum.quadraticpsestimation method)": [[6, "draco.analysis.powerspectrum.QuadraticPSEstimation.process", false]], "process() (draco.analysis.sensitivity.computesystemsensitivity method)": [[7, "draco.analysis.sensitivity.ComputeSystemSensitivity.process", false]], "process() (draco.analysis.sidereal.siderealgrouper method)": [[8, "draco.analysis.sidereal.SiderealGrouper.process", false]], "process() (draco.analysis.sidereal.siderealregridder method)": [[8, "draco.analysis.sidereal.SiderealRegridder.process", false]], "process() (draco.analysis.sidereal.siderealstacker method)": [[8, "draco.analysis.sidereal.SiderealStacker.process", false]], "process() (draco.analysis.sidereal.siderealstackermatch method)": [[8, "draco.analysis.sidereal.SiderealStackerMatch.process", false]], "process() (draco.analysis.sourcestack.groupsourcestacks method)": [[9, "draco.analysis.sourcestack.GroupSourceStacks.process", false]], "process() (draco.analysis.sourcestack.randomsubset method)": [[9, "draco.analysis.sourcestack.RandomSubset.process", false]], "process() (draco.analysis.sourcestack.sourcestack method)": [[9, "draco.analysis.sourcestack.SourceStack.process", false]], "process() (draco.analysis.svdfilter.svdfilter method)": [[10, "draco.analysis.svdfilter.SVDFilter.process", false]], "process() (draco.analysis.svdfilter.svdspectrumestimator method)": [[10, "draco.analysis.svdfilter.SVDSpectrumEstimator.process", false]], "process() (draco.analysis.transform.collateproducts method)": [[11, "draco.analysis.transform.CollateProducts.process", false]], "process() (draco.analysis.transform.downselect method)": [[11, "draco.analysis.transform.Downselect.process", false]], "process() (draco.analysis.transform.frequencyrebin method)": [[11, "draco.analysis.transform.FrequencyRebin.process", false]], "process() (draco.analysis.transform.hpftimestream method)": [[11, "draco.analysis.transform.HPFTimeStream.process", false]], "process() (draco.analysis.transform.mixdata method)": [[11, "draco.analysis.transform.MixData.process", false]], "process() (draco.analysis.transform.mmodeinversetransform method)": [[11, "draco.analysis.transform.MModeInverseTransform.process", false]], "process() (draco.analysis.transform.mmodetransform method)": [[11, "draco.analysis.transform.MModeTransform.process", false]], "process() (draco.analysis.transform.reducebase method)": [[11, "draco.analysis.transform.ReduceBase.process", false]], "process() (draco.analysis.transform.regridder method)": [[11, "draco.analysis.transform.Regridder.process", false]], "process() (draco.analysis.transform.selectfreq method)": [[11, "draco.analysis.transform.SelectFreq.process", false]], "process() (draco.analysis.transform.selectpol method)": [[11, "draco.analysis.transform.SelectPol.process", false]], "process() (draco.analysis.transform.shiftra method)": [[11, "draco.analysis.transform.ShiftRA.process", false]], "process() (draco.analysis.transform.transformjanskytokelvin method)": [[11, "draco.analysis.transform.TransformJanskyToKelvin.process", false]], "process() (draco.core.io.loadfilesfromparams method)": [[14, "draco.core.io.LoadFilesFromParams.process", false]], "process() (draco.core.io.loadfitscatalog method)": [[14, "draco.core.io.LoadFITSCatalog.process", false]], "process() (draco.core.io.saveconfig method)": [[14, "draco.core.io.SaveConfig.process", false]], "process() (draco.core.io.savemoduleversions method)": [[14, "draco.core.io.SaveModuleVersions.process", false]], "process() (draco.core.io.truncate method)": [[14, "draco.core.io.Truncate.process", false]], "process() (draco.core.io.zipzarrcontainers method)": [[14, "draco.core.io.ZipZarrContainers.process", false]], "process() (draco.core.misc.applygain method)": [[15, "draco.core.misc.ApplyGain.process", false]], "process() (draco.core.misc.makecopy method)": [[15, "draco.core.misc.MakeCopy.process", false]], "process() (draco.core.task.delete method)": [[16, "draco.core.task.Delete.process", false]], "process() (draco.core.task.returnfirstinputonfinish method)": [[16, "draco.core.task.ReturnFirstInputOnFinish.process", false]], "process() (draco.core.task.returnlastinputonfinish method)": [[16, "draco.core.task.ReturnLastInputOnFinish.process", false]], "process() (draco.synthesis.gain.basegains method)": [[18, "draco.synthesis.gain.BaseGains.process", false]], "process() (draco.synthesis.gain.gainstacker method)": [[18, "draco.synthesis.gain.GainStacker.process", false]], "process() (draco.synthesis.gain.siderealgains method)": [[18, "draco.synthesis.gain.SiderealGains.process", false]], "process() (draco.synthesis.noise.gaussiannoise method)": [[19, "draco.synthesis.noise.GaussianNoise.process", false]], "process() (draco.synthesis.noise.gaussiannoisedataset method)": [[19, "draco.synthesis.noise.GaussianNoiseDataset.process", false]], "process() (draco.synthesis.noise.receivertemperature method)": [[19, "draco.synthesis.noise.ReceiverTemperature.process", false]], "process() (draco.synthesis.noise.samplenoise method)": [[19, "draco.synthesis.noise.SampleNoise.process", false]], "process() (draco.synthesis.stream.expandproducts method)": [[20, "draco.synthesis.stream.ExpandProducts.process", false]], "process() (draco.synthesis.stream.makesiderealdaystream method)": [[20, "draco.synthesis.stream.MakeSiderealDayStream.process", false]], "process() (draco.synthesis.stream.maketimestream method)": [[20, "draco.synthesis.stream.MakeTimeStream.process", false]], "process() (draco.synthesis.stream.simulatesidereal method)": [[20, "draco.synthesis.stream.SimulateSidereal.process", false]], "process() (draco.util.testing.dummytask method)": [[26, "draco.util.testing.DummyTask.process", false]], "process_finish() (draco.analysis.beamform.beamformbase method)": [[1, "draco.analysis.beamform.BeamFormBase.process_finish", false]], "process_finish() (draco.analysis.sidereal.siderealgrouper method)": [[8, "draco.analysis.sidereal.SiderealGrouper.process_finish", false]], "process_finish() (draco.analysis.sidereal.siderealstacker method)": [[8, "draco.analysis.sidereal.SiderealStacker.process_finish", false]], "process_finish() (draco.analysis.sidereal.siderealstackermatch method)": [[8, "draco.analysis.sidereal.SiderealStackerMatch.process_finish", false]], "process_finish() (draco.analysis.sourcestack.groupsourcestacks method)": [[9, "draco.analysis.sourcestack.GroupSourceStacks.process_finish", false]], "process_finish() (draco.analysis.transform.mixdata method)": [[11, "draco.analysis.transform.MixData.process_finish", false]], "process_finish() (draco.core.task.returnfirstinputonfinish method)": [[16, "draco.core.task.ReturnFirstInputOnFinish.process_finish", false]], "process_finish() (draco.core.task.returnlastinputonfinish method)": [[16, "draco.core.task.ReturnLastInputOnFinish.process_finish", false]], "process_finish() (draco.synthesis.gain.gainstacker method)": [[18, "draco.synthesis.gain.GainStacker.process_finish", false]], "prod (draco.core.containers.viscontainer property)": [[13, "draco.core.containers.VisContainer.prod", false]], "prodstack (draco.core.containers.viscontainer property)": [[13, "draco.core.containers.VisContainer.prodstack", false]], "product_directory (draco.core.io.loadbeamtransfer attribute)": [[14, "draco.core.io.LoadBeamTransfer.product_directory", false]], "product_directory (draco.core.io.loadproductmanager attribute)": [[14, "draco.core.io.LoadProductManager.product_directory", false]], "psname (draco.analysis.powerspectrum.quadraticpsestimation attribute)": [[6, "draco.analysis.powerspectrum.QuadraticPSEstimation.psname", false]], "pstype (draco.analysis.powerspectrum.quadraticpsestimation attribute)": [[6, "draco.analysis.powerspectrum.QuadraticPSEstimation.pstype", false]], "quadraticpsestimation (class in draco.analysis.powerspectrum)": [[6, "draco.analysis.powerspectrum.QuadraticPSEstimation", false]], "quality (draco.core.containers.gridbeam property)": [[13, "draco.core.containers.GridBeam.quality", false]], "ra (draco.core.containers.siderealcontainer property)": [[13, "draco.core.containers.SiderealContainer.ra", false]], "radiometer (draco.core.containers.systemsensitivity property)": [[13, "draco.core.containers.SystemSensitivity.radiometer", false]], "radiometerweight (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.RadiometerWeight", false]], "randomfreqdata (class in draco.util.testing)": [[26, "draco.util.testing.RandomFreqData", false]], "randomgains (class in draco.synthesis.gain)": [[18, "draco.synthesis.gain.RandomGains", false]], "randomsiderealgains (class in draco.synthesis.gain)": [[18, "draco.synthesis.gain.RandomSiderealGains", false]], "randomsubset (class in draco.analysis.sourcestack)": [[9, "draco.analysis.sourcestack.RandomSubset", false]], "randomtask (class in draco.util.random)": [[23, "draco.util.random.RandomTask", false]], "receivertemperature (class in draco.synthesis.noise)": [[19, "draco.synthesis.noise.ReceiverTemperature", false]], "recv_temp (draco.synthesis.noise.gaussiannoise attribute)": [[19, "draco.synthesis.noise.GaussianNoise.recv_temp", false]], "recv_temp (draco.synthesis.noise.receivertemperature attribute)": [[19, "draco.synthesis.noise.ReceiverTemperature.recv_temp", false]], "redefine_stack_index_map() (in module draco.util.tools)": [[27, "draco.util.tools.redefine_stack_index_map", false]], "redistribute (draco.core.io.baseloadfiles attribute)": [[14, "draco.core.io.BaseLoadFiles.redistribute", false]], "reducebase (class in draco.analysis.transform)": [[11, "draco.analysis.transform.ReduceBase", false]], "reducevar (class in draco.analysis.transform)": [[11, "draco.analysis.transform.ReduceVar", false]], "reduction() (draco.analysis.transform.reducebase method)": [[11, "draco.analysis.transform.ReduceBase.reduction", false]], "reduction() (draco.analysis.transform.reducevar method)": [[11, "draco.analysis.transform.ReduceVar.reduction", false]], "redundancy (draco.core.containers.visgridstream property)": [[13, "draco.core.containers.VisGridStream.redundancy", false]], "reference_declination (draco.analysis.transform.transformjanskytokelvin attribute)": [[11, "draco.analysis.transform.TransformJanskyToKelvin.reference_declination", false]], "regridder (class in draco.analysis.transform)": [[11, "draco.analysis.transform.Regridder", false]], "remove (draco.core.io.zipzarrcontainers attribute)": [[14, "draco.core.io.ZipZarrContainers.remove", false]], "remove_average (draco.analysis.flagging.daymask attribute)": [[4, "draco.analysis.flagging.DayMask.remove_average", false]], "remove_integration_window (draco.analysis.transform.mmodetransform attribute)": [[11, "draco.analysis.transform.MModeTransform.remove_integration_window", false]], "remove_median (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.remove_median", false]], "replace (draco.analysis.flagging.radiometerweight attribute)": [[4, "draco.analysis.flagging.RadiometerWeight.replace", false]], "returnfirstinputonfinish (class in draco.core.task)": [[16, "draco.core.task.ReturnFirstInputOnFinish", false]], "returnlastinputonfinish (class in draco.core.task)": [[16, "draco.core.task.ReturnLastInputOnFinish", false]], "rfimask (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.RFIMask", false]], "rfimask (class in draco.core.containers)": [[13, "draco.core.containers.RFIMask", false]], "rfisensitivitymask (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.RFISensitivityMask", false]], "ringmap (class in draco.core.containers)": [[13, "draco.core.containers.RingMap", false]], "ringmapbeamform (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.RingMapBeamForm", false]], "ringmapmask (class in draco.core.containers)": [[13, "draco.core.containers.RingMapMask", false]], "ringmapstack2d (class in draco.analysis.beamform)": [[1, "draco.analysis.beamform.RingMapStack2D", false]], "rms (draco.core.containers.ringmap property)": [[13, "draco.core.containers.RingMap.rms", false]], "rng (draco.util.random.randomtask property)": [[23, "draco.util.random.RandomTask.rng", false]], "root (draco.core.io.save attribute)": [[14, "draco.core.io.Save.root", false]], "root (draco.core.io.saveconfig attribute)": [[14, "draco.core.io.SaveConfig.root", false]], "root (draco.core.io.savemoduleversions attribute)": [[14, "draco.core.io.SaveModuleVersions.root", false]], "sample_frac (draco.synthesis.noise.samplenoise attribute)": [[19, "draco.synthesis.noise.SampleNoise.sample_frac", false]], "sample_variance (draco.core.containers.samplevariancecontainer property)": [[13, "draco.core.containers.SampleVarianceContainer.sample_variance", false]], "sample_variance_amp_phase (draco.core.containers.samplevariancecontainer property)": [[13, "draco.core.containers.SampleVarianceContainer.sample_variance_amp_phase", false]], "sample_variance_iq (draco.core.containers.samplevariancecontainer property)": [[13, "draco.core.containers.SampleVarianceContainer.sample_variance_iq", false]], "sample_weight (draco.core.containers.samplevariancecontainer property)": [[13, "draco.core.containers.SampleVarianceContainer.sample_weight", false]], "samplenoise (class in draco.synthesis.noise)": [[19, "draco.synthesis.noise.SampleNoise", false]], "samples (draco.analysis.sidereal.siderealregridder attribute)": [[8, "draco.analysis.sidereal.SiderealRegridder.samples", false]], "samples (draco.analysis.transform.regridder attribute)": [[11, "draco.analysis.transform.Regridder.samples", false]], "samplevariancecontainer (class in draco.core.containers)": [[13, "draco.core.containers.SampleVarianceContainer", false]], "sanitizeweights (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.SanitizeWeights", false]], "save (class in draco.core.io)": [[14, "draco.core.io.Save", false]], "save (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.save", false]], "save_samples (draco.analysis.delay.delaygibbssamplerbase attribute)": [[2, "draco.analysis.delay.DelayGibbsSamplerBase.save_samples", false]], "saveconfig (class in draco.core.io)": [[14, "draco.core.io.SaveConfig", false]], "savemoduleversions (class in draco.core.io)": [[14, "draco.core.io.SaveModuleVersions", false]], "savezarrzip (class in draco.core.io)": [[14, "draco.core.io.SaveZarrZip", false]], "seed (draco.util.random.randomtask attribute)": [[23, "draco.util.random.RandomTask.seed", false]], "selectfreq (class in draco.analysis.transform)": [[11, "draco.analysis.transform.SelectFreq", false]], "selections (draco.core.io.selectionsmixin attribute)": [[14, "draco.core.io.SelectionsMixin.Selections", false], [14, "draco.core.io.SelectionsMixin.selections", false]], "selectionsmixin (class in draco.core.io)": [[14, "draco.core.io.SelectionsMixin", false]], "selectpol (class in draco.analysis.transform)": [[11, "draco.analysis.transform.SelectPol", false]], "set_weights (draco.synthesis.noise.gaussiannoise attribute)": [[19, "draco.synthesis.noise.GaussianNoise.set_weights", false]], "set_weights (draco.synthesis.noise.samplenoise attribute)": [[19, "draco.synthesis.noise.SampleNoise.set_weights", false]], "setmpilogging (class in draco.core.task)": [[16, "draco.core.task.SetMPILogging", false]], "setup() (draco.analysis.beamform.beamform method)": [[1, "draco.analysis.beamform.BeamForm.setup", false]], "setup() (draco.analysis.beamform.beamformbase method)": [[1, "draco.analysis.beamform.BeamFormBase.setup", false]], "setup() (draco.analysis.beamform.beamformcat method)": [[1, "draco.analysis.beamform.BeamFormCat.setup", false]], "setup() (draco.analysis.beamform.beamformexternalbase method)": [[1, "draco.analysis.beamform.BeamFormExternalBase.setup", false]], "setup() (draco.analysis.beamform.healpixbeamform method)": [[1, "draco.analysis.beamform.HealpixBeamForm.setup", false]], "setup() (draco.analysis.beamform.ringmapbeamform method)": [[1, "draco.analysis.beamform.RingMapBeamForm.setup", false]], "setup() (draco.analysis.delay.delayfilter method)": [[2, "draco.analysis.delay.DelayFilter.setup", false]], "setup() (draco.analysis.delay.delayfilterbase method)": [[2, "draco.analysis.delay.DelayFilterBase.setup", false]], "setup() (draco.analysis.delay.delaypowerspectrumstokesiestimator method)": [[2, "draco.analysis.delay.DelayPowerSpectrumStokesIEstimator.setup", false]], "setup() (draco.analysis.delay.delayspectrumwienerestimator method)": [[2, "draco.analysis.delay.DelaySpectrumWienerEstimator.setup", false]], "setup() (draco.analysis.fgfilter.klmodeproject method)": [[3, "draco.analysis.fgfilter.KLModeProject.setup", false]], "setup() (draco.analysis.fgfilter.svdmodeproject method)": [[3, "draco.analysis.fgfilter.SVDModeProject.setup", false]], "setup() (draco.analysis.flagging.blendstack method)": [[4, "draco.analysis.flagging.BlendStack.setup", false]], "setup() (draco.analysis.flagging.maskbaselines method)": [[4, "draco.analysis.flagging.MaskBaselines.setup", false]], "setup() (draco.analysis.flagging.sanitizeweights method)": [[4, "draco.analysis.flagging.SanitizeWeights.setup", false]], "setup() (draco.analysis.flagging.thresholdvisweightbaseline method)": [[4, "draco.analysis.flagging.ThresholdVisWeightBaseline.setup", false]], "setup() (draco.analysis.mapmaker.basemapmaker method)": [[5, "draco.analysis.mapmaker.BaseMapMaker.setup", false]], "setup() (draco.analysis.powerspectrum.quadraticpsestimation method)": [[6, "draco.analysis.powerspectrum.QuadraticPSEstimation.setup", false]], "setup() (draco.analysis.sensitivity.computesystemsensitivity method)": [[7, "draco.analysis.sensitivity.ComputeSystemSensitivity.setup", false]], "setup() (draco.analysis.sidereal.siderealgrouper method)": [[8, "draco.analysis.sidereal.SiderealGrouper.setup", false]], "setup() (draco.analysis.sidereal.siderealregridder method)": [[8, "draco.analysis.sidereal.SiderealRegridder.setup", false]], "setup() (draco.analysis.sourcestack.groupsourcestacks method)": [[9, "draco.analysis.sourcestack.GroupSourceStacks.setup", false]], "setup() (draco.analysis.sourcestack.randomsubset method)": [[9, "draco.analysis.sourcestack.RandomSubset.setup", false]], "setup() (draco.analysis.transform.collateproducts method)": [[11, "draco.analysis.transform.CollateProducts.setup", false]], "setup() (draco.analysis.transform.mixdata method)": [[11, "draco.analysis.transform.MixData.setup", false]], "setup() (draco.analysis.transform.mmodetransform method)": [[11, "draco.analysis.transform.MModeTransform.setup", false]], "setup() (draco.analysis.transform.regridder method)": [[11, "draco.analysis.transform.Regridder.setup", false]], "setup() (draco.analysis.transform.transformjanskytokelvin method)": [[11, "draco.analysis.transform.TransformJanskyToKelvin.setup", false]], "setup() (draco.core.io.findfiles method)": [[14, "draco.core.io.FindFiles.setup", false]], "setup() (draco.core.io.loadbeamtransfer method)": [[14, "draco.core.io.LoadBeamTransfer.setup", false]], "setup() (draco.core.io.loadfiles method)": [[14, "draco.core.io.LoadFiles.setup", false]], "setup() (draco.core.io.loadproductmanager method)": [[14, "draco.core.io.LoadProductManager.setup", false]], "setup() (draco.core.io.saveconfig method)": [[14, "draco.core.io.SaveConfig.setup", false]], "setup() (draco.core.io.savemoduleversions method)": [[14, "draco.core.io.SaveModuleVersions.setup", false]], "setup() (draco.core.io.savezarrzip method)": [[14, "draco.core.io.SaveZarrZip.setup", false]], "setup() (draco.core.io.selectionsmixin method)": [[14, "draco.core.io.SelectionsMixin.setup", false]], "setup() (draco.core.io.zipzarrcontainers method)": [[14, "draco.core.io.ZipZarrContainers.setup", false]], "setup() (draco.core.misc.checkmpienvironment method)": [[15, "draco.core.misc.CheckMPIEnvironment.setup", false]], "setup() (draco.core.misc.waituntil method)": [[15, "draco.core.misc.WaitUntil.setup", false]], "setup() (draco.synthesis.gain.gainstacker method)": [[18, "draco.synthesis.gain.GainStacker.setup", false]], "setup() (draco.synthesis.gain.siderealgains method)": [[18, "draco.synthesis.gain.SiderealGains.setup", false]], "setup() (draco.synthesis.noise.gaussiannoise method)": [[19, "draco.synthesis.noise.GaussianNoise.setup", false]], "setup() (draco.synthesis.stream.expandproducts method)": [[20, "draco.synthesis.stream.ExpandProducts.setup", false]], "setup() (draco.synthesis.stream.makesiderealdaystream method)": [[20, "draco.synthesis.stream.MakeSiderealDayStream.setup", false]], "setup() (draco.synthesis.stream.maketimestream method)": [[20, "draco.synthesis.stream.MakeTimeStream.setup", false]], "setup() (draco.synthesis.stream.simulatesidereal method)": [[20, "draco.synthesis.stream.SimulateSidereal.setup", false]], "share (draco.analysis.flagging.applybaselinemask attribute)": [[4, "draco.analysis.flagging.ApplyBaselineMask.share", false]], "share (draco.analysis.flagging.applytimefreqmask attribute)": [[4, "draco.analysis.flagging.ApplyTimeFreqMask.share", false]], "share (draco.analysis.flagging.maskbaselines attribute)": [[4, "draco.analysis.flagging.MaskBaselines.share", false]], "share (draco.analysis.transform.transformjanskytokelvin attribute)": [[11, "draco.analysis.transform.TransformJanskyToKelvin.share", false]], "shiftra (class in draco.analysis.transform)": [[11, "draco.analysis.transform.ShiftRA", false]], "siderealbaselinemask (class in draco.core.containers)": [[13, "draco.core.containers.SiderealBaselineMask", false]], "siderealcontainer (class in draco.core.containers)": [[13, "draco.core.containers.SiderealContainer", false]], "siderealgaindata (class in draco.core.containers)": [[13, "draco.core.containers.SiderealGainData", false]], "siderealgains (class in draco.synthesis.gain)": [[18, "draco.synthesis.gain.SiderealGains", false]], "siderealgrouper (class in draco.analysis.sidereal)": [[8, "draco.analysis.sidereal.SiderealGrouper", false]], "siderealmmoderesample (class in draco.analysis.transform)": [[11, "draco.analysis.transform.SiderealMModeResample", false]], "siderealregridder (class in draco.analysis.sidereal)": [[8, "draco.analysis.sidereal.SiderealRegridder", false]], "siderealregriddercubic (class in draco.analysis.sidereal)": [[8, "draco.analysis.sidereal.SiderealRegridderCubic", false]], "siderealregridderlinear (class in draco.analysis.sidereal)": [[8, "draco.analysis.sidereal.SiderealRegridderLinear", false]], "siderealregriddernearest (class in draco.analysis.sidereal)": [[8, "draco.analysis.sidereal.SiderealRegridderNearest", false]], "siderealrfimask (class in draco.core.containers)": [[13, "draco.core.containers.SiderealRFIMask", false]], "siderealstacker (class in draco.analysis.sidereal)": [[8, "draco.analysis.sidereal.SiderealStacker", false]], "siderealstackermatch (class in draco.analysis.sidereal)": [[8, "draco.analysis.sidereal.SiderealStackerMatch", false]], "siderealstream (class in draco.core.containers)": [[13, "draco.core.containers.SiderealStream", false]], "sigma (draco.analysis.flagging.rfimask attribute)": [[4, "draco.analysis.flagging.RFIMask.sigma", false]], "sigma (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.sigma", false]], "sigma_to_p() (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.sigma_to_p", false]], "simulatesidereal (class in draco.synthesis.stream)": [[20, "draco.synthesis.stream.SimulateSidereal", false]], "single_source_bin_index (draco.analysis.sourcestack.sourcestack attribute)": [[9, "draco.analysis.sourcestack.SourceStack.single_source_bin_index", false]], "singletask (class in draco.core.task)": [[16, "draco.core.task.SingleTask", false]], "sir (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.sir", false]], "sir() (in module draco.util.rfi)": [[25, "draco.util.rfi.sir", false]], "sir1d() (in module draco.util.rfi)": [[25, "draco.util.rfi.sir1d", false]], "size (draco.analysis.sourcestack.randomsubset attribute)": [[9, "draco.analysis.sourcestack.RandomSubset.size", false]], "skip_nyquist (draco.analysis.delay.delaytransformbase attribute)": [[2, "draco.analysis.delay.DelayTransformBase.skip_nyquist", false]], "smoothing_length (draco.core.misc.applygain attribute)": [[15, "draco.core.misc.ApplyGain.smoothing_length", false]], "smoothvisweight (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.SmoothVisWeight", false]], "snr_cov (draco.analysis.sidereal.siderealregridder attribute)": [[8, "draco.analysis.sidereal.SiderealRegridder.snr_cov", false]], "snr_cov (draco.analysis.transform.regridder attribute)": [[11, "draco.analysis.transform.Regridder.snr_cov", false]], "sourcecatalog (class in draco.core.containers)": [[13, "draco.core.containers.SourceCatalog", false]], "sourcestack (class in draco.analysis.sourcestack)": [[9, "draco.analysis.sourcestack.SourceStack", false]], "spectroscopiccatalog (class in draco.core.containers)": [[13, "draco.core.containers.SpectroscopicCatalog", false]], "spectrum (draco.core.containers.delaycrossspectrum property)": [[13, "draco.core.containers.DelayCrossSpectrum.spectrum", false]], "spectrum (draco.core.containers.delayspectrum property)": [[13, "draco.core.containers.DelaySpectrum.spectrum", false]], "spectrum (draco.core.containers.delaytransform property)": [[13, "draco.core.containers.DelayTransform.spectrum", false]], "spectrum (draco.core.containers.svdspectrum property)": [[13, "draco.core.containers.SVDSpectrum.spectrum", false]], "spectrum (draco.core.containers.waveletspectrum property)": [[13, "draco.core.containers.WaveletSpectrum.spectrum", false]], "stack (draco.core.containers.baselinemask property)": [[13, "draco.core.containers.BaselineMask.stack", false]], "stack (draco.core.containers.frequencystack property)": [[13, "draco.core.containers.FrequencyStack.stack", false]], "stack (draco.core.containers.siderealbaselinemask property)": [[13, "draco.core.containers.SiderealBaselineMask.stack", false]], "stack (draco.core.containers.stack3d property)": [[13, "draco.core.containers.Stack3D.stack", false]], "stack (draco.core.containers.viscontainer property)": [[13, "draco.core.containers.VisContainer.stack", false]], "stack3d (class in draco.core.containers)": [[13, "draco.core.containers.Stack3D", false]], "stack_ind (draco.analysis.flagging.rfimask attribute)": [[4, "draco.analysis.flagging.RFIMask.stack_ind", false]], "stacked (draco.synthesis.stream.simulatesidereal attribute)": [[20, "draco.synthesis.stream.SimulateSidereal.stacked", false]], "standard_complex_normal() (in module draco.util.random)": [[23, "draco.util.random.standard_complex_normal", false]], "standard_complex_wishart() (in module draco.util.random)": [[23, "draco.util.random.standard_complex_wishart", false]], "start (draco.analysis.transform.regridder attribute)": [[11, "draco.analysis.transform.Regridder.start", false]], "start_threshold_sigma (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.start_threshold_sigma", false]], "staticgaindata (class in draco.core.containers)": [[13, "draco.core.containers.StaticGainData", false]], "stokes_i() (in module draco.analysis.delay)": [[2, "draco.analysis.delay.stokes_I", false]], "subtract (draco.analysis.flagging.blendstack attribute)": [[4, "draco.analysis.flagging.BlendStack.subtract", false]], "sumthreshold() (in module draco.util.rfi)": [[25, "draco.util.rfi.sumthreshold", false]], "sumthreshold_py() (in module draco.util.rfi)": [[25, "draco.util.rfi.sumthreshold_py", false]], "svd_em() (in module draco.analysis.svdfilter)": [[10, "draco.analysis.svdfilter.svd_em", false]], "svdfilter (class in draco.analysis.svdfilter)": [[10, "draco.analysis.svdfilter.SVDFilter", false]], "svdmodeproject (class in draco.analysis.fgfilter)": [[3, "draco.analysis.fgfilter.SVDModeProject", false]], "svdmodes (class in draco.core.containers)": [[13, "draco.core.containers.SVDModes", false]], "svdspectrum (class in draco.core.containers)": [[13, "draco.core.containers.SVDSpectrum", false]], "svdspectrumestimator (class in draco.analysis.svdfilter)": [[10, "draco.analysis.svdfilter.SVDSpectrumEstimator", false]], "systemsensitivity (class in draco.core.containers)": [[13, "draco.core.containers.SystemSensitivity", false]], "table_spec (draco.core.containers.tablebase property)": [[13, "draco.core.containers.TableBase.table_spec", false]], "tablebase (class in draco.core.containers)": [[13, "draco.core.containers.TableBase", false]], "tag (draco.analysis.sidereal.siderealstacker attribute)": [[8, "draco.analysis.sidereal.SiderealStacker.tag", false]], "tag (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.tag", false]], "tag (draco.util.testing.dummytask attribute)": [[26, "draco.util.testing.DummyTask.tag", false]], "telescope_orientation (draco.analysis.delay.delayfilter attribute)": [[2, "draco.analysis.delay.DelayFilter.telescope_orientation", false]], "theta (draco.core.containers.gridbeam property)": [[13, "draco.core.containers.GridBeam.theta", false]], "threads (draco.util.random.randomtask attribute)": [[23, "draco.util.random.RandomTask.threads", false]], "threshold (draco.analysis.fgfilter.klmodeproject attribute)": [[3, "draco.analysis.fgfilter.KLModeProject.threshold", false]], "thresholdvisweightbaseline (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.ThresholdVisWeightBaseline", false]], "thresholdvisweightfrequency (class in draco.analysis.flagging)": [[4, "draco.analysis.flagging.ThresholdVisWeightFrequency", false]], "timestream (class in draco.core.containers)": [[13, "draco.core.containers.TimeStream", false]], "timetrack (draco.analysis.beamform.beamformbase attribute)": [[1, "draco.analysis.beamform.BeamFormBase.timetrack", false]], "todcontainer (class in draco.core.containers)": [[13, "draco.core.containers.TODContainer", false]], "total_len (draco.util.testing.dummytask attribute)": [[26, "draco.util.testing.DummyTask.total_len", false]], "track_type (draco.core.containers.trackbeam property)": [[13, "draco.core.containers.TrackBeam.track_type", false]], "trackbeam (class in draco.core.containers)": [[13, "draco.core.containers.TrackBeam", false]], "transformjanskytokelvin (class in draco.analysis.transform)": [[11, "draco.analysis.transform.TransformJanskyToKelvin", false]], "truncate (class in draco.core.io)": [[14, "draco.core.io.Truncate", false]], "tv_base_size (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.tv_base_size", false]], "tv_channels_flag() (in module draco.analysis.flagging)": [[4, "draco.analysis.flagging.tv_channels_flag", false]], "tv_fraction (draco.analysis.flagging.rfimask attribute)": [[4, "draco.analysis.flagging.RFIMask.tv_fraction", false]], "tv_fraction (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.tv_fraction", false]], "tv_mad_size (draco.analysis.flagging.rfisensitivitymask attribute)": [[4, "draco.analysis.flagging.RFISensitivityMask.tv_mad_size", false]], "update_id (draco.core.containers.gaindata property)": [[13, "draco.core.containers.GainData.update_id", false]], "update_weight (draco.core.misc.applygain attribute)": [[15, "draco.core.misc.ApplyGain.update_weight", false]], "variable_timetrack (draco.analysis.beamform.beamformbase attribute)": [[1, "draco.analysis.beamform.BeamFormBase.variable_timetrack", false]], "versions (draco.core.task.singletask attribute)": [[16, "draco.core.task.SingleTask.versions", false]], "vis (draco.core.containers.mmodes attribute)": [[13, "draco.core.containers.MModes.vis", false]], "vis (draco.core.containers.visbase property)": [[13, "draco.core.containers.VisBase.vis", false]], "visbase (class in draco.core.containers)": [[13, "draco.core.containers.VisBase", false]], "viscontainer (class in draco.core.containers)": [[13, "draco.core.containers.VisContainer", false]], "visgridstream (class in draco.core.containers)": [[13, "draco.core.containers.VisGridStream", false]], "waituntil (class in draco.core.misc)": [[15, "draco.core.misc.WaitUntil", false]], "waitzarrzip (class in draco.core.io)": [[14, "draco.core.io.WaitZarrZip", false]], "waveletspectrum (class in draco.core.containers)": [[13, "draco.core.containers.WaveletSpectrum", false]], "weight (draco.analysis.beamform.beamformbase attribute)": [[1, "draco.analysis.beamform.BeamFormBase.weight", false]], "weight (draco.analysis.sidereal.siderealstacker attribute)": [[8, "draco.analysis.sidereal.SiderealStacker.weight", false]], "weight (draco.core.containers.dataweightcontainer property)": [[13, "draco.core.containers.DataWeightContainer.weight", false]], "weight (draco.core.containers.gaindatabase property)": [[13, "draco.core.containers.GainDataBase.weight", false]], "weight (draco.core.containers.mmodes attribute)": [[13, "draco.core.containers.MModes.weight", false]], "weight (draco.core.containers.systemsensitivity property)": [[13, "draco.core.containers.SystemSensitivity.weight", false]], "weight_boost (draco.analysis.delay.delaytransformbase attribute)": [[2, "draco.analysis.delay.DelayTransformBase.weight_boost", false]], "weight_boost (draco.core.containers.delayspectrum property)": [[13, "draco.core.containers.DelaySpectrum.weight_boost", false]], "weight_boost (draco.core.containers.delaytransform property)": [[13, "draco.core.containers.DelayTransform.weight_boost", false]], "weight_coeff (draco.analysis.transform.mixdata attribute)": [[11, "draco.analysis.transform.MixData.weight_coeff", false]], "weight_tol (draco.analysis.delay.delayfilter attribute)": [[2, "draco.analysis.delay.DelayFilter.weight_tol", false]], "weighting (draco.analysis.transform.reducebase attribute)": [[11, "draco.analysis.transform.ReduceBase.weighting", false]], "width (draco.analysis.flagging.daymask attribute)": [[4, "draco.analysis.flagging.DayMask.width", false]], "wienermapmaker (class in draco.analysis.mapmaker)": [[5, "draco.analysis.mapmaker.WienerMapMaker", false]], "window (draco.analysis.delay.delayfilter attribute)": [[2, "draco.analysis.delay.DelayFilter.window", false]], "window (draco.analysis.delay.delayfilterbase attribute)": [[2, "draco.analysis.delay.DelayFilterBase.window", false]], "window (draco.analysis.delay.delaytransformbase attribute)": [[2, "draco.analysis.delay.DelayTransformBase.window", false]], "window (draco.analysis.flagging.findbeamformedoutliers attribute)": [[4, "draco.analysis.flagging.FindBeamformedOutliers.window", false]], "window_generalised() (in module draco.util.tools)": [[27, "draco.util.tools.window_generalised", false]], "with_sample_variance (draco.analysis.sidereal.siderealstacker attribute)": [[8, "draco.analysis.sidereal.SiderealStacker.with_sample_variance", false]], "z_range (draco.core.io.loadfitscatalog attribute)": [[14, "draco.core.io.LoadFITSCatalog.z_range", false]], "za_cut (draco.analysis.delay.delayfilter attribute)": [[2, "draco.analysis.delay.DelayFilter.za_cut", false]], "zarrziphandle (class in draco.core.io)": [[14, "draco.core.io.ZarrZipHandle", false]], "zero_data (draco.analysis.flagging.daymask attribute)": [[4, "draco.analysis.flagging.DayMask.zero_data", false]], "zero_data (draco.analysis.flagging.maskbaselines attribute)": [[4, "draco.analysis.flagging.MaskBaselines.zero_data", false]], "zipzarrcontainers (class in draco.core.io)": [[14, "draco.core.io.ZipZarrContainers", false]]}, "objects": {"": [[31, 0, 0, "-", "draco"]], "draco": [[0, 0, 0, "-", "analysis"], [12, 0, 0, "-", "core"], [17, 0, 0, "-", "synthesis"], [21, 0, 0, "-", "util"]], "draco.analysis": [[1, 0, 0, "-", "beamform"], [2, 0, 0, "-", "delay"], [3, 0, 0, "-", "fgfilter"], [4, 0, 0, "-", "flagging"], [5, 0, 0, "-", "mapmaker"], [6, 0, 0, "-", "powerspectrum"], [7, 0, 0, "-", "sensitivity"], [8, 0, 0, "-", "sidereal"], [9, 0, 0, "-", "sourcestack"], [10, 0, 0, "-", "svdfilter"], [11, 0, 0, "-", "transform"]], "draco.analysis.beamform": [[1, 1, 1, "", "BeamForm"], [1, 1, 1, "", "BeamFormBase"], [1, 1, 1, "", "BeamFormCat"], [1, 1, 1, "", "BeamFormExternal"], [1, 1, 1, "", "BeamFormExternalBase"], [1, 1, 1, "", "BeamFormExternalCat"], [1, 1, 1, "", "HealpixBeamForm"], [1, 1, 1, "", "RingMapBeamForm"], [1, 1, 1, "", "RingMapStack2D"], [1, 4, 1, "", "icrs_to_cirs"]], "draco.analysis.beamform.BeamForm": [[1, 2, 1, "", "process"], [1, 2, 1, "", "setup"]], "draco.analysis.beamform.BeamFormBase": [[1, 3, 1, "", "collapse_ha"], [1, 3, 1, "", "freqside"], [1, 3, 1, "", "no_beam_model"], [1, 3, 1, "", "polarization"], [1, 2, 1, "", "process"], [1, 2, 1, "", "process_finish"], [1, 2, 1, "", "setup"], [1, 3, 1, "", "timetrack"], [1, 3, 1, "", "variable_timetrack"], [1, 3, 1, "", "weight"]], "draco.analysis.beamform.BeamFormCat": [[1, 2, 1, "", "process"], [1, 2, 1, "", "setup"]], "draco.analysis.beamform.BeamFormExternalBase": [[1, 2, 1, "", "setup"]], "draco.analysis.beamform.HealpixBeamForm": [[1, 3, 1, "", "fwhm"], [1, 2, 1, "", "process"], [1, 2, 1, "", "setup"]], "draco.analysis.beamform.RingMapBeamForm": [[1, 2, 1, "", "process"], [1, 2, 1, "", "setup"]], "draco.analysis.beamform.RingMapStack2D": [[1, 2, 1, "", "process"]], "draco.analysis.delay": [[2, 1, 1, "", "DelayCrossPowerSpectrumEstimator"], [2, 1, 1, "", "DelayFilter"], [2, 1, 1, "", "DelayFilterBase"], [2, 1, 1, "", "DelayGeneralContainerBase"], [2, 1, 1, "", "DelayGibbsSamplerBase"], [2, 1, 1, "", "DelayPowerSpectrumGeneralEstimator"], [2, 1, 1, "", "DelayPowerSpectrumStokesIEstimator"], [2, 3, 1, "", "DelaySpectrumEstimator"], [2, 3, 1, "", "DelaySpectrumEstimatorBase"], [2, 3, 1, "", "DelaySpectrumWienerBase"], [2, 1, 1, "", "DelaySpectrumWienerEstimator"], [2, 1, 1, "", "DelayTransformBase"], [2, 4, 1, "", "delay_power_spectrum_gibbs"], [2, 4, 1, "", "delay_spectrum_gibbs"], [2, 4, 1, "", "delay_spectrum_gibbs_cross"], [2, 4, 1, "", "delay_spectrum_wiener_filter"], [2, 4, 1, "", "flatten_axes"], [2, 4, 1, "", "fourier_matrix"], [2, 4, 1, "", "fourier_matrix_c2c"], [2, 4, 1, "", "fourier_matrix_c2r"], [2, 4, 1, "", "fourier_matrix_r2c"], [2, 4, 1, "", "match_axes"], [2, 4, 1, "", "null_delay_filter"], [2, 4, 1, "", "stokes_I"]], "draco.analysis.delay.DelayFilter": [[2, 3, 1, "", "delay_cut"], [2, 3, 1, "", "extra_cut"], [2, 2, 1, "", "process"], [2, 2, 1, "", "setup"], [2, 3, 1, "", "telescope_orientation"], [2, 3, 1, "", "weight_tol"], [2, 3, 1, "", "window"], [2, 3, 1, "", "za_cut"]], "draco.analysis.delay.DelayFilterBase": [[2, 3, 1, "", "axis"], [2, 3, 1, "", "dataset"], [2, 3, 1, "", "delay_cut"], [2, 2, 1, "", "process"], [2, 2, 1, "", "setup"], [2, 3, 1, "", "window"]], "draco.analysis.delay.DelayGeneralContainerBase": [[2, 3, 1, "", "average_axis"], [2, 3, 1, "", "dataset"]], "draco.analysis.delay.DelayGibbsSamplerBase": [[2, 3, 1, "", "initial_amplitude"], [2, 3, 1, "", "initial_sample_path"], [2, 3, 1, "", "nsamp"], [2, 3, 1, "", "save_samples"]], "draco.analysis.delay.DelayPowerSpectrumStokesIEstimator": [[2, 2, 1, "", "setup"]], "draco.analysis.delay.DelaySpectrumWienerEstimator": [[2, 2, 1, "", "setup"]], "draco.analysis.delay.DelayTransformBase": [[2, 3, 1, "", "apply_window"], [2, 3, 1, "", "complex_timedomain"], [2, 3, 1, "", "freq_spacing"], [2, 3, 1, "", "freq_zero"], [2, 3, 1, "", "nfreq"], [2, 2, 1, "", "process"], [2, 3, 1, "", "skip_nyquist"], [2, 3, 1, "", "weight_boost"], [2, 3, 1, "", "window"]], "draco.analysis.fgfilter": [[3, 1, 1, "", "KLModeProject"], [3, 1, 1, "", "SVDModeProject"]], "draco.analysis.fgfilter.KLModeProject": [[3, 3, 1, "", "klname"], [3, 3, 1, "", "mode"], [3, 2, 1, "", "setup"], [3, 3, 1, "", "threshold"]], "draco.analysis.fgfilter.SVDModeProject": [[3, 2, 1, "", "setup"]], "draco.analysis.flagging": [[4, 1, 1, "", "ApplyBaselineMask"], [4, 3, 1, "", "ApplyRFIMask"], [4, 1, 1, "", "ApplyTimeFreqMask"], [4, 1, 1, "", "BlendStack"], [4, 1, 1, "", "CollapseBaselineMask"], [4, 1, 1, "", "DayMask"], [4, 1, 1, "", "FindBeamformedOutliers"], [4, 1, 1, "", "MaskBadGains"], [4, 1, 1, "", "MaskBaselines"], [4, 1, 1, "", "MaskBeamformedOutliers"], [4, 1, 1, "", "MaskBeamformedWeights"], [4, 3, 1, "", "MaskData"], [4, 1, 1, "", "MaskFreq"], [4, 1, 1, "", "MaskMModeData"], [4, 1, 1, "", "RFIMask"], [4, 1, 1, "", "RFISensitivityMask"], [4, 1, 1, "", "RadiometerWeight"], [4, 1, 1, "", "SanitizeWeights"], [4, 1, 1, "", "SmoothVisWeight"], [4, 1, 1, "", "ThresholdVisWeightBaseline"], [4, 1, 1, "", "ThresholdVisWeightFrequency"], [4, 4, 1, "", "complex_med"], [4, 4, 1, "", "destripe"], [4, 4, 1, "", "inverse_binom_cdf_prob"], [4, 4, 1, "", "mad"], [4, 4, 1, "", "medfilt"], [4, 4, 1, "", "p_to_sigma"], [4, 4, 1, "", "sigma_to_p"], [4, 4, 1, "", "tv_channels_flag"]], "draco.analysis.flagging.ApplyBaselineMask": [[4, 2, 1, "", "process"], [4, 3, 1, "", "share"]], "draco.analysis.flagging.ApplyTimeFreqMask": [[4, 2, 1, "", "process"], [4, 3, 1, "", "share"]], "draco.analysis.flagging.BlendStack": [[4, 3, 1, "", "frac"], [4, 3, 1, "", "match_median"], [4, 2, 1, "", "process"], [4, 2, 1, "", "setup"], [4, 3, 1, "", "subtract"]], "draco.analysis.flagging.CollapseBaselineMask": [[4, 2, 1, "", "process"]], "draco.analysis.flagging.DayMask": [[4, 2, 1, "", "process"], [4, 3, 1, "", "remove_average"], [4, 3, 1, "", "width"], [4, 3, 1, "", "zero_data"]], "draco.analysis.flagging.FindBeamformedOutliers": [[4, 3, 1, "", "nsigma"], [4, 2, 1, "", "process"], [4, 3, 1, "", "window"]], "draco.analysis.flagging.MaskBadGains": [[4, 2, 1, "", "process"]], "draco.analysis.flagging.MaskBaselines": [[4, 3, 1, "", "mask_long_ns"], [4, 3, 1, "", "mask_short"], [4, 3, 1, "", "mask_short_ew"], [4, 3, 1, "", "mask_short_ns"], [4, 3, 1, "", "missing_threshold"], [4, 2, 1, "", "process"], [4, 2, 1, "", "setup"], [4, 3, 1, "", "share"], [4, 3, 1, "", "zero_data"]], "draco.analysis.flagging.MaskBeamformedOutliers": [[4, 2, 1, "", "process"]], "draco.analysis.flagging.MaskBeamformedWeights": [[4, 3, 1, "", "nmed"], [4, 2, 1, "", "process"]], "draco.analysis.flagging.MaskFreq": [[4, 3, 1, "", "all_time"], [4, 3, 1, "", "bad_freq_ind"], [4, 3, 1, "", "factorize"], [4, 3, 1, "", "mask_missing_data"], [4, 2, 1, "", "process"]], "draco.analysis.flagging.MaskMModeData": [[4, 3, 1, "", "auto_correlations"], [4, 3, 1, "", "m_zero"], [4, 3, 1, "", "mask_low_m"], [4, 3, 1, "", "negative_m"], [4, 3, 1, "", "positive_m"], [4, 2, 1, "", "process"]], "draco.analysis.flagging.RFIMask": [[4, 2, 1, "", "process"], [4, 3, 1, "", "sigma"], [4, 3, 1, "", "stack_ind"], [4, 3, 1, "", "tv_fraction"]], "draco.analysis.flagging.RFISensitivityMask": [[4, 3, 1, "", "include_pol"], [4, 3, 1, "", "mask_type"], [4, 3, 1, "", "max_m"], [4, 2, 1, "", "process"], [4, 3, 1, "", "remove_median"], [4, 3, 1, "", "sigma"], [4, 3, 1, "", "sir"], [4, 3, 1, "", "start_threshold_sigma"], [4, 3, 1, "", "tv_base_size"], [4, 3, 1, "", "tv_fraction"], [4, 3, 1, "", "tv_mad_size"]], "draco.analysis.flagging.RadiometerWeight": [[4, 2, 1, "", "process"], [4, 3, 1, "", "replace"]], "draco.analysis.flagging.SanitizeWeights": [[4, 3, 1, "", "max_thresh"], [4, 3, 1, "", "min_thresh"], [4, 2, 1, "", "process"], [4, 2, 1, "", "setup"]], "draco.analysis.flagging.SmoothVisWeight": [[4, 3, 1, "", "kernel_size"], [4, 3, 1, "", "mask_zeros"], [4, 2, 1, "", "process"]], "draco.analysis.flagging.ThresholdVisWeightBaseline": [[4, 2, 1, "", "process"], [4, 2, 1, "", "setup"]], "draco.analysis.flagging.ThresholdVisWeightFrequency": [[4, 2, 1, "", "process"]], "draco.analysis.mapmaker": [[5, 1, 1, "", "BaseMapMaker"], [5, 1, 1, "", "DirtyMapMaker"], [5, 1, 1, "", "MaximumLikelihoodMapMaker"], [5, 1, 1, "", "WienerMapMaker"], [5, 4, 1, "", "pinv_svd"]], "draco.analysis.mapmaker.BaseMapMaker": [[5, 3, 1, "", "nside"], [5, 2, 1, "", "process"], [5, 2, 1, "", "setup"]], "draco.analysis.mapmaker.WienerMapMaker": [[5, 3, 1, "", "prior_amp"], [5, 3, 1, "", "prior_tilt"]], "draco.analysis.powerspectrum": [[6, 1, 1, "", "QuadraticPSEstimation"]], "draco.analysis.powerspectrum.QuadraticPSEstimation": [[6, 2, 1, "", "process"], [6, 3, 1, "", "psname"], [6, 3, 1, "", "pstype"], [6, 2, 1, "", "setup"]], "draco.analysis.sensitivity": [[7, 1, 1, "", "ComputeSystemSensitivity"]], "draco.analysis.sensitivity.ComputeSystemSensitivity": [[7, 2, 1, "", "process"], [7, 2, 1, "", "setup"]], "draco.analysis.sidereal": [[8, 1, 1, "", "SiderealGrouper"], [8, 1, 1, "", "SiderealRegridder"], [8, 1, 1, "", "SiderealRegridderCubic"], [8, 1, 1, "", "SiderealRegridderLinear"], [8, 1, 1, "", "SiderealRegridderNearest"], [8, 1, 1, "", "SiderealStacker"], [8, 1, 1, "", "SiderealStackerMatch"]], "draco.analysis.sidereal.SiderealGrouper": [[8, 3, 1, "", "min_day_length"], [8, 3, 1, "", "offset"], [8, 3, 1, "", "padding"], [8, 2, 1, "", "process"], [8, 2, 1, "", "process_finish"], [8, 2, 1, "", "setup"]], "draco.analysis.sidereal.SiderealRegridder": [[8, 3, 1, "", "down_mix"], [8, 3, 1, "", "lanczos_width"], [8, 2, 1, "", "process"], [8, 3, 1, "", "samples"], [8, 2, 1, "", "setup"], [8, 3, 1, "", "snr_cov"]], "draco.analysis.sidereal.SiderealStacker": [[8, 2, 1, "", "process"], [8, 2, 1, "", "process_finish"], [8, 3, 1, "", "tag"], [8, 3, 1, "", "weight"], [8, 3, 1, "", "with_sample_variance"]], "draco.analysis.sidereal.SiderealStackerMatch": [[8, 2, 1, "", "process"], [8, 2, 1, "", "process_finish"]], "draco.analysis.sourcestack": [[9, 1, 1, "", "GroupSourceStacks"], [9, 1, 1, "", "RandomSubset"], [9, 1, 1, "", "SourceStack"]], "draco.analysis.sourcestack.GroupSourceStacks": [[9, 3, 1, "", "ngroup"], [9, 2, 1, "", "process"], [9, 2, 1, "", "process_finish"], [9, 2, 1, "", "setup"]], "draco.analysis.sourcestack.RandomSubset": [[9, 3, 1, "", "number"], [9, 2, 1, "", "process"], [9, 2, 1, "", "setup"], [9, 3, 1, "", "size"]], "draco.analysis.sourcestack.SourceStack": [[9, 3, 1, "", "freqside"], [9, 2, 1, "", "process"], [9, 3, 1, "", "single_source_bin_index"]], "draco.analysis.svdfilter": [[10, 1, 1, "", "SVDFilter"], [10, 1, 1, "", "SVDSpectrumEstimator"], [10, 4, 1, "", "svd_em"]], "draco.analysis.svdfilter.SVDFilter": [[10, 3, 1, "", "global_threshold"], [10, 3, 1, "", "local_threshold"], [10, 3, 1, "", "niter"], [10, 2, 1, "", "process"]], "draco.analysis.svdfilter.SVDSpectrumEstimator": [[10, 3, 1, "", "niter"], [10, 2, 1, "", "process"]], "draco.analysis.transform": [[11, 1, 1, "", "CollateProducts"], [11, 1, 1, "", "Downselect"], [11, 1, 1, "", "FrequencyRebin"], [11, 1, 1, "", "HPFTimeStream"], [11, 1, 1, "", "MModeInverseTransform"], [11, 1, 1, "", "MModeTransform"], [11, 1, 1, "", "MixData"], [11, 1, 1, "", "ReduceBase"], [11, 1, 1, "", "ReduceVar"], [11, 1, 1, "", "Regridder"], [11, 1, 1, "", "SelectFreq"], [11, 1, 1, "", "SelectPol"], [11, 1, 1, "", "ShiftRA"], [11, 1, 1, "", "SiderealMModeResample"], [11, 1, 1, "", "TransformJanskyToKelvin"]], "draco.analysis.transform.CollateProducts": [[11, 2, 1, "", "process"], [11, 2, 1, "", "setup"]], "draco.analysis.transform.Downselect": [[11, 2, 1, "", "process"]], "draco.analysis.transform.FrequencyRebin": [[11, 2, 1, "", "process"]], "draco.analysis.transform.HPFTimeStream": [[11, 2, 1, "", "process"]], "draco.analysis.transform.MModeInverseTransform": [[11, 3, 1, "", "apply_integration_window"], [11, 3, 1, "", "nra"], [11, 2, 1, "", "process"]], "draco.analysis.transform.MModeTransform": [[11, 2, 1, "", "process"], [11, 3, 1, "", "remove_integration_window"], [11, 2, 1, "", "setup"]], "draco.analysis.transform.MixData": [[11, 3, 1, "", "data_coeff"], [11, 2, 1, "", "process"], [11, 2, 1, "", "process_finish"], [11, 2, 1, "", "setup"], [11, 3, 1, "", "weight_coeff"]], "draco.analysis.transform.ReduceBase": [[11, 3, 1, "", "axes"], [11, 3, 1, "", "dataset"], [11, 2, 1, "", "process"], [11, 2, 1, "", "reduction"], [11, 3, 1, "", "weighting"]], "draco.analysis.transform.ReduceVar": [[11, 2, 1, "", "reduction"]], "draco.analysis.transform.Regridder": [[11, 3, 1, "", "end"], [11, 3, 1, "", "lanczos_width"], [11, 3, 1, "", "mask_zero_weight"], [11, 2, 1, "", "process"], [11, 3, 1, "", "samples"], [11, 2, 1, "", "setup"], [11, 3, 1, "", "snr_cov"], [11, 3, 1, "", "start"]], "draco.analysis.transform.SelectFreq": [[11, 3, 1, "", "channel_index"], [11, 3, 1, "", "channel_range"], [11, 3, 1, "", "freq_physical"], [11, 3, 1, "", "freq_physical_range"], [11, 2, 1, "", "process"]], "draco.analysis.transform.SelectPol": [[11, 3, 1, "", "pol"], [11, 2, 1, "", "process"]], "draco.analysis.transform.ShiftRA": [[11, 2, 1, "", "process"]], "draco.analysis.transform.SiderealMModeResample": [[11, 3, 1, "", "nra"]], "draco.analysis.transform.TransformJanskyToKelvin": [[11, 3, 1, "", "convert_Jy_to_K"], [11, 3, 1, "", "nside"], [11, 2, 1, "", "process"], [11, 3, 1, "", "reference_declination"], [11, 2, 1, "", "setup"], [11, 3, 1, "", "share"]], "draco.core": [[13, 0, 0, "-", "containers"], [14, 0, 0, "-", "io"], [15, 0, 0, "-", "misc"], [16, 0, 0, "-", "task"]], "draco.core.containers": [[13, 1, 1, "", "BaselineMask"], [13, 1, 1, "", "CommonModeGainData"], [13, 1, 1, "", "CommonModeSiderealGainData"], [13, 1, 1, "", "ContainerBase"], [13, 1, 1, "", "DataWeightContainer"], [13, 1, 1, "", "DelayContainer"], [13, 1, 1, "", "DelayCrossSpectrum"], [13, 1, 1, "", "DelayCutoff"], [13, 1, 1, "", "DelaySpectrum"], [13, 1, 1, "", "DelayTransform"], [13, 1, 1, "", "FormedBeam"], [13, 1, 1, "", "FormedBeamHA"], [13, 1, 1, "", "FormedBeamHAMask"], [13, 1, 1, "", "FormedBeamMask"], [13, 1, 1, "", "FreqContainer"], [13, 1, 1, "", "FrequencyStack"], [13, 1, 1, "", "FrequencyStackByPol"], [13, 1, 1, "", "GainData"], [13, 1, 1, "", "GainDataBase"], [13, 1, 1, "", "GridBeam"], [13, 1, 1, "", "HEALPixBeam"], [13, 1, 1, "", "HealpixContainer"], [13, 1, 1, "", "HybridVisMModes"], [13, 1, 1, "", "HybridVisStream"], [13, 1, 1, "", "KLModes"], [13, 1, 1, "", "MContainer"], [13, 1, 1, "", "MModes"], [13, 1, 1, "", "Map"], [13, 1, 1, "", "MockFrequencyStack"], [13, 1, 1, "", "MockFrequencyStackByPol"], [13, 1, 1, "", "Powerspectrum2D"], [13, 1, 1, "", "RFIMask"], [13, 1, 1, "", "RingMap"], [13, 1, 1, "", "RingMapMask"], [13, 1, 1, "", "SVDModes"], [13, 1, 1, "", "SVDSpectrum"], [13, 1, 1, "", "SampleVarianceContainer"], [13, 1, 1, "", "SiderealBaselineMask"], [13, 1, 1, "", "SiderealContainer"], [13, 1, 1, "", "SiderealGainData"], [13, 1, 1, "", "SiderealRFIMask"], [13, 1, 1, "", "SiderealStream"], [13, 1, 1, "", "SourceCatalog"], [13, 1, 1, "", "SpectroscopicCatalog"], [13, 1, 1, "", "Stack3D"], [13, 1, 1, "", "StaticGainData"], [13, 1, 1, "", "SystemSensitivity"], [13, 1, 1, "", "TODContainer"], [13, 1, 1, "", "TableBase"], [13, 1, 1, "", "TimeStream"], [13, 1, 1, "", "TrackBeam"], [13, 1, 1, "", "VisBase"], [13, 1, 1, "", "VisContainer"], [13, 1, 1, "", "VisGridStream"], [13, 1, 1, "", "WaveletSpectrum"], [13, 4, 1, "", "copy_datasets_filter"], [13, 4, 1, "", "empty_like"], [13, 4, 1, "", "empty_timestream"]], "draco.core.containers.BaselineMask": [[13, 5, 1, "", "mask"], [13, 5, 1, "", "stack"]], "draco.core.containers.ContainerBase": [[13, 2, 1, "", "add_dataset"], [13, 5, 1, "", "axes"], [13, 2, 1, "", "copy"], [13, 5, 1, "", "dataset_spec"], [13, 5, 1, "", "datasets"]], "draco.core.containers.DataWeightContainer": [[13, 5, 1, "", "data"], [13, 5, 1, "", "weight"]], "draco.core.containers.DelayContainer": [[13, 5, 1, "", "delay"]], "draco.core.containers.DelayCrossSpectrum": [[13, 5, 1, "", "spectrum"]], "draco.core.containers.DelayCutoff": [[13, 5, 1, "", "cutoff"], [13, 5, 1, "", "el"], [13, 5, 1, "", "pol"]], "draco.core.containers.DelaySpectrum": [[13, 5, 1, "", "freq"], [13, 5, 1, "", "spectrum"], [13, 5, 1, "", "weight_boost"]], "draco.core.containers.DelayTransform": [[13, 5, 1, "", "freq"], [13, 5, 1, "", "spectrum"], [13, 5, 1, "", "weight_boost"]], "draco.core.containers.FormedBeam": [[13, 5, 1, "", "beam"], [13, 5, 1, "", "frequency"], [13, 5, 1, "", "id"], [13, 5, 1, "", "pol"]], "draco.core.containers.FormedBeamHA": [[13, 5, 1, "", "ha"]], "draco.core.containers.FormedBeamMask": [[13, 5, 1, "", "mask"]], "draco.core.containers.FreqContainer": [[13, 5, 1, "", "freq"]], "draco.core.containers.FrequencyStack": [[13, 5, 1, "", "stack"]], "draco.core.containers.FrequencyStackByPol": [[13, 5, 1, "", "pol"]], "draco.core.containers.GainData": [[13, 5, 1, "", "input"], [13, 5, 1, "", "update_id"]], "draco.core.containers.GainDataBase": [[13, 5, 1, "", "gain"], [13, 5, 1, "", "weight"]], "draco.core.containers.GridBeam": [[13, 5, 1, "", "beam"], [13, 5, 1, "", "coords"], [13, 5, 1, "", "gain"], [13, 5, 1, "", "input"], [13, 5, 1, "", "phi"], [13, 5, 1, "", "pol"], [13, 5, 1, "", "quality"], [13, 5, 1, "", "theta"]], "draco.core.containers.HEALPixBeam": [[13, 5, 1, "", "beam"], [13, 5, 1, "", "coords"], [13, 5, 1, "", "input"], [13, 5, 1, "", "nside"], [13, 5, 1, "", "ordering"], [13, 5, 1, "", "pol"]], "draco.core.containers.HealpixContainer": [[13, 5, 1, "", "nside"]], "draco.core.containers.HybridVisStream": [[13, 5, 1, "", "dirty_beam"]], "draco.core.containers.MContainer": [[13, 5, 1, "", "mmax"], [13, 5, 1, "", "oddra"]], "draco.core.containers.MModes": [[13, 3, 1, "", "vis"], [13, 3, 1, "", "weight"]], "draco.core.containers.Map": [[13, 5, 1, "", "map"]], "draco.core.containers.Powerspectrum2D": [[13, 5, 1, "", "C_inv"], [13, 5, 1, "", "powerspectrum"]], "draco.core.containers.RFIMask": [[13, 5, 1, "", "mask"]], "draco.core.containers.RingMap": [[13, 5, 1, "", "dirty_beam"], [13, 5, 1, "", "el"], [13, 5, 1, "", "map"], [13, 5, 1, "", "pol"], [13, 5, 1, "", "rms"]], "draco.core.containers.RingMapMask": [[13, 5, 1, "", "mask"]], "draco.core.containers.SVDModes": [[13, 5, 1, "", "nmode"]], "draco.core.containers.SVDSpectrum": [[13, 5, 1, "", "spectrum"]], "draco.core.containers.SampleVarianceContainer": [[13, 5, 1, "", "component"], [13, 5, 1, "", "nsample"], [13, 5, 1, "", "sample_variance"], [13, 5, 1, "", "sample_variance_amp_phase"], [13, 5, 1, "", "sample_variance_iq"], [13, 5, 1, "", "sample_weight"]], "draco.core.containers.SiderealBaselineMask": [[13, 5, 1, "", "mask"], [13, 5, 1, "", "stack"]], "draco.core.containers.SiderealContainer": [[13, 5, 1, "", "ra"]], "draco.core.containers.SiderealGainData": [[13, 5, 1, "", "input"]], "draco.core.containers.SiderealRFIMask": [[13, 5, 1, "", "mask"]], "draco.core.containers.SiderealStream": [[13, 5, 1, "", "gain"], [13, 5, 1, "", "input_flags"]], "draco.core.containers.Stack3D": [[13, 5, 1, "", "stack"]], "draco.core.containers.StaticGainData": [[13, 5, 1, "", "input"]], "draco.core.containers.SystemSensitivity": [[13, 5, 1, "", "frac_lost"], [13, 5, 1, "", "measured"], [13, 5, 1, "", "pol"], [13, 5, 1, "", "radiometer"], [13, 5, 1, "", "weight"]], "draco.core.containers.TableBase": [[13, 5, 1, "", "table_spec"]], "draco.core.containers.TimeStream": [[13, 5, 1, "", "gain"], [13, 5, 1, "", "input_flags"]], "draco.core.containers.TrackBeam": [[13, 5, 1, "", "beam"], [13, 5, 1, "", "coords"], [13, 5, 1, "", "gain"], [13, 5, 1, "", "input"], [13, 5, 1, "", "pix"], [13, 5, 1, "", "pol"], [13, 5, 1, "", "track_type"]], "draco.core.containers.VisBase": [[13, 5, 1, "", "vis"]], "draco.core.containers.VisContainer": [[13, 5, 1, "", "input"], [13, 5, 1, "", "is_stacked"], [13, 5, 1, "", "prod"], [13, 5, 1, "", "prodstack"], [13, 5, 1, "", "stack"]], "draco.core.containers.VisGridStream": [[13, 5, 1, "", "redundancy"]], "draco.core.containers.WaveletSpectrum": [[13, 5, 1, "", "spectrum"]], "draco.core.io": [[14, 1, 1, "", "BaseLoadFiles"], [14, 1, 1, "", "FindFiles"], [14, 3, 1, "", "LoadBasicCont"], [14, 1, 1, "", "LoadBeamTransfer"], [14, 1, 1, "", "LoadFITSCatalog"], [14, 1, 1, "", "LoadFiles"], [14, 1, 1, "", "LoadFilesFromParams"], [14, 1, 1, "", "LoadMaps"], [14, 1, 1, "", "LoadProductManager"], [14, 1, 1, "", "Print"], [14, 1, 1, "", "Save"], [14, 1, 1, "", "SaveConfig"], [14, 1, 1, "", "SaveModuleVersions"], [14, 1, 1, "", "SaveZarrZip"], [14, 1, 1, "", "SelectionsMixin"], [14, 1, 1, "", "Truncate"], [14, 1, 1, "", "WaitZarrZip"], [14, 1, 1, "", "ZarrZipHandle"], [14, 1, 1, "", "ZipZarrContainers"], [14, 4, 1, "", "get_beamtransfer"], [14, 4, 1, "", "get_telescope"]], "draco.core.io.BaseLoadFiles": [[14, 3, 1, "", "convert_strings"], [14, 3, 1, "", "distributed"], [14, 3, 1, "", "redistribute"]], "draco.core.io.FindFiles": [[14, 2, 1, "", "setup"]], "draco.core.io.LoadBeamTransfer": [[14, 3, 1, "", "product_directory"], [14, 2, 1, "", "setup"]], "draco.core.io.LoadFITSCatalog": [[14, 3, 1, "", "catalogs"], [14, 3, 1, "", "freq_range"], [14, 2, 1, "", "process"], [14, 3, 1, "", "z_range"]], "draco.core.io.LoadFiles": [[14, 2, 1, "", "setup"]], "draco.core.io.LoadFilesFromParams": [[14, 3, 1, "", "files"], [14, 2, 1, "", "process"]], "draco.core.io.LoadMaps": [[14, 3, 1, "", "maps"], [14, 2, 1, "", "next"]], "draco.core.io.LoadProductManager": [[14, 3, 1, "", "product_directory"], [14, 2, 1, "", "setup"]], "draco.core.io.Print": [[14, 2, 1, "", "next"]], "draco.core.io.Save": [[14, 2, 1, "", "next"], [14, 3, 1, "", "root"]], "draco.core.io.SaveConfig": [[14, 2, 1, "", "process"], [14, 3, 1, "", "root"], [14, 2, 1, "", "setup"]], "draco.core.io.SaveModuleVersions": [[14, 2, 1, "", "process"], [14, 3, 1, "", "root"], [14, 2, 1, "", "setup"]], "draco.core.io.SaveZarrZip": [[14, 2, 1, "", "next"], [14, 2, 1, "", "setup"]], "draco.core.io.SelectionsMixin": [[14, 3, 1, "", "Selections"], [14, 3, 1, "", "selections"], [14, 2, 1, "", "setup"]], "draco.core.io.Truncate": [[14, 3, 1, "", "dataset"], [14, 3, 1, "", "ensure_chunked"], [14, 2, 1, "", "process"]], "draco.core.io.WaitZarrZip": [[14, 2, 1, "", "finish"], [14, 2, 1, "", "next"]], "draco.core.io.ZipZarrContainers": [[14, 3, 1, "", "containers"], [14, 2, 1, "", "process"], [14, 3, 1, "", "remove"], [14, 2, 1, "", "setup"]], "draco.core.misc": [[15, 1, 1, "", "AccumulateList"], [15, 1, 1, "", "ApplyGain"], [15, 1, 1, "", "CheckMPIEnvironment"], [15, 1, 1, "", "MakeCopy"], [15, 1, 1, "", "PassOn"], [15, 1, 1, "", "WaitUntil"]], "draco.core.misc.AccumulateList": [[15, 2, 1, "", "finish"], [15, 2, 1, "", "next"]], "draco.core.misc.ApplyGain": [[15, 3, 1, "", "inverse"], [15, 2, 1, "", "process"], [15, 3, 1, "", "smoothing_length"], [15, 3, 1, "", "update_weight"]], "draco.core.misc.CheckMPIEnvironment": [[15, 2, 1, "", "setup"]], "draco.core.misc.MakeCopy": [[15, 2, 1, "", "process"]], "draco.core.misc.PassOn": [[15, 2, 1, "", "next"]], "draco.core.misc.WaitUntil": [[15, 2, 1, "", "next"], [15, 2, 1, "", "setup"]], "draco.core.task": [[16, 1, 1, "", "Delete"], [16, 1, 1, "", "LoggedTask"], [16, 1, 1, "", "MPILogFilter"], [16, 1, 1, "", "MPILoggedTask"], [16, 1, 1, "", "MPITask"], [16, 1, 1, "", "ReturnFirstInputOnFinish"], [16, 1, 1, "", "ReturnLastInputOnFinish"], [16, 1, 1, "", "SetMPILogging"], [16, 1, 1, "", "SingleTask"], [16, 4, 1, "", "group_tasks"]], "draco.core.task.Delete": [[16, 2, 1, "", "process"]], "draco.core.task.LoggedTask": [[16, 5, 1, "", "log"]], "draco.core.task.MPILogFilter": [[16, 2, 1, "", "filter"]], "draco.core.task.ReturnFirstInputOnFinish": [[16, 2, 1, "", "process"], [16, 2, 1, "", "process_finish"]], "draco.core.task.ReturnLastInputOnFinish": [[16, 2, 1, "", "process"], [16, 2, 1, "", "process_finish"]], "draco.core.task.SingleTask": [[16, 3, 1, "", "attrs"], [16, 3, 1, "", "compression"], [16, 2, 1, "", "finish"], [16, 3, 1, "", "nan_check"], [16, 3, 1, "", "nan_dump"], [16, 3, 1, "", "nan_skip"], [16, 2, 1, "", "next"], [16, 3, 1, "", "output_name"], [16, 3, 1, "", "output_root"], [16, 3, 1, "", "pipeline_config"], [16, 3, 1, "", "save"], [16, 3, 1, "", "tag"], [16, 3, 1, "", "versions"]], "draco.synthesis": [[18, 0, 0, "-", "gain"], [19, 0, 0, "-", "noise"], [20, 0, 0, "-", "stream"]], "draco.synthesis.gain": [[18, 1, 1, "", "BaseGains"], [18, 1, 1, "", "GainStacker"], [18, 1, 1, "", "RandomGains"], [18, 1, 1, "", "RandomSiderealGains"], [18, 1, 1, "", "SiderealGains"], [18, 4, 1, "", "constrained_gaussian_realisation"], [18, 4, 1, "", "gaussian_realisation"], [18, 4, 1, "", "generate_fluctuations"]], "draco.synthesis.gain.BaseGains": [[18, 3, 1, "", "amp"], [18, 3, 1, "", "phase"], [18, 2, 1, "", "process"]], "draco.synthesis.gain.GainStacker": [[18, 3, 1, "", "only_gains"], [18, 2, 1, "", "process"], [18, 2, 1, "", "process_finish"], [18, 2, 1, "", "setup"]], "draco.synthesis.gain.SiderealGains": [[18, 2, 1, "", "process"], [18, 2, 1, "", "setup"]], "draco.synthesis.noise": [[19, 1, 1, "", "GaussianNoise"], [19, 1, 1, "", "GaussianNoiseDataset"], [19, 1, 1, "", "ReceiverTemperature"], [19, 1, 1, "", "SampleNoise"]], "draco.synthesis.noise.GaussianNoise": [[19, 3, 1, "", "add_noise"], [19, 3, 1, "", "ndays"], [19, 2, 1, "", "process"], [19, 3, 1, "", "recv_temp"], [19, 3, 1, "", "set_weights"], [19, 2, 1, "", "setup"]], "draco.synthesis.noise.GaussianNoiseDataset": [[19, 3, 1, "", "dataset"], [19, 2, 1, "", "process"]], "draco.synthesis.noise.ReceiverTemperature": [[19, 2, 1, "", "process"], [19, 3, 1, "", "recv_temp"]], "draco.synthesis.noise.SampleNoise": [[19, 2, 1, "", "process"], [19, 3, 1, "", "sample_frac"], [19, 3, 1, "", "set_weights"]], "draco.synthesis.stream": [[20, 1, 1, "", "ExpandProducts"], [20, 1, 1, "", "MakeSiderealDayStream"], [20, 1, 1, "", "MakeTimeStream"], [20, 1, 1, "", "SimulateSidereal"]], "draco.synthesis.stream.ExpandProducts": [[20, 2, 1, "", "process"], [20, 2, 1, "", "setup"]], "draco.synthesis.stream.MakeSiderealDayStream": [[20, 2, 1, "", "process"], [20, 2, 1, "", "setup"]], "draco.synthesis.stream.MakeTimeStream": [[20, 2, 1, "", "process"], [20, 2, 1, "", "setup"]], "draco.synthesis.stream.SimulateSidereal": [[20, 2, 1, "", "process"], [20, 2, 1, "", "setup"], [20, 3, 1, "", "stacked"]], "draco.util": [[22, 0, 0, "-", "exception"], [23, 0, 0, "-", "random"], [24, 0, 0, "-", "regrid"], [25, 0, 0, "-", "rfi"], [26, 0, 0, "-", "testing"], [27, 0, 0, "-", "tools"], [28, 0, 0, "-", "truncate"]], "draco.util.exception": [[22, 6, 1, "", "ConfigError"]], "draco.util.random": [[23, 1, 1, "", "MultithreadedRNG"], [23, 1, 1, "", "RandomTask"], [23, 4, 1, "", "complex_normal"], [23, 4, 1, "", "complex_wishart"], [23, 4, 1, "", "default_rng"], [23, 4, 1, "", "mpi_random_seed"], [23, 4, 1, "", "standard_complex_normal"], [23, 4, 1, "", "standard_complex_wishart"]], "draco.util.random.RandomTask": [[23, 5, 1, "", "local_seed"], [23, 5, 1, "", "rng"], [23, 3, 1, "", "seed"], [23, 3, 1, "", "threads"]], "draco.util.regrid": [[24, 4, 1, "", "band_wiener"], [24, 4, 1, "", "lanczos_forward_matrix"], [24, 4, 1, "", "lanczos_inverse_matrix"], [24, 4, 1, "", "lanczos_kernel"]], "draco.util.rfi": [[25, 4, 1, "", "sir"], [25, 4, 1, "", "sir1d"], [25, 4, 1, "", "sumthreshold"], [25, 4, 1, "", "sumthreshold_py"]], "draco.util.testing": [[26, 1, 1, "", "DummyTask"], [26, 1, 1, "", "RandomFreqData"], [26, 4, 1, "", "mock_freq_data"]], "draco.util.testing.DummyTask": [[26, 2, 1, "", "process"], [26, 3, 1, "", "tag"], [26, 3, 1, "", "total_len"]], "draco.util.testing.RandomFreqData": [[26, 3, 1, "", "delay_cut"], [26, 2, 1, "", "next"], [26, 3, 1, "", "noise"], [26, 3, 1, "", "num_base"], [26, 3, 1, "", "num_correlated"], [26, 3, 1, "", "num_freq"], [26, 3, 1, "", "num_ra"], [26, 3, 1, "", "num_realisation"]], "draco.util.tools": [[27, 4, 1, "", "apply_gain"], [27, 4, 1, "", "baseline_vector"], [27, 4, 1, "", "calculate_redundancy"], [27, 4, 1, "", "cmap"], [27, 4, 1, "", "extract_diagonal"], [27, 4, 1, "", "find_inputs"], [27, 4, 1, "", "find_key"], [27, 4, 1, "", "find_keys"], [27, 4, 1, "", "icmap"], [27, 4, 1, "", "polarization_map"], [27, 4, 1, "", "redefine_stack_index_map"], [27, 4, 1, "", "window_generalised"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "method", "Python method"], "3": ["py", "attribute", "Python attribute"], "4": ["py", "function", "Python function"], "5": ["py", "property", "Python property"], "6": ["py", "exception", "Python exception"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:method", "3": "py:attribute", "4": "py:function", "5": "py:property", "6": "py:exception"}, "terms": {"": [1, 2, 4, 5, 8, 13, 14, 15, 16, 19, 29, 32], "0": [2, 4, 5, 8, 11, 13, 16, 23, 24, 25, 26, 27, 29, 32], "0001": 5, "001": 5, "010": 19, "01242": 2, "08": 2, "1": [2, 4, 5, 13, 14, 18, 19, 23, 24, 25, 26, 27], "10": [2, 4, 8, 19, 32], "1007": 19, "100x": 4, "11": 4, "1145": 8, "12": 18, "1201": 25, "128": 32, "14": 14, "16": [14, 25, 32], "173": 24, "180": 8, "1979": 8, "1d": 25, "1e": [2, 4, 18], "2": [1, 2, 4, 5, 11, 13, 14, 18, 20, 25, 27, 32], "20": [2, 16], "200": 2, "21": 4, "21cm": [14, 32], "2202": 2, "24": 8, "240": 8, "25": 14, "256": [11, 14], "2d": [2, 13, 25, 26, 27], "2fs10440": 19, "3": [4, 14, 32], "30": 16, "31": 4, "3364v2": 25, "359146": 8, "359153": 8, "36": 14, "360": 11, "3d": [2, 13, 26], "4": [4, 14, 23, 32], "400": 32, "401": 32, "4096": 23, "410": 32, "411": 32, "49": 14, "5": [4, 10, 24, 32], "50": 9, "512": 14, "56": 20, "6": 4, "64": [14, 32], "7": 4, "8": 4, "9": 14, "900": 1, "95": 25, "9599": 19, "A": [2, 4, 9, 10, 11, 13, 14, 16, 20, 23, 25, 26, 27, 30], "As": [9, 13, 14, 18, 19, 29], "At": 16, "Be": 4, "By": [2, 13, 19, 23, 25], "For": [2, 4, 9, 13, 16, 19, 25, 32], "If": [1, 2, 4, 11, 13, 14, 16, 18, 19, 20, 23, 26, 27, 32], "In": [5, 8, 13, 24, 32], "Into": 3, "It": [11, 13, 14, 25], "No": [4, 14, 32], "Not": [1, 5, 13, 15], "OR": 25, "Of": 32, "One": [4, 6, 27], "That": [2, 11, 13, 14], "The": [1, 2, 4, 5, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 25, 26, 27, 29, 30, 32], "Then": 32, "There": [2, 8, 11, 32], "These": [1, 2, 11, 13, 14, 16, 29, 32], "To": [1, 13, 32], "Will": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 23, 26], "With": [25, 32], "_": [4, 14], "__file__": 32, "_ax": 13, "_data_dset_nam": 13, "_dataset_spec": 13, "_default_bitgen": 23, "_index": [13, 14], "_mean": 13, "_projectfilterbas": 3, "_rang": 14, "_table_spec": 13, "_weight_dset_nam": 13, "a6": 2, "a_": 5, "ab": 2, "abber": 1, "abil": 13, "abl": 11, "about": [13, 14], "abov": [4, 14, 16], "absolut": 4, "absolute_threshold": 4, "accept": [11, 14, 15], "access": [13, 23, 32], "accord": [1, 4, 14, 16], "accordingli": 13, "account": [4, 8, 13, 27, 32], "accross": [4, 9], "accumul": [9, 15], "accumulatelist": 15, "acond": 5, "across": [4, 8, 9, 11, 13, 14, 15, 23, 25], "activ": [29, 32], "actual": [4, 14, 19, 23, 32], "ad": [1, 8, 11, 13, 14, 19], "add": [2, 8, 9, 11, 13, 16, 19, 26], "add_dataset": 13, "add_mpi_info": 16, "add_nois": 19, "addit": [1, 4, 13, 14, 25], "addition": [13, 14], "advis": 4, "affect": 19, "after": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 23, 25, 26], "again": 16, "against": [18, 23], "aggress": 25, "ahead": 4, "algorithm": [4, 8, 25], "alia": [2, 4, 14], "align": 2, "all": [1, 2, 4, 5, 7, 11, 13, 14, 15, 16, 23, 25, 29, 32], "all_tim": 4, "allow": [13, 15, 16, 23, 24, 27, 32], "allow_chunk": 13, "along": [2, 4, 11, 14, 27], "alreadi": [4, 7, 13, 16, 32], "also": [3, 4, 8, 11, 13, 16, 23, 27, 29, 32], "altern": [2, 14], "alwai": [13, 29], "ambigu": 13, "amount": [4, 8, 13, 15], "amp": [13, 18], "amplitud": [2, 5, 13, 18], "an": [1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 15, 16, 18, 19, 20, 22, 23, 26, 27, 29, 32], "analys": 30, "analysi": [13, 30, 32], "andata": [4, 11], "angl": [1, 2, 13], "ani": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26, 27, 29, 32], "anomal": 4, "anoth": [2, 13, 32], "another_file1": 14, "another_file2": 14, "anyth": [16, 32], "api": 30, "apodis": 2, "appar": 1, "appear": 13, "append": [14, 15], "appli": [2, 4, 11, 13, 15, 18, 24, 25, 27, 32], "applic": [4, 11, 25], "apply_gain": 27, "apply_integration_window": 11, "apply_window": 2, "applybaselinemask": 4, "applygain": 15, "applyrfimask": 4, "applytimefreqmask": 4, "approach": [8, 24], "appropri": [11, 15, 19, 20], "approxim": [10, 11, 19], "ar": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 25, 26, 27, 29, 32], "arbitrari": [11, 13, 27], "area": 11, "arg": [1, 4, 13], "argument": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26], "aros": 2, "around": [4, 8, 11, 15], "arr": 11, "arrai": [1, 2, 4, 10, 13, 14, 15, 23, 25, 26, 27], "arrang": 2, "array_lik": [2, 24], "articl": 19, "artifact": 8, "arxiv": [2, 25], "ascens": 13, "ask": 4, "assert": 24, "assign": 13, "associ": 13, "assum": [1, 2, 4, 5, 10, 13, 14, 19, 24, 32], "assumpt": 4, "astro": 25, "astronom": 13, "attach": 16, "attempt": [4, 14, 32], "attr": 16, "attribut": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26, 27, 32], "attrs_from": 13, "author": 25, "auto": [4, 19, 27], "auto_correl": 4, "autocorrel": 13, "automat": [13, 27, 29], "avail": [2, 13, 14, 32], "averag": [2, 4, 8, 13, 19, 32], "average_axi": 2, "average_typ": 4, "avoid": [4, 14], "awar": 16, "ax": [2, 4, 11, 13, 14, 25], "axes_from": 13, "axes_to_keep": 2, "axi": [2, 4, 11, 13, 14, 25, 26, 27], "b": [5, 23], "back": [2, 14, 23, 29], "background": 14, "backward": 3, "bad": [4, 13, 26, 27], "bad_freq": 26, "bad_freq_ind": 4, "band": [4, 24], "band_wien": 24, "banded": 24, "bandwidth": 24, "bao": [29, 32], "bartlett": 19, "base": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 22, 23, 26, 32], "base_s": 4, "baseclass": 16, "basegain": 18, "baselin": [1, 2, 4, 7, 11, 13, 19, 20, 26, 27, 32], "baseline_ax": 2, "baseline_mask": 4, "baseline_vector": 27, "baselinemask": [4, 13], "baseloadfil": 14, "basemapmak": 5, "basemask": 25, "basi": [1, 3, 13], "basic": [13, 19, 30], "basiccont": [13, 14, 16, 26], "basiccontmixin": 16, "bayesian": 24, "beam": [1, 2, 5, 9, 11, 13, 14, 20, 32], "beamform": [4, 7, 9, 13], "beamformbas": 1, "beamformcat": 1, "beamformextern": 1, "beamformexternalbas": 1, "beamformexternalcat": 1, "beamtransf": [1, 2, 3, 5, 11, 14, 18, 20, 32], "been": [4, 7, 9, 13, 14, 15, 20, 25], "befor": [4, 8, 14, 15, 29, 32], "behav": [8, 11, 27], "behaviour": [13, 16], "behind": 29, "being": [1, 13, 14, 23, 32], "belong": 27, "below": [2, 4, 11, 14, 29, 32], "best": [1, 2], "better": [13, 15], "between": [2, 3, 4, 13, 15, 18, 27, 32], "beyond": [1, 2], "bia": [4, 19], "bias": 4, "bin": [9, 11, 13, 29, 32], "bit": 23, "bit_gener": 23, "bitbucket": [29, 32], "bitgen": 23, "bitgener": 23, "bitshuffl": 14, "blackman": 11, "blackman_harri": [2, 27], "blackman_nuttal": [2, 27], "blend": 4, "blendstack": 4, "block": 14, "bool": [1, 2, 4, 7, 8, 10, 11, 13, 14, 15, 16, 18, 19, 20, 25, 27], "boolean": [4, 10, 16, 24, 25, 27], "boost": 13, "both": [2, 4, 11, 14, 16, 29, 32], "boundari": 13, "briefli": 32, "bright": 11, "broadcast": [4, 23], "bt": [3, 5, 14, 18, 20, 32], "bug": 11, "built": 32, "bulk": 32, "bunch": 16, "bvec_m": 27, "bw": 24, "c": [2, 13, 23], "c_": 18, "c_inv": 13, "c_samp": 23, "ca": 29, "cach": 16, "cadenc": 4, "calcul": [1, 2, 4, 6, 10, 11, 13, 23, 24, 27], "calculate_redund": 27, "call": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26], "can": [1, 2, 3, 4, 7, 8, 11, 13, 14, 15, 16, 18, 23, 27, 29, 32], "cannot": 14, "capabl": [13, 14], "caput": [1, 13, 14, 16, 29, 32], "caputconfigerror": 14, "carefulli": 32, "cartesian": 13, "casa": 4, "case": [8, 13, 14, 20, 32], "casper": 2, "cat": 16, "cat1": 16, "cat2": 16, "catalog": [1, 9, 13, 14, 16], "caus": [2, 4, 16], "caution": 13, "caveat": 1, "cd": 32, "cdf": 4, "celesti": 13, "centr": 13, "certain": [4, 15], "ch_util": 29, "chain": 2, "chan_id": 27, "chang": [4, 23, 32], "channel": [1, 2, 4, 11, 13, 26, 32], "channel_bin": 11, "channel_index": 11, "channel_rang": 11, "characterist": 27, "check": [11, 13, 14, 15, 16, 32], "checkmpienviron": 15, "children": 16, "chime": [2, 4, 11, 13, 25, 29, 30, 32], "choos": [27, 32], "chunk": [13, 14, 16], "cir": [1, 13], "clash": 32, "class": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 23, 26, 32], "clean": [4, 13], "clear": [1, 5], "clearli": [13, 14], "clone": 32, "cluster": 32, "cmap": 27, "co": [8, 18], "code": [6, 14, 29, 32], "codedoc": 29, "coeffici": [5, 11], "collaps": [2, 4, 9, 13], "collapse_ha": [1, 9], "collapsebaselinemask": 4, "collat": 20, "collateproduct": 11, "collect": [14, 16, 23, 25, 27], "column": 13, "com": [19, 29, 32], "combin": [4, 8, 11, 14, 19], "come": [2, 13, 32], "comm": [13, 16], "comm_world": 13, "command": 32, "commit": 29, "common": [4, 13], "commonmodegaindata": [13, 15], "commonmodesiderealgaindata": [13, 15], "commun": [13, 15], "compar": 13, "compat": 2, "complement": 1, "complet": [1, 14, 32], "complex": [2, 4, 13, 15, 19, 23], "complex128": 23, "complex64": 23, "complex_m": 4, "complex_norm": 23, "complex_timedomain": 2, "complex_wishart": 23, "compon": [2, 5, 10, 13], "composit": 2, "compress": [14, 16], "compression_opt": 16, "comput": [7, 8, 13, 23], "computesystemsensit": 7, "concaten": [3, 13, 14], "cond": 24, "condit": 24, "config": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 23, 26, 32], "configerror": 22, "configur": [14, 16, 22, 32], "congratul": 32, "conjug": [2, 13, 27], "connect": [15, 32], "consid": 4, "consist": [4, 8, 13, 18, 27], "constant": 26, "constitu": 2, "constrain": 18, "constrained_gaussian_realis": 18, "constraint": 32, "construct": [4, 8, 9, 16], "constructor": 13, "consult": 32, "cont": [14, 26], "contain": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 27, 29, 32], "containerbas": [4, 11, 13, 14, 15], "containt": 11, "contamin": [4, 13], "content": [13, 30], "context": 23, "continu": 1, "contribut": [8, 24], "conveni": 13, "convent": [2, 4, 13], "convers": 11, "convert": [1, 11, 14], "convert_jy_to_k": 11, "convert_str": 14, "convinc": 25, "coord": 13, "coordiant": 1, "coordin": [1, 13, 18], "copi": [1, 4, 13, 15, 20], "copol": [1, 4], "copy_datasets_filt": 13, "copy_from": 13, "cora": [29, 32], "core": [4, 20, 27, 32], "corr_length_amp": 18, "corr_length_phas": 18, "corrdata": [4, 11, 13], "correct": [8, 11, 25], "correct_for_miss": 25, "correctli": [13, 16, 32], "correl": [2, 4, 10, 11, 13, 18, 19, 26, 27], "correlator_input": 27, "correspond": [2, 4, 13, 19, 27, 32], "corrfunc": 18, "corrinput": 20, "corrupt": 15, "could": 19, "count": [14, 16, 25], "counterpart": 11, "coupl": 24, "covarainc": 23, "covari": [2, 5, 8, 11, 13, 18, 23, 24], "cover": 4, "coverag": 8, "crappi": 4, "creat": [4, 9, 13, 16, 20, 23, 27, 32], "create_dataset": 13, "cross": [2, 4, 8, 13], "crude": [4, 11], "cubic": 8, "cumul": 4, "current": [8, 9, 11, 14, 15], "custom": [13, 32], "cut": [2, 10, 26], "cutoff": [13, 26], "cycl": [19, 26, 32], "cyga": 4, "cylind": [4, 7, 32], "cylinder_width": 32, "cylindr": [2, 4], "d": [8, 13], "dagger": 5, "dai": [4, 8, 13, 18, 20, 32], "data": [1, 2, 3, 4, 5, 6, 7, 8, 11, 13, 14, 15, 18, 19, 20, 24, 25, 26, 29, 30, 32], "data1": 2, "data2": 2, "data_blend": 4, "data_coeff": 11, "data_exp": 19, "data_group": 13, "data_nois": 19, "data_samp": 19, "data_stack": 4, "databas": 32, "datain": 4, "dataset": [1, 2, 4, 8, 11, 13, 14, 16, 19, 20, 25, 26], "dataset_spec": [13, 14], "datatyp": [13, 23], "dataweightcontain": [2, 11, 13], "datetim": [18, 20], "daughter": 1, "daymask": 4, "daytim": 4, "dc": 2, "deal": [4, 13], "debug": [4, 14], "dec": [1, 13], "dec_cir": 1, "declin": [1, 11], "decomposit": 19, "deconvolv": 11, "deconvolvehybridm": 1, "default": [1, 2, 4, 7, 8, 9, 11, 13, 14, 16, 18, 19, 20, 23, 25, 26, 27, 32], "default_rng": 23, "defin": [1, 13, 14], "definit": [13, 32], "deflect": 1, "deg": 11, "degre": [1, 3, 11, 13], "delai": [13, 14, 15, 26], "delay_cut": [2, 26], "delay_p": 2, "delay_power_spectrum_gibb": 2, "delay_spectrum_gibb": 2, "delay_spectrum_gibbs_cross": 2, "delay_spectrum_wiener_filt": 2, "delaycontain": 13, "delaycrosspowerspectrumestim": 2, "delaycrossspectrum": [2, 13], "delaycut": 26, "delaycutoff": 13, "delayfilt": 2, "delayfilterbas": 2, "delaygeneralcontainerbas": 2, "delaygibbssamplerbas": 2, "delaypowerspectrumgeneralestim": 2, "delaypowerspectrumstokesiestim": 2, "delayspectrum": [2, 13], "delayspectrumestim": [2, 4], "delayspectrumestimatorbas": 2, "delayspectrumwienerbas": 2, "delayspectrumwienerestim": 2, "delaytransform": [2, 13], "delaytransformbas": 2, "delet": 16, "delta": 11, "delta_freq": 19, "delta_tim": 19, "denot": 5, "depend": [1, 2, 4, 11, 13, 32], "deprec": 16, "deriv": [4, 11, 13, 16, 19], "describ": [1, 2, 4, 5, 14, 18, 24, 32], "descript": [10, 13, 20, 25], "design": [4, 13, 23], "desir": [4, 8, 16, 19], "dest": 13, "destin": 13, "destrip": 4, "detail": [2, 13, 14, 18, 24, 32], "detect": [4, 25], "determin": [1, 2, 4, 8, 11, 13, 14, 18], "determinist": 23, "develop": 32, "deviat": [4, 23], "diag": [24, 27], "diagon": [24, 27], "dict": [4, 13, 14, 16, 27], "dictionari": [13, 14], "differ": [4, 8, 13, 14, 23, 32], "digit": 4, "dimens": [4, 13], "direct": [2, 4, 11, 13], "directli": [1, 13, 15, 23], "directori": [14, 29, 32], "dirti": [5, 13, 32], "dirty_beam": 13, "dirtymap": 32, "dirtymapmak": [5, 32], "disabl": 16, "discuss": [19, 29], "disk": [14, 16, 32], "distanc": 4, "distinct": [8, 14, 32], "distribut": [2, 4, 11, 13, 14, 18, 19], "distributed_axi": 13, "divid": 13, "do": [1, 8, 11, 13, 14, 15, 26, 30, 32], "doc": 29, "doclib": 24, "docstr": 13, "document": [1, 13, 14, 18, 30, 32], "doe": [1, 4, 11, 13, 14, 18, 27, 32], "doi": 8, "don": [4, 13, 14, 15, 16, 20], "done": [1, 4, 8, 11, 13, 14, 19, 25, 29, 32], "dot": 24, "doubl": 11, "down": [8, 20], "down_mix": 8, "download": 32, "downselect": 11, "downstream": 16, "downweight": 2, "dp": 2, "draco": [29, 31, 32], "draw": [2, 9, 19, 23], "drawn": 18, "drift": [13, 14, 20, 30, 32], "driftscan": [5, 6, 14, 29, 32], "drop": 26, "dset": [2, 13], "dset1": 2, "dset2": 2, "dset2_view": 2, "dsets_from": 13, "dtype": [13, 23, 27], "due": 22, "dummi": 32, "dummytask": 26, "dump": 16, "dure": 1, "duti": 19, "e": [1, 2, 4, 11, 13, 15, 19, 23, 24, 26, 29, 32], "e33b174696509b158c15cf0bfc27f4cb2b0c6406": 29, "each": [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 13, 14, 16, 18, 19, 23, 24, 25, 26, 27, 32], "earlier": 11, "easi": 16, "easiest": 32, "east": [2, 4], "edg": 11, "ee1c55ea4cf8cb7857af2ef3adcb2439d876768d": 29, "effect": [8, 11, 19], "egg": 29, "eigenmod": 18, "eigenvalu": 18, "eigenvector": 10, "either": [1, 4, 8, 11, 13, 14, 18, 20, 23, 26], "el": 13, "elaps": 16, "element": [2, 13, 24, 27], "elev": [4, 13], "em": 10, "emit": 8, "empti": [13, 16, 26], "empty_lik": 13, "empty_timestream": 13, "end": [4, 11, 15, 18, 20, 26], "end_tim": [18, 20], "engin": 2, "enough": [8, 13, 15], "ensembl": 16, "ensur": [14, 15, 16, 18, 19, 27, 32], "ensure_chunk": 14, "ensure_unix": 1, "entir": 2, "entri": [2, 4, 10, 13, 14, 16, 20, 25, 27, 32], "enum": 1, "environ": [15, 23, 32], "epoch": 1, "eq": 2, "equal": [1, 11], "equat": 4, "equival": [13, 24], "error": [16, 18, 22, 32], "estim": [2, 4, 6, 7, 13, 19, 23, 24], "eta": 25, "etc": [11, 13, 16], "evalu": [4, 14, 16, 27], "even": [2, 13], "event": 16, "everi": [11, 16, 18, 20, 27, 32], "everyth": 4, "everywher": 4, "ew": [2, 13], "exact": 29, "exactli": [1, 32], "exampl": [4, 13, 14, 16, 29, 32], "exce": 4, "exceed": 4, "except": [2, 4], "excis": [4, 25], "exclud": [4, 7, 13], "exclude_auto": 27, "exclude_ax": 13, "exclude_intracyl": 7, "excurs": 4, "exist": [4, 13, 16, 18, 19], "exit": 23, "exp": 18, "expand": [2, 20, 25], "expandproduct": 20, "expect": [4, 10, 13, 16, 19, 23], "explicit": [1, 13, 14, 16], "explicitli": [13, 23], "extend": 4, "extern": 1, "extra": [4, 8, 23], "extra_cut": 2, "extract": [1, 2, 11, 14, 27], "extract_diagon": 27, "f": [2, 4], "fact": [4, 27], "factor": [1, 2, 4, 11, 13], "factoriz": 4, "fairli": 13, "fall": [14, 23], "fals": [1, 2, 4, 8, 10, 11, 13, 16, 18, 24, 25, 27], "far": [10, 29], "fast": [23, 24], "faster": 1, "fastest": 2, "favour": 16, "featur": 29, "fed": 14, "feed": [8, 11, 14, 20, 27], "feed_info": 14, "feed_spac": 32, "fetch": 32, "few": [1, 10, 32], "fft": [2, 11], "fi": 27, "field": [5, 18, 32], "figur": 11, "file": [2, 8, 16, 18, 20, 29, 32], "file1": 14, "file2": 14, "file7": 14, "filenam": [14, 16, 32], "fill": [4, 10, 13, 19], "filter": [2, 3, 4, 5, 8, 10, 11, 13, 16, 24, 32], "filtered_tstream": 11, "final": [1, 2, 4, 8, 11, 20, 32], "find": [4, 10, 19, 27], "find_input": 27, "find_kei": 27, "findbeamformedoutli": 4, "findfil": 14, "finish": [14, 15, 16], "finit": 11, "fire": 32, "first": [2, 11, 13, 14, 16, 19, 23, 29, 32], "first_group": 14, "fisher": 13, "fit": 14, "five": 32, "fix": [4, 8, 11, 14, 32], "fixed_precis": 14, "fj": 27, "flag": [2, 25, 27], "flagger": 4, "flat": 2, "flat_arrai": 2, "flat_ax": 2, "flatten": 2, "flatten_ax": 2, "float": [1, 2, 3, 4, 5, 8, 10, 11, 14, 15, 18, 19, 20, 23, 24, 25, 26, 32], "float32": 13, "float64": 13, "fluctuat": [11, 18, 19], "flux": 11, "follow": 32, "foreground": [3, 4, 32], "foreground_map": 32, "form": [1, 4, 9, 13, 27, 32], "formal": [5, 18], "format": [11, 13, 14, 16, 27, 32], "formed_beam": [1, 9], "formedbeam": [1, 4, 9, 13], "formedbeamha": [1, 4, 13], "formedbeamhamask": [4, 13], "formedbeammask": [4, 13], "former": 14, "fortun": 32, "forward": [3, 11, 15], "found": [2, 11, 16, 27], "four": 23, "fourier": [2, 13], "fourier_matrix": 2, "fourier_matrix_c2c": 2, "fourier_matrix_c2r": 2, "fourier_matrix_r2c": 2, "fourth": 11, "fr": 2, "frac": [4, 18], "frac_lost": 13, "fraction": [2, 4, 8, 13, 18, 27, 32], "frame": [2, 20], "free": 16, "freedom": 3, "freq": [2, 4, 13, 14, 26], "freq_end": 26, "freq_low": 32, "freq_phys": 11, "freq_physical_rang": 11, "freq_rang": 14, "freq_spac": 2, "freq_start": 26, "freq_upp": 32, "freq_width": 1, "freq_zero": 2, "freqcontain": [2, 13], "freqcontainertyp": 2, "freqsid": [1, 9], "frequenc": [1, 2, 3, 4, 9, 11, 13, 19, 20, 25, 26, 32], "frequencyrebin": 11, "frequencystack": [9, 13], "frequencystackbypol": [9, 13], "fresh": 29, "fring": 8, "from": [1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 27, 29, 32], "fsel": 2, "full": [1, 2, 4, 8, 9, 10, 13, 19, 20, 24, 25], "full_matric": 10, "fuller": 10, "fulli": [4, 13, 32], "function": [1, 2, 4, 5, 10, 11, 13, 14, 16, 18, 23, 24, 25, 26, 27, 28], "further": [15, 32], "fwhm": 1, "g": [2, 4, 13, 32], "g_": 18, "g_j": 18, "gain": [4, 13, 15, 27], "gain_stack": 18, "gaindata": [13, 15, 18], "gaindatabas": 13, "gainstack": 18, "galact": [13, 32], "gap": [2, 4], "garbag": 16, "gaussian": [1, 4, 5, 18, 19, 23], "gaussian_realis": 18, "gaussiannois": 19, "gaussiannoisedataset": 19, "gen": 23, "gener": [1, 2, 4, 5, 8, 11, 13, 15, 16, 18, 19, 20, 23, 24, 25, 26, 32], "generalis": 27, "generate_fluctu": 18, "geograph": [8, 11, 18, 20], "get": [4, 5, 8, 11, 13, 14, 15, 16, 18, 20, 23, 32], "get_beamtransf": 14, "get_telescop": 14, "gibb": 2, "git": [29, 32], "github": [29, 32], "give": [4, 8, 11, 13, 16, 27, 29, 32], "given": [1, 2, 4, 5, 6, 9, 11, 13, 14, 15, 16, 18, 19, 20, 27, 29, 32], "glob": 14, "global": [16, 23], "global_threshold": 10, "go": [15, 23, 29, 32], "good": [14, 19, 27], "got": 32, "govern": 5, "greater": 4, "grid": [8, 11, 13, 18], "gridbeam": [1, 13], "group": [8, 11, 16, 27], "group_2": 14, "group_task": 16, "groupsourcestack": 9, "guess": 2, "h": 8, "h5": [14, 32], "h5py": 27, "ha": [1, 2, 4, 13, 14, 15, 16, 25, 32], "half": 2, "handl": 14, "hang": 15, "harmon": 5, "hash": 29, "hat": 5, "have": [2, 4, 7, 8, 9, 11, 13, 14, 16, 19, 20, 24, 29, 32], "haven": 4, "hdf5": 32, "healpix": [1, 5, 13, 32], "healpixbeam": 13, "healpixbeamform": 1, "healpixcontain": 13, "held": 13, "help": 32, "here": [13, 14, 15, 29, 32], "hermit": 8, "high": [4, 11, 27], "high_freq": 11, "higher": [4, 10, 32], "highli": 13, "hold": [1, 3, 8, 11, 13, 18, 20], "holograph": 13, "horizon": 2, "hour": [1, 8, 13], "how": [1, 11, 24, 26, 30, 32], "howev": [11, 13, 15], "hpftimestream": 11, "hpmap": 1, "http": [2, 8, 19, 25, 29], "hybridvismmod": 13, "hybridvisstream": [11, 13], "i": [1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 25, 26, 27, 29, 30, 32], "icmap": 27, "icr": [1, 13], "icrs_to_cir": 1, "id": 13, "idea": 29, "identifi": [4, 16], "ie": 2, "ignor": [2, 4, 11, 15, 18], "ignore_absolute_threshold": 4, "ii": 4, "ij": [4, 18], "imag": [4, 13], "imaginari": [2, 13], "immedi": 15, "implement": [1, 4, 11, 16, 25], "impli": 13, "implicitli": [11, 14], "import": [11, 32], "improv": 16, "includ": [1, 2, 4, 11, 13, 14, 16, 24, 27], "include_pol": 4, "incom": [4, 11, 14], "incorrectli": 16, "increas": [2, 11, 14], "increment": 23, "independ": [4, 13, 19, 26], "index": [2, 4, 5, 9, 13, 14, 27, 30], "index_map": [11, 13, 20, 27], "indic": [2, 4, 11, 13, 14, 27], "indirect": 13, "individu": [4, 8, 18], "inf": 16, "infinit": 32, "influenc": 23, "info": [4, 16], "inform": [2, 11, 14, 16, 25, 27], "infrastructur": 32, "inherit": [13, 16, 32], "initi": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 23, 25, 26], "initial_": 2, "initial_amplitud": 2, "initial_sample_path": 2, "initialis": [13, 18, 23, 32], "inject": 11, "input": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 26, 27, 29, 32], "input_": [14, 15], "input_a": 27, "input_b": 27, "input_filenam": 16, "input_flag": [13, 27], "input_index": 27, "input_inswx": 27, "input_root": 16, "input_tag": 16, "insert": [2, 13], "instal": [29, 30, 32], "instanc": [2, 3, 6, 8, 11, 13, 19, 23], "instead": [1, 4, 5, 13, 16], "instrument": [2, 11, 19], "int": [1, 2, 4, 5, 8, 9, 10, 11, 13, 16, 18, 20, 23, 24, 25, 26, 27, 32], "integ": [2, 4, 13, 16, 18, 23, 24, 27, 32], "integr": [11, 20], "integration_frame_exp": 20, "integration_tim": 20, "intend": 30, "intens": 5, "inter": 27, "interchang": 13, "interest": 13, "interfac": [13, 14], "interior": 4, "intermedi": 27, "intern": [15, 23], "interpol": [8, 11, 16, 24], "interpret": [4, 13], "interv": 1, "intra": 4, "intracylind": 7, "intric": 11, "invari": [4, 25], "invers": [1, 2, 4, 5, 7, 8, 11, 13, 15, 24, 27], "inverse_binom_cdf_prob": 4, "inverse_vari": [1, 8, 11], "io": 32, "irregular": 24, "is_stack": 13, "isn": 13, "isol": [9, 32], "issu": [8, 13, 16], "item": [9, 13, 15, 32], "iter": [2, 4, 10, 16, 19, 27], "its": [1, 11, 16, 32], "itself": [16, 32], "ix": 27, "j": 27, "j2000": 13, "janski": 11, "jj": 4, "job": [14, 23], "jump": 32, "just": [2, 5, 13, 14, 16, 23, 27, 32], "jy": 11, "k": [4, 24], "keep": [2, 4, 9, 14, 19, 29], "kei": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26, 27, 32], "kelvin": [5, 11, 19], "kept": [2, 29], "kernel": [4, 8, 11, 24], "kernel_s": 4, "key_list": 27, "keyword": 13, "kid": 32, "kiyo": [29, 32], "kl": [3, 13], "klmode": [6, 13], "klmodeproject": 3, "klname": 3, "kltransform": 32, "know": 14, "known": [1, 19], "kpar_edg": 13, "kperp_edg": 13, "kwarg": [4, 13], "la": 5, "label": 32, "lanczo": [8, 11, 24], "lanczos_forward_matrix": 24, "lanczos_inverse_matrix": 24, "lanczos_kernel": 24, "lanczos_width": [8, 11], "larg": [4, 9, 11, 13], "larger": [4, 19], "largest": [2, 4, 10, 13, 18], "last": [2, 8, 16], "law": 5, "least": [8, 11], "leav": 1, "left": [5, 18], "legaci": [13, 16], "length": [1, 2, 4, 11, 16, 18, 26, 27], "less": [2, 4, 8, 32], "let": 32, "level": [4, 7, 16, 26], "level_al": 16, "level_rank0": 16, "like": [2, 4, 13, 14, 15, 16], "likelihood": [5, 8, 11, 24], "likewis": 11, "limit": 14, "linalg": 10, "line": 14, "linear": [8, 11], "link": 19, "list": [1, 2, 4, 9, 11, 13, 14, 15, 16, 19, 20, 26, 27, 32], "list_of_file_group": 14, "ll": [2, 32], "lm": 5, "load": [2, 8, 14, 32], "loadbasiccont": 14, "loadbeamtransf": [14, 32], "loadfil": 14, "loadfilesfromparam": 14, "loadfitscatalog": 14, "loadmap": 14, "loadproductmanag": 14, "loc": 23, "local": [8, 11, 13], "local_se": 23, "local_threshold": 10, "locat": [1, 8, 11, 13, 18, 20, 27], "log": [16, 23], "loggedtask": 16, "logger": 16, "logic": 25, "login": 32, "long": [1, 8], "longer": 4, "look": [13, 14, 19, 32], "loop": 25, "lost": 11, "low": [2, 4, 11], "low_freq": 11, "lower": [2, 4], "lowest": 14, "lsd": [1, 8, 18, 20], "m": [3, 4, 5, 8, 10, 11, 13, 23, 24, 32], "m_zero": 4, "maanger": 20, "machin": 32, "mad": 4, "mad_siz": 4, "made": 13, "magnitud": [4, 13], "mai": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26], "main": [2, 13], "make": [2, 4, 5, 11, 13, 14, 15, 18, 19, 26], "makecopi": 15, "makeproduct": [14, 32], "maker": [5, 32], "makesiderealdaystream": 20, "makeski": 32, "maketimestream": 20, "manag": [1, 3, 5, 6, 8, 11, 14, 18, 19, 20, 32], "mandatori": 13, "mani": [9, 11, 24, 26], "manual": 13, "map": [1, 2, 4, 5, 11, 13, 14, 16, 20, 27, 30], "map_": 20, "map_dirty2_": 32, "map_wiener2_": 32, "mapmak": 32, "marginalis": 8, "mark": [4, 25], "mask": [2, 4, 10, 11, 13, 25, 26], "mask_cont": 4, "mask_long_n": 4, "mask_low_m": 4, "mask_missing_data": 4, "mask_short": 4, "mask_short_ew": 4, "mask_short_n": 4, "mask_typ": 4, "mask_zero": 4, "mask_zero_weight": 11, "maskbadgain": 4, "maskbaselin": 4, "maskbeamformedoutli": 4, "maskbeamformedweight": 4, "maskdata": 4, "maskfreq": [2, 4], "maskmmodedata": 4, "master": 29, "match": [2, 4, 11, 13, 18, 20, 27], "match_ax": 2, "match_dset": 2, "match_median": 4, "mathbf": [5, 24], "mathrm": 24, "matric": [5, 14, 20, 32], "matrix": [2, 5, 10, 13, 18, 20, 23, 24], "max": [2, 4], "max_delai": 2, "max_m": [4, 25], "max_thresh": 4, "maximis": 10, "maximum": [2, 4, 5, 8, 11, 13, 14, 24, 25, 26], "maximumlikelihoodmapmak": 5, "mcontain": [11, 13], "mean": [4, 8, 13, 14, 16, 23, 29, 32], "meant": 13, "measur": [2, 4, 7, 13, 19, 23, 32], "medfilt": 4, "median": [2, 4, 8, 25], "member": 13, "memdataset": 13, "memdatasetdistribut": 2, "memdiskgroup": 13, "memh5": [13, 14, 16, 26], "memori": [1, 13, 16], "merci": 1, "merg": 29, "mess": 32, "messag": [15, 16, 32], "metadata": 16, "meter": 27, "method": [1, 9, 11, 14, 15, 16, 23, 25, 27, 32], "metric": 7, "mhz": [1, 2, 4, 11, 13, 26, 32], "microsecond": 13, "might": 32, "min": 4, "min_day_length": 8, "min_thresh": 4, "minim": 13, "minimis": 27, "minimum": [4, 13], "minimum_vari": [6, 13], "minut": 4, "misc": 27, "miscellan": [11, 15, 27], "mismatch": 14, "miss": [2, 4, 10, 25], "missing_threshold": 4, "mitig": 11, "mix": [4, 8, 11], "mixdata": 11, "mixed_data": 11, "mixin": 14, "mkvenv": [29, 32], "mmax": [11, 13], "mmode": [4, 5, 10, 11, 13, 32], "mmodeinversetransform": 11, "mmodetransform": [11, 32], "mock": [9, 13, 26], "mock_freq_data": 26, "mockfrequencystack": [9, 13], "mockfrequencystackbypol": [9, 13], "mode": [2, 3, 4, 5, 8, 10, 11, 13, 32], "model": [1, 2, 4, 7, 24, 32], "modif": [13, 19], "modifi": [1, 4, 11, 16], "modul": [14, 15, 16, 29, 30, 32], "moment": [2, 16], "moor": 5, "more": [2, 4, 11, 16, 18, 25], "most": [10, 13, 14, 16], "mostli": [2, 25], "move": [2, 4, 15], "mpi": [13, 15, 16, 23], "mpi4pi": 13, "mpi_random_se": 23, "mpi_rank": 23, "mpiarrai": [2, 13], "mpidataset": [13, 14], "mpilogfilt": 16, "mpiloggedtask": [14, 15, 16, 23], "mpitask": 16, "msign": 13, "mstream": 4, "much": [4, 13], "mulitpli": 18, "multi": 13, "multifrequ": 13, "multipl": [1, 2, 11, 13, 19, 23], "multipli": [2, 4, 18, 19], "multithread": 23, "multithreadedrng": 23, "must": [2, 4, 6, 7, 8, 9, 11, 13, 14, 19, 20, 23, 24, 27, 29, 32], "n": [2, 4, 5, 13, 18, 23, 24, 27], "n_": 4, "naiv": 11, "name": [2, 3, 6, 11, 13, 14, 16, 32], "nan": [4, 16], "nan_check": 16, "nan_dump": 16, "nan_skip": 16, "nanmedian": 4, "narrai": 24, "narrow": [8, 9], "nativ": 1, "natur": [1, 11], "nbase": 2, "ncompon": 13, "ndai": [18, 19, 32], "ndarrai": [1, 2, 4, 10, 11, 13, 18, 23, 24, 25, 26, 27], "ndata": 26, "ndelai": 2, "ndim": 18, "nearest": [8, 24], "need": [2, 11, 13, 16, 18, 20, 23, 32], "neg": [4, 13], "negative_m": 4, "neighbor": [4, 8], "neighbour": 11, "nel": 4, "nest": 13, "new": [9, 11, 13, 18, 23, 27, 32], "new_catalog": 9, "new_data": 11, "new_sstream": [11, 20], "newdata": 11, "newobj": 13, "next": [14, 15, 16, 26], "nfreq": [2, 25], "ngroup": 9, "nha": 4, "ni": [2, 24], "niedermay": 32, "ninput": 27, "niter": [2, 10], "nl": 25, "nmed": 4, "nmode": 13, "no_beam_model": 1, "node": [14, 15], "nois": [2, 4, 7, 8, 11, 13, 24, 26, 32], "noiseless": 19, "noisi": 19, "non": [2, 4, 11, 13, 27], "none": [1, 2, 4, 8, 9, 11, 13, 14, 16, 18, 19, 23, 25, 26, 27, 32], "normal": [8, 13, 23], "normalis": 11, "north": [2, 4], "note": [2, 3, 4, 5, 7, 8, 11, 13, 14, 16, 18, 19, 24], "noth": [14, 15, 23], "now": 27, "np": [1, 2, 4, 10, 11, 13, 18, 23, 24, 25, 27], "npoint": 18, "nprod": [25, 27], "nra": [4, 11], "nsamp": 2, "nsampl": [2, 13], "nside": [5, 11, 13, 32], "nsigma": 4, "nstack": 27, "nthread": 23, "ntime": [2, 25, 26, 27], "null": 2, "null_delay_filt": 2, "num_bas": 26, "num_correl": 26, "num_cylind": 32, "num_dec": 1, "num_delai": 2, "num_fe": 32, "num_freq": [1, 26, 32], "num_ra": [1, 26], "num_realis": 26, "number": [1, 2, 4, 8, 9, 10, 11, 13, 15, 18, 19, 20, 23, 24, 26, 27, 32], "numer": 8, "numpi": [2, 13, 23, 25, 32], "numpy_se": 23, "nuttal": [2, 27], "nw": 24, "nyquist": [2, 11], "obj": [13, 14], "object": [1, 2, 4, 5, 6, 7, 8, 9, 11, 13, 14, 16, 18, 19, 20, 23, 27, 32], "observ": [8, 11, 13, 18, 19, 20, 32], "obtain": [2, 13], "obvious": 32, "odd": 13, "oddra": 13, "off": [2, 10, 13], "offringa": 25, "offset": [8, 19], "often": 32, "omp_num_thread": 23, "one": [2, 8, 11, 13, 14, 16, 19, 27, 32], "ones": 4, "onli": [1, 4, 9, 10, 11, 13, 14, 18, 19, 23, 24, 25, 29, 32], "only_freq": 25, "only_gain": 18, "only_tim": 25, "onto": [2, 8, 11, 18, 24], "onward": 13, "op": 15, "oper": [4, 11, 13, 23, 25], "optim": [2, 25], "option": [1, 2, 3, 4, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 25, 27, 32], "order": [1, 2, 3, 10, 11, 13, 18, 27, 32], "ordin": 18, "org": [2, 8, 29], "organis": 32, "orient": [2, 4], "origin": [2, 11, 13, 14, 23], "original_nra": 11, "orthogon": 2, "other": [2, 4, 11, 13, 14, 16, 32], "otherwis": [8, 11, 14, 25], "our": [4, 13, 32], "out": [2, 3, 4, 8, 9, 10, 11, 14, 15, 16, 19, 23, 26, 27, 32], "out_cont": 2, "outlier": [4, 25], "output": [1, 2, 4, 5, 8, 9, 11, 14, 16, 23, 24, 26, 27, 29, 32], "output_directori": 32, "output_filenam": 16, "output_nam": 16, "output_root": [16, 32], "outsid": [4, 27], "over": [1, 2, 4, 7, 8, 9, 11, 13, 14, 18, 19, 20, 25, 32], "overal": [4, 13, 23], "overcom": 4, "overrid": [11, 13, 14, 16], "overridden": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26], "overwrit": 11, "own": [13, 15], "p": [4, 6], "p_to_sigma": 4, "pack": [2, 3, 13, 27], "packag": [13, 29, 32], "pad": [8, 11], "page": 30, "pair": [2, 11, 13, 27], "pairwis": 13, "parallel": 13, "parallel_method": 23, "parallelis": 14, "param": 32, "paramet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 25, 26, 27, 32], "parent": 1, "pars": [1, 14], "part": [2, 4, 15, 16, 23], "partial": 13, "particip": [14, 23], "particular": 24, "pass": [1, 4, 11, 13, 14, 15, 16, 26, 32], "passon": 15, "password": 32, "passx": 29, "patch": 1, "path": [2, 14, 16, 32], "pathfind": 32, "pattern": [14, 20], "pdf": 25, "penros": 5, "penultim": 2, "per": [4, 14, 20, 26, 27], "percentil": 25, "perform": [1, 2, 3, 4, 5, 10, 11, 14, 32], "period": [4, 8, 11, 18, 24], "persist": 1, "pfb": 2, "pha": 29, "phase": [13, 18], "phi": 13, "physic": [2, 11, 13], "pi": 27, "pick": 2, "piec": [11, 15, 18], "pinv2": 5, "pinv_svd": 5, "pip": 29, "pipelin": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26, 29, 30], "pipeline_config": 16, "pipeline_param": 32, "pipelineruntimeerror": [14, 16], "pix": 13, "pixel": [1, 4, 13], "place": [1, 4, 11, 14, 15, 19, 23, 27], "point": [1, 4, 13, 18, 24, 25, 32], "pointless": 15, "pol": [11, 13, 27], "polar": [1, 2, 4, 13, 27], "polaris": [4, 5, 11, 13, 32], "polarisedcylindertelescop": 32, "polarization_map": 27, "polcont": 11, "polmap": 27, "pols_to_flag": 4, "popen": 14, "popul": 9, "posit": [1, 4, 8, 11, 13], "positive_m": 4, "possibl": [13, 14, 29], "potenti": 18, "power": [2, 5, 6, 13, 30], "powerspectrum": 13, "powerspectrum2d": 13, "pr": 4, "practic": 8, "pre": [4, 5, 32], "precalcul": 6, "preced": [14, 20], "precess": [1, 13], "precis": 14, "present": [2, 4, 13, 16], "preset": 14, "presum": 11, "pretti": [8, 11], "prev_fluc": 18, "prev_x": 18, "prevent": 15, "previou": [9, 13, 16, 18, 19, 23], "previous": 25, "primari": [1, 11], "print": [14, 32], "prior": [4, 5, 8, 11, 14, 18], "prior_amp": 5, "prior_tilt": 5, "prioriti": 11, "proactiv": 15, "probabl": [4, 10], "process": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 23, 26, 32], "process_finish": [1, 8, 9, 11, 16, 18], "prod": [13, 27], "prod_map": 27, "prodstack": 13, "produc": [1, 2, 3, 4, 5, 11, 26, 32], "product": [3, 4, 6, 9, 11, 13, 14, 16, 18, 19, 20, 25, 27], "product_directori": [14, 32], "product_param": 32, "productmanag": [1, 2, 3, 5, 6, 11, 14, 18, 19, 20], "project": [2, 3], "prone": 8, "propag": 13, "proper": [4, 10], "properli": [4, 19, 32], "properti": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 26, 32], "proptyp": 32, "provid": [1, 4, 13, 14, 16, 19, 20, 32], "pseudo": [5, 24], "psfisher": 32, "psname": 6, "pstype": 6, "pure": 2, "purpos": [11, 30], "put": [8, 32], "py": 29, "pyc": 32, "python": [16, 29, 32], "q": [1, 13, 32], "quadrat": 13, "quadraticpsestim": 6, "quadratur": 13, "qualifi": 32, "qualiti": [4, 13], "quantiti": [8, 13, 19], "quasar": 13, "quasar_id": 13, "quasar_mask": 13, "quasi": 4, "quick": 30, "quickli": 32, "quota": 14, "r": [24, 29], "ra": [1, 4, 8, 11, 13, 26], "ra_cir": 1, "radian": 18, "radio": 30, "radiocosmologi": [29, 32], "radiomet": [4, 13], "radiometerweight": 4, "radiometr": [7, 13], "rais": [4, 7, 14, 16, 22, 27], "random": [2, 5, 9, 15, 18, 19, 26, 32], "randomfreqdata": 26, "randomgain": 18, "randomgen": 23, "randomli": 23, "randomsiderealgain": 18, "randomsubset": 9, "randomtask": [2, 9, 19, 23, 26], "rang": [4, 9, 11, 13, 14, 20, 27], "rank": [4, 10, 13, 14, 15, 16, 23, 25], "rate": [4, 8], "rather": [2, 4, 8], "ratio": [8, 11], "raw": [3, 4], "rcond": [5, 18], "re": [2, 23, 32], "read": [13, 14, 16], "read_input": 16, "readi": 32, "real": [2, 4, 11, 13, 19, 32], "realis": [18, 19, 26], "realist": 30, "realli": [5, 14], "reason": [8, 11, 13], "rebin": 11, "receiv": [8, 9, 14, 15, 19], "receivertemperatur": 19, "recommend": 4, "record": 16, "rectangular": [11, 13], "recv_temp": 19, "redefin": 13, "redefine_stack_index_map": 27, "redistribut": [13, 14], "redshift": [1, 13, 14], "reduc": [3, 11], "reducebas": 11, "reducevar": 11, "reduct": 11, "reductino": 11, "redund": [1, 7, 11, 13, 19, 20, 27, 32], "refer": [11, 15, 16, 20, 29, 30], "reference_declin": 11, "reflect": 13, "regardless": 13, "region": 4, "regrid": [8, 11], "regridd": [8, 11], "regular": [8, 11], "regularis": [4, 11], "regularli": [8, 11, 24], "rel": [4, 14, 24], "relat": 15, "relative_threshold": 4, "remain": [9, 32], "rememb": 4, "remov": [2, 4, 10, 11, 13, 14, 15], "remove_averag": 4, "remove_integration_window": 11, "remove_median": [4, 25], "reorder": 11, "repeat": 23, "replac": [4, 11, 13, 19], "repositori": [29, 32], "repres": [2, 13, 19, 27], "requesit": 32, "request": [11, 16], "requir": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26, 27, 29, 32], "require_match": 27, "resampl": 11, "rescal": [4, 13], "reshap": 2, "resolut": [1, 5], "resolv": [13, 14], "respect": 16, "result": [1, 2, 8, 13], "return": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 25, 26, 27], "returnfirstinputonfinish": 16, "returnlastinputonfinish": 16, "revers": 30, "reverse_map": [20, 27], "reverse_stack": 27, "revis": 11, "rfi": [4, 13], "rfimask": [4, 13], "rfisensitivitymask": 4, "richard": 32, "rid": 8, "right": [5, 13, 18], "ring": [1, 4, 13], "ringmap": [1, 4, 11, 13], "ringmapbeamform": 1, "ringmapmask": [4, 13], "ringmapstack2d": 1, "rm": [13, 26], "rng": [2, 23, 26], "root": [13, 14, 16, 29], "rotat": 13, "routin": [2, 14, 24, 25, 27], "row": 13, "rudimentari": 18, "rudimetari": 5, "rug": 25, "rule": 16, "run": [9, 14, 23, 30, 32], "runtimeerror": 7, "safe": 27, "same": [1, 2, 4, 8, 9, 11, 13, 14, 15, 16, 19, 23, 25, 27, 29, 32], "samp": 4, "sampl": [2, 4, 8, 11, 13, 18, 19, 20, 23, 25, 26], "sample_frac": 19, "sample_vari": 13, "sample_variance_amp_phas": 13, "sample_variance_iq": 13, "sample_weight": 13, "samplenois": 19, "sampler": 2, "samples_per_fil": 20, "samplevariancecontain": 13, "sanitizeweight": 4, "save": [2, 3, 7, 13, 14, 15, 16, 32], "save_sampl": 2, "save_vers": 14, "saveconfig": 14, "savemodulevers": 14, "savezarrzip": 14, "sb": 11, "scale": [1, 4, 11, 15, 23, 25], "scan": 30, "scheme": [10, 13], "scinet": 32, "scipi": [10, 32], "script": [29, 32], "sdata": 8, "sdss": 14, "search": [27, 30], "secant": 1, "second": [1, 2, 8, 11, 15, 18, 19, 20, 29, 32], "section": [13, 32], "see": [1, 2, 8, 10, 13, 14, 18, 19, 23, 25], "seed": [23, 32], "seem": [4, 15, 25], "select": [11, 13, 14, 20, 32], "selectedpolcont": 11, "selectfreq": 11, "selectionsmixin": [11, 14], "selectpol": 11, "selet": 11, "self": [16, 29], "send": [8, 15], "sens": 13, "sensit": [4, 13], "sent": 15, "separ": [18, 19, 24], "sequenc": [13, 16, 23], "seri": [2, 14, 20], "serialis": 14, "set": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26, 27, 30], "set_weight": 19, "setmpilog": 16, "settabl": 16, "setup": [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 14, 15, 16, 18, 19, 20, 29, 32], "sever": [2, 13, 14, 16, 32], "sfc64": 23, "sh": [29, 32], "shallow": 13, "shape": [1, 2, 14, 23, 25, 27], "share": [4, 11, 13], "shift": 11, "shiftra": 11, "shorter": 4, "shorthand": 29, "should": [2, 4, 8, 11, 13, 14, 15, 16, 23, 27, 29, 32], "shouldn": [4, 13], "si": 24, "side": [1, 9, 11], "sider": [2, 4, 11, 13, 15, 18, 20, 26, 32], "siderealbaselinemask": [4, 13], "siderealcontain": [11, 13], "siderealgain": 18, "siderealgaindata": [13, 15, 18], "siderealgroup": 8, "siderealmmoderesampl": 11, "siderealregridd": 8, "siderealregriddercub": 8, "siderealregridderlinear": 8, "siderealregriddernearest": 8, "siderealrfimask": [4, 13], "siderealstack": 8, "siderealstackermatch": 8, "siderealstream": [1, 2, 4, 8, 11, 13, 15, 18, 19, 20, 26, 27], "sig": 10, "sigma": [4, 18], "sigma_amp": 18, "sigma_phas": 18, "sigma_to_p": 4, "signal": [2, 5, 8, 9, 11, 13, 24, 32], "significantli": [1, 4], "similar": 13, "similarli": [11, 27], "simpl": 15, "simpli": [23, 32], "simul": [4, 11, 18, 19, 20, 30, 32], "simulatesider": [20, 32], "sine": 2, "singl": [1, 4, 9, 13, 14, 26], "single_group": 14, "single_source_bin_index": 9, "singletask": [1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, 26, 32], "singular": [2, 10], "sir": [4, 25], "sir1d": 25, "size": [2, 4, 9, 11, 13, 16, 18, 23, 25, 27], "skip": 13, "skip_dataset": 13, "skip_nyquist": 2, "sky": [1, 5, 13, 20, 32], "slice": [4, 11, 13, 14], "slightli": 4, "slowest": 2, "small": [4, 11, 13], "smaller": 18, "smallest": [2, 4], "smooth": [1, 4, 15], "smoothing_length": 15, "smoothvisweight": 4, "snr_cov": [8, 11], "so": [1, 4, 13, 23, 32], "softwar": 32, "solut": [8, 24], "solv": 11, "some": [2, 4, 13, 24, 27, 32], "someth": 32, "sometim": 2, "soon": [9, 32], "sort": 32, "sourc": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 22, 23, 24, 25, 26, 27, 29, 32], "source_cat": 1, "source_ra": 8, "sourcecatalog": [1, 9, 13], "sourcestack": 13, "south": [2, 4], "sp": 11, "space": [2, 8, 11, 24], "span": 10, "spars": 14, "spec": [2, 16], "specif": [1, 11, 13, 16, 20, 23], "specifi": [1, 2, 4, 11, 13, 14, 15, 16, 20, 26, 29, 32], "specifict": 13, "spectra": [1, 13, 30], "spectroscop": 13, "spectroscopiccatalog": [13, 14], "spectrum": [2, 5, 6, 10, 13, 26], "spectum": 5, "speed": 14, "sphere": 13, "spheric": [5, 13], "spline": 8, "split": 13, "springer": 19, "sqrt": 4, "squar": [8, 13], "src": 32, "ss": [2, 4, 11, 20], "ss_filt": 2, "sscont": 11, "ssh": [29, 32], "sstream": [2, 4, 11, 18, 20, 32], "stack": [1, 4, 7, 8, 9, 11, 13, 15, 18, 19, 20, 27], "stack3d": 13, "stack_flag": 27, "stack_ind": 4, "stack_index": [14, 27], "stack_new": 27, "stack_rang": 14, "stage": 13, "standard": [4, 13, 14, 16, 23], "standard_complex_norm": 23, "standard_complex_wishart": 23, "start": [2, 4, 11, 14, 18, 20, 26, 32], "start_flag": 25, "start_threshold_sigma": 4, "start_tim": [18, 20], "state": 23, "static": [4, 13], "staticgaindata": [13, 15], "station": 4, "stellar": 13, "step": [11, 14, 32], "still": 4, "stoke": [1, 2, 11, 13, 32], "stokes_i": 2, "stop": [11, 14], "store": [1, 13, 32], "str": [2, 3, 6, 8, 11, 13, 14, 16, 26], "straight": 4, "stream": [2, 4, 8, 11, 13, 15, 18, 19, 26, 32], "stride": 14, "string": [1, 3, 4, 11, 13, 14, 16, 19, 32], "strip": 1, "structur": [11, 13, 26], "struggl": 4, "stupid": 14, "style": 14, "subclass": [13, 14, 26], "subsequ": [19, 23, 26], "subset": [2, 11, 14, 32], "substitut": 4, "subtask": 16, "subtask1": 16, "subtask2": 16, "subtract": [4, 8, 11, 25], "success": 4, "successfulli": [14, 32], "suggest": 13, "suitabl": [2, 23], "sum": [1, 8, 18, 32], "sum_": 18, "summari": 13, "summat": 11, "sumthreshold": [4, 25], "sumthreshold_pi": 25, "sun": 4, "super": [1, 13], "superscript": 5, "supertask": 16, "suppli": [11, 13, 27], "support": [4, 11, 13, 15, 16], "sure": [2, 19, 32], "svd": [3, 5, 10, 13], "svd_em": 10, "svdmode": 13, "svdmodeproject": 3, "svdspectrum": [10, 13], "svdspectrumestim": 10, "synchron": 15, "synchrotron": 32, "synthesi": 32, "synthet": 30, "system": 13, "systemsensit": [4, 7, 13], "t": [4, 8, 13, 14, 15, 16, 18, 20, 24, 32], "t_i": 18, "t_j": 18, "t_recv": 19, "t_sky": 19, "tabl": 13, "table_spec": 13, "tablebas": 13, "tablenam": 13, "tag": [8, 14, 16, 26, 29], "tail": 4, "take": [2, 3, 4, 8, 9, 11, 13, 14, 16, 18, 19, 20, 30], "taken": 4, "talk": [4, 8], "task": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 18, 19, 20, 22, 23, 26, 27, 29, 32], "taskbas": [14, 16], "taskgroup": 11, "tau": 11, "tel": [2, 11, 14, 32], "tel_and_bt": 32, "telescop": [1, 2, 4, 7, 8, 11, 13, 14, 19, 20, 27, 32], "telescope_orient": 2, "tell": 32, "telscop": [18, 20], "temperatur": [11, 19], "term": [2, 13, 19], "termin": 14, "test": [13, 15], "testbeam": 32, "testfg": 32, "teststream_": 32, "text": [4, 14], "than": [1, 2, 4, 10, 11, 16, 18, 19], "thei": [1, 9, 11, 13, 14, 15, 16, 29], "them": [4, 8, 14, 18, 19, 20, 30, 32], "themat": 15, "themselv": 32, "theta": 13, "thi": [1, 2, 3, 4, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 25, 27, 29, 30, 32], "thing": [4, 32], "third": 11, "those": [11, 13, 15], "though": 13, "thread": 23, "three": 32, "threshold": [2, 3, 4, 25], "threshold1": 25, "thresholdvisweightbaselin": 4, "thresholdvisweightfrequ": 4, "through": [1, 3, 4, 13, 16, 32], "thrown": 16, "thu": 20, "time": [1, 2, 4, 8, 10, 11, 13, 15, 16, 18, 19, 20, 25, 26, 27, 32], "time_lik": 1, "timescal": 11, "timestream": [1, 2, 4, 8, 11, 13, 15, 18, 19, 20, 27, 30, 32], "timetrack": 1, "to_hdf5": 14, "tod": [11, 13], "todata": 13, "todcontain": [7, 11, 13], "togeth": [8, 11, 14, 16, 24, 32], "tol": 2, "too": 13, "tool": [2, 32], "top": 4, "topologi": 15, "total": [1, 2, 4, 13, 27], "total_len": 26, "trace": 13, "track": [1, 13, 14, 29], "track_typ": 13, "trackbeam": 13, "transfer": [5, 14, 20, 24, 32], "transform": [2, 5, 13, 20, 32], "transformjanskytokelvin": 11, "transit": [1, 4, 8, 11, 13], "transittelescop": [1, 2, 4, 7, 8, 11, 14, 20], "treat": [8, 13, 20, 24], "tri": 23, "trial": 4, "triangl": [13, 19, 20], "triangular": 27, "trim": 11, "true": [1, 2, 4, 7, 9, 10, 11, 13, 14, 16, 18, 19, 20, 25, 27], "truncat": 14, "truncated_data": 14, "try": [11, 13, 32], "tstream": [4, 8, 11, 15, 20], "tupl": [2, 4, 11, 13, 23, 26], "turn": [2, 14, 20, 30, 32], "tutori": 30, "tv": 4, "tv_base_s": 4, "tv_channels_flag": 4, "tv_fraction": 4, "tv_mad_siz": 4, "two": [13, 25, 29, 32], "txt": 29, "type": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 25, 26, 27, 32], "typic": [4, 13, 14, 20, 27], "u": [1, 10, 13, 20, 24, 26, 27, 32], "ubas": 2, "ubc": 29, "un": [8, 20], "unbias": 13, "uncertainti": 13, "uncondition": 15, "uncorrel": [6, 13, 19, 24], "undefin": 4, "under": [4, 14, 16], "underli": [2, 13], "underneath": 23, "understood": [13, 32], "unexpect": 4, "unfortun": 13, "unicod": 14, "uniform": [1, 8, 11], "union": 29, "uniqu": [8, 27], "unit": [4, 11], "univers": 25, "unix": [18, 20], "unknown": [8, 13], "unless": [1, 13, 32], "unlik": 13, "unmask": [4, 27], "unmodifi": 4, "unqualifi": 16, "until": [14, 15], "unwant": 4, "unwindow": [6, 13], "unwrap": 20, "up": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 26, 30], "updat": [4, 8, 13, 14, 15, 27], "update_id": 13, "update_weight": 15, "upon": 16, "upper": [13, 27], "us": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 18, 19, 20, 23, 24, 25, 26, 27, 29, 32], "user": [4, 32], "usual": [2, 13], "utc": [18, 20], "util": 2, "utmat": 27, "v": [1, 5, 13, 32], "v1": 29, "v_": 4, "valid": [4, 13, 16, 27], "valu": [2, 4, 10, 13, 14, 16, 18, 19, 23, 25, 27], "valueerror": [4, 27], "vari": [2, 4, 13], "variabl": [4, 13, 16, 23, 32], "variable_timetrack": 1, "varianc": [1, 2, 4, 7, 8, 11, 13, 14, 19, 24], "variance_increas": 14, "variat": [13, 23], "variou": [13, 24], "ve": 32, "vector": [2, 10, 18, 27], "venv": [29, 32], "veri": 13, "versa": 11, "version": [11, 13, 14, 16, 29], "vh": 10, "vi": [2, 4, 11, 13, 19, 27], "via": 2, "vice": 11, "view": [2, 13], "virtual": 32, "virtualenv": 32, "vis_i": 2, "vis_weight": [2, 4], "visbas": 13, "viscontain": [4, 13, 19], "visgridstream": 13, "visibili": 4, "visibilit": 18, "visibilti": 4, "visibl": [1, 2, 4, 7, 8, 11, 13, 19, 27, 32], "w": [4, 27], "wa": [1, 11, 13, 15, 19, 27], "wai": 13, "wait": [14, 15], "waituntil": 15, "waitzarrzip": 14, "want": [2, 4, 8, 13, 19, 23, 24, 32], "warn": 4, "wavelet": 13, "waveletspectrum": 13, "we": [2, 4, 8, 11, 13, 18, 23, 24, 29, 32], "weight": [1, 2, 4, 7, 8, 11, 13, 14, 15, 19, 26], "weight_boost": [2, 13], "weight_coeff": 11, "weight_dataset": 14, "weight_tol": 2, "well": [4, 8, 10, 11, 30, 32], "went": 27, "were": [2, 4, 11, 19, 27], "west": [2, 4, 8], "what": [1, 11, 13, 26, 32], "whatev": [9, 14, 15], "when": [1, 2, 4, 8, 9, 11, 13, 14, 15, 16, 19, 20, 32], "whenev": 32, "where": [2, 4, 5, 11, 13, 15, 27, 32], "wherea": [1, 29], "wherev": 32, "whether": [2, 4, 13, 14, 16, 18], "which": [2, 3, 4, 6, 8, 11, 13, 14, 16, 18, 19, 23, 27, 32], "while": [2, 4, 8, 11, 13, 15, 27], "whole": [4, 8], "whose": [4, 13, 23], "why": 5, "width": [4, 8, 11, 24], "wiener": [2, 5, 8, 11, 24, 32], "wienermap": 32, "wienermapmak": [5, 32], "window": [2, 4, 8, 11, 27], "window_generalis": [2, 27], "wise": 2, "wishart": [19, 23], "with_sample_vari": 8, "within": [4, 14, 15, 20, 32], "without": [11, 13, 32], "won": [13, 14, 15, 32], "work": [2, 4, 9, 13, 14, 16, 23, 29, 32], "workaround": 16, "worth": [8, 11], "would": [2, 8, 14, 20], "wrap": [11, 20, 23], "wrapper": [25, 27], "write": [13, 14, 16], "write_output": 16, "written": [14, 16, 32], "www": [25, 29], "x": [4, 16, 18, 19, 24, 27], "x2": 18, "xhat": 24, "xi": 18, "xx": [1, 4, 27], "xy": [1, 27], "y": [4, 18, 24], "y2": 18, "y_spec": 2, "yaml": [14, 32], "ye": 32, "yield": [13, 23], "you": [2, 8, 13, 14, 19, 29, 32], "your": 32, "yx": [1, 27], "yy": [1, 4, 27], "z": 13, "z_end": 13, "z_rang": 14, "z_start": 13, "za_cut": 2, "zarr": 14, "zarrziphandl": 14, "zenith": [2, 8, 11], "zero": [2, 4, 8, 11, 27, 32], "zero_data": 4, "zip": 14, "zipzarrcontain": 14}, "titles": ["draco.analysis", "draco.analysis.beamform", "draco.analysis.delay", "draco.analysis.fgfilter", "draco.analysis.flagging", "draco.analysis.mapmaker", "draco.analysis.powerspectrum", "draco.analysis.sensitivity", "draco.analysis.sidereal", "draco.analysis.sourcestack", "draco.analysis.svdfilter", "draco.analysis.transform", "draco.core", "draco.core.containers", "draco.core.io", "draco.core.misc", "draco.core.task", "draco.synthesis", "draco.synthesis.gain", "draco.synthesis.noise", "draco.synthesis.stream", "draco.util", "draco.util.exception", "draco.util.random", "draco.util.regrid", "draco.util.rfi", "draco.util.testing", "draco.util.tools", "draco.util.truncate", "Development Guidelines", "draco", "API Reference", "Tutorial"], "titleterms": {"analysi": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], "api": 31, "base": 13, "beamform": 1, "branch": 29, "class": 13, "contain": 13, "core": [12, 13, 14, 15, 16], "delai": 2, "depend": 29, "develop": 29, "draco": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30], "except": 22, "extern": 32, "fgfilter": 3, "file": 14, "flag": 4, "gain": 18, "group": 14, "guidelin": 29, "helper": 13, "indic": 30, "io": 14, "make": 32, "map": 32, "mapmak": 5, "misc": 15, "ninja": 32, "nois": 19, "pipelin": 32, "powerspectrum": 6, "product": 32, "random": 23, "refer": 31, "regrid": 24, "rfi": 25, "routin": 13, "sensit": 7, "set": 32, "sider": 8, "sourcestack": 9, "stream": 20, "structur": 29, "submodul": 31, "svdfilter": 10, "synthesi": [17, 18, 19, 20], "tabl": 30, "task": 16, "techniqu": 32, "test": 26, "tool": 27, "transform": 11, "truncat": 28, "tutori": 32, "up": 32, "usag": 8, "util": [21, 22, 23, 24, 25, 26, 27, 28], "virtualenv": 29}}) \ No newline at end of file diff --git a/docs/tutorial.html b/docs/tutorial.html new file mode 100644 index 000000000..5cce285c5 --- /dev/null +++ b/docs/tutorial.html @@ -0,0 +1,343 @@ + + + + + + + Tutorial — draco 0+untagged.1.g9cf3e8b documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Tutorial

+

This tutorial is going to go through the process of generating BAO constraints +from the Pathfinder data. Just kidding! We’re actually just going to generate +some simulated data and the turn it into maps.

+
+

Setting up the Pipeline

+

Before you start, make sure you have access to the CHIME bitbucket organisation, +and have set up your ssh keys for access to the bitbucket repositories from the +machine you want to run the pipeline on. Unless you are working on Scinet, +you’ll also want to ensure you have an account on niedermayer and your ssh +keys are set up to allow password-less login, to ensure the database connection +can be set up.

+

There are a few software pre-requesites to ensure you have installed. Obviously +python is one of them, with numpy and scipy installed, but you also need to +have virtualenv, allowing us to install the pipeline and it’s dependencies +without messing up the base python installation. To check you have it installed +try running:

+
$ virtualenv --help
+
+
+

if you get an error, it’s not installed properly so you’ll need to fix it.

+

With that all sorted, we’re ready to start. First step, download the pipeline +repository to wherever you want it installed:

+
$ git clone git@github.com/radiocosmology/draco.git
+
+
+

Then change into that directory, and run the script mkvenv.sh:

+
$ cd draco
+$ ./mkvenv.sh
+
+
+

The script will do three things. First it will create a python virtual +environment to isolate the CHIME pipeline installation. Second it will fetch the +python pre-requesites for the pipeline and install them into the virtualenv. +Finally, it will install itself into the new virtualenv. Look carefully through +the messages output for errors to make sure it completed successfully. You’ll +need to activate the environment whenever you want to use the pipeline. To do +that, simply do:

+
$ source <path to pipeline>/venv/bin/activate
+
+
+

You can check that it’s installed correctly by firing up python, and attempting +to import some of the packages. For example:

+
>>> from drift.core import telescope
+>>> print telescope.__file__
+/Users/richard/code/draco/venv/src/driftscan/drift/core/telescope.pyc
+>>> from draco import containers
+>>> print containers.__file__
+/Users/richard/code/draco/draco/containers.pyc
+
+
+
+
+

External Products

+

If you are here, you’ve got the pipeline successfully installed. Congratulations.

+

There are a few data products we’ll need to run the pipeline that must be +generated externally. Fortunately installing the pipeline has already setup all +the tools we need to do this.

+

We’ll start with the beam transfer matrices, which describes how the sky gets +mapped into our measured visibilities. These are used both for simulating +observations given a sky map, and for making maps from visibilities (real or +simulated). To generate them we use the driftscan package, telling it what +exactly to generate with a YAML configuration file such as the one below.

+
 1config:
+ 2    # Only generate Beam Transfers.
+ 3    beamtransfers:      Yes
+ 4    kltransform:        No
+ 5    psfisher:           No
+ 6
+ 7    output_directory:   beams
+ 8
+ 9telescope:
+10    type:
+11        # Specify a custom class
+12        class:  PolarisedCylinderTelescope
+13        module: drift.telescope.cylinder
+14
+15    freq_lower:         400.0
+16    freq_upper:         410.0
+17    num_freq:           5
+18
+19    num_cylinders:      2
+20    num_feeds:          4
+21    feed_spacing:       0.3
+22    cylinder_width:     10.0
+
+
+

This file is run with the command:

+
$ drift-makeproducts run product_params.yaml
+
+
+

To simulate the timestreams we also need a sky map to base it on. The cora +package contains several different sky models we can use to produce a sky map. +The easiest method is to use the cora-makesky command, e.g.:

+
$ cora-makesky foreground 64 401.0 411.0 5 foreground_map.h5
+
+
+

which will generate an HDF5 file containing simulated foreground maps at each +polarisation (Stokes I, Q, U and V) with five frequency channels between 401.0 +and 411.0 MHz. Each map is in Healpix format with NSIDE=16. There are options +to produce 21cm signal simulations as well as point source only, and galactic +synchrotron maps.

+
+
+

Map-making with the Pipeline

+

The CHIME pipeline is built using the infrastructure developed by Kiyo in the +caput.pipeline module. Python classes are written to perform task on the +data, and a YAML configuration file describes how these should be configured and +connected together. Below I’ve put the configuration file we are going to use to +make maps from simulated data:

+
 1pipeline:
+ 2    tasks:
+ 3        -   type:       draco.core.io.LoadBeamTransfer
+ 4            out:        tel_and_bt
+ 5            params:
+ 6                product_directory:  "testbeams/bt/"
+ 7
+ 8        -   type:       draco.synthesis.stream.SimulateSidereal
+ 9            requires:   tel_and_bt
+10            out:        sstream
+11            params:
+12                save:   Yes
+13                output_root: teststream_
+14
+15        -   type:       draco.analysis.transform.MModeTransform
+16            in:         sstream
+17            out:        mmodes
+18
+19        -   type:       draco.analysis.mapmaker.DirtyMapMaker
+20            requires:   tel_and_bt
+21            in:         mmodes
+22            out:        dirtymap
+23            params:
+24                nside:      128
+25                save:   Yes
+26                output_root: map_dirty2_
+27
+28        -   type:       draco.analysis.mapmaker.WienerMapMaker
+29            requires:   tel_and_bt
+30            in:         mmodes
+31            out:        wienermap
+32            params:
+33                nside:      128
+34                save:   Yes
+35                output_root: map_wiener2_
+
+
+

Before we jump into making the maps, let’s briefly go over what this all means. +For further details you can consult the caput documentation on the pipeline.

+

The bulk of this configuration file is a list of tasks being configured. There +is a type field where the class is specified by its fully qualified python +name (for example, the first task draco.io.LoadBeamTransfer). To +connect one task to another, you simply specify a label for the output of +one task, and give the same label to the input or requires of the other +task. The labels themselves are dummy variables, any string will do, provided it +does not clash with the name of another label. The distinction between input +and requires is that the first is for an input which is passed every cycle +of the pipeline, and the second is for something required only at initialisation +of the task.

+

Often we might want to configure a task from the YAML file itself. This is done +with the params section of each task. The named items within this section +are passed to the pipeline class when it is created. Each entry corresponds to a +config.Property attribute on the class. For example the SimulateSidereal +class has parameters that can be specified:

+
class SimulateSidereal(task.SingleTask):
+    """Create a simulated timestream.
+
+    Attributes
+    ----------
+    maps : list
+        List of map filenames. The sum of these form the simulated sky.
+    ndays : float, optional
+        Number of days of observation. Setting `ndays = None` (default) uses
+        the default stored in the telescope object; `ndays = 0`, assumes the
+        observation time is infinite so that the noise is zero. This allows a
+        fractional number to account for higher noise.
+    seed : integer, optional
+        Set the random seed used for the noise simulations. Default (None) is
+        to choose a random seed.
+    """
+    maps = config.Property(proptype=list)
+    ndays = config.Property(proptype=float, default=0.0)
+    seed = config.Property(proptype=int, default=None)
+
+    ...
+
+
+

In the YAML file we configured the task as follows:

+
-   type:       draco.synthesis.stream.SimulateSidereal
+    requires:   [tel, bt]
+    out:        sstream
+    params:
+        maps:   [ "testfg.h5" ]
+        save:   Yes
+        output_root: teststream_
+
+
+

Of the three properties available from the definition of SimulateSidereal we +have only configured one of them, the list of maps to process. The remaining two +entries of the params section are inherited from the pipeline base task. +These simply tell the pipeline to save the output of the task, with a base name +given by output_root.

+

The pipeline is run with the caput-pipeline script:

+
$ caput-pipeline run pipeline_params.yaml
+
+
+

What has it actually done? Let’s just quickly go through the tasks in order:

+
    +
  1. Load the beam transfer manager from disk. This just gives the pipeline access +to all the beam transfer matrices produced by the driftscan code.

  2. +
  3. Load a map from disk, use the beam transfers to transform it into a sidereal timestream.

  4. +
  5. Select the products from the timestream that are understood by the given beam +transfer manager. In this case it won’t change anything, but this task can +subset frequencies and products as well as average over redundant baselines.

  6. +
  7. Perform the m-mode transform on the sidereal timestream.

  8. +
  9. Apply the map maker to the m-modes to produce a dirty map.

  10. +
  11. Apply the map maker to the generate a Wiener filtered map.

  12. +
+
+
+

Ninja Techniques

+

Running on a cluster. Coming soon….

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file