def __init__(self): # Instantiate a star with a dipole map A = starry.Primary(starry.Map(ydeg=1), prot=0.0) amp_true = 0.75 y_true = np.array([1, 0.1, 0.2, 0.3]) inc_true = 60 A.map.amp = amp_true A.map[1, :] = y_true[1:] A.map.inc = inc_true # Instantiate two transiting planets with different longitudes of # ascending node. This ensures there's no null space! b = starry.Secondary(starry.Map(amp=0), porb=1.0, r=0.1, t0=-0.05, Omega=30.0) c = starry.Secondary(starry.Map(amp=0), porb=1.0, r=0.1, t0=0.05, Omega=-30.0) sys = starry.System(A, b, c) # Generate a synthetic light curve with just a little noise t = np.linspace(-0.1, 0.1, 100) flux = sys.flux(t) sigma = 1e-5 np.random.seed(1) flux += np.random.randn(len(t)) * sigma # Store self.A = A self.b = b self.c = c self.sys = sys self.t = t self.flux = flux self.sigma = sigma self.amp_true = amp_true self.y_true = y_true self.inc_true = inc_true
def test_period_semi(): # Check that an error is raised if neither a nor porb is given with pytest.raises(ValueError) as e: body = starry.Secondary(starry.Map()) assert "Must provide a value for either `porb` or `a`" in str(e.value) # Check that the semi --> period conversion works pri = starry.Primary(starry.Map(), m=1.0, mass_unit=u.Msun) sec = starry.Secondary(starry.Map(), a=10.0, m=1.0, length_unit=u.AU, mass_unit=u.Mearth) sys = starry.System(pri, sec) period = sys._get_periods()[0] true_period = ( ((2 * np.pi) * (sec.a * sec.length_unit)**(3 / 2) / (np.sqrt(G * (pri.m * pri.mass_unit + sec.m * sec.mass_unit)))).to( u.day).value) assert np.allclose(period, true_period)
def test_sys_flux(): """Test the normalization of the flux.""" # Instantiate a system. Planet has radius `r` and is at # distance `d` from a point illumination source. d = 10 r = 2 planet = starry.Secondary(starry.Map(reflected=True), a=d, r=r) star = starry.Primary(starry.Map(), r=0) sys = starry.System(star, planet) # Get the star & planet flux when it's at full phase t_full = 0.5 * sys._get_periods()[0] f_star, f_planet = sys.flux(t=t_full, total=False) # Star should have unit flux assert np.allclose(f_star, 1.0) # Planet should have flux equal to (2 / 3) r^2 / d^2 f_expected = (2.0 / 3.0) * r ** 2 / d ** 2 assert np.allclose(f_planet, f_expected)
def test_compare_to_exoplanet(): """Ensure we get the same result with `starry` and `exoplanet`. """ # Define the star A = starry.Primary( starry.Map(rv=True, veq=0), r=1.0, m=1.0, prot=0, length_unit=u.Rsun, mass_unit=u.Msun, ) # Define the planet b = starry.Secondary( starry.Map(rv=True, veq=0), r=0.1, porb=1.0, m=0.01, t0=0.0, inc=86.0, ecc=0.3, w=60, length_unit=u.Rsun, mass_unit=u.Msun, angle_unit=u.degree, time_unit=u.day, ) # Define the planet c = starry.Secondary( starry.Map(rv=True, veq=0), r=0.1, porb=1.7, m=0.02, t0=0.3, inc=87.0, ecc=0.2, w=70, length_unit=u.Rsun, mass_unit=u.Msun, angle_unit=u.degree, time_unit=u.day, ) # Define the system sys = starry.System(A, b, c) # Compute with starry time = np.linspace(-0.5, 0.5, 1000) rv1 = sys.rv(time, keplerian=True, total=True) # Compute with exoplanet orbit = exoplanet.orbits.KeplerianOrbit( period=[1.0, 1.7], t0=[0.0, 0.3], incl=[86.0 * np.pi / 180, 87.0 * np.pi / 180], ecc=[0.3, 0.2], omega=[60 * np.pi / 180, 70 * np.pi / 180], m_planet=[0.01, 0.02], m_star=1.0, r_star=1.0, ) rv2 = orbit.get_radial_velocity(time).eval().sum(axis=1) assert np.allclose(rv1, rv2)
inc = planet.inc * np.pi / 180 Omega = planet.Omega * np.pi / 180 # Rotate the map to the correct orientation on the sky planet.map.rotate(axis=(1, 0, 0), theta=np.pi / 2 - inc) planet.map.rotate(axis=(0, 0, 1), theta=Omega) # Rotate the axis of rotation in the same way planet.axis = np.dot(R((1, 0, 0), np.pi / 2 - inc), planet.axis) planet.axis = np.dot(R((0, 0, 1), Omega), planet.axis) # Instantiate the system planet = starry.Planet(L=1, porb=1, prot=1) star = starry.Star() sys = starry.System([star, planet]) time = np.linspace(0, 1, 1000) Omega = 0 planet.Omega = Omega # Set up the figure fig = pl.figure(figsize=(12, 6)) ax = np.array([[pl.subplot2grid((5, 16), (i, j)) for j in range(8)] for i in range(5)]) ax_lc = pl.subplot2grid((5, 16), (0, 9), rowspan=5, colspan=8) x, y = np.meshgrid(np.linspace(-1, 1, 100), np.linspace(-1, 1, 100)) lam = np.linspace(0, 2 * np.pi, 8, endpoint=False) phase = np.linspace(90, 450, 1000) phase[np.argmax(phase > 360)] = np.nan phase[phase > 360] -= 360
6, amp=planet_amp, inc=85.76, ), r=1.138, m=1.162, inc=85.76, porb=2.22, prot=2.22, theta0=180, length_unit=u.Rjup, mass_unit=u.Mjup, angle_unit=u.degree, ) sys = starry.System(star, planet) time = np.arange(-2, 2, MINUTE) A0 = planet_amp * sys.design_matrix(time)[:, 1] A = planet_amp * sys.design_matrix(time)[:, 2:] # Generate sigma = 2.9e-5 N = (planet.map.ydeg + 1)**2 - 1 for lon in np.linspace(-180, 180, 30, endpoint=False): planet.map.add_spot(intensity=0.01, sigma=0.1, lat=0, lon=lon, relative=False) planet.map.rotate([0, 1, 0], 15)
lnlike_inputs = itertools.product(vals, vals, woodbury) # Instantiate a star with a dipole map A = starry.Primary(starry.Map(ydeg=1), prot=0.0) amp_true = 0.75 y_true = np.array([1, 0.1, 0.2, 0.3]) inc_true = 60 A.map.amp = amp_true A.map[1, :] = y_true[1:] A.map.inc = inc_true # Instantiate two transiting planets with different longitudes of # ascending node. This ensures there's no null space! b = starry.Secondary(starry.Map(amp=0), porb=1.0, r=0.1, t0=-0.05, Omega=30.0) c = starry.Secondary(starry.Map(amp=0), porb=1.0, r=0.1, t0=0.05, Omega=-30.0) sys = starry.System(A, b, c) # Generate a synthetic light curve with just a little noise t = np.linspace(-0.1, 0.1, 100) flux = sys.flux(t) sigma = 1e-5 np.random.seed(1) flux += np.random.randn(len(t)) * sigma @pytest.mark.parametrize("L,C", solve_inputs) def test_solve(L, C): # Place a generous prior on the map coefficients if L == "scalar": A.map.set_prior(L=1) elif L == "vector":
def comparison(): """Run the main comparison function.""" # Define SPIDERMAN model parameters # Parameters adapted from # https://github.com/tomlouden/SPIDERMAN/ # blob/master/examples/Brightness%20maps.ipynb spider_params = sp.ModelParams(brightness_model='spherical') spider_params.n_layers = 10 # Will be reset later spider_params.t0 = 0 # Central time of PRIMARY transit [d] spider_params.per = 0.81347753 # Period [days] spider_params.a_abs = 1.0e-30 # Nearly 0 a to ignore light travel spider_params.inc = 90.0 # Inclination [degrees] spider_params.ecc = 0.0 # Eccentricity spider_params.w = 0.0 # Argument of periastron spider_params.rp = 0.1594 # Planet to star radius ratio spider_params.a = 4.855 # Semi-major axis scaled by rstar spider_params.p_u1 = 0.0 # Planetary limb darkening parameter spider_params.p_u2 = 0.0 # Planetary limb darkening parameter # SPIDERMAN spherical harmonics parameters ratio = 1.0e-3 # Planet-star flux ratio spider_params.sph = [ratio, ratio / 2, 0, 0] # vector of Ylm coeffs spider_params.degree = 2 spider_params.la0 = 0.0 spider_params.lo0 = 0.0 # Define starry model parameters to match SPIDERMAN system # Define star star = starry.Star() # Define planet planet = starry.Planet( lmax=2, lambda0=90.0, w=spider_params.w, r=spider_params.rp, # Factor of pi to match SPIDERMAN normalization L=1.0e-3 * np.pi, inc=spider_params.inc, a=spider_params.a, porb=spider_params.per, tref=spider_params.t0, prot=spider_params.per, ecc=spider_params.ecc) # Define spherical harmonic coefficients planet.map[0, 0] = 1.0 planet.map[1, -1] = 0.0 planet.map[1, 0] = 0.0 planet.map[1, 1] = 0.5 # Make a system system = starry.System([star, planet]) # Now make a multiprecision system to compute error estimates mstar = starry.multi.Star() mplanet = starry.multi.Planet(lmax=2, lambda0=90., w=spider_params.w, r=spider_params.rp, L=1.0e-3 * np.pi, inc=spider_params.inc, a=spider_params.a, porb=spider_params.per, tref=spider_params.t0, prot=spider_params.per, ecc=spider_params.ecc) mplanet.map[:] = planet.map[:] msystem = starry.multi.System([mstar, mplanet]) # ## Speed test! ## # # Number of time array points ns = np.array([10, 50, 100, 500, 1000], dtype=int) # SPIDERMAN grid resolution points ngrid = np.array([5, 10, 20, 50, 100], dtype=int) n_repeats = 3 t_starry = np.nan + np.zeros(len(ns)) t_spiderman = np.nan + np.zeros((len(ns), len(ngrid))) diff1 = np.nan + np.zeros_like(t_starry) diff2 = np.nan + np.zeros_like(t_spiderman) flux_comp = [] # Loop over time grid sizes for ii, n in enumerate(ns): # New time array of length n just around the # occulation and some phase curve time_arr = np.linspace(0.4 * spider_params.per, 0.6 * spider_params.per, n) # Compute **exact** flux using multiprecision msystem.compute(time_arr) mflux = np.array(msystem.flux) # Repeat calculation a few times and pick fastest one best_starry = np.inf for _ in range(n_repeats): start = time.time() system.compute(time_arr) flux = np.array(system.flux) dt = time.time() - start if dt < best_starry: best_starry = dt best_starry_flux = flux # Save fastest time t_starry[ii] = best_starry # Compute error diff1[ii] = np.max(np.fabs((mflux - best_starry_flux) / mflux)) # Time batman (for all grid resolutions) for jj, ng in enumerate(ngrid): # New number of layers spider_params.n_layers = ng # Repeat calculation a few times best_spiderman = np.inf for _ in range(n_repeats): start = time.time() lc = spider_params.lightcurve(time_arr, use_phase=False) dt = time.time() - start if (ng == 5): spider_lc_5 = np.array(lc) if dt < best_spiderman: best_spiderman = dt best_spiderman_flux = lc # Save fastest time t_spiderman[ii, jj] = best_spiderman # Save log maximum relative error diff2[ii, jj] = np.max(np.fabs((mflux - best_spiderman_flux) / mflux)) # For highest time resolution, compute differences # between the predictions if n == ns[-1]: flux_comp.append( np.fabs(best_starry_flux - best_spiderman_flux)) # ## Generate the figures! ## # # First figure: relative error fig = plt.figure(figsize=(5, 5)) ax = plt.subplot2grid((3, 1), (0, 0), colspan=1, rowspan=1) ax2 = plt.subplot2grid((3, 1), (1, 0), colspan=1, rowspan=2) time_arr = np.linspace(0.4 * spider_params.per, 0.6 * spider_params.per, ns[-1]) # Flux panel ax.plot(time_arr, best_starry_flux, lw=1, alpha=1, label='starry', color='gray') ax.plot(time_arr, spider_lc_5, lw=1, alpha=1, ls='--', label='spiderman (5)') ax.legend(loc='lower left', framealpha=0.0, fontsize=10) ax.get_xaxis().set_ticklabels([]) ax.set_xlim(time_arr.min(), time_arr.max()) ax.set_ylim(1.000 - 0.001, 1.004 + 0.001) ax.set_ylabel("Flux", fontsize=14) # Error panel for kk in range(len(flux_comp)): ax2.plot(time_arr, flux_comp[kk], lw=1, label="n$_{\mathrm{layers}}$=%d" % ngrid[kk]) ax2.set_ylim(1.0e-11, 1.0e-4) ax2.set_xlim(time_arr.min(), time_arr.max()) ax2.set_yscale("log") ax2.set_ylabel("Relative error", fontsize=14) ax2.set_xlabel("Time [d]", fontsize=14) ax2.legend(loc="best", framealpha=0.0) fig.savefig("spidercomp_flux.pdf", bbox_inches="tight") # Second figure: speed comparison fig = plt.figure(figsize=(7, 4)) ax = plt.subplot2grid((2, 5), (0, 0), colspan=4, rowspan=2) axleg1 = plt.subplot2grid((2, 5), (0, 4)) axleg2 = plt.subplot2grid((2, 5), (1, 4)) axleg1.axis('off') axleg2.axis('off') ax.set_xlabel('Number of points', fontsize=13) for tick in ax.get_xticklabels(): tick.set_fontsize(12) ax.set_ylabel('Evaluation time [seconds]', fontsize=13) ax.set_yscale("log") ax.set_xscale("log") for tick in ax.get_yticklabels(): tick.set_fontsize(12) # Starry, loop over all points for ii in range(len(ns)): ax.plot(ns[ii], t_starry[ii], "o", lw=2, color="gray", ms=ms(diff1[ii])) ax.plot(ns, t_starry, "-", lw=1.5, color="gray", alpha=0.45) # Loop over all grid resolutions for jj, ng in enumerate(ngrid): ax.plot(ns, t_spiderman[:, jj], "-", color="C%d" % (jj), alpha=0.25, lw=1.5) for kk in range(len(ns)): ax.plot(ns[kk], t_spiderman[kk, jj], "o", ms=ms(diff2[kk, jj]), color="C%d" % (jj)) # Legend 1 axleg1.plot([0, 1], [0, 1], color='gray', label='starry') # Loop over all grid resolutions for jj, ng in enumerate(ngrid): axleg1.plot([0, 1], [0, 1], color="C%d" % (jj), label="n$_{\mathrm{layers}}$=%d" % ng) axleg1.set_xlim(2, 3) leg = axleg1.legend(loc='center', frameon=False) leg.set_title('method', prop={'weight': 'bold'}) for logerr in [-16, -12, -8, -4, 0]: axleg2.plot([0, 1], [0, 1], 'o', color='gray', ms=ms(10**logerr), label=r'$%3d$' % logerr) axleg2.set_xlim(2, 3) leg = axleg2.legend(loc='center', labelspacing=1, frameon=False) leg.set_title('log error', prop={'weight': 'bold'}) fig.savefig("spidercomp.pdf", bbox_inches="tight")
def test_light_delay(): pri = starry.Primary(starry.Map()) sec = starry.Secondary(starry.Map(), porb=1.0) sys = starry.System(pri, sec, light_delay=True) assert sys.light_delay is True
r=21.1770214, length_unit=u.earthRad, inc=87.2, t0=-0.65625, ) # Time arrays (secondary eclipse ingress / full phase curve) t_ingress = np.linspace(0, 0.017, 1000) t_egress = 218 / 60 / 24 + t_ingress t_sec = np.append(t_ingress, t_egress) t_phase = kwargs["t0"] + kwargs["porb"] * np.linspace(-0.5, 0.5, 1000) # Uniform e_map = starry.Map(ydeg=1, reflected=False) e = starry.Secondary(e_map, **kwargs) sys = starry.System(star, e) flux_em = sys.flux(t=t_sec, total=False)[1] # Noon approximation e_map = starry.Map(udeg=1, reflected=False) e_map[1] = 1 e = starry.Secondary(e_map, **kwargs) sys = starry.System(star, e) flux_ld = sys.flux(t=t_sec, total=False)[1] # Reflected, point source e_map = starry.Map(ydeg=1, reflected=True) e_map.amp *= 0.2 e = starry.Secondary(e_map, **kwargs) sys = starry.System(star, e) flux_ref = sys.flux(t=t_sec, total=False)[1]
def test_reflected_light(): pri = starry.Primary(starry.Map(amp=0), r=1) sec = starry.Secondary(starry.Map(reflected=True), porb=1.0, r=1) sys = starry.System(pri, sec) t = np.concatenate((np.linspace(0.1, 0.4, 50), np.linspace(0.6, 0.9, 50))) flux = sys.flux(t)
def test_bodies(): pri = starry.Primary(starry.Map()) sec = starry.Secondary(starry.Map(ydeg=1), porb=1.0) sys = starry.System(pri, sec) assert sys.primary == pri assert sys.secondaries[0] == sec
def test_edge_on_eccentric(): # Params ydeg = 10 u = [0.5, 0.25] y = 0.1 * np.random.randn((ydeg + 1)**2 - 1) porb = 1.0 prot = 1.0 amp = 0.25 r = 0.5 m = 0.25 ecc = 0.5 w = 75 t = np.linspace(-0.75, 0.75, 10000) # Beta version pri_beta = starry_beta.kepler.Primary(lmax=2) pri_beta[1], pri_beta[2] = u sec_beta = starry_beta.kepler.Secondary(lmax=ydeg) sec_beta[1:, :] = y sec_beta.porb = porb sec_beta.prot = prot sec_beta.L = amp sec_beta.r = r sec_beta.a = (G_grav * (1.0 + m) * porb**2 / (4 * np.pi**2))**(1.0 / 3) sec_beta.inc = 90 sec_beta.Omega = 0 sec_beta.ecc = ecc sec_beta.w = w sys_beta = starry_beta.kepler.System(pri_beta, sec_beta) sys_beta.compute(t) flux_beta = np.array(sys_beta.lightcurve) # Compute the time of transit M0 = 0.5 * np.pi - w * np.pi / 180.0 f = M0 E = np.arctan2(np.sqrt(1 - ecc**2) * np.sin(f), ecc + np.cos(f)) M = E - ecc * np.sin(E) t0 = (M - M0) * porb / (2 * np.pi) # Compute the time of eclipse E = np.arctan2( np.sqrt(1 - ecc**2) * np.sin(f + np.pi), ecc + np.cos(f + np.pi)) M = E - ecc * np.sin(E) t_ecl = (M - M0) * porb / (2 * np.pi) # This is the required phase offset such that the map coefficients # correspond to what the observer sees at secondary eclipse theta0 = -(t_ecl - t0) * 360 # Version 1 pri = starry.Primary(starry.Map(udeg=2)) pri.map[1:] = u sec = starry.Secondary( starry.Map(ydeg=ydeg, amp=amp), porb=porb, r=r, m=m, inc=90, Omega=0, ecc=ecc, w=w, t0=t0, theta0=theta0, ) sec.map[1:, :] = y sys = starry.System(pri, sec) flux = sys.flux(t) # Compare assert np.allclose(flux, flux_beta)
def test_default_system_units(): pri = starry.Primary(starry.Map()) sec = starry.Secondary(starry.Map(), porb=1.0) sys = starry.System(pri, sec) assert sys.time_unit == u.day