B1实例代码学习记录
五、信息收集
前面讲了,G4中的运行根据Run、Event、Track和Step逐步进行运算,如果我们想要统计在一次Run的过程中所有粒子在某个体积内的沉积能量,我们该如何实现呢?
图1 G4的运算流程
首先我们先看一下图一中G4是如何实现运算的,如果我们使用命令/run/beamOn 100发射100个粒子源,那么在这次Run中就会有100个Event。但是这100个粒子并不是同时发射,而是逐个进行发射,在发射第一个粒子源后,其会与我们定义的模型发生反应并产生若干个次级粒子即n个Track。在每个Track的计算过程中,又是根据一个最基本的运算步长Step进行运算,每个Track中又会有若干个Step。G4就是根据上面流程逐步计算最终完成了所有粒子的模拟计算。
OK,我们回到最初的问题,如果我们想要统计在一次Run的过程中所有粒子在某个体积内的沉积能量,G4给我们提供的接口工具能够提取每一个Step的能量沉积,我们该如何实现上述需求呢?
我认为大体上可以分为以下几个步骤:
- 获取每个Stpe的能量沉积并判断这个Step是否在我们统计的体积内
- 将每个Step的能量沉积加和统计得到每个Track的能量沉积
- 将每个Track的能量沉积加和统计得到每个Event的能量沉积
- 将每个Event的能量沉积加和统计得到Run总的能量沉积
接下来我们来看一下B1是如何实现的。
1.获取每个Stpe的能量沉积并进行判断
同样我们先看一下头文件的内容:
class SteppingAction : public G4UserSteppingAction
{
public:
SteppingAction(EventAction* eventAction);
~SteppingAction() override = default;
// method from the base class
void UserSteppingAction(const G4Step*) override;
private:
EventAction* fEventAction = nullptr;
G4LogicalVolume* fScoringVolume = nullptr;
};
从文件中可以看到,除了构造与析构函数,有一个UserSteppingAction,它的参数是G4Step*的一个指针,即通过这个接口我们可以获取每一个Step中的信息。另外还有两个参数fEventAction和fScoringVolume,具体作用在.cc文件中进行分析。
首先看一下如何实现获取Stpe的能量沉积:
G4double edepStep = step->GetTotalEnergyDeposit();
可以看到这一步的实现比较简单,直接利用step的成员函数GetTotalEnergyDeposit即可获得。
然后看一下如何判断这一Step是否在体积Shape2内,除了上述内容的实现,我们还可以学到如何通过G4RunManager和step来分别获取对应的逻辑体的指针。
if (!fScoringVolume) {
const auto detConstruction = static_cast<const DetectorConstruction*>(
G4RunManager::GetRunManager()->GetUserDetectorConstruction());
fScoringVolume = detConstruction->GetScoringVolume();
}
可以看到在这一段代码中,首先通过G4RunManager获取得到了搭建的DetectorConstruction的指针(接口),之前在讲DetectorConstruction的时候我有提到过,它里面定义了一个方法可以提取Shape2的逻辑体指针,即GetScoringVolume这个方法返回了Shape2的逻辑体指针logicShape2。具体的实现代码在DetectorConstruction.hh与DetectorConstruction.hh文件内:
G4LogicalVolume* GetScoringVolume() const { return fScoringVolume; }
G4LogicalVolume* fScoringVolume = nullptr;
fScoringVolume = logicShape2;
上一步通过G4RunManager获取了我们定义的Shape2的逻辑体指针,接下来我们来获取当前Step所在体积的逻辑体指针:
// get volume of the current step
G4LogicalVolume* volume
= step->GetPreStepPoint()->GetTouchableHandle()
->GetVolume()->GetLogicalVolume();
这一步不做过多解释,先记住通过以上方法可以获取当前Step所在体积的逻辑体指针。
接下来即可判断我们Stpe所在的体积是否在Shape2的内部,是的话就获取Step的沉积能量:
if (volume != fScoringVolume) return;
// collect energy deposited in this step
G4double edepStep = step->GetTotalEnergyDeposit();
首先判断两个逻辑体是否相等,如果不相等就直接返回结束,如果体积相同则获取该Step的沉积能量,通过以上步骤基本实现了我们第一步需要实现的目标。
2.统计每个Event的能量沉积
B1并没有像我预想的那样先统计Track的能量沉积再统计Event的能量沉积,而是跳过了Track直接统计Event的能量沉积。那么这里可以做一个小实验,是否可以跳过Event直接统计Run的能量沉积呢?我认为理论上是可行的,还是我讲的这个不是核心内容,我可能会在B1学习完后把前面挖的坑一起填一下,这里先学习一下B1是如何实现的。
首先看一下头文件的定义:
class EventAction : public G4UserEventAction
{
public:
EventAction(RunAction* runAction);
~EventAction() override = default;
void BeginOfEventAction(const G4Event* event) override;
void EndOfEventAction(const G4Event* event) override;
void AddEdep(G4double edep) { fEdep += edep; }
private:
RunAction* fRunAction = nullptr;
G4double fEdep = 0.;
};
这里有三个函数分别是BeginOfEventAction、EndOfEventAction以及AddEdep另外还有两个成员变量,一会结合具体的实现方法来解释其作用。这里简单可以解释一下两个Action的方法何时被调用:
BeginOfEventAction是在每个Event开始之前被调用,EndOfEventAction是在每个Event所有的粒子全部运算结束后被调用。下面看一下其.cc文件如何定义的:
void EventAction::BeginOfEventAction(const G4Event*)
{ fEdep = 0.; }
可以看到在每个Event开始之前,BeginOfEventAction会将EventAction中的成员变量fEdep赋值为0
然后运行到第一个Step后,在StepAction中的UserSteppingAction方法的最后一行会调用EventAction的AddEdep方法:
EventAction.hh
void AddEdep(G4double edep) { fEdep += edep; }
SteppingAction.cc
fEventAction->AddEdep(edepStep);
AddEdep方法会将它的参数edep加到EventAction中的成员变量fEdep中去,这样每运行一个Step,当能量沉积在Shape2中的话,都会将它的沉积能量加到EventAction中的成员变量fEdep中。当一个Event运行结束后,前面也说了Step是运算的最小步长,这样的话整个Event沉积在Shape2的能量就可以被统计。
3.统计Run的能量沉积
其实Run能量沉积的统计与Event的非常类似,先看一下头文件:
class RunAction : public G4UserRunAction
{
public:
RunAction();
~RunAction() override = default;
void BeginOfRunAction(const G4Run*) override;
void EndOfRunAction(const G4Run*) override;
void AddEdep (G4double edep);
private:
G4Accumulable<G4double> fEdep = 0.;
G4Accumulable<G4double> fEdep2 = 0.;
};
其中BeginOfRunAction与EndOfRunAction的调用与前面EventAction中的类似,请类比一下进行理解。其AddEdep的作用一会结合具体实现方法进行解释,这里有一个新的数据类型G4Accumulable<G4double>这里再挖一个坑,这里可以先将其理解为一个double类型的变量。
这里RunAction与BeginOfRunAction中的有些内容涉及到多线程的问题,可以先忽略,先看AddEdep方法的定义:
void RunAction::AddEdep(G4double edep)
{
fEdep += edep;
fEdep2 += edep*edep;
}
这里与EventAction的AddEdep方法类似,都是讲AddEdep的参数edep的值加给fEdep,只是这里的fEdep是RunAction的成员变量,并且在其声明的位置已经赋值为0。即当一个Run开始的时候其值为0,当遇到第一个Event在其结束以后后调用EndOfEventAction方法
void EventAction::EndOfEventAction(const G4Event*)
{
// accumulate statistics in run action
fRunAction->AddEdep(fEdep);
}
并将每一个Event统计的能量值加给Run中的变量,经过以上的工作就已经基本上完成了最初的目标:统计在一次Run的过程中所有粒子在某个体积内的沉积能量。当然这是一个很简单的例子,但是我认为在这个例子里我们应该去体会每一个RunAction、EventAction以及StepAction包括它们的方法在何时被调用,这样当我们想要统计自己的信息时,我们就可以自己去判断它是在运行的哪一个阶段可以被统计,然后可以去在相应的阶段去查找提取信息的方法。
到这里B1的主要内容基本上就完成了,接下来会将B1的初始化,还有一些未解释清楚地点再写一篇文章进行解释。最后还是希望如果发现有错误的地方帮忙指正,我也是在不断的学习和进步的过程。