def pl_cov(t, Si=4.33, fL=1.0/20, approx_ksum=False): """ Analytically calculate the covariance matrix for a stochastic signal with a power spectral density given by pl_psd. @param t: Time-series timestamps @param Si: Spectral index of power-law spectrum @param fL: Low-frequency cut-off @param approx_ksum: Whether we approximate the infinite sum @return: Covariance matrix """ EulerGamma = 0.5772156649015329 alpha = 0.5*(3.0-Si) # Make a mesh-grid for the covariance matrix elements t1, t2 = np.meshgrid(t,t) x = 2 * np.pi * fL * np.abs(t1 - t2) del t1 del t2 # The tolerance for which to use the Gamma function expansion tol = 1e-5 # the exact solutions for alpha = 0, -1 should be acceptable in a small # interval around them... if abs(alpha) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = cosx - x * sinx sinint, cosint = sl.sici(x) corr = (fL**-2) / (24 * math.pi**2) * (power + x**2 * cosint) elif abs(alpha + 1) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = 6 * cosx - 2 * x * sinx - x**2 * cosx + x**3 * sinx sinint, cosint = ss.sici(x) corr = (fL**-4) / (288 * np.pi**2) * (power - x**4 * cosint) else: # leading-order expansion of Gamma[-2+2*alpha]*Cos[Pi*alpha] around -0.5 # and 0.5 if abs(alpha - 0.5) < tol: cf = np.pi/2 + (np.pi - np.pi*EulerGamma) * (alpha - 0.5) elif abs(alpha + 0.5) < tol: cf = -np.pi/12 + (-11*np.pi/36 + EulerGamma*math.pi/6) * (alpha + 0.5) elif abs(alpha + 1.5) < tol: cf = np.pi/240 + (137*np.pi/7200 - EulerGamma*np.pi/120) * (alpha + 1.5) else: cf = ss.gamma(-2+2*alpha) * np.cos(np.pi*alpha) power = cf * x**(2-2*alpha) # Mathematica solves Sum[(-1)^n x^(2 n)/((2 n)! (2 n + 2 alpha - 2)), # {n, 0, Infinity}] as HypergeometricPFQ[{-1+alpha}, {1/2,alpha}, # -(x^2/4)]/(2 alpha - 2) the corresponding scipy.special function is # hyp1f2 (which returns value and error) if approx_ksum: ksum = 1.0 / (2*alpha - 2) - x**2 / (4*alpha) + x**4 / (24 * (2 + 2*alpha)) else: ksum = ss.hyp1f2(alpha-1,0.5,alpha,-0.25*x**2)[0]/(2*alpha-2) del x corr = -(fL**(-2+2*alpha)) * (power + ksum) return corr
def pl_cov(t, Si=4.33, fL=1.0 / 20, approx_ksum=False): """ Analytically calculate the covariance matrix for a stochastic signal with a power spectral density given by pl_psd. @param t: Time-series timestamps @param Si: Spectral index of power-law spectrum @param fL: Low-frequency cut-off @param approx_ksum: Whether we approximate the infinite sum @return: Covariance matrix """ EulerGamma = 0.5772156649015329 alpha = 0.5 * (3.0 - Si) # Make a mesh-grid for the covariance matrix elements t1, t2 = np.meshgrid(t, t) x = 2 * np.pi * fL * np.abs(t1 - t2) del t1 del t2 # The tolerance for which to use the Gamma function expansion tol = 1e-5 # the exact solutions for alpha = 0, -1 should be acceptable in a small # interval around them... if abs(alpha) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = cosx - x * sinx sinint, cosint = sl.sici(x) corr = (fL**-2) / (24 * math.pi**2) * (power + x**2 * cosint) elif abs(alpha + 1) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = 6 * cosx - 2 * x * sinx - x**2 * cosx + x**3 * sinx sinint, cosint = ss.sici(x) corr = (fL**-4) / (288 * np.pi**2) * (power - x**4 * cosint) else: # leading-order expansion of Gamma[-2+2*alpha]*Cos[Pi*alpha] around -0.5 # and 0.5 if abs(alpha - 0.5) < tol: cf = np.pi / 2 + (np.pi - np.pi * EulerGamma) * (alpha - 0.5) elif abs(alpha + 0.5) < tol: cf = -np.pi / 12 + (-11 * np.pi / 36 + EulerGamma * math.pi / 6) * (alpha + 0.5) elif abs(alpha + 1.5) < tol: cf = np.pi / 240 + (137 * np.pi / 7200 - EulerGamma * np.pi / 120) * (alpha + 1.5) else: cf = ss.gamma(-2 + 2 * alpha) * np.cos(np.pi * alpha) power = cf * x**(2 - 2 * alpha) # Mathematica solves Sum[(-1)^n x^(2 n)/((2 n)! (2 n + 2 alpha - 2)), # {n, 0, Infinity}] as HypergeometricPFQ[{-1+alpha}, {1/2,alpha}, # -(x^2/4)]/(2 alpha - 2) the corresponding scipy.special function is # hyp1f2 (which returns value and error) if approx_ksum: ksum = 1.0 / (2 * alpha - 2) - x**2 / (4 * alpha) + x**4 / (24 * (2 + 2 * alpha)) else: ksum = ss.hyp1f2(alpha - 1, 0.5, alpha, -0.25 * x**2)[0] / (2 * alpha - 2) del x corr = -(fL**(-2 + 2 * alpha)) * (power + ksum) return corr
def Cgw_sec(model, alpha=-2.0/3.0, fL=1.0/500, approx_ksum=False, inc_cor=True): """ Compute the residual covariance matrix for an hc = 1 x (f year)^alpha GW background. Result is in units of (100 ns)^2. Modified from Michele Vallisneri's mc3pta (https://github.com/vallis/mc3pta) @param: list of libstempo pulsar objects (as returned by readRealisations) @param: the H&D correlation matrix @param: the TOAs @param: the GWB spectral index @param: the low-frequency cut-off @param: approx_ksum """ psrobs = model[6] alphaab = model[5] times_f = model[0] day = 86400.0 # seconds, sidereal (?) year = 3.15581498e7 # seconds, sidereal (?) EulerGamma = 0.5772156649015329 npsrs = alphaab.shape[0] t1, t2 = np.meshgrid(times_f,times_f) # t1, t2 are in units of days; fL in units of 1/year (sidereal for both?) # so typical values here are 10^-6 to 10^-3 x = 2 * np.pi * (day/year) * fL * np.abs(t1 - t2) del t1 del t2 # note that the gamma is singular for all half-integer alpha < 1.5 # # for -1 < alpha < 0, the x exponent ranges from 4 to 2 (it's 3.33 for alpha = -2/3) # so for the lower alpha values it will be comparable in value to the x**2 term of ksum # # possible alpha limits for a search could be [-0.95,-0.55] in which case the sign of `power` # is always positive, and the x exponent ranges from ~ 3 to 4... no problem with cancellation # The tolerance for which to use the Gamma function expansion tol = 1e-5 # the exact solutions for alpha = 0, -1 should be acceptable in a small interval around them... if abs(alpha) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = cosx - x * sinx sinint, cosint = sl.sici(x) corr = (year**2 * fL**-2) / (24 * math.pi**2) * (power + x**2 * cosint) elif abs(alpha + 1) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = 6 * cosx - 2 * x * sinx - x**2 * cosx + x**3 * sinx sinint, cosint = ss.sici(x) corr = (year**2 * fL**-4) / (288 * np.pi**2) * (power - x**4 * cosint) else: # leading-order expansion of Gamma[-2+2*alpha]*Cos[Pi*alpha] around -0.5 and 0.5 if abs(alpha - 0.5) < tol: cf = np.pi/2 + (np.pi - np.pi*EulerGamma) * (alpha - 0.5) elif abs(alpha + 0.5) < tol: cf = -np.pi/12 + (-11*np.pi/36 + EulerGamma*math.pi/6) * (alpha + 0.5) elif abs(alpha + 1.5) < tol: cf = np.pi/240 + (137*np.pi/7200 - EulerGamma*np.pi/120) * (alpha + 1.5) else: cf = ss.gamma(-2+2*alpha) * np.cos(np.pi*alpha) power = cf * x**(2-2*alpha) # Mathematica solves Sum[(-1)^n x^(2 n)/((2 n)! (2 n + 2 alpha - 2)), {n, 0, Infinity}] # as HypergeometricPFQ[{-1+alpha}, {1/2,alpha}, -(x^2/4)]/(2 alpha - 2) # the corresponding scipy.special function is hyp1f2 (which returns value and error) # TO DO, for speed: could replace with the first few terms of the sum! if approx_ksum: ksum = 1.0 / (2*alpha - 2) - x**2 / (4*alpha) + x**4 / (24 * (2 + 2*alpha)) else: ksum = ss.hyp1f2(alpha-1,0.5,alpha,-0.25*x**2)[0]/(2*alpha-2) del x # this form follows from Eq. (A31) of Lee, Jenet, and Price ApJ 684:1304 (2008) corr = -(year**2 * fL**(-2+2*alpha)) / (12 * np.pi**2) * (power + ksum) if inc_cor: # multiply by alphaab; there must be a more numpythonic way to do it # npsrs psrobs inda, indb = 0, 0 for a in range(npsrs): for b in range(npsrs): corr[inda:inda+psrobs[a], indb:indb+psrobs[b]] *= alphaab[a, b] indb += psrobs[b] indb = 0 inda += psrobs[a] return corr
def Cred_sec(toas, alpha=-2.0/3.0, fL=1.0/20, approx_ksum=False): day = 86400.0 year = 3.15581498e7 EulerGamma = 0.5772156649015329 psrobs = [len(toas)] alphaab = np.array([[1.0]]) times_f = toas / day npsrs = alphaab.shape[0] t1, t2 = np.meshgrid(times_f,times_f) # t1, t2 are in units of days; fL in units of 1/year (sidereal for both?) # so typical values here are 10^-6 to 10^-3 x = 2 * np.pi * (day/year) * fL * np.abs(t1 - t2) del t1 del t2 # note that the gamma is singular for all half-integer alpha < 1.5 # # for -1 < alpha < 0, the x exponent ranges from 4 to 2 (it's 3.33 for alpha = -2/3) # so for the lower alpha values it will be comparable in value to the x**2 term of ksum # # possible alpha limits for a search could be [-0.95,-0.55] in which case the sign of `power` # is always positive, and the x exponent ranges from ~ 3 to 4... no problem with cancellation # The tolerance for which to use the Gamma function expansion tol = 1e-5 # the exact solutions for alpha = 0, -1 should be acceptable in a small interval around them... if abs(alpha) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = cosx - x * sinx sinint, cosint = sl.sici(x) corr = (year**2 * fL**-2) / (24 * math.pi**2) * (power + x**2 * cosint) elif abs(alpha + 1) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = 6 * cosx - 2 * x * sinx - x**2 * cosx + x**3 * sinx sinint, cosint = ss.sici(x) corr = (year**2 * fL**-4) / (288 * np.pi**2) * (power - x**4 * cosint) else: # leading-order expansion of Gamma[-2+2*alpha]*Cos[Pi*alpha] around -0.5 and 0.5 if abs(alpha - 0.5) < tol: cf = np.pi/2 + (np.pi - np.pi*EulerGamma) * (alpha - 0.5) elif abs(alpha + 0.5) < tol: cf = -np.pi/12 + (-11*np.pi/36 + EulerGamma*math.pi/6) * (alpha + 0.5) elif abs(alpha + 1.5) < tol: cf = np.pi/240 + (137*np.pi/7200 - EulerGamma*np.pi/120) * (alpha + 1.5) else: cf = ss.gamma(-2+2*alpha) * np.cos(np.pi*alpha) power = cf * x**(2-2*alpha) # Mathematica solves Sum[(-1)^n x^(2 n)/((2 n)! (2 n + 2 alpha - 2)), {n, 0, Infinity}] # as HypergeometricPFQ[{-1+alpha}, {1/2,alpha}, -(x^2/4)]/(2 alpha - 2) # the corresponding scipy.special function is hyp1f2 (which returns value and error) # TO DO, for speed: could replace with the first few terms of the sum! if approx_ksum: ksum = 1.0 / (2*alpha - 2) - x**2 / (4*alpha) + x**4 / (24 * (2 + 2*alpha)) else: ksum = ss.hyp1f2(alpha-1,0.5,alpha,-0.25*x**2)[0]/(2*alpha-2) del x # this form follows from Eq. (A31) of Lee, Jenet, and Price ApJ 684:1304 (2008) corr = -(year**2 * fL**(-2+2*alpha)) / (12 * np.pi**2) * (power + ksum) return corr
def Cgw_sec(model, alpha=-2.0 / 3.0, fL=1.0 / 500, approx_ksum=False, inc_cor=True): """ Compute the residual covariance matrix for an hc = 1 x (f year)^alpha GW background. Result is in units of (100 ns)^2. Modified from Michele Vallisneri's mc3pta (https://github.com/vallis/mc3pta) @param: list of libstempo pulsar objects (as returned by readRealisations) @param: the H&D correlation matrix @param: the TOAs @param: the GWB spectral index @param: the low-frequency cut-off @param: approx_ksum """ psrobs = model[6] alphaab = model[5] times_f = model[0] day = 86400.0 # seconds, sidereal (?) year = 3.15581498e7 # seconds, sidereal (?) EulerGamma = 0.5772156649015329 npsrs = alphaab.shape[0] t1, t2 = np.meshgrid(times_f, times_f) # t1, t2 are in units of days; fL in units of 1/year (sidereal for both?) # so typical values here are 10^-6 to 10^-3 x = 2 * np.pi * (day / year) * fL * np.abs(t1 - t2) del t1 del t2 # note that the gamma is singular for all half-integer alpha < 1.5 # # for -1 < alpha < 0, the x exponent ranges from 4 to 2 (it's 3.33 for alpha = -2/3) # so for the lower alpha values it will be comparable in value to the x**2 term of ksum # # possible alpha limits for a search could be [-0.95,-0.55] in which case the sign of `power` # is always positive, and the x exponent ranges from ~ 3 to 4... no problem with cancellation # The tolerance for which to use the Gamma function expansion tol = 1e-5 # the exact solutions for alpha = 0, -1 should be acceptable in a small interval around them... if abs(alpha) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = cosx - x * sinx sinint, cosint = sl.sici(x) corr = (year**2 * fL**-2) / (24 * math.pi**2) * (power + x**2 * cosint) elif abs(alpha + 1) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = 6 * cosx - 2 * x * sinx - x**2 * cosx + x**3 * sinx sinint, cosint = ss.sici(x) corr = (year**2 * fL**-4) / (288 * np.pi**2) * (power - x**4 * cosint) else: # leading-order expansion of Gamma[-2+2*alpha]*Cos[Pi*alpha] around -0.5 and 0.5 if abs(alpha - 0.5) < tol: cf = np.pi / 2 + (np.pi - np.pi * EulerGamma) * (alpha - 0.5) elif abs(alpha + 0.5) < tol: cf = -np.pi / 12 + (-11 * np.pi / 36 + EulerGamma * math.pi / 6) * (alpha + 0.5) elif abs(alpha + 1.5) < tol: cf = np.pi / 240 + (137 * np.pi / 7200 - EulerGamma * np.pi / 120) * (alpha + 1.5) else: cf = ss.gamma(-2 + 2 * alpha) * np.cos(np.pi * alpha) power = cf * x**(2 - 2 * alpha) # Mathematica solves Sum[(-1)^n x^(2 n)/((2 n)! (2 n + 2 alpha - 2)), {n, 0, Infinity}] # as HypergeometricPFQ[{-1+alpha}, {1/2,alpha}, -(x^2/4)]/(2 alpha - 2) # the corresponding scipy.special function is hyp1f2 (which returns value and error) # TO DO, for speed: could replace with the first few terms of the sum! if approx_ksum: ksum = 1.0 / (2 * alpha - 2) - x**2 / (4 * alpha) + x**4 / (24 * (2 + 2 * alpha)) else: ksum = ss.hyp1f2(alpha - 1, 0.5, alpha, -0.25 * x**2)[0] / (2 * alpha - 2) del x # this form follows from Eq. (A31) of Lee, Jenet, and Price ApJ 684:1304 (2008) corr = -(year**2 * fL**(-2 + 2 * alpha)) / (12 * np.pi**2) * (power + ksum) if inc_cor: # multiply by alphaab; there must be a more numpythonic way to do it # npsrs psrobs inda, indb = 0, 0 for a in range(npsrs): for b in range(npsrs): corr[inda:inda + psrobs[a], indb:indb + psrobs[b]] *= alphaab[a, b] indb += psrobs[b] indb = 0 inda += psrobs[a] return corr
def Cgw_sec(toas, alpha=-2.0 / 3.0, fL=1.0 / 20, approx_ksum=False): day = 86400.0 year = 3.15581498e7 EulerGamma = 0.5772156649015329 psrobs = [len(toas)] alphaab = np.array([[1.0]]) times_f = toas / day npsrs = alphaab.shape[0] t1, t2 = np.meshgrid(times_f, times_f) # t1, t2 are in units of days; fL in units of 1/year (sidereal for both?) # so typical values here are 10^-6 to 10^-3 x = 2 * np.pi * (day / year) * fL * np.abs(t1 - t2) del t1 del t2 # note that the gamma is singular for all half-integer alpha < 1.5 # # for -1 < alpha < 0, the x exponent ranges from 4 to 2 (it's 3.33 for alpha = -2/3) # so for the lower alpha values it will be comparable in value to the x**2 term of ksum # # possible alpha limits for a search could be [-0.95,-0.55] in which case the sign of `power` # is always positive, and the x exponent ranges from ~ 3 to 4... no problem with cancellation # The tolerance for which to use the Gamma function expansion tol = 1e-5 # the exact solutions for alpha = 0, -1 should be acceptable in a small interval around them... if abs(alpha) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = cosx - x * sinx sinint, cosint = sl.sici(x) corr = (year**2 * fL**-2) / (24 * math.pi**2) * (power + x**2 * cosint) elif abs(alpha + 1) < 1e-7: cosx, sinx = np.cos(x), np.sin(x) power = 6 * cosx - 2 * x * sinx - x**2 * cosx + x**3 * sinx sinint, cosint = ss.sici(x) corr = (year**2 * fL**-4) / (288 * np.pi**2) * (power - x**4 * cosint) else: # leading-order expansion of Gamma[-2+2*alpha]*Cos[Pi*alpha] around -0.5 and 0.5 if abs(alpha - 0.5) < tol: cf = np.pi / 2 + (np.pi - np.pi * EulerGamma) * (alpha - 0.5) elif abs(alpha + 0.5) < tol: cf = -np.pi / 12 + (-11 * np.pi / 36 + EulerGamma * math.pi / 6) * (alpha + 0.5) elif abs(alpha + 1.5) < tol: cf = np.pi / 240 + (137 * np.pi / 7200 - EulerGamma * np.pi / 120) * (alpha + 1.5) else: cf = ss.gamma(-2 + 2 * alpha) * np.cos(np.pi * alpha) power = cf * x**(2 - 2 * alpha) # Mathematica solves Sum[(-1)^n x^(2 n)/((2 n)! (2 n + 2 alpha - 2)), {n, 0, Infinity}] # as HypergeometricPFQ[{-1+alpha}, {1/2,alpha}, -(x^2/4)]/(2 alpha - 2) # the corresponding scipy.special function is hyp1f2 (which returns value and error) # TO DO, for speed: could replace with the first few terms of the sum! if approx_ksum: ksum = 1.0 / (2 * alpha - 2) - x**2 / (4 * alpha) + x**4 / (24 * (2 + 2 * alpha)) else: ksum = ss.hyp1f2(alpha - 1, 0.5, alpha, -0.25 * x**2)[0] / (2 * alpha - 2) del x # this form follows from Eq. (A31) of Lee, Jenet, and Price ApJ 684:1304 (2008) corr = -(year**2 * fL**(-2 + 2 * alpha)) / (12 * np.pi**2) * (power + ksum) return corr
def Cgw_100ns(alphaab,times_f,alpha=-2/3,fL=1.0/10000,approx_ksum=False): """Compute the residual covariance matrix for an hc = 1 x (f year)^alpha GW background. Result is in units of (100 ns)^2.""" t1, t2 = N.meshgrid(times_f,times_f) # t1, t2 are in units of days; fL in units of 1/year (sidereal for both?) # so typical values here are 10^-6 to 10^-3 x = 2 * math.pi * (day/year) * fL * N.abs(t1 - t2) # note that the gamma is singular for all half-integer alpha < 1.5 # # for -1 < alpha < 0, the x exponent ranges from 4 to 2 (it's 3.33 for alpha = -2/3) # so for the lower alpha values it will be comparable in value to the x**2 term of ksum # # possible alpha limits for a search could be [-0.95,-0.55] in which case the sign of `power` # is always positive, and the x exponent ranges from ~ 3 to 4... no problem with cancellation # the variance, as computed below, is in units of year^2; we convert it # to units of (100 ns)^2, so the multiplier is ~ 10^28 year100ns = year/1e-7 tol = 1e-5 # the exact solutions for alpha = 0, -1 should be acceptable in a small interval around them... if abs(alpha) < 1e-7: cosx, sinx = N.cos(x), N.sin(x) power = cosx - x * sinx sinint, cosint = SL.sici(x) corr = (year100ns**2 * fL**-2) / (24 * math.pi**2) * (power + x**2 * cosint) elif abs(alpha + 1) < 1e-7: cosx, sinx = N.cos(x), N.sin(x) power = 6 * cosx - 2 * x * sinx - x**2 * cosx + x**3 * sinx sinint, cosint = SL.sici(x) corr = (year100ns**2 * fL**-4) / (288 * math.pi**2) * (power - x**4 * cosint) else: # leading-order expansion of Gamma[-2+2*alpha]*Cos[Pi*alpha] around -0.5 and 0.5 if abs(alpha - 0.5) < tol: cf = math.pi/2 + (math.pi - math.pi*EulerGamma) * (alpha - 0.5) elif abs(alpha + 0.5) < tol: cf = -math.pi/12 + (-11*math.pi/36 + EulerGamma*math.pi/6) * (alpha + 0.5) elif abs(alpha + 1.5) < tol: cf = math.pi/240 + (137*math.pi/7200 - EulerGamma*math.pi/120) * (alpha + 1.5) else: cf = SS.gamma(-2+2*alpha) * math.cos(math.pi*alpha) power = cf * x**(2-2*alpha) # Mathematica solves Sum[(-1)^n x^(2 n)/((2 n)! (2 n + 2 alpha - 2)), {n, 0, Infinity}] # as HypergeometricPFQ[{-1+alpha}, {1/2,alpha}, -(x^2/4)]/(2 alpha - 2) # the corresponding scipy.special function is hyp1f2 (which returns value and error) # TO DO, for speed: could replace with the first few terms of the sum! if approx_ksum: ksum = 1.0 / (2*alpha - 2) - x**2 / (4*alpha) + x**4 / (24 * (2 + 2*alpha)) else: ksum = SS.hyp1f2(alpha-1,0.5,alpha,-0.25*x**2)[0]/(2*alpha-2) # this form follows from Eq. (A31) of Lee, Jenet, and Price ApJ 684:1304 (2008) corr = -(year100ns**2 * fL**(-2+2*alpha)) / (12 * math.pi**2) * (power + ksum) # multiply by alphaab; there must be a more numpythonic way to do it ps, ts = len(alphaab), len(times_f) / len(alphaab) for i in range(ps): for j in range(ps): corr[i*ts:(i+1)*ts,j*ts:(j+1)*ts] *= alphaab[i,j] return corr