Exemple #1
0
def get_thruput(inst):
    """Returns complete instrument photon to electron conversion efficiency
    Pulls complete instrument photon to electron conversion efficiency 
    (PCE) based on instrument key input 
    
    Parameters
    ----------
    inst : str
        One of the instrument keys in `print_instruments`
    
    Returns
    ------- 
    dict 
        Dictionary with wave solution and PCE
    
    Example
    -------
    >>> thru_dict = get_thruput('NIRISS SOSS_Or1')
    """
    
    #pull correct dictionary
    input_dict =  SetDefaultModes(inst).pick()
                             
    conf = {'instrument': input_dict['configuration']['instrument']}
    i = InstrumentFactory(config=conf)
    wr = i.get_wave_range()
    wave = np.linspace(wr['wmin'], wr['wmax'], num=500)
    pce = i.get_total_eff(wave)
    return {'wave':wave,'pce':pce}
Exemple #2
0
def get_thruput(inst):
    """Returns complete instrument photon to electron conversion efficiency
    Pulls complete instrument photon to electron conversion efficiency 
    (PCE) based on instrument key input 
    
    Parameters
    ----------
    inst : str
        One of the instrument keys in `print_instruments`
    
    Returns
    ------- 
    dict 
        Dictionary with wave solution and PCE
    
    Example
    -------
    >>> thru_dict = get_thruput('NIRISS SOSS_Or1')
    """

    #pull correct dictionary
    input_dict = SetDefaultModes(inst).pick()

    conf = {'instrument': input_dict['configuration']['instrument']}
    i = InstrumentFactory(config=conf)
    wr = i.get_wave_range()
    wave = np.linspace(wr['wmin'], wr['wmax'], num=500)
    pce = i.get_total_eff(wave)
    return {'wave': wave, 'pce': pce}
Exemple #3
0
    def pandeia_instrument(self):
        if hasattr(self, "_instrument"):
            return self._instrument
        from pandeia.engine.calc_utils import build_default_calc
        from pandeia.engine.instrument_factory import InstrumentFactory

        translate_instrument = {
            'wfi': 'wfirstimager',
            'nircamlong': 'nircam',
            'nircamshort': 'nircam',
            'miri': 'miri'
        }

        conf = build_default_calc(
            self.TELESCOPE.lower(),
            translate_instrument.get(self.INSTRUMENT.lower(),
                                     self.INSTRUMENT.lower()),
            self.MODE)['configuration']
        conf['instrument']['filter'] = self.filter.lower()

        self.logger.info("Creating Instrument with Configuration {}".format(
            conf['instrument']))

        self._instrument = InstrumentFactory(config=conf)
        return self._instrument
Exemple #4
0
def get_pce(instrument='niriss',
            mode='soss',
            filter='clear',
            disperser='gr700xd',
            aperture='soss',
            detector=None):
    """
    Use pandeia to generate the JWST throughputs

    Parameters
    ----------
    instrument: str
        The JWST instrument to use, ['niriss', 'nirspec', 'miri', 'nircam']
    mode: str
        The observing mode to use, ['soss', 'wfss', 'ami', etc.]
    filter: str
        The filter wheel element to use
    disperser: str
        The dispersion element to use
    aperture: str
        The aperture to use

    Returns
    -------
    wave, pce
        The wavelength and throughput
    """
    # Get optical element configuration
    obsmode = {
        'instrument': instrument,
        'mode': mode,
        'filter': filter,
        'aperture': aperture,
        'disperser': disperser
    }
    conf = {'instrument': obsmode, 'detector': detector}
    i = InstrumentFactory(config=conf)

    # Determine wavelength range
    wr = i.get_wave_range()
    wave = np.linspace(wr['wmin'], wr['wmax'], num=500)

    # Evaluate the throughput
    pce = i.get_total_eff(wave)

    return wave, pce
Exemple #5
0
def get_pce(instrument, mode, config):

    obsmode = {
        'instrument': instrument,
        'mode': mode,
        'filter': config['filter'],
        'aperture': config['aperture'],
        'disperser': config['disperser']
    }

    conf = {'instrument': obsmode}

    i = InstrumentFactory(config=conf)
    wr = i.get_wave_range()
    wave = np.linspace(wr['wmin'], wr['wmax'], num=500)
    pce = i.get_total_eff(wave)

    return wave, pce
Exemple #6
0
def get_thruput(inst, niriss=1, nirspec='f100lp'):
    """Returns complete instrument photon to electron conversion efficiency
    Pulls complete instrument photon to electron conversion efficiency 
    (PCE) based on instrument key input 
    
    Parameters
    ----------
    inst : str
        One of the instrument keys in `print_instruments`
    niriss : int
        (Optional) defines which niriss order you want (1 or 2)
    nirspec : str
        (Optional) for NIRISS G140M/H there are two available filters (f100lp and f070lp)
        if you are selecting G140M or G140H, this allows you to pick which one
    Returns
    ------- 
    dict 
        Dictionary with wave solution and PCE
    
    Example
    -------
    >>> thru_dict = get_thruput('NIRISS SOSS_Or1')
    """

    #pull correct dictionary
    input_dict = SetDefaultModes(inst).pick()
    conf = input_dict['configuration']
    conf['detector']['ngroup'] = 2

    if conf['instrument']['instrument'].lower() == 'niriss':
        #pandeia handles slit losses inside the 2d engine. So, you need to account for the
        #extra .663 here
        conf["instrument"][
            "disperser"] = conf["instrument"]["disperser"] + '_' + str(niriss)
        i = InstrumentFactory(config=conf)
        wr = i.get_wave_range()
        wave = np.linspace(wr['wmin'], wr['wmax'], num=500)
        pce = i.get_total_eff(wave)
        return {'wave': wave, 'pce': 0.663 * pce}
    elif (conf['instrument']['instrument'].lower()
          == 'nirspec') and ('g140' in conf["instrument"]["disperser"]):
        conf["instrument"]["filter"] = nirspec

    i = InstrumentFactory(config=conf)
    wr = i.get_wave_range()
    wave = np.linspace(wr['wmin'], wr['wmax'], num=500)
    pce = i.get_total_eff(wave)
    return {'wave': wave, 'pce': pce}
        else:
            file_list.append("none")

    # Create the plot title
    title = " ".join(title_list)

    # Create the plot output filename
    filename = './tp__' + "__".join(file_list).replace(' ', '-').replace(
        '+', 'p').lower() + ".png"

    if os.path.isfile(filename):
        print("%s already exists. skipping..." % filename)
        continue

    # make the instrument instance, set up the wavelength vector, and get out the total efficiency.
    instrument_factory = InstrumentFactory(config=conf)

    print(iconf['instrument'], iconf['mode'], iconf['aperture'])
    try:
        wave_range = instrument_factory.get_wave_range()
    except:
        print('***  Exception with get_wave_range  ***')
        pass

    wave = np.linspace(wave_range['wmin'], wave_range['wmax'], num=500)
    eff = instrument_factory.get_total_eff(wave)

    # make the plot, save it to a file, and close things up when done.
    f = plt.figure(facecolor='white')
    ax = plt.gca()
    ax.tick_params(axis='both', which='major', labelsize='14', colors='black')
Exemple #8
0
def compute_full_sim(dictinput): 
    """Top level function to set up exoplanet obs. for JW
    
    Function to set up explanet observations for JWST only and 
    compute simulated spectrum. It uses STScI's Pandeia to compute 
    instrument throughputs and WebbPSF to compute PSFs. 
    
    Parameters
    ----------
    dictinput : dict
        dictionary containing instrument parameters and exoplanet specific 
        parameters. {"pandeia_input":dict1, "pandexo_input":dict1}
    
    Returns
    -------
    dict
        large dictionary with 1d, 2d simualtions, timing info, instrument info, warnings
    
    Examples
    --------  
      
    >>> from .pandexo.engine.jwst import compute_full_sim 
    >>> from .pandexo.engine.justplotit import jwst_1d_spec
    >>> a = compute_full_sim({"pandeia_input": pandeiadict, "pandexo_input":exodict})
    >>> jwst_1d_spec(a)
    .. image:: 1d_spec.png
    
    Notes
    -----
    It is much easier to run simulations through either **run_online** or **justdoit**. **justdoit** contains functions to create input dictionaries and **run_online** contains web forms to create input dictionaries.
    
    See Also
    -------- 
        pandexo.engine.justdoit.run_pandexo : Best function for running pandexo runs
        pandexo.engine.run_online : Allows running functions through online interface
    """
    pandeia_input = dictinput['pandeia_input']
    pandexo_input = dictinput['pandexo_input']    	
	
    #define the calculation we'll be doing 
    if pandexo_input['planet']['w_unit'] == 'sec':
        calculation = 'phase_spec'
    else: 
        calculation = pandexo_input['calculation'].lower()

    #which instrument 
    instrument = pandeia_input['configuration']['instrument']['instrument']
    conf = pandeia_input['configuration']
    
    #if optimize is in the ngroups section, this will throw an error 
    #so create temp conf with 2 groups 
    try:
        i = InstrumentFactory(config=conf)
        det_pars = i.get_detector_pars()
        exp_pars = i.get_exposure_pars()
    except: 
        conf_temp = deepcopy(conf) 
        conf_temp['detector']['ngroup'] = 2 
        i = InstrumentFactory(config=conf_temp)
        det_pars = i.get_detector_pars()
        exp_pars = i.get_exposure_pars()
        
    #detector parameters
    fullwell = det_pars['fullwell'] #from pandeia data
    rn = det_pars['rn']
    sat_unit = pandexo_input['observation']['sat_unit']

    if sat_unit =='%':
        sat_level = pandexo_input['observation']['sat_level']/100.0*fullwell
    elif sat_unit =='e':
        sat_level = pandexo_input['observation']['sat_level']
    else: 
        raise Exception("Saturation Level Needs Units: % fullwell or Electrons ")

    mingroups = det_pars['mingroups']
    
    #exposure parameters 
    tframe = exp_pars.tframe
    nframe = exp_pars.nframe
    nskip = exp_pars.nskip
    
    #parameteres needed from exo_input
    mag = pandexo_input['star']['mag']
    
    
    noccultations = pandexo_input['observation']['noccultations']
    R = pandexo_input['observation']['R']

    noise_floor = pandexo_input['observation']['noise_floor']

    
    #get stellar spectrum and in transit spec
    star_spec = create.outTrans(pandexo_input['star'])
    #get rstar if user calling from grid 
    both_spec = create.bothTrans(star_spec, pandexo_input['planet'], star=pandexo_input['star'])
    out_spectrum = np.array([both_spec['wave'], both_spec['flux_out_trans']])
    
    #get transit duration from phase curve or from input 
    if calculation == 'phase_spec': 
        transit_duration = max(both_spec['time']) - min(both_spec['time'])
    else: 
        #convert to seconds, then remove quantity and convert back to float 
        transit_duration = float((pandexo_input['planet']['transit_duration']*u.Unit(pandexo_input['planet']['td_unit'])).to(u.second)/u.second)

    #amount of exposure time out-of-occultation, as a fraction of in-occ time 
    try:
        expfact_out = pandexo_input['observation']['fraction'] 
        print("WARNING: key input fraction has been replaced with new 'baseline option'. See notebook example")
        pandexo_input['observation']['baseline'] = pandexo_input['observation']['fraction'] 
        pandexo_input['observation']['baseline_unit'] ='frac'
    except:
        if pandexo_input['observation']['baseline_unit'] =='frac':
            expfact_out = pandexo_input['observation']['baseline'] 
        elif pandexo_input['observation']['baseline_unit'] =='total':
            expfact_out = transit_duration/( pandexo_input['observation']['baseline'] - transit_duration)
        elif pandexo_input['observation']['baseline_unit'] =='total_hrs':
            expfact_out = transit_duration/( pandexo_input['observation']['baseline']*3600.0 - transit_duration)
        else: 
            raise Exception("Wrong units for baseine: either 'frac' or 'total' or 'total_hrs' accepted")

    #add to pandeia input 
    pandeia_input['scene'][0]['spectrum']['sed']['spectrum'] = out_spectrum
    
    if isinstance(pandeia_input["configuration"]["detector"]["ngroup"], (float,int)):
        m = {"ngroup":pandeia_input["configuration"]["detector"]["ngroup"], "tframe":tframe,
            "nframe":nframe,"mingroups":mingroups,"nskip":nskip}
    else:
        #run pandeia once to determine max exposure time per int and get exposure params
        print("Optimization Reqested: Computing Duty Cycle")
        m = {"maxexptime_per_int":compute_maxexptime_per_int(pandeia_input, sat_level) , 
            "tframe":tframe,"nframe":nframe,"mingroups":mingroups,"nskip":nskip}
        print("Finished Duty Cycle Calc")

    #calculate all timing info
    timing, flags = compute_timing(m,transit_duration,expfact_out,noccultations)
    
    #Simulate out trans and in transit
    print("Starting Out of Transit Simulation")
    out = perform_out(pandeia_input, pandexo_input,timing, both_spec)
    
    #extract extraction area before dict conversion
    extraction_area = out.extraction_area
    out = out.as_dict()
    out.pop('3d')
    print("End out of Transit")

    #Remove effects of Quantum Yield from shot noise 
    out = remove_QY(out, instrument)

    #this kind of redundant going to compute inn from out instead 
    #keep perform_in but change inputs to (out, timing, both_spec)
    print("Starting In Transit Simulation")
    inn = perform_in(pandeia_input, pandexo_input,timing, both_spec, out, calculation)
    print("End In Transit")
    


    #compute warning flags for timing info 
    warnings = add_warnings(out, timing, sat_level/fullwell, flags, instrument) 

    compNoise = ExtractSpec(inn, out, rn, extraction_area, timing)
    
    #slope method is pandeia's pure noise calculation (taken from SNR)
    #contains correlated noise, RN, dark current, sky, 
    #uses MULTIACCUM formula so we deviated from this. 
    #could eventually come back to this if Pandeia adopts First-Last formula
    if calculation == 'slope method': 
        #Extract relevant info from pandeia output (1d curves and wavelength) 
        #extracted flux in units of electron/s
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_slope_method()

    #derives noise from 2d postage stamps. Doing this results in a higher 
    #1d flux rate than the Pandeia gets from extracting its own. 
    #this should be used to benchmark Pandeia's 1d extraction  
    elif calculation == '2d extract':
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_2d_extract()
    
    #this is the noise calculation that PandExo uses online. It derives 
    #its own calculation of readnoise and does not use MULTIACUMM 
    #noise formula  
    elif calculation == 'fml':
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_f_minus_l()
    
    elif calculation == 'phase_spec':
        result = compNoise.run_phase_spec()
        w = result['time']
    else:
        result = None
        raise Exception('WARNING: Calculation method not found.')
        
    varin = result['var_in_1d']
    varout = result['var_out_1d']
    extracted_flux_out = result['photon_out_1d']
    extracted_flux_inn = result['photon_in_1d']

        
    #bin the data according to user input 
    if R != None: 
        wbin = bin_wave_to_R(w, R)
        photon_out_bin = uniform_tophat_sum(wbin, w,extracted_flux_out)
        wbin = wbin[photon_out_bin > 0 ]
        photon_in_bin = uniform_tophat_sum(wbin,w, extracted_flux_inn)
        photon_in_bin = photon_in_bin[photon_out_bin > 0 ]
        var_in_bin = uniform_tophat_sum(wbin, w,varin)
        var_in_bin = var_in_bin[photon_out_bin > 0 ]
        var_out_bin = uniform_tophat_sum(wbin,w, varout)
        var_out_bin = var_out_bin[photon_out_bin > 0 ]
        photon_out_bin = photon_out_bin
    else: 
        wbin = w
        photon_out_bin = extracted_flux_out
        wbin = wbin[photon_out_bin>0]
        photon_in_bin = extracted_flux_inn
        photon_in_bin = photon_in_bin[photon_out_bin>0]
        var_in_bin = varin
        var_in_bin = var_in_bin[photon_out_bin>0]
        var_out_bin = varout
        var_out_bin = var_out_bin[photon_out_bin>0]
        photon_out_bin = photon_out_bin[photon_out_bin>0]
        
    
    if calculation == 'phase_spec':
        to = (timing["APT: Num Groups per Integration"]-1)*tframe
        ti = (timing["APT: Num Groups per Integration"]-1)*tframe
    else: 
        #otherwise error propagation and account for different 
        #times in transit and out 
        to = result['on_source_out']
        ti = result['on_source_in']
        
    var_tot = (to/ti/photon_out_bin)**2.0 * var_in_bin + (photon_in_bin*to/ti/photon_out_bin**2.0)**2.0 * var_out_bin
    error_spec = np.sqrt(var_tot)
        
    #factor in occultations to noise 
    nocc = timing['Number of Transits']
    error_spec = error_spec / np.sqrt(nocc)
        
    #Add in user specified noise floor 
    error_spec_nfloor = add_noise_floor(noise_floor, wbin, error_spec) 

    #add in random noise for the simulated spectrum 
    np.random.seed()
    rand_noise= error_spec_nfloor*(np.random.randn(len(wbin)))
    raw_spec = (photon_out_bin/to-photon_in_bin/ti)/(photon_out_bin/to)
    sim_spec = raw_spec + rand_noise 
    
    #if secondary tranist, multiply spectra by -1 
    if pandexo_input['planet']['f_unit'] == 'fp/f*':
        sim_spec = -1.0*sim_spec
        raw_spec = -1.0*raw_spec    
   
    #package processed data
    finalspec = {'wave':wbin,
              'spectrum': raw_spec,
              'spectrum_w_rand':sim_spec,
              'error_w_floor':error_spec_nfloor}
    
    rawstuff = {
                'electrons_out':photon_out_bin*nocc, 
                'electrons_in':photon_in_bin*nocc,
                'var_in':var_in_bin*nocc, 
                'var_out':var_out_bin*nocc,
                'e_rate_out':photon_out_bin/to,
                'e_rate_in':photon_out_bin/ti,
                'wave':wbin,
                'error_no_floor':error_spec, 
                'rn[out,in]':result['rn[out,in]'],
                'bkg[out,in]':result['bkg[out,in]']
                }
 
    result_dict = as_dict(out,both_spec ,finalspec, 
                timing, mag, sat_level, warnings,
                pandexo_input['planet']['f_unit'], rawstuff,calculation)

    return result_dict 
            title_list.append(val.upper().replace('_', '-'))
        else:
            file_list.append("none")

    # Create the plot title
    title = " ".join(title_list)

    # Create the plot output filename
    filename = './tp__' + "__".join(file_list).replace(' ', '-').replace('+', 'p').lower() + ".png"

    if os.path.isfile(filename):
        print("%s already exists. skipping..." % filename)
        continue

    # make the instrument instance, set up the wavelength vector, and get out the total efficiency.
    instrument_factory = InstrumentFactory(config=conf)

    print(iconf['instrument'], iconf['mode'], iconf['aperture'])
    try:
        wave_range = instrument_factory.get_wave_range()
    except:
        print('***  Exception with get_wave_range  ***')
        pass

    wave = np.linspace(wave_range['wmin'], wave_range['wmax'], num=500)
    eff = instrument_factory.get_total_eff(wave)

    # make the plot, save it to a file, and close things up when done.
    f = plt.figure(facecolor='white')
    ax = plt.gca()
    ax.tick_params(axis='both', which='major', labelsize='14', colors='black')
Exemple #10
0
def compute_full_sim(dictinput):
    """Top level function to set up exoplanet obs. for JW
    
    Function to set up explanet observations for JWST only and 
    compute simulated spectrum. It uses STScI's Pandeia to compute 
    instrument throughputs and WebbPSF to compute PSFs. 
    
    Parameters
    ----------
    dictinput : dict
        dictionary containing instrument parameters and exoplanet specific 
        parameters. {"pandeia_input":dict1, "pandexo_input":dict1}
    
    Returns
    -------
    dict
        large dictionary with 1d, 2d simualtions, timing info, instrument info, warnings
    
    Examples
    --------  
      
    >>> from pandexo.engine.jwst import compute_full_sim 
    >>> from pandexo.engine.justplotit import jwst_1d_spec
    >>> a = compute_full_sim({"pandeia_input": pandeiadict, "pandexo_input":exodict})
    >>> jwst_1d_spec(a)
    .. image:: 1d_spec.png
    
    Notes
    -----
    It is much easier to run simulations through either **run_online** or **justdoit**. **justdoit** contains functions to create input dictionaries and **run_online** contains web forms to create input dictionaries.
    
    See Also
    -------- 
        pandexo.engine.justdoit.run_pandexo : Best function for running pandexo runs
        pandexo.engine.run_online : Allows running functions through online interface
    """
    pandeia_input = dictinput['pandeia_input']
    pandexo_input = dictinput['pandexo_input']

    #define the calculation we'll be doing
    if pandexo_input['planet']['w_unit'] == 'sec':
        calculation = 'phase_spec'
    else:
        calculation = pandexo_input['calculation'].lower()

    #which instrument
    instrument = pandeia_input['configuration']['instrument']['instrument']
    conf = pandeia_input['configuration']

    #if optimize is in the ngroups section, this will throw an error
    #so create temp conf with 2 groups
    try:
        i = InstrumentFactory(config=conf)
        det_pars = i.get_detector_pars()
        exp_pars = i.get_exposure_pars()
    except:
        conf_temp = deepcopy(conf)
        conf_temp['detector']['ngroup'] = 2
        i = InstrumentFactory(config=conf_temp)
        det_pars = i.get_detector_pars()
        exp_pars = i.get_exposure_pars()

    #detector parameters
    fullwell = det_pars['fullwell']
    rn = det_pars['rn']
    sat_level = pandexo_input['observation']['sat_level'] / 100.0 * fullwell
    mingroups = det_pars['mingroups']

    #exposure parameters
    tframe = exp_pars.tframe
    nframe = exp_pars.nframe
    nskip = exp_pars.nskip

    #parameteres needed from exo_input
    mag = pandexo_input['star']['mag']

    noccultations = pandexo_input['observation']['noccultations']
    R = pandexo_input['observation']['R']
    #amount of exposure time out-of-occultation, as a fraction of in-occ time
    expfact_out = pandexo_input['observation']['fraction']
    noise_floor = pandexo_input['observation']['noise_floor']

    #get stellar spectrum and in transit spec
    star_spec = create.outTrans(pandexo_input['star'])
    both_spec = create.bothTrans(star_spec, pandexo_input['planet'])
    out_spectrum = np.array([both_spec['wave'], both_spec['flux_out_trans']])

    #get transit duration from phase curve or from input
    if calculation == 'phase_spec':
        transit_duration = max(both_spec['time']) - min(both_spec['time'])
    else:
        transit_duration = pandexo_input['planet']['transit_duration']

    #add to pandeia input
    pandeia_input['scene'][0]['spectrum']['sed']['spectrum'] = out_spectrum

    if isinstance(pandeia_input["configuration"]["detector"]["ngroup"],
                  (float, int)):
        m = {
            "ngroup": pandeia_input["configuration"]["detector"]["ngroup"],
            "tframe": tframe,
            "nframe": nframe,
            "mingroups": mingroups,
            "nskip": nskip
        }
    else:
        #run pandeia once to determine max exposure time per int and get exposure params
        print("Optimization Reqested: Computing Duty Cycle")
        m = {
            "maxexptime_per_int":
            compute_maxexptime_per_int(pandeia_input, sat_level),
            "tframe":
            tframe,
            "nframe":
            nframe,
            "mingroups":
            mingroups,
            "nskip":
            nskip
        }
        print("Finished Duty Cycle Calc")

    #calculate all timing info
    timing, flags = compute_timing(m, transit_duration, expfact_out,
                                   noccultations)

    #Simulate out trans and in transit
    print("Starting Out of Transit Simulation")
    out = perform_out(pandeia_input, pandexo_input, timing, both_spec)

    #extract extraction area before dict conversion
    extraction_area = out.extraction_area
    out = out.as_dict()
    out.pop('3d')
    print("End out of Transit")

    #this kind of redundant going to compute inn from out instead
    #keep perform_in but change inputs to (out, timing, both_spec)
    print("Starting In Transit Simulation")
    inn = perform_in(pandeia_input, pandexo_input, timing, both_spec, out,
                     calculation)
    print("End In Transit")

    #compute warning flags for timing info
    warnings = add_warnings(out, timing, sat_level, flags, instrument)

    compNoise = ExtractSpec(inn, out, rn, extraction_area, timing)

    #slope method is pandeia's pure noise calculation (taken from SNR)
    #contains correlated noise, RN, dark current, sky,
    #uses MULTIACCUM formula so we deviated from this.
    #could eventually come back to this if Pandeia adopts First-Last formula
    if calculation == 'slope method':
        #Extract relevant info from pandeia output (1d curves and wavelength)
        #extracted flux in units of electron/s
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_slope_method()

    #derives noise from 2d postage stamps. Doing this results in a higher
    #1d flux rate than the Pandeia gets from extracting its own.
    #this should be used to benchmark Pandeia's 1d extraction
    elif calculation == '2d extract':
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_2d_extract()

    #this is the noise calculation that PandExo uses online. It derives
    #its own calculation of readnoise and does not use MULTIACUMM
    #noise formula
    elif calculation == 'fml':
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_f_minus_l()

    elif calculation == 'phase_spec':
        result = compNoise.run_phase_spec()
        w = result['time']
    else:
        result = None
        raise Exception('WARNING: Calculation method not found.')

    varin = result['var_in_1d']
    varout = result['var_out_1d']
    extracted_flux_out = result['photon_out_1d']
    extracted_flux_inn = result['photon_in_1d']

    #bin the data according to user input
    if R != None:
        wbin = bin_wave_to_R(w, R)
        photon_out_bin = uniform_tophat_sum(wbin, w, extracted_flux_out)
        photon_in_bin = uniform_tophat_sum(wbin, w, extracted_flux_inn)
        var_in_bin = uniform_tophat_sum(wbin, w, varin)
        var_out_bin = uniform_tophat_sum(wbin, w, varout)
    else:
        wbin = w
        photon_out_bin = extracted_flux_out
        photon_in_bin = extracted_flux_inn
        var_in_bin = varin
        var_out_bin = varout

    #calculate total variance
    var_tot = var_in_bin + var_out_bin
    error = np.sqrt(var_tot)

    #calculate error on spectrum
    error_spec = error / photon_out_bin

    #Add in user specified noise floor
    error_spec_nfloor = add_noise_floor(noise_floor, wbin, error_spec)

    #add in random noise for the simulated spectrum
    rand_noise = np.sqrt(
        (var_in_bin + var_out_bin)) * (np.random.randn(len(wbin)))
    raw_spec = (photon_out_bin - photon_in_bin) / photon_out_bin
    sim_spec = (photon_out_bin - photon_in_bin + rand_noise) / photon_out_bin

    #if secondary tranist, multiply spectra by -1
    if pandexo_input['planet']['f_unit'] == 'fp/f*':
        sim_spec = -1.0 * sim_spec
        raw_spec = -1.0 * raw_spec

    #package processed data
    binned = {
        'wave': wbin,
        'spectrum': raw_spec,
        'spectrum_w_rand': sim_spec,
        'error_w_floor': error_spec_nfloor
    }

    unbinned = {
        'flux_out': extracted_flux_out,
        'flux_in': extracted_flux_inn,
        'var_in': varin,
        'var_out': varout,
        'wave': w,
        'error_no_floor': np.sqrt(varin + varout) / extracted_flux_out
    }

    result_dict = as_dict(out, both_spec, binned, timing, mag, sat_level,
                          warnings, pandexo_input['planet']['f_unit'],
                          unbinned, calculation)

    return result_dict
Exemple #11
0
def compute_full_sim(dictinput): 
    """Top level function to set up exoplanet obs. for JW
    
    Function to set up explanet observations for JWST only and 
    compute simulated spectrum. It uses STScI's Pandeia to compute 
    instrument throughputs and WebbPSF to compute PSFs. 
    
    Parameters
    ----------
    dictinput : dict
        dictionary containing instrument parameters and exoplanet specific 
        parameters. {"pandeia_input":dict1, "pandexo_input":dict1}
    
    Returns
    -------
    dict
        large dictionary with 1d, 2d simualtions, timing info, instrument info, warnings
    
    Examples
    --------  
      
    >>> from pandexo.engine.jwst import compute_full_sim 
    >>> from pandexo.engine.justplotit import jwst_1d_spec
    >>> a = compute_full_sim({"pandeia_input": pandeiadict, "pandexo_input":exodict})
    >>> jwst_1d_spec(a)
    .. image:: 1d_spec.png
    
    Notes
    -----
    It is much easier to run simulations through either **run_online** or **justdoit**. **justdoit** contains functions to create input dictionaries and **run_online** contains web forms to create input dictionaries.
    
    See Also
    -------- 
        pandexo.engine.justdoit.run_pandexo : Best function for running pandexo runs
        pandexo.engine.run_online : Allows running functions through online interface
    """
    pandeia_input = dictinput['pandeia_input']
    pandexo_input = dictinput['pandexo_input']    	
	
    #define the calculation we'll be doing 
    if pandexo_input['planet']['w_unit'] == 'sec':
        calculation = 'phase_spec'
    else: 
        calculation = pandexo_input['calculation'].lower()

    #which instrument 
    instrument = pandeia_input['configuration']['instrument']['instrument']
    conf = pandeia_input['configuration']
    
    #if optimize is in the ngroups section, this will throw an error 
    #so create temp conf with 2 groups 
    try:
        i = InstrumentFactory(config=conf)
        det_pars = i.get_detector_pars()
        exp_pars = i.get_exposure_pars()
    except: 
        conf_temp = deepcopy(conf) 
        conf_temp['detector']['ngroup'] = 2 
        i = InstrumentFactory(config=conf_temp)
        det_pars = i.get_detector_pars()
        exp_pars = i.get_exposure_pars()
        
    #detector parameters
    fullwell = det_pars['fullwell']
    rn = det_pars['rn']
    sat_level = pandexo_input['observation']['sat_level']/100.0*fullwell
    mingroups = det_pars['mingroups']
    
    #exposure parameters 
    tframe = exp_pars.tframe
    nframe = exp_pars.nframe
    nskip = exp_pars.nskip
    
    #parameteres needed from exo_input
    mag = pandexo_input['star']['mag']
    
    
    noccultations = pandexo_input['observation']['noccultations']
    R = pandexo_input['observation']['R']
    #amount of exposure time out-of-occultation, as a fraction of in-occ time 
    expfact_out = pandexo_input['observation']['fraction'] 
    noise_floor = pandexo_input['observation']['noise_floor']

    
    #get stellar spectrum and in transit spec
    star_spec = create.outTrans(pandexo_input['star'])
    both_spec = create.bothTrans(star_spec, pandexo_input['planet'])
    out_spectrum = np.array([both_spec['wave'], both_spec['flux_out_trans']])
    
    #get transit duration from phase curve or from input 
    if calculation == 'phase_spec': 
        transit_duration = max(both_spec['time']) - min(both_spec['time'])
    else: 
        transit_duration = pandexo_input['planet']['transit_duration']

    #add to pandeia input 
    pandeia_input['scene'][0]['spectrum']['sed']['spectrum'] = out_spectrum
    
    if isinstance(pandeia_input["configuration"]["detector"]["ngroup"], (float,int)):
        m = {"ngroup":pandeia_input["configuration"]["detector"]["ngroup"], "tframe":tframe,
            "nframe":nframe,"mingroups":mingroups,"nskip":nskip}
    else:
        #run pandeia once to determine max exposure time per int and get exposure params
        print("Optimization Reqested: Computing Duty Cycle")
        m = {"maxexptime_per_int":compute_maxexptime_per_int(pandeia_input, sat_level) , 
            "tframe":tframe,"nframe":nframe,"mingroups":mingroups,"nskip":nskip}
        print("Finished Duty Cycle Calc")

    #calculate all timing info
    timing, flags = compute_timing(m,transit_duration,expfact_out,noccultations)
    
    #Simulate out trans and in transit
    print("Starting Out of Transit Simulation")
    out = perform_out(pandeia_input, pandexo_input,timing, both_spec)
    
    #extract extraction area before dict conversion
    extraction_area = out.extraction_area
    out = out.as_dict()
    out.pop('3d')
    print("End out of Transit")

    #Remove effects of Quantum Yield from shot noise 
    out = remove_QY(out, instrument)

    #this kind of redundant going to compute inn from out instead 
    #keep perform_in but change inputs to (out, timing, both_spec)
    print("Starting In Transit Simulation")
    inn = perform_in(pandeia_input, pandexo_input,timing, both_spec, out, calculation)
    print("End In Transit")
    


    #compute warning flags for timing info 
    warnings = add_warnings(out, timing, sat_level/fullwell, flags, instrument) 

    compNoise = ExtractSpec(inn, out, rn, extraction_area, timing)
    
    #slope method is pandeia's pure noise calculation (taken from SNR)
    #contains correlated noise, RN, dark current, sky, 
    #uses MULTIACCUM formula so we deviated from this. 
    #could eventually come back to this if Pandeia adopts First-Last formula
    if calculation == 'slope method': 
        #Extract relevant info from pandeia output (1d curves and wavelength) 
        #extracted flux in units of electron/s
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_slope_method()

    #derives noise from 2d postage stamps. Doing this results in a higher 
    #1d flux rate than the Pandeia gets from extracting its own. 
    #this should be used to benchmark Pandeia's 1d extraction  
    elif calculation == '2d extract':
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_2d_extract()
    
    #this is the noise calculation that PandExo uses online. It derives 
    #its own calculation of readnoise and does not use MULTIACUMM 
    #noise formula  
    elif calculation == 'fml':
        w = out['1d']['extracted_flux'][0]
        result = compNoise.run_f_minus_l()
    
    elif calculation == 'phase_spec':
        result = compNoise.run_phase_spec()
        w = result['time']
    else:
        result = None
        raise Exception('WARNING: Calculation method not found.')
        
    varin = result['var_in_1d']
    varout = result['var_out_1d']
    extracted_flux_out = result['photon_out_1d']
    extracted_flux_inn = result['photon_in_1d']

        
    #bin the data according to user input 
    if R != None: 
        wbin = bin_wave_to_R(w, R)
        photon_out_bin = uniform_tophat_sum(wbin, w,extracted_flux_out)
        wbin = wbin[photon_out_bin > 0 ]
        photon_in_bin = uniform_tophat_sum(wbin,w, extracted_flux_inn)
        photon_in_bin = photon_in_bin[photon_out_bin > 0 ]
        var_in_bin = uniform_tophat_sum(wbin, w,varin)
        var_in_bin = var_in_bin[photon_out_bin > 0 ]
        var_out_bin = uniform_tophat_sum(wbin,w, varout)
        var_out_bin = var_out_bin[photon_out_bin > 0 ]
        photon_out_bin = photon_out_bin
    else: 
        wbin = w
        photon_out_bin = extracted_flux_out
        wbin = wbin[photon_out_bin>0]
        photon_in_bin = extracted_flux_inn
        photon_in_bin = photon_in_bin[photon_out_bin>0]
        var_in_bin = varin
        var_in_bin = var_in_bin[photon_out_bin>0]
        var_out_bin = varout
        var_out_bin = var_out_bin[photon_out_bin>0]
        photon_out_bin = photon_out_bin[photon_out_bin>0]
    
    if calculation == 'phase_spec':
        to = timing["APT: Num Groups per Integration"]*tframe
        ti = timing["APT: Num Groups per Integration"]*tframe
    else: 
        #otherwise error propagation and account for different 
        #times in transit and out 
        to = result['on_source_out']
        ti = result['on_source_in']
        
    var_tot = (to/ti/photon_out_bin)**2.0 * var_in_bin + (photon_in_bin*to/ti/photon_out_bin**2.0)**2.0 * var_out_bin
    error_spec = np.sqrt(var_tot)
        
    #factor in occultations to noise 
    nocc = timing['Number of Transits']
    error_spec = error_spec / np.sqrt(nocc)
        
    #Add in user specified noise floor 
    error_spec_nfloor = add_noise_floor(noise_floor, wbin, error_spec) 

    #add in random noise for the simulated spectrum 
    rand_noise= error_spec_nfloor*(np.random.randn(len(wbin)))
    raw_spec = (photon_out_bin/to-photon_in_bin/ti)/(photon_out_bin/to)
    sim_spec = raw_spec + rand_noise 
    
    #if secondary tranist, multiply spectra by -1 
    if pandexo_input['planet']['f_unit'] == 'fp/f*':
        sim_spec = -1.0*sim_spec
        raw_spec = -1.0*raw_spec    
   
    #package processed data
    finalspec = {'wave':wbin,
              'spectrum': raw_spec,
              'spectrum_w_rand':sim_spec,
              'error_w_floor':error_spec_nfloor}
    
    rawstuff = {
                'electrons_out':photon_out_bin*nocc, 
                'electrons_in':photon_in_bin*nocc,
                'var_in':varin*nocc, 
                'var_out':varout*nocc,
                'e_rate_out':photon_out_bin/to,
                'e_rate_in':photon_out_bin/ti,
                'wave':w,
                'error_no_floor':error_spec, 
                'rn[out,in]':result['rn[out,in]'],
                'bkg[out,in]':result['bkg[out,in]']
                }
 
    result_dict = as_dict(out,both_spec ,finalspec, 
                timing, mag, sat_level, warnings,
                pandexo_input['planet']['f_unit'], rawstuff,calculation)

    return result_dict