Discovery
Next-generation pulsar-timing-array data analysis
Install / Use
/learn @nanograv/DiscoveryREADME
Discovery
Sphinx Documentation is here. The old README user guide is below as well.
<img src="discovery.png" alt="Logo" width="300" align="left" style="margin-right: 20px;"/>Discovery is a next-generation pulsar-timing-array data-analysis package, built for speed on a JAX backend that supports GPU execution and autodifferentiation. If Enterprise is Spock, logical and elegant, Discovery is all Scotty, fast, efficient, and not above a hack if it gets you to warp speed.
Requirements
Discovery needs a modern Python with numpy, scipy, jax, pyarrow. It will be happier running on an Nvidia GPU with CUDA-enabled JAX.
Discovery's subpackages (such as discovery.flow and the packages under discovery.samplers) require additional dependencies.
Examples
The folder examples contains a growing set of usage examples.
Data model
The Discovery data model consists of Kernel objects (think of a noise matrix N, which can be inverted and applied to a vector, N^{-1} y, or even sandwiched with it, y^T N^{-1} y) and of GP objects, consisting of a basis F (sized ntoas x ngp) and a prior/kernel Phi. The kernel WoodburyKernel(N, F, P) combines a noise kernel and a GP.
Pulsar data
Discovery uses lightweight Pulsar objects saved as Arrow Feather files. To convert an Enterprise Pulsar objects use discovery.Pulsar.save_feather(psr, filename, noisedict), where noisedict is an optional dictionary of default parameter values. Some example datasets (based on NG 15yr) are included in the data folder.
Noise and signal components (signals.py)
-
residuals(psr): just anumpyarray of residuals for pulsarpsr. -
makenoise_measurement(psr, [noisedict]): returns aKernelobject that implements EFAC + EQUAD measurement noise for pulsarpsr. Parameters are multiplexed to pulsar and backend (e.g.,B1855+09_430_ASP_efac), Enterprise-style. If those parameters appear isnoisedict, their values will be frozen to those specifications. -
makenoise_measurement_simple(psr, [noisedict]): same, but no backends. -
makegp_ecorr(psr, [noisedict]): returns aGPobject that implements ECORR measurement noise for pulsarpsr. The quantization style. Parameters are multiplexed to pulsar and backend, and frozen if included in noisedict. -
makegp_ecorr_simple(psr, [noisedict]): same, but no backends. -
makegp_fourier(psr, prior, components, [T, fourierbasis, common, name]): returns aGPobject that implements a finite GP over a vector basis. Herepriormust be a JAX-ready function with signaturepriorfunc(f, df, arg1, [...]), wherefis a vector of frequencies, anddfa vector of integration weights associated with the frequencies. The resulting GP parameters are named{psrname}_{name}_{argX}, unless they are included in the listcommon. The Fourier basis is generated by callingfourierbasis(psr, components, T)(see below), which must return(f, df, F). In this functionTdefaults to the pulsar span. -
makegp_fourier_global(psrs, prior, orf, components, T, [fourierbasis, name]): returns aGlobalVariableGPobject that implements a GP for all pulsars together, with a matched Fourier basis. Hereprioris the same as formakegp_fourier(with GP parameters named{name}_{argX}), andorfmust be a functionorf(pos1, pos2)of two numpy vectors, such ashd_orf,monopole_orf,dipole_orf, and the diagonaluncorrelated_orf. The Fourier basis is generated by callingfourierbasis(psr, components, T)(see below), which must return(f, df, F). In this functionTdoes not have a default (but seegetspan). Ifpriorandorfare given as equal-length lists of functions, the object implements the composite processsum_i GP(prior[i], orf[i])(with GP parameters named{name}_{orf[i].__name__}_{argX}).
makegp_improper(psr, fmat, [constant, name]): returns aGPobject with improper prior (formally, a constant vector equal toconstant) and basis matrixfmat. Hereconstantdefaults to1e40.makegp_timing(psr, [constant], [variance]): convenience function to callmakegp_improperwith the pulsar design matrixpsr.Mmatand prior diagonal valueconstant. Design-matrix columns are normalized. To obtain "physical" priors, usevarianceto a value in s^2 (e.g.,1e-12``) instead of settingconstant`.
GP basis and priors (signals.py)
-
fourierbasis(psr, components, [T]): returns(f, df, F)for a basis of interleaved sines and cosines evaluated overpsrTOAs with frequenciesk/T, withk = 1, ..., components. AgainTdefaults to the pulsar span. -
dmfourierbasis(psr, components, [T, fref]): same, but rescales the basis by(fref / psr.freqs)**2, useful to define DMGP. HereTdefaults to the pulsar span andfrefto 1400. -
getspan(psr or psrs): returns the TOA span of the pulsar or iterable of pulsars. Useful formakegp_fourierandmakegp_fourier_global. -
powerlaw(f, df, log10_A, gamma): implements the standard red-noise/GW spectrum10^(2 log10_A) f^(-gamma) (yr^(gamma - 3) / pi^2 / 12) df. This is a JAX-ready function, so one would passpowerlawtomakegp_fourier. -
freespectrum(f, df, log10_rho: typing.Sequence): implements10^(2 log10_rho). Note thatmakegp_fourieruses thelog10_rhoannotation to treat it as a vector; the resulting parameter name would be, e.g.,B1855+09_fourierGP_log10_rho(10)ifcomponents = 10. -
makepowerlaw_crn(components): makes the prior for a combined red-noise/common-process GP, limiting the common-process tocomponentsfrequencies. Returns a function with the signaturepowerlaw_crn(f, df, log10_A, gamma, crn_log10_A, crn_gamma); callingmakegp_fourier(..., common=['crn_log10_A, crn_gamma'], name='rednoise')would then result in parameter names['B1855+09_rednoise_log10_A', 'B1855+09_rednoise_gamma', ..., 'crn_log10_A', 'crn_gamma'].
Delays (signals.py)
makedelay(psr, delayfunc, [common, name]): returns a JAX-ready function that implements a deterministic delay forpsr. Heredelayfuncmust be a JAX-ready function with signaturedelayfunc(arg1, ...). The resulting delay parameters are named{psrname}_{name}_{argX}, unless they are included in the listcommon. If the first few arguments are defined attributes ofpsr(e.g.,toasorfreqs), they are automatically passed to the function and excluded fromparams; however they must come before all the other variable parameters.make_solardm(psr)[insolar.py]: calculates the DM delay in a 1/r^2 solar wind density model. Returns a function with signaturesolardm(n_earth).make_chromaticdecay(psr)[insolar.py]: calculates a chromatic exponential-dip delay. Returns a function with signaturedecay(t0, log10_Amp, log10_tau, idx).
Likelihood (likelihood.py)
-
PulsarLikelihood(signals, concat=True): returns aPulsarLikelihoodobject, with alogLproperty that implements the single-pulsar likelihood as a JAX-ready function. The likelihood takes as a single argument a dictionary of parameters named as discussed above. Heresignalsis an iterable that must contain exactly one residual vector, exactly one noiseKernel, any number ofGPobjects, and any number of deterministic delays (any callable). Ifconcat=False, the likelihood is built by nestingWoodburyKernels, first consumingConstantGPobjects (those without parameters) and thenVariableGP, but otherwise respecting the order insignals. Ifconcat=True, theConstantGPs andVariableGPs are separately concatenated, and then nested. -
GlobalLikelihood(psls, globalgp=None): returnsGlobalLikelihoodobject, with alogLproperty that implements the multi-pulsar likelihood as a JAX-ready function. The likelihood takes as a single argument a dictionary of parameters. Herepslsis an iterable that may contain any number ofPulsarLikelihoodobjects, andglobalgpis aGlobalVariableGPobject, such as returned bymakegp_fourier_global, encoding a joint GP for all pulsars. -
The two likelihood objects have a
sampleproperty—a JAX-ready function that generates a random realization of the data if given a JAX key and a dictionary of parameters. (According to JAX protocol,sampleactually returns a tuple consisting of the "split" key and the data realization.) -
The
PulsarLikelihoodandGlobalLikelihoodobjects have asample_conditional(key, params) -> (key, dict)property–a JAX-ready function that returns a dictionary of GP coefficients drawn from the conditional (normal) probability distribution of the parameter-dependent GPs (forPulsarLikelihood) or of theglobalgp(s), given theparams. This function callsGlobalLikelihood.conditional(params), which returns a concatenated vectormuof the mean GP coefficients and the resultcfof runningscipy.linalg.cho_factoron the matrixPhi^-1 + T^t K T, wherePhiandTare prior and basis of the relevant GP.
Priors (prior.py)
makelogprior_uniform(params, [priordict]): returns a functionlogprior(params)that evaluates the total log prior according topriordict(given, e.g., as{'FourierGP_log10_A': [-18, -11]'}). Some standard [enterprise_extensions](https:
