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 samples

  • order (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 coefficient

    • For ideal ADC: k2 = 0

    • Represents 2nd-order distortion (HD2)

    • Returns NaN if order < 2

  • k3_extracted (float) — Cubic nonlinearity coefficient

    • For 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

References

  1. S. Max, "Fast Accurate and Complete ADC Testing," Proc. IEEE ITC, 1989

  2. J. Doernberg et al., "Full-Speed Testing of A/D Converters," IEEE JSSC, 1984

  3. IEEE Std 1241-2010, "IEEE Standard for Terminology and Test Methods for ADCs"