Урок 1: Минимально рабочая программа

В этом уроке мы попробуем сделать свою первую симуляцию с помощью GEANT4

Схематичное устройсво и основные понятия

Основные понятия: сеанс, событие, трек, шаг, стек ожидания, элемент, материал, тело, логический объем, физический объем.

Как же построен процесс моделирования?

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

Соглашение о физических единицах

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

  • миллиметр,

  • наносекунда,

  • мегаэлектрон-вольт,

  • Кельвин,

  • моль,

  • кандела,

  • радиан,

  • стерадиан.

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

#include "G4SystemOfUnits.hh"
using namespace CLHEP;

...

double kineticEnergy = 10 * MeV;
double size = 1000*meter;

В данном пример мы подключили заголовочный файл и пространство имен, в которых определены переменные, позволяющие приводить наши физические величины к базовым размерностям. Так в переменную size будет записано значение 1000000, поскольку переменная meter имеет значение 1000, так как подсказывает капитан Очевидность в метре тысяча миллиметров.

Обязательные пользовательские настройки: геометрия, генератор событий и физический лист

Как мы уже сказали GEANT4 это не программа для моделирования, а платформа (фреймворк) для написания программ моделирования. Фактически это устроено следующим образом: в GEANT4 определены абстрактные классы, для которых пользователю нужно самостоятельно реализовать класс-наследник в своей программе. Эти классы позволяют ввести информацию о конкретной части модели, оказать влияние на процесс моделирования или провести регистрацию результатов моделирования.

Для части абстрактных классов необходимо реализовать класс-наследник в обязательном порядке - это классы отвечающие за описание геометрии модели, генерацию первичных частиц и описание физических процессов. В данном уроке мы создадим простейшую однофайловую программу минимально реализующую все обязательные требования, а в последующих уроках мы рассмотрим каждое обязательное требование более детально (код к данному уроку лежит в директории examples/lesson01).

Поскольку мы занимаемся Монте-Карло моделированием, то важной частью является генератор случайных чисел:

#include <random>

...

int main() {
    ...
    random_device rd;
    uniform_int_distribution<long> uid(0, LONG_MAX);
    G4long seed = uid(rd);
    G4Random::setTheEngine(new CLHEP::RanecuEngine);
    G4Random::setTheSeed(seed);
    ...

В данной части программы мы выбираем какой из генераторов случайных чисел нам использовать (GEANT4 предлагает несколько на выбор) и устанавливаем его начальное состояние переменной seed. Задавать одно и тоже начальное состояние генератора бывает полезно при отладке программы, поскольку оно позволяет повторить псевдослучайную последовательность выдаваемую генератором и тем самым исключить влияние случайности. В рабочих симуляциях напротив, необходимо рандомизировать выбор начального состояния генератора, для этого в данном примере используется класс random_device - он позволяет сгенерировать случайное число используя пул энтропии создаваемой шумами в электронике.

Управление симуляций GEANT4 выполняет с помощью набора так называемых классов-менеджеров (Их можно отличить по наличию в названии класса слова Manager), отвечающих за различные аспекты симуляции и делающих это на разных уровнях. Менеджером верхнего уровня является G4RunManager, в с помощью которого мы зарегистрируем созданные пользователем классы и будем управлять запуском сеанса симуляции:

#include "G4RunManager.hh"

...

int main() {
    ...
    auto runManager = new G4RunManager;
    ...

Для начала подключим физический лист - объект класса, отвечающего за набор физических процессов, в которых участвуют элементарные частицы в симуляции. В качестве физического листа мы используем один из готовых листов FTFP_BERT, который включает в себя набор электромагнитной и адронной физики, обеспечивающий достаточную точность, если ваша симуляция не предпологает каких то сверхточных вычислений или сложных физических моделей (например не рекомендуется данный набор если вы хотите работать с точными симуляциями мягкого рентгена).
Характерный прием который используется во многих методах GEANT4 класов — это прием в качесве входного параметра целочисленной переменной (verbose) отвечающий за подробность текстового вывода о работе системы (0 соответствует наименее подробному выводу или его отсутствию). Подключение физического листа осущетвляется вызовом метода SetUserInitialization и передачей туда объекта физического листа.

#include "FTFP_BERT.hh"

...

int main() {
    ...
    G4int verbose = 1;
    FTFP_BERT *physlist = new FTFP_BERT(verbose);
    runManager->SetUserInitialization(physlist);
    ...

Для геометрического объёма нет стандартных классов, поэтому создадим свой класс, описывающий очень простую ситуацию: кубическое пространство с вакуумом, не содержащее никаких других геометрических объектов.
Для этого опишем наш класс DetectorConstruction, унаследовав его от G4VUserDetectorConstruction, и реализуем в нем вирутальный метод Construct. Геометрическая модель GEANT4 пердпологает три уровня абстрации и представляет собой иерахическую структуру геометрических примитивов, примитив верхнего уровня называется мировым объемом и указатель на этот мировой объем (переменная world) должен возращать метод Construct.

#include "G4SystemOfUnits.hh"
#include "G4VUserDetectorConstruction.hh"
#include "G4NistManager.hh"
#include "G4Box.hh"
#include "G4LogicalVolume.hh"
#include "G4VPhysicalVolume.hh"
#include "G4PVPlacement.hh"

class DetectorConstruction : public G4VUserDetectorConstruction {
public:
    G4VPhysicalVolume *Construct() {
        auto nistMngr = G4NistManager::Instance();
        auto vacuum = nistMngr->FindOrBuildMaterial("G4_Galactic");
        G4Box *worldSolid = new G4Box("world",1000*meter, 1000*meter, 1000*meter);
        G4LogicalVolume *worldLogic = new G4LogicalVolume(worldSolid, vacuum, "worldLogic");
        G4VPhysicalVolume *world = new G4PVPlacement(0, G4ThreeVector(0, 0, 0), worldLogic, "WorldPhys", 0, false, 0);
        return world;
    }
};

Далее мы должны зарегистрировать наш класс в G4RunManager, это делается аналогично регичтрации физического листа:

#include 

...

int main() {
    ...
    auto massWorld = new DetectorConstruction();
    runManager->SetUserInitialization(massWorld);
    ...

Последняя обязательная пользовательская опция - это генератор первичных частиц, в нем определяется какие частицы и с какими параметрами буду смоделированы в начала события.
Существует два основых способа генерации первичных частиц: воспользоваться одним из существующих генераторов, настроив его с помощью макросов или описать свой. Последний способ более универсален, но перед тем как прибегать к нему, стоит убедится что вы не можете использовать один из существующих генраторов, благо они поддерживают большое количество опций (в том числе генерацию частиц из пользовательских гистограмм), позволяя избежать ненужной работы. Для нашего примера мы воспользуемся вторым путём и создадим монохромный точечный источник протонов с энергией 3 ГэВ. Для этого создадим класс PrimaryGeneratorAction унаследовав его от класса G4VUserPrimaryGeneratorAction. В классе создадим поле fParticleGun, в котором будем хранить объект описывающий нашу пушку частиц. В конструкторе инициализируем пушку, задав параметры выстреливаемых частиц: тип частицы, энергию, направление импульса, начальное положение, количество частиц в банче. Затем реализуем метод GeneratePrimaries отвечающий за создание частицы в начале события. Для генерации первичной частицы, нужно передать объект события в метод GeneratePrimaryVertex нашей пушки. Обратим ваше внимание что мы можем менять параметры пушки в этом методе и таким образом генерировать в различных событиях (да и внутри одного тоже) частицы с различными параметрами.

#include "G4SystemOfUnits.hh"
#include "G4VUserPrimaryGeneratorAction.hh"
#include "G4ParticleGun.hh"

class PrimaryGeneratorAction : public G4VUserPrimaryGeneratorAction {
public:
    G4ParticleGun* fParticleGun;
    PrimaryGeneratorAction() : G4VUserPrimaryGeneratorAction() {
        G4int nofParticles = 1;
        fParticleGun = new G4ParticleGun(nofParticles);
        // default particle kinematic

        G4ParticleDefinition* particleDefinition
                = G4ParticleTable::GetParticleTable()->FindParticle("proton");

        fParticleGun->SetParticleDefinition(particleDefinition);
        fParticleGun->SetParticleMomentumDirection(G4ThreeVector(0.,0.,1.));
        fParticleGun->SetParticleEnergy(3.0*GeV);
        fParticleGun->SetParticlePosition(G4ThreeVector(0., 0., 0.));
    }

    void GeneratePrimaries(G4Event *anEvent) override {
        fParticleGun->GeneratePrimaryVertex(anEvent);
        fParticleGun->SetParticleEnergy(2*GeV);
        fParticleGun->GeneratePrimaryVertex(anEvent);
    }

};

В отличии от геометрии и физического листа зарегистрировать пушку можно двумя путями: через специальный класс-наследник класса G4VUserActionInitialization или же не посредственно через вызов метода SetUserAction. Для простоты мы воспользуемся вторым способ, хотя более правилен первый метод.

#include 

...

int main() {
    ...
    auto gun = new PrimaryGeneratorAction();
    runManager->SetUserAction(gun);
    ...

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

Для начала симуляции нужно явно провести инициализацию вызвав метод Initialize, после чего можно запустить сеанс вызвав метод BeamOn, который в качестве аргумента принимает количесвто событий в сеансе

#include 

...

int main() {
    ...
    runManager->Initialize();
    runManager->BeamOn(10);
    ...

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

Скомпилируем код из урока, действуя аналогичным образом как при запуске примера. Более подродно об управлении симуляцией и настройке `cmake` мы поговорим в следующем уроке.