Fundamental Utilities (fundamentals)

The fundamentals module provides core utility functions and signal processing tools used across the toolbox.

Sine Fitting

adctoolbox.fit_sine_4param(data, frequency_estimate=None, max_iterations=1, tolerance=1e-09, verbose=0)[source]

Fit sine wave: y = A*cos(wt) + B*sin(wt) + C.

Parameters:
  • data – Input signal (1D or 2D array).

  • frequency_estimate – Initial normalized frequency (0 to 0.5). If None, estimated via FFT.

  • max_iterations – Iterations for frequency refinement.

  • tolerance – Convergence threshold for frequency updates.

  • verbose – Verbosity level (0=silent, >0=print iteration info).

Returns:

  • fitted_signal: Reconstructed sine wave
    • residuals: Data - fitted_signal

    • frequency: Normalized frequency (0 to 0.5)

    • amplitude: sqrt(A² + B²)

    • phase: atan2(-B, A) in radians

    • dc_offset: DC component

    • rmse: Root mean square error

For 2D input, all values (except fitted_signal, residuals) are 1D arrays.

Return type:

Dictionary with fitted parameters

Frequency Utilities

adctoolbox.find_coherent_frequency(fs, fin_target, n_fft, force_odd=True, search_radius=200)[source]

Calculate the precise coherent input frequency and bin index.

Supports Undersampling (Fin > Fs/2).

Parameters:
  • fs (float) – Sampling frequency (Hz)

  • fin_target (float) – Target input frequency (Hz)

  • n_fft (int) – FFT size (number of points)

  • force_odd (bool, optional) – If True, only search for odd bin indices (default: True)

  • search_radius (int, optional) – Search radius around the ideal bin (default: 200)

Returns:

(fin_actual, best_bin) - Coherent frequency and corresponding bin index

Return type:

tuple

Raises:

ValueError – If no valid coherent frequency is found within search radius

Examples

>>> fin, bin_idx = find_coherent_frequency(800e6, 100e6, 8192)
>>> print(f"Coherent frequency: {fin/1e6:.6f} MHz at bin {bin_idx}")
adctoolbox.estimate_frequency(data, frequency_estimate=None, fs=1.0)[source]

Estimate the physical fundamental frequency (Hz) of a signal.

This is a wrapper around the robust fit_sine_4param algorithm. It converts the normalized frequency (0 ~ 0.5) returned by fit_sine into physical frequency (Hz) based on the sampling rate.

Parameters:
  • data (np.ndarray) – Input signal data. 1D or 2D array.

  • fs (float, optional) – Sampling frequency in Hz (default: 1.0)

Returns:

Estimated frequency in Hz. (Scalar if input is 1D, Array if input is 2D)

Return type:

float or np.ndarray

Examples

>>> import numpy as np
>>> # Generate a 100 MHz sine wave sampled at 800 MHz
>>> t = np.arange(8192) / 800e6
>>> signal = np.sin(2 * np.pi * 100e6 * t)
>>> freq = estimate_frequency(signal, fs=800e6)
>>> print(f"Estimated frequency: {freq/1e6:.2f} MHz")
Estimated frequency: 100.00 MHz
adctoolbox.fold_frequency_to_nyquist(fin, fs)[source]

Calculate the aliased (folded) frequency in the first Nyquist zone.

The aliased frequency is the absolute difference between the input frequency and the nearest integer multiple of the sampling rate.

Parameters:
  • fin (float or np.ndarray) – Input frequency (Hz). Can be positive or negative.

  • fs (float) – Sampling frequency (Hz)

Returns:

Aliased frequency in range [0, Fs/2]

Return type:

float or np.ndarray

Examples

>>> fold_frequency_to_nyquist(100e6, 800e6)
100000000.0
>>> fold_frequency_to_nyquist(900e6, 800e6)
100000000.0
adctoolbox.fold_bin_to_nyquist(bin_idx: float, n_fft: int) float[source]

Calculate the aliased bin index in the first Nyquist zone [0, n_fft/2].

For real signals, FFT bins above n_fft/2 are mirrored to the first Nyquist zone. This function handles the wrapping and mirroring.

Parameters:
  • bin_idx (float) – Bin index (can be fractional, negative, or > n_fft)

  • n_fft (int) – Total number of FFT bins

Returns:

Aliased bin index in range [0, n_fft/2]

Return type:

float

Examples

>>> fold_bin_to_nyquist(100, 8192)
100.0
>>> fold_bin_to_nyquist(100.5, 8192)  # Fractional bins supported
100.5
>>> fold_bin_to_nyquist(5000, 8192)  # Above Nyquist, mirrors back
3192.0
>>> fold_bin_to_nyquist(-100, 8192)  # Negative wraps around
92.0

Unit Conversions

adctoolbox.db_to_mag(db)[source]

Convert dB to magnitude ratio: 10^(x/20)

adctoolbox.mag_to_db(mag)[source]

Convert magnitude ratio to dB: 20*log10(x)

adctoolbox.db_to_power(db)[source]

Convert dB to power ratio: 10^(x/10)

adctoolbox.power_to_db(power)[source]

Convert power ratio to dB: 10*log10(x)

adctoolbox.snr_to_enob(snr_db)[source]

Convert SNR/SNDR (dB) to ENOB (bits): (SNR - 1.76) / 6.02

adctoolbox.enob_to_snr(enob)[source]

Convert ENOB (bits) to ideal SNR (dB): ENOB * 6.02 + 1.76

adctoolbox.fundamentals.lsb_to_volts(lsb_count, vref, n_bits)[source]

Convert LSB count to voltage

adctoolbox.fundamentals.volts_to_lsb(volts, vref, n_bits)[source]

Convert voltage to LSB count

adctoolbox.fundamentals.bin_to_freq(bin_idx, fs, n_fft)[source]

Convert FFT bin index to frequency (Hz)

adctoolbox.fundamentals.freq_to_bin(freq, fs, n_fft)[source]

Convert frequency (Hz) to nearest FFT bin index

adctoolbox.fundamentals.dbm_to_vrms(dbm, z_load=50)[source]

Convert dBm to Vrms (assuming load impedance z_load)

adctoolbox.fundamentals.vrms_to_dbm(vrms, z_load=50)[source]

Convert Vrms to dBm

adctoolbox.fundamentals.dbm_to_mw(dbm)[source]

Convert dBm to mW: mW = 10^(dBm/10)

adctoolbox.fundamentals.mw_to_dbm(mw)[source]

Convert mW to dBm: dBm = 10*log10(mW)

adctoolbox.fundamentals.sine_amplitude_to_power(amplitude, z_load=50)[source]

Convert sine wave peak amplitude to power.

For sine wave: Vrms = A / sqrt(2) Power = Vrms^2 / Z = A^2 / (2*Z)

SNR/NSD Conversion

adctoolbox.amplitudes_to_snr(sig_amplitude: float | ndarray, noise_amplitude: float | ndarray, osr: float = 1, return_power: bool = False) float | ndarray | tuple[float | ndarray, ...][source]

Calculate Signal-to-Noise Ratio (SNR) in dB from sine wave peak amplitude and noise RMS.

This function computes SNR, assuming the signal is a pure sine wave and the noise is Gaussian (White Noise).

SNR is calculated based on the power ratio: SNR (dB) = 10 * log10(P_sig / P_noise). When oversampling is used, SNR improves by 10*log10(OSR).

Parameters:
  • sig_amplitude (float or array_like) – Sine wave peak amplitude (A), in Volts (V).

  • noise_amplitude (float or array_like) – Noise RMS amplitude (σ), in Volts (V).

  • osr (float, optional) – Oversampling ratio. SNR improves by 10*log10(OSR) dB. Default is 1 (no oversampling).

  • return_power (bool, optional) – If True, returns a tuple containing (snr_db, sig_power, noise_power). Default is False, returning only snr_db.

Returns:

  • snr_db (float or ndarray) – The calculated SNR in dB. Returns np.inf if noise_amplitude is zero.

  • (snr_db, sig_power, noise_power) (tuple (if return_power=True)) – The SNR in dB, Signal Power (V^2), and Noise Power (V^2), respectively.

Examples

>>> snr = amplitudes_to_snr(sig_amplitude=1.0, noise_amplitude=0.01)
>>> print(f"SNR = {snr:.2f} dB")
SNR = 40.00 dB
>>> snr, sig_pwr, noise_pwr = amplitudes_to_snr(1.0, 0.01, return_power=True)
>>> print(f"SNR = {snr:.2f} dB, Signal Power = {sig_pwr:.4f} V^2")
SNR = 40.00 dB, Signal Power = 0.5000 V^2
adctoolbox.snr_to_nsd(snr_db: float | ndarray, fs: float, osr: float = 1.0, psignal_dbfs: float = 0.0) float | ndarray[source]

Convert Signal-to-Noise Ratio (SNR) to Noise Spectral Density (NSD).

This function converts SNR in dB to NSD in dBFS/Hz, given the sampling frequency and oversampling ratio. It assumes a full-scale sine wave signal (0 dBFS) unless specified otherwise.

The relationship is derived from: - Signal power: P_signal = 10^(Psignal_dBFS / 10) - Noise power: P_noise = P_signal / 10^(SNR_dB / 10) - Noise bandwidth: BW = fs / (2 * OSR) - NSD = P_noise / BW (linear scale) - NSD_dBFS/Hz = 10 * log10(NSD)

Parameters:
  • snr_db (float or array_like) – Signal-to-Noise Ratio in dB.

  • fs (float) – Sampling frequency in Hz.

  • osr (float, optional) – Oversampling ratio. Default is 1.0 (Nyquist sampling). The noise bandwidth is fs / (2 * OSR).

  • psignal_dbfs (float, optional) – Signal power in dBFS. Default is 0.0 dBFS (full-scale signal).

Returns:

nsd_dbfs_hz – Noise Spectral Density in dBFS/Hz.

Return type:

float or ndarray

Examples

>>> # For a full-scale signal with 80 dB SNR, fs=1 MHz, OSR=256
>>> nsd = snr_to_nsd(snr_db=80, fs=1e6, osr=256)
>>> print(f"NSD = {nsd:.2f} dBFS/Hz")
NSD = -134.08 dBFS/Hz
>>> # For a -6 dBFS signal with 70 dB SNR, fs=100 kHz, Nyquist sampling
>>> nsd = snr_to_nsd(snr_db=70, fs=1e5, osr=1, psignal_dbfs=-6)
>>> print(f"NSD = {nsd:.2f} dBFS/Hz")
NSD = -122.99 dBFS/Hz
adctoolbox.fundamentals.nsd_to_snr(nsd_dbfs_hz: float | ndarray, fs: float, osr: float = 1.0, psignal_dbfs: float = 0.0) float | ndarray[source]

Convert Noise Spectral Density (NSD) to Signal-to-Noise Ratio (SNR).

This function converts NSD in dBFS/Hz to SNR in dB, given the sampling frequency and oversampling ratio. It assumes a full-scale sine wave signal (0 dBFS) unless specified otherwise.

The relationship is derived from: - NSD in linear scale: NSD_linear = 10^(NSD_dBFS/Hz / 10) - Noise bandwidth: BW = fs / (2 * OSR) - Noise power: P_noise = NSD_linear * BW - Signal power: P_signal = 10^(Psignal_dBFS / 10) - SNR = 10 * log10(P_signal / P_noise)

Parameters:
  • nsd_dbfs_hz (float or array_like) – Noise Spectral Density in dBFS/Hz.

  • fs (float) – Sampling frequency in Hz.

  • osr (float, optional) – Oversampling ratio. Default is 1.0 (Nyquist sampling). The noise bandwidth is fs / (2 * OSR).

  • psignal_dbfs (float, optional) – Signal power in dBFS. Default is 0.0 dBFS (full-scale signal).

Returns:

snr_db – Signal-to-Noise Ratio in dB.

Return type:

float or ndarray

Examples

>>> # For NSD = -134 dBFS/Hz, fs=1 MHz, OSR=256
>>> snr = nsd_to_snr(nsd_dbfs_hz=-134, fs=1e6, osr=256)
>>> print(f"SNR = {snr:.2f} dB")
SNR = 79.92 dB
>>> # For NSD = -123 dBFS/Hz, fs=100 kHz, Nyquist sampling, -6 dBFS signal
>>> snr = nsd_to_snr(nsd_dbfs_hz=-123, fs=1e5, osr=1, psignal_dbfs=-6)
>>> print(f"SNR = {snr:.2f} dB")
SNR = 70.01 dB

Validation

adctoolbox.fundamentals.validate_aout_data(aout_data, min_samples=100)[source]

Validate analog output data format.

Checks: Numeric, Real, Finite, Sufficient Length, Signal Variation.

adctoolbox.fundamentals.validate_dout_data(bits, min_samples=100)[source]

Validate digital output (bits) data format.

Checks: Binary (0/1), Dimensions, Stuck Bits. Expects: (N_samples, N_bits) matrix.

Figures of Merit

adctoolbox.fundamentals.calculate_walden_fom(power, fs, enob)[source]

Calculate Walden Figure of Merit (FoM_w).

Standard metric for medium-resolution ADCs. Lower is better.

Formula: Power / (2^ENOB * Fs)

Parameters:
  • power – Power consumption (W)

  • fs – Sampling frequency (Hz)

  • enob – Effective number of bits

Returns:

FoM_w in J/conv-step (Joules per conversion step)

adctoolbox.fundamentals.calculate_schreier_fom(power, sndr_db, bw)[source]

Calculate Schreier Figure of Merit (FoM_s).

Standard metric for high-resolution / Sigma-Delta ADCs. Higher is better.

Formula: SNDR + 10*log10(BW / Power)

Parameters:
  • power – Power consumption (W)

  • sndr_db – Signal-to-Noise and Distortion Ratio (dB)

  • bw – Signal bandwidth (Hz)

Returns:

FoM_s in dB

adctoolbox.fundamentals.calculate_thermal_noise_limit(cap_pf, v_fs=1.0)[source]

Calculate maximum achievable SNR limited by kT/C noise.

Thermal noise sets the fundamental limit for switched-capacitor circuits and sample-and-hold amplifiers.

Parameters:
  • cap_pf – Sampling capacitance in pF

  • v_fs – Full-scale voltage (Vpp), default 1.0V

Returns:

Maximum SNR in dB

Theory:
  • Noise power = kT/C

  • Signal power (sine) = (Vfs/2)^2 / 2 = Vfs^2 / 8

  • SNR = 10*log10(P_signal / P_noise)

adctoolbox.fundamentals.calculate_jitter_limit(freq, jitter_rms_sec)[source]

Calculate maximum achievable SNR limited by aperture jitter.

Sampling jitter creates phase noise that limits SNR, especially at high input frequencies.

Formula: SNR = -20 * log10(2 * pi * fin * tj)

Parameters:
  • freq – Input frequency (Hz)

  • jitter_rms_sec – RMS jitter in seconds

Returns:

Maximum SNR in dB