Source code for cbadc.digital_control.multi_level_digital_control

from cbadc.digital_control.digital_control import (
    DigitalControl,
    _ImpulseResponse,
    _valid_clock_types,
)
from cbadc.analog_signal import StepResponse
from typing import List
import numpy as np


[docs]class MultiLevelDigitalControl(DigitalControl): """Multi-level digital control system. Parameters ---------- clock : :py:class:`cbadc.analog_signal.clock.Clock` the clock to which the digital control synchronizes its updates. M : `int` number of controls. number_of_levels: `[int]` number of levels for each of the M quantizers. t0 : `float`, `optional` determines initial time, defaults to 0. impulse_response : :py:class:`cbadc.analog_signal.AnalogSignal`, optional the digital control's impulse response. offsets: [float], `optional` a M sized list with offsets for each control, defaults to all 0. Attributes ---------- clock : :py:class:`cbadc.analog_signal.clock.Clock` the digital control system clock. M : `int` number of controls :math:`M`. M_tilde : `int` number of control observations :math:`\\tilde{M}`. Note ---- For this digital control system :math:`M=\\tilde{M}`. Also, the number of levels should equal M_tilde. """ def __init__( self, clock: _valid_clock_types, M: int, number_of_levels: List[int], impulse_response: List[_ImpulseResponse] = None, offsets: List[float] = [], ): self.number_of_levels = number_of_levels self._levels = [] self._references = [] if not offsets: self.offsets = np.zeros(M) elif offsets and len(offsets) == M: self.offsets = np.array(offsets) else: raise Exception("offsets must be empty or M sized list of floats.") if len(number_of_levels) != M: raise Exception("Must have M number of levels") for m in range(M): smallest_step = 1.0 / number_of_levels[m] self._levels.append( np.linspace(-1 + smallest_step, 1 - smallest_step, number_of_levels[m]) ) self._references.append(np.linspace(0, 1, number_of_levels[m] + 1)) super().__init__(clock, M, impulse_response) self._s = np.zeros(self.M, dtype=np.double)
[docs] def control_update(self, t: float, s_tilde: np.ndarray): """Updates the control at time t if valid. Parameters ---------- t : `float` time at which the digital control i evaluated. s_tilde : `array_like`, shape=(M_tilde,) state vector evaluated at time t """ for m_tilde in range(self.M_tilde): if np.allclose( t, self._impulse_response[m_tilde].t0, atol=self.clock._tt_2 ): # quantize self._s[m_tilde] = self.quantize(m_tilde, s_tilde[m_tilde]) # update control decisions self._control_decisions[m_tilde] = ( np.asarray(2.0 * self._s[m_tilde] - 1.0, dtype=np.double) + self.offsets[m_tilde] )
[docs] def quantize(self, m_tilde: int, s_tilde_m: np.array): """Quantizes the state vector. Parameters ---------- m_tilde : `int` index of the control. s_tilde_m : `array_like`, shape=(M_tilde,) state vector evaluated at time t Returns ------- `int` quantized state vector. """ index_mask = s_tilde_m >= self._levels[m_tilde] for level in range(self.number_of_levels[m_tilde]): if not index_mask[level]: return self._references[m_tilde][level] elif level == (self.number_of_levels[m_tilde] - 1): return self._references[m_tilde][-1]