sd=0.12) # milliarcsec GAIA DR2 parallax = pm.Deterministic("parallax", 1e-3 * mparallax) # arcsec a_ang = pm.Uniform("aAng", 0.1, 10.0, testval=3.51) # milliarcsec # the semi-major axis in au a = pm.Deterministic("a", 1e-3 * a_ang / parallax) # au logP = pm.Uniform("logP", lower=np.log(20.0), upper=np.log(50.0), testval=np.log(34.87846)) # days P = pm.Deterministic("P", tt.exp(logP)) e = pm.Uniform("e", lower=0, upper=1, testval=0.62) omega = Angle("omega", testval=80.5 * deg) # omega_Aa Omega = Angle("Omega", testval=110.0 * deg) # - pi to pi # estimated assuming same as CB disk gamma = pm.Uniform("gamma", lower=0, upper=20, testval=10.1) # km/s # uniform on cos incl. testpoint assuming same as CB disk. cos_incl = pm.Uniform("cosIncl", lower=0.0, upper=1.0, testval=np.cos(48.0 * deg)) # radians, 0 to 180 degrees incl = pm.Deterministic("incl", tt.arccos(cos_incl)) # Since we're doing an RV + astrometric fit, M2 now becomes a parameter of the model # use a bounded normal to enforce positivity PosNormal = pm.Bound(pm.Normal, lower=0.0)
a_ang_inner = pm.Uniform("aAngInner", 0.1, 10.0, testval=4.613) # milliarcsec # the semi-major axis in au a_inner = pm.Deterministic("aInner", 1e-3 * a_ang_inner / parallax) # au logP_inner = pm.Uniform("logPInner", lower=np.log(20), upper=np.log(50.0), testval=np.log(34.88)) # days P_inner = pm.Deterministic("PInner", tt.exp(logP_inner)) e_inner = pm.Uniform("eInner", lower=0, upper=1, testval=0.63) omega_inner = Angle("omegaInner", testval=1.415) # omega_Aa Omega_inner = Angle("OmegaInner", testval=1.821) # constrained to be i > 90 cos_incl_inner = pm.Uniform("cosInclInner", lower=-1.0, upper=0.0, testval=-0.659) incl_inner = pm.Deterministic("inclInner", tt.arccos(cos_incl_inner)) MAb = pm.Normal("MAb", mu=0.29, sd=0.5, testval=0.241) # solar masses t_periastron_inner = pm.Uniform("tPeriastronInner", lower=1140.0, upper=1180.0, testval=1159.57) # + 2400000 + jd0
def default_nonlinear_prior(P_min=None, P_max=None, s=None, model=None, pars=None): r""" Retrieve pymc3 variables that specify the default prior on the nonlinear parameters of The Joker. See docstring of `JokerPrior.default()` for more information. The nonlinear parameters an default prior forms are: * ``P``, period: :math:`p(P) \propto 1/P`, over the domain :math:`(P_{\rm min}, P_{\rm max})` * ``e``, eccentricity: the short-period form from Kipping (2013) * ``M0``, phase: uniform over the domain :math:`(0, 2\pi)` * ``omega``, argument of pericenter: uniform over the domain :math:`(0, 2\pi)` * ``s``, additional extra variance added in quadrature to data uncertainties: delta-function at 0 Parameters ---------- P_min : `~astropy.units.Quantity` [time] P_max : `~astropy.units.Quantity` [time] s : `~pm.model.TensorVariable`, ~astropy.units.Quantity` [speed] model : `pymc3.Model` This is either required, or this function must be called within a pymc3 model context. """ import theano.tensor as tt import pymc3 as pm from exoplanet.distributions import Angle import exoplanet.units as xu from .distributions import UniformLog, Kipping13Global model = pm.modelcontext(model) if pars is None: pars = dict() if s is None: s = 0 * u.m / u.s if isinstance(s, pm.model.TensorVariable): pars['s'] = pars.get('s', s) else: if not hasattr(s, 'unit') or not s.unit.is_equivalent(u.km / u.s): raise u.UnitsError( "Invalid unit for s: must be equivalent to km/s") # dictionary of parameters to return out_pars = dict() with model: # Set up the default priors for parameters with defaults # Note: we have to do it this way (as opposed to with .get(..., default) # because this can only get executed if the param is not already # defined, otherwise variables are defined twice in the model if 'e' not in pars: out_pars['e'] = xu.with_unit(Kipping13Global('e'), u.one) # If either omega or M0 is specified by user, default to U(0,2π) if 'omega' not in pars: out_pars['omega'] = xu.with_unit(Angle('omega'), u.rad) if 'M0' not in pars: out_pars['M0'] = xu.with_unit(Angle('M0'), u.rad) if 's' not in pars: out_pars['s'] = xu.with_unit( pm.Deterministic('s', tt.constant(s.value)), s.unit) if 'P' not in pars: if P_min is None or P_max is None: raise ValueError( "If you are using the default period prior, " "you must pass in both P_min and P_max to set " "the period prior domain.") out_pars['P'] = xu.with_unit( UniformLog('P', P_min.value, P_max.to_value(P_min.unit)), P_min.unit) for k in pars.keys(): out_pars[k] = pars[k] return out_pars
# We'll include the parallax data as a prior on the parallax value # 27.31 mas +/- 0.12 mas # from GAIA mparallax = pm.Normal("mparallax", mu=27.31, sd=0.12) # milliarcsec parallax = pm.Deterministic("parallax", 1e-3 * mparallax) # arcsec a_ang = pm.Uniform("a_ang", 0.1, 4.0, testval=2.0) # arcsec # the semi-major axis in au a = pm.Deterministic("a", a_ang / parallax) # au # we expect the period to be somewhere in the range of 25 years, # so we'll set a broad prior on logP logP = pm.Normal("logP", mu=np.log(400), sd=1.0) P = pm.Deterministic("P", tt.exp(logP) * yr) # days omega = Angle("omega", testval=1.8) # - pi to pi Omega = Angle("Omega", testval=-0.7) # - pi to pi phi = Angle("phi", testval=2.) n = 2 * np.pi * tt.exp(-logP) / yr t_periastron = (phi + omega) / n cos_incl = pm.Uniform("cosIncl", lower=-1., upper=1.0, testval=np.cos(3.0)) incl = pm.Deterministic("incl", tt.arccos(cos_incl)) e = pm.Uniform("e", lower=0.0, upper=1.0, testval=0.3) gamma_keck = pm.Uniform("gammaKeck", lower=5, upper=10, testval=7.5) # km/s on Keck RV scale MB = pm.Normal("MB", mu=0.3, sd=0.5) # solar masses
def get_model(parallax=None): with pm.Model() as model: if parallax is None: # Without an actual parallax measurement, we can model the orbit in units of arcseconds # by providing a fake_parallax and conversion constant plx = 1 # arcsec else: # Below we will run a version of this model where a measurement of parallax is provided # The measurement is in milliarcsec m_plx = pm.Bound(pm.Normal, lower=0, upper=100)( "m_plx", mu=parallax[0], sd=parallax[1], testval=parallax[0] ) plx = pm.Deterministic("plx", 1e-3 * m_plx) a_ang = pm.Uniform("a_ang", 0.1, 1.0, testval=0.324) a = pm.Deterministic("a", a_ang / plx) # We expect the period to be somewhere in the range of 25 years, # so we'll set a broad prior on logP logP = pm.Normal( "logP", mu=np.log(25 * yr), sd=10.0, testval=np.log(28.8 * yr) ) P = pm.Deterministic("P", tt.exp(logP)) # For astrometric-only fits, it's generally better to fit in # p = (Omega + omega)/2 and m = (Omega - omega)/2 instead of omega and Omega # directly omega0 = 251.6 * deg - np.pi Omega0 = 159.6 * deg p = Angle("p", testval=0.5 * (Omega0 + omega0)) m = Angle("m", testval=0.5 * (Omega0 - omega0)) omega = pm.Deterministic("omega", p - m) Omega = pm.Deterministic("Omega", p + m) # For these orbits, it can also be better to fit for a phase angle # (relative to a reference time) instead of the time of periasteron # passage directly phase = Angle("phase", testval=0.0) tperi = pm.Deterministic("tperi", T0 + P * phase / (2 * np.pi)) # Geometric uiform prior on cos(incl) cos_incl = pm.Uniform( "cos_incl", lower=-1, upper=1, testval=np.cos(96.0 * deg) ) incl = pm.Deterministic("incl", tt.arccos(cos_incl)) ecc = pm.Uniform("ecc", lower=0.0, upper=1.0, testval=0.798) # Set up the orbit orbit = xo.orbits.KeplerianOrbit( a=a * au_to_R_sun, t_periastron=tperi, period=P, incl=incl, ecc=ecc, omega=omega, Omega=Omega, ) if parallax is not None: pm.Deterministic("M_tot", orbit.m_total) # Compute the model in rho and theta rho_model, theta_model = orbit.get_relative_angles(astro_jds, plx) pm.Deterministic("rho_model", rho_model) pm.Deterministic("theta_model", theta_model) # Add jitter terms to both separation and position angle log_rho_s = pm.Normal( "log_rho_s", mu=np.log(np.median(rho_err)), sd=5.0 ) log_theta_s = pm.Normal( "log_theta_s", mu=np.log(np.median(theta_err)), sd=5.0 ) rho_tot_err = tt.sqrt(rho_err ** 2 + tt.exp(2 * log_rho_s)) theta_tot_err = tt.sqrt(theta_err ** 2 + tt.exp(2 * log_theta_s)) # define the likelihood function, e.g., a Gaussian on both rho and theta pm.Normal("rho_obs", mu=rho_model, sd=rho_tot_err, observed=rho_data) # We want to be cognizant of the fact that theta wraps so the following is equivalent to # pm.Normal("obs_theta", mu=theta_model, observed=theta_data, sd=theta_tot_err) # but takes into account the wrapping. Thanks to Rob de Rosa for the tip. theta_diff = tt.arctan2( tt.sin(theta_model - theta_data), tt.cos(theta_model - theta_data) ) pm.Normal("theta_obs", mu=theta_diff, sd=theta_tot_err, observed=0.0) # Set up predicted orbits for later plotting rho_dense, theta_dense = orbit.get_relative_angles(t_fine, plx) rho_save = pm.Deterministic("rho_save", rho_dense) theta_save = pm.Deterministic("theta_save", theta_dense) # Optimize to find the initial parameters map_soln = model.test_point map_soln = xo.optimize(map_soln, vars=[log_rho_s, log_theta_s]) map_soln = xo.optimize(map_soln, vars=[phase]) map_soln = xo.optimize(map_soln, vars=[p, m, ecc]) map_soln = xo.optimize(map_soln, vars=[logP, a_ang, phase]) map_soln = xo.optimize(map_soln) return model, map_soln
sd=0.12) # milliarcsec GAIA DR2 parallax = pm.Deterministic("parallax", 1e-3 * mparallax) # arcsec # a_ang = pm.Uniform("aAng", 0.1, 4.0, testval=2.0) # arcsec a_ang = pm.Gamma("aAng", alpha=3.0, beta=1.5, testval=1.9) # arcsec # the semi-major axis in au a = pm.Deterministic("a", a_ang / parallax) # au # we expect the period to be somewhere in the range of 400 years, # so we'll set a broad prior on logP logP = pm.Normal("logP", mu=np.log(400), sd=0.8) P = pm.Deterministic("P", tt.exp(logP) * yr) # days # omega = Angle("omega", testval=180 * deg) # - pi to pi omega = Angle("omega") # - pi to pi # because we don't have RV information in this model, # Omega and Omega + 180 are degenerate. # I think flipping Omega also advances omega and phi by pi # So, just limit it to -90 to 90 degrees # Omega_intermediate = Angle("OmegaIntermediate") # - pi to pi # Omega = pm.Deterministic("Omega", Omega_intermediate / 2 + np.pi / 2) # 0 to pi Omega = Angle("Omega") phi = Angle("phi") # phase (Mean anom) at t = 0 n = 2 * np.pi * tt.exp(-logP) / yr t_periastron = pm.Deterministic("tPeri", (phi + omega) / n)
with pm.Model() as model: # Parameters logK = pm.Uniform( "logK", lower=0, upper=np.log(200), testval=np.log(0.5 * (np.max(rv) - np.min(rv))), ) logP = pm.Uniform("logP", lower=0, upper=np.log(10), testval=np.log(lit_period)) phi = pm.Uniform("phi", lower=0, upper=2 * np.pi, testval=0.1) e = pm.Uniform("e", lower=0, upper=1, testval=0.1) w = Angle("w") logjitter = pm.Uniform("logjitter", lower=-10, upper=5, testval=np.log(np.mean(rv_err))) rv0 = pm.Normal("rv0", mu=0.0, sd=10.0, testval=0.0) rvtrend = pm.Normal("rvtrend", mu=0.0, sd=10.0, testval=0.0) # Deterministic transformations n = 2 * np.pi * tt.exp(-logP) P = pm.Deterministic("P", tt.exp(logP)) K = pm.Deterministic("K", tt.exp(logK)) cosw = tt.cos(w) sinw = tt.sin(w) s2 = tt.exp(2 * logjitter) t0 = (phi + w) / n
a_ang_inner = pm.Uniform("a_ang_inner", 0.1, 10.0, testval=4.66) # milliarcsec # the semi-major axis in au a_inner = pm.Deterministic("a_inner", 1e-3 * a_ang_inner / parallax) # au logP_inner = pm.Uniform("logP_inner", lower=0, upper=np.log(50.0), testval=np.log(34.879)) # days P_inner = pm.Deterministic("P_inner", tt.exp(logP_inner)) e_inner = pm.Uniform("e_inner", lower=0, upper=1, testval=0.63) omega_inner = Angle("omega_inner", testval=1.42) # omega_Aa Omega_inner = Angle("Omega_inner", testval=1.99) cos_incl_inner = pm.Uniform("cos_incl_inner", lower=-1.0, upper=1.0, testval=0.67) incl_inner = pm.Deterministic("incl_inner", tt.arccos(cos_incl_inner)) MAb = pm.Normal("MAb", mu=0.29, sd=0.5) # solar masses t_periastron_inner = pm.Uniform("t_periastron_inner", lower=4050.0, upper=4100.0, testval=4073.0) # + 2400000 + jd0
upper=np.log(100), testval=np.log(25)) # km/s KAa = pm.Deterministic("KAa", tt.exp(logKAa)) KAb = pm.Deterministic("KAb", tt.exp(logKAb)) logP = pm.Uniform("logP", lower=np.log(20.0), upper=np.log(50.0), testval=np.log(34.87846)) # days P = pm.Deterministic("P", tt.exp(logP)) e = pm.Uniform("e", lower=0, upper=1, testval=0.62) omega = Angle("omega", testval=80.5 * deg) # omega_Aa gamma = pm.Uniform("gamma", lower=0, upper=20, testval=10.1) # relative to jd0 t_periastron = pm.Uniform("tPeri", lower=1130.0, upper=1180.0, testval=1159.0) # + 2400000 days orbit = xo.orbits.KeplerianOrbit(period=P, ecc=e, t_periastron=t_periastron, omega=omega) # since we have 4 instruments, we need to predict 4 different dataseries