Optimization of resonant system based on Gaussian fit

Driver:

ActiveLearning

Download script:

harmonic_oscillator_fit.py

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}")