def in_transit(self, t, r=None, texp=None, light_delay=False): """Get a list of timestamps that are in transit Args: t (vector): A vector of timestamps to be evaluated. r (Optional): The radii of the planets. texp (Optional[float]): The exposure time. Returns: The indices of the timestamps that are in transit. """ if light_delay: raise NotImplementedError( "Light travel time delay is not implemented for simple orbits") dt = tt.mod(tt.shape_padright(t) - self._ref_time, self.period) dt -= self._half_period if r is None: tol = 0.5 * self.duration else: x = (r + self.r_star)**2 - self._b_norm**2 tol = tt.sqrt(x) / self.speed if texp is not None: tol += 0.5 * texp mask = tt.any(tt.abs_(dt) < tol, axis=-1) return tt.arange(t.size)[mask]
def get_value(self, tau0): dt = self.delta ar, cr, a, b, c, d = self.term.coefficients # Format the lags correctly tau0 = tt.abs_(tau0) tau = tau0[..., None] # Precompute some factors dpt = dt + tau dmt = dt - tau # Real parts: # tau > Delta crd = cr * dt cosh = tt.cosh(crd) norm = 2 * ar / crd ** 2 K_large = tt.sum(norm * (cosh - 1) * tt.exp(-cr * tau), axis=-1) # tau < Delta crdmt = cr * dmt K_small = K_large + tt.sum(norm * (crdmt - tt.sinh(crdmt)), axis=-1) # Complex part cd = c * dt dd = d * dt c2 = c ** 2 d2 = d ** 2 c2pd2 = c2 + d2 C1 = a * (c2 - d2) + 2 * b * c * d C2 = b * (c2 - d2) - 2 * a * c * d norm = 1.0 / (dt * c2pd2) ** 2 k0 = tt.exp(-c * tau) cdt = tt.cos(d * tau) sdt = tt.sin(d * tau) # For tau > Delta cos_term = 2 * (tt.cosh(cd) * tt.cos(dd) - 1) sin_term = 2 * (tt.sinh(cd) * tt.sin(dd)) factor = k0 * norm K_large += tt.sum( (C1 * cos_term - C2 * sin_term) * factor * cdt, axis=-1 ) K_large += tt.sum( (C2 * cos_term + C1 * sin_term) * factor * sdt, axis=-1 ) # tau < Delta edmt = tt.exp(-c * dmt) edpt = tt.exp(-c * dpt) cos_term = ( edmt * tt.cos(d * dmt) + edpt * tt.cos(d * dpt) - 2 * k0 * cdt ) sin_term = ( edmt * tt.sin(d * dmt) + edpt * tt.sin(d * dpt) - 2 * k0 * sdt ) K_small += tt.sum(2 * (a * c + b * d) * c2pd2 * dmt * norm, axis=-1) K_small += tt.sum((C1 * cos_term + C2 * sin_term) * norm, axis=-1) return tt.switch(tt.le(tau0, dt), K_small, K_large)
def get_value(self, tau): ar, cr, ac, bc, cc, dc = self.coefficients tau = tt.abs_(tau) tau = tt.shape_padright(tau) K = tt.sum(ar * tt.exp(-cr * tau), axis=-1) factor = tt.exp(-cc * tau) K += tt.sum(ac * factor * tt.cos(dc * tau), axis=-1) K += tt.sum(bc * factor * tt.sin(dc * tau), axis=-1) return K
def _get_retarded_position(self, a, t, parallax=None, z0=0.0, _pad=True): """Get the retarded position of a body, accounting for light delay. Args: a: the semi-major axis of the orbit. t: the time (or tensor of times) to calculate the position. parallax: (arcseconds) if provided, return the position in units of arcseconds. z0: the reference point along the z axis whose light travel time delay is taken to be zero. Default is the origin. Returns: The position of the body in the observer frame. Default is in units of R_sun, but if parallax is provided, then in units of arcseconds. """ sinf, cosf = self._get_true_anomaly(t, _pad=_pad) # Compute the orbital radius and the component of the velocity # in the z direction angvel = 2 * np.pi / self.period if self.ecc is None: r = a vamp = angvel * a vz = vamp * self.sin_incl * cosf else: r = a * (1.0 - self.ecc**2) / (1 + self.ecc * cosf) vamp = angvel * a / tt.sqrt(1 - self.ecc**2) cwf = self.cos_omega * cosf - self.sin_omega * sinf vz = vamp * self.sin_incl * (self.ecc * self.cos_omega + cwf) # True position of the body x, y, z = self._rotate_vector(r * cosf, r * sinf) # Component of the acceleration in the z direction az = -(angvel**2) * (a / r)**3 * z # Compute the time delay at the **retarded** position, accounting # for the instantaneous velocity and acceleration of the body. # See the derivation at https://github.com/rodluger/starry/issues/66 delay = tt.switch( tt.lt(tt.abs_(az), 1.0e-10), (z0 - z) / (c_light + vz), (c_light / az) * ((1 + vz / c_light) - tt.sqrt((1 + vz / c_light) * (1 + vz / c_light) - 2 * az * (z0 - z) / c_light**2)), ) if _pad: new_t = tt.shape_padright(t) - delay else: new_t = t - delay # Re-compute Kepler's equation, this time at the **retarded** position return self._get_position(a, new_t, parallax, _pad=False)
def get_relative_position(self, t, light_delay=False): """The planets' positions relative to the star Args: t: The times where the position should be evaluated. Returns: The components of the position vector at ``t`` in units of ``R_sun``. """ if light_delay: raise NotImplementedError( "Light travel time delay is not implemented for simple orbits") dt = tt.mod(tt.shape_padright(t) - self._ref_time, self.period) dt -= self._half_period x = tt.squeeze(self.speed * dt) y = tt.squeeze(self._b_norm + tt.zeros_like(dt)) m = tt.abs_(dt) < 0.5 * self.duration z = tt.squeeze(m * 1.0 - (~m) * 1.0) return x, y, z
def duration_to_eccentricity(func, duration, ror, **kwargs): # pragma: no cover num_planets = kwargs.pop("num_planets", 1) orbit_type = kwargs.pop("orbit_type", KeplerianOrbit) name = kwargs.get("name", "dur_ecc") inputs = _get_consistent_inputs( kwargs.get("a", None), kwargs.get("period", None), kwargs.get("rho_star", None), kwargs.get("r_star", None), kwargs.get("m_star", None), kwargs.get("rho_star_units", None), kwargs.get("m_planet", 0.0), kwargs.get("m_planet_units", None), ) a, period, rho_star, r_star, m_star, m_planet = inputs b = kwargs.get("b", 0.0) s = tt.sin(kwargs["omega"]) umax_inv = tt.switch(tt.lt(s, 0), tt.sqrt(1 - s**2), 1.0) const = (period * tt.shape_padright(r_star) * tt.sqrt((1 + ror)**2 - b**2)) const /= np.pi * a u = duration / const e1 = -s * u**2 / ((s * u)**2 + 1) e2 = tt.sqrt((s**2 - 1) * u**2 + 1) / ((s * u)**2 + 1) models = [] logjacs = [] logprobs = [] for args in product(*(zip("np", (-1, 1)) for _ in range(num_planets))): labels, signs = zip(*args) # Compute the eccentricity branch ecc = tt.stack([e1[i] + signs[i] * e2[i] for i in range(num_planets)]) # Work out the Jacobian valid_ecc = tt.and_(tt.lt(ecc, 1.0), tt.ge(ecc, 0.0)) logjac = tt.switch( tt.all(valid_ecc), tt.sum(0.5 * tt.log(1 - ecc**2) + 2 * tt.log(s * ecc + 1) - tt.log(tt.abs_(s + ecc)) - tt.log(const)), -np.inf, ) ecc = tt.switch(valid_ecc, ecc, tt.zeros_like(ecc)) # Create a sub-model to capture this component with pm.Model(name="dur_ecc_" + "_".join(labels)) as model: pm.Deterministic("ecc", ecc) orbit = orbit_type(ecc=ecc, **kwargs) logprob = tt.sum(func(orbit)) models.append(model) logjacs.append(logjac) logprobs.append(logprob) # Compute the marginalized likelihood logjacs = tt.stack(logjacs) logprobs = tt.stack(logprobs) logprob = tt.switch( tt.gt(1.0 / u, umax_inv), tt.sum(pm.logsumexp(logprobs + logjacs)), -np.inf, ) pm.Potential(name + "_logp", logprob) pm.Deterministic(name + "_logjacs", logjacs) pm.Deterministic(name + "_logprobs", logprobs) # Loop over models and compute the marginalized values for all the # parameters and deterministics norm = tt.sum(pm.logsumexp(logjacs)) logw = tt.switch( tt.gt(1.0 / u, umax_inv), logjacs - norm, -np.inf + tt.zeros_like(logjacs), ) pm.Deterministic(name + "_logw", logw) for k in models[0].named_vars.keys(): name = k[len(models[0].name) + 1:] pm.Deterministic( name, sum( tt.exp(logw[i]) * model.named_vars[model.name + "_" + name] for i, model in enumerate(models)), )