Example #3: non-equilibrium spectrum (Tvib, Trot, x_CO)

With the new fitting module introduced in fit_spectrum() function, and in the example of Tgas fitting using new fitting module, we can see its 1-temperature fitting performance for equilibrium condition.

This example features how new fitting module can fit non-equilibrium spectrum, with multiple fit parameters, such as vibrational/rotational temperatures, or mole fraction, etc.

This is a real fitting case introduced Grimaldi’s thesis: https://doi.org/10.2514/1.T6768 . This case features CO molecule emitting on a wide range of spectrum. This case also includes a user-defined trapezoid slit function, which is accounted by the distortion (dispersion) of the slit, as a result of the influences from experimental parameters and spectrometer dispersion parameters during the experiment.

  • plot3 fit Trot Tvib molfrac
  • plot3 fit Trot Tvib molfrac
======================= COMMENCE FITTING PROCESS =======================

Successfully retrieved the experimental data in 0.0007104873657226562s.

Acquired spectral quantity 'radiance' from the spectrum.
NaN values successfully purged.Number of data points left: 1479 points.
Successfully refined the experimental data in 0.0002532005310058594s.
0.19s - Loaded database

Commence fitting process for non-LTE spectrum!
/home/docs/checkouts/readthedocs.org/user_builds/radis/checkouts/latest/radis/misc/warning.py:434: PerformanceWarning: 'gu' was recomputed although 'gp' already in DataFrame. All values are equal
  warnings.warn(WarningType(message))
[[Fit Statistics]]
    # fitting method   = L-BFGS-B
    # function evals   = 96
    # data points      = 1
    # variables        = 3
    chi-square         = 4.1191e-21
    reduced chi-square = 4.1191e-21
    Akaike info crit   = -40.9386489
    Bayesian info crit = -46.9386489
##  Warning: uncertainties could not be estimated:
    this fitting method does not natively calculate uncertainties
    and numdifftools is not installed for lmfit to do this. Use
    `pip install numdifftools` for lmfit to estimate uncertainties
    with this fitting method.
[[Variables]]
    Tvib:           4019.23957 (init = 6000)
    Trot:           3213.32473 (init = 4000)
    mole_fraction:  0.06307478 (init = 0.05)

Successfully finished the fitting process in 7.707149982452393s.
/home/docs/checkouts/readthedocs.org/user_builds/radis/checkouts/latest/radis/misc/curve.py:240: UserWarning: Presence of NaN in curve_divide!
Think about interpolation=2
  warnings.warn(

======================== END OF FITTING PROCESS ========================


Residual history:

[np.float64(1.6884210236199504e-10), np.float64(1.6884210440381979e-10), np.float64(1.6884210060033468e-10), np.float64(1.6884210671365353e-10), np.float64(3.738560781412175e-10), np.float64(3.7385607749613685e-10), np.float64(3.7385607855679e-10), np.float64(3.7385607553763786e-10), np.float64(1.1128815372128413e-10), np.float64(1.1128815390646927e-10), np.float64(1.112881541942443e-10), np.float64(1.1128815294674758e-10), np.float64(1.0954119400145755e-10), np.float64(1.0954119450442979e-10), np.float64(1.0954119419457839e-10), np.float64(1.0954119402681508e-10), np.float64(1.0815468208401315e-10), np.float64(1.0815468267182474e-10), np.float64(1.0815468221710159e-10), np.float64(1.08154682280824e-10), np.float64(1.0308407647672229e-10), np.float64(1.0308407742635263e-10), np.float64(1.0308407637797923e-10), np.float64(1.0308407728109503e-10), np.float64(2.5622069956334375e-10), np.float64(2.562207030140033e-10), np.float64(2.562206993262903e-10), np.float64(2.562206988928061e-10), np.float64(9.533544922549783e-11), np.float64(9.53354504862476e-11), np.float64(9.533544899450552e-11), np.float64(9.533545030593561e-11), np.float64(8.48427735600552e-11), np.float64(8.484277492938125e-11), np.float64(8.484277332533733e-11), np.float64(8.484277453072342e-11), np.float64(2.942162855844573e-10), np.float64(2.9421628239932186e-10), np.float64(2.9421628625246667e-10), np.float64(2.942162845188683e-10), np.float64(7.545778695626082e-11), np.float64(7.545778797764293e-11), np.float64(7.545778688540757e-11), np.float64(7.545778741530068e-11), np.float64(6.932656171835516e-11), np.float64(6.932656034728585e-11), np.float64(6.932656236751499e-11), np.float64(6.932656036175411e-11), np.float64(7.949298283128666e-11), np.float64(7.949298422503194e-11), np.float64(7.949298256334672e-11), np.float64(7.949298374088136e-11), np.float64(6.81498435553155e-11), np.float64(6.81498430188567e-11), np.float64(6.814984397170331e-11), np.float64(6.814984272733105e-11), np.float64(6.719214857166717e-11), np.float64(6.71921481791797e-11), np.float64(6.719214886698425e-11), np.float64(6.719214790974426e-11), np.float64(6.549161955740076e-11), np.float64(6.549161954424184e-11), np.float64(6.549161943871277e-11), np.float64(6.54916193652463e-11), np.float64(6.525776504687181e-11), np.float64(6.525776505808518e-11), np.float64(6.525776491546529e-11), np.float64(6.525776489787045e-11), np.float64(6.423320929399242e-11), np.float64(6.42332091556419e-11), np.float64(6.423320930784754e-11), np.float64(6.423320919680103e-11), np.float64(6.418825495583594e-11), np.float64(6.418825503117023e-11), np.float64(6.418825493438653e-11), np.float64(6.418825499331539e-11), np.float64(6.418046441427161e-11), np.float64(6.418046440587178e-11), np.float64(6.41804644195429e-11), np.float64(6.41804644093244e-11), np.float64(6.418033839618103e-11), np.float64(6.418033839454034e-11), np.float64(6.418033839742535e-11), np.float64(6.418033839463614e-11), np.float64(6.418032597727522e-11), np.float64(6.418032597694697e-11), np.float64(6.418032597759614e-11), np.float64(6.418032597681513e-11), np.float64(6.418032367206344e-11), np.float64(6.418032367209917e-11), np.float64(6.418032367204627e-11), np.float64(6.418032367207511e-11), np.float64(6.418032366726698e-11), np.float64(6.418032366727002e-11), np.float64(6.41803236672653e-11), np.float64(6.418032366726851e-11), np.float64(6.418032366726698e-11)]

Fitted values history:

[6000.0, 4000.0, 0.05]
[6000.00002, 4000.0, 0.05]
[6000.0, 4000.000024494898, 0.05]
[6000.0, 4000.0, 0.0500000005]
[5105.589014486926, 4855.661714738742, 0.012435519427667975]
[5105.589038742363, 4855.661714738742, 0.012435519427667975]
[5105.589014486926, 4855.661739484458, 0.012435519427667975]
[5105.589014486926, 4855.661714738742, 0.012435519757654313]
[5818.190228435902, 4187.474367518324, 0.04066587313976969]
[5818.190249678251, 4187.474367518324, 0.04066587313976969]
[5818.190228435902, 4187.4743923222095, 0.04066587313976969]
[5818.190228435902, 4187.474367518324, 0.040665873630979836]
[5807.164848659225, 4147.48038979007, 0.041997575235936876]
[5807.164869969598, 4147.48038979007, 0.041997575235936876]
[5807.164848659225, 4147.480414540282, 0.041997575235936876]
[5807.164848659225, 4147.48038979007, 0.04199757572949145]
[5757.359686017544, 4111.453799922989, 0.04251231193185258]
[5757.359707625521, 4111.453799922989, 0.04251231193185258]
[5757.359686017544, 4111.453824619207, 0.04251231193185258]
[5757.359686017544, 4111.453799922989, 0.04251231242621424]
[5551.640562063738, 3968.2486675433383, 0.04457882915299982]
[5551.64058474424, 3968.2486675433383, 0.04457882915299982]
[5551.640562063738, 3968.248691971274, 0.04457882915299982]
[5551.640562063738, 3968.2486675433383, 0.04457882965005222]
[2363.2518437831704, 2028.035310886635, 0.0881500560958061]
[2363.2518308050835, 2028.035310886635, 0.0881500560958061]
[2363.2518437831704, 2028.0353071531306, 0.0881500560958061]
[2363.2518437831704, 2028.035310886635, 0.08815005641900468]
[5270.46023593549, 3795.36469150175, 0.046884234996570164]
[5270.4602597186595, 3795.36469150175, 0.046884234996570164]
[5270.46023593549, 3795.364715488182, 0.046884234996570164]
[5270.46023593549, 3795.36469150175, 0.04688423549559842]
[4949.513430682526, 3610.0314990484503, 0.0494185411328201]
[4949.5134552750815, 3610.0314990484503, 0.0494185411328201]
[4949.513430682526, 3610.031522410719, 0.0494185411328201]
[4949.513430682526, 3610.0314990484503, 0.049418541632786285]
[2440.7348269315166, 2341.0416722030936, 0.07075974094128255]
[2440.7348411069424, 2341.0416722030936, 0.07075974094128255]
[2440.7348269315166, 2341.041684808247, 0.07075974094128255]
[2440.7348269315166, 2341.0416722030936, 0.07075974139614881]
[4677.684247042535, 3460.717135614628, 0.05151738666294371]
[4677.684271979313, 3460.717135614628, 0.05151738666294371]
[4677.684247042535, 3460.7171583520226, 0.05151738666294371]
[4677.684247042535, 3460.717135614628, 0.05151738716271342]
[4198.138537235358, 3209.915406743408, 0.05518699273191109]
[4198.138562052449, 3209.915406743408, 0.05518699273191109]
[4198.138537235358, 3209.915428157613, 0.05518699273191109]
[4198.138537235358, 3209.915406743408, 0.055186993229213326]
[4786.938383930233, 3491.189773422493, 0.050731820854976586]
[4786.93840876502, 3491.189773422493, 0.050731820854976586]
[4786.938383930233, 3491.1897962967187, 0.050731820854976586]
[4786.938383930233, 3491.189773422493, 0.050731821354923025]
[4316.412798024692, 3265.031847683382, 0.05429200564275049]
[4316.412822957192, 3265.031847683382, 0.05429200564275049]
[4316.412798024692, 3265.0318694201105, 0.05429200564275049]
[4316.412798024692, 3265.031847683382, 0.05429200614090495]
[4284.404479920046, 3228.845901782738, 0.05493288400739585]
[4284.404504826909, 3228.845901782738, 0.05493288400739585]
[4284.404479920046, 3228.8459233098542, 0.05493288400739585]
[4284.404479920046, 3228.845901782738, 0.054932884504956564]
[4145.561172586466, 3098.5223656113867, 0.05762044917567394]
[4145.561197333936, 3098.5223656113867, 0.05762044917567394]
[4145.561172586466, 3098.522386313707, 0.05762044917567394]
[4145.561172586466, 3098.5223656113867, 0.0576204496698327]
[4126.632862305605, 3101.825182110372, 0.05824217878616823]
[4126.632887025227, 3101.825182110372, 0.05824217878616823]
[4126.632862305605, 3101.8252028350125, 0.05824217878616823]
[4126.632862305605, 3101.825182110372, 0.0582421792793281]
[4017.831626970656, 3184.5833354398346, 0.06249112747735987]
[4017.831651501277, 3184.5833354398346, 0.06249112747735987]
[4017.831626970656, 3184.5833566993715, 0.06249112747735987]
[4017.831626970656, 3184.5833354398346, 0.06249112796150568]
[4030.6305844299595, 3216.4136047497223, 0.06286712013815833]
[4030.6306089853915, 3216.4136047497223, 0.06286712013815833]
[4030.6305844299595, 3216.4136262029415, 0.06286712013815833]
[4030.6305844299595, 3216.4136047497223, 0.06286712062131848]
[4019.035314678171, 3214.7623983051985, 0.06308862400585541]
[4019.035339211154, 3214.7623983051985, 0.06308862400585541]
[4019.035314678171, 3214.7624197485293, 0.06308862400585541]
[4019.035314678171, 3214.7623983051985, 0.06308862448842022]
[4019.701570616155, 3213.6181185448568, 0.06306124382660912]
[4019.701595150444, 3213.6181185448568, 0.06306124382660912]
[4019.701570616155, 3213.618139981325, 0.06306124382660912]
[4019.701570616155, 3213.6181185448568, 0.0630612443092481]
[4019.4652564456005, 3213.397863673256, 0.06306791743856699]
[4019.465280979427, 3213.397863673256, 0.06306791743856699]
[4019.4652564456005, 3213.397885108402, 0.06306791743856699]
[4019.4652564456005, 3213.397863673256, 0.06306791792118792]
[4019.248506097908, 3213.3204797019584, 0.06307448349188255]
[4019.24853063131, 3213.3204797019584, 0.06307448349188255]
[4019.248506097908, 3213.3205011366404, 0.06307448349188255]
[4019.248506097908, 3213.3204797019584, 0.06307448397448569]
[4019.2395650969092, 3213.3247316929546, 0.06307478479743257]
[4019.2395896302933, 3213.3247316929546, 0.06307478479743257]
[4019.2395650969092, 3213.324753127662, 0.06307478479743257]
[4019.2395650969092, 3213.3247316929546, 0.06307478528003489]
[4019.2395650969092, 3213.3247316929546, 0.06307478479743257]

Total fitting time:
7.707149982452393 s

import astropy.units as u
import numpy as np

from radis import load_spec
from radis.test.utils import getTestFile
from radis.tools.new_fitting import fit_spectrum

# The following function and parameters reproduce the slit function in Dr. Corenti Grimaldi's thesis.


def slit_dispersion(w):
    phi = -6.33
    f = 750
    gr = 300
    m = 1
    phi *= -2 * np.pi / 360
    d = 1e-3 / gr
    disp = (
        w
        / (2 * f)
        * (-np.tan(phi) + np.sqrt((2 * d / m / (w * 1e-9) * np.cos(phi)) ** 2 - 1))
    )
    return disp  # nm/mm


slit = 1500  # µm
pitch = 20  # µm
top_slit_um = slit - pitch  # µm
base_slit_um = slit + pitch  # µm
center_slit = 5090
dispersion = slit_dispersion(center_slit)
top_slit_nm = top_slit_um * 1e-3 * dispersion
base_slit_nm = base_slit_um * 1e-3 * dispersion * 1.33


# -------------------- Step 1. Load experimental spectrum -------------------- #


specName = (
    "Corentin_0_100cm_DownSampled_20cm_10pctCO2_1-wc-gw450-gr300-sl1500-acc5000-.spec"
)
s_experimental = load_spec(getTestFile(specName)).offset(-0.6, "nm")


# -------------------- Step 2. Fill ground-truths and data -------------------- #


# Experimental conditions which will be used for spectrum modeling. Basically, these are known ground-truths.
experimental_conditions = {
    "molecule": "CO",  # Molecule ID
    "isotope": "1,2,3",  # Isotope ID, can have multiple at once
    "wmin": 2270
    * u.nm,  # Starting wavelength/wavenumber to be cropped out from the original experimental spectrum.
    "wmax": 2400 * u.nm,  # Ending wavelength/wavenumber for the cropping range.
    "pressure": 1.01325
    * u.bar,  # Total pressure of gas, in "bar" unit by default, but you can also use Astropy units.
    "path_length": 1
    / 195
    * u.cm,  # Experimental path length, in "cm" unit by default, but you can also use Astropy units.
    "slit": {  # Experimental slit. In simple form: "[value] [unit]", i.e. "-0.2 nm". In complex form: a dict with parameters of apply_slit()
        "slit_function": (top_slit_nm, base_slit_nm),
        "unit": "nm",
        "shape": "trapezoidal",
        "center_wavespace": center_slit,
        "slit_dispersion": slit_dispersion,
        "inplace": False,
    },
    "cutoff": 0,  # (RADIS native) Discard linestrengths that are lower that this to reduce calculation time, in cm-1.
    "databank": "hitemp",  # Databank used for calculation. Must be stated.
}

# List of parameters to be fitted, accompanied by its initial value
fit_parameters = {
    "Tvib": 6000,  # Vibrational temperature, in K.
    "Trot": 4000,  # Rotational temperature, in K.
    "mole_fraction": 0.05,  # Species mole fraction, from 0 to 1.
}

# List of bounding ranges applied for those fit parameters above.
# You can skip this step and let it use default bounding ranges, but this is not recommended.
# Bounding range must be at format [<lower bound>, <upper bound>].
bounding_ranges = {
    "Tvib": [2000, 7000],
    "Trot": [2000, 7000],
    "mole_fraction": [0, 0.1],
}

# Fitting pipeline setups.
fit_properties = {
    "method": "lbfgsb",  # Preferred fitting method. By default, "leastsq".
    "fit_var": "radiance",  # Spectral quantity to be extracted for fitting process, such as "radiance", "absorbance", etc.
    "normalize": False,  # Either applying normalization on both spectra or not.
    "max_loop": 300,  # Max number of loops allowed. By default, 200.
    "tol": 1e-20,  # Fitting tolerance, only applicable for "lbfgsb" method.
}

"""

For the fitting method, you can try one among 17 different fitting methods and algorithms of LMFIT,
introduced in `LMFIT method list <https://lmfit.github.io/lmfit-py/fitting.html#choosing-different-fitting-methods>`.

You can see the benchmark result of these algorithms here:
`RADIS Newfitting Algorithm Benchmark <https://github.com/radis/radis-benchmark/blob/master/manual_benchmarks/plot_newfitting_comparison_algorithm.py>`.

"""


# -------------------- Step 3. Run the fitting and retrieve results -------------------- #

# Conduct the fitting process!
s_best, result, log = fit_spectrum(
    s_exp=s_experimental,  # Experimental spectrum.
    fit_params=fit_parameters,  # Fit parameters.
    bounds=bounding_ranges,  # Bounding ranges for those fit parameters.
    model=experimental_conditions,  # Experimental ground-truths conditions.
    pipeline=fit_properties,  # # Fitting pipeline references.
)

# Now investigate the result logs for additional information about what's going on during the fitting process

print("\nResidual history: \n")
print(log["residual"])

print("\nFitted values history: \n")
for fit_val in log["fit_vals"]:
    print(fit_val)

print("\nTotal fitting time: ")
print(log["time_fitting"], end=" s\n")

Total running time of the script: (0 minutes 8.751 seconds)