Transfer Functions

This example demonstrates how to visualize the related transfer functions of the analog system and digital estimator.

 8 import matplotlib.pyplot as plt
 9 import numpy as np
10 import cbadc

Chain-of-Integrators ADC Example

The chain of integrators ADC.

In this example, we will use the chain-of-integrators ADC analog system for demonstrational purposes. However, except for the analog system creation, the steps for a generic analog system and digital estimator.

For in-depth details regarding the chain-of-integrators transfer function see, chain-of-integrators

First we will import dependent modules and initialize a chain-of-integrators setup. With the following analog system parameters

  • \(\beta = \beta_1 = \dots = \beta_N = 6250\)

  • \(\rho_1 = \dots = \rho_N = - 0.02\)

  • \(\kappa_1 = \dots = \kappa_N = - 1\)

  • \(N = 6\)

note that \(\mathbf{C}^\mathsf{T}\) is automatically assumed an identity matrix of size \(N\times N\).

Using the cbadc.analog_system.ChainOfIntegrators class which derives from the main analog system class cbadc.analog_system.AnalogSystem.

45 # We fix the number of analog states.
46 N = 6
47 # Set the amplification factor.
48 beta = 6250.
49 rho = - 0.02
50 kappa = - 1.0
51 # In this example, each nodes amplification and local feedback will be set
52 # identically.
53 betaVec = beta * np.ones(N)
54 rhoVec = betaVec * rho
55 kappaVec = kappa * beta * np.eye(N)
56
57 # Instantiate a chain-of-integrators analog system.
58 analog_system = cbadc.analog_system.ChainOfIntegrators(betaVec, rhoVec, kappaVec)
59 # print the system matrices.
60 print(analog_system)

Out:

The analog system is parameterized as:
A =
[[-125.    0.    0.    0.    0.    0.]
 [6250. -125.    0.    0.    0.    0.]
 [   0. 6250. -125.    0.    0.    0.]
 [   0.    0. 6250. -125.    0.    0.]
 [   0.    0.    0. 6250. -125.    0.]
 [   0.    0.    0.    0. 6250. -125.]],
B =
[[6250.]
 [   0.]
 [   0.]
 [   0.]
 [   0.]
 [   0.]],
CT =
[[1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1.]],
Gamma =
[[-6250.    -0.    -0.    -0.    -0.    -0.]
 [   -0. -6250.    -0.    -0.    -0.    -0.]
 [   -0.    -0. -6250.    -0.    -0.    -0.]
 [   -0.    -0.    -0. -6250.    -0.    -0.]
 [   -0.    -0.    -0.    -0. -6250.    -0.]
 [   -0.    -0.    -0.    -0.    -0. -6250.]],
Gamma_tildeT =
[[1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1.]], and D=[[0.]
 [0.]
 [0.]
 [0.]
 [0.]
 [0.]]

Plotting the Analog System’s Transfer Function

Next, we plot the transfer function of the analog system

\(\mathbf{G}(\omega) = \begin{pmatrix}G_1(\omega), \dots, G_N(\omega)\end{pmatrix}^\mathsf{T} = \mathbf{C}^\mathsf{T} \left(i \omega \mathbf{I}_N - \mathbf{A}\right)^{-1}\mathbf{B}\)

using the class method cbadc.analog_system.AnalogSystem.transfer_function_matrix().

 73 # Logspace frequencies
 74 frequencies = np.logspace(-3, 0, 500)
 75 omega = 4 * np.pi * beta * frequencies
 76
 77 # Compute transfer functions for each frequency in frequencies
 78 transfer_function = analog_system.transfer_function_matrix(omega)
 79 transfer_function_dB = 20 * np.log10(np.abs(transfer_function))
 80
 81 # For each output 1,...,N compute the corresponding tranfer function seen
 82 # from the input.
 83 for n in range(N):
 84     plt.semilogx(
 85         frequencies, transfer_function_dB[n, 0, :], label=f"$G_{n+1}(\omega)$")
 86
 87 # Add the norm ||G(omega)||_2
 88 plt.semilogx(
 89     frequencies,
 90     20 * np.log10(np.linalg.norm(
 91         transfer_function[:, 0, :],
 92         axis=0)),
 93     '--',
 94     label="$ ||\mathbf{G}(\omega)||_2 $")
 95
 96 # Add labels and legends to figure
 97 plt.legend()
 98 plt.grid(which='both')
 99 plt.title("Transfer functions, $G_1(\omega), \dots, G_N(\omega)$")
100 plt.xlabel("$\omega / (4 \pi \\beta ) $")
101 plt.ylabel("dB")
102 plt.xlim((frequencies[0], frequencies[-1]))
103 plt.gcf().tight_layout()
Transfer functions, $G_1(\omega), \dots, G_N(\omega)$

Plotting the Estimator’s Signal and Noise Transfer Function

To determine the estimate’s signal and noise transfer function, we must instantiate a digital estimator cbadc.digital_estimator.DigitalEstimator. The bandwidth of the digital estimation filter is mainly determined by the parameter \(\eta^2\) as the noise transfer function (NTF) follows as

\(\text{NTF}( \omega) = \mathbf{G}( \omega)^\mathsf{H} \left( \mathbf{G}( \omega)\mathbf{G}( \omega)^\mathsf{H} + \eta^2 \mathbf{I}_N \right)^{-1}\)

and similarly, the signal transfer function (STF) follows as

\(\text{STF}( \omega) = \text{NTF}( \omega) \mathbf{G}( \omega)\).

We compute these two by invoking the class methods cbadc.digital_estimator.DigitalEstimator.noise_transfer_function() and cbadc.digital_estimator.DigitalEstimator.signal_transfer_function() respectively.

the digital estimator requires us to also instantiate a digital control cbadc.digital_control.DigitalControl.

For the chain-of-integrators example, the noise transfer function results in a row vector \(\text{NTF}(\omega) = \begin{pmatrix} \text{NTF}_1(\omega), \dots, \text{NTF}_N(\omega)\end{pmatrix} \in \mathbb{C}^{1 \times \tilde{N}}\) where \(\text{NTF}_\ell(\omega)\) refers to the noise transfer function from the \(\ell\)-th observation to the final estimate.

138 # Define dummy control and control sequence (not used when computing transfer
139 # functions). However necessary to instantiate the digital estimator
140
141 T = 1/(2 * beta)
142 digital_control = cbadc.digital_control.DigitalControl(T, N)
143
144
145 # Compute eta2 for a given bandwidth.
146 omega_3dB = (4 * np.pi * beta) / 100.
147 eta2 = np.linalg.norm(analog_system.transfer_function_matrix(
148     np.array([omega_3dB])).flatten()) ** 2
149
150 # Instantiate estimator.
151 digital_estimator = cbadc.digital_estimator.DigitalEstimator(
152     analog_system, digital_control, eta2, K1=1)
153
154 # Compute NTF
155 ntf = digital_estimator.noise_transfer_function(omega)
156 ntf_dB = 20 * np.log10(np.abs(ntf))
157
158 # Compute STF
159 stf = digital_estimator.signal_transfer_function(omega)
160 stf_dB = 20 * np.log10(np.abs(stf.flatten()))
161
162
163 # Plot
164 plt.figure()
165 plt.semilogx(frequencies, stf_dB, label='$STF(\omega)$')
166 for n in range(N):
167     plt.semilogx(frequencies, ntf_dB[0, n, :], label=f"$|NTF_{n+1}(\omega)|$")
168 plt.semilogx(frequencies, 20 * np.log10(np.linalg.norm(
169     ntf[0, :, :], axis=0)), '--', label="$ || NTF(\omega) ||_2 $")
170
171 # Add labels and legends to figure
172 plt.legend()
173 plt.grid(which='both')
174 plt.title("Signal and noise transfer functions")
175 plt.xlabel("$\omega / (4 \pi \\beta ) $")
176 plt.ylabel("dB")
177 plt.xlim((frequencies[0], frequencies[-1]))
178 plt.gcf().tight_layout()
Signal and noise transfer functions

Setting the Bandwidth of the Estimation Filter

Finally, we will investigate the effect of eta2 on the STF and NTF.

186 # create a vector of etas to be evaluated,
187 eta2_vec = np.logspace(0, 10, 11)[::2]
188
189 plt.figure()
190 for eta2 in eta2_vec:
191     # Instantiate an estimator for each eta.
192     digital_estimator = cbadc.digital_estimator.DigitalEstimator(
193         analog_system, digital_control, eta2, K1=1)
194     # Compute stf and ntf
195     ntf = digital_estimator.noise_transfer_function(omega)
196     ntf_dB = 20 * np.log10(np.abs(ntf))
197     stf = digital_estimator.signal_transfer_function(omega)
198     stf_dB = 20 * np.log10(np.abs(stf.flatten()))
199
200     # Plot
201     color = next(plt.gca()._get_lines.prop_cycler)['color']
202     plt.semilogx(frequencies, 20 *
203                  np.log10(np.linalg.norm(ntf[0, :, :], axis=0)),
204                  '--', color=color)
205     plt.semilogx(frequencies, stf_dB,
206                  label=f"$\eta^2={10 * np.log10(eta2):0.0f} dB$", color=color)
207
208 # Add labels and legends to figure
209 plt.legend(loc='lower left')
210 plt.grid(which='both')
211 plt.title("$|G(\omega)|$ - solid, $||\mathbf{H}(\omega)||_2$ - dashed")
212 plt.xlabel("$\omega / (4 \pi \\beta ) $")
213 plt.ylabel("dB")
214 plt.xlim((3e-3, 1))
215 plt.ylim((-240, 20))
216 plt.gcf().tight_layout()
$|G(\omega)|$ - solid, $||\mathbf{H}(\omega)||_2$ - dashed

Out:

/drives1/PhD/cbadc/docs/code_examples/a_getting_started/plot_d_transfer_function.py:196: RuntimeWarning: divide by zero encountered in log10
  ntf_dB = 20 * np.log10(np.abs(ntf))
/drives1/PhD/cbadc/docs/code_examples/a_getting_started/plot_d_transfer_function.py:196: RuntimeWarning: divide by zero encountered in log10
  ntf_dB = 20 * np.log10(np.abs(ntf))
/drives1/PhD/cbadc/docs/code_examples/a_getting_started/plot_d_transfer_function.py:196: RuntimeWarning: divide by zero encountered in log10
  ntf_dB = 20 * np.log10(np.abs(ntf))

Total running time of the script: ( 0 minutes 21.316 seconds)

Gallery generated by Sphinx-Gallery