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.0007793903350830078s.

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.

Commence fitting process for non-LTE spectrum!
/home/docs/checkouts/readthedocs.org/user_builds/radis/checkouts/master/radis/misc/warning.py:427: 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.23955 (init = 6000)
    Trot:           3213.32472 (init = 4000)
    mole_fraction:  0.06307479 (init = 0.05)

Successfully finished the fitting process in 7.122132062911987s.
/home/docs/checkouts/readthedocs.org/user_builds/radis/checkouts/master/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.6884210236199506e-10), np.float64(1.6884210440381984e-10), np.float64(1.6884210060033468e-10), np.float64(1.6884210671365353e-10), np.float64(3.738560779482434e-10), np.float64(3.738560773031628e-10), np.float64(3.7385607836381597e-10), np.float64(3.738560753446639e-10), np.float64(1.1128815360527545e-10), np.float64(1.1128815379046055e-10), np.float64(1.1128815407823561e-10), np.float64(1.1128815283073891e-10), np.float64(1.0954119407728126e-10), np.float64(1.095411945802535e-10), np.float64(1.0954119427040208e-10), np.float64(1.0954119410263883e-10), np.float64(1.081546823623992e-10), np.float64(1.0815468295021072e-10), np.float64(1.0815468249548762e-10), np.float64(1.0815468255921006e-10), np.float64(1.0308407717343896e-10), np.float64(1.0308407812306925e-10), np.float64(1.0308407707469599e-10), np.float64(1.0308407797781155e-10), np.float64(2.5621875733211255e-10), np.float64(2.562187607828064e-10), np.float64(2.5621875709505406e-10), np.float64(2.5621875666157347e-10), np.float64(9.533543779641823e-11), np.float64(9.533543905716837e-11), np.float64(9.53354375654258e-11), np.float64(9.533543887685615e-11), np.float64(8.484273770465598e-11), np.float64(8.484273907398177e-11), np.float64(8.484273746993825e-11), np.float64(8.48427386753232e-11), np.float64(2.9421697749802616e-10), np.float64(2.942169743129064e-10), np.float64(2.942169781660324e-10), np.float64(2.942169764324424e-10), np.float64(7.54577676798936e-11), np.float64(7.545776870127448e-11), np.float64(7.545776760904074e-11), np.float64(7.545776813893211e-11), np.float64(6.932653661965616e-11), np.float64(6.932653524859567e-11), np.float64(6.932653726881375e-11), np.float64(6.932653526306025e-11), np.float64(7.949285617556162e-11), np.float64(7.94928575693033e-11), np.float64(7.94928559076233e-11), np.float64(7.949285708515032e-11), np.float64(6.814984172976307e-11), np.float64(6.814984119330466e-11), np.float64(6.814984214615076e-11), np.float64(6.814984090177898e-11), np.float64(6.719214631439374e-11), np.float64(6.719214592190663e-11), np.float64(6.719214660971042e-11), np.float64(6.719214565247127e-11), np.float64(6.549162026237408e-11), np.float64(6.549162024921514e-11), np.float64(6.549162014368664e-11), np.float64(6.549162007021926e-11), np.float64(6.525776580881548e-11), np.float64(6.525776582002855e-11), np.float64(6.525776567740905e-11), np.float64(6.525776565981386e-11), np.float64(6.42332113641256e-11), np.float64(6.423321122577801e-11), np.float64(6.423321137797768e-11), np.float64(6.423321126693566e-11), np.float64(6.418825478779295e-11), np.float64(6.418825486312706e-11), np.float64(6.418825476634321e-11), np.float64(6.41882548252725e-11), np.float64(6.418046442464562e-11), np.float64(6.41804644162452e-11), np.float64(6.418046442991702e-11), np.float64(6.418046441969829e-11), np.float64(6.41803383933946e-11), np.float64(6.418033839175417e-11), np.float64(6.418033839463874e-11), np.float64(6.418033839184987e-11), np.float64(6.418032597713388e-11), np.float64(6.418032597680558e-11), np.float64(6.418032597745491e-11), np.float64(6.418032597667379e-11), np.float64(6.418032367206444e-11), np.float64(6.418032367210007e-11), np.float64(6.418032367204727e-11), np.float64(6.418032367207604e-11), np.float64(6.418032366726713e-11), np.float64(6.418032366727015e-11), np.float64(6.418032366726541e-11), np.float64(6.418032366726866e-11), np.float64(6.418032366726713e-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.589010335032, 4855.661718393352, 0.012435519473900064]
[5105.589034590468, 4855.661718393352, 0.012435519473900064]
[5105.589010335032, 4855.66174313907, 0.012435519473900064]
[5105.589010335032, 4855.661718393352, 0.012435519803886397]
[5818.190228771631, 4187.474367180093, 0.04066587321110825]
[5818.190250013982, 4187.474367180093, 0.04066587321110825]
[5818.190228771631, 4187.47439198398, 0.04066587321110825]
[5818.190228771631, 4187.474367180093, 0.04066587370231839]
[5807.1648517841895, 4147.480389673976, 0.0419975752938115]
[5807.164873094562, 4147.480389673976, 0.0419975752938115]
[5807.1648517841895, 4147.480414424188, 0.0419975752938115]
[5807.1648517841895, 4147.480389673976, 0.04199757578736607]
[5757.359696234136, 4111.453804699595, 0.042512311868348265]
[5757.359717842112, 4111.453804699595, 0.042512311868348265]
[5757.359696234136, 4111.453829395813, 0.042512311868348265]
[5757.359696234136, 4111.453804699595, 0.04251231236270993]
[5551.640602378711, 3968.248691625253, 0.044578828600610916]
[5551.640625059214, 3968.248691625253, 0.044578828600610916]
[5551.640602378711, 3968.2487160531887, 0.044578828600610916]
[5551.640602378711, 3968.248691625253, 0.044578829097663315]
[2363.258906498787, 2028.0365503627763, 0.08815012505490516]
[2363.258893520584, 2028.0365503627763, 0.08815012505490516]
[2363.258906498787, 2028.0365466291896, 0.08815012505490516]
[2363.258906498787, 2028.0365503627763, 0.08815012537810292]
[5270.459855349492, 3795.364451309296, 0.046884237967710636]
[5270.459879132662, 3795.364451309296, 0.046884237967710636]
[5270.459855349492, 3795.3644752957266, 0.046884237967710636]
[5270.459855349492, 3795.364451309296, 0.04688423846673889]
[4949.512393747401, 3610.0308913631948, 0.049418549258804734]
[4949.5124183399585, 3610.0308913631948, 0.049418549258804734]
[4949.512393747401, 3610.0309147254616, 0.049418549258804734]
[4949.512393747401, 3610.0308913631948, 0.04941854975877092]
[2440.730670951443, 2341.0394159153043, 0.07075979316396933]
[2440.7306851268086, 2341.0394159153043, 0.07075979316396933]
[2440.730670951443, 2341.0394285204193, 0.07075979316396933]
[2440.730670951443, 2341.0394159153043, 0.07075979361883536]
[4677.68364067358, 3460.716773535213, 0.05151739148765927]
[4677.683665610357, 3460.716773535213, 0.05151739148765927]
[4677.68364067358, 3460.716796272606, 0.05151739148765927]
[4677.68364067358, 3460.716773535213, 0.051517391987428975]
[4198.139731846036, 3209.9159649686617, 0.05518698394224544]
[4198.139756663128, 3209.9159649686617, 0.05518698394224544]
[4198.139731846036, 3209.91598638287, 0.05518698394224544]
[4198.139731846036, 3209.9159649686617, 0.055186984439547684]
[4786.934731711908, 3491.1877412664817, 0.0507318508130918]
[4786.9347565467, 3491.1877412664817, 0.0507318508130918]
[4786.934731711908, 3491.187764140698, 0.0507318508130918]
[4786.934731711908, 3491.1877412664817, 0.05073185131303823]
[4316.412760333622, 3265.0317905576635, 0.054292006570630526]
[4316.412785266122, 3265.0317905576635, 0.054292006570630526]
[4316.412760333622, 3265.031812294392, 0.054292006570630526]
[4316.412760333622, 3265.0317905576635, 0.05429200706878499]
[4284.4043846550685, 3228.8457755549384, 0.05493288553095069]
[4284.404409561932, 3228.8457755549384, 0.05493288553095069]
[4284.4043846550685, 3228.8457970820537, 0.05493288553095069]
[4284.4043846550685, 3228.8457755549384, 0.05493288602851139]
[4145.561510048648, 3098.522621578824, 0.05762044312738075]
[4145.561534796119, 3098.522621578824, 0.05762044312738075]
[4145.561510048648, 3098.5226422811456, 0.05762044312738075]
[4145.561510048648, 3098.522621578824, 0.05762044362153951]
[4126.632888218992, 3101.825145312422, 0.05824217707545718]
[4126.632912938614, 3101.825145312422, 0.05824217707545718]
[4126.632888218992, 3101.8251660370624, 0.05824217707545718]
[4126.632888218992, 3101.825145312422, 0.05824217756861705]
[4017.831776104892, 3184.582033973548, 0.06249110875070785]
[4017.831800635513, 3184.582033973548, 0.06249110875070785]
[4017.831776104892, 3184.582055233076, 0.06249110875070785]
[4017.831776104892, 3184.582033973548, 0.06249110923485371]
[4030.630355300628, 3216.4133792192188, 0.06286712412691618]
[4030.6303798560593, 3216.4133792192188, 0.06286712412691618]
[4030.630355300628, 3216.413400672436, 0.06286712412691618]
[4030.630355300628, 3216.4133792192188, 0.0628671246100763]
[4019.035074189604, 3214.762390960352, 0.06308863084988077]
[4019.0350987225875, 3214.762390960352, 0.06308863084988077]
[4019.035074189604, 3214.762412403683, 0.06308863084988077]
[4019.035074189604, 3214.762390960352, 0.06308863133244556]
[4019.7015375870724, 3213.618094126655, 0.06306124494443911]
[4019.7015621213613, 3213.618094126655, 0.06306124494443911]
[4019.7015375870724, 3213.618115563123, 0.06306124494443911]
[4019.7015375870724, 3213.618094126655, 0.0630612454270781]
[4019.4652380984885, 3213.397911448943, 0.06306791859457209]
[4019.4652626323145, 3213.397911448943, 0.06306791859457209]
[4019.4652380984885, 3213.3979328840896, 0.06306791859457209]
[4019.4652380984885, 3213.397911448943, 0.063067919077193]
[4019.248493923078, 3213.3204521369044, 0.06307448334325043]
[4019.2485184564794, 3213.3204521369044, 0.06307448334325043]
[4019.248493923078, 3213.320473571586, 0.06307448334325043]
[4019.248493923078, 3213.3204521369044, 0.06307448382585355]
[4019.2395520501627, 3213.3247220926482, 0.06307478507965011]
[4019.239576583547, 3213.3247220926482, 0.06307478507965011]
[4019.2395520501627, 3213.3247435273556, 0.06307478507965011]
[4019.2395520501627, 3213.3247220926482, 0.06307478556225243]
[4019.2395520501627, 3213.3247220926482, 0.06307478507965011]

Total fitting time:
7.122132062911987 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.194 seconds)