def _conduct_FIRuncFilter_and_MonteCarlo(Ub, b1, blow, kind, runs, sigma_noise,
                                         x):
    y, Uy = FIRuncFilter(x, sigma_noise, b1, Ub, blow=blow,
                         kind=kind)  # apply uncertain FIR filter (GUM formula)
    yMC, UyMC = MC(x, sigma_noise, b1, np.ones(1), Ub, runs=runs,
                   blow=blow)  # apply uncertain FIR filter (Monte Carlo)
    return Uy, UyMC, y, yMC
Example #2
0
def test_FIRuncFilter_MC_uncertainty_comparison(filters, signals, lowpasses):
    # Check output for thinkable permutations of input parameters against a Monte Carlo approach.

    # run method
    y_fir, Uy_fir = FIRuncFilter(**filters,
                                 **signals,
                                 **lowpasses,
                                 return_full_covariance=True)

    # run Monte Carlo simulation of an FIR
    ## adjust input to match conventions of MC
    x = signals["y"]
    ux = signals["sigma_noise"]

    b = filters["theta"]
    a = [1.0]
    if isinstance(filters["Utheta"], np.ndarray):
        Uab = filters["Utheta"]
    else:  # Utheta == None
        Uab = np.zeros(
            (len(b), len(b)))  # MC-method cant deal with Utheta = None

    blow = lowpasses["blow"]
    if isinstance(blow, np.ndarray):
        n_blow = len(blow)
    else:
        n_blow = 0

    ## run FIR with MC and extract diagonal of returned covariance
    y_mc, Uy_mc = MC(x, ux, b, a, Uab, blow=blow, runs=2000)

    # HACK for visualization during debugging
    # import matplotlib.pyplot as plt
    # fig, ax = plt.subplots(nrows=1, ncols=3)
    # ax[0].plot(y_fir, label="fir")
    # ax[0].plot(y_mc, label="mc")
    # ax[0].set_title("filter: {0}, signal: {1}".format(len(b), len(x)))
    # ax[0].legend()
    # ax[1].imshow(Uy_fir)
    # ax[1].set_title("FIR")
    # ax[2].imshow(Uy_mc)
    # ax[2].set_title("MC")
    # plt.show()
    # /HACK

    # check basic properties
    assert np.all(np.diag(Uy_fir) >= 0)
    assert np.all(np.diag(Uy_mc) >= 0)
    assert Uy_fir.shape == Uy_mc.shape

    # approximative comparison after swing-in of MC-result
    # (which is after the combined length of blow and b)
    assert np.allclose(
        Uy_fir[len(b) + n_blow:, len(b) + n_blow:],
        Uy_mc[len(b) + n_blow:, len(b) + n_blow:],
        atol=2e-1 *
        Uy_fir.max(),  # very broad check, increase runs for better fit
        rtol=1e-1,
    )
def test_FIRuncFilter_float():

    # input signal + run methods
    x = rect(time,100*Ts,250*Ts,1.0,noise=sigma_noise)

    # apply uncertain FIR filter (GUM formula)
    for blow in [None, b2]:
        y, Uy = FIRuncFilter(x, sigma_noise, b1, Ub, blow=blow, kind="float")
        assert len(y) == len(x)
        assert len(Uy) == len(x)
def test_FIRuncFilter_diag():
    sigma_diag = sigma_noise * ( 1 + np.heaviside(np.arange(len(time)) - len(time)//2,0) )    # std doubles after half of the time
    noise = sigma_diag * white_gaussian(len(time))

    # input signal + run methods
    x = rect(time,100*Ts,250*Ts,1.0,noise=noise)

    # apply uncertain FIR filter (GUM formula)
    for blow in [None, b2]:
        y, Uy = FIRuncFilter(x, sigma_diag, b1, Ub, blow=blow, kind="diag")
        assert len(y) == len(x)
        assert len(Uy) == len(x)
Example #5
0
def test_FIRuncFilter_legacy_comparison(filters, signals, lowpasses):
    # Compare output of both functions for thinkable permutations of input parameters.
    y, Uy = legacy_FIRuncFilter(**filters, **signals, **lowpasses)
    y2, Uy2 = FIRuncFilter(**filters, **signals, **lowpasses)

    # check output dimensions
    assert len(y2) == len(signals["y"])
    assert Uy2.shape == (len(signals["y"]), )

    # check value identity
    assert np.allclose(y, y2)
    assert np.allclose(Uy, Uy2)
Example #6
0
def test_FIR_IIR_identity(kind, fir_filter, input_signal):

    # run signal through both implementations
    y_iir, Uy_iir, _ = IIRuncFilter(*input_signal.values(), **fir_filter, kind=kind)
    y_fir, Uy_fir = FIRuncFilter(
        *input_signal.values(),
        theta=fir_filter["b"],
        Utheta=fir_filter["Uab"],
        kind=kind,
    )

    assert_allclose(y_fir, y_iir)
    assert_allclose(Uy_fir, Uy_iir)
def test_FIRuncFilter_corr():

    # get an instance of noise, the covariance and the covariance-matrix with the specified color
    color = "white"
    noise = power_law_noise(N=nTime, color_value=color, std=sigma_noise)
    Ux = power_law_acf(nTime, color_value=color, std=sigma_noise)

    # input signal
    x = rect(time,100*Ts,250*Ts,1.0,noise=noise)

    # apply uncertain FIR filter (GUM formula)
    for blow in [None, b2]:
        y, Uy = FIRuncFilter(x, Ux, b1, Ub, blow=blow, kind="corr")
        assert len(y) == len(x)
        assert len(Uy) == len(x)
Example #8
0
def test_FIRuncFilter_equality(equal_filters, equal_signals):
    # Check expected output for being identical across different equivalent input
    # parameter cases.
    all_y = []
    all_uy = []

    # run all combinations of filter and signals
    for (f, s) in itertools.product(equal_filters, equal_signals):
        y, uy = FIRuncFilter(**f, **s)
        all_y.append(y)
        all_uy.append(uy)

    # check that all have the same output, as they are supposed to represent equal cases
    for a, b in itertools.combinations(all_y, 2):
        assert np.allclose(a, b)

    for a, b in itertools.combinations(all_uy, 2):
        assert np.allclose(a, b)
def evaluate():
    if covariance_case == "full":
        U_theta = np.diag(np.full_like(theta, sigma_noise))
    elif covariance_case == "zero":
        len_theta = len(theta)
        U_theta = np.zeros((len_theta, len_theta))
    elif covariance_case == "none":
        U_theta = None
    else:
        raise NotImplementedError

    # measure runtime (timeit.timeit is unable to use both local+global variables)
    start_time = time.time()
    current_y, current_Uy = FIRuncFilter(signal,
                                         sigma_noise,
                                         theta,
                                         U_theta,
                                         blow=None)
    end_time = time.time()

    return end_time - start_time, current_y, current_Uy
Example #10
0
    def apply_filter(self,
                     b,
                     a=1,
                     filter_uncertainty=None,
                     MonteCarloRuns=None):
        """Apply digital filter (b,a) to the signal values and propagate the uncertainty associated with the signal
		Parameters
		----------
			b: np.ndarray
				filter numerator coefficients
			a: np.ndarray
				filter denominator coefficients, use a=1 for FIR-type filter
			filter_uncertainty: np.ndarray
				covariance matrix associated with filter coefficients, Uab=None if no uncertainty associated with filter
			MonteCarloRuns: int
				number of Monte Carlo runs, if `None` then GUM linearization will be used
		Returns
		-------
			no return variables
		"""
        if isinstance(a, list):
            a = np.array(a)
        if not (isinstance(a, np.ndarray)):  # FIR type filter
            if len(self.uncertainty.shape) == 1:
                if not isinstance(MonteCarloRuns, int):
                    self.values, self.uncertainty = \
                     FIRuncFilter(self.values, self.uncertainty, b, Utheta = filter_uncertainty)
                else:
                    self.values, self.uncertainty = \
                     MC(self.values, self.uncertainty, b, a, filter_uncertainty, runs=MonteCarloRuns)
            else:
                if not isinstance(MonteCarloRuns, int):
                    MonteCarloRuns = 10000
                self.values, self.uncertainty = \
                 MC(self.values, self.uncertainty, b, a, filter_uncertainty, runs=MonteCarloRuns)
        else:  # IIR-type filter
            if not isinstance(MonteCarloRuns, int):
                MonteCarloRuns = 10000
                self.values, self.uncertainty = \
                 MC(self.values, self.uncertainty, b, a, filter_uncertainty, runs=MonteCarloRuns)
Example #11
0
# uncertain knowledge: cutoff between 19.5kHz and 20.5kHz
runs = 1000
FC = fcut + (2 * np.random.rand(runs) - 1) * 0.5e3

B = np.zeros((runs, L + 1))
for k in range(runs):
    B[k, :] = kaiser_lowpass(L, FC[k], Fs)[0]

Ub = make_semiposdef(np.cov(B, rowvar=0))

# simulate input and output signals
time = np.arange(0, 499 * Ts, Ts)
noise = 1e-3
x = rect(time, 100 * Ts, 250 * Ts, 1.0, noise=noise)

y, Uy = FIRuncFilter(x, noise, b, Ub)
yMC, UyMC = MC.MC(x, noise, b, [1.0], Ub, runs=10000)
yMC2, UyMC2 = MC.SMC(x, noise, b, [1.0], Ub, runs=10000)

plt.figure(1)
plt.cla()
plt.plot(time, col_hstack([x, y]))
plt.legend(('input', 'output'))

plt.figure(3)
plt.cla()
plt.plot(time, col_hstack([Uy, UyMC, UyMC2]))
plt.title('Uncertainty of filter output signal')
plt.legend(('FIR formula', 'Monte Carlo', 'Sequential Monte Carlo'))

plt.show()
Example #12
0
bF, UbF = model_est.invLSFIR_unc(
    H, UH, N, tau, f,
    Fs)  # Calculation of FIR deconvolution filter and its assoc. unc.
CbF = UbF / (np.tile(np.sqrt(np.diag(UbF))[:, np.newaxis], (1, N + 1)) *
             np.tile(np.sqrt(np.diag(UbF))[:, np.newaxis].T,
                     (N + 1, 1)))  # correlation of filter coefficients

# Deconvolution Step1: lowpass filter for noise attenuation
fcut = f0 + 20e3
low_order = 100  # cut-off frequency and filter order
blow, lshift = kaiser_lowpass(low_order, fcut,
                              Fs)  # FIR low pass filter coefficients
shift = tau + lshift  # delay of low-pass plus that of the FIR deconv filter
# Deconvolution Step2: Application of deconvolution filter
xhat, Uxhat = FIRuncFilter(yn, noise, bF, UbF, shift,
                           blow)  # apply low-pass and FIR deconv filter

# Plot of results
fplot = np.linspace(0, 80e3, 1000)
Hc = dsp.freqz(b, a, 2 * np.pi * fplot / Fs)[1]
Hif = dsp.freqz(bF, 1.0, 2 * np.pi * fplot / Fs)[1]
Hl = dsp.freqz(blow, 1.0, 2 * np.pi * fplot / Fs)[1]

plt.figure(1)
plt.clf()
plt.plot(fplot * 1e-3, db(Hc), fplot * 1e-3, db(Hif * Hl), fplot * 1e-3,
         db(Hc * Hif * Hl))
plt.legend(
    ('System freq. resp.', 'compensation filter', 'compensation result'))
# plt.title('Amplitude of frequency responses')
plt.xlabel('frequency / kHz', fontsize=22)
Example #13
0
# uncertain knowledge: cutoff between 19.5kHz and 20.5kHz
runs = 1000
FC = fcut + (2 * np.random.rand(runs) - 1) * 0.5e3

B = np.zeros((runs, L + 1))
for k in range(runs):  # Monte Carlo for filter coefficients of low-pass filter
    B[k, :] = kaiser_lowpass(L, FC[k], Fs)[0]

Ub = make_semiposdef(np.cov(B, rowvar=False))  # covariance matrix of MC result

# simulate input and output signals
time = np.arange(0, 499 * Ts, Ts)  # time values
noise = 1e-5  # std of white noise
x = rect(time, 100 * Ts, 250 * Ts, 1.0, noise=noise)  # input signal

y, Uy = FIRuncFilter(x, noise, b, Ub,
                     blow=b)  # apply uncertain FIR filter (GUM formula)
yMC, UyMC = MC(x, noise, b, np.ones(1), Ub, runs=1000,
               blow=b)  # apply uncertain FIR filter (Monte Carlo)

plt.figure(1)
plt.cla()
plt.plot(time, x, label="input")
plt.plot(time, y, label="output")
plt.xlabel("time / au")
plt.ylabel("signal amplitude / au")
plt.legend()

plt.figure(2)
plt.cla()
plt.plot(time, Uy, label="FIR formula")
plt.plot(time, np.sqrt(np.diag(UyMC)), label="Monte Carlo")
# different cases
sigma_noise = 1e-2  # 1e-5

for kind in ["float", "corr", "diag"]:

    for blow in [None, b2]:

        print(kind, type(blow))

        if kind == "float":
            # input signal + run methods
            x = rect(time, 100 * Ts, 250 * Ts, 1.0, noise=sigma_noise)

            y, Uy = FIRuncFilter(
                x, sigma_noise, b1, Ub, blow=blow,
                kind=kind)  # apply uncertain FIR filter (GUM formula)
            yMC, UyMC = MC(
                x, sigma_noise, b1, [1.0], Ub, runs=runs,
                blow=blow)  # apply uncertain FIR filter (Monte Carlo)

        elif kind == "corr":

            # get an instance of noise, the covariance and the covariance-matrix with the specified color
            color = "red"
            noise = power_law_noise(N=nTime,
                                    color_value=color,
                                    std=sigma_noise)

            Ux = power_law_acf(nTime, color_value=color, std=sigma_noise)
Example #15
0
UH = np.cov(np.c_[np.real(HMC), np.imag(HMC)], rowvar=0)
UH = make_semiposdef(UH)
# Calculation of FIR deconvolution filter and its assoc. unc.
bF, UbF = deconv.LSFIR_unc(H, UH, N, tau, f, Fs)

# correlation of filter coefficients
CbF = UbF / (np.tile(np.sqrt(np.diag(UbF))[:, np.newaxis], (1, N + 1)) *
             np.tile(np.sqrt(np.diag(UbF))[:, np.newaxis].T, (N + 1, 1)))

# Deconvolution Step1: lowpass filter for noise attenuation
fcut = f0 + 20e3
low_order = 100
blow, lshift = kaiser_lowpass(low_order, fcut, Fs)
shift = -tau - lshift
# Deconvolution Step2: Application of deconvolution filter
xhat, Uxhat = FIRuncFilter(yn, noise, bF, UbF, shift, blow)

# Plot of results
fplot = np.linspace(0, 80e3, 1000)
Hc = dsp.freqz(b, a, 2 * np.pi * fplot / Fs)[1]
Hif = dsp.freqz(bF, 1.0, 2 * np.pi * fplot / Fs)[1]
Hl = dsp.freqz(blow, 1.0, 2 * np.pi * fplot / Fs)[1]

plt.figure(1)
plt.clf()
plt.plot(fplot * 1e-3, db(Hc), fplot * 1e-3, db(Hif * Hl), fplot * 1e-3,
         db(Hc * Hif * Hl))
plt.legend(
    ('System freq. resp.', 'compensation filter', 'compensation result'))
# plt.title('Amplitude of frequency responses')
plt.xlabel('frequency / kHz', fontsize=22)
def conduct_validation_of_FIRuncFilter():
    # parameters of simulated measurement
    Fs = 100e3  # sampling frequency (in Hz)
    Ts = 1 / Fs  # sampling interval length (in s)
    # nominal system parameters
    fcut = 20e3  # low-pass filter cut-off frequency (6 dB)
    L = 100  # filter order
    b1 = kaiser_lowpass(L, fcut, Fs)[0]
    b2 = kaiser_lowpass(L - 20, fcut, Fs)[0]
    # uncertain knowledge: cutoff between 19.5kHz and 20.5kHz
    runs = 1000
    FC = fcut + (2 * np.random.rand(runs) - 1) * 0.5e3
    B = np.zeros((runs, L + 1))
    for k in range(
            runs):  # Monte Carlo for filter coefficients of low-pass filter
        B[k, :] = kaiser_lowpass(L, FC[k], Fs)[0]
    Ub = make_semiposdef(np.cov(
        B, rowvar=False))  # covariance matrix of MC result
    # simulate input and output signals
    nTime = 500
    time = np.arange(nTime) * Ts  # time values
    # different cases
    sigma_noise = 1e-2  # 1e-5
    for kind in ["float", "corr", "diag"]:

        for blow in [None, b2]:

            print(kind, type(blow))

            if kind == "float":
                # input signal + run methods
                x = rect(time, 100 * Ts, 250 * Ts, 1.0, noise=sigma_noise)

                Uy, UyMC, y, yMC = _conduct_FIRuncFilter_and_MonteCarlo(
                    Ub, b1, blow, kind, runs, sigma_noise, x)

            elif kind == "corr":

                # get an instance of noise, the covariance and the covariance-matrix
                # with
                # the specified color
                color = "red"
                noise = power_law_noise(N=nTime,
                                        color_value=color,
                                        std=sigma_noise)

                Ux = power_law_acf(nTime, color_value=color, std=sigma_noise)

                # input signal
                x = rect(time, 100 * Ts, 250 * Ts, 1.0, noise=noise)

                # build Ux_matrix from autocorrelation Ux
                Ux_matrix = toeplitz(trimOrPad(Ux, nTime))

                # run methods
                y, Uy = FIRuncFilter(
                    x, Ux, b1, Ub, blow=blow,
                    kind=kind)  # apply uncertain FIR filter (GUM formula)
                yMC, UyMC = MC(
                    x, Ux_matrix, b1, np.ones(1), Ub, runs=runs,
                    blow=blow)  # apply uncertain FIR filter (Monte Carlo)

            elif kind == "diag":
                sigma_diag = sigma_noise * (
                    1 +
                    np.heaviside(np.arange(len(time)) - len(time) // 2.5, 0)
                )  # std doubles after half of the time
                noise = sigma_diag * white_gaussian(len(time))

                # input signal + run methods
                x = rect(time, 100 * Ts, 250 * Ts, 1.0, noise=noise)

                Uy, UyMC, y, yMC = _conduct_FIRuncFilter_and_MonteCarlo(
                    Ub, b1, blow, kind, runs, sigma_diag, x)

            # compare FIR and MC results
            plt.figure(1)
            plt.cla()
            plt.plot(time, x, label="input")
            plt.plot(time, y, label="output FIR direct")
            plt.plot(time, yMC, label="output FIR MC")
            plt.xlabel("time [s]")
            plt.ylabel("signal amplitude [1]")
            plt.legend()

            plt.figure(2)
            plt.cla()
            plt.plot(time, Uy, label="FIR formula")
            plt.plot(time, np.sqrt(np.diag(UyMC)), label="Monte Carlo")
            plt.xlabel("time [s]")
            plt.ylabel("signal uncertainty [1]")
            plt.legend()
            plt.show()
Example #17
0
from PyDynamic.uncertainty.propagate_filter import FIRuncFilter

def evaluate(signal, filter, case="full/zero/none"):
    # different covariance matrices
    if case == "full":
        U_theta = 1e-2 * np.eye(filter_length)
    elif case == "zero":
        U_theta = np.zeros((filter_length, filter_length))
    elif case == "none":
        U_theta = None
    else:
        raise NotImplementedError
    
    # measure runtime (timeit.timeit is unable to use both local+global variables)
    start = time.time()
    y, Uy = FIRuncFilter(signal, sigma_noise, theta, U_theta, blow=None)
    end = time.time()
    runtime = end - start

    return runtime, y, Uy


filter_lengths = [10, 50, 100, 200, 500, 1000]
cases = ["full", "zero", "none"]
results = pd.DataFrame(index=filter_lengths, columns=cases)


# prepare signal
signal_length = 2000
sigma_noise = 1e-2  # 1e-5
signal = sigma_noise * np.random.randn(signal_length)
def test(fir_unc_filter_input):
    def legacy_FIRuncFilter(y,
                            sigma_noise,
                            theta,
                            Utheta=None,
                            shift=0,
                            blow=None,
                            kind="corr"):
        """Uncertainty propagation for signal y and uncertain FIR filter theta

        A preceding FIR low-pass filter with coefficients `blow` can be provided
        optionally.

        Parameters
        ----------
        y : np.ndarray
            filter input signal
        sigma_noise : float or np.ndarray
            float: standard deviation of white noise in y
            1D-array: interpretation depends on kind
        theta : np.ndarray
            FIR filter coefficients
        Utheta : np.ndarray, optional
            covariance matrix associated with theta
            if the filter is fully certain, use `Utheta = None` (default) to make use of
            more efficient calculations. See also the comparison given in
            <examples/Digital filtering/FIRuncFilter_runtime_comparison.py>
        shift : int, optional
            time delay of filter output signal (in samples) (defaults to 0)
        blow : np.ndarray, optional
            optional FIR low-pass filter
        kind : string
            only meaningful in combination with sigma_noise a 1D numpy array
            "diag": point-wise standard uncertainties of non-stationary white noise
            "corr": single sided autocovariance of stationary (colored/correlated)
            noise (default)

        Returns
        -------
        x : np.ndarray
            FIR filter output signal
        ux : np.ndarray
            point-wise standard uncertainties associated with x


        References
        ----------

        * Elster and Link 2008 [Elster2008]_

        .. seealso:: :mod:`PyDynamic.deconvolution.fit_filter`

        """

        Ntheta = len(theta)  # FIR filter size

        # check which case of sigma_noise is necessary
        if isinstance(sigma_noise, float):
            sigma2 = sigma_noise**2

        elif isinstance(sigma_noise, np.ndarray) and len(
                sigma_noise.shape) == 1:
            if kind == "diag":
                sigma2 = sigma_noise**2
            elif kind == "corr":
                sigma2 = sigma_noise
            else:
                raise ValueError("unknown kind of sigma_noise")

        else:
            raise ValueError(
                f"FIRuncFilter: Uncertainty sigma_noise associated "
                f"with input signal is expected to be either a float or a 1D array but "
                f"is of shape {sigma_noise.shape}. Please check documentation for "
                f"input "
                f"parameters sigma_noise and kind for more information.")

        if isinstance(
                blow, np.ndarray
        ):  # calculate low-pass filtered signal and propagate noise

            if isinstance(sigma2, float):
                Bcorr = np.correlate(blow, blow,
                                     "full")  # len(Bcorr) == 2*Ntheta - 1
                ycorr = (sigma2 * Bcorr[len(blow) - 1:]
                         )  # only the upper half of the correlation is needed

                # trim / pad to length Ntheta
                ycorr = trimOrPad(ycorr, Ntheta)
                Ulow = toeplitz(ycorr)

            elif isinstance(sigma2, np.ndarray):

                if kind == "diag":
                    # [Leeuw1994](Covariance matrix of ARMA errors in closed form)
                    # can be
                    # used, to derive this formula
                    # The given "blow" corresponds to a MA(q)-process.
                    # Going through the calculations of Leeuw, but assuming
                    # that E(vv^T) is a diagonal matrix with non-identical elements,
                    # the covariance matrix V becomes (see Leeuw:corollary1)
                    # V = N * SP * N^T + M * S * M^T
                    # N, M are defined as in the paper
                    # and SP is the covariance of input-noise prior to the observed
                    # time-interval (SP needs be available len(blow)-time steps into the
                    # past. Here it is assumed, that SP is constant with the first value
                    # of sigma2)

                    # V needs to be extended to cover Ntheta-1 time steps more into
                    # the past
                    sigma2_extended = np.append(
                        sigma2[0] * np.ones((Ntheta - 1)), sigma2)

                    N = toeplitz(blow[1:][::-1],
                                 np.zeros_like(sigma2_extended)).T
                    M = toeplitz(
                        trimOrPad(blow, len(sigma2_extended)),
                        np.zeros_like(sigma2_extended),
                    )
                    SP = np.diag(sigma2[0] * np.ones_like(blow[1:]))
                    S = np.diag(sigma2_extended)

                    # Ulow is to be sliced from V, see below
                    V = N.dot(SP).dot(N.T) + M.dot(S).dot(M.T)

                elif kind == "corr":

                    # adjust the lengths sigma2 to fit blow and theta
                    # this either crops (unused) information or appends zero-information
                    # note1: this is the reason, why Ulow will have dimension
                    # (Ntheta x Ntheta) without further ado

                    # calculate Bcorr
                    Bcorr = np.correlate(blow, blow, "full")

                    # pad or crop length of sigma2, then reflect some part to the
                    # left and
                    # invert the order
                    # [0 1 2 3 4 5 6 7] --> [0 0 0 7 6 5 4 3 2 1 0 1 2 3]
                    sigma2 = trimOrPad(sigma2, len(blow) + Ntheta - 1)
                    sigma2_reflect = np.pad(sigma2, (len(blow) - 1, 0),
                                            mode="reflect")

                    ycorr = np.correlate(
                        sigma2_reflect, Bcorr, mode="valid"
                    )  # used convolve in a earlier version, should make no
                    # difference as
                    # Bcorr is symmetric
                    Ulow = toeplitz(ycorr)

            xlow, _ = lfilter(blow, 1.0, y, zi=y[0] * lfilter_zi(blow, 1.0))

        else:  # if blow is not provided
            if isinstance(sigma2, float):
                Ulow = np.eye(Ntheta) * sigma2

            elif isinstance(sigma2, np.ndarray):

                if kind == "diag":
                    # V needs to be extended to cover Ntheta time steps more into the
                    # past
                    sigma2_extended = np.append(
                        sigma2[0] * np.ones((Ntheta - 1)), sigma2)

                    # Ulow is to be sliced from V, see below
                    V = np.diag(
                        sigma2_extended
                    )  # this is not Ulow, same thing as in the case of a provided blow
                    # (see above)

                elif kind == "corr":
                    Ulow = toeplitz(trimOrPad(sigma2, Ntheta))

            xlow = y

        # apply FIR filter to calculate best estimate in accordance with GUM
        x, _ = lfilter(theta, 1.0, xlow, zi=xlow[0] * lfilter_zi(theta, 1.0))
        x = np.roll(x, -int(shift))

        # add dimension to theta, otherwise transpose won't work
        if len(theta.shape) == 1:
            theta = theta[:, np.newaxis]

        # NOTE: In the code below wherever `theta` or `Utheta` get used, they need to be
        # flipped. This is necessary to take the time-order of both variables into
        # account. (Which is descending for `theta` and `Utheta` but ascending for
        # `Ulow`.)
        #
        # Further details and illustrations showing the effect of not-flipping
        # can be found at https://github.com/PTB-M4D/PyDynamic/issues/183

        # handle diag-case, where Ulow needs to be sliced from V
        if kind == "diag":
            # UncCov needs to be calculated inside in its own for-loop
            # V has dimension (len(sigma2) + Ntheta) * (len(sigma2) + Ntheta) -->
            # slice a
            # fitting Ulow of dimension (Ntheta x Ntheta)
            UncCov = np.zeros((len(sigma2)))

            if isinstance(Utheta, np.ndarray):
                for k in range(len(sigma2)):
                    Ulow = V[k:k + Ntheta, k:k + Ntheta]
                    UncCov[k] = np.squeeze(
                        np.flip(theta).T.dot(Ulow.dot(np.flip(theta))) +
                        np.abs(np.trace(Ulow.dot(
                            np.flip(Utheta)))))  # static part of uncertainty
            else:
                for k in range(len(sigma2)):
                    Ulow = V[k:k + Ntheta, k:k + Ntheta]
                    UncCov[k] = np.squeeze(
                        np.flip(theta).T.dot(Ulow.dot(
                            np.flip(theta))))  # static part of uncertainty

        else:
            if isinstance(Utheta, np.ndarray):
                UncCov = np.flip(theta).T.dot(Ulow.dot(
                    np.flip(theta))) + np.abs(
                        np.trace(Ulow.dot(
                            np.flip(Utheta))))  # static part of uncertainty
            else:
                UncCov = np.flip(theta).T.dot(Ulow.dot(
                    np.flip(theta)))  # static part of uncertainty

        if isinstance(Utheta, np.ndarray):
            unc = np.empty_like(y)

            # use extended signal to match assumption of stationary signal prior to
            # first
            # entry
            xlow_extended = np.append(np.full(Ntheta - 1, xlow[0]), xlow)

            for m in range(len(xlow)):
                # extract necessary part from input signal
                XL = xlow_extended[m:m + Ntheta, np.newaxis]
                unc[m] = XL.T.dot(
                    np.flip(Utheta).dot(XL))  # apply formula from paper
        else:
            unc = np.zeros_like(y)

        ux = np.sqrt(np.abs(UncCov + unc))
        ux = np.roll(ux, -int(shift))  # correct for delay

        return x, ux.flatten()  # flatten in case that we still have 2D array

    legacy_y, legacy_Uy = legacy_FIRuncFilter(**fir_unc_filter_input)
    current_y, current_Uy = FIRuncFilter(**fir_unc_filter_input)
    assert_allclose(
        legacy_y,
        current_y,
        atol=1e-15,
    )
    assert_allclose(
        legacy_Uy,
        current_Uy,
        atol=np.max((np.max(current_Uy) * 1e-7, 1e-7)),
        rtol=3e-6,
    )
Example #19
0
def test_FIRuncFilter(filters, signals, lowpasses):
    # Check expected output for thinkable permutations of input parameters.
    y, Uy = FIRuncFilter(**filters, **signals, **lowpasses)
    assert len(y) == len(signals["y"])
    assert len(Uy) == len(signals["y"])