Flare Line Diagnostics#

Here, we assess the SNR in a number of flare lines for classes M1 and above

[1]:
import asdf
from astropy.coordinates import SkyCoord
import astropy.table
import astropy.units as u
from astropy.visualization import quantity_support
from astropy.wcs.utils import wcs_to_celestial_frame
import fiasco
import matplotlib.pyplot as plt
import ndcube
import numpy as np
from scipy.interpolate import interp1d
from sunpy.coordinates import get_earth

from mocksipipeline.modeling import convolve_with_response, _blur_spectra
from mocksipipeline.instrument.configuration import moxsi_slot_spectrogram_pinhole, moxsi_slot
[2]:
def annotate_lines(axis, line_list, flux, color, unit=None, rtol=0.1):
    wavelength = flux.axis_world_coords(0)[0]
    data = flux.to(flux.unit if unit is None else unit).data
    for row in line_list:
        i_x = flux.wcs.world_to_array_index(u.Quantity([row['wavelength']]))
        if data[i_x] / data.max() < rtol:
            continue
        axis.annotate(
            f'{row["ion name"]}, {row["wavelength"].to_string(format="latex_inline", precision=5)}',
            (wavelength[i_x].to_value(), data[i_x]),
            xytext=(0, 150),
            textcoords='offset points',
            rotation=90,
            color=color,
            horizontalalignment='center',
            verticalalignment='center',
            arrowprops=dict(color=color, arrowstyle='-', ls='--'),
        )

Creating a flare line list#

Load the line list and estimate the MOXSI flux from the CHIANTI intensities

[4]:
channel_o1 = moxsi_slot_spectrogram_pinhole.channel_list[12]
[5]:
channel_o1
[5]:
MOXSI Detector Channel--spectrogram_pinhole
-----------------------------------
Spectral order: 1
Filter: Al+Al2O3, 150.000 nm
Detector dimensions: (1504, 2000)
Wavelength range: [0.0 Angstrom, 83.61862217082933 Angstrom]
Spectral plate scale: 0.07179487179487179 Angstrom / pix
Spatial plate scale: [7.40437766 7.40437766] arcsec / pix
Reference pixel: [1000.  752.    0.] pix
aperture: Circular Aperture
-------------------------
diameter: 44.0 micron
area: 1.5205308443374597e-05 cm2

[6]:
earth_obs = get_earth('2020-01-01')
hpc_frame = wcs_to_celestial_frame(channel_o1.get_wcs(earth_obs))
source_loc = SkyCoord(Tx=0*u.arcsec, Ty=0*u.arcsec, frame=hpc_frame)
WARNING: FITSFixedWarning: 'datfix' made the change 'Set MJD-OBS to 58849.000000 from DATE-OBS'. [astropy.wcs.wcs]
WARNING: FITSFixedWarning: 'unitfix' made the change 'Changed units:
  'angstrom' -> 'Angstrom'. [astropy.wcs.wcs]

First, load the line list and convert intensities to units of the MOXSI detector. Note that we can carry out a similar process for the AR case using the AR intensity and the lower energy flare classes.

[7]:
full_line_list = astropy.table.QTable.read('../data/line_lists/full-line-list.asdf')
full_line_list = full_line_list[['ion name',
                                 'wavelength',
                                 'max temperature_flare_ext',
                                 'intensity (coronal)_flare_ext',
                                 'intensity (coronal)_active_region']]
full_line_list['energy'] = full_line_list['wavelength'].to('keV', equivalencies=u.spectral())
# Convert intensities to instrument units
f_ea_interp = interp1d(channel_o1.wavelength.to_value('AA'), channel_o1.effective_area.to_value('cm2'),)
ea_interp = f_ea_interp(full_line_list['wavelength'].to_value('AA')) * u.cm**2
full_line_list['intensity (coronal)_flare_ext'] = (full_line_list['intensity (coronal)_flare_ext'] *
                                                   ea_interp * channel_o1.pixel_solid_angle)
full_line_list.rename_column('intensity (coronal)_flare_ext', r'flare\_ext')
full_line_list['intensity (coronal)_active_region'] = (full_line_list['intensity (coronal)_active_region'] *
                                                       ea_interp * channel_o1.pixel_solid_angle).to('ph/pix/h')
full_line_list.rename_column('intensity (coronal)_active_region', r'active\_region')
# Add approximate formation temperature
temperature_grid = 10**np.arange(5.5,7.5,0.05)*u.K
all_chianti_ions = [fiasco.Ion(i, temperature_grid) for i in fiasco.list_ions(fiasco.defaults['hdf5_dbase_root'])]
ioneq_tmax = {i.ion_name_roman: i.formation_temperature for i in all_chianti_ions}
full_line_list['$T_{max}$'] = list(map(lambda x: ioneq_tmax[x].to('MK'), full_line_list['ion name']))
# Add FIP information
all_chianti_elements = [fiasco.Element(el, 1*u.MK) for el in fiasco.list_elements(fiasco.defaults['hdf5_dbase_root'])]
element_fip = {el.atomic_symbol: el[0].ip for el in all_chianti_elements}
full_line_list['IP'] = list(map(lambda x: element_fip[x.split()[0]].to('eV'), full_line_list['ion name']))
full_line_list['FIP'] = list(map(lambda x: 'low' if x<=10*u.eV else 'high', full_line_list['IP']))
# Add information about which moxsi pixel corresponds to which wavelength
full_line_list['MOXSI pixel'] = channel_o1.get_wcs(earth_obs).world_to_array_index(source_loc, full_line_list['wavelength'])[-1]
# Reorder columns
full_line_list = full_line_list[[
    'ion name',
    'wavelength',
    'energy',
    'MOXSI pixel',
    '$T_{max}$',
    'IP',
    'FIP',
    r'flare\_ext',
]]
full_line_list.sort(keys='wavelength')
WARNING: UnitsWarning: The unit 'Angstrom' has been deprecated in the VOUnit standard. Suggested: 0.1nm. [astropy.units.format.utils]
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/astropy/units/equivalencies.py:141: RuntimeWarning: divide by zero encountered in divide
  (si.m, si.J, lambda x: hc / x),
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/xrt/backends/raycing/materials.py:253: UserWarning: Reading `.npy` or `.npz` file required additional header parsing as it was created on Python 2. Save the file again to speed up loading and avoid this warning.
  ef1f2 = (np.array(res[self.name+'_E']),
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/xrt/backends/raycing/materials.py:254: UserWarning: Reading `.npy` or `.npz` file required additional header parsing as it was created on Python 2. Save the file again to speed up loading and avoid this warning.
  np.array(res[self.name+'_f1']),
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/xrt/backends/raycing/materials.py:255: UserWarning: Reading `.npy` or `.npz` file required additional header parsing as it was created on Python 2. Save the file again to speed up loading and avoid this warning.
  np.array(res[self.name+f2key]))
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/astropy/units/equivalencies.py:141: RuntimeWarning: divide by zero encountered in divide
  (si.m, si.J, lambda x: hc / x),
WARNING: UnitsWarning: 'ph/pix/h' contains multiple slashes, which is discouraged by the FITS standard [astropy.units.format.generic]
WARNING: FITSFixedWarning: 'datfix' made the change 'Set MJD-OBS to 58849.000000 from DATE-OBS'. [astropy.wcs.wcs]
WARNING: FITSFixedWarning: 'unitfix' made the change 'Changed units:
  'angstrom' -> 'Angstrom'. [astropy.wcs.wcs]
WARNING: No observer defined on WCS, SpectralCoord will be converted without any velocity frame change [astropy.wcs.wcsapi.fitswcs]

We want to avoid cases where we include lines that are actually weak, but get grouped into the same pixel as strong lines and thus appear as if they’re just as strong. Thus, we’ll group by MOXSI pixel and if a particular line is significantly below that line in that pixel, then we drop that line

[9]:
max_threshold = 0.05
group_tables = []
for group in full_line_list.group_by('MOXSI pixel').groups:
    group_tables.append(group[group['flare\\_ext'] >= group['flare\\_ext'].max() * max_threshold])
full_line_list = astropy.table.vstack(group_tables)

Then, load the reference flare spectra

[10]:
goes_class = ['m1', 'm5', 'x5']
flare_spectra = []
with asdf.open('../data/reference_spectra/caspi_flare_spectra.asdf', copy_arrays=True) as af:
    for gc in goes_class:
        flare_spectra.append((gc, ndcube.NDCube(af.tree[gc]['data'], wcs=af.tree[gc]['wcs'])))
flare_spectra = ndcube.NDCollection(flare_spectra, aligned_axes=(0,))
WARNING: UnitsWarning: The unit 'Angstrom' has been deprecated in the VOUnit standard. Suggested: 0.1nm. [astropy.units.format.utils]

Now, compute the MOXSI counts for a source location at disk center as a function of pixel

First, let’s just look at the flux in each line in first order

[11]:
flux_columns = {}
for gc in goes_class:
    flux = convolve_with_response(flare_spectra[gc], channel_o1, include_gain=False)
    #flux = _blur_spectra(flux, blur / channel_o1.spatial_plate_scale[0] * channel_o1.spectral_plate_scale, channel_o1)
    #indices = flux.wcs.world_to_array_index(full_line_list['wavelength'])
    # NOTE: Calling w2ai on an array when the WCS is 1D reverses the order of the returned indices (!!!)
    #indices = np.array(indices)[::-1]
    f_interp = interp1d(flux.axis_world_coords(0)[0].to_value('AA'), flux.data, kind='nearest')
    flux_columns[gc.capitalize()] = (f_interp(full_line_list['wavelength'].to_value('AA'))*flux.unit).to('ph/pix/s')
flare_snr_table = astropy.table.QTable(flux_columns)
flare_snr_table = astropy.table.hstack([full_line_list,flare_snr_table])
flare_snr_table.sort(keys='wavelength')
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/astropy/units/equivalencies.py:141: RuntimeWarning: divide by zero encountered in divide
  (si.m, si.J, lambda x: hc / x),
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/xrt/backends/raycing/materials.py:253: UserWarning: Reading `.npy` or `.npz` file required additional header parsing as it was created on Python 2. Save the file again to speed up loading and avoid this warning.
  ef1f2 = (np.array(res[self.name+'_E']),
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/xrt/backends/raycing/materials.py:254: UserWarning: Reading `.npy` or `.npz` file required additional header parsing as it was created on Python 2. Save the file again to speed up loading and avoid this warning.
  np.array(res[self.name+'_f1']),
/Users/wtbarnes/mambaforge/envs/mocksipipeline-dev/lib/python3.11/site-packages/xrt/backends/raycing/materials.py:255: UserWarning: Reading `.npy` or `.npz` file required additional header parsing as it was created on Python 2. Save the file again to speed up loading and avoid this warning.
  np.array(res[self.name+f2key]))
WARNING: UnitsWarning: 'ph/pix/s' contains multiple slashes, which is discouraged by the FITS standard [astropy.units.format.generic]

The next thing we need to do is make a cut in SNR to eliminate those lines we don’t have a chance of seeing. We require that this threshold be met at the M1 level and above

[12]:
threshold = 0.1 * u.photon / u.pix / u.s
[13]:
flare_snr_table = flare_snr_table[flare_snr_table['M1'] > threshold]

Finally, now that we’ve really pared down the table, let’s compute a \(T_{max}\) based on the contribution function

[14]:
contribution_functions = {}
for ion_name in np.unique(flare_snr_table['ion name']):
    ion = fiasco.Ion(ion_name, temperature_grid)
    #density = 1e15*u.K/(u.cm**3)/ion.temperature
    density = 1e10 * u.cm**(-3)
    goft = ion.contribution_function(density, couple_density_to_temperature=False)
    transitions = ion.transitions.wavelength[~ion.transitions.is_twophoton]
    contribution_functions[ion_name] = (transitions, goft)
goft_col = []
for row in flare_snr_table:
    _t, _g = contribution_functions[row['ion name']]
    idx = np.argmin(np.abs(_t - row['wavelength']))
    goft_col.append(_g[:,0,idx])
flare_snr_table['$T_{max,G}$'] = list(map(lambda x: temperature_grid[np.argmax(x)].to('MK'), goft_col))
WARNING: No proton data available for Al 12. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Ar 9. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Fe 16. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Fe 17. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Fe 24. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Mg 10. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Mg 11. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Mg 12. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for N 7. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Ne 9. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Ne 10. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Ni 19. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for O 7. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for O 8. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Si 12. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Si 13. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Si 14. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]

One last reordering of the columns

[15]:
flare_snr_table = flare_snr_table[[
    'ion name',
    'wavelength',
    'energy',
    'MOXSI pixel',
    '$T_{max}$',
    '$T_{max,G}$',
    'IP',
    'FIP',
    r'flare\_ext',
    'M1',
    'M5',
    'X5',
]]
flare_snr_table.sort(keys='wavelength')
[18]:
flare_snr_table
[18]:
QTable length=193
ion namewavelengthenergyMOXSI pixel$T_{max}$$T_{max,G}$IPFIPflare\_extM1M5X5
AngstromkeVMKMKeVph / (pix s)ph / (pix s)ph / (pix s)ph / (pix s)
str9float64float64int64float64float64float64str4float64float64float64float64
Si XIV6.18039989471435552.0060869934845944108612.58925411794150815.8489319246109148.151683322378426low2.771501687788080.190920681634322630.77262318791353875.958959528393517
Si XIV6.1858000755310062.0043356868845636108612.58925411794150815.8489319246109148.151683322378426low1.38601114673663120.190920681634322630.77262318791353875.958959528393517
Si XIII6.6479001045227051.865012958736416810934.4668359215095899.9999999999998778.151683322378426low2.58228719944288580.38851132864979031.69897627042211716.01591653315726
Si XIII6.6881999969482421.85377528318191710934.4668359215095898.912509381337358.151683322378426low0.37613814913171490.100985022807762570.441027274399776244.266389879196875
Mg XII7.1058001518249511.744830923810289310997.9432823472427239.9999999999998777.646231981257862low0.174670063587469450.16563201583333780.71830169326471456.601093936462812
Mg XII7.1069002151489261.744560844809921710997.9432823472427239.9999999999998777.646231981257862low0.087518334954537410.16563201583333780.71830169326471456.601093936462812
Al XII7.7572999000549321.598290642757311111083.5481338923357247.9432823472427235.985755006111463low0.083375382773861860.189148773514530440.8094610635922227.7635870033849725
Mg XI9.2312002182006841.343099439970405511292.8183829312644326.3095734448018657.646231981257862low0.09274847311527750.326277217319849161.38018277903787513.309287922783925
Fe XXII9.2406997680664061.3417187176847714112912.58925411794150814.1253754462273527.902380855536887low0.054378011017156390.326277217319849161.38018277903787513.309287922783925
Fe XXI9.4750003814697271.3085403001743023113211.22018454301949212.5892541179415087.902380855536887low0.37685213397167540.150390444723689260.3797285927670792.514803350288388
Fe XIX9.6905002593994141.279440638917896311358.912509381337359.9999999999998777.902380855536887low0.048696147149940420.112050446150890210.466729163075931044.468277244525059
Ni XXV9.9670000076293951.2439470085110327113919.952623149688519.95262314968857.637426623485137low0.082489355163338570.13115272410129970.57243257350746395.512369204832659
Fe XXI9.9729995727539061.24319867386662113911.22018454301949212.5892541179415087.902380855536887low0.049034463788492130.13115272410129970.57243257350746395.512369204832659
Ni XIX9.9771003723144531.242687692881642411395.6234132519034337.9432823472427237.637426623485137low0.0414581071131214850.13115272410129970.57243257350746395.512369204832659
Ne X10.2384996414184571.210960617038448211434.4668359215095896.30957344480186521.564534438226993high0.066126731711192070.10792155553305520.453799700939928664.336895828035151
Fe XVII10.5039997100830081.18035226442538111463.98107170553493676.3095734448018657.902380855536887low0.051763271545904080.151280735555572750.69079151035929936.8035718533056615
....................................
O VII21.8036003112792970.568640943069670613040.89125093813374211.995262314968866413.61805488299139high0.00153889474254888440.207365780833256550.220335724819764070.3664870391017608
O VII22.0977001190185550.561072861725063913080.89125093813374211.995262314968866413.61805488299139high0.0061409194972097020.32539531744425030.35165234460728380.64856053729616
N VII24.7791996002197270.500355945444262113451.58489319246110431.995262314968866414.534097255011275high0.0139483156800106760.14688993772001630.197913731320465830.7718378776017261
N VII24.784599304199220.500246935249801613451.58489319246110431.995262314968866414.534097255011275high0.0069609870991282030.14688993772001630.197913731320465830.7718378776017261
Si XI43.7620010375976560.283314737657175616101.58489319246110431.77827941003891198.151683322378426low0.0035143755841989090.104078517220974570.13798396535467890.5217646851356907
Si XII44.018600463867190.281663199480803716131.99526231496886642.2387211385683248.151683322378426low0.0313247909048884750.14375873336702370.276323995027977441.7775620686470603
Mg X44.049999237060550.2814624303759095716141.12201845430195851.25892541179416087.646231981257862low0.0015384118740010720.101389002856307660.19461424882198081.2502676590741986
Mg X44.049999237060550.2814624303759095716141.12201845430195851.25892541179416087.646231981257862low0.00232530679183823050.101389002856307660.19461424882198081.2502676590741986
Si XII44.1603012084960940.280759403899506616151.99526231496886642.2387211385683248.151683322378426low0.0564782427202857940.34596079055434160.65144035542578494.1115770161745875
Si XII45.690601348876950.2713560224049178516361.99526231496886642.2387211385683248.151683322378426low0.0253872911893230670.148289693787040130.273297976019184041.6889536403974332
Si XI49.175998687744140.2521233970670739316851.58489319246110431.77827941003891198.151683322378426low0.000194213430221375280.116228440113692460.13968613877035880.40526176497891203
Ar IX49.185501098632810.2520746879950900316850.70794578438413581.122018454301958515.759606666004402high6.688484348572473e-050.116228440113692460.13968613877035880.40526176497891203
Fe XVI50.3610000610351560.2461908982802907817012.8183829312644322.8183829312644327.902380855536887low0.0123271159458598050.137845410537976640.284458745380501371.9476444322558906
Fe XVI54.7099990844726560.2266207284006122617622.8183829312644322.8183829312644327.902380855536887low0.0111051139706551620.105418946454254480.192993469126182651.1863695616483072
Fe XVI63.710998535156250.1946040735255228418872.8183829312644322.8183829312644327.902380855536887low0.008592768982985390.109461162019788050.188537363680456341.0857416459736378
Si VIII63.7319984436035160.1945399508269208518880.89125093813374210.99999999999999598.151683322378426low1.8936964471402224e-050.109461162019788050.188537363680456341.0857416459736378

Now, save the line table

[19]:
flare_snr_table.write('../data/line_lists/flare-line-table.asdf')
WARNING: UnitsWarning: The unit 'Angstrom' has been deprecated in the VOUnit standard. Suggested: 0.1nm. [astropy.units.format.utils]
WARNING: UnitsWarning: The unit 'Angstrom' has been deprecated in the VOUnit standard. Suggested: 0.1nm. [astropy.units.format.utils]

Assessing Relationship between formation temperature and IP#

Plot ionization potential versus formation temperature

[15]:
el_colors = {el: f'C{i}' for i,el in enumerate(np.unique(list(map(lambda x: x.split()[0], flare_snr_table['ion name']))))}
with quantity_support():
    fig = plt.figure(figsize=(20,10))
    # Contribution function
    ax = fig.add_subplot(121)
    for row in flare_snr_table:
        ax.scatter(row['$T_{max,G}$'], row['IP'],
                   marker='.',
                   s=500,
                   color=el_colors[row['ion name'].split()[0]],
                   #s=row['X5']*1e3,
                   #alpha=0.25,
                   label=row['ion name'].split()[0])
    ax.axhline(y=10*u.eV, color='k', ls=':')
    ax.invert_yaxis()
    handles, labels = ax.get_legend_handles_labels()
    by_label = dict(zip(labels, handles))
    ax.legend(by_label.values(), by_label.keys(),loc=4)
    # Ionization fraction
    ax = fig.add_subplot(122,sharex=ax,sharey=ax)
    for row in flare_snr_table:
        ax.scatter(row['$T_{max}$'], row['IP'],
                   marker='.',
                   s=500,
                   color=el_colors[row['ion name'].split()[0]],
                   #s=row['X5']*1e3,
                   #alpha=0.25,
                   label=row['ion name'].split()[0])
    ax.axhline(y=10*u.eV, color='k', ls=':')
../_images/reports_flare-line-assessment_29_0.png
[16]:
with quantity_support():
    fig = plt.figure(figsize=(20,10))
    # Compare high and low FIP
    ax = fig.add_subplot(121)
    for row in flare_snr_table:
        ax.scatter(row['$T_{max,G}$'], row['IP'],
                   marker='o',
                   color=el_colors[row['ion name'].split()[0]],
                   #s=row['M5']*1e3,
                   alpha=0.5,
                   label=row['ion name'].split()[0])
    ax.axhline(y=10*u.eV, color='k', ls=':')
    handles, labels = ax.get_legend_handles_labels()
    by_label = dict(zip(labels, handles))
    ax.legend(by_label.values(), by_label.keys(), loc=3)
    ax.set_xlim(0,6)
    ax.invert_yaxis()
    # Compare temperature coverage
    ax = fig.add_subplot(122)
    for row in flare_snr_table:
        ax.scatter(row['$T_{max,G}$'], row['IP'],
                   marker='o',
                   color=el_colors[row['ion name'].split()[0]],
                   #s=row['M5']*1e3,
                   alpha=0.5,
                   label=row['ion name'].split()[0])
    ax.axhline(y=10*u.eV, color='k', ls=':')
    handles, labels = ax.get_legend_handles_labels()
    by_label = dict(zip(labels, handles))
    ax.legend(by_label.values(), by_label.keys(), loc=4)
    ax.set_ylim(5,9)
../_images/reports_flare-line-assessment_30_0.png

Assessing \(G(T)\) ratio for High and Low FIP Pairs#

[17]:
fig = plt.figure(figsize=(10,7))
ax = fig.add_subplot()
for goft,ion_name,fip in zip(goft_col, flare_snr_table['ion name'], flare_snr_table['FIP']):
    ax.plot(temperature_grid, goft, color=el_colors[ion_name.split()[0]], alpha=0.2 if fip=='low' else 1.0, label=ion_name.split()[0])
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_ylim(1e-30, 1e-23)
ax.set_xlim(4e5, 2e7)
handles, labels = ax.get_legend_handles_labels()
by_label = dict(zip(labels, handles))
ax.legend(by_label.values(), by_label.keys())
[17]:
<matplotlib.legend.Legend at 0x1771e1510>
../_images/reports_flare-line-assessment_32_1.png

Maybe take ratios between the contribution functions of the high-FIP elements? Which ones are the most flat across the relevant temperature ranges?

[18]:
modded_table = flare_snr_table.copy()
modded_table['contribution function'] = goft_col
[19]:
low_fip_lines = modded_table[modded_table['FIP']=='low']
high_fip_lines = modded_table[modded_table['FIP']=='high']
[20]:
from cycler import cycler
ls_cycler = cycler(linestyle=['-', '--', ':', '-.', (5, (10, 3))])
el_linestyles = {el: ls for el,ls in zip(np.unique(list(map(lambda x: x.split()[0], low_fip_lines['ion name']))), ls_cycler)}
[22]:
for hf_row in high_fip_lines:#.group_by('ion name').groups:
    #hf_row = tab[np.argsort(tab['flare\_ext'])][-1]
    goft_hf_strongest = hf_row['contribution function']
    goft_hf_strongest /= goft_hf_strongest.max()
    #plt.plot(temperature_grid, goft_hf_strongest, label=hf_row['ion name'])
    fig = plt.figure(figsize=(12,6))
    ax = fig.add_subplot()
    ax.set_title(f"{hf_row['ion name']} {hf_row['wavelength']:.3f}")
    #ax2 = ax.twinx()
    ax.plot(temperature_grid, goft_hf_strongest, color='k', lw=2,)
    for tab_lf in low_fip_lines.group_by('ion name').groups:
        lf_row = tab_lf[np.argsort(tab_lf['flare\_ext'])][-1]
        goft_lf_strongest = lf_row['contribution function']
        goft_lf_strongest /= goft_lf_strongest.max()
        goft_ratio = goft_hf_strongest/goft_lf_strongest
        ax.plot(temperature_grid, goft_hf_strongest/goft_lf_strongest,
                label=f"{lf_row['ion name']} {lf_row['wavelength'].to_value('Angstrom'):.2f} Å",
                ls=el_linestyles[lf_row['ion name'].split()[0]]['linestyle'])
    ax.set_yscale('log')
    ax.set_xscale('log')
    ax.set_ylim(1e-1, 1e1)
    ax.set_xlim(temperature_grid[[0,-1]].value)
    ax.axhline(y=1, ls='--', color='k')
    ax.legend(loc='center right', bbox_to_anchor=(1.3,0.5))
../_images/reports_flare-line-assessment_37_0.png
../_images/reports_flare-line-assessment_37_1.png
../_images/reports_flare-line-assessment_37_2.png
../_images/reports_flare-line-assessment_37_3.png
../_images/reports_flare-line-assessment_37_4.png
../_images/reports_flare-line-assessment_37_5.png
../_images/reports_flare-line-assessment_37_6.png
../_images/reports_flare-line-assessment_37_7.png
../_images/reports_flare-line-assessment_37_8.png
../_images/reports_flare-line-assessment_37_9.png
../_images/reports_flare-line-assessment_37_10.png
../_images/reports_flare-line-assessment_37_11.png
../_images/reports_flare-line-assessment_37_12.png
../_images/reports_flare-line-assessment_37_13.png
../_images/reports_flare-line-assessment_37_14.png
../_images/reports_flare-line-assessment_37_15.png
../_images/reports_flare-line-assessment_37_16.png
../_images/reports_flare-line-assessment_37_17.png
../_images/reports_flare-line-assessment_37_18.png

Compute the gradient of this \(G(T)\) ratio. Ideally, the ratio approaches 0 near the peak of the contribution function such that there is little temperature sensitivity near where the lines are strongest and thus any differences when taking a ratio are due to changes in abundance.

[118]:
for hf_row in high_fip_lines:#.group_by('ion name').groups:
    #hf_row = tab[np.argsort(tab['flare\_ext'])][-1]
    goft_hf_strongest = hf_row['contribution function']
    goft_hf_strongest #/= goft_hf_strongest.max()
    #plt.plot(temperature_grid, goft_hf_strongest, label=hf_row['ion name'])
    fig = plt.figure(figsize=(12,6))
    ax = fig.add_subplot()
    ax.set_title(f"{hf_row['ion name']} {hf_row['wavelength']:.3f}")
    ax2 = ax.twinx()
    ax2.plot(temperature_grid, goft_hf_strongest, color='k', lw=2,)
    for tab_lf in low_fip_lines.group_by('ion name').groups:
        lf_row = tab_lf[np.argsort(tab_lf['flare\_ext'])][-1]
        goft_lf_strongest = lf_row['contribution function']
        goft_lf_strongest #/= goft_lf_strongest.max()
        goft_ratio = goft_hf_strongest/goft_lf_strongest
        ax.plot(temperature_grid, np.gradient(np.log10(goft_ratio), np.log10(temperature_grid.value)),#goft_hf_strongest/goft_lf_strongest,
                label=f"{lf_row['ion name']} {lf_row['wavelength'].to_value('Angstrom'):.2f} Å",
                ls=el_linestyles[lf_row['ion name'].split()[0]]['linestyle'])
    #ax.set_yscale('log')
    ax.set_xscale('log')
    #ax.set_ylim(1e-3, 5)
    ax.set_ylim(-10,10)
    ax.set_xlim(temperature_grid[[0,-1]].value)
    ax.axhline(y=0, ls='--', color='k')
    ax.legend(loc='center right', bbox_to_anchor=(1.3,0.5))
../_images/reports_flare-line-assessment_39_0.png
../_images/reports_flare-line-assessment_39_1.png
../_images/reports_flare-line-assessment_39_2.png
../_images/reports_flare-line-assessment_39_3.png
../_images/reports_flare-line-assessment_39_4.png
../_images/reports_flare-line-assessment_39_5.png
../_images/reports_flare-line-assessment_39_6.png
../_images/reports_flare-line-assessment_39_7.png
../_images/reports_flare-line-assessment_39_8.png
../_images/reports_flare-line-assessment_39_9.png
../_images/reports_flare-line-assessment_39_10.png
../_images/reports_flare-line-assessment_39_11.png
../_images/reports_flare-line-assessment_39_12.png
../_images/reports_flare-line-assessment_39_13.png
../_images/reports_flare-line-assessment_39_14.png
../_images/reports_flare-line-assessment_39_15.png
../_images/reports_flare-line-assessment_39_16.png
../_images/reports_flare-line-assessment_39_17.png
../_images/reports_flare-line-assessment_39_18.png

Write the table to a format that enables easier reading. This is messy but is a convenient way to render the table as a nicely formatted PDF.

[20]:
import sys
import io
with io.StringIO() as _f:
    flare_snr_table.write(_f,
                          format='ascii.aastex',
                          caption=f'Predicted flare line intensities for MOXSI assuming a source is confined to a single pixel located at disk center at the center of the detector. Lines with an M1 intensity below {threshold:latex_inline} are not included. The flare\\_ext column refers to the CHIANTI flare\\_ext DEM. These intensities are calculated per line. Lines that share a pixel and have flare\\_ext below {max_threshold} of the maximum flare\\_ext in that pixel are also excluded.',
                          #latexdict={'tabletype':'longtable'},
                          formats={
                              'MOXSI pixel': '%d',
                              'wavelength':'%.3f',
                              'energy':'%.3f',
                              '$T_{max}$': '%.1f',
                              '$T_{max,G}$': '%.1f',
                              'IP': '%.1f',
                              r'flare\_ext': '%.3g',
                          #    r'active\_region': '%.3f',
                              **{k.upper(): '%.3f' for k in goes_class}
                          },
                          overwrite=True)
    table_string = _f.getvalue()
md_file = f"""---
title: "MOXSI Flare Lines"
author:
- name: Will Barnes
aastexopts: [singlecolumn]
---

\startlongtable
{table_string}
"""
with open('line-table.md', mode='w') as f:
    f.write(md_file)
!pandoc --template aastex63_template.tex line-table.md -o line-table.pdf

These were the lines that I manually selected based on their relative strength and the criteria that the lines should be reasonably well-separated in first order. Thes critieria were somewhat subjective, but represent a first cut at identifying the lines that have the most diagnostic potential.

[229]:
selected_lines = [
    ('Si XIV        6.180'),
    ('Si XIII       6.648'),
    ('Mg XII        7.106'),
    ('Mg XI 9.231'),
    ('Fe XXI        9.475'),
    ('Fe XIX        9.691'),
    ('Ni XXV        9.967'),
    ('Ne X  10.238'),
    ('Fe XXIV       10.619'),
    ('Fe XXIII      10.980'),
    ('Fe XXIII      11.737'),
    ('Fe XXII       11.767'),
    ('Ni XX 11.832'),
    ('Fe XIX        13.525'),
    ('Fe XX 13.545'),
    ('Ni XIX        14.040'),
    ('Fe XVIII      14.204'),
    ('Fe XVII       15.013'),
    ('Fe XVIII      16.072'),
    ('Fe XVII       17.051'),
    ('O VIII        18.967'),
    ('O VII 21.601'),
    ('N VII 24.779'),
    ('Si XI 43.762'),
    ('Mg X  44.050'),
    ('Si XII        44.160'),
    ('Fe XVI        50.361'),
    ('Si VIII       63.732'),
]
selected_lines = [l.split() for l in selected_lines]
selected_lines = [(f'{l[0]} {l[1]}', float(l[2])*u.AA) for l in selected_lines]
selected_table = []
for ion_name, wavelength in selected_lines:
    _tab = flare_snr_table[flare_snr_table['ion name'] == ion_name]
    _idx = np.argmin(np.fabs(_tab['wavelength']-wavelength))
    selected_table.append(_tab[_idx])
selected_table = astropy.table.vstack(selected_table,)
selected_table.meta = {}
#selected_table
selected_table[['ion name', 'wavelength', 'FIP', 'M1', 'M5', 'X5']].write(
    sys.stdout,
    format='ascii.fixed_width_no_header',
    formats={
        'MOXSI pixel': '%d',
        'wavelength':'%.3f',
        #'energy':'%.3f',
        #'$T_{max}$': '%.1f',
        #r'flare\_ext': '%.3g',
        **{k.upper(): '%.3f' for k in goes_class}
    },
)
WARNING: The key(s) {'MOXSI pixel'} specified in the formats argument do not match a column name. [astropy.io.ascii.ui]
|   Si XIV |  6.180 |  low | 0.191 |  0.773 |   5.959 |
|  Si XIII |  6.648 |  low | 0.389 |  1.699 |  16.016 |
|   Mg XII |  7.106 |  low | 0.166 |  0.718 |   6.601 |
|    Mg XI |  9.231 |  low | 0.326 |  1.380 |  13.309 |
|   Fe XXI |  9.475 |  low | 0.150 |  0.380 |   2.515 |
|   Fe XIX |  9.691 |  low | 0.112 |  0.467 |   4.468 |
|   Ni XXV |  9.967 |  low | 0.131 |  0.572 |   5.512 |
|     Ne X | 10.238 | high | 0.108 |  0.454 |   4.337 |
|  Fe XXIV | 10.619 |  low | 0.152 |  0.717 |   6.446 |
| Fe XXIII | 10.980 |  low | 0.174 |  0.600 |   3.470 |
| Fe XXIII | 11.737 |  low | 0.508 |  1.788 |  10.485 |
|  Fe XXII | 11.767 |  low | 0.176 |  0.422 |   1.695 |
|    Ni XX | 11.832 |  low | 0.105 |  0.437 |   4.166 |
|   Fe XIX | 13.525 |  low | 0.242 |  1.007 |   9.706 |
|    Fe XX | 13.545 |  low | 0.242 |  1.007 |   9.706 |
|   Ni XIX | 14.040 |  low | 0.516 |  2.369 |  23.403 |
| Fe XVIII | 14.204 |  low | 1.501 |  7.086 |  70.478 |
|  Fe XVII | 15.013 |  low | 6.078 | 25.602 | 247.163 |
| Fe XVIII | 16.072 |  low | 0.625 |  2.941 |  29.235 |
|  Fe XVII | 17.051 |  low | 5.158 | 20.753 | 197.723 |
|   O VIII | 18.967 | high | 0.556 |  0.839 |   4.025 |
|    O VII | 21.601 | high | 1.100 |  1.217 |   2.539 |
|    N VII | 24.779 | high | 0.147 |  0.198 |   0.772 |
|    Si XI | 43.762 |  low | 0.104 |  0.138 |   0.522 |
|     Mg X | 44.050 |  low | 0.101 |  0.195 |   1.250 |
|   Si XII | 44.160 |  low | 0.346 |  0.651 |   4.112 |
|   Fe XVI | 50.361 |  low | 0.138 |  0.284 |   1.948 |
|  Si VIII | 63.732 |  low | 0.109 |  0.189 |   1.086 |

Look at some contribution functions to assess the relative thermal sensitivity of some lines

[125]:
si_13 = fiasco.Ion('Si XIII', np.logspace(5,8,1000)*u.K)
si_12 = fiasco.Ion('Si XII', si_13.temperature)
ne_10 = fiasco.Ion('Ne X', si_13.temperature)
o_8 = fiasco.Ion('O VIII', si_13.temperature)
fe_16 = fiasco.Ion('Fe XVI', si_13.temperature)
fe_17 = fiasco.Ion('Fe XVII', si_13.temperature)
si_11 = fiasco.Ion('Si XI', si_13.temperature)
n_7 = fiasco.Ion('N VII', si_13.temperature)
ni_19 = fiasco.Ion('Ni XIX', si_13.temperature)
mg_11 = fiasco.Ion('Mg XI', si_13.temperature)
[126]:
#goft_si13 = si_13.contribution_function(1e9*u.cm**(-3))
#_idx = np.argmin(np.fabs(si_13.transitions.wavelength[~si_13.transitions.is_twophoton] - 6.6479*u.AA))
#goft_si13 = goft_si13.squeeze()[:,_idx]

goft_si11 = si_11.contribution_function(1e9*u.cm**(-3))
_idx = np.argmin(np.fabs(si_11.transitions.wavelength[~si_11.transitions.is_twophoton] - 49.176*u.AA))
goft_si11 = goft_si11.squeeze()[:,_idx]

goft_n7 = n_7.contribution_function(1e9*u.cm**(-3))
_idx = np.argmin(np.fabs(n_7.transitions.wavelength[~n_7.transitions.is_twophoton] - 24.779*u.AA))
goft_n7 = goft_n7.squeeze()[:,_idx]

goft_ni19 = ni_19.contribution_function(1e9*u.cm**(-3))
_idx = np.argmin(np.fabs(ni_19.transitions.wavelength[~ni_19.transitions.is_twophoton] - 14.040*u.AA))
goft_ni19 = goft_ni19.squeeze()[:,_idx]

# goft_si12 = si_12.contribution_function(1e9*u.cm**(-3))
# _idx = np.argmin(np.fabs(si_12.transitions.wavelength[~si_12.transitions.is_twophoton] - 44.16030*u.AA))
# goft_si12 = goft_si12.squeeze()[:,_idx]

goft_ne10 = ne_10.contribution_function(1e9*u.cm**(-3))
transitions = [10.23849, 12.13210,]*u.AA
_idx = [np.argmin(np.fabs(ne_10.transitions.wavelength[~ne_10.transitions.is_twophoton] - t)) for t in transitions]
goft_ne10 = goft_ne10.squeeze()[:,_idx]

goft_o8 = o_8.contribution_function(1e9*u.cm**(-3))
_idx = np.argmin(np.fabs(o_8.transitions.wavelength[~o_8.transitions.is_twophoton] - 18.9671*u.AA))
goft_o8 = goft_o8.squeeze()[:,_idx]

goft_mg11 = mg_11.contribution_function(1e9*u.cm**(-3))
_idx = np.argmin(np.fabs(mg_11.transitions.wavelength[~mg_11.transitions.is_twophoton] - 9.231*u.AA))
goft_mg11 = goft_mg11.squeeze()[:,_idx]

#goft_fe16 = fe_16.contribution_function(1e9*u.cm**(-3))
#_idx = np.argmin(np.fabs(fe_16.transitions.wavelength[~fe_16.transitions.is_twophoton] - 50.361*u.AA))
#goft_fe16 = goft_fe16.squeeze()[:,_idx]

#goft_fe17 = fe_17.contribution_function(1e9*u.cm**(-3))
#transitions = [15.013, 17.051,]*u.AA
#_idx = [np.argmin(np.fabs(fe_17.transitions.wavelength[~fe_17.transitions.is_twophoton] - t)) for t in transitions]
#goft_fe17 = goft_fe17.squeeze()[:,_idx]
WARNING: No proton data available for N 7. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Ni 19. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Ne 10. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for O 8. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
WARNING: No proton data available for Mg 11. Not including proton excitation and de-excitation in level populations calculation. [fiasco.ions]
[134]:
for _ion,_goft in [(ne_10, goft_ne10),
                   (o_8, goft_o8),
                   (fe_16,goft_fe16),
                   #(fe_17, goft_fe17),
                   (n_7, goft_n7),
                   #(si_11,goft_si11),
                   (si_12,goft_si12),
                   #(si_13,goft_si13),
                   #(ni_19,goft_ni19),
                   (mg_11,goft_mg11)]:
    l = plt.plot(_ion.temperature, _goft, label=_ion.ion_name_roman)
    if len(_goft.shape) == 1:
        _goft = _goft[:,np.newaxis]
    for i in range(_goft.shape[1]):
        plt.axvline(x=_ion.temperature[np.argmax(_goft[:,i])].to_value('K'), color=l[i].get_color(), ls=':')

plt.xscale('log')
#plt.yscale('log')
#plt.ylim(1e-27, 8e-24)
plt.xlim(1e6,4e7)
plt.legend()
[134]:
<matplotlib.legend.Legend at 0x356a34510>
../_images/reports_flare-line-assessment_47_1.png
[82]:
ions_unique = np.unique(flare_snr_table['ion name'])
[89]:
fig = plt.figure(figsize=(15,5))
ax = fig.add_subplot()
for i in ions_unique:
    _ion = fiasco.Ion(i, np.logspace(4,8,10000)*u.K)
    ax.plot(_ion.temperature, _ion.ioneq,)
    ax.text(_ion.formation_temperature.value, _ion.ioneq.max(), _ion.ion_name_roman)
plt.xscale('log')
plt.xlim(2e5,1e8)
[89]:
(200000.0, 100000000.0)
../_images/reports_flare-line-assessment_49_1.png

How does this compare to our previous curated line list?

[11]:
curated_line_list = astropy.table.QTable.read('../data/line_lists/curated_line_list.asdf')
[12]:
curated_line_list
[12]:
QTable length=32
ion namewavelengthT_max (Maxwellian)T_max (kappa=2)temperature diagnosticdensity diagnostickappa diagnosticsassmoxsiflare lineactive region line
AngstromKK
str8float64float64float64boolboolboolboolboolboolbool
Fe XXV1.8631622776.6016837931622776.60168379TrueFalseFalseTrueFalseTrueFalse
Ca XIX3.2125118864.31509582331622776.60168379TrueFalseFalseTrueFalseTrueFalse
Si XIV6.180416185305.906356486nanTrueFalseFalseTrueTrueTrueTrue
Si XIII6.7410000000.012589254.117941663TrueFalseFalseTrueTrueTrueFalse
Mg XII8.41910000000.015848931.924611142TrueFalseFalseTrueTrueTrueFalse
Mg XI9.1696309573.4448019310000000.0TrueFalseFalseFalseTrueTrueTrue
Mg XI9.3146309573.4448019310000000.0TrueFalseFalseFalseTrueTrueTrue
Fe XXIV11.17119952623.14968878831622776.60168379TrueFalseFalseTrueTrueTrueFalse
Fe XVII11.256309573.444801937943282.347242822TrueFalseTrueFalseTrueTrueTrue
Fe XXIII11.73715848931.92461114231622776.60168379TrueFalseFalseTrueTrueTrueFalse
Ne X12.1376309573.444801937943282.347242822TrueFalseFalseTrueTrueTrueTrue
Fe XX12.8310000000.015848931.924611142TrueFalseFalseFalseTrueTrueFalse
Ne IX13.4473981071.70553496955011872.336272725TrueFalseTrueFalseTrueTrueTrue
Fe XIX13.5310000000.015848931.924611142TrueFalseFalseFalseTrueTrueFalse
Ne IX13.6693981071.70553496955011872.336272725TrueFalseTrueFalseTrueTrueTrue
Fe XVIII14.2047943282.34724282210000000.0TrueFalseTrueFalseTrueTrueTrue
Fe XVII15.0156309573.444801937943282.347242822TrueFalseTrueFalseTrueTrueTrue
Fe XVII15.256309573.444801937943282.347242822TrueFalseTrueFalseTrueTrueTrue
O VIII16.0063162277.66016837953981071.7055349695TrueTrueTrueFalseTrueTrueTrue
Fe XVII16.786309573.444801937943282.347242822TrueFalseTrueFalseTrueTrueTrue
Fe XVII17.056309573.444801937943282.347242822TrueFalseTrueFalseTrueTrueTrue
Fe XVIII17.6217943282.34724282210000000.0TrueFalseTrueFalseTrueTrueTrue
O VII18.671995262.31496887892511886.4315095823TrueTrueTrueFalseTrueTrueTrue
O VIII18.9673162277.66016837953981071.7055349695TrueTrueTrueFalseTrueTrueTrue
O VII21.61995262.31496887892511886.4315095823TrueTrueTrueFalseTrueTrueTrue
O VII21.811995262.31496887892511886.4315095823TrueTrueTrueFalseTrueTrueTrue
O VII22.11995262.31496887892511886.4315095823TrueTrueTrueFalseTrueTrueTrue
N VII24.771995262.31496887893162277.6601683795TrueFalseFalseFalseTrueTrueTrue
C VI33.731258925.4117941661995262.3149688789TrueFalseFalseFalseTrueFalseTrue
C V40.271000000.01258925.411794166TrueFalseFalseFalseTrueFalseTrue
Si XII44.161995262.31496887893162277.6601683795TrueFalseFalseFalseTrueTrueTrue
Si XI49.181584893.19246111411995262.3149688789TrueFalseFalseFalseTrueFalseTrue

Do some visual checks

[20]:
blur = 40 * u.arcsec
[25]:
for gc in goes_class:
    fig = plt.figure(figsize=(20,4))
    ax = fig.add_subplot()
    flux = convolve_with_response(flare_spectra[gc], channel_o1, electrons=False, include_gain=False)
    flux = _blur_spectra(flux, blur / channel_o1.spatial_plate_scale[0] * channel_o1.spectral_plate_scale, channel_o1)
    ax.plot(flux.axis_world_coords(0)[0], flux.data)
    annotate_lines(ax, combined_line_list, flux, 'C1', rtol=0.0)
    ax.set_yscale('log')
    ax.set_xlim(1,20)
    ax.set_ylim(1e-3,flux.data.max()*5)
../_images/reports_flare-line-assessment_55_0.png
../_images/reports_flare-line-assessment_55_1.png
../_images/reports_flare-line-assessment_55_2.png
[ ]: