def testNormalTriad(self): """ Check the correct working of the normalTriad() function. """ phi=array([0.0,pi/2,pi,3*pi/2,0.0,0.0]) theta=array([0.0,0.0,0.0,0.0,pi/2,-pi/2]) pExpected=array([[0,-1,0,1,0,0], [1,0,-1,0,1,1], [0,0,0,0,0,0]]) qExpected=array([[0,0,0,0,-1,1], [0,0,0,0,0,0], [1,1,1,1,0,0]]) rExpected=array([[1,0,-1,0,0,0], [0,1,0,-1,0,0], [0,0,0,0,1,-1]]) p, q, r = normalTriad(phi,theta) assert_array_almost_equal(pExpected, p, decimal=15) assert_array_almost_equal(qExpected, q, decimal=15) assert_array_almost_equal(rExpected, r, decimal=15) phiRandom = 2.0*pi*rand(10) thetaRandom = -pi/2.0+pi*rand(10) z=array([0,0,1]) for phi in phiRandom: for theta in thetaRandom: p, q, r = normalTriad(phi,theta) rExpected=array([cos(phi)*cos(theta), sin(phi)*cos(theta), sin(theta)]) pExpected=cross(z,rExpected) pExpected=pExpected/sqrt(dot(pExpected,pExpected)) qExpected=cross(rExpected,pExpected) assert_array_almost_equal(pExpected, p, decimal=8) assert_array_almost_equal(qExpected, q, decimal=8) assert_array_almost_equal(rExpected, r, decimal=8)
def propagate_astrometry(self, phi, theta, parallax, muphistar, mutheta, vrad, t0, t1): """ Propagate the position of a source from the reference epoch t0 to the new epoch t1. Parameters ---------- phi : float Longitude at reference epoch (radians). theta : float Latitude at reference epoch (radians). parallax : float Parallax at the reference epoch (mas). muphistar : float Proper motion in longitude (including cos(latitude) term) at reference epoch (mas/yr). mutheta : float Proper motion in latitude at reference epoch (mas/yr). vrad : float Radial velocity at reference epoch (km/s). t0 : float Reference epoch (Julian years). t1 : float New epoch (Julian years). Returns ------- Astrometric parameters, including the "radial proper motion" (NOT the radial velocity), at the new epoch. phi1, theta1, parallax1, muphistar1, mutheta1, mur1 = epoch_prop_pos(..., t0, t1) """ t = t1-t0 p0, q0, r0 = normalTriad(phi, theta) # Convert input data to units of radians and Julian year. Use ICRS coordinate names internally to # avoid errors in translating the formulae to code. pmra0 = muphistar*self.mastorad pmdec0 = mutheta*self.mastorad plx0 = parallax*self.mastorad pmr0 = vrad*parallax/auKmYearPerSec*self.mastorad pmtot0sqr = (muphistar**2 + mutheta**2) * self.mastorad**2 # Proper motion vector pmvec0 = pmra0*p0+pmdec0*q0 f = (1 + 2*pmr0*t + (pmtot0sqr+pmr0**2)*t**2)**(-0.5) u = (r0*(1+pmr0*t) + pmvec0*t)*f _, phi1, theta1 = cartesianToSpherical(u[0], u[1], u[2]) parallax1 = parallax*f pmr1 = (pmr0+(pmtot0sqr + pmr0**2)*t)*f**2 pmvec1 = (pmvec0*(1+pmr0*t) - r0*pmr0**2*t)*f**3 p1, q1, r1 = normalTriad(phi1, theta1) muphistar1 = sum(p1*pmvec1/self.mastorad, axis=0) mutheta1 = sum(q1*pmvec1/self.mastorad, axis =0) murad1 = pmr1/self.mastorad return phi1, theta1, parallax1, muphistar1, mutheta1, murad1
def radial_velocity_distribution(PDFs, ra, dec, plx, mura, mudec, C, x = np.arange(-500., 500., 0.0025), v_r_err = 0.): p_vr_M = [] p_vr_C = [] p_vr_C_temp = [] ICRS_to_galactic = coords.CoordinateTransformation(coords.Transformations.ICRS2GAL) l, b = ICRS_to_galactic.transformSkyCoordinates(ra, dec) pml, pmb = ICRS_to_galactic.transformProperMotions(ra, dec, mura,mudec) C_gal = ICRS_to_galactic.transformCovarianceMatrix(ra, dec, C) jacobian = array([[-pml * k/plx**2., k/plx, 0. ], [-pmb * k/plx**2., 0. , k/plx]]) cov_v_tan = jacobian.dot(C_gal[2:5,2:5]).dot(jacobian.T) v_t_gal = array([pml*k/plx, pmb*k/plx]) p_gal, q_gal, r_gal = normalTriad(l, b) coordtrans = np.vstack((p_gal, q_gal, r_gal)) for N in PDFs: mean_v_rad_M = r_gal.dot(N.mean) sig_v_rad_M = np.sqrt(r_gal.dot(N.cov).dot(r_gal) + v_r_err**2.) newPDF_M = univariate(mean = mean_v_rad_M, sigma = sig_v_rad_M) newPDF_M.amp = N.amp p_vr_M.append(newPDF_M) mean_v_l = p_gal.dot(N.mean) mean_v_b = q_gal.dot(N.mean) mean_v_tan = array([mean_v_l, mean_v_b]) cov_pqr_gal = coordtrans.dot(N.cov).dot(coordtrans.T) cov_pqr_gal[:2,:2] += cov_v_tan cov_pqr_gal[2,2] += v_r_err**2. A = cov_pqr_gal[:2,:2] B = cov_pqr_gal[2, 2] C = cov_pqr_gal[2,:2] A_inv = np.linalg.inv(A) mean_v_rad_C = mean_v_rad_M + C.dot(A_inv.dot(v_t_gal - mean_v_tan)) sig_v_rad_C = np.sqrt(B - C.dot(A_inv).dot(C.T)) newPDF_C = norm(loc=mean_v_rad_C, scale=sig_v_rad_C) newPDF_C.A = A newPDF_C.mean_v_tan = mean_v_tan prob_v_tan_gal = N.amp * m_n.pdf(v_t_gal, mean = mean_v_tan, cov = A) newPDF_C.prob_v_tan_gal = prob_v_tan_gal p_vr_C_temp.append(newPDF_C) prob_v_tan_gal = np.sum(N_C.prob_v_tan_gal for N_C in p_vr_C_temp) for N, N_C in zip(PDFs, p_vr_C_temp): N_C.q = N.amp * m_n.pdf(v_t_gal, mean = N_C.mean_v_tan, cov = N_C.A) / prob_v_tan_gal p_vr_C.append(N_C) p_m = array([N_M.amp * N_M.pdf(x) for N_M in p_vr_M]) p_c = array([N_C.q * N_C.pdf(x) for N_C in p_vr_C]) p_m = p_m.sum(axis=0) p_c = p_c.sum(axis=0) y = np.vstack((x, p_c, p_m)) return y
def _like3(init_par, alpha, delta, plx_obs, mualpha_obs, mudelta_obs, sigma_obs, ccoef, i): """ Estimate the likelihood function for every set of observation. Then, estimate the function U(init_par) that needs to be minimized to find the best fit to the parameters. Parameters: ------------ init_par - Set of initial values for: 1) i-th parallax [mas]; 2) The cluster centroid velocity [vx_0, vy_0, vz_0] [km/s]; 3) The cluster velocity dispersion, sigma_v [km/s]; alpha, delta - position of i-th star [rad]; plx_obs, mualpha_obs, mudelta_obs - observed values for parallaxes and proper motions [mas, mas/yr] of i-th star; vrad_obs - radial velocities [km/s] of i-th star; sigma_obs - 3-dim array of observed errors for parallax and proper motion [mas, mas/yr] of i-th star; sigma_vrad - error on radial velocities [km/s] of i-th star; ccoef - 3-dim array of correlation coefficients from the HIP catalogue of i-th star. Returns: ----------- g - values of g_i(theta) for the i-th star, see eq. 19 in Lindegren+2000; U(init_par) - The function in Eq. (18) of Lindegren+2000, for the i-th star. """ plx_mod, v, sigma_v = init_par[i], init_par[-4:-1], init_par[-1] p, q, r = normalTriad(alpha, delta) mualpha_mod = np.dot(np.transpose(p),v)*plx_mod/_A mudelta_mod = np.dot(np.transpose(q),v)*plx_mod/_A sigma_plx, sigma_mualpha, sigma_mudelta = sigma_obs r_plx_muRa, r_plx_muDec, r_muRa_muDec = ccoef[0], ccoef[1], ccoef[2] C = np.zeros((3,3),dtype=np.float64) C[0,0],C[1,1],C[2,2] = sigma_plx**2.,sigma_mualpha**2., sigma_mudelta**2. C[0,1], C[0,2] = r_plx_muRa*sigma_plx*sigma_mualpha, r_plx_muDec*sigma_plx*sigma_mudelta C[1,0], C[1,2] = r_plx_muRa*sigma_plx*sigma_mualpha, r_muRa_muDec*sigma_mualpha*sigma_mudelta C[2,0], C[2,1] = r_plx_muDec*sigma_plx*sigma_mudelta, r_muRa_muDec*sigma_mualpha*sigma_mudelta E = np.zeros((3,3),dtype=np.float64) E[1,1],E[2,2] = (sigma_v**2.)*(plx_mod/_A)**2., (sigma_v**2.)*(plx_mod/_A)**2. D = np.add(E,C) detD = det(D) invD = inv(D) a_c = np.array([plx_obs - plx_mod, mualpha_obs - mualpha_mod, mudelta_obs-mudelta_mod]) g_func = row_matrix_col(a_c, a_c, invD) return detD, g_func
def _getJacobian(self, phi, theta): """ Calculates the Jacobian for the transformation of the position errors and proper motion errors between coordinate systems. This Jacobian is also the rotation matrix for the transformation of proper motions. See section 1.5.3 of the Hipparcos Explanatory Volume 1 (equation 1.5.20). This matrix has the following form: | c s | J = | | | -s c | Parameters ---------- phi - The longitude-like angle of the position of the source (radians). theta - The latitude-like angle of the position of the source (radians). Returns ------- c, s - The Jacobian matrix elements c and s corresponding to (phi, theta) and the currently desired coordinate system transformation. """ p, q, r = normalTriad(phi, theta) # zRot = z-axis of new coordinate system expressed in terms of old system zRot = self.rotationMatrix[2, :] if (p.ndim == 2): zRotAll = tile(zRot, p.shape[1]).reshape(p.shape[1], 3) pRot = cross(zRotAll, r.T) normPRot = norm(pRot, axis=1) for i in range(pRot.shape[0]): pRot[i] = pRot[i] / normPRot[i] c = zeros(pRot.shape[0]) s = zeros(pRot.shape[0]) for i in range(pRot.shape[0]): c[i] = dot(pRot[i], p.T[i]) s[i] = dot(pRot[i], q.T[i]) return c, s else: pRot = cross(zRot, r.T) pRot = pRot / norm(pRot) return dot(pRot, p), dot(pRot, q)
def _getJacobian(self, phi, theta): """ Calculates the Jacobian for the transformation of the position errors and proper motion errors between coordinate systems. This Jacobian is also the rotation matrix for the transformation of proper motions. See section 1.5.3 of the Hipparcos Explanatory Volume 1 (equation 1.5.20). This matrix has the following form: | c s | J = | | | -s c | Parameters ---------- phi - The longitude-like angle of the position of the source (radians). theta - The latitude-like angle of the position of the source (radians). Returns ------- c, s - The Jacobian matrix elements c and s corresponding to (phi, theta) and the currently desired coordinate system transformation. """ p, q, r = normalTriad(phi, theta) # zRot = z-axis of new coordinate system expressed in terms of old system zRot = self.rotationMatrix[2,:] if (p.ndim == 2): zRotAll = tile(zRot, p.shape[1]).reshape(p.shape[1],3) pRot = cross(zRotAll, r.T) normPRot = norm(pRot,axis=1) for i in range(pRot.shape[0]): pRot[i] = pRot[i]/normPRot[i] c = zeros(pRot.shape[0]) s = zeros(pRot.shape[0]) for i in range(pRot.shape[0]): c[i] = dot(pRot[i], p.T[i]) s[i] = dot(pRot[i], q.T[i]) return c, s else: pRot = cross(zRot, r.T) pRot = pRot/norm(pRot) return dot(pRot,p), dot(pRot,q)
def _getJacobian(self, phi, theta): """ Calculates the Jacobian for the transformation of the position errors and proper motion errors between coordinate systems. This Jacobian is also the rotation matrix for the transformation of proper motions. See section 1.5.3 of the Hipparcos Explanatory Volume 1 (equation 1.5.20). Parameters ---------- phi - The longitude-like angle of the position of the source (radians). theta - The latitude-like angle of the position of the source (radians). Returns ------- jacobian - The Jacobian matrix corresponding to (phi, theta) and the currently desired coordinate system transformation. """ p, q, r = normalTriad(phi, theta) # zRot = z-axis of new coordinate system expressed in terms of old system zRot = self.rotationMatrix[2, :] zRotAll = zRot if (p.ndim == 2): for i in range(p.shape[1] - 1): zRotAll = vstack((zRotAll, zRot)) pRot = cross(zRotAll, transpose(r)) if (p.ndim == 2): normPRot = sqrt(diag(dot(pRot, transpose(pRot)))) for i in range(pRot.shape[0]): pRot[i, :] = pRot[i, :] / normPRot[i] else: pRot = pRot / norm(pRot) if (p.ndim == 2): return diag(dot(pRot, p)), diag(dot(pRot, q)) else: return dot(pRot, p), dot(pRot, q)
def _like(init_par, alpha, delta, obs, sigma_obs, ccoef, N): """ Estimate the likelihood function for every set of observations. Then, estimate the function U(init_par) that needs to be minimized to find the best fit to the parameters. Parameters: ------------ init_par - Set of initial values for: 1) All the parallaxes [mas]; 2) The cluster centroid velocity [vx_0, vy_0, vz_0] [km/s]; 3) The cluster velocity dispersion, sigma_v [km/s]; alpha, delta - Array of cluster member positions [rad]; obs - Matrix of observed values for parallaxes and proper motions [mas, mas/yr]; sigma_obs - 3-dim array of observed errors for parallaxes and proper motions [mas, mas/yr]; ccoef - 3-dim array of correlation coefficients. N - the number of stars; Returns: ----------- g - An array of values with the values of g_i(theta) for each star in the group, see eq. 19 in Lindegren+2000; U(init_par) - The function in Eq. (18) of Lindegren+2000, i.e. the function that needs to be minimized. """ plx_mod, v, sigma_v = init_par[:-4], init_par[-4:-1], init_par[-1] plx_obs, mualpha_obs, mudelta_obs = obs[:, 0], obs[:, 1], obs[:, 2] p, q, r = normalTriad(alpha, delta) mualpha_mod = np.dot(np.transpose(p), v) * plx_mod / _A mudelta_mod = np.dot(np.transpose(q), v) * plx_mod / _A sigma_plx, sigma_mualpha, sigma_mudelta = np.transpose(sigma_obs) C = np.zeros((3, 3, N), dtype=np.float64) C[0, 0, :], C[1, 1, :], C[ 2, 2, :] = sigma_plx**2., sigma_mualpha**2., sigma_mudelta**2. r_plx_muRa, r_plx_muDec, r_muRa_muDec = np.zeros(N), np.zeros(N), np.zeros( N) r_plx_muRa[:], r_plx_muDec[:], r_muRa_muDec[:] = ccoef[:, 0], ccoef[:, 1], ccoef[:, 2] C[0, 1, :], C[ 0, 2, :] = r_plx_muRa * sigma_plx * sigma_mualpha, r_plx_muDec * sigma_plx * sigma_mudelta C[1, 0, :], C[ 1, 2, :] = r_plx_muRa * sigma_plx * sigma_mualpha, r_muRa_muDec * sigma_mualpha * sigma_mudelta C[2, 0, :], C[ 2, 1, :] = r_plx_muDec * sigma_plx * sigma_mudelta, r_muRa_muDec * sigma_mualpha * sigma_mudelta E = np.zeros((3, 3, N), dtype=np.float64) E[1, 1, :], E[2, 2, :] = (sigma_v**2.) * (plx_mod / _A)**2., ( sigma_v**2.) * (plx_mod / _A)**2. D, invD, detD = np.zeros((3, 3, N), dtype=np.float64), np.zeros( (3, 3, N), dtype=np.float64), np.ones(N) D = np.add(E, C) for i in range(N): det, invmat = matrix_det_inv(D[:, :, i]) #print(det, invmat) detD[i] = det invD[:, :, i] = invmat a_c = np.ones((3, N)) a_c = [ plx_obs - plx_mod, mualpha_obs - mualpha_mod, mudelta_obs - mudelta_mod ] g_func = row_matrix_col(a_c, a_c, invD) like = np.ones(N) like = ((2 * np.pi)**(-1.5) * detD**(-0.5)) * np.exp(-0.5 * g_func) return np.array(np.sum(np.log(detD)) + np.sum(g_func)), g_func
def stella_Nmatrix_4d(init_par, alpha, delta, plx_obs, mualpha_obs, mudelta_obs, vrad_obs, sigma_obs, sigma_vrad, ccoef, N, i): """ Estimate covariance matrix of the likelihood function. Parameters: ------------ init_par - Set of initial values for: 1) All the parallaxes [mas]; 2) The cluster centroid velocity [vx_0, vy_0, vz_0] [km/s]; 3) The cluster velocity dispersion [km/s]; alpha, delta - Cluster member positions [rad]; plx_obs, mualpha_obs, mudelta_obs - observed values for parallaxes and proper motions [mas, mas/yr]; sigma_obs - observed errors for parallaxes and proper motions [mas, mas/yr]; ccoef - 3-dim array of correlation coefficients from the HIP catalogue; N - number of stars; Returns: ----------- Cov - N+4 x N+4 matrix, corresponding to the covariance of the parameters of the likelihood function. """ parallax, v, sigma_v = init_par[i], init_par[-4:-1], init_par[-1] p, q, r = normalTriad(alpha, delta) mualpha_mod = np.dot(np.transpose(p), v) * parallax / _A mudelta_mod = np.dot(np.transpose(q), v) * parallax / _A vrad_mod = np.dot(np.transpose(r), v) plx_mod, mualpha_mod, mudelta_mod = parallax, mualpha_mod, mudelta_mod sigma_plx, sigma_mualpha, sigma_mudelta = np.transpose(sigma_obs) C = np.zeros((4, 4), dtype=np.float64) C[0, 0], C[1, 1], C[2, 2] = sigma_plx**2., sigma_mualpha**2., sigma_mudelta**2. C[3, 3] = sigma_vrad**2. corr_coefficient_plx_mualpha, corr_coefficient_plx_mudelta, corr_coefficient_mualpha_mudelta = ccoef[ 0], ccoef[1], ccoef[2] C[0, 1], C[ 0, 2] = corr_coefficient_plx_mualpha * sigma_plx * sigma_mualpha, corr_coefficient_plx_mudelta * sigma_plx * sigma_mudelta C[1, 0], C[ 1, 2] = corr_coefficient_plx_mualpha * sigma_plx * sigma_mualpha, corr_coefficient_mualpha_mudelta * sigma_mualpha * sigma_mudelta C[2, 0], C[ 2, 1] = corr_coefficient_plx_mudelta * sigma_plx * sigma_mudelta, corr_coefficient_mualpha_mudelta * sigma_mualpha * sigma_mudelta E = np.zeros((4, 4), dtype=np.float64) E[1, 1], E[2, 2], E[3, 3] = (sigma_v**2.) * (parallax / _A)**2., ( sigma_v**2.) * (parallax / _A)**2., sigma_v**2. D, invD = np.zeros((4, 4), dtype=np.float64), np.zeros((4, 4), dtype=np.float64) D = np.add(E, C) invD = np.linalg.inv(D) cprime_pi, cprime_vx, cprime_vy, cprime_vz, = np.ones(4), np.ones( 4), np.ones(4), np.ones(4) cprime_pi[0] = 1. cprime_pi[1] = np.dot(np.transpose(p), v) / _A cprime_pi[2] = np.dot(np.transpose(q), v) / _A cprime_pi[3] = 0 cprime_vx[0] = 0. cprime_vx[1] = -np.sin(alpha) * plx_mod / _A cprime_vx[2] = -np.sin(delta) * np.cos(alpha) * plx_mod / _A cprime_vx[3] = np.cos(delta) * np.cos(alpha) cprime_vy[0] = 0. cprime_vy[1] = np.cos(alpha) * plx_mod / _A cprime_vy[2] = -np.sin(delta) * np.sin(alpha) * plx_mod / _A cprime_vy[3] = np.cos(delta) * np.sin(alpha) cprime_vz[0] = 0. cprime_vz[1] = 0. cprime_vz[2] = np.cos(delta) * plx_mod / _A cprime_vz[3] = np.sin(delta) dD_dpi, dD_dsigmav, dD_dpi2, dD_dsigmav2, dD_dpisigmav = np.zeros( (4, 4)), np.zeros((4, 4)), np.zeros((4, 4)), np.zeros( (4, 4)), np.zeros((4, 4)) dD_dpi[1, 1] = 2. * plx_mod * ((sigma_v / _A)**2.) dD_dpi[2, 2] = 2. * plx_mod * ((sigma_v / _A)**2.) dD_dsigmav[1, 1] = 2. * sigma_v * ((plx_mod / _A)**2.) dD_dsigmav[2, 2] = 2. * sigma_v * ((plx_mod / _A)**2.) dD_dsigmav[3, 3] = 2. * sigma_v dD_dpi2[1, 1] = 2. * ((sigma_v / _A)**2.) dD_dpi2[2, 2] = 2. * ((sigma_v / _A)**2.) dD_dsigmav2[1, 1] = 2. * ((plx_mod / _A)**2.) dD_dsigmav2[2, 2] = 2. * ((plx_mod / _A)**2.) dD_dsigmav2[3, 3] = 2. dD_dpisigmav[1, 1] = 4. * plx_mod * sigma_v / ((_A)**2.) dD_dpisigmav[2, 2] = 4. * plx_mod * sigma_v / ((_A)**2.) ### See formula A.7 hess = np.zeros((N + 4, N + 4)) hess_pi2, hess_vx2, hess_vy2, hess_vz2, hess_sigmav2 = 0, 0, 0, 0, 0 hess_pi_vx, hess_pi_vy, hess_pi_vz, hess_pi_sigmav = 0, 0, 0, 0 hess_vx_vy, hess_vx_vz, hess_vy_vz = 0, 0, 0 hess_vx2 += np.dot(np.dot(invD, cprime_vx), cprime_vx) hess_vy2 += np.dot(np.dot(invD, cprime_vy), cprime_vy) hess_vz2 += np.dot(np.dot(invD, cprime_vz), cprime_vz) hess_pi2 += np.dot(np.dot(invD, cprime_pi), cprime_pi) hess_pi_vx += np.dot(np.dot(invD, cprime_pi), cprime_vx) hess_pi_vy += np.dot(np.dot(invD, cprime_pi), cprime_vy) hess_pi_vz += np.dot(np.dot(invD, cprime_pi), cprime_vz) hess_vx_vy += np.dot(np.dot(invD, cprime_vx), cprime_vy) hess_vx_vz += np.dot(np.dot(invD, cprime_vx), cprime_vz) hess_vy_vz += np.dot(np.dot(invD, cprime_vy), cprime_vz) hess_pi2 += 0.5 * np.trace( -np.dot(np.dot(invD, dD_dpi), np.dot(invD, dD_dpi)) + np.dot(invD, dD_dpi2)) hess_pi2 += 0.5 * np.tensordot( D, 2 * np.dot(np.dot(np.dot(invD, dD_dpi), np.dot(invD, dD_dpi)), invD) - np.dot(np.dot(invD, dD_dpi2), invD)) hess_sigmav2 += 0.5 * np.trace( -np.dot(np.dot(invD, dD_dsigmav), np.dot(invD, dD_dsigmav)) + np.dot(invD, dD_dsigmav2)) hess_sigmav2 += 0.5 * np.tensordot( D, 2 * np.dot(np.dot(np.dot(invD, dD_dsigmav), np.dot(invD, dD_dsigmav)), invD) - np.dot(np.dot(invD, dD_dsigmav2), invD)) hess_pi_sigmav += 0.5 * np.trace( -np.dot(np.dot(invD, dD_dpi), np.dot(invD, dD_dsigmav)) + np.dot(invD, dD_dpisigmav)) hess_pi_sigmav += 0.5 * np.tensordot( D, 2 * np.dot(np.dot(np.dot(invD, dD_dpi), np.dot(invD, dD_dsigmav)), invD) - np.dot(np.dot(invD, dD_dpisigmav), invD)) hess[i, i] = hess_pi2 hess[i, -1] = hess_pi_sigmav hess[i, -2] = hess_pi_vz hess[i, -3] = hess_pi_vy hess[i, -4] = hess_pi_vx hess[-1, i] = hess[i, -1] hess[-2, i] = hess[i, -2] hess[-3, i] = hess[i, -3] hess[-4, i] = hess[i, -4] hess[-1, -1], hess[-2, -2], hess[-3, -3], hess[ -4, -4] = hess_sigmav2, hess_vz2, hess_vy2, hess_vx2 hess[-2, -3], hess[-3, -4], hess[-2, -4] = hess_vy_vz, hess_vx_vy, hess_vx_vz hess[-3, -2], hess[-4, -3], hess[-4, -2] = hess[-2, -3], hess[-3, -4], hess[-2, -4] return hess ### N = -E(H) for L
def stella_grad_3d(init_par, alpha, delta, plx_obs, mualpha_obs, mudelta_obs, vrad_obs, sigma_obs, sigma_vrad, ccoef, N, i): """ Estimate the jacobian matrix of the likelihood function. Parameters: ------------ init_par - Set of initial values for: 1) All the parallaxes [mas]; 2) The cluster centroid velocity [vx_0, vy_0, vz_0] [km/s]; 3) The cluster velocity dispersion [km/s]; alpha, delta - Cluster member positions [rad]; plx_obs, mualpha_obs, mudelta_obs - observed values for parallaxes and proper motions [mas, mas/yr]; sigma_obs - observed errors for parallaxes and proper motions [mas, mas/yr]; ccoef - 3-dim array of correlation coefficients from the HIP catalogue; N - number of stars; """ parallax, v, sigma_v = init_par[i], init_par[-4:-1], init_par[-1] p, q, r = normalTriad(alpha, delta) mualpha_mod = np.dot(np.transpose(p), v) * parallax / _A mudelta_mod = np.dot(np.transpose(q), v) * parallax / _A plx_mod, mualpha_mod, mudelta_mod = parallax, mualpha_mod, mudelta_mod sigma_plx, sigma_mualpha, sigma_mudelta = np.transpose(sigma_obs) C = np.zeros((3, 3), dtype=np.float64) C[0, 0], C[1, 1], C[2, 2] = sigma_plx**2., sigma_mualpha**2., sigma_mudelta**2. corr_coefficient_plx_mualpha, corr_coefficient_plx_mudelta, corr_coefficient_mualpha_mudelta = ccoef[ 0], ccoef[1], ccoef[2] C[0, 1], C[ 0, 2] = corr_coefficient_plx_mualpha * sigma_plx * sigma_mualpha, corr_coefficient_plx_mudelta * sigma_plx * sigma_mudelta C[1, 0], C[ 1, 2] = corr_coefficient_plx_mualpha * sigma_plx * sigma_mualpha, corr_coefficient_mualpha_mudelta * sigma_mualpha * sigma_mudelta C[2, 0], C[ 2, 1] = corr_coefficient_plx_mudelta * sigma_plx * sigma_mudelta, corr_coefficient_mualpha_mudelta * sigma_mualpha * sigma_mudelta E = np.zeros((3, 3), dtype=np.float64) E[1, 1], E[2, 2] = (sigma_v**2.) * (parallax / _A)**2., (sigma_v**2.) * (parallax / _A)**2. D, invD = np.zeros((3, 3), dtype=np.float64), np.zeros((3, 3), dtype=np.float64) D = np.add(E, C) invD = np.linalg.inv(D) a_c = np.array([ plx_obs - plx_mod, mualpha_obs - mualpha_mod, mudelta_obs - mudelta_mod ]) cprime_pi, cprime_vx, cprime_vy, cprime_vz, = np.ones(3), np.ones( 3), np.ones(3), np.ones(3) cprime_pi[0] = 1. cprime_pi[1] = np.dot(np.transpose(p), v) / _A cprime_pi[2] = np.dot(np.transpose(q), v) / _A cprime_vx[0] = 0. cprime_vx[1] = -np.sin(alpha) * plx_mod / _A cprime_vx[2] = -np.sin(delta) * np.cos(alpha) * plx_mod / _A cprime_vy[0] = 0. cprime_vy[1] = np.cos(alpha) * plx_mod / _A cprime_vy[2] = -np.sin(delta) * np.sin(alpha) * plx_mod / _A cprime_vz[0] = 0. cprime_vz[1] = 0. cprime_vz[2] = np.cos(delta) * plx_mod / _A dD_dpi, dD_dsigmav = np.zeros((3, 3)), np.zeros((3, 3)) dD_dpi[1, 1] = 2. * plx_mod * ((sigma_v / _A)**2.) dD_dpi[2, 2] = 2. * plx_mod * ((sigma_v / _A)**2.) dD_dsigmav[1, 1] = 2. * sigma_v * ((plx_mod / _A)**2.) dD_dsigmav[2, 2] = 2. * sigma_v * ((plx_mod / _A)**2.) ### See formula A.3 f = np.zeros(N + 4) f_pi = 0 f_vx, f_vy, f_vz, f_sigmav = 0, 0, 0, 0 f_vx += np.dot(np.dot(invD, cprime_vx), a_c) f_vy += np.dot(np.dot(invD, cprime_vy), a_c) f_vz += np.dot(np.dot(invD, cprime_vz), a_c) f_pi += np.dot(np.dot(invD, cprime_pi), a_c) f_pi -= 0.5 * np.trace(np.dot(invD, dD_dpi)) f_pi += 0.5 * np.dot(np.dot(np.dot(np.dot(invD, dD_dpi), invD), a_c), a_c) f_sigmav -= 0.5 * np.trace(np.dot(invD, dD_dsigmav)) f_sigmav += 0.5 * np.dot( np.dot(np.dot(np.dot(invD, dD_dsigmav), invD), a_c), a_c) f[i] = f_pi f[-4], f[-3], f[-2], f[-1] = f_vx, f_vy, f_vz, f_sigmav ### f is Grad L(theta), see Eq. 17 return -2 * f ### Grad U(theta), see Eq. 18
def Nmatrix(init_par, alpha, delta, obs, sigma_obs, ccoef, N): """ Estimate covariance matrix of the likelihood function. Parameters: ------------ init_par - Set of initial values for: 1) All the parallaxes [mas]; 2) The cluster centroid velocity [vx_0, vy_0, vz_0] [km/s]; 3) The cluster velocity dispersion [km/s]; alpha, delta - Cluster member positions [rad]; obs - observed values for parallaxes and proper motions [mas, mas/yr]; sigma_obs - observed errors for parallaxes and proper motions [mas, mas/yr]; ccoef - 3-dim array of correlation coefficients from the HIP catalogue; N - number of stars; Returns: ----------- Cov - N+4 x N+4 matrix, corresponding to the covariance of the parameters of the likelihood function. """ parallax, v, sigma_v = init_par[:-4], init_par[-4:-1], init_par[-1] plx_obs, mualpha_obs, mudelta_obs = obs[:, 0], obs[:, 1], obs[:, 2] p, q, r = normalTriad(alpha, delta) mualpha_mod = np.dot(np.transpose(p), v) * parallax / _A mudelta_mod = np.dot(np.transpose(q), v) * parallax / _A plx_mod, mualpha_mod, mudelta_mod = parallax, mualpha_mod, mudelta_mod sigma_plx, sigma_mualpha, sigma_mudelta = np.transpose(sigma_obs) a, like, expo, detD = np.ones(N), np.ones(N), np.ones(N), np.ones(N) C = np.zeros((3, 3, N), dtype=np.float64) C[0, 0, :], C[1, 1, :], C[ 2, 2, :] = sigma_plx**2., sigma_mualpha**2., sigma_mudelta**2. corr_coefficient_plx_mualpha, corr_coefficient_plx_mudelta, corr_coefficient_mualpha_mudelta = np.zeros( N), np.zeros(N), np.zeros(N) corr_coefficient_plx_mualpha[:], corr_coefficient_plx_mudelta[:], corr_coefficient_mualpha_mudelta[:] = ccoef[:, 0], ccoef[:, 1], ccoef[:, 2] C[0, 1, :], C[ 0, 2, :] = corr_coefficient_plx_mualpha * sigma_plx * sigma_mualpha, corr_coefficient_plx_mudelta * sigma_plx * sigma_mudelta C[1, 0, :], C[ 1, 2, :] = corr_coefficient_plx_mualpha * sigma_plx * sigma_mualpha, corr_coefficient_mualpha_mudelta * sigma_mualpha * sigma_mudelta C[2, 0, :], C[ 2, 1, :] = corr_coefficient_plx_mudelta * sigma_plx * sigma_mudelta, corr_coefficient_mualpha_mudelta * sigma_mualpha * sigma_mudelta E = np.zeros((3, 3, N), dtype=np.float64) E[1, 1, :], E[2, 2, :] = (sigma_v**2.) * (parallax / _A)**2., ( sigma_v**2.) * (parallax / _A)**2. D, invD = np.zeros((3, 3, N), dtype=np.float64), np.zeros((3, 3, N), dtype=np.float64) D = np.add(E, C) for i in range(N): detD[i] = matrix_det(D[:, :, i]) invD[:, :, i] = matrix_inv(D[:, :, i]) a_c = np.ones((3, N)) a_c = [ plx_obs - plx_mod, mualpha_obs - mualpha_mod, mudelta_obs - mudelta_mod ] cprime_pi, cprime_vx, cprime_vy, cprime_vz, = np.ones((3,N)), np.ones((3,N)), \ np.ones((3,N)), np.ones((3,N)), cprime_pi[0, :] = 1. cprime_pi[1, :] = np.dot(np.transpose(p), v) / _A cprime_pi[2, :] = np.dot(np.transpose(q), v) / _A cprime_vx[0, :] = 0. cprime_vx[1, :] = -np.sin(alpha) * plx_mod / _A cprime_vx[2, :] = -np.sin(delta) * np.cos(alpha) * plx_mod / _A cprime_vy[0, :] = 0. cprime_vy[1, :] = np.cos(alpha) * plx_mod / _A cprime_vy[2, :] = -np.sin(delta) * np.sin(alpha) * plx_mod / _A cprime_vz[0, :] = 0. cprime_vz[1, :] = 0. cprime_vz[2, :] = np.cos(delta) * plx_mod / _A dlnd_dpi, dlnd_dsigmav = np.zeros(N), np.zeros(N) de_dpi, de_dsigmav = np.zeros(N), np.zeros(N) ### See formula A.5 de_dpi[:] = ((sigma_v / _A)**2.) * 2. * plx_mod[:] de_dsigmav[:] = ((plx_mod[:] / _A)**2.) * 2. * sigma_v dlnd_dpi[:] = (invD[1, 1, :] + invD[2, 2, :]) * de_dpi[:] dlnd_dsigmav[:] = (invD[1, 1, :] + invD[2, 2, :]) * de_dsigmav[:] ### See formula A.7 hess = np.zeros((N + 4, N + 4)) hess_diag_pi, hess_diag_pi_1, hess_diag_pi_2 = np.zeros(N), np.zeros( N), np.zeros(N) hess_diag_pi_1[:] = invD[0, 0, :]*cprime_pi[0, :]*cprime_pi[0, :] + invD[0, 1, :]*cprime_pi[0, :]*cprime_pi[1, :] + invD[0, 2, :]*cprime_pi[0, :]*cprime_pi[2, :] + \ invD[1, 0, :]*cprime_pi[1, :]*cprime_pi[0, :] + invD[1, 1, :]*cprime_pi[1, :]*cprime_pi[1, :] + invD[1, 2, :]*cprime_pi[1, :]*cprime_pi[2, :] + \ invD[2, 0, :]*cprime_pi[2, :]*cprime_pi[0, :] + invD[2, 1, :]*cprime_pi[2, :]*cprime_pi[1, :] + invD[2, 2, :]*cprime_pi[2, :]*cprime_pi[2, :] #hess_diag_pi_2[:] = np.sum(0.5*(invD[1, 1, :]**2. + 2.*invD[1, 2, :]**2. + invD[2, 2, :]**2.)*de_dpi[:]*de_dpi[:]) ### Check if it's with or without sum: without! # So correct formula is below. hess_diag_pi_2[:] = ( 0.5 * (invD[1, 1, :]**2. + 2. * invD[1, 2, :]**2. + invD[2, 2, :]**2.) * de_dpi[:] * de_dpi[:]) hess_diag_pi[:] = hess_diag_pi_1[:] + hess_diag_pi_2[:] hess_diag_vx, hess_diag_vy, hess_diag_vz, hess_diag_sigmav = np.zeros( N), np.zeros(N), np.zeros(N), np.zeros(N) hess_pi_vx, hess_pi_vy, hess_pi_vz, hess_pi_sigmav = np.zeros(N), np.zeros( N), np.zeros(N), np.zeros(N) hess_diag_vxi, hess_diag_vyi, hess_diag_vzi = np.zeros(N), np.zeros( N), np.zeros(N) hess_diag_vxi[:] = invD[0, 0, :]*cprime_vx[0, :]*cprime_vx[0, :] + invD[0, 1, :]*cprime_vx[0, :]*cprime_vx[1, :] + invD[0, 2, :]*cprime_vx[0, :]*cprime_vx[2, :] + \ invD[1, 0, :]*cprime_vx[1, :]*cprime_vx[0, :] + invD[1, 1, :]*cprime_vx[1, :]*cprime_vx[1, :] + invD[1, 2, :]*cprime_vx[1, :]*cprime_vx[2, :] + \ invD[2, 0, :]*cprime_vx[2, :]*cprime_vx[0, :] + invD[2, 1, :]*cprime_vx[2, :]*cprime_vx[1, :] + invD[2, 2, :]*cprime_vx[2, :]*cprime_vx[2, :] hess_diag_vyi[:] = invD[0, 0, :]*cprime_vy[0, :]*cprime_vy[0, :] + invD[0, 1, :]*cprime_vy[0, :]*cprime_vy[1, :] + invD[0, 2, :]*cprime_vy[0, :]*cprime_vy[2, :] +\ invD[1, 0, :]*cprime_vy[1, :]*cprime_vy[0, :] + invD[1, 1, :]*cprime_vy[1, :]*cprime_vy[1, :] + invD[1, 2, :]*cprime_vy[1, :]*cprime_vy[2, :] +\ invD[2, 0, :]*cprime_vy[2, :]*cprime_vy[0, :] + invD[2, 1, :]*cprime_vy[2, :]*cprime_vy[1, :] + invD[2, 2, :]*cprime_vy[2, :]*cprime_vy[2, :] hess_diag_vzi[:] = invD[0, 0, :]*cprime_vz[0, :]*cprime_vz[0, :] + invD[0, 1, :]*cprime_vz[0, :]*cprime_vz[1, :] + invD[0, 2, :]*cprime_vz[0, :]*cprime_vz[2, :] +\ invD[1, 0, :]*cprime_vz[1, :]*cprime_vz[0, :] + invD[1, 1, :]*cprime_vz[1, :]*cprime_vz[1, :] + invD[1, 2, :]*cprime_vz[1, :]*cprime_vz[2, :] +\ invD[2, 0, :]*cprime_vz[2, :]*cprime_vz[0, :] + invD[2, 1, :]*cprime_vz[2, :]*cprime_vz[1, :] + invD[2, 2, :]*cprime_vz[2, :]*cprime_vz[2, :] hess_pi_vx[:] = invD[0, 0, :]*cprime_pi[0,:]*cprime_vx[0, :] + invD[0, 1, :]*cprime_pi[0,:]*cprime_vx[1, :] + invD[0, 2, :]*cprime_pi[0,:]*cprime_vx[2, :] +\ invD[1, 0, :]*cprime_pi[1,:]*cprime_vx[0, :] + invD[1, 1, :]*cprime_pi[1,:]*cprime_vx[1, :] + invD[1, 2, :]*cprime_pi[1,:]*cprime_vx[2, :] +\ invD[2, 0, :]*cprime_pi[2,:]*cprime_vx[0, :] + invD[2, 1, :]*cprime_pi[2,:]*cprime_vx[1, :] + invD[2, 2, :]*cprime_pi[2,:]*cprime_vx[2, :] hess_pi_vy[:] = invD[0, 0, :]*cprime_pi[0,:]*cprime_vy[0, :] + invD[0, 1, :]*cprime_pi[0,:]*cprime_vy[1, :] + invD[0, 2, :]*cprime_pi[0,:]*cprime_vy[2, :] +\ invD[1, 0, :]*cprime_pi[1,:]*cprime_vy[0, :] + invD[1, 1, :]*cprime_pi[1,:]*cprime_vy[1, :] + invD[1, 2, :]*cprime_pi[1,:]*cprime_vy[2, :] +\ invD[2, 0, :]*cprime_pi[2,:]*cprime_vy[0, :] + invD[2, 1, :]*cprime_pi[2,:]*cprime_vy[1, :] + invD[2, 2, :]*cprime_pi[2,:]*cprime_vy[2, :] hess_pi_vz[:] = invD[0, 0, :]*cprime_pi[0,:]*cprime_vz[0, :] + invD[0, 1, :]*cprime_pi[0,:]*cprime_vz[1, :] + invD[0, 2, :]*cprime_pi[0,:]*cprime_vz[2, :] +\ invD[1, 0, :]*cprime_pi[1,:]*cprime_vz[0, :] + invD[1, 1, :]*cprime_pi[1,:]*cprime_vz[1, :] + invD[1, 2, :]*cprime_pi[1,:]*cprime_vz[2, :] +\ invD[2, 0, :]*cprime_pi[2,:]*cprime_vz[0, :] + invD[2, 1, :]*cprime_pi[2,:]*cprime_vz[1, :] + invD[2, 2, :]*cprime_pi[2,:]*cprime_vz[2, :] hess_diag_vx = np.sum(hess_diag_vxi) hess_diag_vy = np.sum(hess_diag_vyi) hess_diag_vz = np.sum(hess_diag_vzi) hess_diag_sigmav = np.sum( 0.5 * (invD[1, 1, :]**2. + 2. * invD[1, 2, :]**2. + invD[2, 2, :]**2.) * de_dsigmav[:] * de_dsigmav[:]) hess_pi_sigmav[:] = 0.5 * (invD[1, 1, :]**2. + 2. * invD[1, 2, :]**2. + invD[2, 2, :]**2.) * de_dpi[:] * de_dsigmav[:] hess_diag = np.concatenate( (hess_diag_pi, np.array([hess_diag_vx, hess_diag_vy, hess_diag_vz, hess_diag_sigmav]))) for i in range(N + 4): hess[i, i] = hess_diag[i] for j in range(N): hess[j, -4] = hess_pi_vx[j] hess[j, -3] = hess_pi_vy[j] hess[j, -2] = hess_pi_vz[j] hess[j, -1] = hess_pi_sigmav[j] hess[-4, j] = hess_pi_vx[j] hess[-3, j] = hess_pi_vy[j] hess[-2, j] = hess_pi_vz[j] hess[-1, j] = hess_pi_sigmav[j] part_12, part_13, part_23 = np.zeros(N), np.zeros(N), np.zeros(N) for ia in range(3): for ib in range(3): part_12[:] += invD[ia, ib, :] * cprime_vx[ia, :] * cprime_vy[ib, :] part_13[:] += invD[ia, ib, :] * cprime_vx[ia, :] * cprime_vz[ib, :] part_23[:] += invD[ia, ib, :] * cprime_vy[ia, :] * cprime_vz[ib, :] hess[-4, -3] = np.sum(part_12) hess[-3, -4] = hess[-4, -3] hess[-4, -2] = np.sum(part_13) hess[-2, -4] = hess[-4, -2] hess[-3, -2] = np.sum(part_23) hess[-2, -3] = hess[-3, -2] #### I am returning here the matrix Njk, which is defined as -E(H), #### where H is the hessian of the likelihood: therefore to obtain the real hessian, one #### should multiply this by '-1' (see function below.) return hess ### See eq. 18
def gradient(init_par, alpha, delta, obs, sigma_obs, ccoef, N): """ Estimate gradient of the likelihood function. Parameters: ------------ init_par - Set of initial values for: 1) All the parallaxes [mas]; 2) The cluster centroid velocity [vx_0, vy_0, vz_0] [km/s]; 3) The cluster velocity dispersion, sigma_v [km/s]; alpha, delta - Cluster member positions [rad]; obs - observed values for parallaxes and proper motions [mas, mas/yr]; sigma_obs - observed errors for parallaxes and proper motions [mas, mas/yr]; ccoef - 3-dim array of correlation coefficients from the HIP catalogue; N - number of stars; Returns: ----------- f - An array with n+4 elements corresponding to the gradient of the likelihood with respect to the n+4 variables. """ ## Initial parameters parallax, v, sigma_v = init_par[:-4], init_par[-4:-1], init_par[-1] plx_obs, mualpha_obs, mudelta_obs = obs[:, 0], obs[:, 1], obs[:, 2] ### Define normal triad and proper motions p, q, r = normalTriad(alpha, delta) mualpha_mod = np.dot(np.transpose(p), v) * parallax / _A mudelta_mod = np.dot(np.transpose(q), v) * parallax / _A plx_mod, mualpha_mod, mudelta_mod = parallax, mualpha_mod, mudelta_mod sigma_plx, sigma_mualpha, sigma_mudelta = np.transpose(sigma_obs) a, like, expo, detD = np.ones(N), np.ones(N), np.ones(N), np.ones(N) ### Eq. 8 in Lindegren+2000 (Covariance Matrix) C = np.zeros((3, 3, N), dtype=np.float64) C[0, 0, :], C[1, 1, :], C[ 2, 2, :] = sigma_plx**2., sigma_mualpha**2., sigma_mudelta**2. corr_coefficient_plx_mualpha, corr_coefficient_plx_mudelta, corr_coefficient_mualpha_mudelta = np.zeros( N), np.zeros(N), np.zeros(N) corr_coefficient_plx_mualpha[:], corr_coefficient_plx_mudelta[:], corr_coefficient_mualpha_mudelta[:] = ccoef[:, 0], ccoef[:, 1], ccoef[:, 2] C[0, 1, :], C[ 0, 2, :] = corr_coefficient_plx_mualpha * sigma_plx * sigma_mualpha, corr_coefficient_plx_mudelta * sigma_plx * sigma_mudelta C[1, 0, :], C[ 1, 2, :] = corr_coefficient_plx_mualpha * sigma_plx * sigma_mualpha, corr_coefficient_mualpha_mudelta * sigma_mualpha * sigma_mudelta C[2, 0, :], C[ 2, 1, :] = corr_coefficient_plx_mudelta * sigma_plx * sigma_mudelta, corr_coefficient_mualpha_mudelta * sigma_mualpha * sigma_mudelta ### Eq. 16 in Lindegren+2000 (Definition of D matrix) E = np.zeros((3, 3, N), dtype=np.float64) E[1, 1, :], E[2, 2, :] = (sigma_v * parallax[:] / _A)**2., (sigma_v * parallax[:] / _A)**2. D, invD = np.zeros((3, 3, N), dtype=np.float64), np.zeros((3, 3, N), dtype=np.float64) D = np.add(E, C) for i in range(N): detD[i] = matrix_det(D[:, :, i]) invD[:, :, i] = matrix_inv(D[:, :, i]) a_c = np.ones((3, N)) a_c = [ plx_obs - plx_mod, mualpha_obs - mualpha_mod, mudelta_obs - mudelta_mod ] ### First derivatives in Eq. A3 cprime_pi, cprime_vx, cprime_vy, cprime_vz, = np.ones((3,N)), np.ones((3,N)), \ np.ones((3,N)), np.ones((3,N)), cprime_pi[0, :] = 1. cprime_pi[1, :] = np.dot(np.transpose(p), v) / _A cprime_pi[2, :] = np.dot(np.transpose(q), v) / _A cprime_vx[0, :] = 0. cprime_vx[1, :] = -np.sin(alpha) * plx_mod / _A cprime_vx[2, :] = -np.sin(delta) * np.cos(alpha) * plx_mod / _A cprime_vy[0, :] = 0. cprime_vy[1, :] = np.cos(alpha) * plx_mod / _A cprime_vy[2, :] = -np.sin(delta) * np.sin(alpha) * plx_mod / _A cprime_vz[0, :] = 0. cprime_vz[1, :] = 0. cprime_vz[2, :] = np.cos(delta) * plx_mod / _A dlnd_dpi, dlnd_dsigmav = np.zeros(N), np.zeros(N) de_dpi, de_dsigmav = np.zeros(N), np.zeros(N) ### See Eq. A5 de_dpi[:] = ((sigma_v / _A)**2.) * 2. * plx_mod[:] de_dsigmav[:] = ((plx_mod[:] / _A)**2.) * 2. * sigma_v dlnd_dpi[:] = (invD[1, 1, :] + invD[2, 2, :]) * de_dpi[:] dlnd_dsigmav[:] = (invD[1, 1, :] + invD[2, 2, :]) * de_dsigmav[:] ### See Eq. A6 dG_dpi, dG_dsigmav = np.zeros((3, 3, N)), np.zeros((3, 3, N)) dG_dpi[0,0,:], dG_dpi[0,1,:], dG_dpi[0,2,:] = (-invD[0,1,:]*invD[1, 0, :] - invD[0, 2, :]*invD[2,0,:])*de_dpi[:], \ (-invD[0,1,:]*invD[1, 1, :] - invD[0,2,:]*invD[2, 1, :])*de_dpi[:], \ (-invD[0,1,:]*invD[1,2,:] - invD[0,2,:]*invD[2,2,:])*de_dpi[:] dG_dpi[1,0,:], dG_dpi[1,1,:], dG_dpi[1,2,:] = (-invD[1,1,:]*invD[1, 0, :] - invD[1, 2, :]*invD[2,0,:])*de_dpi[:], \ (-invD[1,1,:]*invD[1, 1, :] - invD[1,2,:]*invD[2, 1, :])*de_dpi[:], \ (-invD[1,1,:]*invD[1,2,:] - invD[1,2,:]*invD[2,2,:])*de_dpi[:] dG_dpi[2,0,:], dG_dpi[2,1,:], dG_dpi[2,2,:] = (-invD[2,1,:]*invD[1, 0, :] - invD[2, 2, :]*invD[2,0,:])*de_dpi[:], \ (-invD[2,1,:]*invD[1, 1, :] - invD[2,2,:]*invD[2, 1, :])*de_dpi[:], \ (-invD[2,1,:]*invD[1,2,:] - invD[2,2,:]*invD[2,2,:])*de_dpi[:] dG_dsigmav[0,0,:], dG_dsigmav[0,1,:], dG_dsigmav[0,2,:] = (-invD[0,1,:]*invD[1, 0, :] - invD[0, 2, :]*invD[2,0,:])*de_dsigmav[:], \ (-invD[0,1,:]*invD[1, 1, :] - invD[0,2,:]*invD[2, 1, :])*de_dsigmav[:], \ (-invD[0,1,:]*invD[1,2,:] - invD[0,2,:]*invD[2,2,:])*de_dsigmav[:] dG_dsigmav[1,0,:], dG_dsigmav[1,1,:], dG_dsigmav[1,2,:] = (-invD[1,1,:]*invD[1, 0, :] - invD[1, 2, :]*invD[2,0,:])*de_dsigmav[:], \ (-invD[1,1,:]*invD[1, 1, :] - invD[1,2,:]*invD[2, 1, :])*de_dsigmav[:], \ (-invD[1,1,:]*invD[1,2,:] - invD[1,2,:]*invD[2,2,:])*de_dsigmav[:] dG_dsigmav[2,0,:], dG_dsigmav[2,1,:], dG_dsigmav[2,2,:] = (-invD[2,1,:]*invD[1, 0, :] - invD[2, 2, :]*invD[2,0,:])*de_dsigmav[:], \ (-invD[2,1,:]*invD[1, 1, :] - invD[2,2,:]*invD[2, 1, :])*de_dsigmav[:], \ (-invD[2,1,:]*invD[1,2,:] - invD[2,2,:]*invD[2,2,:])*de_dsigmav[:] f_dpi = np.zeros((N), dtype=np.float64) for i in range(N): f_dpi_1, f_dpi_3 = 0., 0.0 for ia in range(3): for ib in range(3): f_dpi_1 += invD[ia, ib, i] * cprime_pi[ia, i] * a_c[ib][i] f_dpi_3 += (-0.5) * (dG_dpi[ia, ib, i] * a_c[ia][i] * a_c[ib][i]) f_dpi_2 = (-0.5) * dlnd_dpi[i] f_dpi[i] = f_dpi_1 + f_dpi_2 + f_dpi_3 f_vx, f_vy, f_vz, f_sigmav = np.zeros(N), np.zeros(N), np.zeros( N), np.zeros(N) f_vx = np.sum(invD[0,0,:]*cprime_vx[0,:]*a_c[0][:] + invD[0,1,:]*cprime_vx[0,:]*a_c[1][:] + invD[0,2,:]*cprime_vx[0,:]*a_c[2][:] + \ invD[1,0,:]*cprime_vx[1,:]*a_c[0][:] + invD[1,1,:]*cprime_vx[1,:]*a_c[1][:] + invD[1,2,:]*cprime_vx[1,:]*a_c[2][:] + \ invD[2,0,:]*cprime_vx[2,:]*a_c[0][:] + invD[2,1,:]*cprime_vx[2,:]*a_c[1][:] + invD[2,2,:]*cprime_vx[2,:]*a_c[2][:]) f_vy = np.sum(invD[0,0,:]*cprime_vy[0,:]*a_c[0][:] + invD[0,1,:]*cprime_vy[0,:]*a_c[1][:] + invD[0,2,:]*cprime_vy[0,:]*a_c[2][:] + \ invD[1,0,:]*cprime_vy[1,:]*a_c[0][:] + invD[1,1,:]*cprime_vy[1,:]*a_c[1][:] + invD[1,2,:]*cprime_vy[1][:]*a_c[2][:] + \ invD[2,0,:]*cprime_vy[2,:]*a_c[0][:] + invD[2,1,:]*cprime_vy[2,:]*a_c[1][:] + invD[2,2,:]*cprime_vy[2,:]*a_c[2][:]) f_vz = np.sum(invD[0,0,:]*cprime_vz[0,:]*a_c[0][:] + invD[0,1,:]*cprime_vz[0,:]*a_c[1][:] + invD[0,2,:]*cprime_vz[0,:]*a_c[2][:] + \ invD[1,0,:]*cprime_vz[1,:]*a_c[0][:] + invD[1,1,:]*cprime_vz[1,:]*a_c[1][:] + invD[1,2,:]*cprime_vz[1,:]*a_c[2][:] + \ invD[2,0,:]*cprime_vz[2,:]*a_c[0][:] + invD[2,1,:]*cprime_vz[2,:]*a_c[1][:] + invD[2,2,:]*cprime_vz[2,:]*a_c[2][:]) f_sigmav = np.sum(-0.5*(dG_dsigmav[0,0,:]*a_c[0][:]*a_c[0][:] + dG_dsigmav[0,1,:]*a_c[1][:]*a_c[0][:]+ dG_dsigmav[0,2,:]*a_c[2][:]*a_c[0][:] + \ dG_dsigmav[1,0,i]*a_c[1][:]*a_c[0][:] + dG_dsigmav[1,1,:]*a_c[1][:]*a_c[1][:]+ dG_dsigmav[1,2,:]*a_c[1][:]*a_c[2][:] + dG_dsigmav[2,0,i]*a_c[2][:]*a_c[0][:] + dG_dsigmav[2,1,:]*a_c[2][:]*a_c[1][:]+ dG_dsigmav[2,2,:]*a_c[2][:]*a_c[2][:])) f_sigmav = f_sigmav - 0.5 * np.sum(dlnd_dsigmav) f = np.concatenate( (f_dpi, np.array([f_vx, f_vy, f_vz, f_sigmav]))) ### Grad L(theta), see Eq. 17 return -2. * f ### Grad U(theta), see Eq. 18
def propagate_astrometry_and_covariance_matrix(self, a0, c0, t0, t1): """ Propagate the covariance matrix of the astrometric parameters and radial proper motion of a source from epoch t0 to epoch t1. Code based on the Hipparcos Fortran implementation by Lennart Lindegren. Parameters ---------- a0 : array_like 6-element vector: (phi, theta, parallax, muphistar, mutheta, vrad) in units of (radians, radians, mas, mas/yr, mas/yr, km/s). Shape of a should be (6,) or (6,N), with N the number of sources for which the astrometric parameters are provided. c0 : array_like Covariance matrix stored in a 6x6 element array. This can be constructed from the columns listed in the Gaia catalogue. The units are [mas^2, mas^2/yr, mas^2/yr^2] for the various elements. Note that the elements in the 6th row and column should be: c[6,i]=c[i,6]=c[i,3]*vrad/auKmYearPerSec for i=1,..,5 and c[6,6]=c[3,3]*(vrad^2+vrad_error^2)/auKmYearPerSec^2+(parallax*vrad_error/auKmYearPerSec)^2 Shape of c0 should be (6,6) or (N,6,6). t0 : float Reference epoch (Julian years). t1 : float New epoch (Julian years). Returns ------- Astrometric parameters, including the "radial proper motion" (NOT the radial velocity), and covariance matrix at the new epoch as a 2D matrix with the new variances on the diagional and the covariance in the off-diagonal elements. """ zero, one, two, three = 0, 1, 2, 3 tau = t1-t0 # Calculate the normal triad [p0 q0 r0] at t0 p0, q0, r0 = normalTriad(a0[0], a0[1]) # Convert to internal units (radians, Julian year) par0 = a0[2]*self.mastorad pma0 = a0[3]*self.mastorad pmd0 = a0[4]*self.mastorad pmr0 = a0[5]*a0[2]/auKmYearPerSec*self.mastorad # Proper motion vector pmvec0 = pma0*p0+pmd0*q0 # Auxiliary quantities tau2 = tau*tau pm02 = pma0**2 + pmd0**2 w = one + pmr0*tau f2 = one/(one + two*pmr0*tau + (pm02+pmr0**2)*tau2) f = sqrt(f2) f3 = f2*f f4 = f2*f2 # Position vector and parallax at t1 u = (r0*w + pmvec0*tau)*f _, ra, dec = cartesianToSpherical(u[0], u[1], u[2]) par = par0*f # Proper motion vector and radial proper motion at t1 pmvec = (pmvec0*(one+pmr0*tau) - r0*pmr0**2*tau)*f3 pmr = (pmr0+(pm02 + pmr0**2)*tau)*f2 # Normal triad at t1 p, q, r = normalTriad(ra, dec) # Convert parameters at t1 to external units (mas, Julian year) pma = sum(p*pmvec, axis=0) pmd = sum(q*pmvec, axis =0) a = zeros_like(a0) a[0] = ra a[1] = dec a[2] = par/self.mastorad a[3] = pma/self.mastorad a[4] = pmd/self.mastorad a[5] = pmr/self.mastorad # Auxiliary quantities for the partial derivatives pmz = pmvec0*f - three*pmvec*w pp0 = sum(p*p0, axis=0) pq0 = sum(p*q0, axis=0) pr0 = sum(p*r0, axis=0) qp0 = sum(q*p0, axis=0) qq0 = sum(q*q0, axis=0) qr0 = sum(q*r0, axis=0) ppmz = sum(p*pmz, axis=0) qpmz = sum(q*pmz, axis=0) J = zeros_like(c0) if (c0.ndim==2): J = J[newaxis,:,:] # Partial derivatives J[:,0,0] = pp0*w*f - pr0*pma0*tau*f J[:,0,1] = pq0*w*f - pr0*pmd0*tau*f J[:,0,2] = zero J[:,0,3] = pp0*tau*f J[:,0,4] = pq0*tau*f J[:,0,5] = -pma*tau2 J[:,1,0] = qp0*w*f - qr0*pma0*tau*f J[:,1,1] = qq0*w*f - qr0*pmd0*tau*f J[:,1,2] = zero J[:,1,3] = qp0*tau*f J[:,1,4] = qq0*tau*f J[:,1,5] = -pmd*tau2 J[:,2,0] = zero J[:,2,1] = zero J[:,2,2] = f J[:,2,3] = -par*pma0*tau2*f2 J[:,2,4] = -par*pmd0*tau2*f2 J[:,2,5] = -par*w*tau*f2 J[:,3,0] = -pp0*pm02*tau*f3 - pr0*pma0*w*f3 J[:,3,1] = -pq0*pm02*tau*f3 - pr0*pmd0*w*f3 J[:,3,2] = zero J[:,3,3] = pp0*w*f3 - two*pr0*pma0*tau*f3 - three*pma*pma0*tau2*f2 J[:,3,4] = pq0*w*f3 - two*pr0*pmd0*tau*f3 - three*pma*pmd0*tau2*f2 J[:,3,5] = ppmz*tau*f2 J[:,4,0] = -qp0*pm02*tau*f3 - qr0*pma0*w*f3 J[:,4,1] = -qq0*pm02*tau*f3 - qr0*pmd0*w*f3 J[:,4,2] = zero J[:,4,3] = qp0*w*f3 - two*qr0*pma0*tau*f3 - three*pmd*pma0*tau2*f2 J[:,4,4] = qq0*w*f3 - two*qr0*pmd0*tau*f3 - three*pmd*pmd0*tau2*f2 J[:,4,5] = qpmz*tau*f2 J[:,5,0] = zero J[:,5,1] = zero J[:,5,2] = zero J[:,5,3] = two*pma0*w*tau*f4 J[:,5,4] = two*pmd0*w*tau*f4 J[:,5,5] = (w**2 - pm02*tau2)*f4 JT = zeros_like(J) for i in range(J.shape[0]): JT[i] = J[i].T if (c0.ndim==2): c = matmul(J,matmul(c0[newaxis,:,:],JT)) else: c = matmul(J,matmul(c0,JT)) return a, squeeze(c)