Source code for PyDynamic.uncertainty.propagate_multiplication

"""This module assists in uncertainty propagation for multiplication tasks

The multiplication of signals is a common operation in signal and data
processing.

This module contains the following functions:

* :func:`hadamar_product`: Elementwise Multiplication of two signals
* :func:`window_application`: Application of a real-valued window to a complex signal
"""

__all__ = ["hadamar_product", "window_application"]

import numpy as np
from scipy.linalg import block_diag
from typing import Optional

from .propagate_convolution import _ensure_cov_matrix
from ..misc.tools import complex_2_real_imag


[docs] def hadamar_product( x1: np.ndarray, U1: Optional[np.ndarray], x2: np.ndarray, U2: Optional[np.ndarray], real_valued: bool = False, ): """Hadamar product of two uncorrelated signals with uncertainty propagation This is also known as elementwise multiplication. By default, both input signals are assumed to represent a complex signal, where the real and imaginary part are concatenated into a single vector: [Re(x), Im(x)] Parameters ---------- x1 : np.ndarray, (2N,) or (N,) first input signal U1 : np.ndarray, (2N, 2N), (N, N) or (2N,), (N,) - 1D-array: standard uncertainties associated with x1 (corresponding to uncorrelated entries of x1) - 2D-array: full 2D-covariance matrix associated with x1 - None: corresponds to a fully certain signal x1, results in more efficient calculation (compared to using np.zeros(...)) x2 : np.ndarray, (2N,) or (N,) second input signal, same length as x1 U2 : np.ndarray, (2N, 2N), (N, N) or (2N,), (N,) - 2D-array: full 2D-covariance matrix associated with x2 - 1D-array: standard uncertainties associated with x2 (corresponding to uncorrelated entries of x2) - None: corresponds to a fully certain signal x2, results in more efficient calculation (compared to using np.zeros(...)) real_valued : bool, optional By default, both input signals are assumed to represent a complex signal, where the real and imaginary part are concatenated into a single vector [Re(x), Im(x)]. Alternatively, if both represent purely real signals, performance gains can be achieved by enabling this switch. Returns ------- prod : np.ndarray, (2N,) or (N,) multiplied output signal Uprod : np.ndarray, (2N, 2N) or (N, N) full 2D-covariance matrix of output prod References ---------- .. seealso:: :func:`np.multiply` """ # check input lengths assert len(x1) == len(x2) # deal with std-unc-only case U1, U2 = _ensure_cov_matrix(U1), _ensure_cov_matrix(U2) # simplified calculations for real-valued signals if real_valued: N = len(x1) prod = x1 * x2 cov_prod = np.zeros((N, N)) if isinstance(U1, np.ndarray): cov_prod += np.atleast_2d(x2).T * U1 * np.atleast_2d(x2) if isinstance(U2, np.ndarray): cov_prod += np.atleast_2d(x1).T * U2 * np.atleast_2d(x1) else: N = len(x1) // 2 # main calculations prod = np.empty_like(x1) prod[:N] = x1[:N] * x2[:N] - x1[N:] * x2[N:] prod[N:] = x1[N:] * x2[:N] + x1[:N] * x2[N:] # cov calculations cov_prod = np.zeros((2 * N, 2 * N)) if isinstance(U1, np.ndarray): x2r = np.atleast_2d(x2[:N]) x2i = np.atleast_2d(x2[N:]) U1rr = U1[:N, :N] U1ri = U1[:N, N:] U1ir = U1[N:, :N] U1ii = U1[N:, N:] cov_prod[:N, :N] += ( +x2r.T * U1rr * x2r - x2r.T * U1ri * x2i - x2i.T * U1ir * x2r + x2i.T * U1ii * x2i ) cov_prod[:N, N:] += ( +x2r.T * U1rr * x2i + x2r.T * U1ri * x2r - x2i.T * U1ir * x2i - x2i.T * U1ii * x2r ) cov_prod[N:, :N] = cov_prod[:N, N:].T cov_prod[N:, N:] += ( +x2i.T * U1rr * x2i + x2i.T * U1ri * x2r + x2r.T * U1ir * x2i + x2r.T * U1ii * x2r ) if isinstance(U2, np.ndarray): x1r = np.atleast_2d(x1[:N]) x1i = np.atleast_2d(x1[N:]) U2rr = U2[:N, :N] U2ri = U2[:N, N:] U2ir = U2[N:, :N] U2ii = U2[N:, N:] cov_prod[:N, :N] += ( +x1r.T * U2rr * x1r - x1r.T * U2ri * x1i - x1i.T * U2ir * x1r + x1i.T * U2ii * x1i ) cov_prod[:N, N:] += ( +x1r.T * U2rr * x1i + x1r.T * U2ri * x1r - x1i.T * U2ir * x1i - x1i.T * U2ii * x1r ) cov_prod[N:, :N] = cov_prod[:N, N:].T cov_prod[N:, N:] += ( +x1i.T * U2rr * x1i + x1i.T * U2ri * x1r + x1r.T * U2ir * x1i + x1r.T * U2ii * x1r ) return prod, cov_prod
[docs] def window_application( A: np.ndarray, W: np.ndarray, cov_A: Optional[np.ndarray] = None, cov_W: Optional[np.ndarray] = None, ): """Application of a real window to a complex signal Parameters ---------- A : np.ndarray, (2N,) signal the window will be applied to W : np.ndarray, (N,) window cov_A : np.ndarray, (2N,2N) or (2N,) - 2D-array: full 2D-covariance matrix associated with A - 1D-array: standard uncertainties associated with A (corresponding to uncorrelated entries of A) - None: corresponds to a fully certain signal A, results in more efficient calculation (compared to using np.zeros(...)) cov_W : np.ndarray, (N, N) or (N,) - 2D-array: full 2D-covariance matrix associated with W - 1D-array: standard uncertainties associated with W (corresponding to uncorrelated entries of W) - None: corresponds to a fully certain signal W, results in more efficient calculation (compared to using np.zeros(...)) Returns ------- y : np.ndarray signal with window applied Uy : np.ndarray full 2D-covariance matrix of windowed signal References ---------- .. seealso:: :func:`np.multiply` """ # deal with std-unc-only case cov_A, cov_W = _ensure_cov_matrix(cov_A), _ensure_cov_matrix(cov_W) # map to variables of hadamar product x2 = complex_2_real_imag(W) if isinstance(cov_W, np.ndarray): U2 = block_diag(cov_W, np.zeros_like(cov_W)) else: U2 = None return hadamar_product(A, cov_A, x2, U2)