calibrate_weight_sine
Overview
calibrate_weight_sine provides comprehensive, production-ready ADC foreground calibration using sinewave input. This is the full-featured version supporting automatic frequency search, rank deficiency handling, harmonic rejection, and multi-dataset calibration.
Key Features:
✅ Automatic frequency search with coarse and fine refinement
✅ Rank deficiency handling for redundant ADC architectures
✅ Harmonic rejection to exclude distortion
✅ Multi-dataset calibration for time-interleaved ADCs
✅ Numerical conditioning for robust convergence
✅ Comprehensive diagnostics (SNDR, ENOB, error signals)
Syntax
from adctoolbox.calibration import calibrate_weight_sine
# Basic usage (auto frequency search)
result = calibrate_weight_sine(bits)
# With known frequency
result = calibrate_weight_sine(bits, freq=0.001587)
# With redundant weights
result = calibrate_weight_sine(bits, freq=0.001587,
nominal_weights=[2048, 1024, 512, 256, 128, 128, ...])
# With harmonic rejection
result = calibrate_weight_sine(bits, freq=0.001587, harmonic_order=3)
# Multi-dataset (time-interleaved ADC)
result = calibrate_weight_sine([bits_ch0, bits_ch1, bits_ch2, bits_ch3])
Parameters
bits(ndarray or list of ndarrays) — Binary data matrixSingle dataset: (N samples × M bits) ndarray
Multi-dataset: list of ndarrays for time-interleaved ADCs
Each row is one sample, each column is a bit (MSB first)
freq(float, array, or None, optional) — Normalized frequency (f_in / f_s)None: Automatic frequency search (default)float: Single frequency for all datasetsarray: Per-dataset frequencies for multi-dataset mode
force_search(bool, optional) — Force fine frequency search even when frequency is providedDefault:
FalseSet to
Trueto refine provided frequencies
nominal_weights(array, optional) — Nominal bit weights (only effective when rank is deficient)Default:
[2^(M-1), 2^(M-2), ..., 2, 1]Required for redundant ADCs (e.g.,
[128, 128, 64, ...])
harmonic_order(int, optional) — Number of harmonic terms to exclude in calibrationDefault:
1(fundamental only, no harmonic exclusion)Higher values exclude more harmonics from error term
Example:
harmonic_order=3excludes 1st, 2nd, 3rd harmonics
learning_rate(float, optional) — Adaptive learning rate for frequency updatesDefault:
0.5Range:
0 < learning_rate < 1Lower values = more conservative convergence
reltol(float, optional) — Relative error tolerance for convergenceDefault:
1e-12
max_iter(int, optional) — Maximum iterations for fine frequency searchDefault:
100
verbose(int, optional) — Print verbosity level0: Silent (default)1: Basic progress2: Detailed diagnostics
Returns
Dictionary with keys:
weight(ndarray) — Calibrated bit weights, normalized by sinewave magnitudeLength: M (bit width)
Normalized so max weight ≈ 1.0
offset(float or ndarray) — Calibrated DC offset(s)Single value for single dataset
Array for multi-dataset
calibrated_signal(ndarray or list) — Signal after calibrationSingle array for single dataset
List of arrays for multi-dataset
ideal(ndarray or list) — Best fitted sinewave (excluding harmonics > H)Single array for single dataset
List of arrays for multi-dataset
error(ndarray or list) — Residue errors after calibrationerror = calibrated_signal - ideal
Excludes distortion harmonics
refined_frequency(float or ndarray) — Refined frequency from calibrationSingle value for single dataset
Array for multi-dataset
snr_db(float or ndarray) — Signal-to-noise ratio in dBenob(float or ndarray) — Effective number of bitsCalculated as:
(snr_db - 1.76) / 6.02
Algorithm
Modular Pipeline (8 Stages)
Input → Prepare → Patch Rank → Scale Columns → Estimate Freq
↓
Solve (Freq Search or Static) → Recover Scaling → Recover Rank → Post-process → Output
Each stage is implemented as a separate helper function.
Mathematical Model
The ADC output with harmonic rejection is modeled as:
y(n) = Σ w_i · b_i(n) = Σ [A_k·cos(2πkfn) + B_k·sin(2πkfn)] + C
k=1 to H
where:
y(n)= reconstructed analog signalw_i= weight of bit i (unknown)b_i(n) ∈ {0, 1}= binary value of bit if= normalized frequencyH= harmonic orderA_k, B_k= amplitude coefficients for harmonic kC= DC offset
Dual-Basis Least Squares
Unlike the lite version, the full algorithm tries both basis assumptions:
Cosine Basis (A_1 = 1):
[B | 1 | sin(2πfn) | ... | sin(2πHfn) | cos(2π·2fn) | ...] × [w; C; B_1; ...; B_H; A_2; ...] = -cos(2πfn)
Sine Basis (B_1 = 1):
[B | 1 | cos(2πfn) | ... | cos(2πHfn) | sin(2π·2fn) | ...] × [w; C; A_1; ...; A_H; B_2; ...] = -sin(2πfn)
The algorithm solves both and selects the one with lower residual error.
Rank Deficiency Handling
For redundant ADCs (e.g., weights [128, 128, 64, ...]), the bit matrix has rank deficiency. The algorithm:
Identifies redundant columns — Bits with identical patterns
Groups identical bits —
{b_i, b_j}ifb_i(n) = b_j(n)for all nSolves reduced system — One representative per group
Distributes weights using nominal weights:
w_i = w_eff^(group) × (w_i^nom / Σ w_j^nom) j in group
Example:
Redundant bits:
[b_4, b_5]both have pattern[0,1,1,0,1,...]Nominal weights:
w_4^nom = 128, w_5^nom = 128Solved effective weight:
w_eff = 256Recovered weights:
w_4 = w_5 = 256 × (128/(128+128)) = 128✅
This preserves redundancy rather than collapsing it to zero!
Frequency Search
Coarse Search (when freq=None):
Reconstruct signal using nominal weights:
y_nom(n) = Σ w_i^nom · b_i(n)Compute FFT:
Y(k) = FFT(y_nom)Find peak:
k_peak = argmax |Y(k)|Initial frequency:
f_0 = k_peak / N
Fine Search (iterative refinement):
for iteration = 1 to max_iter:
1. Solve weights at current frequency f^(t)
2. Compute residual error: e(n) = y(n) - fitted_sinewave(n)
3. Compute phase gradient: ∇φ = angle(e · exp(-j2πf^(t)n))
4. Update frequency: f^(t+1) = f^(t) + α · ∇φ / (2πN)
5. Check convergence: |f^(t+1) - f^(t)| / f^(t) < reltol
6. If converged, break
Examples
Example 1: Basic Calibration (Known Frequency)
import numpy as np
from adctoolbox.calibration import calibrate_weight_sine
# Generate test data
n_samples = 8192
bit_width = 12
freq_true = 13 / n_samples
# Create sinewave and quantize
t = np.arange(n_samples)
signal = 0.5 * np.sin(2 * np.pi * freq_true * t) + 0.5
quantized = np.clip(np.floor(signal * (2**bit_width)), 0, 2**bit_width - 1).astype(int)
# Extract bits
bits = (quantized[:, None] >> np.arange(bit_width - 1, -1, -1)) & 1
# Calibrate with known frequency
result = calibrate_weight_sine(bits, freq=freq_true, verbose=2)
# Access results
print(f"Recovered weights: {result['weight']}")
print(f"Refined frequency: {result['refined_frequency']:.8f}")
print(f"SNDR: {result['snr_db']:.2f} dB")
print(f"ENOB: {result['enob']:.2f} bits")
Example 2: Automatic Frequency Search
# Calibrate without knowing frequency
result = calibrate_weight_sine(bits, freq=None, verbose=2)
# Algorithm will:
# 1. Estimate frequency from FFT
# 2. Refine using iterative search
# 3. Return refined frequency
print(f"Estimated frequency: {result['refined_frequency']:.8f}")
print(f"True frequency: {freq_true:.8f}")
print(f"Error: {abs(result['refined_frequency'] - freq_true):.2e}")
Example 3: Redundant ADC Calibration
# Redundant weights: [2048, 1024, 512, 256, 128, 128, 64, ...]
true_weights = np.array([2048, 1024, 512, 256, 128, 128, 64, 32, 16, 8, 4, 2])
# Generate redundant bit data using greedy decomposition
def decompose_to_redundant_bits(codes, weights):
n_samples = len(codes)
bit_width = len(weights)
bits = np.zeros((n_samples, bit_width), dtype=int)
for i, code in enumerate(codes):
remaining = float(code)
for j, weight in enumerate(weights):
if remaining >= weight - 0.5:
bits[i, j] = 1
remaining -= weight
return bits
bits_redundant = decompose_to_redundant_bits(quantized, true_weights)
# Provide nominal weights as hints
result = calibrate_weight_sine(bits_redundant, freq=freq_true,
nominal_weights=true_weights)
# Both redundant bits will be recovered correctly
print(f"True weights: {true_weights}")
print(f"Recovered weights: {result['weight'] * np.max(true_weights)}")
# Expected: [2048, 1024, 512, 256, 128, 128, 64, 32, 16, 8, 4, 2] ✅
# NOT: [2048, 1024, 512, 256, 128, 0, 64, 32, 16, 8, 4, 2] ❌
Example 4: Harmonic Rejection
# Exclude up to 3rd harmonic from error calculation
result = calibrate_weight_sine(
bits,
freq=freq_true,
harmonic_order=3, # Model fundamental + 2nd + 3rd harmonics
verbose=2
)
# Error signal excludes harmonics 1-3
# Improves weight accuracy for ADCs with significant INL
print(f"SNDR (with harmonic rejection): {result['snr_db']:.2f} dB")
Example 5: Multi-Dataset Calibration (Time-Interleaved ADC)
# Time-interleaved ADC with 4 channels
bits_ch0 = ... # Channel 0 data (N0 samples × M bits)
bits_ch1 = ... # Channel 1 data (N1 samples × M bits)
bits_ch2 = ... # Channel 2 data (N2 samples × M bits)
bits_ch3 = ... # Channel 3 data (N3 samples × M bits)
# Calibrate all channels together (shared weights)
result = calibrate_weight_sine(
bits=[bits_ch0, bits_ch1, bits_ch2, bits_ch3],
freq=None, # Auto frequency search for each channel
verbose=2
)
# Results are lists for multi-dataset
print(f"Shared weights: {result['weight']}")
print(f"Per-channel offsets: {result['offset']}")
print(f"Per-channel frequencies: {result['refined_frequency']}")
print(f"Per-channel ENOB: {result['enob']}")
Example 6: Forced Frequency Refinement
# Even with provided frequency, force refinement
result = calibrate_weight_sine(
bits,
freq=13/8192, # Approximate frequency
force_search=True, # Refine it anyway
learning_rate=0.3, # Conservative learning rate
reltol=1e-14, # Tight convergence
max_iter=200, # Allow more iterations
verbose=2
)
print(f"Initial frequency: {13/8192:.8f}")
print(f"Refined frequency: {result['refined_frequency']:.8f}")
Performance
Computational Complexity
Time Complexity: O(I·N·M² + M³)
I = number of frequency search iterations
N = number of samples
M = bit width
Space Complexity: O(N·M)
Typical Performance (12-bit ADC, N=8192, Intel i7):
Known frequency: ~20 ms
Frequency search: ~50-100 ms (depends on convergence)
Multi-dataset (4 channels): ~80-150 ms
Accuracy
Weight Recovery:
Ideal conditions: Error < 10⁻⁶ LSB
With noise (SNR > 60 dB): Error < 10⁻³ LSB
Redundant weights: Fully preserved (both bits active)
Frequency Estimation:
Coarse FFT: Accuracy ≈ 1/N bins
Fine search: Accuracy < 10⁻¹² (relative)
ENOB Improvement:
Binary ADC (no INL): +0.5 to +2 ENOB
Redundant ADC: +2 to +4 ENOB (error correction)
Time-interleaved: +3 to +6 ENOB (timing skew correction)
Limitations
Input Signal Requirements
Amplitude: Input should be > -6 dBFS for stable weight recovery
Purity: Input signal should have low distortion (THD < -60 dB) for best results
Coherency: For FFT-based frequency estimation, use coherent sampling when possible
Frequency Constraints
Nyquist:
0 < f < 0.5(normalized)Avoid DC and Nyquist:
0.01 < f < 0.49recommendedMulti-tone interference: Avoid frequencies near
k/Mwhere k is small integer
Convergence Issues
Frequency search may fail to converge if:
Initial frequency estimate is very poor (> 10% error)
Input amplitude is too low (< -10 dBFS)
Learning rate is too aggressive (α > 0.8)
Solutions:
Provide better initial frequency estimate
Reduce
learning_rateto 0.1-0.3Increase
max_iterto 200-500Check
verbose=2output for convergence diagnostics
Multi-Dataset Assumptions
Multi-dataset calibration assumes:
All channels share same bit weights (valid for time-interleaved ADCs)
Independent offset and gain per channel
Small timing skew between channels
For ADCs with per-channel weight variation, use separate single-dataset calibrations.
Comparison with Lite Version
Feature |
Lite Version |
Full Version |
|---|---|---|
Frequency handling |
Known frequency only |
Auto search + refinement |
Rank deficiency |
❌ Collapses redundancy |
✅ Preserves all weights |
Harmonic rejection |
❌ No |
✅ Configurable order |
Multi-dataset |
❌ Single only |
✅ Multiple datasets |
Numerical stability |
Basic lstsq |
Column scaling + conditioning |
Output |
Weights only (ndarray) |
Full diagnostics (dict) |
Return values |
1 (weights) |
7 (weights, offset, signals, SNDR, ENOB, etc.) |
Code complexity |
~40 lines |
~600+ lines (modular) |
Typical runtime |
5 ms |
20-100 ms |
When to Use Full Version
✅ Use full version when:
Unknown or imprecise frequency
Redundant ADC architecture
Need comprehensive diagnostics
Multi-dataset or time-interleaved ADCs
Harmonic rejection required
Production/research environments
❌ Use lite version when:
Frequency precisely known
Binary weighted ADC (no redundancy)
Speed critical (embedded systems)
Minimal code footprint needed
Pipeline Details
Stage 1: Input Preparation (_prepare_input)
Purpose: Normalize input to unified format and validate
Operations:
Convert single array to list:
[bits]Validate all arrays have same bit width M
Stack:
bits_stacked = [bits_1; bits_2; ...]Generate nominal weights if not provided:
w_i^nom = 2^(M-1-i)
Stage 2: Rank Deficiency Patching (_patch_rank_deficiency)
Purpose: Detect and handle redundant bits
Algorithm:
Compute column norms:
||b_i|| = sqrt(Σ b_i(n)²)Find identical columns:
||b_i - b_j|| < εGroup identical bits:
G_1, G_2, ..., G_Kwhere K = M_effSelect representatives for each group
Compute weight ratios:
r_i = w_i^nom / Σ w_j^nom(j in group)
Stage 3: Column Scaling (_scale_columns_for_conditioning)
Purpose: Normalize bit columns for numerical stability
Operations:
s_i = ||b_i||_2
b̃_i = b_i / s_i
Stage 4: Frequency Estimation (_estimate_frequencies)
Purpose: Estimate or validate input frequencies
Logic:
If
freq=None: FFT-based coarse estimation for each datasetIf
freq=scalar: broadcast to all datasetsIf
freq=array: validate length matches number of datasets
Stage 5: Least Squares Solve
Purpose: Solve for weights and sinewave parameters
Known Frequency Path:
Build design matrix A with all harmonics
Try both cosine and sine basis
Solve both and select basis with lower residual
Frequency Search Path:
Initialize with coarse frequency
Loop until convergence:
Solve weights at current frequency
Compute phase error
Update frequency using gradient
Check convergence
Stage 6-8: Recovery and Post-Processing
Recover column scaling: Undo normalization
Recover rank deficiency: Distribute effective weights to all physical bits
Post-process: Assemble results dictionary with SNDR, ENOB, error signals
References
IEEE Standard 1057-2017: “IEEE Standard for Digitizing Waveform Recorders”
Vogel, C., & Johansson, H. (2006). “Time-interleaved analog-to-digital converters: Status and future directions.” IEEE Transactions on Circuits and Systems I, 53(11), 2386-2394.
Jin, H., & Lee, E. K. F. (1992). “A digital-background calibration technique for minimizing timing-error effects in time-interleaved ADCs.” IEEE Transactions on Circuits and Systems II, 47(7), 603-613.
Le Dortz, N., et al. (2014). “A 1.62GS/s time-interleaved SAR ADC with digital background mismatch calibration achieving interleaving spurs below 70dBFS.” ISSCC Digest of Technical Papers, 386-387.
MATLAB Signal Processing Toolbox:
sinefitweightsdocumentation
See Also
calibrate_weight_sine_lite - Lightweight version for embedded systems
analyze_spectrum - Spectral analysis for SNDR/ENOB
fit_sine_4param - IEEE 1057 sinewave fitting