Module libquantum.atoms

This module contains functions to construct quantized, standardized information packets using binary metrics.

Expand source code
"""
This module contains functions to construct quantized, standardized information packets using binary metrics.
"""

import numpy as np
import scipy.signal as signal
from libquantum import scales
from libquantum import utils
from typing import Tuple, Union

"""
The purpose of this code is to construct quantized, standardized information packets
using binary metrics. Based on Garces (2020). 
Cleaned up and compartmentalized for debugging
"""


def chirp_complex(band_order_Nth: float,
                  time_s: np.ndarray,
                  offset_time_s: float,
                  scale_frequency_center_hz: float,
                  frequency_sample_rate_hz: float,
                  index_shift: float = 0,
                  scale_base: float = scales.Slice.G2) -> Tuple[np.ndarray, np.ndarray, float, float]:
    """
    Quantum chirp for specified band_order_Nth and arbitrary time duration
    Unscaled, to be used by both Dictionary 1 and Dictionary 2

    :param band_order_Nth: Nth order of constant Q bands
    :param time_s: time in seconds, duration should be greater than or equal to M/fc
    :param offset_time_s: offset time in seconds, should be between min and max of time_s
    :param scale_frequency_center_hz: center frequency fc in Hz
    :param frequency_sample_rate_hz: sample rate on Hz
    :param index_shift: Redshift = -1, Blueshift = +1, None=0
    :param scale_base: G2 or G3
    :return: waveform_complex, time_shifted_s
    """

    xtime_shifted = chirp_time(time_s, offset_time_s, frequency_sample_rate_hz)
    time_shifted_s = xtime_shifted/frequency_sample_rate_hz

    # Fundamental chirp parameters
    cycles_M, quality_factor_Q, gamma = \
        chirp_MQG_from_N(band_order_Nth, index_shift, scale_base)
    scale_atom = chirp_scale(cycles_M, scale_frequency_center_hz, frequency_sample_rate_hz)
    p_complex = chirp_p_complex(scale_atom, gamma, index_shift)
    amp_dict_0, amp_dict_1 = chirp_amplitude(scale_atom, gamma, index_shift)

    wavelet_gauss = np.exp(-p_complex * xtime_shifted**2)
    wavelet_gabor = wavelet_gauss * np.exp(1j * cycles_M*xtime_shifted/scale_atom)

    return wavelet_gabor, time_shifted_s, amp_dict_0, amp_dict_1


def chirp_spectrum(frequency_hz: np.ndarray,
                   offset_time_s: float,
                   band_order_Nth: float,
                   scale_frequency_center_hz: float,
                   frequency_sample_rate_hz: float,
                   index_shift: float = 0,
                   scale_base: float = scales.Slice.G2) -> Tuple[Union[complex, float, np.ndarray], np.ndarray]:
    """
    Spectrum of quantum wavelet for specified band_order_Nth and arbitrary time duration

    :param frequency_hz: frequency range below Nyquist
    :param offset_time_s: time of wavelet centroid
    :param band_order_Nth: Nth order of constant Q bands
    :param scale_frequency_center_hz: band center frequency in Hz
    :param frequency_sample_rate_hz: sample rate on Hz
    :param index_shift: index of shift. Default is 0.0
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: Fourier transform of the Gabor atom
    """

    cycles_M, quality_factor_Q, gamma = \
        chirp_MQG_from_N(band_order_Nth, index_shift, scale_base)
    scale_atom = chirp_scale(cycles_M, scale_frequency_center_hz, frequency_sample_rate_hz)
    p_complex = chirp_p_complex(scale_atom, gamma, index_shift)

    angular_frequency_center = 2 * np.pi * scale_frequency_center_hz/frequency_sample_rate_hz
    angular_frequency = 2 * np.pi * frequency_hz/frequency_sample_rate_hz
    offset_phase = angular_frequency * frequency_sample_rate_hz * offset_time_s
    angular_frequency_shifted = angular_frequency - angular_frequency_center
    frequency_shifted_hz = angular_frequency_shifted*frequency_sample_rate_hz/(2*np.pi)

    spectrum_amplitude = np.sqrt(np.pi/p_complex)
    gauss_arg = 1./(4*p_complex)
    spectrum_gauss = np.exp(-gauss_arg * (angular_frequency_shifted * scale_atom) ** 2)
    # Phase shift from time offset
    spectrum_gabor = spectrum_amplitude * spectrum_gauss * np.exp(-1j * offset_phase)

    return spectrum_gabor, frequency_shifted_hz


def chirp_MQG_from_N(band_order_Nth: float,
                     index_shift: float = 0,
                     scale_base: float = scales.Slice.G2) -> Tuple[float, float, float]:
    """
    Compute the quality factor Q and multiplier M for a specified band order N
    N is THE quantization parameter for the binary constant Q wavelet filters

    :param band_order_Nth: Band order, must be > 0.75 or reverts to N=3
    :param index_shift: index fo shift. Default is 0.
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: cycles M, quality factor Q, gamma
    """
    if band_order_Nth < 0.7:
        print('N<0.7 specified, using N = ', 3)
        band_order_Nth = 3.
    order_bandedge = scale_base ** (1. / 2. / band_order_Nth)  # kN in Garces 2013
    order_scaled_bandwidth = order_bandedge - 1. / order_bandedge
    quality_factor_Q = 1./order_scaled_bandwidth  # Exact for Nth octave bands
    # Gamma is M/(2Q)
    gamma = np.sqrt(np.log(2))*(1-np.log(2)*(index_shift/np.pi)**2)**(-0.5)
    cycles_M = 2*quality_factor_Q*gamma  # Exact, from 1/2 power points

    return cycles_M, quality_factor_Q, gamma


def chirp_scale(cycles_M: float,
                scale_frequency_center_hz: Union[np.ndarray, float],
                frequency_sample_rate_hz: float) -> float:
    """
    Nondimensional scale for canonical Morlet wavelet

    :param cycles_M: number of cycles per band period
    :param scale_frequency_center_hz: scale frequency in hz
    :param frequency_sample_rate_hz: sample rate in hz
    :return: scale atom
    """
    scale_atom = cycles_M*frequency_sample_rate_hz/scale_frequency_center_hz/(2. * np.pi)
    return scale_atom


def chirp_scale_from_order(band_order_Nth: float,
                           scale_frequency_center_hz: float,
                           frequency_sample_rate_hz: float,
                           index_shift: float = 0,
                           scale_base: float = scales.Slice.G2) -> float:
    """
    Nondimensional scale for canonical Morlet wavelet

    :param cycles_M: number of cycles per band period
    :param scale_frequency_center_hz: scale frequency in hz
    :param frequency_sample_rate_hz: sample rate in hz
    :param index_shift: index fo shift. Default is 0.0
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: scale atom
    """
    cycles_M, _, _ = chirp_MQG_from_N(band_order_Nth,
                                      index_shift,
                                      scale_base)
    scale_atom = cycles_M*frequency_sample_rate_hz/scale_frequency_center_hz/(2. * np.pi)
    return scale_atom


def chirp_uncertainty(scale_atom: float,
                      frequency_sample_rate_hz: float,
                      gamma: float,
                      index_shift: float) -> Tuple[float, float, float]:
    """
    Uncertainty of chirp

    :param scale_atom: from chirp_scale or chirp_scale_from_order
    :param frequency_sample_rate_hz: sample rate in hz
    :param gamma: from index_shift, M/(2Q)
    :param index_shift: index of shift
    :return: time std in seconds, frequency std in Hz, angular frequency std in Hz
    """

    time_std_s = scale_atom/np.sqr(2)/frequency_sample_rate_hz
    angular_frequency_std = np.sqrt(1+(index_shift*gamma)**2)/scale_atom/np.sqr(2)
    angular_frequency_std_hz = frequency_sample_rate_hz*angular_frequency_std
    frequency_std_hz = angular_frequency_std_hz/2/np.pi

    return time_std_s, frequency_std_hz, angular_frequency_std_hz


def chirp_p_complex(scale_atom: float,
                    gamma: float,
                    index_shift: float) -> complex:
    """
    Fundamental chirp variable

    :param scale_atom: from chirp_scale or chirp_scale_from_order
    :param gamma: from index_shift, M/(2Q)
    :param index_shift: index of shift
    :return: p_complex
    """
    p_complex = (1 - 1j*index_shift*gamma/np.pi)/(2*scale_atom**2)
    return p_complex


def chirp_amplitude(scale_atom: float,
                    gamma: float,
                    index_shift: float) -> Tuple[float, float]:
    """
    Return chirp amplitude

    :param scale_atom: from chirp_scale or chirp_scale_from_order
    :param gamma: from index_shift, M/(2Q)
    :param index_shift: index of shift
    :return: amp_dict_0, amp_dict_1
    """
    p_complex = chirp_p_complex(scale_atom, gamma, index_shift)
    amp_dict_0 = 1/np.pi**0.25 * 1/np.sqrt(scale_atom)
    amp_dict_1 = np.sqrt(np.abs(p_complex)/np.pi)
    return amp_dict_0, amp_dict_1


def chirp_time(time_s: np.ndarray,
               offset_time_s: float,
               frequency_sample_rate_hz: float) -> np.ndarray:
    """
    Scaled time-shifted time

    :param time_s: array with time
    :param offset_time_s: offset time in seconds
    :param frequency_sample_rate_hz: sample rate in Hz
    :return: numpy array with time-shifted time
    """
    xtime_shifted = frequency_sample_rate_hz*(time_s-offset_time_s)
    return xtime_shifted


def chirp_scales_from_duration(band_order_Nth : float,
                               sig_duration_s: float,
                               index_shift: float = 0.,
                               scale_base: float = scales.Slice.G2) -> Tuple[float, float]:
    """
    Calculate scale factor for time and frequency from chirp duration

    :param band_order_Nth: Band order
    :param sig_duration_s: signal duration in seconds
    :param index_shift: index fo shift. Default is 0.0
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: time in seconds and frequency in Hz scale factors
    """
    cycles_M, _, _ = chirp_MQG_from_N(band_order_Nth,
                                      index_shift,
                                      scale_base)
    scale_time_s = sig_duration_s/cycles_M
    scale_frequency_hz = 1/scale_time_s
    return scale_time_s, scale_frequency_hz


def chirp_frequency_bands(scale_order_input: float,
                          frequency_low_input: float,
                          frequency_sample_rate_input: float,
                          frequency_high_input: float,
                          index_shift: float = 0,
                          frequency_ref: float = scales.Slice.F1,
                          scale_base: float = scales.Slice.G2) -> Tuple[float, float, float, float,
                                                                        np.ndarray, np.ndarray, np.ndarray]:
    """
    Calculate frequency bands for chirp

    :param scale_order_input: Nth order specification
    :param frequency_low_input: lowest frequency of interest
    :param frequency_sample_rate_input: sample rate
    :param frequency_high_input: highest frequency of interest
    :param index_shift: index of shift
    :param frequency_ref: reference frequency
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: Nth order, cycles M, quality factor Q, gamma, geometric center of frequencies, start frequency,
        end frequency
    """

    order_Nth, scale_base, scale_band_number, \
    frequency_ref, frequency_center_algebraic, frequency_center_geometric, \
    frequency_start, frequency_end = \
        scales.band_frequencies_low_high(frequency_order_input=scale_order_input,
                                         frequency_base_input=scale_base,
                                         frequency_ref_input=frequency_ref,
                                         frequency_low_input=frequency_low_input,
                                         frequency_high_input=frequency_high_input,
                                         frequency_sample_rate_input=frequency_sample_rate_input)
    cycles_M, quality_Q, gamma = chirp_MQG_from_N(order_Nth, index_shift, scale_base)

    return order_Nth, cycles_M, quality_Q, gamma, frequency_center_geometric, frequency_start, frequency_end


def chirp_centered_4cwt(band_order_Nth: float,
                        sig_or_time: np.ndarray,
                        scale_frequency_center_hz: float,
                        frequency_sample_rate_hz: float,
                        index_shift: float = 0,
                        scale_base: float = scales.Slice.G2,
                        dictionary_type: str = "norm") -> Tuple[np.ndarray, np.ndarray]:
    """
    Gabor atoms for CWT computation centered on the duration of signal

    :param sig_or_time: time or time series, wavelet matches this duration
    :param band_order_Nth: Nth order of constant Q bands
    :param scale_frequency_center_hz: center frequency fc in Hz
    :param frequency_sample_rate_hz: sample rate is Hz
    :param index_shift: index of shift
    :param scale_base: G2 or G3
    :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect")
    :return: waveform_complex, time_shifted_s
    """

    duration_points = len(sig_or_time)
    time_s = np.arange(duration_points)/frequency_sample_rate_hz
    offset_time_s = time_s[-1]/2.

    wavelet_gabor, time_centered_s, amp_dict_0, amp_dict_1 = \
        chirp_complex(band_order_Nth,
                      time_s, offset_time_s, scale_frequency_center_hz, frequency_sample_rate_hz,
                      index_shift, scale_base)

    if dictionary_type == "norm":
        wavelet_chirp = amp_dict_0*wavelet_gabor
    elif dictionary_type == "tone":
        wavelet_chirp = np.sqrt(2)*amp_dict_1*wavelet_gabor
    else:
        wavelet_chirp = amp_dict_1*wavelet_gabor

    return wavelet_chirp, time_centered_s


def cwt_chirp_complex(band_order_Nth: float,
                      sig_wf: np.ndarray,
                      frequency_low_hz: float,
                      frequency_sample_rate_hz: float,
                      frequency_high_hz: float = scales.Slice.F0,
                      cwt_type: str = "fft",
                      index_shift: float = 0,
                      frequency_ref: float = scales.Slice.F1,
                      scale_base: float = scales.Slice.G2,
                      dictionary_type: str = "norm") -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """
    Calculate CWT for chirp

    :param band_order_Nth: Nth order of constant Q bands
    :param sig_wf: array with input signal
    :param frequency_low_hz: lowest frequency in Hz
    :param frequency_sample_rate_hz: sample rate in Hz
    :param frequency_high_hz: highest frequency in Hz
    :param cwt_type: one of "conv", "fft", or "morlet2". Default is "fft"
           Address ghost folding in "fft", compared to "conv"
    :param index_shift: index of shift. Default is 0.0
    :param frequency_ref: reference frequency in Hz. Default is F1
    :param scale_base: G2 or G3. Default is G2
    :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect"). Default is "norm"
    :return: cwt, cwt_bits, time_s, frequency_cwt_hz
    """

    wavelet_points = len(sig_wf)
    time_s = np.arange(wavelet_points)/frequency_sample_rate_hz

    if cwt_type == "morlet2":
        index_shift = 0

    # Planck frequency is absolute upper limit
    if frequency_high_hz > frequency_sample_rate_hz/2.:
        frequency_high_hz = frequency_sample_rate_hz/2.

    order_Nth, cycles_M, quality_Q, _,\
    frequency_cwt_hz_flipped, frequency_start_flipped, frequency_end_flipped = \
        chirp_frequency_bands(scale_order_input=band_order_Nth,
                              frequency_low_input=frequency_low_hz,
                              frequency_sample_rate_input=frequency_sample_rate_hz,
                              frequency_high_input=frequency_high_hz,
                              index_shift=index_shift,
                              frequency_ref=frequency_ref,
                              scale_base=scale_base)

    scale_points = len(frequency_cwt_hz_flipped)

    if cwt_type == "morlet2":
        scale_atom = chirp_scale(cycles_M, frequency_cwt_hz_flipped, frequency_sample_rate_hz)
        cwt_flipped = signal.cwt(data=sig_wf, wavelet=signal.morlet2,
                                 widths=scale_atom,
                                 w=cycles_M,
                                 dtype=np.complex128)
    elif cwt_type == "fft":
        sig_fft = np.fft.fft(sig_wf)
        cwt_flipped = np.empty((scale_points, wavelet_points), dtype=np.complex128)
        for ii in range(scale_points):
            atom, _ = chirp_centered_4cwt(band_order_Nth=order_Nth,
                                          sig_or_time=sig_wf,
                                          scale_frequency_center_hz=frequency_cwt_hz_flipped[ii],
                                          frequency_sample_rate_hz=frequency_sample_rate_hz,
                                          index_shift=index_shift,
                                          scale_base=scale_base,
                                          dictionary_type=dictionary_type)
            atom_fft = np.fft.fft(atom)
            cwt_raw = np.fft.ifft(sig_fft*np.conj(atom_fft))
            cwt_flipped[ii, :] = np.append(cwt_raw[wavelet_points//2:], cwt_raw[0:wavelet_points//2])

    elif cwt_type == "conv":
        cwt_flipped = np.empty((scale_points, wavelet_points), dtype=np.complex128)
        for ii in range(scale_points):
            atom, _ = chirp_centered_4cwt(band_order_Nth=order_Nth,
                                          sig_or_time=sig_wf,
                                          scale_frequency_center_hz=frequency_cwt_hz_flipped[ii],
                                          frequency_sample_rate_hz=frequency_sample_rate_hz,
                                          index_shift=index_shift,
                                          scale_base=scale_base,
                                          dictionary_type=dictionary_type)
            cwt_flipped[ii, :] = signal.convolve(sig_wf, np.conj(atom)[::-1], mode='same')
    else:
        print("Incorrect cwt_type specification in cwt_chirp_complex")

    # Time scales are increasing, which is the opposite of what is expected for the frequency. Flip.
    frequency_cwt_hz = np.flip(frequency_cwt_hz_flipped)
    cwt = np.flipud(cwt_flipped)
    cwt_bits = utils.log2epsilon(cwt)

    return cwt, cwt_bits, time_s, frequency_cwt_hz


def cwt_chirp_from_sig(sig_wf: np.ndarray,
                       frequency_sample_rate_hz: float,
                       band_order_Nth: float = 3,
                       cwt_type: str = "fft",
                       index_shift: float = 0,
                       frequency_ref: float = scales.Slice.F1,
                       scale_base: float = scales.Slice.G2,
                       dictionary_type: str = "norm"):
    """
    Calculate CWT for chirp

    :param sig_wf: array with input signal
    :param frequency_sample_rate_hz: sample rate in Hz
    :param band_order_Nth: Nth order of constant Q bands
    :param cwt_type: one of "conv", "fft", or "morlet2". Default is "fft"
    :param index_shift: index of shift. Default is 0.0
    :param frequency_ref: reference frequency in Hz. Default is F1
    :param scale_base: G2 or G3. Default is G2
    :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect"). Default is "norm"
    :return: cwt, cwt_bits, time_s, frequency_cwt_hz
    """

    wavelet_points = len(sig_wf)
    duration_s = wavelet_points/frequency_sample_rate_hz
    _, min_frequency_hz = \
        chirp_scales_from_duration(band_order_Nth=band_order_Nth,
                                   sig_duration_s=duration_s,
                                   index_shift=index_shift,
                                   scale_base=scale_base)

    cwt, cwt_bits, time_s, frequency_cwt_hz = \
        cwt_chirp_complex(band_order_Nth=band_order_Nth,
                          sig_wf=sig_wf,
                          frequency_low_hz=min_frequency_hz,
                          frequency_sample_rate_hz=frequency_sample_rate_hz,
                          frequency_high_hz=frequency_sample_rate_hz/2.,
                          cwt_type=cwt_type,
                          index_shift=index_shift,
                          frequency_ref=frequency_ref,
                          scale_base=scale_base,
                          dictionary_type=dictionary_type)

    return cwt, cwt_bits, time_s, frequency_cwt_hz

Functions

def chirp_MQG_from_N(band_order_Nth: float, index_shift: float = 0, scale_base: float = 2.0) ‑> Tuple[float, float, float]

Compute the quality factor Q and multiplier M for a specified band order N N is THE quantization parameter for the binary constant Q wavelet filters

:param band_order_Nth: Band order, must be > 0.75 or reverts to N=3 :param index_shift: index fo shift. Default is 0. :param scale_base: positive reference Base G > 1. Default is G2 :return: cycles M, quality factor Q, gamma

Expand source code
def chirp_MQG_from_N(band_order_Nth: float,
                     index_shift: float = 0,
                     scale_base: float = scales.Slice.G2) -> Tuple[float, float, float]:
    """
    Compute the quality factor Q and multiplier M for a specified band order N
    N is THE quantization parameter for the binary constant Q wavelet filters

    :param band_order_Nth: Band order, must be > 0.75 or reverts to N=3
    :param index_shift: index fo shift. Default is 0.
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: cycles M, quality factor Q, gamma
    """
    if band_order_Nth < 0.7:
        print('N<0.7 specified, using N = ', 3)
        band_order_Nth = 3.
    order_bandedge = scale_base ** (1. / 2. / band_order_Nth)  # kN in Garces 2013
    order_scaled_bandwidth = order_bandedge - 1. / order_bandedge
    quality_factor_Q = 1./order_scaled_bandwidth  # Exact for Nth octave bands
    # Gamma is M/(2Q)
    gamma = np.sqrt(np.log(2))*(1-np.log(2)*(index_shift/np.pi)**2)**(-0.5)
    cycles_M = 2*quality_factor_Q*gamma  # Exact, from 1/2 power points

    return cycles_M, quality_factor_Q, gamma
def chirp_amplitude(scale_atom: float, gamma: float, index_shift: float) ‑> Tuple[float, float]

Return chirp amplitude

:param scale_atom: from chirp_scale or chirp_scale_from_order :param gamma: from index_shift, M/(2Q) :param index_shift: index of shift :return: amp_dict_0, amp_dict_1

Expand source code
def chirp_amplitude(scale_atom: float,
                    gamma: float,
                    index_shift: float) -> Tuple[float, float]:
    """
    Return chirp amplitude

    :param scale_atom: from chirp_scale or chirp_scale_from_order
    :param gamma: from index_shift, M/(2Q)
    :param index_shift: index of shift
    :return: amp_dict_0, amp_dict_1
    """
    p_complex = chirp_p_complex(scale_atom, gamma, index_shift)
    amp_dict_0 = 1/np.pi**0.25 * 1/np.sqrt(scale_atom)
    amp_dict_1 = np.sqrt(np.abs(p_complex)/np.pi)
    return amp_dict_0, amp_dict_1
def chirp_centered_4cwt(band_order_Nth: float, sig_or_time: numpy.ndarray, scale_frequency_center_hz: float, frequency_sample_rate_hz: float, index_shift: float = 0, scale_base: float = 2.0, dictionary_type: str = 'norm') ‑> Tuple[numpy.ndarray, numpy.ndarray]

Gabor atoms for CWT computation centered on the duration of signal

:param sig_or_time: time or time series, wavelet matches this duration :param band_order_Nth: Nth order of constant Q bands :param scale_frequency_center_hz: center frequency fc in Hz :param frequency_sample_rate_hz: sample rate is Hz :param index_shift: index of shift :param scale_base: G2 or G3 :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect") :return: waveform_complex, time_shifted_s

Expand source code
def chirp_centered_4cwt(band_order_Nth: float,
                        sig_or_time: np.ndarray,
                        scale_frequency_center_hz: float,
                        frequency_sample_rate_hz: float,
                        index_shift: float = 0,
                        scale_base: float = scales.Slice.G2,
                        dictionary_type: str = "norm") -> Tuple[np.ndarray, np.ndarray]:
    """
    Gabor atoms for CWT computation centered on the duration of signal

    :param sig_or_time: time or time series, wavelet matches this duration
    :param band_order_Nth: Nth order of constant Q bands
    :param scale_frequency_center_hz: center frequency fc in Hz
    :param frequency_sample_rate_hz: sample rate is Hz
    :param index_shift: index of shift
    :param scale_base: G2 or G3
    :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect")
    :return: waveform_complex, time_shifted_s
    """

    duration_points = len(sig_or_time)
    time_s = np.arange(duration_points)/frequency_sample_rate_hz
    offset_time_s = time_s[-1]/2.

    wavelet_gabor, time_centered_s, amp_dict_0, amp_dict_1 = \
        chirp_complex(band_order_Nth,
                      time_s, offset_time_s, scale_frequency_center_hz, frequency_sample_rate_hz,
                      index_shift, scale_base)

    if dictionary_type == "norm":
        wavelet_chirp = amp_dict_0*wavelet_gabor
    elif dictionary_type == "tone":
        wavelet_chirp = np.sqrt(2)*amp_dict_1*wavelet_gabor
    else:
        wavelet_chirp = amp_dict_1*wavelet_gabor

    return wavelet_chirp, time_centered_s
def chirp_complex(band_order_Nth: float, time_s: numpy.ndarray, offset_time_s: float, scale_frequency_center_hz: float, frequency_sample_rate_hz: float, index_shift: float = 0, scale_base: float = 2.0) ‑> Tuple[numpy.ndarray, numpy.ndarray, float, float]

Quantum chirp for specified band_order_Nth and arbitrary time duration Unscaled, to be used by both Dictionary 1 and Dictionary 2

:param band_order_Nth: Nth order of constant Q bands :param time_s: time in seconds, duration should be greater than or equal to M/fc :param offset_time_s: offset time in seconds, should be between min and max of time_s :param scale_frequency_center_hz: center frequency fc in Hz :param frequency_sample_rate_hz: sample rate on Hz :param index_shift: Redshift = -1, Blueshift = +1, None=0 :param scale_base: G2 or G3 :return: waveform_complex, time_shifted_s

Expand source code
def chirp_complex(band_order_Nth: float,
                  time_s: np.ndarray,
                  offset_time_s: float,
                  scale_frequency_center_hz: float,
                  frequency_sample_rate_hz: float,
                  index_shift: float = 0,
                  scale_base: float = scales.Slice.G2) -> Tuple[np.ndarray, np.ndarray, float, float]:
    """
    Quantum chirp for specified band_order_Nth and arbitrary time duration
    Unscaled, to be used by both Dictionary 1 and Dictionary 2

    :param band_order_Nth: Nth order of constant Q bands
    :param time_s: time in seconds, duration should be greater than or equal to M/fc
    :param offset_time_s: offset time in seconds, should be between min and max of time_s
    :param scale_frequency_center_hz: center frequency fc in Hz
    :param frequency_sample_rate_hz: sample rate on Hz
    :param index_shift: Redshift = -1, Blueshift = +1, None=0
    :param scale_base: G2 or G3
    :return: waveform_complex, time_shifted_s
    """

    xtime_shifted = chirp_time(time_s, offset_time_s, frequency_sample_rate_hz)
    time_shifted_s = xtime_shifted/frequency_sample_rate_hz

    # Fundamental chirp parameters
    cycles_M, quality_factor_Q, gamma = \
        chirp_MQG_from_N(band_order_Nth, index_shift, scale_base)
    scale_atom = chirp_scale(cycles_M, scale_frequency_center_hz, frequency_sample_rate_hz)
    p_complex = chirp_p_complex(scale_atom, gamma, index_shift)
    amp_dict_0, amp_dict_1 = chirp_amplitude(scale_atom, gamma, index_shift)

    wavelet_gauss = np.exp(-p_complex * xtime_shifted**2)
    wavelet_gabor = wavelet_gauss * np.exp(1j * cycles_M*xtime_shifted/scale_atom)

    return wavelet_gabor, time_shifted_s, amp_dict_0, amp_dict_1
def chirp_frequency_bands(scale_order_input: float, frequency_low_input: float, frequency_sample_rate_input: float, frequency_high_input: float, index_shift: float = 0, frequency_ref: float = 1.0, scale_base: float = 2.0) ‑> Tuple[float, float, float, float, numpy.ndarray, numpy.ndarray, numpy.ndarray]

Calculate frequency bands for chirp

:param scale_order_input: Nth order specification :param frequency_low_input: lowest frequency of interest :param frequency_sample_rate_input: sample rate :param frequency_high_input: highest frequency of interest :param index_shift: index of shift :param frequency_ref: reference frequency :param scale_base: positive reference Base G > 1. Default is G2 :return: Nth order, cycles M, quality factor Q, gamma, geometric center of frequencies, start frequency, end frequency

Expand source code
def chirp_frequency_bands(scale_order_input: float,
                          frequency_low_input: float,
                          frequency_sample_rate_input: float,
                          frequency_high_input: float,
                          index_shift: float = 0,
                          frequency_ref: float = scales.Slice.F1,
                          scale_base: float = scales.Slice.G2) -> Tuple[float, float, float, float,
                                                                        np.ndarray, np.ndarray, np.ndarray]:
    """
    Calculate frequency bands for chirp

    :param scale_order_input: Nth order specification
    :param frequency_low_input: lowest frequency of interest
    :param frequency_sample_rate_input: sample rate
    :param frequency_high_input: highest frequency of interest
    :param index_shift: index of shift
    :param frequency_ref: reference frequency
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: Nth order, cycles M, quality factor Q, gamma, geometric center of frequencies, start frequency,
        end frequency
    """

    order_Nth, scale_base, scale_band_number, \
    frequency_ref, frequency_center_algebraic, frequency_center_geometric, \
    frequency_start, frequency_end = \
        scales.band_frequencies_low_high(frequency_order_input=scale_order_input,
                                         frequency_base_input=scale_base,
                                         frequency_ref_input=frequency_ref,
                                         frequency_low_input=frequency_low_input,
                                         frequency_high_input=frequency_high_input,
                                         frequency_sample_rate_input=frequency_sample_rate_input)
    cycles_M, quality_Q, gamma = chirp_MQG_from_N(order_Nth, index_shift, scale_base)

    return order_Nth, cycles_M, quality_Q, gamma, frequency_center_geometric, frequency_start, frequency_end
def chirp_p_complex(scale_atom: float, gamma: float, index_shift: float) ‑> complex

Fundamental chirp variable

:param scale_atom: from chirp_scale or chirp_scale_from_order :param gamma: from index_shift, M/(2Q) :param index_shift: index of shift :return: p_complex

Expand source code
def chirp_p_complex(scale_atom: float,
                    gamma: float,
                    index_shift: float) -> complex:
    """
    Fundamental chirp variable

    :param scale_atom: from chirp_scale or chirp_scale_from_order
    :param gamma: from index_shift, M/(2Q)
    :param index_shift: index of shift
    :return: p_complex
    """
    p_complex = (1 - 1j*index_shift*gamma/np.pi)/(2*scale_atom**2)
    return p_complex
def chirp_scale(cycles_M: float, scale_frequency_center_hz: Union[numpy.ndarray, float], frequency_sample_rate_hz: float) ‑> float

Nondimensional scale for canonical Morlet wavelet

:param cycles_M: number of cycles per band period :param scale_frequency_center_hz: scale frequency in hz :param frequency_sample_rate_hz: sample rate in hz :return: scale atom

Expand source code
def chirp_scale(cycles_M: float,
                scale_frequency_center_hz: Union[np.ndarray, float],
                frequency_sample_rate_hz: float) -> float:
    """
    Nondimensional scale for canonical Morlet wavelet

    :param cycles_M: number of cycles per band period
    :param scale_frequency_center_hz: scale frequency in hz
    :param frequency_sample_rate_hz: sample rate in hz
    :return: scale atom
    """
    scale_atom = cycles_M*frequency_sample_rate_hz/scale_frequency_center_hz/(2. * np.pi)
    return scale_atom
def chirp_scale_from_order(band_order_Nth: float, scale_frequency_center_hz: float, frequency_sample_rate_hz: float, index_shift: float = 0, scale_base: float = 2.0) ‑> float

Nondimensional scale for canonical Morlet wavelet

:param cycles_M: number of cycles per band period :param scale_frequency_center_hz: scale frequency in hz :param frequency_sample_rate_hz: sample rate in hz :param index_shift: index fo shift. Default is 0.0 :param scale_base: positive reference Base G > 1. Default is G2 :return: scale atom

Expand source code
def chirp_scale_from_order(band_order_Nth: float,
                           scale_frequency_center_hz: float,
                           frequency_sample_rate_hz: float,
                           index_shift: float = 0,
                           scale_base: float = scales.Slice.G2) -> float:
    """
    Nondimensional scale for canonical Morlet wavelet

    :param cycles_M: number of cycles per band period
    :param scale_frequency_center_hz: scale frequency in hz
    :param frequency_sample_rate_hz: sample rate in hz
    :param index_shift: index fo shift. Default is 0.0
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: scale atom
    """
    cycles_M, _, _ = chirp_MQG_from_N(band_order_Nth,
                                      index_shift,
                                      scale_base)
    scale_atom = cycles_M*frequency_sample_rate_hz/scale_frequency_center_hz/(2. * np.pi)
    return scale_atom
def chirp_scales_from_duration(band_order_Nth: float, sig_duration_s: float, index_shift: float = 0.0, scale_base: float = 2.0) ‑> Tuple[float, float]

Calculate scale factor for time and frequency from chirp duration

:param band_order_Nth: Band order :param sig_duration_s: signal duration in seconds :param index_shift: index fo shift. Default is 0.0 :param scale_base: positive reference Base G > 1. Default is G2 :return: time in seconds and frequency in Hz scale factors

Expand source code
def chirp_scales_from_duration(band_order_Nth : float,
                               sig_duration_s: float,
                               index_shift: float = 0.,
                               scale_base: float = scales.Slice.G2) -> Tuple[float, float]:
    """
    Calculate scale factor for time and frequency from chirp duration

    :param band_order_Nth: Band order
    :param sig_duration_s: signal duration in seconds
    :param index_shift: index fo shift. Default is 0.0
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: time in seconds and frequency in Hz scale factors
    """
    cycles_M, _, _ = chirp_MQG_from_N(band_order_Nth,
                                      index_shift,
                                      scale_base)
    scale_time_s = sig_duration_s/cycles_M
    scale_frequency_hz = 1/scale_time_s
    return scale_time_s, scale_frequency_hz
def chirp_spectrum(frequency_hz: numpy.ndarray, offset_time_s: float, band_order_Nth: float, scale_frequency_center_hz: float, frequency_sample_rate_hz: float, index_shift: float = 0, scale_base: float = 2.0) ‑> Tuple[Union[complex, float, numpy.ndarray], numpy.ndarray]

Spectrum of quantum wavelet for specified band_order_Nth and arbitrary time duration

:param frequency_hz: frequency range below Nyquist :param offset_time_s: time of wavelet centroid :param band_order_Nth: Nth order of constant Q bands :param scale_frequency_center_hz: band center frequency in Hz :param frequency_sample_rate_hz: sample rate on Hz :param index_shift: index of shift. Default is 0.0 :param scale_base: positive reference Base G > 1. Default is G2 :return: Fourier transform of the Gabor atom

Expand source code
def chirp_spectrum(frequency_hz: np.ndarray,
                   offset_time_s: float,
                   band_order_Nth: float,
                   scale_frequency_center_hz: float,
                   frequency_sample_rate_hz: float,
                   index_shift: float = 0,
                   scale_base: float = scales.Slice.G2) -> Tuple[Union[complex, float, np.ndarray], np.ndarray]:
    """
    Spectrum of quantum wavelet for specified band_order_Nth and arbitrary time duration

    :param frequency_hz: frequency range below Nyquist
    :param offset_time_s: time of wavelet centroid
    :param band_order_Nth: Nth order of constant Q bands
    :param scale_frequency_center_hz: band center frequency in Hz
    :param frequency_sample_rate_hz: sample rate on Hz
    :param index_shift: index of shift. Default is 0.0
    :param scale_base: positive reference Base G > 1. Default is G2
    :return: Fourier transform of the Gabor atom
    """

    cycles_M, quality_factor_Q, gamma = \
        chirp_MQG_from_N(band_order_Nth, index_shift, scale_base)
    scale_atom = chirp_scale(cycles_M, scale_frequency_center_hz, frequency_sample_rate_hz)
    p_complex = chirp_p_complex(scale_atom, gamma, index_shift)

    angular_frequency_center = 2 * np.pi * scale_frequency_center_hz/frequency_sample_rate_hz
    angular_frequency = 2 * np.pi * frequency_hz/frequency_sample_rate_hz
    offset_phase = angular_frequency * frequency_sample_rate_hz * offset_time_s
    angular_frequency_shifted = angular_frequency - angular_frequency_center
    frequency_shifted_hz = angular_frequency_shifted*frequency_sample_rate_hz/(2*np.pi)

    spectrum_amplitude = np.sqrt(np.pi/p_complex)
    gauss_arg = 1./(4*p_complex)
    spectrum_gauss = np.exp(-gauss_arg * (angular_frequency_shifted * scale_atom) ** 2)
    # Phase shift from time offset
    spectrum_gabor = spectrum_amplitude * spectrum_gauss * np.exp(-1j * offset_phase)

    return spectrum_gabor, frequency_shifted_hz
def chirp_time(time_s: numpy.ndarray, offset_time_s: float, frequency_sample_rate_hz: float) ‑> numpy.ndarray

Scaled time-shifted time

:param time_s: array with time :param offset_time_s: offset time in seconds :param frequency_sample_rate_hz: sample rate in Hz :return: numpy array with time-shifted time

Expand source code
def chirp_time(time_s: np.ndarray,
               offset_time_s: float,
               frequency_sample_rate_hz: float) -> np.ndarray:
    """
    Scaled time-shifted time

    :param time_s: array with time
    :param offset_time_s: offset time in seconds
    :param frequency_sample_rate_hz: sample rate in Hz
    :return: numpy array with time-shifted time
    """
    xtime_shifted = frequency_sample_rate_hz*(time_s-offset_time_s)
    return xtime_shifted
def chirp_uncertainty(scale_atom: float, frequency_sample_rate_hz: float, gamma: float, index_shift: float) ‑> Tuple[float, float, float]

Uncertainty of chirp

:param scale_atom: from chirp_scale or chirp_scale_from_order :param frequency_sample_rate_hz: sample rate in hz :param gamma: from index_shift, M/(2Q) :param index_shift: index of shift :return: time std in seconds, frequency std in Hz, angular frequency std in Hz

Expand source code
def chirp_uncertainty(scale_atom: float,
                      frequency_sample_rate_hz: float,
                      gamma: float,
                      index_shift: float) -> Tuple[float, float, float]:
    """
    Uncertainty of chirp

    :param scale_atom: from chirp_scale or chirp_scale_from_order
    :param frequency_sample_rate_hz: sample rate in hz
    :param gamma: from index_shift, M/(2Q)
    :param index_shift: index of shift
    :return: time std in seconds, frequency std in Hz, angular frequency std in Hz
    """

    time_std_s = scale_atom/np.sqr(2)/frequency_sample_rate_hz
    angular_frequency_std = np.sqrt(1+(index_shift*gamma)**2)/scale_atom/np.sqr(2)
    angular_frequency_std_hz = frequency_sample_rate_hz*angular_frequency_std
    frequency_std_hz = angular_frequency_std_hz/2/np.pi

    return time_std_s, frequency_std_hz, angular_frequency_std_hz
def cwt_chirp_complex(band_order_Nth: float, sig_wf: numpy.ndarray, frequency_low_hz: float, frequency_sample_rate_hz: float, frequency_high_hz: float = 1e+42, cwt_type: str = 'fft', index_shift: float = 0, frequency_ref: float = 1.0, scale_base: float = 2.0, dictionary_type: str = 'norm') ‑> Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray, numpy.ndarray]

Calculate CWT for chirp

:param band_order_Nth: Nth order of constant Q bands :param sig_wf: array with input signal :param frequency_low_hz: lowest frequency in Hz :param frequency_sample_rate_hz: sample rate in Hz :param frequency_high_hz: highest frequency in Hz :param cwt_type: one of "conv", "fft", or "morlet2". Default is "fft" Address ghost folding in "fft", compared to "conv" :param index_shift: index of shift. Default is 0.0 :param frequency_ref: reference frequency in Hz. Default is F1 :param scale_base: G2 or G3. Default is G2 :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect"). Default is "norm" :return: cwt, cwt_bits, time_s, frequency_cwt_hz

Expand source code
def cwt_chirp_complex(band_order_Nth: float,
                      sig_wf: np.ndarray,
                      frequency_low_hz: float,
                      frequency_sample_rate_hz: float,
                      frequency_high_hz: float = scales.Slice.F0,
                      cwt_type: str = "fft",
                      index_shift: float = 0,
                      frequency_ref: float = scales.Slice.F1,
                      scale_base: float = scales.Slice.G2,
                      dictionary_type: str = "norm") -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """
    Calculate CWT for chirp

    :param band_order_Nth: Nth order of constant Q bands
    :param sig_wf: array with input signal
    :param frequency_low_hz: lowest frequency in Hz
    :param frequency_sample_rate_hz: sample rate in Hz
    :param frequency_high_hz: highest frequency in Hz
    :param cwt_type: one of "conv", "fft", or "morlet2". Default is "fft"
           Address ghost folding in "fft", compared to "conv"
    :param index_shift: index of shift. Default is 0.0
    :param frequency_ref: reference frequency in Hz. Default is F1
    :param scale_base: G2 or G3. Default is G2
    :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect"). Default is "norm"
    :return: cwt, cwt_bits, time_s, frequency_cwt_hz
    """

    wavelet_points = len(sig_wf)
    time_s = np.arange(wavelet_points)/frequency_sample_rate_hz

    if cwt_type == "morlet2":
        index_shift = 0

    # Planck frequency is absolute upper limit
    if frequency_high_hz > frequency_sample_rate_hz/2.:
        frequency_high_hz = frequency_sample_rate_hz/2.

    order_Nth, cycles_M, quality_Q, _,\
    frequency_cwt_hz_flipped, frequency_start_flipped, frequency_end_flipped = \
        chirp_frequency_bands(scale_order_input=band_order_Nth,
                              frequency_low_input=frequency_low_hz,
                              frequency_sample_rate_input=frequency_sample_rate_hz,
                              frequency_high_input=frequency_high_hz,
                              index_shift=index_shift,
                              frequency_ref=frequency_ref,
                              scale_base=scale_base)

    scale_points = len(frequency_cwt_hz_flipped)

    if cwt_type == "morlet2":
        scale_atom = chirp_scale(cycles_M, frequency_cwt_hz_flipped, frequency_sample_rate_hz)
        cwt_flipped = signal.cwt(data=sig_wf, wavelet=signal.morlet2,
                                 widths=scale_atom,
                                 w=cycles_M,
                                 dtype=np.complex128)
    elif cwt_type == "fft":
        sig_fft = np.fft.fft(sig_wf)
        cwt_flipped = np.empty((scale_points, wavelet_points), dtype=np.complex128)
        for ii in range(scale_points):
            atom, _ = chirp_centered_4cwt(band_order_Nth=order_Nth,
                                          sig_or_time=sig_wf,
                                          scale_frequency_center_hz=frequency_cwt_hz_flipped[ii],
                                          frequency_sample_rate_hz=frequency_sample_rate_hz,
                                          index_shift=index_shift,
                                          scale_base=scale_base,
                                          dictionary_type=dictionary_type)
            atom_fft = np.fft.fft(atom)
            cwt_raw = np.fft.ifft(sig_fft*np.conj(atom_fft))
            cwt_flipped[ii, :] = np.append(cwt_raw[wavelet_points//2:], cwt_raw[0:wavelet_points//2])

    elif cwt_type == "conv":
        cwt_flipped = np.empty((scale_points, wavelet_points), dtype=np.complex128)
        for ii in range(scale_points):
            atom, _ = chirp_centered_4cwt(band_order_Nth=order_Nth,
                                          sig_or_time=sig_wf,
                                          scale_frequency_center_hz=frequency_cwt_hz_flipped[ii],
                                          frequency_sample_rate_hz=frequency_sample_rate_hz,
                                          index_shift=index_shift,
                                          scale_base=scale_base,
                                          dictionary_type=dictionary_type)
            cwt_flipped[ii, :] = signal.convolve(sig_wf, np.conj(atom)[::-1], mode='same')
    else:
        print("Incorrect cwt_type specification in cwt_chirp_complex")

    # Time scales are increasing, which is the opposite of what is expected for the frequency. Flip.
    frequency_cwt_hz = np.flip(frequency_cwt_hz_flipped)
    cwt = np.flipud(cwt_flipped)
    cwt_bits = utils.log2epsilon(cwt)

    return cwt, cwt_bits, time_s, frequency_cwt_hz
def cwt_chirp_from_sig(sig_wf: numpy.ndarray, frequency_sample_rate_hz: float, band_order_Nth: float = 3, cwt_type: str = 'fft', index_shift: float = 0, frequency_ref: float = 1.0, scale_base: float = 2.0, dictionary_type: str = 'norm')

Calculate CWT for chirp

:param sig_wf: array with input signal :param frequency_sample_rate_hz: sample rate in Hz :param band_order_Nth: Nth order of constant Q bands :param cwt_type: one of "conv", "fft", or "morlet2". Default is "fft" :param index_shift: index of shift. Default is 0.0 :param frequency_ref: reference frequency in Hz. Default is F1 :param scale_base: G2 or G3. Default is G2 :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect"). Default is "norm" :return: cwt, cwt_bits, time_s, frequency_cwt_hz

Expand source code
def cwt_chirp_from_sig(sig_wf: np.ndarray,
                       frequency_sample_rate_hz: float,
                       band_order_Nth: float = 3,
                       cwt_type: str = "fft",
                       index_shift: float = 0,
                       frequency_ref: float = scales.Slice.F1,
                       scale_base: float = scales.Slice.G2,
                       dictionary_type: str = "norm"):
    """
    Calculate CWT for chirp

    :param sig_wf: array with input signal
    :param frequency_sample_rate_hz: sample rate in Hz
    :param band_order_Nth: Nth order of constant Q bands
    :param cwt_type: one of "conv", "fft", or "morlet2". Default is "fft"
    :param index_shift: index of shift. Default is 0.0
    :param frequency_ref: reference frequency in Hz. Default is F1
    :param scale_base: G2 or G3. Default is G2
    :param dictionary_type: Canonical unit-norm ("norm") or unit spectrum ("spect"). Default is "norm"
    :return: cwt, cwt_bits, time_s, frequency_cwt_hz
    """

    wavelet_points = len(sig_wf)
    duration_s = wavelet_points/frequency_sample_rate_hz
    _, min_frequency_hz = \
        chirp_scales_from_duration(band_order_Nth=band_order_Nth,
                                   sig_duration_s=duration_s,
                                   index_shift=index_shift,
                                   scale_base=scale_base)

    cwt, cwt_bits, time_s, frequency_cwt_hz = \
        cwt_chirp_complex(band_order_Nth=band_order_Nth,
                          sig_wf=sig_wf,
                          frequency_low_hz=min_frequency_hz,
                          frequency_sample_rate_hz=frequency_sample_rate_hz,
                          frequency_high_hz=frequency_sample_rate_hz/2.,
                          cwt_type=cwt_type,
                          index_shift=index_shift,
                          frequency_ref=frequency_ref,
                          scale_base=scale_base,
                          dictionary_type=dictionary_type)

    return cwt, cwt_bits, time_s, frequency_cwt_hz