diff --git a/adc_eval/adcs/basic.py b/adc_eval/adcs/basic.py index 01210fe..eb4810d 100644 --- a/adc_eval/adcs/basic.py +++ b/adc_eval/adcs/basic.py @@ -14,8 +14,6 @@ class ADC: """ Generic ADC Class. - ... - Parameters ---------- nbits : int, default=8 diff --git a/adc_eval/eval/calc.py b/adc_eval/eval/calc.py index 80d7e61..0d3ce61 100644 --- a/adc_eval/eval/calc.py +++ b/adc_eval/eval/calc.py @@ -4,27 +4,90 @@ def db_to_pow(value, places=3): - """Convert dBW to W.""" + """ + Convert dBW to W. + + Parameters + ---------- + value : float or ndarray + Value to convert to power, in dBW. + places : int, optional + Number of places to round output value to. Default is 3. + + Returns + ------- + float or ndarray + Returns either the rounded and converted value, or the ndarray + """ if isinstance(value, np.ndarray): return np.round(10 ** (0.1 * value), places) return round(10 ** (0.1 * value), places) def dBW(value, places=1): - """Convert to dBW.""" + """ + Convert to dBW. + + Parameters + ---------- + value : float or ndarray + Value to convert to dBW, in W. + places : int, optional + Number of places to round output value to. Default is 1. + + Returns + ------- + float or ndarray + Returns either the rounded and converted value, or the ndarray + """ if isinstance(value, np.ndarray): return np.round(10 * np.log10(value), places) return round(10 * np.log10(value), places) def enob(sndr, places=1): - """Return ENOB for given SNDR.""" + """ + Return ENOB for given SNDR. + + Parameters + ---------- + sndr : float + SNDR value in dBW to convert to ENOB. + places : int, optional + Number of places to round output value to. Default is 1. + + Returns + ------- + float or ndarray + Returns either the rounded and converted value, or the ndarray + """ return round((sndr - 1.76) / 6.02, places) -def sndr_sfdr(spectrum, freq, fs, nfft, leak, full_scale=0): - """Get SNDR and SFDR.""" - +def sndr_sfdr(spectrum, freq, fs, nfft, leak=0, full_scale=0): + """ + Get SNDR and SFDR. + + Parameters + ---------- + spectrum : ndarray + Power spectrum as ndarray in units of Watts. + freq : ndarray + Array of frequencies for the input power spectrum. + fs : float + Sample frequency of power spectrum in Hz. + nfft : int + Number of samples in the FFT. + leak : int, optional + Number of leakage bins to consider when looking for peaks. Default is 0. + full_scale : float, optional + Full scale reference value for spectrum in Watts. + + Returns + ------- + dict + Returns a dictionary of computed stats. + """ # Zero the DC bin for i in range(0, leak + 1): spectrum[i] = 0 @@ -81,7 +144,33 @@ def sndr_sfdr(spectrum, freq, fs, nfft, leak, full_scale=0): def find_harmonics(spectrum, freq, nfft, bin_sig, psig, harms=5, leak=20, fscale=1e6): - """Get the harmonic contents of the data.""" + """ + Get the harmonic contents of the data. + + Parameters + ---------- + spectrum : ndarray + Power spectrum as ndarray in units of Watts. + freq : ndarray + Array of frequencies for the input power spectrum. + nfft : int + Number of samples in the FFT. + bin_sig : int + Frequency bin of the dominant signal. + psig : float + Power of dominant signal in spectrum. + harms : int, optional + Number of input harmonics to calculate. Default is 5. + leak : int, optional + Number of leakage bins to look at when finding harmonics. Default is 20. + fscale : float, optional + Value to scale frequencies by in Hz. Default is 1MHz. + + Returns + ------- + dict + Returns a dictionary of computed stats. + """ harm_stats = {"harm": {}} harm_index = 2 for harm in bin_sig * np.arange(2, harms + 1): diff --git a/adc_eval/eval/simulate.py b/adc_eval/eval/simulate.py index 7a4765b..a8b3b2f 100644 --- a/adc_eval/eval/simulate.py +++ b/adc_eval/eval/simulate.py @@ -5,7 +5,28 @@ class Simulator: - """Class for handling simulation functions.""" + """ + Class for handling simulation functions. + + Parameters + ---------- + adc_obj : ADC.__class__ + An ADC object from the adc_eval.eval.adc class list. + xarray : ndarray + Input signal array to simulate the adc_obj with. + + + Attributes + ---------- + out : ndarray of ADC output values. + adc : Reference to the input adc_obj. + vin : xarray with global signal errors included as set by adj_obj. + + Methods + ------- + run + + """ def __init__(self, adc_obj, xarray): """Initialize the simulator class.""" @@ -19,7 +40,7 @@ def out(self): return np.array(self.dval) def calc_error(self, vin): - """Using the adc obj, calculates global signal error.""" + """Using the adc_obj, calculates global signal error before simulation.""" vinx = vin # First calculate gain error diff --git a/adc_eval/eval/spectrum.py b/adc_eval/eval/spectrum.py index 757bcf7..657a215 100644 --- a/adc_eval/eval/spectrum.py +++ b/adc_eval/eval/spectrum.py @@ -5,8 +5,25 @@ from adc_eval.eval import calc -def calc_psd(data, fs, nfft=2**12): - """Calculate the PSD using the Bartlett method.""" +def calc_psd(data, fs=1, nfft=2**12): + """ + Calculate the PSD using the Bartlett method. + + Parameters + ---------- + data : ndarray + Time-series input data. + fs : float, optional + Sample frequency of the input time series data in Hz. Default is 1Hz. + nfft : int, optional + Number of FFT samples to use for PSD calculation. Default is 2^12. + + Returns + ------- + list + [freq_ss, psd_ss, freq_ds, psd_ds] + List containing single and double-sided PSDs along with frequncy array. + """ nwindows = max(1, int(np.floor(len(data) / nfft))) nfft = int(nfft) xs = data[0 : int(nwindows * nfft)] @@ -29,7 +46,27 @@ def calc_psd(data, fs, nfft=2**12): def get_spectrum(data, fs=1, nfft=2**12, single_sided=True): - """Get the power spectrum for an input signal.""" + """ + Get the power spectrum for an input signal. + + Parameters + ---------- + data : ndarray + Time-series input data. + fs : float, optional + Sample frequency of the input time series data in Hz. Default is 1Hz. + nfft : int, optional + Number of FFT samples to use for PSD calculation. Default is 2^12. + single_sided : bool, optional + Set to `True` for single-sided spectrum or `False` for double-sided. + Default is `True`. + + Returns + ------- + tuple + (freq, psd) + Tuple containing frequency array and PSD of input data. + """ (freq_ss, psd_ss, freq_ds, psd_ds) = calc_psd(np.array(data), fs=fs, nfft=nfft) if single_sided: return (freq_ss, psd_ss * fs / nfft) @@ -37,7 +74,21 @@ def get_spectrum(data, fs=1, nfft=2**12, single_sided=True): def window_data(data, window="rectangular"): - """Applies a window to the time-domain data.""" + """ + Applies a window to the time-domain data. + + Parameters + ---------- + data : ndarray + Time-series input data. + window : str, optional + Window to use for input data. Default is rectangular. + + Returns + ------- + ndarray + Windowed version of input data. + """ try: wsize = data.size except AttributeError: @@ -71,7 +122,41 @@ def plot_spectrum( single_sided=True, fscale=("MHz", 1e6), ): - """Plot Power Spectrum for input signal.""" + """ + Plot Power Spectrum for input signal. + + Parameters + ---------- + data : ndarray + Time-series input data. + fs : float, optional + Sample frequency of the input time series data in Hz. Default is 1Hz. + nfft : int, optional + Number of FFT samples to use for PSD calculation. Default is 2^12. + dr : float, optional + Dynamic range for input data to be referenced to. Default is 1. + harmonics : int, optional + Number of harmonics to calculate and annotate on plot. Default is 7. + leak : int, optional + Number of leakage bins to use in signal and harmonic calculation. Default is 1. + window : str, optional + Type of input window to use for input data. Default is rectangular. + no_plot : bool, optional + Selects whether to plot (`False`) or not (`True`). Default is `False`. + yaxis : str, optional + Selects y-axis reference units. Example: `power`, `fullscale`, etc. Default is `power`. + single_sided : bool, optional + Set to `True` for single-sided spectrum or `False` for double-sided. + Default is `True`. + fscale : tuple, optional + Selects x-axis scaling and units. Default is ('MHz', 1e6). + + Returns + ------- + tuple + (freq, psd, stats) + Tuple containing frequency array, PSD of input data, and calculated statstics dictionary. + """ (freq, pwr) = get_spectrum(data, fs=fs, nfft=nfft, single_sided=single_sided) # Calculate the fullscale range of the spectrum in Watts @@ -210,7 +295,41 @@ def analyze( single_sided=True, fscale="MHz", ): - """Perform spectral analysis on input waveform.""" + """ + Perform spectral analysis on input waveform. + + Parameters + ---------- + data : ndarray + Time-series input data. + nfft : int + Number of FFT samples to use for PSD calculation. + fs : float, optional + Sample frequency of the input time series data in Hz. Default is 1Hz. + dr : float, optional + Dynamic range for input data to be referenced to. Default is 1. + harmonics : int, optional + Number of harmonics to calculate and annotate on plot. Default is 7. + leak : int, optional + Number of leakage bins to use in signal and harmonic calculation. Default is 1. + window : str, optional + Type of input window to use for input data. Default is rectangular. + no_plot : bool, optional + Selects whether to plot (`False`) or not (`True`). Default is `False`. + yaxis : str, optional + Selects y-axis reference units. Example: `power`, `fullscale`, etc. Default is `power`. + single_sided : bool, optional + Set to `True` for single-sided spectrum or `False` for double-sided. + Default is `True`. + fscale : str, optional + Selects x-axis units. Default is 'MHz'. + + Returns + ------- + tuple + (freq, psd, stats) + Tuple containing frequency array, PSD of input data, and calculated statstics dictionary. + """ fscalar = { "uHz": 1e-6, "mHz": 1e-3, diff --git a/adc_eval/filt.py b/adc_eval/filt.py index bc27cee..5f6c383 100644 --- a/adc_eval/filt.py +++ b/adc_eval/filt.py @@ -12,8 +12,6 @@ class CICDecimate: """ Generic CIC Decimator Object. - ... - Parameters ---------- dec : int, default=2 @@ -138,8 +136,6 @@ class FIRLowPass: """ Generic FIR Low Pass Filter. - ... - Parameters ---------- dec : int, optional diff --git a/adc_eval/signals.py b/adc_eval/signals.py index aaf6874..4c72f74 100644 --- a/adc_eval/signals.py +++ b/adc_eval/signals.py @@ -3,23 +3,89 @@ import numpy as np -def time(nsamp, fs=1): - """Create time array based on signal length and sample rate.""" - return 1 / fs * np.linspace(0, nsamp - 1, nsamp) +def time(nlen, fs=1): + """ + Create time array based on signal length and sample rate. + + Paraneters + ---------- + nlen : int + Desired length of time array. + fs : float, optional + Sample frequency of data in Hz. Default is 1 Hz. + + Returns + ------- + ndarray + Time list stored in ndarray type. + + """ + return 1 / fs * np.linspace(0, nlen - 1, nlen) def sin(t, amp=0.5, offset=0.5, freq=1e3, ph0=0): - """Generate a sine wave.""" + """ + Generate a sine wave. + + Parameters + ---------- + t : ndarray + Time array list for sine wave. + amp : float, optional + Amplitude of desired sine wave. Default is 0.5. + offset : float, optional + DC offset of the desired sine wave. Default is 0.5. + freq : float, optional + Desired frequency of the sine wave in Hz. Default is 1kHz. + ph0 : float, optional + Desired phase shift of the sine wave in radians. Default is 0. + + Returns + ------- + ndarray + Sine wave stored in ndarray type. + + """ return offset + amp * np.sin(ph0 + 2 * np.pi * freq * t) -def noise(t, mean=0, std=0.1): - """Generate random noise.""" - return np.random.normal(mean, std, size=len(t)) +def noise(nlen, mean=0, std=0.1): + """ + Generate random noise. + + Parameters + ---------- + nlen : int + Desired length of noise array. + mean : float, optional + Desired average of noise array. Default is 0. + std : float, optional + Desired standard deviation of noise array. Default is 0.1. + + Returns + ------- + ndarray + Gaussian distributed noise array. + """ + return np.random.normal(mean, std, size=nlen) + + +def impulse(nlen, mag=1): + """ + Generate an impulse input. + Parameters + ---------- + nlen : int + Desired length of noise array. + mag : float, optional + Desired magnitude of impulse. Default is 1. -def impulse(nsamp, mag=1): - """Generate an impulse input.""" - data = np.zeros(nsamp) + Returns + ------- + ndarray + Impulse waveform in ndarray type. + """ + data = np.zeros(nlen) data[0] = mag return data diff --git a/tests/test_signals.py b/tests/test_signals.py index 5e94d22..c9ccaa3 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -41,8 +41,7 @@ def test_sin(nlen, offset, amp): @pytest.mark.parametrize("nlen", np.random.randint(1, 2**16, 5)) def test_noise_length(nlen): """Test noise generation with random data.""" - t = signals.time(nlen, fs=1) - value = signals.noise(t, mean=0, std=1) + value = signals.noise(nlen, mean=0, std=1) # Just check correct size assert value.size == nlen @@ -52,8 +51,7 @@ def test_noise_length(nlen): def test_noise_length(std): """Test noise is gaussian with random data.""" nlen = 2**12 - t = signals.time(nlen, fs=1) - noise = signals.noise(t, mean=0, std=std) + noise = signals.noise(nlen, mean=0, std=std) autocorr = np.correlate(noise, noise, mode="full") autocorr /= max(autocorr) asize = autocorr.size