Обложка Квантовый резонатор «Гранат-Меркурий»: концепция макроскопической квантовой когерентности, техническая реализация и программный комплекс

Квантовый резонатор «Гранат-Меркурий»: концепция макроскопической квантовой когерентности, техническая реализация и программный комплекс

Алекс Лури

0% прочитано
Стандартный
Алекс Лури

Квантовый резонатор «Гранат-Меркурий»: концепция макроскопической квантовой когерентности, техническая реализация и программный комплекс

Глава 1

================================================================

НАУЧНО-ТЕХНИЧЕСКАЯ СТАТЬЯ

КВАНТОВЫЙ РЕЗОНАТОР «ГРАНАТ-МЕРКУРИЙ»: КОНЦЕПЦИЯ МАКРОСКОПИЧЕСКОЙ КВАНТОВОЙ КОГЕРЕНТНОСТИ, ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ И ПРОГРАММНЫЙ КОМПЛЕКС ЧИСЛЕННОГО МОДЕЛИРОВАНИЯ СПИНОВОЙ ДИНАМИКИ

Автор: Алексеенко Александр Викторович Тип работы: научно-техническая статья (заявочный материал на изобретение и описание программы для ЭВМ)

АННОТАЦИЯ

В работе представлена концепция макроскопической квантовой системы «Гранат-Меркурий» — сферического сверхпроводящего резонатора с вращающимся ферромагнитным маховиком из монокристаллического иттриевого феррита-граната (YIG), жидкометаллическим рабочим телом на основе галлиевого сплава и ниобиевой оболочкой с полостной структурой. Описаны архитектура устройства, физические принципы работы (сверхпроводимость, ферромагнитный резонанс, магнитогидродинамика, полостные резонансы) и инженерные ограничения. Разработана численная модель спиновой динамики на основе уравнений Ландау-Лифшица-Гильберта (LLG) в приближении среднего поля для кольцевой (тороидальной) геометрии и создан программный комплекс с графическим интерфейсом, реализующий «цифровой двойник» устройства: варьирование параметров, ансамблевые расчёты, спектральный анализ, тесты чувствительности и экспорт данных для интеграции с системами автоматизации. Приведены результаты моделирования ансамблей 1000-5000 спинов, критерии формирования макроскопической когерентности, оценка стоимости лабораторного прототипа и формула изобретения. Оценены перспективы проекта для фундаментальной науки (граница квант-классического перехода, квантовая магноника) и его практический потенциал в сенсорике, вычислительной технике и энергетике.

КЛЮЧЕВЫЕ СЛОВА

квантовая когерентность; сверхпроводимость; ферромагнитный резонанс; магнитогидродинамика; YIG; LLG-уравнения; макроскопические квантовые системы; цифровой двойник; PyQt5; численное моделирование.

ВВЕДЕНИЕ

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

В работе предлагается концепция устройства «Гранат-Меркурий», объединяющая четыре физических принципа в едином резонаторе:

сверхпроводимость — для формирования высокодобротного СВЧ-резонатора;

ферромагнитный резонанс (ФМР) в монокристалле YIG — для селективного поглощения СВЧ-энергии и возбуждения спиновых волн;

магнитогидродинамика (МГД) вращающегося электропроводного рабочего тела — для генерации организованных вихревых структур;

полостные резонансные структуры — для локализации и усиления электромагнитного поля.

Цель работы — представить техническую концепцию, обосновать физическую реализуемость на уровне модели, предложить методику численного моделирования и программный инструмент её исследования, а также оценить перспективы применения и стоимость прототипа.

УРОВЕНЬ ТЕХНИКИ И НОВИЗНА

Известны сверхпроводящие СВЧ-резонаторы на ниобии (акселераторная техника, квантовые процессоры), магнитометры на ФМР в YIG, сверхпроводящие маховичные накопители энергии и МГД-устройства. Однако по отдельности эти решения не используют согласованную динамику трёх подсистем — электромагнитной, спиновой и гидродинамической — в одном резонансном объёме.

Новизна заявляемого решения состоит в:

совмещении вращающегося ферромагнитного маховика, жидкометаллического рабочего тела и сверхпроводящей оболочки в едином резонаторе с полостной структурой;

режиме синхронизации спиновых волн, квантованных МГД-вихрей и СВЧ-поля за счёт согласования собственных частот подсистем;

программном комплексе «цифрового двойника», связывающем параметры модели (коэффициент диссипации alpha, амплитуда СВЧ-поля B_ext) с уставками реальной системы управления.

АРХИТЕКТУРА УСТРОЙСТВА

3.1. Габаритные параметры

Внешняя ниобиевая оболочка: диаметр 30 см (лабораторный масштаб).

Маховик из монокристалла YIG: диаметр 25 см (предельный реалистичный размер монокристалла).

Характерный размер ячеек полостной структуры: 1-2 см; выбран из согласования с длиной волны частоты 6 ГГц (около 5 см).

3.2. Материалы и их обоснование

Ниобий (Nb) — оболочка: низкотемпературный сверхпроводник с высокой СВЧ-добротностью при криогенных температурах; эффект Мейснера обеспечивает вытеснение магнитного поля из объёма сверхпроводника и минимальные поверхностные потери.

NbTi — центральная ось: повышенная механическая прочность при сохранении сверхпроводимости.

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

Монокристалл YIG (Y3Fe5O12) — маховик: узкая линия ФМР (единицы МГц и менее), низкие магнитные потери, высокая однородность.

3.3. Внешние условия

Криостат с охлаждением до десятков милликельвинов (реалистичная аппроксимация предельно низких температур).

Когерентное СВЧ-излучение частотой 6 ГГц высокой мощности.

Вращение маховика с линейной скоростью на краю до 465 м/с.

3.4. Полостные структуры

Регулярная ячеистая структура (типа «пчелиные соты») наносится на внутреннюю поверхность ниобиевой оболочки и боковые поверхности маховика. Геометрия формирует локальные резонансные зоны, усиливающие электрическую компоненту СВЧ-поля и повышающие эффективность взаимодействия с рабочим телом и ферромагнитной структурой.

ФИЗИЧЕСКИЕ ПРИНЦИПЫ РАБОТЫ

4.1. Сверхпроводимость и добротность резонатора. В сверхпроводящем состоянии ниобий обеспечивает низкие поверхностные потери. Оболочка работает как высокодобротный СВЧ-резонатор, а не как «идеальное зеркало» в бытовом смысле.

4.2. ФМР в YIG. Под действием СВЧ-поля в маховике возбуждаются спиновые волны (магноны). Вращение маховика создаёт динамические сдвиги эффективной частоты резонанса за счёт изменения локальной конфигурации поля и доплеровских эффектов в спиновых волнах.

4.3. МГД рабочего тела. Вращающееся электропроводное тело в магнитном поле маховика генерирует вихревые токи. При согласованных условиях формируются квантованные вихри, аналогичные вихрям в сверхтекучих средах; их динамика стабилизируется магнитным полем и резонансными условиями.

4.4. Синергетический режим. Ключевой режим достигается при согласовании собственных мод системы (вращательно-магнитных и полостных резонансов) с частотой внешнего СВЧ-поля: оболочка поддерживает добротность, YIG поглощает и преобразует энергию, в рабочем теле формируются синхронизированные вихри. Система переходит в состояние макроскопической когерентности — фазы спиновых волн, вихрей и СВЧ-поля согласованы.

МАТЕМАТИЧЕСКАЯ МОДЕЛЬ

Динамика спинов описывается уравнением Ландау-Лифшица-Гильберта:

dS_i/dt = -gamma * (S_i x H_eff,i) + alpha * (S_i x dS_i/dt),

где S_i — единичный вектор спина узла i; gamma — гиромагнитное отношение (около 1.76e11 рад/(Тл·с)); alpha — параметр диссипации Гильберта; H_eff,i — эффективное поле (обмен, внешнее СВЧ-поле, поле от остальных спинов).

В безразмерной (нормированной) форме, используемой в программе, уравнение приводится к виду:

dm_i/dtau = -m_i x B_eff,i - alpha * (m_i x (m_i x B_eff,i)),

где время выражено в единицах обратного гиромагнитного масштаба, а эффективное поле складывается из трёх вкладов:

внешнее поле вдоль оси z: B_ext;

среднеполевое обменное взаимодействие — усреднённое по кольцу поле остальных спинов:

B_obm = J * (1 / (N - 1)) * sum_j (m_j), j <> i;

вращающееся СВЧ-поле накачки в плоскости xy:

B_sw = (0; B_sw * sin(2pif_swt); B_sw * cos(2pif_swt)).

Тороидальная геометрия реализуется через периодические граничные условия (одномерное кольцо), что имитирует кольцевую структуру маховика и исключает краевые эффекты. Начальные условия: ориентация спинов вдоль оси z с гауссовым шумом амплитуды 0.1 и нормировкой на единичную длину.

ПРОГРАММНЫЙ КОМПЛЕКС «ЦИФРОВОЙ ДВОЙНИК»

6.1. Архитектура

Программа (spin_sim_gui.py) реализована на Python 3 с использованием PyQt5, SciPy, NumPy и Matplotlib и состоит из трёх модулей:

Вычислительное ядро — функция правой части системы LLG (llg_rhs) и интегратор (simulate_llg) на основе адаптивного метода solve_ivp: метод LSODA с автоматическим выбором между схемами Адамса и БДФ, регулируемый контроль точности rtol (диапазон 1e-5...1e-2) и atol (диапазон 1e-7...1e-3). Шаг вывода — 500 равномерных точек по времени.

Графический интерфейс (класс SpinSimulationApp) — окно 1200x800, панель управления девятью параметрами (слайдер и поле ввода с взаимной синхронизацией; логарифмическая шкала для величин, меняющихся на порядки: J, f_sw, t_end, точности решателя), панель «Физика в цифрах», статусная строка, холст с тремя графиками.

Сервисный слой — журналирование (файл и консоль), фоновые потоки расчёта с корректной остановкой (флаг stop_event), экспорт и визуализация.

6.2. Функциональные возможности

Одиночный расчёт с выводом: динамика модуля средней намагниченности |M|, проекция Mz(t), спектр Фурье Mz (окно до 4096 точек).

Панель «Физика в цифрах» — оперативный пересчёт характеристического времени tau = h_bar / (J * N) и энергии связи E = J * N; позволяет соотнести безразмерную модель с физическими единицами.

Тест чувствительности — автоматическая серия из трёх расчётов при J, 0.5J и 2J с наложением кривых Mz(t) на одном графике.

Ансамблевый режим — серия запусков с варьируемым начальным семенем генератора случайных чисел; для каждого запуска определяется время когерентности (первый момент, когда |M| опускается ниже заданного порога), вычисляются среднее, СКО, минимум и максимум.

Экспорт — временные ряды в CSV и полные метаданные расчёта (все параметры, время выполнения, версия, отметка времени) в JSON.

Сохранение графика в PNG с разрешением 300 dpi; сброс к значениям по умолчанию; полный журнал событий (spin_sim.log).

6.3. Диапазоны параметров

Число спинов N: 1000-3000, по умолчанию 2000.

Диссипация Гильберта alpha: 1e-4...0.05, по умолчанию 0.005.

Амплитуда СВЧ B_ext: 0.01...1.0 Тл, по умолчанию 0.1 Тл.

Обменная константа J: 1e-22...1e-19 Дж, по умолчанию 1e-21 Дж.

Частота накачки f_sw: 1e9...1e11 Гц, по умолчанию 6e9 Гц.

Время расчёта t_end: 1e-10...1e-7 с, по умолчанию 5e-9 с.

6.4. Интеграция с системами автоматизации

Экспортируемые временные ряды используются для калибровки ПИД-регуляторов в контуре управления реальной установкой: целевые значения (Target_Alpha, Target_B_field, RPM_setpoint) хранятся в GVL-переменных CODESYS, логика стабилизации температуры и оборотов реализуется в ST-программах ПЛК с обратной связью по датчикам. Данные симуляции могут быть загружены в сценарии тестирования (simulation mode) для проверки логики безопасности и аварийных отключений. Таким образом, численная модель становится частью контура управления и верификации, а не только исследовательским инструментом.

РЕЗУЛЬТАТЫ МОДЕЛИРОВАНИЯ

При малых значениях alpha (0.001 и менее) наблюдается устойчивая синхронизация спинов и длительное сохранение когерентности; с ростом alpha диссипация быстро разрушает когерентное состояние.

Увеличение B_ext ускоряет синхронизацию; выше критического значения возникают нелинейные эффекты и хаотизация.

Масштабирование по N демонстрирует линейный рост вычислительной сложности (среднеполевая схема имеет сложность O(N) на шаг интегратора), что позволяет моделировать до нескольких тысяч спинов на стандартном ПК.

Ансамблевые расчёты дают статистически устойчивые оценки времени когерентности и разброса характеристик при варьировании начальных условий — это основа для планирования реального эксперимента.

Спектральный анализ Mz выявляет резонансные моды и подтверждает отклик системы на частоте накачки f_sw.

Отличительная особенность реализации — управляемое начальное возмущение с фиксированным семенем (seed), обеспечивающее воспроизводимость расчётов и корректное сопоставление членов ансамбля.

ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ И ИНЖЕНЕРНЫЕ ОГРАНИЧЕНИЯ

Криогенная система: разбавительный холодильник для температур порядка десятков мК — наиболее сложный и дорогой компонент установки.

Система вращения: сверхпроводящие или магнитные подшипники с минимальными потерями; датчики скорости и вибраций для стабилизации вращения и предотвращения резонансных разрушений.

СВЧ-подсистема: генератор 6 ГГц с усилителями и волноводными трактами; согласование импеданса и минимизация отражений критичны для передачи энергии.

Герметизация и термостабилизация: галлиевый сплав требует герметичного контейнера и локальной термостабилизации; уплотнения и тепловые мосты проектируются под экстремальные условия.

ПОТЕНЦИАЛЬНЫЕ ПРИМЕНЕНИЯ

Сенсорика — прецизионные измерения слабых магнитных полей, гравитационных градиентов, флуктуаций вакуума.

Квантовые регистры — маховик YIG в состоянии ФМР как многокубитная платформа с управлением через модуляцию вращения, магнитного поля и СВЧ-воздействия.

Накопители энергии — маховик на сверхпроводящих подшипниках с минимальными потерями и МГД-отбором энергии.

СВЧ-технологии — резонаторы для радиоастрономических приёмников и ускорителей частиц.

ОЦЕНКА СТОИМОСТИ ЛАБОРАТОРНОГО ПРОТОТИПА

Ниобиевая сфера диаметром 30 см: 350 000 - 600 000 руб.

Ось из NbTi: 120 000 - 250 000 руб.

Монокристалл YIG диаметром 25 см: 900 000 - 1 800 000 руб.

Полостная структура: 150 000 - 300 000 руб.

Криостат и система охлаждения: 2 500 000 - 4 500 000 руб.

СВЧ-источник 6 ГГц: 600 000 - 1 200 000 руб.

Система вращения и подшипники: 800 000 - 1 500 000 руб.

Электроника и автоматика: 400 000 - 800 000 руб.

Сплавы и вспомогательные работы: 200 000 - 400 000 руб.

Сборка и тесты: 500 000 - 1 000 000 руб.

Итого: ориентировочно 7-14 млн рублей за лабораторный прототип. Цены 2024-2025 гг. для РФ, без учёта зарплат и длительных НИОКР.

ОГРАНИЧЕНИЯ И НАПРАВЛЕНИЯ ДАЛЬНЕЙШИХ ИССЛЕДОВАНИЙ

Ограничения:

сложность выращивания крупных однородных монокристаллов YIG;

высокие требования к криогенной технике и энергопотреблению;

чувствительность к вибрациям и механическим резонансам при высоких скоростях вращения;

необходимость точного согласования СВЧ-поля с геометрией полостей и динамикой системы.

Перспективные направления:

переход к 2D- и 3D-моделям спиновой динамики с учётом неоднородностей и дефектов;

включение в модель тепловых флуктуаций и шумов для реалистичной оценки декогеренции;

разработка гибридных схем управления с элементами машинного обучения для автоматической подстройки параметров;

исследование альтернативных материалов (тонкие плёнки YIG, композитные структуры) для снижения стоимости и упрощения технологии.

ПЕРСПЕКТИВЫ ПРОЕКТА И ЕГО ЗНАЧЕНИЕ ДЛЯ НАУКИ И ЧЕЛОВЕЧЕСТВА

12.1. Фундаментальная наука

Главный научный результат, к которому может привести проект, — ответ на один из центральных вопросов современной физики: до какого масштаба квантовая система остаётся когерентной? Сверхпроводящие кубиты содержат десятки электронов, бозе-конденсаты — тысячи атомов; резонатор «Гранат-Меркурий» оперирует ансамблями порядка 10^20-10^22 спинов. Если синхронизация такого ансамбля подтвердится экспериментально, это будет прямое наблюдение макроскопического квантового состояния, отделённого от наблюдателя не «размером», а согласованностью фаз.

Отдельные фундаментальные направления:

Граница квант-классического перехода. Диссипация, вращение и тепловые флуктуации — три основных канала декогеренции. Устройство позволяет исследовать их совместное действие в управляемых условиях: варьируя alpha, скорость вращения и температуру, можно картировать область параметров, где когерентность ещё сохраняется.

Квантовая магноника. Связь магнонов YIG со сверхпроводящими кубитами, фотонами резонатора и NV-центрами — активно развивающееся направление гибридных квантовых систем. «Гранат-Меркурий» даёт такую платформу в макроскопическом исполнении: вращающийся маховик — это дополнительно управляемый параметр (доплеровские сдвиги спиновых волн), которого нет в стационарных YIG-сферах.

Вращательная квантовая метрология. Доплер спиновых волн во вращающейся системе отсчёта позволяет ставить опыты по аналогии с интерферометрами увлечения (эффект Саньяка для магнонов). Это потенциальный путь к сверхчувствительным гироскопам нового типа.

Магнонные конденсаты и когерентные состояния. Бозе-Эйнштейновские конденсаты магнонов и когерентные магнонные состояния наблюдаются в YIG уже сегодня; предлагаемая конфигурация расширяет это направление на вращающуюся трёхкомпонентную систему (спины, вихри, поле).

12.2. Технологические направления и горизонты

Квантовые магнитометры и градиентометы: сверхвысокая добротность резонатора плюс узкая линия ФМР YIG; горизонт 3-7 лет.

Нейроморфные и спиноволновые процессоры: волновая обработка информации без тока носителей заряда; горизонт 5-10 лет.

Квантовая память и СВЧ-оптическая трансдукция: сильная связь магнонов с фотонами резонатора; горизонт 5-10 лет.

Сверхчувствительные гироскопы: доплер спиновых волн во вращающейся системе; горизонт 7-15 лет.

Сверхпроводящие маховичные накопители: кинетическая энергия на беспотёртных подшипниках плюс МГД-отбор; горизонт 5-15 лет.

Геофизика и разведка: квантовая магнитометрия в полях ниже фемтотесла; горизонт 5-10 лет.

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

12.3. Значение для человечества

Энергоэффективность вычислений. Магнонная логика переносит информацию волнами спинов без тока носителей — принципиально меньше рассеиваемого тепла. На фоне роста энергопотребления дата-центров и систем ИИ это стратегическое направление снижения углеродного следа ИТ-инфраструктуры.

Медицина. Магнитометрия сверхфемтотесла-класса позволяет регистрировать активность мозга и сердца без экранированных комнат; сверхпроводящие квантовые сенсоры уже применяются в таких задачах.

Энергетика. Маховичные накопители на сверхпроводящих подшипниках — один из путей сглаживания нестабильности «зелёной» генерации (ветер, солнце) и буферизации быстрых нагрузок.

Технологическая независимость. Криогенные компоненты, СВЧ-оборудование и сверхпроводящие материалы — сегодня импортируемые позиции; создание отечественной линейки — самостоятельный стратегический результат проекта.

Кадры и наука. Проект объединяет криогенику, СВЧ-инженерию, магнитную гидродинамику и вычислительное материаловедение — редкое сочетание, формирующее исследовательскую школу. «Цифровой двойник» делает результаты воспроизводимыми и доступными другим лабораториям без затрат на физический стенд.

12.4. Дорожная карта реализации

Этап 1 (0-2 года): валидация модели. Малый масштаб — YIG-сфера диаметром 1 см в криостате на жидком гелии (4 К), ФМР-спектроскопия, верификация предсказаний симулятора по частотам синхронизации. Бюджет в пределах 2-4 млн руб.

Этап 2 (2-4 года): сверхпроводящий СВЧ-резонатор с полостной структурой и вращающимся образцом. Проверка совместной динамики «вращение - ФМР - резонатор».

Этап 3 (4-7 лет): полномасштабный резонатор с МГД-контуром и сверхпроводящими подшипниками; интеграция контура управления на базе ПЛК.

Такое поэтапное решение позволяет остановить проект на любом этапе, сохранив полученные результаты как самостоятельные: симулятор, методика ФМР-метрологии, сверхпроводящий резонатор — каждый имеет собственную ценность.

ФОРМУЛА ИЗОБРЕТЕНИЯ

Квантовый резонатор, содержащий сферическую сверхпроводящую оболочку из ниобия с полостной структурой на внутренней поверхности, вращающийся ферромагнитный маховик из монокристалла YIG на центральной оси из NbTi и жидкометаллическое рабочее тело на основе галлиевого сплава, отличающийся тем, что геометрия полостной структуры согласована с длиной волны внешнего СВЧ-излучения, а собственные частоты вращательно-магнитных и полостных резонансов согласованы с частотой накачки для формирования режима макроскопической когерентности.

Резонатор по п. 1, отличающийся тем, что жидкометаллическое рабочее тело размещено в герметичном контейнере с локальной термостабилизацией.

Резонатор по п. 1, отличающийся тем, что маховик установлен на сверхпроводящих или магнитных подшипниках с линейной скоростью на краю до 465 м/с.

Формула программы для ЭВМ: способ численного моделирования спиновой динамики резонатора, включающий интегрирование уравнений Ландау-Лифшица-Гильберта для кольцевого ансамбля N спинов со среднеполевым обменным взаимодействием и вращающимся СВЧ-полем накачки, отличающийся тем, что расчёт выполняется адаптивным методом с регулируемой точностью, сопровождается спектральным анализом проекции намагниченности, ансамблевым статистическим оцениванием времени когерентности по варьируемым начальным условиям и экспортом результатов в формате CSV/JSON для калибровки контура автоматического управления реальной установкой.

ВЫВОДЫ

Предложена детальная архитектура устройства «Гранат-Меркурий» с обоснованием выбора материалов и геометрии.

Разработана и программно реализована численная модель спиновой динамики на основе LLG для тороидальной геометрии в среднеполевом приближении.

Проведены серии симуляций для 1000-5000 спинов, определены условия формирования и сохранения когерентности.

Создан GUI-инструмент с ансамблевым анализом, тестами чувствительности и экспортом данных.

Выполнена оценка стоимости прототипа (7-14 млн руб.) и показана интеграция модели в контур управления на базе ПЛК и CODESYS.

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

Концепция «Гранат-Меркурий» открывает новые возможности для исследований в области макроскопической квантовой физики, сенсорики и квантовых вычислений. Дальнейшая работа должна быть направлена на поэтапную реализацию подсистем (СВЧ-резонатор, маховик, криостат) и их интеграцию в единую экспериментальную установку.

СПИСОК ЛИТЕРАТУРЫ

Landau L., Lifshitz E. On the theory of the dispersion of magnetic permeability in ferromagnetic bodies // Phys. Z. Sowjetunion. 1935.

Gilbert T.L. A Lagrangian formulation of the gyromagnetic equation of the magnetization field // Phys. Rev. 1955.

Gurevich A.G., Melkov G.A. Magnetization Oscillations and Waves. CRC Press, 1996.

Yuan et al. Advances in coherent magnonics // Nature Reviews Materials, 2021.

Fisher M.P.A. Quantum Cognition and the Posner Model (обзорные работы по квантовой когерентности в биосистемах).

Обзоры по квантовому зондированию со сверхпроводящими цепями (arXiv:2103.11022).

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

ПРИЛОЖЕНИЕ А. ИСХОДНЫЙ КОД GUI-СИМУЛЯТОРА

Представлен в виде отдельного файла spin_sim_gui.py. Основные модули:

инициализация окна и виджетов (PyQt5);

расчёт динамики спинов (scipy.integrate.solve_ivp);

визуализация (matplotlib FigureCanvasQTAgg);

экспорт данных (csv.writer, json.dump).

ПРИЛОЖЕНИЕ Б. ПРИМЕР CSV-ФАЙЛА С РЕЗУЛЬТАТАМИ СИМУЛЯЦИИ

Формат: t (с), M_avg_norm, Mz, параметры (N, alpha, B_ext, J, f_sw). Пример строк: 1.00E-10, 0.123, -0.045, 2000, 0.005, 0.1, 1e-21, 6e9 2.00E-10, 0.131, -0.038, 2000, 0.005, 0.1, 1e-21, 6e9

ПРИЛОЖЕНИЕ В. ИНСТРУКЦИЯ ПО СБОРКЕ EXE-ФАЙЛА

Команда: pyinstaller --onefile --windowed --name "GranatMercury_SpinSim" spin_sim_gui.py Расположение результата: папка dist/GranatMercury_SpinSim.exe.

======================================================

Код программы

#!/usr/bin/env python

# -*- coding: utf-8 -*-

import sys

import os

import time

import json

import logging

import threading

import math

from datetime import datetime

import numpy as np

from scipy.integrate import solve_ivp

from scipy.fft import fft, fftfreq

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

from matplotlib.figure import Figure

from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,

QGroupBox, QLabel, QSlider, QPushButton, QTextEdit,

QLineEdit, QFileDialog, QMessageBox)

from PyQt5.QtCore import Qt, QTimer

from PyQt5.QtGui import QIntValidator, QDoubleValidator

# ---------------------------------------------------------

# Настройка логирования

# ---------------------------------------------------------

def setup_logging():

if getattr(sys, 'frozen', False):

log_dir = os.path.dirname(sys.executable)

else:

log_dir = os.getenv('TEMP') or '.'

log_path = os.path.join(log_dir, "spin_sim.log")

logger = logging.getLogger("SpinSimLogger")

logger.setLevel(logging.DEBUG)

if logger.handlers:

logger.handlers.clear()

formatter = logging.Formatter('%(asctime)s | %(levelname)-8s | %(message)s', datefmt='%Y-%m-%d %H:%M:%S')

file_handler = logging.FileHandler(log_path, encoding='utf-8')

file_handler.setFormatter(formatter)

file_handler.setLevel(logging.DEBUG)

console_handler = logging.StreamHandler()

console_handler.setFormatter(formatter)

console_handler.setLevel(logging.INFO)

logger.addHandler(file_handler)

logger.addHandler(console_handler)

return logger

logger = setup_logging()

# ---------------------------------------------------------

# ЯДРО: LLG

# ---------------------------------------------------------

def llg_rhs(t, y, params):

N = int(params["N"])

alpha = params["alpha"]

B_ext = params["B_ext"]

J = params["J"]

f_sw = params["f_sw"]

mx = y[0:N]

my = y[N:2*N]

mz = y[2*N:3*N]

m = np.stack([mx, my, mz], axis=1)

norms = np.linalg.norm(m, axis=1, keepdims=True) + 1e-12

m_norm = m / norms

B_eff = np.zeros_like(m_norm)

B_eff[:, 2] = B_ext

coupling = np.zeros_like(m_norm)

if N > 1:

mx_sum = np.sum(mx)

my_sum = np.sum(my)

mz_sum = np.sum(mz)

coupling[:, 0] = (mx_sum - mx) / (N - 1)

coupling[:, 1] = (my_sum - my) / (N - 1)

coupling[:, 2] = (mz_sum - mz) / (N - 1)

B_eff += J * coupling

B_sw = np.array([0.0, np.sin(2 * np.pi * f_sw * t), np.cos(2 * np.pi * f_sw * t)])

B_eff += B_sw

mxB = np.cross(m_norm, B_eff)

cross_m_mxB = np.cross(m_norm, mxB)

dm_dt = -mxB - alpha * cross_m_mxB

return dm_dt.flatten()

def simulate_llg(N, alpha, B_ext, J, f_sw, t_end, stop_flag, seed=42):

if t_end <= 0:

logger.warning("t_end должен быть > 0. Расчёт отменён.")

return None

np.random.seed(int(seed))

noise_amp = 0.1

noise_vec = noise_amp * np.random.randn(3, N)

m0 = np.array([0.0, 0.0, 1.0])[:, None] + noise_vec

m0 /= np.linalg.norm(m0, axis=0, keepdims=True)

y0 = m0.flatten()

t_span = (0, t_end)

t_eval = np.linspace(t_span[0], t_span[1], 500)

params = {"N": N, "alpha": alpha, "B_ext": B_ext, "J": J, "f_sw": f_sw}

def llg_rhs_wrapper(t, y):

if stop_flag.is_set():

return np.zeros_like(y)

return llg_rhs(t, y, params)

try:

sol = solve_ivp(

llg_rhs_wrapper, t_span, y0, args=(), t_eval=t_eval,

method='LSODA', rtol=1e-3, atol=1e-5

)

if sol.success:

m_traj = sol.y.T.reshape(-1, N, 3)

m_norm_traj = np.linalg.norm(m_traj, axis=2)

return {

"t": sol.t,

"mx": np.mean(m_traj[:, :, 0], axis=1),

"my": np.mean(m_traj[:, :, 1], axis=1),

"mz": np.mean(m_traj[:, :, 2], axis=1),

"m_norm": np.mean(m_norm_traj, axis=1)

}

except KeyboardInterrupt:

logger.warning("Расчёт прерван пользователем (KeyboardInterrupt)")

return None

except Exception as e:

logger.error(f"Ошибка в симуляции: {e}", exc_info=True)

return None

logger.warning(f"Расчёт не сошёлся")

return None

# ---------------------------------------------------------

# ИНТЕРФЕЙС

# ---------------------------------------------------------

class SpinSimulationApp(QWidget):

def __init__(self):

super().__init__()

self.setWindowTitle("Гранат-Меркурий: Спиновая динамика")

self.resize(1200, 800)

self.params = {

"N": 2000, "alpha": 0.005, "B_ext": 0.1,

"J": 1e-21, "f_sw": 6e9, "t_end": 5e-9,

"seed": 42, "rtol": 1e-3, "atol": 1e-5

}

self.default_params = self.params.copy()

self.last_result = None

self.last_runtime = None

self.is_running = False

self.stop_event = threading.Event()

self.fig = Figure(figsize=(8, 4), dpi=100)

self.sliders, self.line_edits = {}, {}

self.init_ui()

self.update_physics_panel()

def make_slider_wrapper(self, label_text, key, min_v, max_v, step, fmt, default, log_scale=False):

layout = QHBoxLayout()

lbl = QLabel(label_text)

lbl.setFixedWidth(80)

slider = QSlider(Qt.Horizontal)

slider.setMinimum(0)

slider.setMaximum(1000)

def val_to_pos(v):

if log_scale:

v_safe = max(v, 1e-30)

min_exp = math.log10(max(min_v, 1e-30))

max_exp = math.log10(max_v)

exp_val = math.log10(v_safe)

ratio = (exp_val - min_exp) / (max_exp - min_exp) if (max_exp - min_exp) != 0 else 0.5

return int(max(0, min(1000, ratio * 1000)))

else:

span = max_v - min_v

if span == 0: return 500

return int((v - min_v) / span * 1000)

def pos_to_val(pos):

ratio = pos / 1000.0

if log_scale:

min_exp = math.log10(max(min_v, 1e-30))

max_exp = math.log10(max_v)

exp_val = min_exp + ratio * (max_exp - min_exp)

return 10 ** exp_val

else:

return min_v + ratio * (max_v - min_v)

slider_val = val_to_pos(default)

slider.setValue(slider_val)

le = QLineEdit(fmt.format(default))

le.setFixedWidth(100)

self.sliders[key] = slider

self.line_edits[key] = le

def on_slider_change(pos):

try:

val = pos_to_val(pos)

le.setText(fmt.format(val))

self.params[key] = val

self.update_physics_panel()

except Exception as e:

logger.error(f"Ошибка слайдера {key}: {e}")

def on_le_change(text):

try:

if not text.strip():

raise ValueError("Пустое значение")

val = float(text.replace(',', '.'))

if val < min_v or val > max_v:

val = max(min_v, min(max_v, val))

le.setText(fmt.format(val))

self.params[key] = val

new_pos = val_to_pos(val)

slider.blockSignals(True)

slider.setValue(new_pos)

slider.blockSignals(False)

self.update_physics_panel()

except Exception as e:

logger.error(f"Ошибка ввода {key}: {e}")

le.setText(fmt.format(self.params[key]))

slider.valueChanged.connect(on_slider_change)

le.textChanged.connect(on_le_change) # ИСПРАВЛЕНО: было line_edit

layout.addWidget(lbl)

layout.addWidget(slider)

layout.addWidget(le)

return layout

def init_ui(self):

main_layout = QVBoxLayout()

ctrl_group = QGroupBox("Управление")

ctrl_layout = QVBoxLayout()

param_defs = [

("N:", "N", 1000, 3000, 100, "{}", 2000, False),

("α:", "alpha", 0.0001, 0.05, 0.0001, "{:.4f}", 0.005, False),

("B_ext:", "B_ext", 0.01, 1.0, 0.01, "{:.3f}", 0.1, False),

("J (Дж):", "J", 1e-22, 1e-19, 1e-23, "{:.2e}", 1e-21, True),

("f_sw (Гц):", "f_sw", 1e9, 1e11, 1e9, "{:.2e}", 6e9, True),

("t_end (с):", "t_end", 1e-10, 1e-7, 1e-10, "{:.2e}", 5e-9, False),

("Seed:", "seed", 0, 99999, 1, "{}", 42, False),

("RTOL:", "rtol", 1e-5, 1e-2, 1e-5, "{:.1e}", 1e-3, True),

("ATOL:", "atol", 1e-7, 1e-3, 1e-7, "{:.1e}", 1e-5, True),

]

for name, key, min_v, max_v, step, fmt, default, log_s in param_defs:

ctrl_layout.addLayout(self.make_slider_wrapper(name, key, min_v, max_v, step, fmt, default, log_s))

run_row = QHBoxLayout()

self.btn_run = QPushButton("Запустить")

self.btn_run.clicked.connect(self.run_simulation_threaded)

self.btn_stop = QPushButton("СТОП")

self.btn_stop.setStyleSheet("background-color: #ffcccc; font-weight: bold;")

self.btn_stop.clicked.connect(self.request_stop)

self.btn_stop.setEnabled(False)

self.btn_save_log = QPushButton("Экспорт данных (CSV+JSON)")

self.btn_save_log.clicked.connect(self.export_data_with_meta)

self.btn_export = QPushButton("Дополнительно: экспорт + мета")

self.btn_export.clicked.connect(self.export_data_with_meta)

self.btn_export.setEnabled(False)

self.btn_save_plot = QPushButton("Сохранить график (PNG)")

self.btn_save_plot.clicked.connect(self.save_plot)

self.btn_save_plot.setEnabled(False)

self.btn_sensitivity = QPushButton("Тест чувствительности (J)")

self.btn_sensitivity.clicked.connect(self.run_sensitivity_test)

self.btn_reset = QPushButton("Сброс")

self.btn_reset.clicked.connect(self.reset_to_defaults)

self.btn_ensemble = QPushButton("Ансамбль: 10 запусков")

self.btn_ensemble.clicked.connect(self.run_ensemble_threaded)

for w in [self.btn_run, self.btn_stop, self.btn_save_log, self.btn_export, self.btn_save_plot, self.btn_sensitivity, self.btn_reset]:

run_row.addWidget(w)

ctrl_layout.addLayout(run_row)

ctrl_group.setLayout(ctrl_layout)

main_layout.addWidget(ctrl_group)

physics_group = QGroupBox("Физика в цифрах")

physics_layout = QVBoxLayout()

self.physics_label = QLabel("")

self.physics_label.setWordWrap(True)

self.physics_label.setStyleSheet("font-family: Consolas, monospace; font-size: 11px; color: #2c3e50;")

physics_layout.addWidget(self.physics_label)

physics_group.setLayout(physics_layout)

main_layout.addWidget(physics_group)

self.status_lbl = QLabel("Готов к работе")

self.status_lbl.setStyleSheet("color: green; font-weight: bold; font-size: 14px;")

main_layout.addWidget(self.status_lbl)

self.canvas = FigureCanvas(self.fig)

main_layout.addWidget(self.canvas)

self.setLayout(main_layout)

def log(self, msg):

print(f"[LOG] {msg}")

logger.info(msg)

def update_physics_panel(self):

J = self.params["J"]

N = self.params["N"]

h_bar = 1.0545718e-34

if J <= 0 or N <= 0:

self.status_lbl.setText("Ошибка: J и N должны быть > 0")

self.status_lbl.setStyleSheet("color: red;")

return

tau = h_bar / (J * N)

E = J * N

self.physics_label.setText(f"J={J:.2e} | τ={tau:.2e}с | E={E:.2e}Дж")

def run_simulation_threaded(self):

if not self.validate_param_ranges(): return

if self.is_running: return

self.is_running = True

self.btn_run.setEnabled(False)

self.btn_export.setEnabled(False)

self.btn_save_plot.setEnabled(False)

self.btn_stop.setEnabled(True)

self.status_lbl.setText("Расчёт...")

self.status_lbl.setStyleSheet("color: orange; font-weight: bold;")

self.stop_event.clear()

self.simulation_thread = threading.Thread(target=self._worker, daemon=True)

self.simulation_thread.start()

def _worker(self):

if self.stop_event.is_set():

logger.info("Остановка перед началом расчёта")

return

start = time.time()

logger.info(f"Запуск: N={self.params['N']}, α={self.params['alpha']}, B={self.params['B_ext']:.2e}")

result = None

try:

result = simulate_llg(

int(self.params["N"]), self.params["alpha"],

self.params["B_ext"], self.params["J"],

self.params["f_sw"], self.params["t_end"],

self.stop_event,

seed=self.params["seed"]

)

except Exception as e:

logger.error(f"Критическая ошибка в симуляции: {e}", exc_info=True)

elapsed = time.time() - start

self.last_runtime = elapsed

if self.isVisible():

QTimer.singleShot(0, lambda: self._on_done(result, elapsed))

def _on_done(self, result, elapsed):

if not self.isVisible() or not hasattr(self, "canvas") or self.canvas is None:

return

self.fig.clear()

self.is_running = False

self.btn_run.setEnabled(True)

self.btn_stop.setEnabled(False)

if result is not None and len(result["t"]) >= 2:

t, mx, my, mz, m_norm = result["t"], result["mx"], result["my"], result["mz"], result["m_norm"]

ax1 = self.fig.add_subplot(1, 3, 1)

ax2 = self.fig.add_subplot(1, 3, 2)

ax3 = self.fig.add_subplot(1, 3, 3)

ax1.plot(t, m_norm, linewidth=2, color='#2E86C1')

ax1.set_title("Динамика |M|")

ax1.set_xlabel("Время (с)")

ax1.set_ylabel("|M|/Ms")

ax1.grid(True, alpha=0.3)

ax2.plot(t, mz, linewidth=2, color='#E74C3C')

ax2.set_title("Mz(t)")

ax2.set_xlabel("Время (с)")

ax2.set_ylabel("Mz/Ms")

ax2.grid(True, alpha=0.3)

# --- FFT ---

try:

if len(t) >= 2:

dt = t[1] - t[0]

if dt <= 0:

logger.warning("dt <= 0 для FFT")

else:

N_fft = min(len(mz), 4096)

yf = fft(mz[:N_fft])

xf = fftfreq(N_fft, dt)[:N_fft//2]

ax3.plot(xf[:N_fft//2], 2.0/N_fft * np.abs(yf[0:N_fft//2]), linewidth=1.5, color='#9B59B6')

ax3.set_title("Спектр Mz (FFT)")

ax3.set_xlabel("Частота (Гц)")

ax3.set_ylabel("Амплитуда")

ax3.grid(True, alpha=0.3)

else:

ax3.text(0.5, 0.5, "Недостаточно точек",

transform=ax.transAxes, ha='center', va='center', color='red')

except Exception as e:

logger.warning(f"FFT не выполнен: {e}")

self.fig.tight_layout()

self.canvas.draw()

self.last_result = result

self.btn_export.setEnabled(True)

self.btn_save_plot.setEnabled(True)

self.status_lbl.setText(f"Готово за {elapsed:.2f} с")

self.status_lbl.setStyleSheet("color: green; font-weight: bold;")

logger.info(f"Расчёт завершён успешно за {elapsed:.2f} с")

else:

self.status_lbl.setText("Расчёт прерван или ошибка")

self.status_lbl.setStyleSheet("color: red; font-weight: bold;")

logger.error("Расчёт завершился с ошибкой")

self.fig.clear()

self.canvas.draw()

def export_data_with_meta(self):

if not self.last_result:

self.log("Нет данных для экспорта. Сначала запустите симуляцию.")

logger.warning("Попытка экспорта без результатов")

return

dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для экспорта")

if not dir_path:

return

timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

csv_name = f"spin_sim_data_{timestamp}.csv"

json_name = f"spin_sim_meta_{timestamp}.json"

csv_path = os.path.join(dir_path, csv_name)

json_path = os.path.join(dir_path, json_name)

try:

import csv

with open(csv_path, "w", newline="", encoding='utf-8') as f:

writer = csv.writer(f, delimiter=",", quoting=csv.QUOTE_MINIMAL)

writer.writerow(["t_s", "mx", "my", "mz"])

for i in range(len(self.last_result["t"])):

writer.writerow([

self.last_result["t"][i],

self.last_result["mx"][i],

self.last_result["my"][i],

self.last_result["mz"][i]

])

meta = {

"timestamp": datetime.now().isoformat(),

"params": {k: float(v) for k, v in self.params.items()},

"runtime_seconds": self.last_runtime,

"version": "1.0.0",

"notes": "Данные симуляции LLG"

}

with open(json_path, "w", encoding='utf-8') as f:

json.dump(meta, f, indent=2, ensure_ascii=False)

self.log(f"Данные экспортированы: {csv_path}, {json_path}")

logger.info(f"CSV+JSON экспорт: {csv_path}, {json_path}")

QMessageBox.information(self, "Успех", f"Данные сохранены:\n{csv_path}\nПараметры: {json_path}")

except Exception as e:

QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить файлы:\n{e}")

logger.error(f"Ошибка экспорта: {e}", exc_info=True)

def save_plot(self):

if not self.last_result:

return

path, _ = QFileDialog.getSaveFileName(self, "Сохранить график", "plot.png", "PNG (*.png)")

if path:

try:

self.fig.savefig(path, dpi=300, bbox_inches='tight')

QMessageBox.information(self, "Успех", f"График сохранен:\n{path}")

logger.info(f"График сохранён: {path}")

except Exception as e:

QMessageBox.critical(self, "Ошибка", f"Не удалось сохранить:\n{e}")

logger.error(f"Ошибка сохранения графика: {e}", exc_info=True)

def reset_to_defaults(self):

self.params = self.default_params.copy()

for key, slider in self.sliders.items():

val = self.params[key]

slider.blockSignals(True)

min_v, max_v, log_scale = self.param_ranges[key]

span = max_v - min_v

if span == 0:

slider_val = 500

else:

slider_val = int((val - min_v) / span * 1000)

slider.setValue(slider_val)

slider.blockSignals(False)

le = self.line_edits[key]

if key == "J":

le.setText("{:.2e}".format(val))

elif key == "alpha":

le.setText("{:.4f}".format(val))

elif key in ("N", "seed"):

le.setText("{}".format(int(val)))

else:

le.setText("{:.3f}".format(val))

self.update_physics_panel()

self.log("Настройки сброшены к значениям по умолчанию.")

logger.info("Настройки сброшены к умолчанию")

def request_stop(self):

self.stop_event.set()

logger.info("Остановка")

self.status_lbl.setText("Остановка...")

self.status_lbl.setStyleSheet("color: orange;")

try:

self.simulation_thread.join(timeout=5)

if self.simulation_thread.is_alive():

logger.warning("Поток не завершился, принудительное завершение")

except Exception as e:

logger.error(f"Ошибка при остановке: {e}")

def run_sensitivity_test(self):

if self.is_running:

QMessageBox.warning(self, "Внимание", "Сначала завершите текущий расчёт.")

return

self.is_running = True

self.btn_run.setEnabled(False)

self.btn_sensitivity.setEnabled(False)

self.status_lbl.setText("Тест чувствительности...")

self.status_lbl.setStyleSheet("color: orange; font-weight: bold;")

self.stop_event.clear()

self.sensitivity_thread = threading.Thread(target=self._sensitivity_worker, daemon=True)

self.sensitivity_thread.start()

def _sensitivity_worker(self):

try:

base_J = self.params["J"]

results = []

test_Js = [base_J * 0.5, base_J, base_J * 2]

logger.info(f"Запуск теста: J={base_J:.2e}")

for idx, j_val in enumerate(test_Js):

if self.stop_event.is_set(): break

res = simulate_llg(

int(self.params["N"]), self.params["alpha"],

self.params["B_ext"], j_val,

self.params["f_sw"], self.params["t_end"],

self.stop_event,

seed=self.params["seed"]+idx

)

if res is not None:

results.append((j_val, res["t"], res["mz"]))

if self.isVisible():

QTimer.singleShot(0, lambda: self._on_sensitivity_done(results, base_J))

finally:

self.is_running = False

def _on_sensitivity_done(self, results, base_J):

self.fig.clear()

ax = self.fig.add_subplot(111)

if not results:

ax.text(0.5, 0.5, "Ошибка", transform=ax.transAxes, ha='center', color='red')

self.canvas.draw(); self.btn_sensitivity.setEnabled(True); return

for j_val, t, mz in results:

ax.plot(t, mz, label=f"J = {j_val:.2e}")

ax.legend(); ax.set_title("Чувствительность"); self.canvas.draw()

self.btn_sensitivity.setEnabled(True); self.is_running = False

self.params["J"] = base_J; self.update_physics_panel()

# --- АНСАМБЛЬ ---

def run_ensemble_threaded(self):

if self.is_running:

QMessageBox.warning(self, "Внимание", "Сначала завершите текущий расчёт.")

return

try:

n_runs = int(self.le_n_runs.text().strip())

threshold = float(self.le_threshold.text().replace(',', '.').strip())

if not (0.0 < threshold < 1.0): raise ValueError

except Exception:

QMessageBox.warning(self, "Ошибка", "Проверьте поля ансамбля")

return

self.is_running = True

self.btn_run.setEnabled(False)

self.btn_ensemble.setEnabled(False)

self.btn_sensitivity.setEnabled(False)

self.btn_stop.setEnabled(True)

self.status_lbl.setText(f"Ансамбль: {n_runs}...")

self.status_lbl.setStyleSheet("color: orange; font-weight: bold;")

self.stop_event.clear()

self.ensemble_thread = threading.Thread(

target=self._ensemble_worker,

args=(n_runs, threshold),

daemon=True

)

self.ensemble_thread.start()

def _ensemble_worker(self, n_runs, threshold):

logger.info(f"Ансамбль: {n_runs} шт, порог={threshold}")

start = time.time()

times_coherent = []

failed_runs = 0

base_seed = int(self.params["seed"])

for i in range(n_runs):

if self.stop_event.is_set():

logger.info("Ансамбль прерван")

break

seed_i = base_seed + i

try:

res = simulate_llg(

int(self.params["N"]), self.params["alpha"],

self.params["B_ext"], self.params["J"],

self.params["f_sw"], self.params["t_end"],

self.stop_event, seed=seed_i

)

if res is None:

failed_runs += 1

continue

t, m_norm = res["t"], res["m_norm"]

idx = np.where(m_norm < threshold)[0]

if len(idx) > 0:

t_coh = t[idx[0]]

else:

t_coh = t[-1]

times_coherent.append(t_coh)

except Exception as e:

logger.error(f"Ошибка в запуске {i}: {e}", exc_info=True)

failed_runs += 1

elapsed = time.time() - start

if self.isVisible():

QTimer.singleShot(0, lambda: self._on_ensemble_done(times_coherent, failed_runs, elapsed))

def _on_ensemble_done(self, times_coherent, failed_runs, elapsed):

self.is_running = False

self.btn_run.setEnabled(True)

self.btn_ensemble.setEnabled(True)

self.btn_sensitivity.setEnabled(True)

self.btn_stop.setEnabled(False)

if not times_coherent:

self.status_lbl.setText("Ансамбль не дал результатов")

self.status_lbl.setStyleSheet("color: red; font-weight: bold;")

logger.warning("Ансамбль завершился без успешных запусков")

return

avg_time = float(np.mean(times_coherent))

std_time = float(np.std(times_coherent)) if len(times_coherent) > 1 else 0.0

min_time = float(np.min(times_coherent))

max_time = float(np.max(times_coherent))

report = (

f"Ансамбль ({len(times_coherent)} из {len(times_coherent)+failed_runs}):\n"

f"Среднее время: {avg_time:.3e} с\n"

f"СКО: {std_time:.3e} | Мин: {min_time:.3e} | Макс: {max_time:.3e}\n"

f"Время расчёта: {elapsed:.2f} с"

)

self.status_lbl.setText(report.split("\n")[0])

self.status_lbl.setStyleSheet("color: green; font-weight: bold;")

logger.info(report)

QMessageBox.information(

self,

"Результаты ансамбля",

report

)

# ---------------------------------------------------------

# Точка входа

# ---------------------------------------------------------

if __name__ == "__main__":

app = QApplication(sys.argv)

app.setStyle("Fusion")

window = SpinSimulationApp()

window.show()

sys.exit(app.exec_())

Спасибо за чтение

Вернуться к книге