Note
Click here to download the full example code
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
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()
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()
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()
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)