def learn_map(high_snr=False): """ In this case, we know the spectrum and the baseline perfectly. The problem is linear in the map, so solving the Doppler problem is easy! """ # High or low SNR? if high_snr: ferr = 1e-4 name = "learn_map_high_snr" else: ferr = 1e-3 name = "learn_map_low_snr" # Generate data dop = pp.Doppler(ydeg=15) dop.generate_data(ferr=ferr) # Compute the true baseline (assumed to be known exactly) dop.u = dop.u_true baseline = dop.baseline() # Reset all coefficients dop.vT = dop.vT_true dop.u = None # Solve! loss, cho_u, cho_vT = dop.solve(vT=dop.vT, baseline=baseline) plot_results(dop, name=name, loss=loss, cho_u=cho_u, cho_vT=cho_vT)
def learn_map_and_baseline(high_snr=False): """ In this case, we know the rest frame spectrum, but we don't know the map coefficients or the baseline flux. The problem can be linearized to solve for the coefficients, and then refined with the non-linear solver. """ # High or low SNR? if high_snr: # At high SNR, we need to do a bit of refinement # with the non-linear solver. ferr = 1e-4 niter = 50 lr = 1e-4 name = "learn_map_baseline_high_snr" else: # At low SNR, a single run of the bi-linear solver # gets us to the optimum! ferr = 1e-3 niter = 0 lr = None name = "learn_map_baseline_low_snr" # Generate data dop = pp.Doppler(ydeg=15) dop.generate_data(ferr=ferr) # Reset all coefficients dop.vT = dop.vT_true dop.u = None # Solve! loss, cho_u, cho_vT = dop.solve(vT=dop.vT, niter=niter, lr=lr) plot_results(dop, name=name, loss=loss, cho_u=cho_u, cho_vT=cho_vT)
def flux(lam, t, A, ydeg=5, vsini=40.0, inc=60.0, P=1.0): N = (ydeg + 1) ** 2 doppler = pp.Doppler(lam, ydeg=ydeg, vsini=vsini, inc=inc, P=P) G = doppler._g() map = starry.Map(ydeg, nw=len(doppler.lam_padded)) axis = map.ops.get_axis(inc * np.pi / 180.0, 0.0) theta = (2 * np.pi / P * t) % (2 * np.pi) F = tt.as_tensor_variable( [ convdot(map.ops.rotate(axis, th, A), G, N) for n, th in enumerate(theta) ] ) return F
def learn_everything(high_snr=False): """ In this case, we know nothing: we're going to learn both the map and the spectrum. We're not giving the algorithm an initial guess, either: the spectrum is learned by deconvolving the data, and the initial guess for the map is computed via the linearized problem. """ # High or low SNR? if high_snr: # We rely heavily on tempering here. Once we get # a good initial guess via the bilinear solver, # we run the non-linear solver with a slow learning # rate. ferr = 1e-4 T = 5000.0 niter = 1000 lr = 1e-4 name = "learn_everything_high_snr" else: # This case is easier; just a little tempering for # good measure, followed by a fast non-linear # refinement. ferr = 1e-3 T = 10.0 niter = 250 lr = 2e-3 name = "learn_everything_low_snr" # Generate data dop = pp.Doppler(ydeg=15) dop.generate_data(ferr=ferr) # Reset all coefficients dop.u = None dop.vT = None # Solve! loss, cho_u, cho_vT = dop.solve(niter=niter, lr=lr, T=T) plot_results(dop, name=name, loss=loss, cho_u=cho_u, cho_vT=cho_vT)
def load_data(self, t, lam, F, ferr=1.e-4): """Load a dataset. Args: t (ndarray): Time array in days. lam (ndarray): Uniformly spaced log-wavelength array. F (ndarray): Matrix of observed spectra of shape `(nt, nlam)`. ferr (float, optional): Uncertainty on the flux. Defaults to 1.e-4. """ doppler = pp.Doppler(lam, ydeg=self.ydeg, vsini=self.vsini, inc=self.inc) self.theta = (360.0 * t) % 360.0 self.D = doppler.D(theta=self.theta) self.lam_padded = doppler.lam_padded self.t = t self.lam = lam self.F = F self.ferr = ferr self.M, self.K = self.F.shape self.Kp = self.D.shape[1] // self.N self._loaded = True
guess for the map is computed via the linearized problem. """ import paparazzi as pp import numpy as np from utils.spot import plot_results np.random.seed(13) # Let's plot the high SNR case for the paper ferr = 1e-4 T = 5000.0 niter = 3000 lr = 2.5e-5 dlogT = -0.04 # Generate data dop = pp.Doppler(ydeg=15, u=[0.5, 0.25]) dop.generate_data(ferr=ferr) # Reset all coefficients dop.y1 = None dop.s = None # Solve! loss, cho_y1, cho_s = dop.solve(niter=niter, lr=lr, T=T, dlogT=dlogT) # Plot the results plot_results(dop, name="spot_y1bs", loss=loss, cho_y1=cho_y1, cho_s=cho_s)
# Get the Ylm expansion of a Gaussian spot ydeg = 20 N = (ydeg + 1)**2 map = starry.Map(ydeg) map.add_spot(amp=-0.03, sigma=0.05, lat=30, lon=0) y1 = np.array(map.y.eval())[1:] # Check that the specific intensity is positive everywhere assert np.nanmin(map.render().eval()) > 0 # Generate the dataset vsini = 40.0 # km/s nt = 15 theta = np.append([-180], np.linspace(-90, 90, nt)) dop = pp.Doppler(ydeg, vsini=vsini, inc=90) dop.generate_data(y1=y1, R=3.0e5, nlam=149, sigma=2.0e-5, nlines=1, theta=theta, ferr=0.0) lnlam = dop.lnlam F0 = dop.F[0] F = dop.F[1:] # Render the images img = map.render(theta=theta[1:], res=100).eval() # Set up the plot
def generate_data(self, R=3e5, nlam=200, sigma=7.5e-6, nlines=20, nt=11, ferr=1.e-4, image="vogtstar.jpg"): """Generate a synthetic dataset. Args: R (float, optional): The spectral resolution. Defaults to 3e5. nlam (int, optional): Number of observed wavelength bins. Defaults to 200. sigma (float, optional): Line width in log space. Defaults to 7.5e-6, equivalent to ~0.05A at 6430A. nlines (int, optional): Number of additional small lines to include. Defaults to 20. nt (int, optional): Number of spectra. Defaults to 11. ferr (float, optional): Gaussian error to add to the fluxes. Defaults to 1.e-3 image (string, optional): Path to the image to expand in Ylms. Defaults to "vogtstar.jpg" """ # The time array in units of the period t = np.linspace(-0.5, 0.5, nt + 1)[:-1] # The log-wavelength array dlam = np.log(1.0 + 1.0 / R) lam = np.arange(-(nlam // 2), nlam // 2 + 1) * dlam # Pre-compute the Doppler basis doppler = pp.Doppler(lam, ydeg=self.ydeg, vsini=self.vsini, inc=self.inc) theta = (360.0 * t) % 360.0 D = doppler.D(theta=theta) # Now let's generate a synthetic spectrum. We do this on the # *padded* wavelength grid to avoid convolution edge effects. lam_padded = doppler.lam_padded # A deep line at the center of the wavelength range vT = 1 - 0.5 * np.exp(-0.5 * lam_padded ** 2 / sigma ** 2) # Scatter some smaller lines around for good measure for _ in range(nlines): amp = 0.1 * np.random.random() mu = 2.1 * (0.5 - np.random.random()) * lam_padded.max() vT -= amp * np.exp(-0.5 * (lam_padded - mu) ** 2 / sigma ** 2) # Now generate our "Vogtstar" map self.map.load(image) u = np.array(self.map.y.eval()) # Compute the map matrix & the flux matrix A = u.reshape(-1, 1).dot(vT.reshape(1, -1)) F = D.dot(A.reshape(-1)).reshape(nt, -1) # Let's divide out the baseline flux. This is a bummer, # since it contains really important information about # the map, but unfortunately we can't typically # measure it with a spectrograph. b = self.map.flux(theta=theta).eval() F /= b.reshape(-1, 1) # Finally, we add some noise F += ferr * np.random.randn(*F.shape) # Store the dataset self.u_true = u[1:] self.vT_true = vT self.b_true = b self.D = D self.t = t self.theta = theta self.lam_padded = lam_padded self.lam = lam self.F = F self.ferr = ferr self.M, self.K = self.F.shape self.Kp = self.D.shape[1] // self.N self._loaded = True
# -*- coding: utf-8 -*- """ Plots the basis of `g` kernels. """ import matplotlib import matplotlib.pyplot as plt import numpy as np import paparazzi as pp # Get the `kT` functions at high res ydeg = 10 dop = pp.Doppler(ydeg=ydeg) dop.generate_data(R=1e6, nlam=99, y1=np.zeros(dop.N - 1)) kT = dop.kT() # Set up the plot fig, ax = plt.subplots(ydeg + 1, 2 * ydeg + 1, figsize=(16, 10), sharex=True, sharey=True) fig.subplots_adjust(hspace=0) for axis in ax.flatten(): axis.spines["top"].set_visible(False) axis.spines["right"].set_visible(False) axis.spines["bottom"].set_visible(False) axis.spines["left"].set_visible(False) axis.set_xticks([]) axis.set_yticks([])
CLIGHT = 3.0e5 # Get the Ylm expansion of a Gaussian spot ydeg = 20 N = (ydeg + 1)**2 map = starry.Map(ydeg) map.add_spot(amp=-0.03, sigma=0.05, lat=30, lon=30) y1 = np.array(map.y.eval())[1:] # Check that the specific intensity is positive everywhere assert np.nanmin(map.render().eval()) > 0 # This work vsini = 40.0 # km/s dop = pp.Doppler(ydeg, vsini=vsini, inc=map.inc.eval()) dop.generate_data(y1=y1, R=3.0e6, nlam=1999, sigma=2.0e-5, nlines=1, theta=[0.0], ferr=0.0) F = dop.F[0] / dop.F[0][0] # The rest frame spectrum s = dop.s_true lnlam = dop.lnlam lnlam_padded = dop.lnlam_padded obs = (lnlam_padded >= lnlam[0]) & (lnlam_padded <= lnlam[-1])
# Settings for this figure ydeg = 2 ntheta = 11 inc = 40.0 vsini = 35.0 nlam = 151 u = [] # # Compute stuff! # # Compute the `D` matrix theta = np.linspace(0, 360, ntheta) theta[-1] = 0.0 dop = pp.Doppler(ydeg=ydeg, vsini=vsini, inc=inc, u=u) dop.generate_data(theta=theta, nlam=nlam) D = dop.D().todense() # Plot it fig, ax = plt.subplots(1, figsize=(11, 9.25)) fig.subplots_adjust(left=0, right=1, bottom=0, top=1) cmap = plt.get_cmap("inferno") vmax = np.nanmax(np.abs(D)) D[D == 0] = -99 cmap.set_under((0.9, 0.9, 0.9)) ax.imshow(D, cmap=cmap, vmin=-vmax, vmax=vmax) ax.axis("off") # Plot the Ylms x0 = 0.0
# -*- coding: utf-8 -*- """ Plots the basis of `g` kernels. """ import matplotlib import matplotlib.pyplot as plt import numpy as np import paparazzi as pp # Get the `kT` functions at high res ydeg = 10 dop = pp.Doppler(ydeg=ydeg, inc=60) dop.generate_data(R=1e6, nlam=99, y1=np.zeros(dop.N - 1)) kT = dop.kT() # Set up the plot fig, ax = plt.subplots(ydeg + 1, 2 * ydeg + 1, figsize=(16, 10), sharex=True, sharey=True) fig.subplots_adjust(hspace=0) for axis in ax.flatten(): axis.spines["top"].set_visible(False) axis.spines["right"].set_visible(False) axis.spines["bottom"].set_visible(False) axis.spines["left"].set_visible(False) axis.set_xticks([]) axis.set_yticks([])
""" Return ``True`` if any of ``objs`` is a ``Theano`` object. """ for obj in objs: for c in getmro(type(obj)): if c is theano.gof.graph.Node: return True return False # Initialize some stuff np.random.seed(13) ferr = 1.0e-4 res = 300 dop = pp.Doppler(ydeg=15) dop.generate_data(ferr=ferr) D = dop.D() kT = dop.kT() theta = dop.theta K = dop.K Kp = dop.Kp W = dop.W N = dop.N M = dop.M lnlam = dop.lnlam lnlam_padded = dop.lnlam_padded B1 = dop._map.X(theta=dop.theta).eval()[:, 1:] B1 = np.repeat(B1, K, axis=0) # Get the Ylm decomposition & the baseline
return F # Linear R = 3e5 nlam = 199 nt = 3 P = 1 inc = 90.0 ydeg = 5 vsini = 40.0 sigma = 7.5e-6 dlam = np.log(1.0 + 1.0 / R) lam = np.arange(-(nlam // 2), nlam // 2 + 1) * dlam t = np.linspace(-0.5 * P, 0.5 * P, nt + 1)[:-1] doppler = pp.Doppler(lam, ydeg=ydeg, vsini=vsini, inc=inc, P=P) D = doppler.D(t=t) lam_padded = doppler.lam_padded vT = np.ones_like(lam_padded) vT = 1 - 0.5 * np.exp(-0.5 * lam_padded ** 2 / sigma ** 2) map = starry.Map(ydeg) map.load("vogtstar.jpg") u = np.array(map.y.eval()) A = u.reshape(-1, 1).dot(vT.reshape(1, -1)) for i in tqdm(range(100)): F = D.dot(A.reshape(-1)).reshape(nt, -1) # Conv A_t = tt.dmatrix() F_t = theano.function(