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
5jcm_optimizer_path = r"<JCM_OPTIMIZER_PATH>"
6sys.path.insert(0, os.path.join(jcm_optimizer_path, "interface", "python"))
7from jcmoptimizer import Server, Client, Study, Observation
8server = Server()
9client = Client(server.host)
10
11#Amplitude of a driven harmonic oscillator.
12def amplitude(omega: float, F: float, omega0: float, gamma: float) -> float:
13 return F/np.sqrt((2*omega*omega0*gamma)**2 + (omega0**2 - omega**2)**2)
14
15# Definition of the search domain to tune the oscillator
16design_space = [
17 {'name': 'F', 'type': 'continuous', 'domain': (0.1, 40.0)},
18 {'name': 'omega0', 'type': 'continuous', 'domain': (1.0, 4.0)},
19 {'name': 'gamma', 'type': 'continuous', 'domain': (0.03, 0.3)},
20]
21
22#The amplitude is scaned for 10 different driving frequencies
23omegas = np.linspace(1,3,10)
24
25#Since the system depends on the driving frequency which is not a design parameter,
26#it is defined as an environment parameter.
27environment = [
28 {'name': 'omega', 'type': 'variable', 'domain': (omegas[0], omegas[-1])},
29]
30
31# Creation of the study object with study_id 'harmonic_oscillator_fit'
32study = client.create_study(
33 design_space=design_space,
34 environment=environment,
35 driver="ActiveLearning",
36 name="Optimization of resonant system based on Gaussian fit",
37 study_id="harmonic_oscillator_fit"
38)
39
40#configuration of study
41study.configure(
42 max_iter = 30,
43 surrogates = [
44 #A single-output Gaussian process learns the dependence of the amplitude on the
45 #design and environment parameters.
46 dict(type="GP", name="amplitude", output_dim=1)
47 ],
48 variables = [
49 #The single-output Gaussian process is scanned over all omegas
50 dict(type="Scan", name="amplitude_scan", output_dim=len(omegas),
51 scan_parameters=["omega"], scan_values=omegas[:,None].tolist(),
52 input_surrogate="amplitude"),
53 #The result of the omega-scan is fitted to a Gaussian + linear expression with
54 #amplitude A, resonance frequency tau, linear gradient B, and constant offset C.
55 #The output are the fitted values and the mean-squared error (MSE).
56 dict(type="Fit", name="fit", input="amplitude_scan",
57 expression="A*exp(-0.5*(omega-tau)^2/sigma^2) + B*omega + C",
58 output_names=["A", "B", "C", "tau", "sigma", "MSE"],
59 output_dim=6,
60 model_variables=["omega"], variable_values=omegas[:,None].tolist(),
61 initial_parameters=[1.0, 0.0, 0.0, 1.0, 0.5]
62 ),
63 #The expression that defines the loss function:
64 dict(type="Expression", name="loss",
65 expression="(A-20)^2 + (tau - 2)^2 + sigma^2 + 0.001*MSE")
66 ],
67 objectives = [
68 #The only objective of the study is to minimize the loss.
69 dict(type="Minimizer", variable="loss"),
70 ],
71 acquisition_optimizer = dict(
72 #Sample computation based on fits is more expensive. In this case
73 #advanced sample computation is usually too laborious.
74 compute_suggestion_in_advance = False
75 ),
76)
77
78# Evaluation of the black-box function for specified design parameters
79def evaluate(study: Study, F: float, omega0: float, gamma: float) -> Observation:
80 #The harmonic-oscillator amplitude is evaluated for all omega values and the
81 #observed values are used as training input for the Gaussian process "amplitude"
82 observation = study.new_observation()
83 for omega in omegas:
84 observation.add(amplitude(omega, F, omega0, gamma),
85 environment_value=[omega], model_name="amplitude")
86 return observation
87
88# Run the minimization
89study.set_evaluator(evaluate)
90study.run()
91
92# Print result
93best = study.get_state("driver.best_sample")
94print(f"Best design parameters: F={best['F']:.3f}, omega0={best['omega0']:.3f}, "
95 f"gamma={best['gamma']:.3f}")
96print("Objective: (A-20)^2 + (tau - 2)^2 + sigma^2 + 0.001*MSE = "
97 f"{study.get_state('driver.min_objective'):.3f}")