Source code for adctoolbox.calibration.calibrate_weight_sine

"""
Foreground Calibration using Sinewave Input

Main wrapper function that uses modular helper functions for:
- Input preparation and validation
- Rank deficiency patching
- Least-squares solving with frequency refinement
- Result assembly and normalization
"""

import numpy as np

from adctoolbox.calibration._prepare_input import _prepare_input

from adctoolbox.calibration._patch_rank_deficiency import _patch_rank_deficiency
from adctoolbox.calibration._patch_rank_deficiency import _recover_rank_deficiency

from adctoolbox.calibration._scale_columns_for_conditioning import _scale_columns_for_conditioning
from adctoolbox.calibration._scale_columns_for_conditioning import _recover_columns_for_conditioning

from adctoolbox.calibration._estimate_frequencies import _estimate_frequencies

from adctoolbox.calibration._lstsq_solver import _solve_weights_with_known_freq
from adctoolbox.calibration._lstsq_solver import _solve_weights_searching_freq

from adctoolbox.calibration._post_process import _post_process

[docs] def calibrate_weight_sine( bits: np.ndarray | list[np.ndarray], freq: float | np.ndarray | None = None, force_search: bool = False, nominal_weights: np.ndarray | None = None, harmonic_order: int = 1, learning_rate: float = 0.5, reltol: float = 1e-12, max_iter: int = 100, verbose: int = 0 ) -> dict: """ FGCalSine — Foreground calibration using a sinewave input This function estimates per-bit weights and a DC offset for an ADC by fitting the weighted sum of raw bit columns to a sine series at a given (or estimated) normalized frequency Fin/Fs. It optionally performs a coarse and fine frequency search to refine the input tone frequency. Implementation uses a unified pipeline where single-dataset calibration is treated as a special case of multi-dataset calibration (N=1). Parameters ---------- bits : ndarray or list of ndarrays Binary data as matrix (N rows by M cols, N is data points, M is bitwidth). Each row is one sample; each column is a bit/segment. Can also be a list of arrays for multi-dataset calibration. freq : float, array-like, or None, optional Normalized frequency Fin/Fs. Default is None (triggers auto frequency search). - None: Automatic frequency search - float: Single frequency for all datasets - array-like: Per-dataset frequencies for multi-dataset mode force_search : bool, optional Force fine frequency search even when frequency is provided. Default is False. Set to True to refine provided frequencies. nominal_weights : array-like, optional Nominal bit weights (only effective when rank is deficient). Default is 2^(M-1) down to 2^0. harmonic_order : int, optional Number of harmonic terms to exclude in calibration. Default is 1 (fundamental only, no harmonic exclusion). Higher values exclude more harmonics from the error term. learning_rate : float, optional Adaptive learning rate for frequency updates (0..1), default is 0.5. reltol : float, optional Relative error tolerance for convergence, default is 1e-12. max_iter : int, optional Maximum iterations for fine frequency search, default is 100. verbose : int, optional Print frequency search progress (1) or not (0), default is 0. Returns ------- dict Dictionary with keys: - weight : ndarray The calibrated weights, normalized by the magnitude of sinewave. - offset : float The calibrated DC offset, normalized by the magnitude of sinewave. - calibrated_signal : ndarray or list of ndarrays The signal after calibration (single array for single dataset, list of arrays for multi-dataset). - ideal : ndarray or list of ndarrays The best fitted sinewave (single array for single dataset, list of arrays for multi-dataset). - error : ndarray or list of ndarrays The residue errors after calibration, excluding distortion (single array for single dataset, list of arrays for multi-dataset). - refined_frequency : float or ndarray The refined frequency from calibration (float for single dataset, array for multi-dataset). """ # 1. Normalize input to unified format clean_input = _prepare_input(bits, nominal_weights, verbose) bits_stacked = clean_input["bits_stacked"] bits_segments = clean_input["bits_segments"] segment_lengths = clean_input["segment_lengths"] nominal_weights = clean_input["nominal_weights"] # 2. Patch rank deficiency globally patched_input = _patch_rank_deficiency(bits_stacked, nominal_weights, verbose) bits_stacked_effective = patched_input["bits_effective"] bit_to_col_map = patched_input["bit_to_col_map"] bit_weight_ratios = patched_input["bit_weight_ratios"] bit_width_effective = patched_input["bit_width_effective"] # Scale columns for numerical conditioning bits_stacked_effective_scaled, bit_scales = _scale_columns_for_conditioning(bits_stacked_effective, verbose) # Estimate or validate frequencies freq_array = _estimate_frequencies(bits_stacked, segment_lengths, freq, verbose) bits_segments_scaled = [] curr = 0 for length in segment_lengths: bits_segments_scaled.append(bits_stacked_effective_scaled[curr : curr + length]) curr += length if force_search or np.any(freq_array == 0): # Iterative frequency search (unified for single and multi-dataset) freq_array, coeffs, basis_choice, cos_basis, sin_basis = _solve_weights_searching_freq( bits_segments_scaled, freq_array, harmonic_order, learning_rate, reltol, max_iter, verbose=verbose ) else: # Static solve at known frequencies (unified for single and multi-dataset) coeffs, basis_choice, cos_basis, sin_basis = _solve_weights_with_known_freq( bits_segments_scaled, freq_array, harmonic_order ) num_datasets = len(bits_segments) num_harm_total = num_datasets * harmonic_order idx_quadrature = bit_width_effective + num_harm_total norm_factor = np.sqrt(1.0 + coeffs[idx_quadrature]**2) w_phys_effective = _recover_columns_for_conditioning( coeffs=coeffs, bit_width_effective=bit_width_effective, norm_factor=norm_factor, bit_scales=bit_scales ) weights_final = _recover_rank_deficiency( w_effective=w_phys_effective, bit_to_col_map=bit_to_col_map, bit_weight_ratios=bit_weight_ratios ) # 8. Assemble results (Unified for single and multi-dataset) results = _post_process( weights_final=weights_final, solution_vector=coeffs, norm_factor=norm_factor, basis_choice=basis_choice, bit_segments=bits_segments, bit_width_effective=bit_width_effective, segment_lengths=segment_lengths, harmonic_order=harmonic_order, cos_basis=cos_basis, sin_basis=sin_basis, freq_array=freq_array ) return results