def test_get_aor_from_transit_duration(): duration = 0.12 period = 10.1235 b = 0.34 ror = 0.06 r_star = 0.7 dv = tt.as_tensor_variable(duration) aor, jac = get_aor_from_transit_duration(dv, period, b, ror) assert np.allclose(theano.grad(aor, dv).eval(), jac.eval()) for orbit in [ KeplerianOrbit(period=period, t0=0.0, b=b, a=r_star * aor, r_star=r_star), KeplerianOrbit( period=period, t0=0.0, b=b, duration=duration, r_star=r_star, ror=ror, ), ]: x, y, z = orbit.get_planet_position(0.5 * duration) assert np.allclose(tt.sqrt(x**2 + y**2).eval(), r_star * (1 + ror)) x, y, z = orbit.get_planet_position(-0.5 * duration) assert np.allclose(tt.sqrt(x**2 + y**2).eval(), r_star * (1 + ror)) x, y, z = orbit.get_planet_position(period + 0.5 * duration) assert np.allclose(tt.sqrt(x**2 + y**2).eval(), r_star * (1 + ror))
def get_ror_from_approx_transit_depth(self, delta, b, jac=False): """Get the radius ratio corresponding to a particular transit depth This result will be approximate and it requires ``|b| < 1`` because it relies on the small planet approximation. Args: delta (tensor): The approximate transit depth in relative units b (tensor): The impact parameter jac (bool): If true, the Jacobian ``d ror / d delta`` is also returned Returns: ror: The radius ratio that approximately corresponds to the depth ``delta`` at impact parameter ``b``. """ b = as_tensor_variable(b) delta = as_tensor_variable(delta) f0 = 1 - 2 * self.u1 / 6.0 - 2 * self.u2 / 12.0 arg = 1 - tt.sqrt(1 - b**2) f = 1 - self.u1 * arg - self.u2 * arg**2 factor = f0 / f ror = tt.sqrt(delta * factor) if not jac: return tt.reshape(ror, b.shape) drorddelta = 0.5 * factor / ror return tt.reshape(ror, b.shape), tt.reshape(drorddelta, b.shape)
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 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_relative_angles(self, t, parallax=None, light_delay=False): """The planets' relative position to the star in the sky plane, in separation, position angle coordinates. .. note:: This treats each planet independently and does not take the other planets into account when computing the position of the star. This is fine as long as the planet masses are small. Args: t: The times where the position should be evaluated. light_delay: account for the light travel time delay? Default is False. Returns: The separation (arcseconds) and position angle (radians, measured east of north) of the planet relative to the star. """ X, Y, Z = self._get_position(-self.a, t, parallax, light_delay=light_delay) # calculate rho and theta rho = tt.squeeze(tt.sqrt(X**2 + Y**2)) # arcsec theta = tt.squeeze(tt.arctan2(Y, X)) # radians between [-pi, pi] return (rho, theta)
def get_aor_from_transit_duration(duration, period, b, ror=None): """Get the semimajor axis implied by a circular orbit and duration Args: duration: The transit duration period: The orbital period b: The impact parameter of the transit ror: The radius ratio of the planet to the star Returns: The semimajor axis in units of the stellar radius and the Jacobian ``d a / d duration`` """ if ror is None: ror = as_tensor_variable(0.0) b2 = b**2 opk2 = (1 + ror)**2 phi = np.pi * duration / period sinp = tt.sin(phi) cosp = tt.cos(phi) num = tt.sqrt(opk2 - b2 * cosp**2) aor = num / sinp grad = np.pi * cosp * (b2 - opk2) / (num * period * sinp**2) return aor, grad
def backward(self, y): y = tt.nnet.sigmoid(y) r1 = y[0] r2 = y[1] pl, pu = self.min_radius, self.max_radius b1 = (1 + pl) * (1 + (r1 - 1) / (1 - self.Ar)) p1 = pl + r2 * self.dr q1 = r1 / self.Ar q2 = r2 b2 = (1 + pl) + tt.sqrt(q1) * q2 * self.dr p2 = pu - self.dr * tt.sqrt(q1) * (1 - q2) pb = tt.switch( r1 > self.Ar, tt.stack((p1, b1), axis=0), tt.stack((p2, b2), axis=0), ) return pb
def __init__(self, *, sigma, period, Q0, dQ, f, **kwargs): self.sigma = tt.as_tensor_variable(sigma).astype("float64") self.period = tt.as_tensor_variable(period).astype("float64") self.Q0 = tt.as_tensor_variable(Q0).astype("float64") self.dQ = tt.as_tensor_variable(dQ).astype("float64") self.f = tt.as_tensor_variable(f).astype("float64") self.amp = self.sigma ** 2 / (1 + self.f) # One term with a period of period Q1 = 0.5 + self.Q0 + self.dQ w1 = 4 * np.pi * Q1 / (self.period * tt.sqrt(4 * Q1 ** 2 - 1)) S1 = self.amp / (w1 * Q1) # Another term at half the period Q2 = 0.5 + self.Q0 w2 = 8 * np.pi * Q2 / (self.period * tt.sqrt(4 * Q2 ** 2 - 1)) S2 = self.f * self.amp / (w2 * Q2) super().__init__( SHOTerm(S0=S1, w0=w1, Q=Q1), SHOTerm(S0=S2, w0=w2, Q=Q2), **kwargs )
def underdamped(self): Q = self.Q f = tt.sqrt(tt.maximum(4.0 * Q ** 2 - 1.0, self.eps)) a = self.S0 * self.w0 * Q c = 0.5 * self.w0 / Q empty = tt.zeros(0, dtype=self.dtype) return ( empty, empty, tt.stack([a]), tt.stack([a / f]), tt.stack([c]), tt.stack([c * f]), )
def overdamped(self): Q = self.Q f = tt.sqrt(tt.maximum(1.0 - 4.0 * Q ** 2, self.eps)) empty = tt.zeros(0, dtype=self.dtype) return ( 0.5 * self.S0 * self.w0 * Q * tt.stack([1.0 + 1.0 / f, 1.0 - 1.0 / f]), 0.5 * self.w0 / Q * tt.stack([1.0 - f, 1.0 + f]), empty, empty, empty, empty, )
def test_impact(): m_star = 0.151 r_star = 0.189 period = 0.4626413 t0 = 0.2 b = 0.5 ecc = 0.8 omega = 0.1 orbit = KeplerianOrbit( r_star=r_star, m_star=m_star, period=period, t0=t0, b=b, ecc=ecc, omega=omega, ) coords = orbit.get_relative_position(t0) assert np.allclose((tt.sqrt(coords[0]**2 + coords[1]**2) / r_star).eval(), b) assert coords[2].eval() > 0
def _get_consistent_inputs(a, period, rho_star, r_star, m_star, m_planet): if a is None and period is None: raise ValueError("values must be provided for at least one of a " "and period") if m_planet is not None: m_planet = as_tensor_variable(to_unit(m_planet, u.M_sun)) if a is not None: a = as_tensor_variable(to_unit(a, u.R_sun)) if m_planet is None: m_planet = tt.zeros_like(a) if period is not None: period = as_tensor_variable(to_unit(period, u.day)) if m_planet is None: m_planet = tt.zeros_like(period) # Compute the implied density if a and period are given implied_rho_star = False if a is not None and period is not None: if rho_star is not None or m_star is not None: raise ValueError("if both a and period are given, you can't " "also define rho_star or m_star") # Default to a stellar radius of 1 if not provided if r_star is None: r_star = as_tensor_variable(1.0) else: r_star = as_tensor_variable(to_unit(r_star, u.R_sun)) # Compute the implied mass via Kepler's 3rd law m_tot = 4 * np.pi * np.pi * a**3 / (G_grav * period**2) # Compute the implied density m_star = m_tot - m_planet vol_star = 4 * np.pi * r_star**3 / 3.0 rho_star = m_star / vol_star implied_rho_star = True # Make sure that the right combination of stellar parameters are given if r_star is None and m_star is None: r_star = 1.0 if rho_star is None: m_star = 1.0 if (not implied_rho_star) and sum( arg is None for arg in (rho_star, r_star, m_star)) != 1: raise ValueError("values must be provided for exactly two of " "rho_star, m_star, and r_star") if rho_star is not None and not implied_rho_star: if has_unit(rho_star): rho_star = as_tensor_variable( to_unit(rho_star, u.M_sun / u.R_sun**3)) else: rho_star = as_tensor_variable(rho_star) / gcc_per_sun if r_star is not None: r_star = as_tensor_variable(to_unit(r_star, u.R_sun)) if m_star is not None: m_star = as_tensor_variable(to_unit(m_star, u.M_sun)) # Work out the stellar parameters if rho_star is None: rho_star = 3 * m_star / (4 * np.pi * r_star**3) elif r_star is None: r_star = (3 * m_star / (4 * np.pi * rho_star))**(1 / 3) elif m_star is None: m_star = 4 * np.pi * r_star**3 * rho_star / 3.0 # Work out the planet parameters if a is None: a = (G_grav * (m_star + m_planet) * period**2 / (4 * np.pi**2))**(1.0 / 3) elif period is None: period = (2 * np.pi * a**(3 / 2) / (tt.sqrt(G_grav * (m_star + m_planet)))) return a, period, rho_star * gcc_per_sun, r_star, m_star, m_planet
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)), )
def __init__(self, period=None, a=None, t0=None, t_periastron=None, incl=None, b=None, duration=None, ecc=None, omega=None, sin_omega=None, cos_omega=None, Omega=None, m_planet=0.0, m_star=None, r_star=None, rho_star=None, ror=None, model=None, **kwargs): add_citations_to_model(self.__citations__, model=model) if "m_planet_units" in kwargs: deprecation_warning( "'m_planet_units' is deprecated; Use `with_unit` instead") m_planet = with_unit(m_planet, kwargs.pop("m_planet_units")) if "rho_star_units" in kwargs: deprecation_warning( "'rho_star_units' is deprecated; Use `with_unit` instead") rho_star = with_unit(rho_star, kwargs.pop("rho_star_units")) self.jacobians = defaultdict(lambda: defaultdict(None)) daordtau = None if ecc is None and duration is not None: if r_star is None: r_star = as_tensor_variable(1.0) if b is None: raise ValueError( "'b' must be provided for a circular orbit with a " "'duration'") if ror is None: warnings.warn( "When using the 'duration' parameter in KeplerianOrbit, " "the 'ror' parameter should also be provided.", UserWarning, ) aor, daordtau = get_aor_from_transit_duration(duration, period, b, ror=ror) a = r_star * aor duration = None inputs = _get_consistent_inputs(a, period, rho_star, r_star, m_star, m_planet) ( self.a, self.period, self.rho_star, self.r_star, self.m_star, self.m_planet, ) = inputs self.m_total = self.m_star + self.m_planet self.n = 2 * np.pi / self.period self.a_star = self.a * self.m_planet / self.m_total self.a_planet = -self.a * self.m_star / self.m_total # Track the Jacobian between the duration and a if daordtau is not None: dadtau = self.r_star * daordtau self.jacobians["duration"]["a"] = dadtau self.jacobians["duration"]["a_star"] = (dadtau * self.m_planet / self.m_total) self.jacobians["duration"]["a_planet"] = (-dadtau * self.m_star / self.m_total) # rho = 3 * pi * (a/R)**3 / (G * P**2) # -> drho / d(a/R) = 9 * pi * (a/R)**2 / (G * P**2) self.jacobians["duration"]["rho_star"] = ( 9 * np.pi * (self.a / self.r_star)**2 * daordtau * gcc_per_sun / (G_grav * self.period**2)) self.K0 = self.n * self.a / self.m_total if Omega is None: self.Omega = None else: self.Omega = as_tensor_variable(Omega) self.cos_Omega = tt.cos(self.Omega) self.sin_Omega = tt.sin(self.Omega) # Eccentricity if ecc is None: self.ecc = None self.M0 = 0.5 * np.pi + tt.zeros_like(self.n) incl_factor = 1 else: self.ecc = as_tensor_variable(ecc) if omega is not None: if sin_omega is not None and cos_omega is not None: raise ValueError( "either 'omega' or 'sin_omega' and 'cos_omega' can be " "provided") self.omega = as_tensor_variable(omega) self.cos_omega = tt.cos(self.omega) self.sin_omega = tt.sin(self.omega) elif sin_omega is not None and cos_omega is not None: self.cos_omega = as_tensor_variable(cos_omega) self.sin_omega = as_tensor_variable(sin_omega) self.omega = tt.arctan2(self.sin_omega, self.cos_omega) else: raise ValueError("both e and omega must be provided") opsw = 1 + self.sin_omega E0 = 2 * tt.arctan2( tt.sqrt(1 - self.ecc) * self.cos_omega, tt.sqrt(1 + self.ecc) * opsw, ) self.M0 = E0 - self.ecc * tt.sin(E0) ome2 = 1 - self.ecc**2 self.K0 /= tt.sqrt(ome2) incl_factor = (1 + self.ecc * self.sin_omega) / ome2 # The Jacobian for the transform cos(i) -> b self.dcosidb = self.jacobians["b"]["cos_incl"] = (incl_factor * self.r_star / self.a) if b is not None: if incl is not None or duration is not None: raise ValueError( "only one of 'incl', 'b', and 'duration' can be given") self.b = as_tensor_variable(b) self.cos_incl = self.dcosidb * self.b self.incl = tt.arccos(self.cos_incl) elif incl is not None: if duration is not None: raise ValueError( "only one of 'incl', 'b', and 'duration' can be given") self.incl = as_tensor_variable(incl) self.cos_incl = tt.cos(self.incl) self.b = self.cos_incl / self.dcosidb elif duration is not None: # This assertion should never be hit because of the first # conditional in this method, but let's keep it here anyways assert self.ecc is not None self.duration = as_tensor_variable(to_unit(duration, u.day)) c = tt.sin(np.pi * self.duration * incl_factor / self.period) c2 = c * c aor = self.a_planet / self.r_star esinw = self.ecc * self.sin_omega self.b = tt.sqrt( (aor**2 * c2 - 1) / (c2 * esinw**2 + 2 * c2 * esinw + c2 - self.ecc**4 + 2 * self.ecc**2 - 1)) self.b *= 1 - self.ecc**2 self.cos_incl = self.dcosidb * self.b self.incl = tt.arccos(self.cos_incl) else: zla = tt.zeros_like(self.a) self.incl = 0.5 * np.pi + zla self.cos_incl = zla self.b = zla if t0 is not None and t_periastron is not None: raise ValueError("you can't define both t0 and t_periastron") if t0 is None and t_periastron is None: t0 = tt.zeros_like(self.period) if t0 is None: self.t_periastron = as_tensor_variable(t_periastron) self.t0 = self.t_periastron + self.M0 / self.n else: self.t0 = as_tensor_variable(t0) self.t_periastron = self.t0 - self.M0 / self.n self.tref = self.t_periastron - self.t0 self.sin_incl = tt.sin(self.incl)
def in_transit(self, t, r=0.0, 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 not yet implemented for `in_transit`") z = tt.zeros_like(self.a) r = as_tensor_variable(r) + z R = self.r_star + z # Wrap the times into time since transit hp = 0.5 * self.period dt = tt.mod(self._warp_times(t) + hp, self.period) - hp if self.ecc is None: # Equation 14 from Winn (2010) k = r / R arg = tt.square(1 + k) - tt.square(self.b) factor = R / (self.a * self.sin_incl) hdur = hp * tt.arcsin(factor * tt.sqrt(arg)) / np.pi t_start = -hdur t_end = hdur flag = z else: M_contact = ops.contact_points( self.a, self.ecc, self.cos_omega, self.sin_omega, self.cos_incl + z, self.sin_incl + z, R + r, ) flag = M_contact[2] t_start = (M_contact[0] - self.M0) / self.n t_start = tt.mod(t_start + hp, self.period) - hp t_end = (M_contact[1] - self.M0) / self.n t_end = tt.mod(t_end + hp, self.period) - hp t_start = tt.switch(tt.gt(t_start, 0.0), t_start - self.period, t_start) t_end = tt.switch(tt.lt(t_end, 0.0), t_end + self.period, t_end) if texp is not None: t_start -= 0.5 * texp t_end += 0.5 * texp mask = tt.any(tt.and_(dt >= t_start, dt <= t_end), axis=-1) result = ifelse( tt.all(tt.eq(flag, 0)), tt.arange(t.shape[0])[mask], tt.arange(t.shape[0]), ) return result
def backward(self, y): q = tt.nnet.sigmoid(y) sqrtq1 = tt.sqrt(q[0]) twoq2 = 2 * q[1] u = tt.stack([sqrtq1 * twoq2, sqrtq1 * (1 - twoq2)]) return u
def get_amplitude(self): return tt.sqrt(self.cos**2 + self.sin**2)
def get_light_curve( self, orbit=None, r=None, t=None, texp=None, oversample=7, order=0, use_in_transit=None, light_delay=False, ): """Get the light curve for an orbit at a set of times Args: orbit: An object with a ``get_relative_position`` method that takes a tensor of times and returns a list of Cartesian coordinates of a set of bodies relative to the central source. This method should return three tensors (one for each coordinate dimension) and each tensor should have the shape ``append(t.shape, r.shape)`` or ``append(t.shape, oversample, r.shape)`` when ``texp`` is given. The first two coordinate dimensions are treated as being in the plane of the sky and the third coordinate is the line of sight with positive values pointing *away* from the observer. For an example, take a look at :class:`orbits.KeplerianOrbit`. r (tensor): The radius of the transiting body in the same units as ``r_star``. This should have a shape that is consistent with the coordinates returned by ``orbit``. In general, this means that it should probably be a scalar or a vector with one entry for each body in ``orbit``. Note that this is a different quantity than the planet-to-star radius ratio; do not confuse the two! t (tensor): The times where the light curve should be evaluated. texp (Optional[tensor]): The exposure time of each observation. This can be a scalar or a tensor with the same shape as ``t``. If ``texp`` is provided, ``t`` is assumed to indicate the timestamp at the *middle* of an exposure of length ``texp``. oversample (Optional[int]): The number of function evaluations to use when numerically integrating the exposure time. order (Optional[int]): The order of the numerical integration scheme. This must be one of the following: ``0`` for a centered Riemann sum (equivalent to the "resampling" procedure suggested by Kipping 2010), ``1`` for the trapezoid rule, or ``2`` for Simpson's rule. use_in_transit (Optional[bool]): If ``True``, the model will only be evaluated for the data points expected to be in transit as computed using the ``in_transit`` method on ``orbit``. """ if orbit is None: raise ValueError("missing required argument 'orbit'") if r is None: raise ValueError("missing required argument 'r'") if t is None: raise ValueError("missing required argument 't'") use_in_transit = (not light_delay if use_in_transit is None else use_in_transit) r = as_tensor_variable(r) r = tt.reshape(r, (r.size, )) t = as_tensor_variable(t) # If use_in_transit, we should only evaluate the model at times where # at least one planet is transiting if use_in_transit: transit_model = tt.shape_padleft(tt.zeros_like(r), t.ndim) + tt.shape_padright( tt.zeros_like(t), r.ndim) inds = orbit.in_transit(t, r=r, texp=texp, light_delay=light_delay) t = t[inds] # Handle exposure time integration if texp is None: tgrid = t rgrid = tt.shape_padleft(r, tgrid.ndim) + tt.shape_padright( tt.zeros_like(tgrid), r.ndim) else: texp = as_tensor_variable(texp) oversample = int(oversample) oversample += 1 - oversample % 2 stencil = np.ones(oversample) # Construct the exposure time integration stencil if order == 0: dt = np.linspace(-0.5, 0.5, 2 * oversample + 1)[1:-1:2] elif order == 1: dt = np.linspace(-0.5, 0.5, oversample) stencil[1:-1] = 2 elif order == 2: dt = np.linspace(-0.5, 0.5, oversample) stencil[1:-1:2] = 4 stencil[2:-1:2] = 2 else: raise ValueError("order must be <= 2") stencil /= np.sum(stencil) if texp.ndim == 0: dt = texp * dt else: if use_in_transit: dt = tt.shape_padright(texp[inds]) * dt else: dt = tt.shape_padright(texp) * dt tgrid = tt.shape_padright(t) + dt # Madness to get the shapes to work out... rgrid = tt.shape_padleft(r, tgrid.ndim) + tt.shape_padright( tt.zeros_like(tgrid), 1) # Evalute the coordinates of the transiting body in the plane of the # sky coords = orbit.get_relative_position(tgrid, light_delay=light_delay) b = tt.sqrt(coords[0]**2 + coords[1]**2) b = tt.reshape(b, rgrid.shape) los = tt.reshape(coords[2], rgrid.shape) lc = self._compute_light_curve(b / orbit.r_star, rgrid / orbit.r_star, los / orbit.r_star) if texp is not None: stencil = tt.shape_padright(tt.shape_padleft(stencil, t.ndim), 1) lc = tt.sum(stencil * lc, axis=t.ndim) if use_in_transit: transit_model = tt.set_subtensor(transit_model[inds], lc) return transit_model else: return lc