В своих предыдущих постах я исследовал, как реализовать модели регрессии гауссовского процесса, имитировать лазер и выполнить байесовскую оптимизацию с использованием Python. В этом посте мы соберем все вместе и используем байесовскую оптимизацию для оптимизации лазера!
Оптимизация лазера
Лазеры чрезвычайно сложны и непостоянны: их стабилизация занимает много времени, они зависят от большого количества параметров и требуют большого количества ресурсов просто для настройки. Например, в Linac Coherent Light Source технические специалисты должны активно настраивать несколько параметров управления для достижения определенной характеристики луча. Этот процесс настройки отнимает драгоценное время, которое можно было бы потратить на эксперименты!
Большинство алгоритмов оптимизации не подходят для настройки лазера, отчасти из-за чрезмерного количества требуемых вычислительных ресурсов, отчасти из-за уровня сложности, связанного с лазером, и отчасти из-за того, что многие алгоритмы оптимизации легко застревают в локальных минимумах. . Следовательно, чтобы настроить лазер, нам нужно выполнить оптимизацию дорогостоящей функции черного ящика, что и делает байесовская оптимизация!
В следующем разделе мы рассмотрим, как использовать байесовскую оптимизацию для оптимизации мощности накачки лазера для заданной выходной мощности.
Код Python для байесовской оптимизации лазера
Мы используем библиотеку bayes_opt
для выполнения байесовской оптимизации. Лазер моделируется с помощью fdtd1d_laser
, доступного в моем GitHub FDTD-репозитории.
import numpy as np import matplotlib.pyplot as plt from fdtd import fdtd1d_laser from bayes_opt import BayesianOptimization, UtilityFunction
Учитывая некоторую целевую выходную мощность, мы оптимизируем наилучшую мощность насоса D0
для использования. Поэтому мы определяем функцию для запуска и измерения выходной мощности лазера для некоторой мощности накачки D0
.
def run_and_measure_laser(D0, n_iter = 50000, t_measure = 10000): # Run 1D FDTD laser simulation for pump strength D0. fdtd = fdtd1d_laser(D0 = D0) fdtd.run(initiate_pulse = True, n_iter = n_iter) # "Measure" the output power. E = np.abs(fdtd.E_measure)[-t_measure:] t = np.arange(0, len(E) * fdtd.dt, fdtd.dt) P = np.trapz(E, t) return P
Затем мы определяем функцию, которая принимает входные данные target_power
, а также количество итераций оптимизации для запуска. Функция запускает лазер для некоторого пробного значения D0
, измеряет выходную мощность лазера и сравнивает ее с target_power
. Затем оптимизатор итеративно корректирует D0
, сводя к минимуму разницу между выходной мощностью и target_power
.
def optimize_laser(target_power, n_iter = 15): # Initialize the optimizer. pbounds = {"D0": [1, 20]} # Search bounds. optimizer = BayesianOptimization(f = None, pbounds = pbounds, random_state = 42) # Upper confidence bounds utility function. utility = UtilityFunction(kind = "ucb", kappa = 1.96, xi = 0.01) D0s = np.array([np.nan] * n_iter) lasing_powers = D0s.copy() targets = D0s.copy() # Iterate the optimizer for n_iter iterations. for i in range(n_iter): # Optimizer suggests a value of D0 to try based on # historical results. next_point = optimizer.suggest(utility) # Perform the lasing measurements. lasing_power = run_and_measure_laser(**next_point) # Minimize the difference between lasing power and # target_power. target = -np.abs(lasing_power - target_power) # Store the results for output. D0s[i] = next_point["D0"] lasing_powers[i] = lasing_power targets[i] = target try: # Update the optimizer with the evaluation results. # This needs to be in try-except structure in order # to prevent repeat errors from occuring. optimizer.register(params = next_point, target = target) except: pass return optimizer, D0s, lasing_powers, targets
Мы запускаем оптимизатор на 20 итераций для целевой мощности 215233.
target_power = 215233 n_iter = 20 optimization_results = optimize_laser(target_power, n_iter) optimizer = optimization_results[0] D0s = optimization_results[1] lasing_powers = optimization_results[2] targets = optimization_results[3] # Get the best results. D0 = optimizer.max["params"]["D0"] target = optimizer.max["target"] lasing_power = int(lasing_powers[np.where(D0s == D0)[0]][0]) # Plot the optimization iterations. plt.figure(figsize = (15, 5)) plt.plot(range(1, 1+len(optimizer.space.target)), optimizer.space.target, "-o") plt.grid(True) plt.xlabel("Iteration", fontsize = 14) plt.ylabel("-|lasing_power - target_power|", fontsize = 14) plt.xticks(range(1, 1+len(optimizer.space.target))) plt.show()
Мы обнаруживаем, что в течение 20 итераций оптимизатору удалось определить, что при силе накачки D0 = 9.943
мы получаем выходную мощность lasing_power = 214871
, что очень близко к исходной цели 215233.
print("Optimized pump strength value: {:.3f}.".format(D0)) print("Output power: {}.".format(lasing_power)) >>> Optimized pump strength value: 9.943. >>> Output power: 214871.
Заключительные замечания
В этой статье мы использовали байесовскую оптимизацию, чтобы оптимизировать мощность накачки смоделированного одномерного лазера для некоторой целевой выходной мощности. Это очень простой пример, в котором мы оптимизируем одну входную переменную для одной целевой переменной. В действительности лазеры намного сложнее, и несколько целевых переменных будут оптимизированы одновременно для набора нескольких входных переменных. Для этого нам нужно будет использовать многоцелевую оптимизацию, которая является более сложной, чем одноцелевая оптимизация, выполненная выше.
Рекомендации
Дж. Дурис, Д. Кеннеди, А. Ханука, Дж. Шталенкова, А. Эделен, А. Эггер, Т. Коуп, Д. Ратнер. Байесовская оптимизация лазера на свободных электронах, Физ. Преподобный Летт. 124, 124801, 2020.