Optimization of resonant system based on Gaussian fit
- Driver:
- Download script:
The target of the study is to tune a resonant system. For simplicity, the resonant system is a harmonic oscillator with eigenfrequency \(\omega_0\) and damping \(\gamma\) driven with force \(F\) at frequency \(\omega\). The amplitude of the oscillator is
\[a_{\rm harm}(\omega) = \frac{F}{\sqrt{(2\omega\omega_0\gamma)^2 + (\omega_0^2 - \omega)^2}} .\]
We assume the system as a black box with unknown resonant behavior. To capture the resonant feature, it is fitted against a Gaussian plus a linear function
\[a_{\rm fit}(\omega) = A \exp\left(-\frac{1}{2}\frac{(\omega-\tau)^2}{\sigma^2}\right) + B\omega + C .\]
The target is to tune \(F, \omega_0, \gamma\) such that the fitted amplitude is \(A = 10\), the resonance frequency is \(\tau = 2\) and the resonance width \(\sigma\) is as small as possible.
1import sys,os
2import numpy as np
3import time
4
5from jcmoptimizer import Client, Study, Obseravtion
6client = Client()
7
8
9#Amplitude of a driven harmonic oscillator.
10def amplitude(omega: float, F: float, omega0: float, gamma: float) -> float:
11 return F/np.sqrt((2*omega*omega0*gamma)**2 + (omega0**2 - omega**2)**2)
12
13# Definition of the search domain to tune the oscillator
14design_space = [
15 {'name': 'F', 'type': 'continuous', 'domain': (0.1, 40.0)},
16 {'name': 'omega0', 'type': 'continuous', 'domain': (1.0, 4.0)},
17 {'name': 'gamma', 'type': 'continuous', 'domain': (0.03, 0.3)},
18]
19
20#The amplitude is scaned for 10 different driving frequencies
21omegas = np.linspace(1,3,10)
22
23# Creation of the study object with study_id 'harmonic_oscillator_fit'
24study = client.create_study(
25 design_space=design_space,
26 driver="ActiveLearning",
27 study_name="Optimization of resonant system based on Gaussian fit",
28 study_id="harmonic_oscillator_fit"
29)
30
31#configuration of study
32study.configure(
33 max_iter = 30,
34 surrogates = [
35 #A Gaussian process learns the dependence of the amplitudes for
36 #all wavelengths on the design parameters.
37 dict(type="GP", name="amplitudes", output_dim=len(omegas))
38 ],
39 variables = [
40 #The amplitudes are fitted to a Gaussian + linear expression with
41 #amplitude A, resonance frequency tau, linear gradient B, and constant offset C.
42 #The output are the fitted values and the mean-squared error (MSE).
43 dict(type="Fit", name="fit", input="amplitudes",
44 expression="A*exp(-0.5*(omega-tau)^2/sigma^2) + B*omega + C",
45 output_names=["A", "B", "C", "tau", "sigma", "MSE"],
46 output_dim=6,
47 model_variables=["omega"], variable_values=omegas[:,None].tolist(),
48 initial_parameters=[1.0, 0.0, 0.0, 1.0, 0.5],
49 prior_uncertainties=[10.0, 10.0, 10.0, 10.0, 10.0],
50 ),
51 #The expression that defines the loss function:
52 dict(type="Expression", name="loss",
53 expression="(A-20)^2 + (tau - 2)^2 + sigma^2 + 0.001*MSE")
54 ],
55 objectives = [
56 #The only objective of the study is to minimize the loss.
57 dict(type="Minimizer", variable="loss"),
58 ],
59 acquisition_optimizer = dict(
60 #Sample computation based on fits is more expensive. In this case
61 #advanced sample computation is usually too laborious.
62 compute_suggestion_in_advance = False
63 ),
64)
65
66# Evaluation of the black-box function for specified design parameters
67def evaluate(study: Study, F: float, omega0: float, gamma: float) -> Observation:
68 #The harmonic-oscillator amplitude is evaluated for all omega values and the
69 #observed values are used as training input for the Gaussian process "amplitudes"
70 observation = study.new_observation()
71 observation.add([amplitude(omega, F, omega0, gamma) for omega in omegas])
72 return observation
73
74# Run the minimization
75study.set_evaluator(evaluate)
76study.run()
77
78# Print result
79best = study.get_state("driver.best_sample")
80print(f"Best design parameters: F={best['F']:.3f}, omega0={best['omega0']:.3f}, "
81 f"gamma={best['gamma']:.3f}")
82print("Objective: (A-20)^2 + (tau - 2)^2 + sigma^2 + 0.001*MSE = "
83 f"{study.get_state('driver.min_objective'):.3f}")
84client.shutdown_server()