Fitting NASA-7 polynomials from data
This notebook shows how to generate NASA-7 coefficients from tabulated \(C_p(T)\) data using pyglenn — the reverse of the usual read-only workflow.
Two functions are covered:
fit_nasa7— fit a single temperature interval;fit_nasa7_multi— fit several intervals with continuity of \(C_p\) and its first derivative at each breakpoint.
Both honor the NASA-7 convention H°(298.15) = h_ref (the enthalpy
integration constant is b1, not a6).
import numpy as np
from pyglenn import (
NASACoefficients,
R_GLENN,
ThermoDBQuery,
fit_nasa7,
fit_nasa7_multi,
)
print('pyglenn imported successfully')
pyglenn imported successfully
Synthetic reference data
We generate “measurements” from a known NASA-7 polynomial (the real O₂ low-temperature coefficients) so we can check how well the fit recovers the source.
# Real O2 NASA-7 coefficients (200-1000 K interval, from the bundled database).
TRUE = NASACoefficients(
a1=-34255.6342,
a2=484.700097,
a3=1.119010961,
a4=0.00429388924,
a5=-6.83630052e-07,
a6=-2.0233727e-09,
a7=1.039040018e-12,
b1=-3391.45487,
b2=18.4969947,
)
def cp(coeffs, T):
return ThermoDBQuery.calculate_cp(coeffs, T) * R_GLENN
def h(coeffs, T):
return ThermoDBQuery.calculate_h(coeffs, T) * R_GLENN * T
T = np.linspace(200.0, 1000.0, 50)
cp_meas = np.array([cp(TRUE, t) for t in T])
print(f'{len(T)} points from {T[0]:.1f} to {T[-1]:.1f} K')
print(f'Cp(298.15 K) = {cp(TRUE, 298.15):.3f} J/(mol·K)')
50 points from 200.0 to 1000.0 K
Cp(298.15 K) = 29.378 J/(mol·K)
Single-interval fit
fit_nasa7 recovers the seven heat-capacity coefficients and anchors the
enthalpy so that H°(t_ref) = h_ref exactly.
h_ref = h(TRUE, 298.15)
fitted = fit_nasa7(T, cp_meas, t_ref=298.15, h_ref=h_ref)
print('fitted a1..a7 vs true:')
for name in ('a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7'):
true = getattr(TRUE, name)
got = getattr(fitted, name)
print(f' {name:>3}: {got: .8e} (true {true: .8e})')
fitted a1..a7 vs true:
a1: -3.42556342e+04 (true -3.42556342e+04)
a2: 4.84700097e+02 (true 4.84700097e+02)
a3: 1.11901096e+00 (true 1.11901096e+00)
a4: 4.29388924e-03 (true 4.29388924e-03)
a5: -6.83630052e-07 (true -6.83630052e-07)
a6: -2.02337270e-09 (true -2.02337270e-09)
a7: 1.03904002e-12 (true 1.03904002e-12)
cp_fit = np.array([cp(fitted, t) for t in T])
max_rel_err = float(np.max(np.abs(cp_fit - cp_meas) / np.abs(cp_meas)))
print(f'max relative Cp error : {max_rel_err:.2e}')
print(f'H°(298.15) fit vs true : {h(fitted, 298.15):.6f} vs {h_ref:.6f} J/mol')
max relative Cp error : 6.40e-16
H°(298.15) fit vs true : -0.000013 vs -0.000013 J/mol
Multi-interval fit with continuity
fit_nasa7_multi fits one coefficient set per interval and enforces
continuity of \(C_p\) and its first derivative at each breakpoint
(e.g. 1000 K), with \(H°\) continuous across intervals.
T_wide = np.linspace(200.0, 6000.0, 300)
cp_wide = np.array([cp(TRUE, t) for t in T_wide])
low, high = fit_nasa7_multi(
T_wide, cp_wide, breakpoints=[1000.0], t_ref=298.15, h_ref=h_ref
)
print('interval 0 (200-1000 K):', low)
print('interval 1 (1000-6000 K):', high)
interval 0 (200-1000 K): NASACoefficients(a1=-34255.634198868036, a2=484.7000969860279, a3=1.119010961067088, a4=0.004293889239839271, a5=-6.836300517947506e-07, a6=-2.0233727001341847e-09, a7=1.039040018035353e-12, b1=-3391.4548699310194, b2=0.0)
interval 1 (1000-6000 K): NASACoefficients(a1=-34255.634201091336, a2=484.7000970025349, a3=1.1190109609975611, a4=0.004293889240001214, a5=-6.836300520003201e-07, a6=-2.023372699999958e-09, a7=1.0390400179999982e-12, b1=-3391.454870016678, b2=0.0)
Tb = 1000.0
def dcp_dt(coeffs, T):
a = coeffs
return (
-2 * a.a1 / T**3
- a.a2 / T**2
+ a.a4
+ 2 * a.a5 * T
+ 3 * a.a6 * T**2
+ 4 * a.a7 * T**3
) * R_GLENN
print(f'Cp left vs right : {cp(low, Tb):.6f} vs {cp(high, Tb):.6f} J/(mol·K)')
print(f'dCp left vs right : {dcp_dt(low, Tb):.6f} vs {dcp_dt(high, Tb):.6f} J/(mol·K²)')
print(f'H° left vs right : {h(low, Tb):.3f} vs {h(high, Tb):.3f} J/mol')
Cp left vs right : 34.882545 vs 34.882545 J/(mol·K)
dCp left vs right : 0.004959 vs 0.004959 J/(mol·K²)
H° left vs right : 22707.211 vs 22707.211 J/mol
Summary
fit_nasa7reproduces the reference \(C_p(T)\) to near machine precision and anchors \(H°(298.15)\) exactly.fit_nasa7_multiextends the same to several intervals, with \(C_p\) and its derivative continuous at every breakpoint and \(H°\) continuous across intervals.NASA datasets store one coefficient set per interval — this is exactly how the multiple intervals in
thermo.inpare produced.