Source code for cbadc.digital_control.dither_control

"""the calibration control"""
from typing import List, Union
import numpy as np
from copy import copy
from . import DigitalControl
from cbadc.analog_signal.impulse_responses import StepResponse
from ..analog_signal.impulse_responses import _ImpulseResponse


[docs]class DitherControl(DigitalControl): """A control with dither signal. An extension to the traditional digital controls, that stabilize the digital dimension, where the dither control adds a randomly generated dithering signal to the mix. Note that the dithering controls are prepended those of the initalizing digital control. Parameters ---------- number_of_random_controls: `int` number of dithering controls digital_control: :py:class:`cbadc.digital_control.DigitalControl` a digital control to be extended """ def __init__( self, number_of_random_controls: int, digital_control: DigitalControl, impulse_response: List[_ImpulseResponse] = None, dithering=True, random_sequence_length=1 << 22, t_delay: float = 0.0, ): if t_delay < 0: raise Exception("t_delay must be non-negative") else: self.t_delay = t_delay self._deterministic_control = copy(digital_control) self.number_of_random_control = number_of_random_controls self.clock = copy(digital_control.clock) self.M = digital_control.M + self.number_of_random_control self.M_tilde = self._deterministic_control.M_tilde self._s = np.zeros(self.M) self._s[:] = False self._control_decisions = np.zeros(self.M, dtype=np.double) # initialize dac values self.dithering = dithering rng = np.random.default_rng() self._pseudo_random_sequence = rng.integers( 2, size=(random_sequence_length, number_of_random_controls) ) # self._pseudo_random_sequence = rng.normal( # 0.5, 0.1, size=(random_sequence_length, number_of_random_controls) # ) self._pseudo_random_index = 0 self._pseudo_random_sequence_size = random_sequence_length self._initialize_impulse_response(digital_control.clock, impulse_response) def turn_on_dither(self): self.dithering = True def turn_off_dither(self): self.dithering = False def _initialize_impulse_response(self, clock, impulse_response): self._random_control_index = [i for i in range(self.number_of_random_control)] self._deterministic_control_index: List[int] = [] self._impulse_response = [] if impulse_response is not None: if len(impulse_response) != self.number_of_random_control: raise Exception( "must be number_of_random_control speciefied impulse responses" ) t0s = np.array([x.t0 for x in impulse_response]) if ( (t0s < 0.0).any() or (t0s > self.clock.T).any() or (t0s[:-1] > t0s[1:]).any() ): raise Exception("Invalid impulse respones") self._mulit_phase = ( np.sum(t0s) > 0.0 or self._deterministic_control._mulit_phase ) for i in range(self.number_of_random_control): self._impulse_response.append(copy(impulse_response[i])) else: for i in range(self.number_of_random_control): self._impulse_response.append(StepResponse(0.0)) self._mulit_phase = self._deterministic_control._mulit_phase for i in range(self._deterministic_control.M): self._deterministic_control_index.append(i + self.number_of_random_control) self._impulse_response.append( copy(self._deterministic_control._impulse_response[i]) ) self._setup_clock_phases()
[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 """ # Deterministic controls self._deterministic_control.control_update(t, s_tilde) for i, m in enumerate(self._deterministic_control_index): self._s[m] = self._deterministic_control._s[i] # DAC self._control_decisions[m] = 2.0 * self._s[m] - 1.0 # Random controls for m in self._random_control_index: if np.allclose(t, self._impulse_response[m].t0, atol=self.clock._tt_2): if self.dithering: self._s[m] = self._pseudo_random_sequence[ self._pseudo_random_index, m ] self._pseudo_random_index = ( self._pseudo_random_index + 1 ) % self._pseudo_random_sequence_size else: self._s[m] = 0.5 # DAC self._control_decisions[m] = 2.0 * self._s[m] - 1.0
[docs] def impulse_response(self, m: int, t: float) -> np.ndarray: """The impulse response of the corresponding DAC waveform Parameters ---------- m : `int` determines which :math:`m\in\{0,\dots,M-1\}` control dimension which is triggered. t : `float` evaluate the impulse response at time t. Returns ------- `array_like`, shape=(M,) the dac waveform of the digital control system. """ temp = np.zeros(self.M, dtype=np.double) temp[m] = self._impulse_response[m](t) return temp