Как я в 16 лет написал программу для оптимизации ловушки Пеннинга с GPU-ускорением

Здравствуйте! Мне 16 лет, и я хотел бы рассказать о своём пути от идеи до программы, которая способна оптимизировать электромагнитные ловушки для заряженных частиц.

Мотивация

Меня достаточно давно (несколько лет назад) начала интересовать тема межзвёздных перелётов. Мне хотелось увидеть, как выглядят экзопланеты, узнать, есть ли на них жизнь, и т.д. И однажды я решил поставить себе цель - понять, что мешает отправить зонд в другую звёздную систему, и решить все проблемы.

Этап 1: осознание ограничений существующих систем

Думаю, нужно начать с того, что ракетные двигатели, которые уже есть у человечества, не позволяют разогнать ракету до значительных долей от скорости света за разумное время. Химические двигатели обладают большой тягой, но крайне малым удельным импульсом, т.е. они расходуют топливо крайне, крайне неэффективно. Ионные же двигатели обладают достаточно большим удельным импульсом, но очень маленькой тягой, т.е. они не позволяют разогнать аппарат за разумное время, несмотря на эффективный расход топлива.

Этап 2: поиск новых принципов

Когда я начал разбираться с концептами теоретически обоснованных ракетных двигателей, я узнал про ядерный ракетный двигатель (ЯРД), термоядерный ракетный двигатель (ТЯРД) и фотонный (аннигиляционный) ракетный двигатель. После разбора каждого двигателя, я понял, что наиболее подходящий двигатель для межзвёздных перелётов - это фотонный (аннигиляционный) двигатель, так как он обладает наибольшей эффективностью среди всех теоретически обоснованных двигателей.

Что такое аннигиляция? Аннигиляция - это процесс контакта частицы и соответствующей ей античастицы, при котором они взаимоуничтожаются с высвобождением огромного количества энергии, то есть аннигиляционный ракетный двигатель должен использовать энергию аннигиляции вещества и антивещества.

Этап 3: разбор проблем при создании ракеты на антивеществе

Я начал пытаться понять, что мешает создать ракету на антивеществе, и понял, что спроектировать двигатель для этой ракеты - это далеко не самая сложная задача. Созданию ракеты на антивеществе, в первую очередь, мешает проблема удержания (хранения) и производства огромного количества антивещества. На данный момент человечество не способно создавать и хранить в течении продолжительного времени даже миллиграммы антивещества. Речь идёт о нанограммах или об отдельных атомах и частицах.

Я решил взяться за первую проблему, за проблему удержания и хранения антивещества.

Этап 4: понимание принципов работы существующих решений проблемы удержания

Я понял, что антивещество хранят при помощи различных ловушек. Для заряженных частиц и античастиц используют ловушки Пеннинга и ловушки Пауля, а для нейтральных частиц (например, для атомов антиводорода) применяют ловушки Иоффе-Питчарда. Ниже представлена схема устройства ловушки Пеннинга.

Ловушка Пеннинга
Ловушка Пеннинга

Ловушка Пеннинга состоит из двух торцевых электродов, кольцевого электрода (на картинке он в разрезе и обозначен буквой b) и электромагнита, создающего магнитное поле, удерживающее частицы в радиальном направлении. Торцевые электроды же создают электрическое поле, удерживающее частицы в осевом направлении.

Этап 5: осознание ограничений существующих решений

Сейчас ловушки Пеннинга используют гораздо чаще для хранения заряженных античастиц по сравнению с ловушками Пауля, поэтому ловушки Пауля я рассматривать не стал. При удержании одноимённо заряженных частиц возникает проблема пространственного заряда, т.е. частицы начинают отталкиваться друг от друга настолько сильно, что поле ловушки перестаёт их удерживать, а удерживать нейтральные частицы достаточно сложно, так как они слабо реагируют на магнитное поле и вообще не реагируют на электрическое поле.

Этап 6: попытки обойти эти ограничения

Я решил попытаться обойти проблему пространственного заряда при удержании заряженных частиц. Вначале я пытался как-то использовать Теорему Гаусса, но потом понял, что можно компенсировать пространственный заряд одних частиц пространственным зарядом других частиц, т.е. сделать ловушку, которая может одновременно удерживать противоположно заряженные частицы в разных "областях". После некоторых размышлений, я пришёл к такой конфигурации:

Модификация ловушки Пеннинга
Модификация ловушки Пеннинга

Зелёным цветом на изображении выделены электроды, а серым диэлектрик. У этой ловушки нет кольцевого электрода, как у обычной ловушки Пеннинга. На картинке вокруг этой ловушки слой диэлектрика. Торцевые электроды в этой ловушке параллельно выполняют функции и торцевых, и кольцевых. В электродах на этом изображении имеются отверстия для инжекции. Каждая пара торцевых электродов должна иметь заряд с тем же знаком, что и у частиц между этой парой, т.е. удерживать их в осевом направлении. Пример такой пары изображен ниже.

Пара электродов
Пара электродов

Соседние пары электродов, в данном случае, должны притягивать эти частицы, т.е. иметь противоположный по знаку заряд. Далее показано, какие, в данном случае, электроды входят в эти пары.

Соседние пары электродов
Соседние пары электродов

Этап 7: анализ

На этом я не остановился, и начал анализировать свою конструкцию. Для этого мне пришлось написать на Python симулятор взаимодействия заряженных частиц в однородном магнитном поле, а затем адаптировать его под симуляцию своей ловушки и оптимизировать его под GPU-вычисления, так как учитывалось взаимодействие достаточно большого количества частиц. Ниже представлен фрагмент моего кода, отвечающий за вычисления.

import math import numpy as np from numba import cuda k = 9e+9 t = 1e-9 @cuda.jit('void(float64[:,:], float64[:,:], int64, float64[:,:])') def f(coordinates, characteristics, i, forces): i2 = cuda.blockIdx.x * cuda.blockDim.x + cuda.threadIdx.x # Отображение потока на индекс массива if i2 >= len(coordinates): return q2 = characteristics[i2][0] # Заряд второй частицы x_target = coordinates[i] rx = (coordinates[i2][0] - x_target[0]) * 1e-5 # Расстояние по оси X ry = (coordinates[i2][1] - x_target[1]) * 1e-5 # Расстояние по оси Y rz = (coordinates[i2][2] - x_target[2]) * 1e-5 # Расстояние по оси Z R_norm = math.sqrt(rx ** 2 + ry ** 2 + rz ** 2) # Модуль расстояния if R_norm < 1e-10: return q1 = characteristics[i][0] # Заряд первой частицы # Закон Кулона F_mag = k * (q1 * q2) / R_norm ** 2 fx = F_mag * (rx / R_norm) fy = F_mag * (ry / R_norm) fz = F_mag * (rz / R_norm) forces[i2][0] = -fx forces[i2][1] = -fy forces[i2][2] = -fz def calc( coordinates: np.ndarray, characteristics: np.ndarray, velocities: np.ndarray, index: int, B_vector: np.ndarray, F: np.ndarray ): """ Обновляет координаты и скорости при помощи модифицированного метода Эйлера """ coordinates2 = list(coordinates) v = list(velocities) # Получение информации о видеокарте device = cuda.get_current_device() # Перенос данных на видеокарту d_coordinates = cuda.to_device(np.array(coordinates2)) d_characteristics = cuda.to_device(np.array(characteristics)) tpb = device.WARP_SIZE bpg = int(np.ceil(len(coordinates) / tpb)) a2 = [] for i, x in enumerate(coordinates[index:]): forces = cuda.device_array_like(np.zeros((len(coordinates), 3,))) f[bpg, tpb](d_coordinates, d_characteristics, i+index, forces) # Перенос данных обратно на хост forces = forces.copy_to_host() # Результирующая сила + Внешняя дестабилизирующая сила + Сила Лоренца total_force = np.sum(forces, axis=0) total_force += F total_force += characteristics[i+index][0] * np.cross(v[i+index], B_vector) # Ускорение a = total_force / characteristics[i+index][1] a2.append(a) v[i+index] = velocities[i+index] + (a * t) coordinates2[i+index] = x + (v[i+index] * t) * 1e+5 # Перенос данных на видеокарту d_coordinates = cuda.to_device(np.array(coordinates2)) # Коррекция for i, x in enumerate(coordinates[index:]): forces = cuda.device_array_like(np.zeros((len(coordinates), 3,))) f[bpg, tpb](d_coordinates, d_characteristics, i+index, forces) # Перенос данных обратно на хост forces = forces.copy_to_host() # Результирующая сила + Внешняя дестабилизирующая сила + Сила Лоренца total_force = np.sum(forces, axis=0) total_force += F total_force += characteristics[i+index][0] * np.cross(v[i+index], B_vector) # Ускорение a = total_force / characteristics[i+index][1] # Вычисление новых координат и скоростей v[i+index] = velocities[i+index] + (0.5 * (a + a2[i]) * t) coordinates2[i+index] = x + (v[i+index] * t) * 1e+5 return coordinates2, v

Этап 8: попытка автоматизировать процесс проектирования

После попыток решить (или обойти) проблему пространственного заряда, мне пришла в голову мысль, что можно попробовать автоматизировать процесс проектирования. Для этого мне пришлось разобраться с генетическим алгоритмом, а затем сформулировать функцию оценки качества удержания. Я понял, что можно просто рассчитывать, насколько сильно частицы сдвинулись со своих изначальных положений:

import numpy as np def fit(tr: list, coordinates: list): """ Функция оценки качества ловушки """ List2 = [] for point1 in coordinates: List = [] for point2 in tr: distance = np.sqrt(np.sum((point2 - point1) ** 2)) List.append(distance) List2.append(min(List)) return sum(List2)

После этого я реализовал упрощённый генетический алгоритм для оптимизации своей ловушки.

Заключение

За время своего исследования я узнал много нового и много чему научился. Я узнал множество тем из физики, программирования и математики. Если кому-то интересно узнать чуть больше подробностей о моём проекте, то я рассказывал о нём в одном Telegram-чате.

Спасибо за внимание!

2