Example 001: Analytical Resonator fitting

import numpy as np
import matplotlib.pyplot as plt
import iddefix

Modelling a three resonator impedance spectrum by three arbitrary resonators

Any arbitrary number of resonators can be initialized. This is example will demonstrate the creation of three resonators with the resonator Formulas functions inside iddeficx and later fitting.

Three random resonators (\(R_s\), Q, \(f_r\)):

  1. 400 \(\Omega\), 30, 0.2 GHz

  2. 1000 \(\Omega\), 10, 1 GHz

  3. 500 \(\Omega\), 20, 1.75 GHz

# Assigning the resonator parameters

parameters ={   '1': [400, 30, 0.2e9],
                '2': [1000, 10, 1e9],
                '3': [500, 20, 1.75e9],}

# Computing the impedance spectrum for the resonators

frequency = np.linspace(0, 2e9, 1000)

impedance = iddefix.Impedances.n_Resonator_longitudinal_imp(frequency, parameters)

Plotting the impedance spectrum

Plots of both the real- and imaginary part of the impedance, as well as the absolute of the complex impedance

plt.plot(frequency, impedance.real, label='Real')
plt.plot(frequency, impedance.imag, label='Imag')
plt.plot(frequency, np.abs(impedance), label='Abs')
plt.xlabel('Frequency [Hz]')
plt.ylabel('Impedance [Ohm]')
plt.legend()
<matplotlib.legend.Legend at 0x7f43de3cd590>
../_images/d2441a2bc83e0745ca3ea243f50f9165dff1553ca6972e758da371c3b7d349c1.png

Fitting resonators with IDDEFIX on the absolute magnitude of the 3 resonator impdance spectrum

# Setting amount of resonators to fit

N_resonators = 3

# Bounds on resonators parameters
""" Bounds have this format [(Rs_min, Rs_max), (Q_min, Q_max), (fres_min, fres_max)].
ParameterBounds allows us to manually add a resonator with desired parameters """

parameterBounds = [(0, 2000), (1, 1e3), (0.1e9, 2e9),
                   (0, 2000), (1, 1e3), (0.1e9, 2e9),
                   (0, 2000), (1, 1e3), (0.1e9, 2e9)]

Running IDDEFIX DE

Running the DE algorithm with IDDEFIX and chosen parameters. A good rule-of-thumb is to have the population set at N = 5 * Nres. Mutation parameters and crossover_rate can be changed to adjust level of exploration/exploitation.

%%time
DE_model = iddefix.EvolutionaryAlgorithm(frequency, 
                                         impedance,
                                         N_resonators=N_resonators, 
                                         parameterBounds=parameterBounds,
                                         plane="longitudinal", 
                                         objectiveFunction=iddefix.ObjectiveFunctions.sumOfSquaredError)

DE_model.run_differential_evolution(maxiter=400,
                                    popsize=45,
                                    tol=0.01,
                                    mutation=(0.4, 1.0),
                                    crossover_rate=0.7)
print(DE_model.warning)
Optimization Progress %:   3%|▎         | 2.6286408213164307/100 [00:23<14:41,  9.05s/it]
----------------------------------------------------------------------
Resonator |   Rs [Ohm/m or Ohm]    |        Q         |    fres [Hz]     
----------------------------------------------------------------------
    1     |        1.00e+03        |       9.84       |    1.000e+09     
    2     |        5.03e+02        |      19.97       |    1.750e+09     
    3     |        4.01e+02        |      30.64       |    1.998e+08     
----------------------------------------------------------------------
Maximum number of iterations has been exceeded.
CPU times: user 15.7 s, sys: 7.29 s, total: 23 s
Wall time: 23.8 s

The found resonator parameters are close to being exactly correct. We can fit better by running for more generations or by doing the mimimization step:

Minimization step

To further refine the solution obtained by the DE algorithm, a second optimization step is applied using the Nelder-Mead minimization algorithm. This additional step starts with the results from the DE algorithm as the initial guess and iteratively adjusts the parameters within a predefined range of 10% above or below their original values. By doing so, the Nelder-Mead algorithm fine-tunes the solution to reduce the error further, leveraging its capability to explore the local parameter space efficiently. This two-step optimization ap- proach ensures a more precise fit by combining the global search power of DE with the local refinement capabilities of Nelder-Mead.

DE_model.run_minimization_algorithm()
Method for minimization : Nelder-Mead


----------------------------------------------------------------------
Resonator |   Rs [Ohm/m or Ohm]    |        Q         |    fres [Hz]     
----------------------------------------------------------------------
    1     |        1.00e+03        |      10.00       |    1.000e+09     
    2     |        5.00e+02        |      20.00       |    1.750e+09     
    3     |        4.00e+02        |      30.00       |    2.000e+08     
----------------------------------------------------------------------

Both the minimization and the differential evolution routines return the parameters in a list size [3*Nres]

print('Minimization parameters:')
print(DE_model.minimizationParameters)
print('Diferential evolution parameters:')
print(DE_model.evolutionParameters)
Minimization parameters:
[9.99999993e+02 9.99999983e+00 1.00000000e+09 4.99999992e+02
 1.99999994e+01 1.75000000e+09 4.00000007e+02 3.00000019e+01
 2.00000000e+08]
Diferential evolution parameters:
[9.99551882e+02 9.84124038e+00 9.99983157e+08 5.03335415e+02
 1.99657188e+01 1.75026584e+09 4.01449425e+02 3.06411196e+01
 1.99815069e+08]

There’s also a method to print them in Markdown syntax

print('For terminal:')
DE_model.display_resonator_parameters(DE_model.minimizationParameters)
print('\nFor Markdown:')
DE_model.display_resonator_parameters(DE_model.minimizationParameters, to_markdown=True)
For terminal:


----------------------------------------------------------------------
Resonator |   Rs [Ohm/m or Ohm]    |        Q         |    fres [Hz]     
----------------------------------------------------------------------
    1     |        1.00e+03        |      10.00       |    1.000e+09     
    2     |        5.00e+02        |      20.00       |    1.750e+09     
    3     |        4.00e+02        |      30.00       |    2.000e+08     
----------------------------------------------------------------------

For Markdown:


| Resonator | Rs [Ohm/m or Ohm] | Q | fres [Hz] |
|-----------|------------------|---|-----------|
| 1 | 1000 | 10 | 1e+09 |
| 2 | 500 | 20 | 1.75e+09 |
| 3 | 400 | 30 | 2e+08 |

Assesing the fitting visually

plt.figure(figsize=(10, 6))
result_DE = np.abs(DE_model.get_impedance(use_minimization=False))
result_DE_MIN = np.abs(DE_model.get_impedance())

plt.plot(frequency, np.abs(impedance), lw=5, label='Real', color='black')
plt.plot(frequency, result_DE,  lw=2, label='DE - Res. fitting')
plt.plot(frequency, result_DE_MIN,  lw=2, label='DE + Min. - Res. fitting')
plt.xlabel('Frequency [Hz]')
plt.ylabel('Impedance [Ohm]')
plt.legend()
<matplotlib.legend.Legend at 0x7f43df0ef4d0>
../_images/cfb36ee79134b47cca6db1f983465519f0c08a847a4bd3b4baaba7ec5f07a6a8.png

Retrieve the wake function and wake potential

Using iddefix Resonator formalism formulas, derived by S. Joly, one can reconstruct the wake function and the wake potential using the analytical expressions available inside resonatorFormalism.py and the parameters

plt.figure(figsize=(10, 6))

time = np.linspace(-1e-9, 20e-9, 100000)

wake = DE_model.get_wake(time_data=time)
wake_potential = DE_model.get_wake_potential(time_data=time, sigma=1e-10)

plt.plot(time*1e9, wake, lw=2, label='Wake function', color='blue')
plt.plot(time*1e9, wake_potential,  lw=2, label='Wake potential', color='red')
plt.xlabel('Time [ns]')
plt.ylabel('Wake potential & wake function')
plt.legend()
<matplotlib.legend.Legend at 0x7f441fe79390>
../_images/32f72699c21b4d82f5aa39c0b89faf01a9a90eb44403ecb49817b144056908cd.png