Source code for cbadc.analog_frontend

"""The analog frontend"""

import cbadc
import numpy as np
import scipy.integrate
import scipy.linalg


[docs]class AnalogFrontend: """Represents an analog frontend. Parameters ---------- analog_system: :py:class:`cbadc.analog_system.AnalogSystem` an analog system instance digital_control: :py:class:`cbadc.digital_control.DigitalControl` a digital control instance Attributes ---------- analog_system: :py:class:`cbadc.analog_system.AnalogSystem` the analog frontend's analog system instance digital_control: :py:class:`cbadc.digital_control.DigitalControl` the analog frontend's digital control instance """ def __init__( self, analog_system: cbadc.analog_system._valid_analog_system_types, digital_control: cbadc.digital_control._valid_digital_control_types, ): self.analog_system = analog_system self.digital_control = digital_control
def _analog_system_matrix_exponential(A: np.ndarray, t: float) -> np.ndarray: return np.asarray(scipy.linalg.expm(np.asarray(A) * t))
[docs]def get_global_control( analog_system: cbadc.analog_system.AnalogSystem, digital_control: cbadc.digital_control.DigitalControl, phi_delay: np.ndarray, atol=1e-12, rtol=1e-12, ) -> AnalogFrontend: """Compute the global control for the given analog system and digital control. Parameters ---------- analog_system: :py:class:`cbadc.analog_system.AnalogSystem` an analog system instance digital_control: :py:class:`cbadc.digital_control.DigitalControl` a digital control instance phi_delay: np.ndarray the delay vector atol: float absolute tolerance for the ODE solver rtol: float relative tolerance for the ODE solver Returns ------- :py:class:`cbadc.analog_frontend.AnalogFrontend` an analog frontend with global control """ def zero_order_hold(l: int, t: float) -> np.ndarray: res = np.zeros(analog_system.L) if t >= 0 and t < digital_control.clock.T: res[l] = 1.0 return res A_tilde = np.zeros((analog_system.M_tilde, analog_system.M)) B_tilde = np.zeros((analog_system.M_tilde, analog_system.L)) Gamma_tildeT = np.zeros((analog_system.M_tilde, analog_system.N)) # analog_system.CT = np.zeros((analog_system.N, analog_system.N)) # analog_system.CT[-1, -1] = 1.0 for m_tilde in range(analog_system.M_tilde): m_tilde_index = ( m_tilde + analog_system.M - analog_system.M_tilde + analog_system.L ) index_offset = (analog_system.M + analog_system.L) * analog_system.N def derivative_m(t: float, x: np.ndarray) -> np.ndarray: res = np.zeros_like(x) # extract the relevant control vector psi_s_m = np.dot( analog_system.CT, x[ m_tilde_index * analog_system.N : (1 + m_tilde_index) * analog_system.N ], ) # compute the input signal psi_u for l in range(analog_system.L): res[l * analog_system.N : (l + 1) * analog_system.N] = np.dot( analog_system.A, x[l * analog_system.N : (l + 1) * analog_system.N] ) + np.dot(analog_system.B, zero_order_hold(l, t - phi_delay[m_tilde])) # compute all control signal psi_s, with relevant impulse responses for m in range(analog_system.M): if m > m_tilde + analog_system.M - analog_system.M_tilde: corrected_impulse_response = digital_control.impulse_response( m, t + digital_control.clock.T ) else: corrected_impulse_response = digital_control.impulse_response(m, t) res[ (m + analog_system.L) * analog_system.N : (m + 1 + analog_system.L) * analog_system.N ] = np.dot( analog_system.A, x[ (m + analog_system.L) * analog_system.N : (m + 1 + analog_system.L) * analog_system.N ], ) + np.dot( analog_system.Gamma, corrected_impulse_response ) # compute inner product between homogeneous state vector and control signal res[index_offset : index_offset + analog_system.N] = np.dot( psi_s_m.transpose(), np.dot( analog_system.CT, _analog_system_matrix_exponential( analog_system.A, t - phi_delay[m_tilde] ), ), ) # compute inner product between input signal and control vector for l in range(analog_system.L): res[index_offset + analog_system.N + l] = np.inner( psi_s_m, np.dot( analog_system.CT, x[l * analog_system.N : (l + 1) * analog_system.N], ), ) # compute inner product between control signal and control vector for m in range(analog_system.M): res[index_offset + analog_system.N + analog_system.L + m] = np.inner( psi_s_m, np.dot( analog_system.CT, x[ (m + analog_system.L) * analog_system.N : (m + 1 + analog_system.L) * analog_system.N ], ), ) return res events = [] for m in range(analog_system.M_tilde): def event_function(t, x): return t - phi_delay[m] event_function.terminal = False event_function.direction = 0 events.append(event_function) # solve ode sol = scipy.integrate.solve_ivp( derivative_m, (phi_delay[m_tilde], phi_delay[m_tilde] + digital_control.clock.T), np.zeros( index_offset + analog_system.N + analog_system.L + analog_system.M ), atol=atol, rtol=rtol, events=events, ) y = sol.y[:, -1] Q = y[index_offset + analog_system.N + m_tilde_index] for l in range(analog_system.L): B_tilde[m_tilde, l] = -1 / Q * y[index_offset + analog_system.N + l] for m in range(analog_system.M): if m != m_tilde - analog_system.M_tilde + analog_system.M: A_tilde[m_tilde, m] = ( -1 / Q * y[index_offset + analog_system.N + analog_system.L + m] ) Gamma_tildeT[m_tilde, :] = ( -1 / Q * y[index_offset : index_offset + analog_system.N] ) analog_system = cbadc.analog_system.AnalogSystem( analog_system.A, analog_system.B, analog_system.CT, analog_system.Gamma, Gamma_tildeT, A_tilde=A_tilde, B_tilde=B_tilde, ) return AnalogFrontend(analog_system, digital_control)
[docs]def inverse_state_trajectory(A: np.ndarray, T: float): def derivative(t: float, x: np.ndarray) -> np.ndarray: return _analog_system_matrix_exponential(A, t).reshape(x.shape) sol = scipy.integrate.solve_ivp( derivative, (0, T), np.zeros(A.size), atol=1e-10, rtol=1e-10 ) res = sol.y[:, -1].reshape(A.shape) return res, np.linalg.inv(res)