def calculate_true_anomaly(epoch, sma, ecc, tau, mtot): """ Calculate true anomaly Args: epoch (float): epoch at which to calculate contrast [mjd] sma (float): semimajor axis [au] ecc (float): eccentricity tau (float): epoch of periastron, in mjd, divided by period mtot (float): total mass [solar masses] Returns: true anomaly [float] History: 2018: shamelessly stolen from orbitize.kepler.calc_orbit() by Sarah Blunt """ # compute period (from Kepler's third law) and mean motion period = np.sqrt((sma**3) / mtot) * 365.25 mean_motion = 2 * np.pi / (period) # in rad/day # compute mean anomaly manom = (mean_motion * epoch - 2 * np.pi * tau) % (2.0 * np.pi) # compute eccentric anomalies eanom = _calc_ecc_anom(manom, ecc) # compute the true anomalies tanom = 2. * np.arctan( np.sqrt((1.0 + ecc) / (1.0 - ecc)) * np.tan(0.5 * eanom)) return tanom
def profile_mikkola_ecc_anom_solver(n_orbits=1000, use_c=True): """ Test orbitize.kepler._calc_ecc_anom() in the iterative solver regime (e < 0.95) by comparing the mean anomaly computed from _calc_ecc_anom() output vs the input mean anomaly """ mean_anoms = np.linspace(0, 2.0 * np.pi, n_orbits) eccs = np.linspace(.95, 0.999999, n_orbits) for ee in eccs: ecc_anoms = kepler._calc_ecc_anom(mean_anoms, ee, use_c=use_c)
def test_iterative_ecc_anom_solver(): """ Test orbitize.kepler._calc_ecc_anom() in the iterative solver regime (e < 0.95) by comparing the mean anomaly computed from _calc_ecc_anom() output vs the input mean anomaly """ mean_anoms = np.linspace(0, 2.0 * np.pi, 100) eccs = np.linspace(0, 0.9499999, 100) for ee in eccs: ecc_anoms = kepler._calc_ecc_anom(mean_anoms, ee, tolerance=1e-9) calc_ma = (ecc_anoms - ee * np.sin(ecc_anoms)) % ( 2 * np.pi) # plug solutions into Kepler's equation for meas, truth in zip(calc_ma, mean_anoms): assert angle_diff(meas, truth) == pytest.approx(0.0, abs=threshold)
def test_analytical_ecc_anom_solver(use_c=False, use_gpu=False): """ Test orbitize.kepler._calc_ecc_anom() in the analytical solver regime (e > 0.95) by comparing the mean anomaly computed from _calc_ecc_anom() output vs the input mean anomaly """ mean_anoms = np.linspace(0, 2.0 * np.pi, 1000) eccs = np.linspace(0.95, 0.999999, 100) for ee in eccs: ecc_anoms = kepler._calc_ecc_anom(mean_anoms, ee, tolerance=1e-9, use_c=use_c, use_gpu=use_gpu) calc_mm = (ecc_anoms - ee * np.sin(ecc_anoms)) % ( 2 * np.pi) # plug solutions into Kepler's equation for meas, truth in zip(calc_mm, mean_anoms): assert angle_diff(meas, truth) == pytest.approx(0.0, abs=threshold)
def calc_orbit_3d(epochs, sma, ecc, inc, argp, lan, tau, plx, mtot, mass=None, tau_ref_epoch=0, tolerance=1e-9, max_iter=100): """ Returns the separation and radial velocity of the body given array of orbital parameters (size n_orbs) at given epochs (array of size n_dates) Based on orbit solvers from James Graham and Rob De Rosa. Adapted by Jason Wang and Henry Ngo. Args: epochs (np.array): MJD times for which we want the positions of the planet sma (np.array): semi-major axis of orbit [au] ecc (np.array): eccentricity of the orbit [0,1] inc (np.array): inclination [radians] argp (np.array): argument of periastron [radians] lan (np.array): longitude of the ascending node [radians] tau (np.array): epoch of periastron passage in fraction of orbital period past MJD=0 [0,1] plx (np.array): parallax [mas] mtot (np.array): total mass [Solar masses] mass (np.array, optional): mass of the body [Solar masses]. For planets mass ~ 0 (default) tau_ref_epoch (float, optional): reference date that tau is defined with respect to (i.e., tau=0) tolerance (float, optional): absolute tolerance of iterative computation. Defaults to 1e-9. max_iter (int, optional): maximum number of iterations before switching. Defaults to 100. Return: 3-tuple: raoff (np.array): array-like (n_dates x n_orbs) of RA offsets between the bodies (origin is at the other body) [mas] deoff (np.array): array-like (n_dates x n_orbs) of Dec offsets between the bodies [mas] vz (np.array): array-like (n_dates x n_orbs) of radial velocity offset between the bodies [km/s] Written: Jason Wang, Henry Ngo, 2018 """ n_orbs = np.size(sma) # num sets of input orbital parameters n_dates = np.size(epochs) # number of dates to compute offsets and vz # Necessary for _calc_ecc_anom, for now if np.isscalar(epochs): # just in case epochs is given as a scalar epochs = np.array([epochs]) ecc_arr = np.tile(ecc, (n_dates, 1)) # If mass not given, assume test particle case if mass is None: mass = np.zeros(n_orbs) # Compute period (from Kepler's third law) and mean motion period = np.sqrt(4 * np.pi**2.0 * (sma * u.AU)**3 / (consts.G * (mtot * u.Msun))) period = period.to(u.day).value mean_motion = 2 * np.pi / (period) # in rad/day # # compute mean anomaly (size: n_orbs x n_dates) manom = (mean_motion * (epochs[:, None] - tau_ref_epoch) - 2 * np.pi * tau) % (2.0 * np.pi) # compute eccentric anomalies (size: n_orbs x n_dates) eanom = _calc_ecc_anom(manom, ecc_arr, tolerance=tolerance, max_iter=max_iter) # compute the true anomalies (size: n_orbs x n_dates) # Note: matrix multiplication makes the shapes work out here and below tanom = 2. * np.arctan( np.sqrt((1.0 + ecc) / (1.0 - ecc)) * np.tan(0.5 * eanom)) # compute 3-D orbital radius of second body (size: n_orbs x n_dates) radius = sma * (1.0 - ecc * np.cos(eanom)) # compute ra/dec offsets (size: n_orbs x n_dates) # math from James Graham. Lots of trig c2i2 = np.cos(0.5 * inc)**2 s2i2 = np.sin(0.5 * inc)**2 arg1 = tanom + argp + lan arg2 = tanom + argp - lan c1 = np.cos(arg1) c2 = np.cos(arg2) s1 = np.sin(arg1) s2 = np.sin(arg2) # updated sign convention for Green Eq. 19.4-19.7 raoff = radius * (c2i2 * s1 - s2i2 * s2) * plx deoff = radius * (c2i2 * c1 + s2i2 * c2) * plx # zoff = np.ones(raoff.shape) zoff = radius * np.sin(tanom + argp) * np.sin(inc) * plx # compute the radial velocity (vz) of the body (size: n_orbs x n_dates) # first comptue the RV semi-amplitude (size: n_orbs x n_dates) m1 = mtot - mass # mass of the primary star Kv = np.sqrt(consts.G / (1.0 - ecc**2)) * (m1 * u.Msun * np.sin(inc)) / np.sqrt( mtot * u.Msun) / np.sqrt(sma * u.au) # Convert to km/s Kv = Kv.to(u.km / u.s) # compute the vz vz = Kv.value * (ecc * np.cos(argp) + np.cos(argp + tanom)) # Squeeze out extra dimension (useful if n_orbs = 1, does nothing if n_orbs > 1) # [()] used to convert 1-element arrays into scalars, has no effect for larger arrays # raoff = np.transpose(np.squeeze(raoff)[()]) # deoff = np.transpose(np.squeeze(deoff)[()]) # vz = np.transpose(np.squeeze(vz)[()]) vz = np.squeeze(vz)[()] return raoff, deoff, zoff, vz