fit_static_nonlin
Overview
fit_static_nonlin extracts static nonlinearity coefficients (k2, k3) from a distorted sine wave signal. This quantifies the 2nd-order and 3rd-order nonlinearity of an ADC’s transfer function, which are the primary sources of harmonic distortion.
Syntax
from adctoolbox import fit_static_nonlin
# Extract quadratic nonlinearity only
k2, k3, fitted_sine, fitted_transfer = fit_static_nonlin(distorted_signal, order=2)
# Extract both quadratic and cubic
k2, k3, fitted_sine, fitted_transfer = fit_static_nonlin(distorted_signal, order=3)
# Higher order polynomial (advanced)
k2, k3, fitted_sine, fitted_transfer = fit_static_nonlin(distorted_signal, order=5)
Parameters
sig_distorted(array_like) — Distorted sine wave signal samplesorder(int) — Polynomial order for fitting (typically 2-3)order=2: Quadratic nonlinearity only (k2)
order=3: Quadratic + cubic nonlinearity (k2, k3)
order>3: Higher-order terms (advanced use)
Returns
Tuple containing:
k2_extracted(float) — Quadratic nonlinearity coefficientFor ideal ADC: k2 = 0
Represents 2nd-order distortion (HD2)
Returns NaN if order < 2
k3_extracted(float) — Cubic nonlinearity coefficientFor ideal ADC: k3 = 0
Represents 3rd-order distortion (HD3)
Returns NaN if order < 3
fitted_sine(array) — Fitted ideal sine wave input (reference signal)Same length as sig_distorted
This is the ideal sine extracted from distorted signal
fitted_transfer(tuple) — Fitted transfer curve for plotting(x, y) where x is 1000 smooth input points, y is output
For ideal system: y = x (straight line)
Transfer Function Model
The ADC transfer function is modeled as:
y = x + k2·x² + k3·x³
where:
x = ideal input (zero-mean sine)
y = actual output (zero-mean)
k2 = 2nd-order nonlinearity coefficient
k3 = 3rd-order nonlinearity coefficient
Algorithm
# 1. Fit sine to get ideal input reference
result = fit_sine_4param(sig_distorted)
fitted_sine = result['fitted_signal']
# 2. Remove DC, normalize
x = fitted_sine - mean(fitted_sine)
y = sig_distorted - mean(sig_distorted)
# 3. Construct polynomial basis
# For order=3: [x, x², x³]
A = np.column_stack([x**i for i in range(1, order+1)])
# 4. Least-squares solve: A × coeffs = y
coeffs = np.linalg.lstsq(A, y)[0]
# 5. Extract k2, k3
k2 = coeffs[1] if order >= 2 else np.nan
k3 = coeffs[2] if order >= 3 else np.nan
Examples
Example 1: Extract Nonlinearity Coefficients
import numpy as np
from adctoolbox import fit_static_nonlin
# Analyze distorted signal
k2, k3, fitted_sine, fitted_transfer = fit_static_nonlin(distorted_signal, order=3)
print(f"2nd-order coefficient k2: {k2:.6f}")
print(f"3rd-order coefficient k3: {k3:.6f}")
# Interpret
if abs(k2) > abs(k3):
print("Dominant: 2nd-order (even) nonlinearity")
else:
print("Dominant: 3rd-order (odd) nonlinearity")
Example 2: Plot Nonlinearity Curve
import matplotlib.pyplot as plt
k2, k3, fitted_sine, fitted_transfer = fit_static_nonlin(signal, order=3)
transfer_x, transfer_y = fitted_transfer
# Plot transfer function
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# Transfer curve
ax1.plot(transfer_x, transfer_y, 'b-', linewidth=2, label='Actual')
ax1.plot(transfer_x, transfer_x, 'k--', linewidth=1, label='Ideal')
ax1.set_xlabel('Input (V)')
ax1.set_ylabel('Output (V)')
ax1.set_title('Transfer Function')
ax1.legend()
ax1.grid(True)
# Nonlinearity error
ax2.plot(transfer_x, transfer_y - transfer_x, 'r-', linewidth=2)
ax2.set_xlabel('Input (V)')
ax2.set_ylabel('Nonlinearity Error (V)')
ax2.set_title(f'Static Nonlinearity\nk2={k2:.6f}, k3={k3:.6f}')
ax2.grid(True)
ax2.axhline(0, color='k', linestyle='--', linewidth=1)
plt.tight_layout()
plt.show()
Example 3: Relate Coefficients to Harmonic Distortion
from adctoolbox import fit_static_nonlin, analyze_spectrum
# Extract nonlinearity coefficients
k2, k3, _, _ = fit_static_nonlin(signal, order=3)
# Measure harmonic distortion
result = analyze_spectrum(signal, fs=800e6, harmonic=5)
hd2_db = result['harmonic_powers'][0] # 2nd harmonic
hd3_db = result['harmonic_powers'][1] # 3rd harmonic
print(f"k2 = {k2:.6f} → HD2 = {hd2_db:.2f} dBc")
print(f"k3 = {k3:.6f} → HD3 = {hd3_db:.2f} dBc")
# Theoretical relationship (for small distortion):
# HD2 ≈ 20·log10(k2/4)
# HD3 ≈ 20·log10(k3/6)
Example 4: Before/After Calibration
# Before calibration
k2_before, k3_before, _, _ = fit_static_nonlin(signal_uncal, order=3)
# After calibration
k2_after, k3_after, _, _ = fit_static_nonlin(signal_cal, order=3)
print("Before Calibration:")
print(f" k2 = {k2_before:.6f}, k3 = {k3_before:.6f}")
print("After Calibration:")
print(f" k2 = {k2_after:.6f}, k3 = {k3_after:.6f}")
print(f"Improvement: {20*np.log10(abs(k2_before/k2_after)):.1f} dB (k2)")
print(f" {20*np.log10(abs(k3_before/k3_after)):.1f} dB (k3)")
Interpretation
Coefficient Magnitude
Coefficient |
Typical Range |
Interpretation |
|---|---|---|
k2 |
< 0.001 |
Excellent linearity (HD2 < -80 dBc) |
k2 |
0.001 - 0.01 |
Good linearity (HD2: -60 to -80 dBc) |
k2 |
> 0.01 |
Poor linearity (HD2 > -60 dBc) |
k3 |
< 0.0001 |
Excellent linearity (HD3 < -80 dBc) |
k3 |
0.0001 - 0.001 |
Good linearity (HD3: -60 to -80 dBc) |
k3 |
> 0.001 |
Poor linearity (HD3 > -60 dBc) |
Coefficient Sign
Sign |
Physical Meaning |
|---|---|
k2 > 0 |
Upward curvature (compression → expansion) |
k2 < 0 |
Downward curvature (expansion → compression) |
k3 > 0 |
Positive cubic term (soft saturation) |
k3 < 0 |
Negative cubic term (hard limiting) |
Dominant Nonlinearity
Condition |
ADC Characteristic |
|---|---|
|k2| >> |k3| |
Even-order dominant (differential pair mismatch) |
|k3| >> |k2| |
Odd-order dominant (single-ended nonlinearity) |
|k2| ≈ |k3| |
Mixed nonlinearity |
Limitations
Cannot extract gain error: Sine fitting absorbs amplitude variations
Gain calibration requires DC sweep or multi-amplitude testing
Assumes static nonlinearity: Dynamic effects (memory, settling) not captured
Single-tone only: Multi-tone distortion (IMD) requires different analysis
Numerical stability: Order > 10 may cause ill-conditioning
Common Issues
High-Order Fitting (order > 5)
# BAD: May overfit or become numerically unstable
k2, k3, _, _ = fit_static_nonlin(signal, order=15)
# GOOD: Use order 2-3 for most applications
k2, k3, _, _ = fit_static_nonlin(signal, order=3)
Interpreting NaN Results
k2, k3, _, _ = fit_static_nonlin(signal, order=2)
# k3 will be NaN because order < 3
if not np.isnan(k3):
print(f"k3 = {k3}")
else:
print("k3 not extracted (order < 3)")
Use Cases
Characterize ADC linearity quantitatively
Compare designs (architecture, process, layout)
Validate calibration (before/after)
Root cause analysis (k2 vs k3 dominance)
Predict harmonic distortion from coefficients
See Also
analyze_spectrum— Measure HD2, HD3 in frequency domainanalyze_decomposition_time— Time-domain harmonic extractionanalyze_inl_from_sine— INL/DNL (code-level nonlinearity)calibrate_weight_sine— Calibrate to reduce k2/k3
References
S. Max, “Fast Accurate and Complete ADC Testing,” Proc. IEEE ITC, 1989
J. Doernberg et al., “Full-Speed Testing of A/D Converters,” IEEE JSSC, 1984
IEEE Std 1241-2010, “IEEE Standard for Terminology and Test Methods for ADCs”