def get_frac_dev(self, logZ, CO_ratio, custom_abundances): Rp = 7.14e7 Mp = 2.0e27 Rs = 7e8 T = 1200 depth_calculator = TransitDepthCalculator() wavelengths, transit_depths = depth_calculator.compute_depths( Rs, Mp, Rp, T, logZ=logZ, CO_ratio=CO_ratio, custom_abundances=custom_abundances, cloudtop_pressure=1e4) # This ExoTransmit run is done without SH, since it's not present in # GGchem ref_wavelengths, ref_depths = np.loadtxt("tests/testing_data/hot_jupiter_spectra.dat", unpack=True, skiprows=2) ref_depths /= 100 frac_dev = np.abs(ref_depths - transit_depths) / ref_depths '''plt.plot(wavelengths, transit_depths, label="platon") plt.plot(ref_wavelengths, ref_depths, label="ExoTransmit") plt.legend() plt.figure() plt.plot(np.log10(frac_dev)) plt.show()''' return frac_dev
def test_power_law_haze(self): Rs = R_sun Mp = M_jup Rp = R_jup T = 1200 abundances = AbundanceGetter().get(0, 0.53) for key in abundances: abundances[key] *= 0 abundances["H2"] += 1 depth_calculator = TransitDepthCalculator() wavelengths, transit_depths, info_dict = depth_calculator.compute_depths( Rs, Mp, Rp, T, logZ=None, CO_ratio=None, cloudtop_pressure=np.inf, custom_abundances = abundances, add_gas_absorption=False, add_collisional_absorption=False, full_output=True) g = G * Mp / Rp**2 H = k_B * T / (2 * AMU * g) gamma = 0.57721 polarizability = 0.8059e-30 sigma = 128. * np.pi**5/3 * polarizability**2 / depth_calculator.atm.lambda_grid**4 kappa = sigma / (2 * AMU) P_surface = 1e8 R_surface = info_dict["radii"][-1] tau_surface = P_surface/g * np.sqrt(2*np.pi*R_surface/H) * kappa analytic_R = R_surface + H*(gamma + np.log(tau_surface) + scipy.special.expn(1, tau_surface)) analytic_depths = analytic_R**2 / Rs**2 ratios = analytic_depths / transit_depths relative_diffs = np.abs(ratios - 1) self.assertTrue(np.all(relative_diffs < 0.001))
def test_unbound_atmosphere(self): Rp = 6.378e6 Mp = 5.97e20 # Note how low this is--10^-4 Earth masses! Rs = 6.97e8 T = 300 depth_calculator = TransitDepthCalculator() with self.assertRaises(AtmosphereError): wavelengths, transit_depths = depth_calculator.compute_depths( Rs, Mp, Rp, T, logZ=0.2, CO_ratio=1.1, T_star=6100)
def test_k_coeffs_binned(self): wavelengths = np.exp(np.arange(np.log(0.31e-6), np.log(29e-6), 1./20)) wavelength_bins = np.array([wavelengths[0:-1], wavelengths[1:]]).T xsec_calc = TransitDepthCalculator(method="xsec") xsec_calc.change_wavelength_bins(wavelength_bins) ktab_calc = TransitDepthCalculator(method="ktables") ktab_calc.change_wavelength_bins(wavelength_bins) wavelengths, xsec_depths = xsec_calc.compute_depths(R_sun, M_jup, R_jup, 300, logZ=1, CO_ratio=1.5) wavelengths, ktab_depths = ktab_calc.compute_depths(R_sun, M_jup, R_jup, 300, logZ=1, CO_ratio=1.5) diffs = np.abs(ktab_depths - xsec_depths) '''plt.semilogx(wavelengths, xsec_depths) plt.semilogx(wavelengths, ktab_depths) plt.figure() plt.semilogx(wavelengths, 1e6 * diffs) plt.show()''' self.assertTrue(np.median(diffs) < 10e-6) self.assertTrue(np.percentile(diffs, 95) < 20e-6) self.assertTrue(np.max(diffs) < 30e-6)
def test_k_coeffs_unbinned(self): xsec_calc = TransitDepthCalculator(method="xsec") ktab_calc = TransitDepthCalculator(method="ktables") xsec_wavelengths, xsec_depths = xsec_calc.compute_depths(R_sun, M_jup, R_jup, 1000) #Smooth from R=1000 to R=100 to match ktables N = 10 smoothed_xsec_wavelengths = uniform_filter(xsec_wavelengths, N)[::N] smoothed_xsec_depths = uniform_filter(xsec_depths, N)[::N] ktab_wavelengths, ktab_depths = ktab_calc.compute_depths(R_sun, M_jup, R_jup, 1000) diffs = np.abs(ktab_depths - smoothed_xsec_depths[:-1]) self.assertTrue(np.median(diffs) < 20e-6) self.assertTrue(np.percentile(diffs, 95) < 50e-6) self.assertTrue(np.max(diffs) < 150e-6)
def test_bin_wavelengths(self): Rp = 7.14e7 Mp = 7.49e26 Rs = 7e8 T = 1200 depth_calculator = TransitDepthCalculator() bins = np.array([[0.4,0.6], [1,1.1], [1.2,1.4], [3.2,4], [5,6]]) bins *= 1e-6 depth_calculator.change_wavelength_bins(bins) wavelengths, transit_depths = depth_calculator.compute_depths( Rs, Mp, Rp, T, logZ=0.2, CO_ratio=1.1, T_star=6100) self.assertEqual(len(wavelengths), len(bins)) self.assertEqual(len(transit_depths), len(bins)) wavelengths, transit_depths = depth_calculator.compute_depths( Rs, Mp, Rp, T, logZ=0.2, CO_ratio=1.1, T_star=12000) self.assertEqual(len(wavelengths), len(bins)) self.assertEqual(len(transit_depths), len(bins))
def __init__(self, *args, **kwargs): super(TestMieAbsorption, self).__init__(*args, **kwargs) # We're storing this object to take advantage of its cache, not # for speed, but to test the cache self.calc = TransitDepthCalculator()
def test_bounds_checking(self): Rp = 7.14e7 Mp = 7.49e26 Rs = 7e8 T = 1200 logZ = 0 CO_ratio = 1.1 calculator = TransitDepthCalculator() with self.assertRaises(AtmosphereError): calculator.compute_depths(Rs, Mp, Rp, 199, logZ=logZ, CO_ratio=CO_ratio) with self.assertRaises(AtmosphereError): calculator.compute_depths(Rs, Mp, Rp, 3001, logZ=logZ, CO_ratio=CO_ratio) with self.assertRaises(ValueError): calculator.compute_depths(Rs, Mp, Rp, T, logZ=-1.1, CO_ratio=CO_ratio) with self.assertRaises(ValueError): calculator.compute_depths(Rs, Mp, Rp, T, logZ=3.1, CO_ratio=CO_ratio) with self.assertRaises(ValueError): calculator.compute_depths(Rs, Mp, Rp, T, logZ=logZ, CO_ratio=0.01) with self.assertRaises(ValueError): calculator.compute_depths(Rs, Mp, Rp, T, logZ=logZ, CO_ratio=11) with self.assertRaises(ValueError): calculator.compute_depths(Rs, Mp, Rp, T, logZ=logZ, CO_ratio=CO_ratio, cloudtop_pressure=1e-4) with self.assertRaises(ValueError): calculator.compute_depths(Rs, Mp, Rp, T, logZ=logZ, CO_ratio=CO_ratio, cloudtop_pressure=1.1e8) # Infinity should be fine calculator.compute_depths(Rs, Mp, Rp, T, logZ=logZ, CO_ratio=CO_ratio, cloudtop_pressure=np.inf)
def __init__(self): ''' The SpectrumGenerator generates spectra at different resolutions and with noises ''' self.calculator = TransitDepthCalculator()
return np.array(wavelength_bins) def spitzer_bins(): wave_bins = [] wave_bins.append([3.2, 4.0]) wave_bins.append([4.0, 5.0]) return 1e-6 * np.array(wave_bins) bins = np.concatenate([stis_bins(), wfc3_bins(), spitzer_bins()]) R_guess = 1.4 * R_jup T_guess = 1200 depth_calculator = TransitDepthCalculator() depth_calculator.change_wavelength_bins(bins) wavelengths, depths = depth_calculator.compute_depths(1.19 * R_sun, 0.73 * M_jup, R_guess, T_guess, T_star=6091) # Uncomment the code below to print #print("#Wavelength(um) Depth") #for i in range(len(wavelengths)): # print(wavelengths[i], depths[i]) # Uncomment the code below to plot
import matplotlib.pyplot as plt import corner from platon.fit_info import FitInfo from platon.transit_depth_calculator import TransitDepthCalculator from platon.retriever import Retriever Rs = 7e8 g = 9.8 Rp = 7.14e7 logZ = 0 CO = 0.53 log_cloudtop_P = 3 temperature = 1200 depth_calculator = TransitDepthCalculator(Rs, g) wavelength_bins = [] stis_wavelengths = np.linspace(0.4e-6, 0.7e-6, 30) for i in range(len(stis_wavelengths) - 1): wavelength_bins.append([stis_wavelengths[i], stis_wavelengths[i + 1]]) wfc_wavelengths = np.linspace(1.1e-6, 1.7e-6, 30) for i in range(len(wfc_wavelengths) - 1): wavelength_bins.append([wfc_wavelengths[i], wfc_wavelengths[i + 1]]) wavelength_bins.append([3.2e-6, 4e-6]) wavelength_bins.append([4e-6, 5e-6]) depth_calculator.change_wavelength_bins(wavelength_bins) wavelengths, transit_depths = depth_calculator.compute_depths(
def run_multinest(self, transit_bins, transit_depths, transit_errors, eclipse_bins, eclipse_depths, eclipse_errors, fit_info, include_condensation=True, plot_best=False, **nestle_kwargs): '''Runs nested sampling to retrieve atmospheric parameters. Parameters ---------- transit_bins : array_like, shape (N,2) Wavelength bins, where wavelength_bins[i][0] is the start wavelength and wavelength_bins[i][1] is the end wavelength for bin i. transit_depths : array_like, length N Measured transit depths for the specified wavelength bins transit_errors : array_like, length N Errors on the aforementioned transit depths eclipse_bins : array_like, shape (N,2) Wavelength bins, where wavelength_bins[i][0] is the start wavelength and wavelength_bins[i][1] is the end wavelength for bin i. eclipse_depths : array_like, length N Measured eclipse depths for the specified wavelength bins eclipse_errors : array_like, length N Errors on the aforementioned eclipse depths fit_info : :class:`.FitInfo` object Tells us what parameters to freely vary, and in what range those parameters can vary. Also sets default values for the fixed parameters. include_condensation : bool, optional When determining atmospheric abundances, whether to include condensation. plot_best : bool, optional If True, plots the best fit model with the data **nestle_kwargs : keyword arguments to pass to nestle's sample method Returns ------- result : Result object This returns the object returned by nestle.sample, slightly modified. The object is dictionary-like and has many useful items. For example, result.samples (or alternatively, result["samples"]) are the parameter values of each sample, result.weights contains the weights, result.logl contains the ln likelihoods, and result.logp contains the ln posteriors (this is added by PLATON). result.logz is the natural logarithm of the evidence. ''' transit_calc = TransitDepthCalculator( include_condensation=include_condensation) transit_calc.change_wavelength_bins(transit_bins) eclipse_calc = EclipseDepthCalculator() eclipse_calc.change_wavelength_bins(eclipse_bins) self._validate_params(fit_info, transit_calc) def transform_prior(cube): new_cube = np.zeros(len(cube)) for i in range(len(cube)): new_cube[i] = fit_info._from_unit_interval(i, cube[i]) return new_cube def multinest_ln_like(cube): return self._ln_like(cube, transit_calc, eclipse_calc, fit_info, transit_depths, transit_errors, eclipse_depths, eclipse_errors) def callback(callback_info): print("Iteration {}: {}".format(callback_info["it"], self.pretty_print(fit_info))) result = nestle.sample(multinest_ln_like, transform_prior, fit_info._get_num_fit_params(), callback=callback, method='multi', **nestle_kwargs) result.logp = result.logl + np.array( [fit_info._ln_prior(params) for params in result.samples]) best_params_arr = result.samples[np.argmax(result.logp)] write_param_estimates_file( nestle.resample_equal(result.samples, result.weights), best_params_arr, np.max(result.logp), fit_info.fit_param_names) if plot_best: self._ln_prob(best_params_arr, transit_calc, eclipse_calc, fit_info, transit_depths, transit_errors, eclipse_depths, eclipse_errors, plot=True) return result
def run_emcee(self, transit_bins, transit_depths, transit_errors, eclipse_bins, eclipse_depths, eclipse_errors, fit_info, nwalkers=50, nsteps=1000, include_condensation=True, plot_best=False): '''Runs affine-invariant MCMC to retrieve atmospheric parameters. Parameters ---------- transit_bins : array_like, shape (N,2) Wavelength bins, where wavelength_bins[i][0] is the start wavelength and wavelength_bins[i][1] is the end wavelength for bin i. transit_depths : array_like, length N Measured transit depths for the specified wavelength bins transit_errors : array_like, length N Errors on the aforementioned transit depths eclipse_bins : array_like, shape (N,2) Wavelength bins, where wavelength_bins[i][0] is the start wavelength and wavelength_bins[i][1] is the end wavelength for bin i. eclipse_depths : array_like, length N Measured eclipse depths for the specified wavelength bins eclipse_errors : array_like, length N Errors on the aforementioned eclipse depths fit_info : :class:`.FitInfo` object Tells the method what parameters to freely vary, and in what range those parameters can vary. Also sets default values for the fixed parameters. nwalkers : int, optional Number of walkers to use nsteps : int, optional Number of steps that the walkers should walk for include_condensation : bool, optional When determining atmospheric abundances, whether to include condensation. plot_best : bool, optional If True, plots the best fit model with the data Returns ------- result : EnsembleSampler object This returns emcee's EnsembleSampler object. The most useful attributes in this item are result.chain, which is a (W x S X P) array where W is the number of walkers, S is the number of steps, and P is the number of parameters; and result.lnprobability, a (W x S) array of log probabilities. For your convenience, this object also contains result.flatchain, which is a (WS x P) array where WS = W x S is the number of samples; and result.flatlnprobability, an array of length WS ''' initial_positions = fit_info._generate_rand_param_arrays(nwalkers) transit_calc = TransitDepthCalculator( include_condensation=include_condensation) transit_calc.change_wavelength_bins(transit_bins) eclipse_calc = EclipseDepthCalculator() eclipse_calc.change_wavelength_bins(eclipse_bins) self._validate_params(fit_info, transit_calc) sampler = emcee.EnsembleSampler( nwalkers, fit_info._get_num_fit_params(), self._ln_prob, args=(transit_calc, eclipse_calc, fit_info, transit_depths, transit_errors, eclipse_depths, eclipse_errors)) for i, result in enumerate( sampler.sample(initial_positions, iterations=nsteps)): if (i + 1) % 10 == 0: print("Step {}: {}".format(i + 1, self.pretty_print(fit_info))) best_params_arr = sampler.flatchain[np.argmax( sampler.flatlnprobability)] write_param_estimates_file(sampler.flatchain, best_params_arr, np.max(sampler.flatlnprobability), fit_info.fit_param_names) if plot_best: self._ln_prob(best_params_arr, transit_calc, eclipse_calc, fit_info, transit_depths, transit_errors, eclipse_depths, eclipse_errors, plot=True) return sampler
from platon.constants import M_jup, R_sun, R_jup, M_earth, R_earth from platon.visualizer import Visualizer # All quantities in SI Rs = 0.947 * R_sun Mp = 8.145 * M_earth Rp = 1.7823 * R_earth T = 1970 logZ = 1.09 CO_ratio = 1.57 log_cloudtop_P = 4.875 #create a TransitDepthCalculator object and compute wavelength dependent transit depths depth_calculator = TransitDepthCalculator() wavelengths, transit_depths, info = depth_calculator.compute_depths( Rs, Mp, Rp, T, T_star=5196, logZ=logZ, CO_ratio=CO_ratio, cloudtop_pressure=10.0**log_cloudtop_P, full_output=True) color_bins = 1e-6 * np.array([ [4, 5], [3.2, 4], [1.1, 1.7],
from __future__ import print_function import numpy as np import matplotlib.pyplot as plt from platon.transit_depth_calculator import TransitDepthCalculator from platon.constants import M_jup, R_sun, R_jup # All quantities in SI Rs = 1.16 * R_sun #Radius of star Mp = 0.73 * M_jup #Mass of planet Rp = 1.40 * R_jup #Radius of planet T = 1200 #Temperature of isothermal part of the atmosphere #create a TransitDepthCalculator object and compute wavelength dependent transit depths depth_calculator = TransitDepthCalculator() wavelengths, transit_depths = depth_calculator.compute_depths( Rs, Mp, Rp, T, CO_ratio=0.2, cloudtop_pressure=1e4) # Uncomment the code below to print #print("#Wavelength(m) Depth") #for i in range(len(wavelengths)): # print(wavelengths[i], transit_depths[i]) # Uncomment the code below to plot #plt.plot(1e6*wavelengths, transit_depths) #plt.xlabel("Wavelength (um)") #plt.ylabel("Transit depth") #plt.show()
import numpy as np import matplotlib.pyplot as plt from platon.transit_depth_calculator import TransitDepthCalculator from platon.constants import M_jup, R_sun, R_jup # All quantities in SI Rs = 1.16 * R_sun #Radius of star Mp = 0.73 * M_jup #Mass of planet Rp = 1.40 * R_jup #Radius of planet T = 1200 #Temperature of isothermal part of the atmosphere #create a TransitDepthCalculator object and compute wavelength dependent transit depths depth_calculator = TransitDepthCalculator( method="ktables") #put "xsec" for opacity sampling wavelengths, transit_depths = depth_calculator.compute_depths( Rs, Mp, Rp, T, CO_ratio=0.2, cloudtop_pressure=1e4) # Uncomment the code below to print #print("#Wavelength(m) Depth") #for i in range(len(wavelengths)): # print(wavelengths[i], transit_depths[i]) # Uncomment the code below to plot #plt.semilogx(1e6*wavelengths, transit_depths) #plt.xlabel("Wavelength (um)") #plt.ylabel("Transit depth") #plt.show()
time_stamp = datetime.utcnow().strftime("%Y%m%d%H%M%S") result_dict = {'samples':result.samples, 'weights':result.weights, 'logl':result.logl} joblib.dump(result_dict, 'multinest_results_{}.joblib.save'.format(time_stamp)) # Establish the Range in Wavelength to plot high resolution figures wave_min = wave_bins.min() wave_max = wave_bins.max() n_theory_pts = 500 wavelengths_theory = np.linspace(wave_min, wave_max, n_theory_pts) half_diff_lam = 0.5*np.median(np.diff(wavelengths_theory)) # Setup calculator to use the theoretical wavelengths calculator = TransitDepthCalculator(include_condensation=True) calculator.change_wavelength_bins(np.transpose([wavelengths_theory-half_diff_lam, wavelengths_theory+half_diff_lam])) retriever._validate_params(fit_info, calculator) # Allocate the best-fit parameters from the `result` class best_params_arr = result.samples[np.argmax(result.logl)] best_params_dict = {key:val for key,val in zip(fit_info.fit_param_names, best_params_arr)} # Set the static parameteres to the default values for key in fit_info.all_params.keys(): if key not in best_params_dict.keys(): best_params_dict[key] = fit_info.all_params[key].best_guess # Assign the best fit model parameters to necessary variables Rs = best_params_dict['Rs']
class SpectrumGenerator: def __init__(self): ''' The SpectrumGenerator generates spectra at different resolutions and with noises ''' self.calculator = TransitDepthCalculator() def generate_spectrum(self, Rs, Mp, Rp, T_planet, T_star, Ms, logZ=0, CO_ratio=0.53, scattering_factor=1, cloudtop_pressure=np.inf, resolution=None, noise_level=None, max_wavelength=3e-5, min_wavelength=3e-7, albedo=0.8): ''' Generates a spectrum using the PLATON TransitDepthCalculator. Parameters ---------- Rs : float Stellar radius in solar radii Mp : float Planet mass in Jupiter masses Rp : float Planet radius in Jupiter radii T_planet : float Temperature of the isothermal atmosphere in Kelvin T_star : float Blackbody temperature of the star Ms : float Mass of the star in solar masses logZ : float, optional base-10 logarithm of the metallicity in solar units. Default is 0. CO_ratio : float, optional C/O atomic ratio in the atmosphere. The default value is 0.53 (solar) value. scattering_factor : float, optional Makes rayleigh scattering this many times as strong. Default is 1 cloudtop_pressure : float, optional Pressure level (in PA) below which light cannot penetrate. Use np.inf for cloudless. Default is np.inf resolution : int or None, optional If provided, will produce spectra of the given spectral resolution. Default is None noise_level : float or None, optional The noise on the data in ppm. If provided, Gaussian noise will be added to the spectrum. Default is None. max_wavelength : float, optional The maximum wavelength to consider in m. Default is 3e-5 min_wavelength : float, optional The minimum wavelength to consider in m. Default is 3e-7 Returns ------- spectrum : TransitCurveGen.Spectrum A Spectrum object containing all the information on the spectrum ''' # Generate the basic spectrum wavelengths, depths = self.calculator.compute_depths( Rs * R_sun, Mp * M_jup, Rp * R_jup, T_planet, logZ=logZ, CO_ratio=CO_ratio, scattering_factor=scattering_factor, cloudtop_pressure=cloudtop_pressure) # Remove wavelengths not of interest # First cut off the large wavelengths from depths and wavelengths. # MUST be done in this order else depths can't reference wavelengths depths = depths[wavelengths <= max_wavelength] wavelengths = wavelengths[wavelengths <= max_wavelength] depths = depths[wavelengths >= min_wavelength] wavelengths = wavelengths[wavelengths >= min_wavelength] # Convolve to give a particular resolution if resolution is not None: wavelengths, depths = self._bin_spectrum(wavelengths, depths, resolution) # Add in noise if required if noise_level is not None: wavelengths, depths = self._add_noise(wavelengths, depths, noise_level) info_dict = { 'Rs': Rs, 'Mp': Mp, 'Rp': Rp, 'T_planet': T_planet, 'logZ': logZ, 'CO_ratio': CO_ratio, 'scattering_factor': scattering_factor, 'cloudtop_pressure': cloudtop_pressure, 'resolution': resolution, 'noise': noise_level, 'T_star': T_star, 'Ms': Ms, 'albedo': albedo } return Spectrum(wavelengths, depths, info_dict) def _bin_spectrum(self, wavelengths, depths, R): ''' Bins a given spectrum to a spectral resolution R Parameters ---------- wavelengths : array_like, shape (n_wavelengths,) The wavelengths of each data point. We assume that these are logarithmically spaced. depths : array_like, shape (n_wavelengths,) The transit depth of each data point. R : int Spectral resolution to bin to. Returns ------- wavelengths : np.array The centre wavelength of each new wavelength bin depths : np.array The transit depth in each of the new wavelength bins Notes ----- The binning is done using convolution with a top-hat. ''' # Take log base-10 of the wavelengths to work on resolution log_wl = np.log10(wavelengths) # Find the wavelength spacing in log space delta = round(log_wl[1] - log_wl[0], 7) # Find the original resolution of the spectrum R_original = 1 // delta if R > R_original: raise ValueError( 'Spectrum has resolution {}: cannot increase resolution to {}'. format(R_original, R)) # Work out how many current wl bins we need for the given resolution nbins = int(1 // (R * delta)) # Do the binning wavelengths = wavelengths[:(wavelengths.size // nbins) * nbins].reshape(-1, nbins).mean(axis=1) depths = depths[:(depths.size // nbins) * nbins].reshape( -1, nbins).mean(axis=1) return wavelengths, depths def _add_noise(self, wavelengths, depths, noise_ppm): ''' Adds Gaussian noise to a given spectrum Parameters ---------- wavelengths : array_like, shape (n_wavelengths,) The wavelengths of each data point. We assume that these are logarithmically spaced. depths : array_like, shape (n_wavelengths,) The transit depth of each data point. noise_ppm : int The noise in parts per million Returns ------- wavelengths : np.array The wavelengths depths : np.array The depths with Gaussian noise added. ''' disp_arr = np.zeros(len(depths)) for i, d in enumerate(depths): disp_arr[i] = np.random.normal(0, noise_ppm * 1e-6) depths += disp_arr return wavelengths, depths