Pandeia Tutorials

This article provides several examples on how to use Pandeia for Roman.




How to Use this Article 

In this article, several examples on how to use Pandeia for Roman are given. Prior to reading these examples, users should consult the article Overview of Pandeia for information on how to install Pandeia and the necessary supporting data files, as well as for configuration information for  Pandeia simulations. All calculations in this article were performed using Pandeia and reference data This documentation is written for  Pandeia version 2024.12 (released on December 11, 2024).



Example 1: Computing the Signal-to-Noise Ratio 

Running the code below will generate output in the form of a dictionary that contains all of the information from the Pandeia Engine Report. This is largely the standard way of running Pandeia where the properties of the instrumental set up and astronomical scene are specified.

Description of Code Snippet

The configuration below uses the default point source normalized to an AB magnitude of 25, the WFI multi-accumulation (MA) table "c2a_img_hlwas" (a MA table optimized for an early design of the High Latitude Wide Area Survey) with no truncation (139.15 seconds of total exposure time), and the F129 imaging filter. The Appendix: WFI MultiAccum Tables article in the Roman APT User's Guide provides and overview of MA tables in Roman at this time. See the article Overview of Pandeia for more information.

Python Implementation

SNR Calculation Example
from pandeia.engine.perform_calculation import perform_calculation
from pandeia.engine.calc_utils import build_default_calc
 
# Get Default Parameters
calc = build_default_calc('roman', 'wfi', 'imaging')
 
# Set the global variable for the filter name (change to any valid filter)
FILTER = 'f129'
 
# Modify defaults to simulate a 25th AB magnitude source
mag = 25
calc['scene'][0]['spectrum']['normalization']['norm_flux'] = mag
calc['scene'][0]['spectrum']['normalization']['norm_fluxunit'] = 'abmag'
 
# Set number of exposures and filter
nexp = 3
calc['configuration']['detector']['nexp'] = nexp
calc['configuration']['instrument']['filter'] = FILTER
 
# Run calculation and return signal-to-noise ratio
report = perform_calculation(calc)
SNR = report['scalar']['sn']

print(f'Estimated S/N: {SNR:.2f}')

Warnings from Running the Code Block

This step may generate a WARNING from synphot that the spectrum is extrapolated, which can be ignored.


Running Pandeia for Roman will likely return a warning such as: if np.log(abs(val)) < -1*precision and val != 0.0. This is related to a JWST-specific test for float precision, and can be ignored in this case.

Result from the Example 

This calculation should output an estimated signal-to-noise of  11.89 .



Example 2: Calculating the Corresponding Magnitude for a Given Setup 

In this example, it is assumed that the user has an exposure configuration and is interested in understanding the corresponding magnitude for a given signal-to-noise ratio and for a specific observing setup. This application may be common for Roman when users are exploring the Roman science data archive. 

The default observational setup for Roman will be used. As in the previous example, the MA table is set to the "High Latitude Wide Area Survey – Imaging" table with 5 resultants. The Table of Code Inputs for Limiting Magnitude Calculation summarizes the parameters that can be adjusted in the Python implementation of this example and their presets. Starting with this example, users can change these parameters to better match their scientific use case. 

Description of Code Snippet 

The output from the code is the the  limiting magnitude of a source with SN = 5  from NEXP = 10  FILTER ='f129'; the code will assume a flat SED. The calculation is determined by setting up a helper function to optimize the signal-to-noise at the input magnitude and a method that computes the magnitude at a given signal-to-noise given the number of exposures. The latter function sets up the build_default_calc for Roman and performs the Pandeia simulations over a range of magnitudes iteratively to find the best match magnitude for the specified signal-to-noise. The parameters summarized in the Table of Code Inputs for Corresponding Magnitude Calculation are input near the end of the code-block and can be easily modified for the use case of interest. The result of this code is given at the end of the code block for a user to confirm their execution of the code. 


Table of Code Inputs for Corresponding Magnitude Calculation 


Specified Input DescriptionParameter in Code ExampleValue in Code Example

signal-to-noise

the value that is useful for the science case being investigated

SN

5

number of exposures

the number of individual exposures of a given Multi-Accumulation sequence

NEXP

10

filter

the filter used in the observation 

FILTER

'f129'

Python Implementation 

Code to Estimate the Limiting Magnitude
from pandeia.engine.calc_utils import build_default_calc
from pandeia.engine.perform_calculation import perform_calculation
from scipy import interpolate
import numpy as np
 
def compute_mag(filt, nexp, bracket=(18, 30)):
    """
    Method to compute the magnitude from S/N and number of exposures
 
    Parameters
    ----------
    filt : str
        Name of Roman WFI filter
    nexp : int
        Number of exposures
    bracket : tuple
        Range of magnitudes to test. default: (18, 30)

 
    Returns
    -------
    mag_range : float
        An array of magnitudes used to compute the SNRs
    computed_snrs: float
        An array of computed SNRs from Pandeia calculations
    """
 
    # Set up default Roman observation
    calc = build_default_calc('roman', 'wfi', 'imaging')
 
    # Modify defaults to place a source with an AB magnitude
    calc['scene'][0]['spectrum']['normalization']['norm_fluxunit'] = 'abmag'
    calc['scene'][0]['spectrum']['normalization']['norm_waveunit'] = 'um'
      
    # Set number of exposures and filter
    calc['configuration']['detector']['nexp'] = nexp
    calc['configuration']['instrument']['filter'] = filt

    # Create an array of magnitudes range of interest
    mag_range = np.arange(bracket[0], bracket[1]+1, 1)
    # Create empty lists to save the computations
    computed_snrs = []
    # Compute the SNRs for a given magnitude
    for m in range(len(mag_range)):
        mag = mag_range[m]
        calc['scene'][0]['spectrum']['normalization']['norm_flux'] = mag
        report = perform_calculation(calc)
        computed_snrs.append(report['scalar']['sn'])

    return mag_range, computed_snrs

def _mag2sn_(mag_range, computed_snrs, sntarget):
    """
    Calculate a magnitude given a desired SNR by interpolating (computed_snrs, mag_range) from compute_mag
    
    Parameters
    ----------
    mag_range: float
        An array of magnitudes used in calculating a range of SNRs in compute_mag
    computed_snrs: float
        An array of computed SNR given the mag_range using Pandeia calculation object
    sntarget: float
        Required S/N
    
    """
    interpolator = interpolate.interp1d(computed_snrs, mag_range)
    mag = interpolator(sntarget)

    return mag

 
# Required S/N and number of exposures
sn = 5.
nexp = 10
FILTER = 'f129'
 
# Run minimizer function to estimate the magnitude given sn and nexp
mag_range, computed_snrs = compute_mag(FILTER, nexp)
mag = _mag2sn_(mag_range, computed_snrs, sn)
print(f'Estimated magnitude: {mag:.2f}')

Result from the Example 

This calculation should output an estimated limiting magnitude of  26.77 mag at a signal-to-noise of 5 based on the inputs from the Table of Code Inputs for Corresponding Magnitude Calculation.



Example 3: Determining the Optimal Number of Exposures 

In this example, we assume the user has a required signal-to-noise at a desired magnitude limit, and wishes to know the number of exposures, for the default MA table, required to achieve these observational results.

Description of Code Snippet 

In this case, the inputs to the code are SN , MAG , and FILTER , which are described in the Table of Code Inputs for Determining Number of Exposures. The output will be NEXP . The code will assume a flat spectral energy distribution (SED). The calculation is determined by setting up a helper function to optimize the signal-to-noise at the input magnitude and a method that computes the number of exposures at a given signal-to-noise given the source magnitude. The latter function sets up the build_default_calc for Roman and iteratively performs the  Pandeia simulations over a range of exposures to find the best match. The parameters summarized in the Table of Code Inputs for Determining Number of Exposures are input near the end of the code-block and can be easily modified for the use case of interest. The result of this code is given at the end of the code block.


Table of Code Inputs for Determining Number of Exposures 


Specified Input DescriptionParameter in Code ExampleValue in Code Example

signal-to-noise

value that is useful for the science case being investigated

SN

20

source magnitude

magnitude in ABMag for the source of interest

MAG

26

filter

filter used in the observation 

FILTER

'f129'


Python Implementation 

from scipy.optimize import minimize_scalar
from pandeia.engine.calc_utils import build_default_calc
from pandeia.engine.perform_calculation import perform_calculation
 
def _nexp2sn_(nexp, calc, sntarget):
    """
    Helper function to optimize the S/N given a number of exposures.
    """
    calc['configuration']['detector']['nexp'] = int(nexp)
    etc = perform_calculation(calc)['scalar']

    return (sntarget - etc['sn'])**2
 
def compute_nexp(filt, sn, mag, bracket=(1, 1000), xtol=0.1):
    """
    Method to compute the number of exposures from S/N and magnitude
 
    Parameters
    ----------
    filt : str
        Name of Roman WFI filter
    sn : float
        Required S/N
    mag : float
        AB Magnitude of source
    bracket : tuple, default (1, 1000)
        Range of magnitudes to test
    xtold: float, default 0.1
        Target tolerance for minimizer
 
    Returns
    -------
    nexp : float
        Optimal number of exposures for specified S/N and magnitude
    report: dict
        Pandeia dictionary with optimal parameters
    exptime: float
        Exposure time for optimal observation
    """
 
    # Setup default Roman observation
    calc = build_default_calc('roman', 'wfi', 'imaging')
 
    # Modify defaults to place a source with an AB magnitude
    calc['scene'][0]['spectrum']['normalization']['norm_flux'] = mag
    calc['scene'][0]['spectrum']['normalization']['norm_fluxunit'] = 'abmag'
    calc['scene'][0]['spectrum']['normalization']['norm_waveunit'] = 'um'
      
    # Set filter
    calc['configuration']['instrument']['filter'] = filt
 
    # Check that the minimum of 1 exposure has a S/N lower than requested,
    # otherwise there is no sense in attempting to minimize nexp.
 
    calc['configuration']['detector']['nexp'] = 1
    report = perform_calculation(calc)

    if report['scalar']['sn'] > sn:
        nexp = 1
    else:
        res = minimize_scalar(_nexp2sn_,
                              bracket=bracket,
                              bounds=bracket,
                              args=(calc, sn),
                              method='bounded',
                              options={'xatol':xtol})
        
		# Take the optimization result and set it to nexp
        # 'x' is the solution array in the optimization result object
        # For more details on the minimize_scalar function, refer to https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize_scalar.html
		nexp = int(res['x'])  
        calc['configuration']['detector']['nexp'] = nexp
        report = perform_calculation(calc)
 
        # This generally returns a S/N less than the required amount.
        # Let's ensure that we get *AT LEAST* the required S/N for two reasons:
        # 1) Better to err on the side of caution
        # 2) Make code consistent with the above if-clause
        if report['scalar']['sn'] < sn:
            nexp += 1
             
    exptime = report['scalar']['total_exposure_time']
         
    return nexp, report, exptime
 
# Desired magnitude and S/N
mag = 26.
sn = 20.
FILTER = 'f129'
 
# Run minimizer function
nexp, etc, exptime = compute_nexp(FILTER, sn, mag)
 
# Print reported numbers
print(f'Number of exposures: {nexp}')
print(f'Actual S/N reached: {etc["scalar"]["sn"]:.2f}')
print(f'Exposure time: {exptime:.2f}')

Warnings Issued by Running this Code 

This step may generate a WARNING from synphot that the spectrum is extrapolated, which may be ignored. There may be an additional WARNING that the signal-to-noise for a single exposure is larger than what was requested, which may also be ignored.

Result from the Example

This calculation should output 48 exposures, a signal-to-noise reached of  20.12 , and an exposure time of 6679.14 seconds. 

Since nexp must be an integer, the signal-to-noise returned will be at least the required value, but could be a higher value. The value of the returned signal-to-noise can be significantly higher than the requested value when the inferred nexp is small.



Example 4: Modifying the Spectral Energy Distribution (SED)

A scientific goal may require specifying something more complex than a flat SED (as assumed in other examples). In this instance, we assume that the SED is determined by a star selected from a grid of Phoenix models (the only supported stellar models at this time).

Description of Code Snippet 

The code will simulate a  mag = 25 AB magnitude source in  FILTER = 'f129' with nexp = 3 (using the default MA table). A step is added, however, to modify the SED shape from the default flat spectrum; the user sets the sed_type to 'phoenix' and then specifies the  key as 'a0v', which is a star of type A0V (i.e., an A0 main-sequence star). A summary of these options is given in Pre-Configured Spectral Energy Distributions section of the article Overview of Pandeia.

Python Implementation 

from pandeia.engine.perform_calculation import perform_calculation
from pandeia.engine.calc_utils import build_default_calc
 
# Get Default Parameters
calc = build_default_calc('roman', 'wfi', 'imaging')
 
# Set the global variable for the filter name (change to any valid filter)
FILTER = 'f129'
 
# Modify defaults to simulate a 25th AB magnitude source
mag = 25
calc['scene'][0]['spectrum']['normalization']['norm_flux'] = mag
calc['scene'][0]['spectrum']['normalization']['norm_fluxunit'] = 'abmag'
 
# Set number of exposures and filter
nexp = 3
calc['configuration']['detector']['nexp'] = nexp
calc['configuration']['instrument']['filter'] = FILTER
 
# Modify SED shape
calc['scene'][0]['spectrum']['sed']['sed_type'] = 'phoenix'
calc['scene'][0]['spectrum']['sed']['key'] = 'a0v'
 
# Run calculation and return signal-to-noise ratio
report = perform_calculation(calc)
SNR = report['scalar']['sn']
print(f'Estimated S/N: {SNR:.2f}')

Result from the Example 

This calculation should output an estimated signal-to-noise ratio of  15.87 .

More Information and Options to Explore 

Further information about Pandeia is available on the Pandeia for JWST Documentation on JDox, including detailed breakdowns of all of the allowable keywords and pre-configured options. 




For additional questions not answered in this article, please contact the Roman Help Desk at STScI.




Latest Update

 

Outputs updated to match most recent Pandeia version.
Publication

 

Initial publication of the article.