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))
示例#7
0
    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
示例#11
0
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
示例#14
0
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],
示例#15
0
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()
示例#16
0
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()
示例#17
0
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