"""The digital IIR estimator"""
import cbadc
import logging
import numpy as np
from .batch_estimator import BatchEstimator
from ._filter_coefficients import FilterComputationBackend
logger = logging.getLogger(__name__)
[docs]class IIRFilter(BatchEstimator):
"""IIR filter implementation of the digital estimator.
Specifically, the IIR filter estimator estimates a filtered version :math:`\hat{\mathbf{u}}(t)` (shaped by
:py:func:`signal_transfer_function`) of the
input signal :math:`\mathbf{u}(t)` from a sequence of control signals :math:`\mathbf{s}[k]`.
Specifically, the estimate is of the form
:math:`\hat{\mathbf{u}}(k T) = - \mathbf{W}^{\mathsf{T}} \overrightarrow{\mathbf{m}}_k + \sum_{\ell=0}^{K_2} \mathbf{h}[\ell] \mathbf{s}[k + \ell]`
where
:math:`\mathbf{h}[\ell]=\mathbf{W}^{\mathsf{T}} \mathbf{A}_b^\ell \mathbf{B}_b`
:math:`\overrightarrow{\mathbf{m}}_k = \mathbf{A}_f \mathbf{m}_{k-1} + \mathbf{B}_f \mathbf{s}[k-1]`
and :math:`\mathbf{W}^{\mathsf{T}}`, :math:`\mathbf{A}_b`,
:math:`\mathbf{B}_b`, :math:`\mathbf{A}_f`, and :math:`\mathbf{B}_f`
are computed based on the analog system, the sample period :math:`T_s`, and the
digital control's DAC waveform as described in
# page=67/>`_.
`control-bounded converters <https://www.research-collection.ethz.ch/bitstream/handle/20.500.11850/469192/control-bounded_converters_a_dissertation_by_hampus_malmberg.pdf?sequence=1&isAllowed=y
Parameters
----------
analog_system : :py:class:`cbadc.analog_system.AnalogSystem`
an analog system (necessary to compute the estimators filter coefficients).
digital_control : :py:class:`cbadc.digital_control.DigitalControl`
a digital control (necessary to determine the corresponding DAC waveform).
eta2 : `float`
the :math:`\eta^2` parameter determines the bandwidth of the estimator.
K2 : `int`, `optional`
lookahead size, defaults to 0.
stop_after_number_of_iterations : `int`
determine a max number of iterations by the iterator, defaults to :math:`2^{63}`.
Ts: `float`, `optional`
the sampling time, defaults to the time period of the digital control.
mid_point: `bool`, `optional`
set samples in between control updates, i.e., :math:`\hat{u}(kT + T/2)`, defaults to False.
downsample: `int`, `optional`
specify down sampling rate in relation to the control period :math:`T`, defaults to 1, i.e.,
no down sampling.
solver_type: :py:class:`cbadc.digital_estimator._filter_coefficients.FilterComputationBackend`
determine which solver type to use when computing filter coefficients.
Attributes
----------
analog_system : :py:class:`cbadc.analog_system.AnalogSystem`
analog system as in :py:class:`cbadc.analog_system.AnalogSystem` or from
derived class.
digital_control : :py:class:`cbadc.digital_control.DigitalControl`
digital control as in :py:class:`cbadc.digital_control.DigitalControl` or from
derived class.
eta2 : float
eta2, or equivalently :math:`\eta^2`, sets the bandwidth of the estimator.
control_signal : :py:class:`cbadc.digital_control.DigitalControl`
a iterator suppling control signals as :py:class:`cbadc.digital_control.DigitalControl`.
number_of_iterations : `int`
number of iterations until iterator raises :py:class:`StopIteration`.
mid_point: `bool`
estimated samples shifted in between control updates, i.e., :math:`\hat{u}(kT + T/2)`.
K2 : `int`
number of lookahead samples per computed batch.
Ts : `float`
spacing between samples in seconds.
Af : `array_like`, shape=(N, N)
The Af matrix.
Ab : `array_like`, shape=(N, N)
The Ab matrix.
Bf : `array_like`, shape=(N, M)
The Bf matrix.
Bb : `array_like`, shape=(N, M)
The Bb matrix.
WT : `array_like`, shape=(L, N)
The W matrix transposed.
h : `array_like`, shape=(L, K2, M)
filter impulse response.
downsample: `int`
down sampling rate in relation to the control period :math:`T`.
solver_type: :py:class:`cbadc.digital_estimator._filter_coefficients.FilterComputationBackend`
The solver used for computing the filter coefficients.
Yields
------
`array_like`, shape=(L,)
an input estimate sample :math:`\hat{\mathbf{u}}(t)`
"""
def __init__(
self,
analog_system: cbadc.analog_system.AnalogSystem,
digital_control: cbadc.digital_control.DigitalControl,
eta2: float,
K2: int,
stop_after_number_of_iterations: int = (1 << 63),
Ts: float = None,
mid_point: bool = False,
downsample: int = 1,
solver_type: FilterComputationBackend = FilterComputationBackend.mpmath,
modulation_frequency: float = None,
*args,
**kwargs,
):
"""Initializes filter coefficients"""
if K2 < 0:
raise Exception("K2 must be non negative integer.")
self.K2 = K2
self._filter_lag = self.K2 - 2
self.analog_system = analog_system
self.digital_control = digital_control
if eta2 < 0:
raise Exception("eta2 must be non negative.")
self.eta2 = eta2
self.control_signal = None
self.number_of_iterations = stop_after_number_of_iterations
self._iteration = 0
if Ts:
self.Ts = Ts
else:
self.Ts = digital_control.clock.T
self.downsample = int(downsample)
self.mid_point = mid_point
# For transfer functions
self.eta2Matrix = np.eye(self.analog_system.CT.shape[0]) * self.eta2
# Compute filter coefficients
self.solver_type = solver_type
BatchEstimator._compute_filter_coefficients(
self, analog_system, digital_control, eta2
)
# Initialize filter
self.h = np.zeros(
(self.analog_system.L, self.K2, self.analog_system.M), dtype=np.double
)
# Compute lookback
temp2 = np.copy(self.Bb)
for k2 in range(self.K2):
self.h[:, k2, :] = np.dot(self.WT, temp2)
temp2 = np.dot(self.Ab, temp2)
self._control_signal_valued = np.zeros((self.K2, self.analog_system.M))
self._mean = np.zeros(self.analog_system.N, dtype=np.double)
# For modulation
self._time_index = 0
if modulation_frequency is not None:
self._modulation_frequency = modulation_frequency
else:
self._modulation_frequency = 0
def __iter__(self):
return self
def __next__(self) -> np.ndarray:
# Check if control signal iterator is set.
if self.control_signal is None:
raise Exception("No iterator set.")
# Check if the end of prespecified size
self._iteration += 1
if self.number_of_iterations and self.number_of_iterations < self._iteration:
raise StopIteration
# Rotate control_signal vector
self._control_signal_valued = np.roll(self._control_signal_valued, -1, axis=0)
# insert new control signal
try:
temp = self.control_signal.__next__()
except RuntimeError:
logger.warning("Estimator received Stop Iteration")
raise StopIteration
self._control_signal_valued[-1, :] = np.asarray(2 * temp - 1)
# self._control_signal_valued.shape -> (K2, M)
# self.h.shape -> (L, K2, M)
result = -np.dot(self.WT, self._mean)
self._mean = np.dot(self.Af, self._mean) + np.dot(
self.Bf, self._control_signal_valued[0, :]
)
if ((self._iteration - 1) % self.downsample) == 0:
self._time_index += 1
return (
np.tensordot(self.h, self._control_signal_valued, axes=((1, 2), (0, 1)))
+ result
)
# return np.einsum('ijk,jk', self.h, self._control_signal_valued) + result
return self.__next__()
[docs] def lookahead(self):
"""Return lookahead size :math:`K2`
Returns
-------
int
lookahead size.
"""
return self.K2
def __str__(self):
return f"IIR estimator is parameterized as \neta2 = {self.eta2:.2f}, {10 * np.log10(self.eta2):.0f} [dB],\nTs = {self.Ts},\nK2 = {self.K2},\nand\nnumber_of_iterations = {self.number_of_iterations}.\nResulting in the filter coefficients\nAf = \n{self.Af},\nBf = \n{self.Bf},WT = \n{self.WT},\n and h = \n{self.h}."