ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Geant4入门:从强制类到可选类,跑通第一个粒子模拟程序

Geant4入门:从强制类到可选类,跑通第一个粒子模拟程序 说实话第一次在实验室的服务器上敲下cmake ..再执行make我对着屏幕等编译等到怀疑人生。Geant4 这套东西编译时间长、报错又抽象、概念术语还特别多刚开始接触的人真的很容易被劝退。但只要熬过第一周把它的设计思路捋清楚后面写模拟程序会越来越顺手。这篇文章写给所有打算入门 Geant4 的同学我会从基本概念讲起把工具包的结构、强制类、可选类这几个最让人头疼的名词一次说清楚最后再给一个能直接跑通的迷你示例。如果你之前没接触过蒙特卡洛模拟也完全没关系我尽量用大白话解释保证你看完能对 Geant4 有个整体框架式的理解。1. 先把Geant4到底是什么搞清楚1.1 名字背后的来头Geant4 全称是GEometry ANd Tracking直译过来就是“几何与追踪”。它来自欧洲核子研究中心CERN主导的跨国合作项目脱胎于早期的 Fortran 程序 GEANT3从 1994 年左右开始用 C 彻底重写到如今已经成了高能物理、核技术、医学物理、辐射防护、空间科学等领域最主流的蒙特卡洛模拟工具之一。我最早接触它是因为要做粒子探测器的响应模拟。那会儿我误以为它是个“装好就能用的仿真软件”结果一打开发现连个图形界面都没有只能写 C 代码心态直接崩了。后来才明白Geant4 根本不是一个“软件”而是一套C 类库的工具包。你想让它干什么就得自己写代码去调用、去组合这些类。1.2 蒙特卡洛方法到底在算什么东西Geant4 的核心计算思想是蒙特卡洛Monte Carlo方法。说白了就是利用随机数模拟大量粒子的运动轨迹再对这些轨迹的统计结果进行物理分析。打个比方你想知道一滴滴入清水中的墨水最后会扩散成什么样。理论上你可以去解扩散方程但那很复杂。蒙特卡洛的思路是——我直接扔几百万个墨水滴进去让每个水滴都随机游走最后统计每个区域有多少水滴就得到了浓度分布。粒子物理里的情况也类似一个高能电子打进材料里它可能在某个位置被原子散射可能在某个位置损失能量产生次级粒子整个过程本身就带有随机性所以蒙特卡洛方法天然适合做这件事。Geant4 做的事就是告诉你每一步有哪些物理过程可能发生、概率多大然后用随机抽样决定“走哪条路”。大量粒子跑完之后你统计能量沉积、粒子通量、径迹长度等等就得到了想要的模拟结果。1.3 为什么强调“工具包”而不是“软件”这是很多人入门时最大的认知误区。像一些商业仿真软件装完打开填参数点运行就能出结果。Geant4 不是这个路子它只提供一堆半成品的类比如“定义一个几何体”的类、“定义一种材料”的类、“给定一个粒子源”的类。你需要自己写一个 main 函数在程序里把这些类组织起来再交给你自己的 C 编译器编译成一个可执行文件。网上搜“Geant4 工具包”的时候经常会混进来一堆完全不相关的名字什么 CTF 工具包、adb 工具包、SARscape 的 gacos 补丁包之类的其实跟我们要学的 Geant4 八竿子打不着。这也侧面说明“工具包”这个词在不同领域含义差别挺大。在 Geant4 这儿它指的就是一套供你组装和扩展的 C 类库它的设计哲学是“框架在你手上怎么组合由你定”。2. 工具包整体结构四大模块怎么分工2.1 从几何、物理、粒子到追踪Geant4 的功能可以拆成四大块理解这四块之后你再去看那些类名就不会晕了。第一块是几何模块Geometry。它负责描述模拟环境长什么样包括有什么材料、什么形状、怎么摆放。比如你要模拟一台 CT 机器那就得在这个模块里定义 X 射线管、探测器晶体、准直器、甚至人体的各个组织器官。几何模块的核心问题是“粒子在哪儿”所有和空间位置打交道的类都归它管。第二块是物理模块Physics。它负责定义“粒子会跟物质发生哪些相互作用”比如光电效应、康普顿散射、轫致辐射、电离能损等等。物理模块决定了粒子在每一步会经历什么过程以及这些过程的概率是多少。这是 Geant4 最复杂、也最需要经验的一块因为不同的物理列表Physics List专门适用不同的能量范围和物理场景。第三块是粒子与追踪模块Tracking。粒子在这个模块里被发射出来一步步地在几何中穿行每走一小段都要判断有没有发生相互作用、有没有进入新材料、有没有飞出边界。追踪模块把一个连续的物理过程拆成了无数个离散的“步”Step每个 Step 都要记录粒子的能量变化和位置变化。第四块是事件与管理模块Run / Event。它负责把上面这些过程组织成可以批量执行的任务。一次完整的模拟叫一个 Run运行一次发射一颗粒子的完整处理叫一个 Event事件一颗粒子从出生到死亡的全部轨迹叫一个 Track径迹路径上每一步就是一个 Step。这四个层级的概念非常重要后面所有强制类和可选类都是挂在这四个层级上的。2.2 Run、Event、Track、Step四个层级的关系我见过很多教材用专业术语解释这四个词结果越解释越糊涂。我用拍电影来打个比方Run相当于一整部电影。一个 Run 包含你指定的所有事件比如你要求模拟一百万颗粒子那这整个一百万次发射就是同一个 Run。一个 Run 开始前要初始化很多东西结束后要汇总所有统计结果。Event相当于电影里的一个镜头。每次从源发出一个粒子或者一束粒子并处理完就是一个 Event。在一百万颗粒子的模拟中就有一百万个 Event每个 Event 之间是独立且互不干扰的。Track相当于一个演员的完整表演。一颗粒子从诞生到被吸收、逃逸或者能量低于阈值这整个生命周期就是一条 Track。一个 Event 里可能包含许多条 Track因为粒子碰撞会产生次级粒子次级粒子又会有自己的 Track。Step相当于表演中的一个动作。Track 被切成很多小段每走一小段就是一个 Step。每次 Step 的起点和终点之间粒子可能跨越了几何边界、经历了能量损失或发生了散射。理解这四个层级之后“强制类”和“可选类”就好懂了——因为它们本质上是为这四级服务的钩子。3. 强制类少写一个都跑不起来的三个类3.1 为什么“强制”这三个类在 Geant4 的官方文档里有一个词叫 User Initialization Classes中文常翻译成“用户初始化类”。其中三个类是必须由用户自己实现的否则程序就跑不起来这三个类统称为强制类G4VUserDetectorConstruction负责搭建几何与探测器G4VUserPhysicsList负责定义物理过程G4VUserPrimaryGeneratorAction负责发射粒子我当年第一次看官方教程时看到“VUser”这个前缀很不理解。后来才知道 V 代表 Virtual虚基类意思是 Geant4 已经给你定义好了接口框架但具体内容是一张白纸你必须自己去画。这三个类就是三个必须由你填写的空表格少填一张整个程序就运转不起来。3.2 DetectorConstruction先把“舞台”搭出来第一个强制类要解决的是“粒子在什么样的环境里跑”。如果你的模拟没有几何体那粒子就没有可以发生碰撞的对象整个模拟就没有意义。继承G4VUserDetectorConstruction之后你需要重写一个Construct()方法并返回一个G4VPhysicalVolume*。这个物理体积就是整个模拟世界的“世界体”所有其他几何体都必须被包含它里面。在实际写代码时一个最简单的 DetectorConstruction 需要做这几件事第一用G4NistManager创建材料。NIST 材料数据库里已经内置了上百种常见材料直接用名称调用就行比如G4_Al是铝G4_WATER是水G4_AIR是空气不用自己去手动设置密度和元素比例。第二用各种 Solid 类定义形状。比如G4Box是长方体G4Tubs是圆柱壳G4Sphere是球壳。你给它长宽高或者半径尺寸它就帮你构建出一个几何形状。第三把形状和材料组成逻辑体积Logical Volume。逻辑体积是一个“没有位置”的抽象体积它只关心形状和材料。第四把逻辑体积摆放到物理空间里成为物理体积Physical Volume。一个逻辑体积可以被重复放置很多次位置不同、旋转角度不同就形成了多个实体。很多人容易搞混“逻辑体积”和“物理体积”的区别。我自己的理解是逻辑体积像“模具”物理体积像“用模具倒出来的实物”。同一个模具可以倒出很多个实物放在不同位置这样设计是为了节省内存、方便批量管理。3.3 PhysicsList决定粒子在这个世界里的“行为规则”第二个强制类要解决的是“粒子会经历哪些物理过程”。如果你的物理列表是空的粒子就会像幽灵一样穿过所有物质什么都不发生。在旧版本 Geant4 中用户通常直接继承G4VUserPhysicsList并重写三个方法ConstructParticle()构造粒子、ConstructProcess()构造物理过程、SetCuts()设置截断参数。但在新版本里官方更推荐直接使用现成的物理列表组合通过G4PhysListFactory调用即可。我实际入门时用的是比较省事的方式#include G4PhysListFactory.hh G4PhysListFactory factory; G4VModularPhysicsList* physicsList factory.GetReferencePhysList(FTFP_BERT);这种叫法里的字母都是有含义的。FTF 表示 Fritiof 弦模型适用于高能强子BERT 表示 Bertini 级联模型适用于中低能中子还有 QGSP 表示夸克胶子弦模型。不夸张地说物理列表的选择直接决定了你模拟结果靠不靠谱也决定了程序跑得快不快。入门阶段直接用官方推荐列表就好不要自己手动去组合一个个物理过程很容易漏漏了结果就错。3.4 PrimaryGeneratorAction往舞台上“扔”粒子第三个强制类解决的是“粒子从哪里来、什么种类、多大能量、飞向哪里”。最常用的方式是G4ParticleGun粒子枪它像一把可以自由设定的枪每次开火发射一颗或一束指定粒子。这个类里面要重写GeneratePrimaries()方法。你需要在这里完成第一选择粒子类型。比如G4ParticleTable::GetParticleTable()-FindParticle(e-)可以得到电子同理还有gamma光子、proton质子、neutron中子等。第二设置粒子能量。注意 Geant4 内部默认的单位体系是毫米、纳秒、兆电子伏写的时候要显式乘上单位比如gun-SetParticleEnergy(5 * MeV)而不是写5。第三设置粒子位置和方向。位置用G4ThreeVector表示方向也用它来表示系统会自动帮你归一化。除了粒子枪后续你还会遇到G4GeneralParticleSourceGPS它可以定义更复杂的源分布比如空间展宽源、能量谱源、方向分布源。但入门阶段先掌握粒子枪就足够跑通第一个示例了。3.5 三个强制类如何被组装到一起这三个强制类最终都要在 main 函数里交给 G4RunManager 去管理这也是整个程序的主心骨。G4RunManager* runManager new G4RunManager(); runManager-SetUserInitialization(new DetectorConstruction()); runManager-SetUserInitialization(new PhysicsList()); runManager-SetUserAction(new PrimaryGeneratorAction()); runManager-Initialize(); runManager-BeamOn(1000);看到没有DetectorConstruction和PhysicsList是通过SetUserInitialization注册的而PrimaryGeneratorAction是通过SetUserAction注册的。这暗示了两者在生命周期上的差异前两个在程序初始化阶段就要创建好而粒子源是在每次 BeamOn 时被反复调用的。4. 可选类让模拟结果真正可用的关键扩展4.1 RunAction跑完整个Run后做什么如果你只写强制类Geant4 确实能跑起来但跑完之后你什么数据都拿不到。这时候就需要“可选类”登场了。可选类的共同点是它们都继承自 G4UserXXXAction并且挂在不同的事件层级上。G4UserRunAction挂在整个 Run 层面。它有BeginOfRunAction()和EndOfRunAction()两个虚函数分别在 Run 开始之前和 Run 结束之后被调用。最常见的用法是在EndOfRunAction()里输出统计汇总。比如你模拟了一百万颗电子你可以在 RunAction 里读到一共发生了多少次相互作用、探测器里总沉积了多少能量然后写到文件或者直接打印到屏幕上。我还习惯在 RunAction 里创建分析管理器G4AnalysisManager的输出文件比如 ROOT 文件或者 CSV 文件这样整个 Run 的数据结构可以统一管理。4.2 EventAction每个Event的起止时机G4UserEventAction对应的是 Event 层级。它也有两个接口BeginOfEventAction()和EndOfEventAction()。有些物理量只在事件级别才有意义。比如你想知道“每个事件里探测器沉积了多少能量”那就需要记录每个 Event 的能量沉积然后在EndOfEventAction()里进行统计并清零。我经常把 RunAction 和 EventAction 配合使用EventAction 负责每个事件的累加RunAction 负责所有事件的汇总。要注意的是在多线程模式下每个线程会有自己独立的 RunAction 和 EventAction 实例跨线程汇总数据需要额外处理合并逻辑。4.3 SteppingAction最精细也最耗性能的探针G4UserSteppingAction是粒度最细的可选类它在每个 Step 结束之后都会被调用。你可以通过G4Step对象拿到当前步的很多信息比如能量沉积、粒子的当前位置、所在的物理体积、当前过程的类型等等。SteppingAction 非常重要但我建议新手谨慎使用它。原因是它的调用频率实在太高了。一个事件可能有几千个 Step一百万个事件就是几十亿次调用如果你在 Step 里写了复杂的计算或 I/O 操作程序能慢到让你怀疑人生。我自己的经验是能用 EventAction 或 RunAction 解决的统计需求就别开 SteppingAction。只有当你要做非常精细的空间剂量分布比如记录粒子在各层材料里的能量沉积二维分布时才值得用 SteppingAction而且最好只记录你关心的那一小部分体积通过判断step-GetPreStepPoint()-GetPhysicalVolume()-GetName()来过滤。4.4 TrackingAction与StackingAction容易被忽视的两个助手除了上面三个还有两个可选类在实际项目中也非常有用G4UserTrackingAction和G4UserStackingAction。G4UserTrackingAction挂靠在 Track 层级提供PreUserTrackingAction()和PostUserTrackingAction()。它的典型用途是“追踪粒子的一生”某个粒子在哪个位置诞生、走了多远、最后因为什么过程结束。想统计探测器里的粒子径迹长度时用它很方便。G4UserStackingAction则负责管理粒子的“待办清单”。它有一个ClassifyNewTrack()接口你可以在这里决定新产生的次级粒子是“马上处理”fPush、“放入待处理队列”fWaiting还是“直接丢弃”fKill。比如有的模拟场景里你只关心光子不关心电子和正电子就可以在 StackingAction 里把电子类的次级粒子直接丢掉省去大量计算时间。4.5 该选哪些可选类按需选择不要全都实现新手容易犯的一个毛病是看到教程里介绍了这几个可选类就一股脑全继承一遍往 main 函数里全部注册进去。这其实没有必要还会拖慢程序速度。我建议按需选择的标准是只想要总沉积能量RunAction 就够或者 EventAction 也可以。想要每个事件的沉积能量分布EventAction 足够。想要空间分辨的剂量分布SteppingAction 或 SensitiveDetector 才需要。想要粒子种类和数量统计TrackingAction 配合 RunAction。想要通过剪裁降低计算量StackingAction。记住一点可选类的本质是“钩子”钩子越多程序干预越频繁性能开销越大。够用就好别贪多。5. 实操示例让一束电子穿过一块铝靶5.1 环境准备编译Geant4工具包开始写代码之前得先把 Geant4 编译出来。这个过程本身就是不少人卡住的第一道坎。我的建议是不要自己从零开始定制编译选项直接用 CMake 的默认配置加上你要用的图形界面可选就行。大致的步骤是wget https://geant4-data.web.cern.ch/releases/geant4-v11.2.0.tar.gz tar -xzf geant4-v11.2.0.tar.gz mkdir geant4-build cd geant4-build cmake -DCMAKE_INSTALL_PREFIX/path/to/geant4-install \ -DGEANT4_USE_QTON \ -DGEANT4_USE_OPENGL_X11ON \ ../geant4-v11.2.0 make -j4 make install编译时间取决于机器性能少则几十分钟多则一两小时这是正常的。装完之后记得 source 一下环境变量脚本一般是/path/to/geant4-install/bin/geant4.sh这样 CMake 才能找到 Geant4 的包。5.2 搭一个最小工程CMakeLists.txt的写法Geant4 官方推荐用 CMake 来构建用户程序。一个最小工程的CMakeLists.txt长这样cmake_minimum_required(VERSION 3.10) project(FirstGeant4) find_package(Geant4 REQUIRED) include(${Geant4_USE_FILE}) add_executable(firstGeant4 main.cc src/DetectorConstruction.cc src/PhysicsList.cc src/PrimaryGeneratorAction.cc ) target_link_libraries(firstGeant4 ${Geant4_LIBRARIES})这里有几个容易踩坑的地方。find_package如果报错说明环境变量没 source 对检查一下Geant4_DIR是否指向安装目录的lib/cmake/Geant4。include(${Geant4_USE_FILE})会自动帮你加一堆编译宏和头文件路径所以别漏掉这一行。5.3 main.cc把强制类串起来main 文件非常短核心就是创建 RunManager、注册三个强制类、初始化、跑起来。#include G4RunManager.hh #include G4UImanager.hh #include DetectorConstruction.hh #include PhysicsList.hh #include PrimaryGeneratorAction.hh int main() { G4RunManager* runManager new G4RunManager(); runManager-SetUserInitialization(new DetectorConstruction()); runManager-SetUserInitialization(new PhysicsList()); runManager-SetUserAction(new PrimaryGeneratorAction()); runManager-Initialize(); // 批量跑5000个事件不启动交互界面 runManager-BeamOn(5000); delete runManager; return 0; }如果你后面想加图形界面可以在BeamOn之前加一行启动 UI 会话的代码但这个例子先不加目标就是一个批处理式的后台模拟程序能跑出结果就够了。5.4 DetectorConstruction建一个世界体和一块铝靶这个类的实现重点在于返回一个世界体物理体积。世界体里面放一块铝靶电子从外面射进去。#include DetectorConstruction.hh #include G4NistManager.hh #include G4Box.hh #include G4LogicalVolume.hh #include G4PVPlacement.hh G4VPhysicalVolume* DetectorConstruction::Construct() { G4NistManager* nist G4NistManager::Instance(); G4Material* worldMat nist-FindOrBuildMaterial(G4_AIR); G4Material* alMat nist-FindOrBuildMaterial(G4_Al); // 世界体30 cm 的立方体 G4Box* worldBox new G4Box(WorldBox, 15*cm, 15*cm, 15*cm); G4LogicalVolume* worldLog new G4LogicalVolume(worldBox, worldMat, WorldLog); G4PVPlacement* worldPhys new G4PVPlacement(nullptr, G4ThreeVector(0,0,0), worldLog, WorldPhys, nullptr, false, 0); // 铝靶5 cm x 5 cm x 2 cm放在原点 G4Box* targetBox new G4Box(TargetBox, 2.5*cm, 2.5*cm, 1*cm); G4LogicalVolume* targetLog new G4LogicalVolume(targetBox, alMat, TargetLog); new G4PVPlacement(nullptr, G4ThreeVector(0,0,0), targetLog, TargetPhys, worldLog, false, 0); return worldPhys; }注意这里 G4Box 构造函数的参数是“半长”也就是说你写 15cm它实际是 30cm 的边长。我第一次用的时候就栽过这个跟头导致几何体和预期相差一倍。铝靶放在世界体中间电子从 z 轴负方向射向正方向这样会穿过铝靶产生韧致辐射和电离等一系列过程。5.5 PhysicsList用工厂类直接选择物理列表这个类的实现最省事因为我们要用的是现成的组合物理列表。#include PhysicsList.hh #include G4PhysListFactory.hh PhysicsList::PhysicsList() : G4VModularPhysicsList() { G4PhysListFactory factory; G4VModularPhysicsList* base factory.GetReferencePhysList(FTFP_BERT); // 把工厂产生的物理列表里的所有过程复制到当前列表 G4PhysicsListHelper::GetPhysicsListHelper()-AddPhysicsList(base); }不过稍微要注意一下继承G4VModularPhysicsList后直接复制另一个列表的方式在新版本里有点绕。更常见也更直接的做法是干脆不自己写这个类直接在 main 里头实例化工厂生成的物理列表G4PhysListFactory factory; G4VModularPhysicsList* physicsList factory.GetReferencePhysList(FTFP_BERT); runManager-SetUserInitialization(physicsList);这样连 PhysicsList.cc 都可以不写了。我分享这个写法的原因是想告诉你Geant4 工具包的很多功能不一定要通过继承去实现直接用现成工厂类组合也是一种高效方式。强制类里的 PhysicsList 之所以仍是“强制”是因为你总要设置物理列表至于用哪种方式设置可以灵活处理。5.6 PrimaryGeneratorAction用粒子枪发射5 MeV电子#include PrimaryGeneratorAction.hh #include G4ParticleGun.hh #include G4ParticleTable.hh #include G4SystemOfUnits.hh PrimaryGeneratorAction::PrimaryGeneratorAction() { fGun new G4ParticleGun(1); G4ParticleTable* table G4ParticleTable::GetParticleTable(); G4ParticleDefinition* electron table-FindParticle(e-); fGun-SetParticleDefinition(electron); fGun-SetParticleEnergy(5 * MeV); fGun-SetParticlePosition(G4ThreeVector(0, 0, -5*cm)); fGun-SetParticleMomentumDirection(G4ThreeVector(0, 0, 1)); }我这里设了 5 MeV 的电子方向沿 z 轴正方向初始位置在离原点 5 cm 的地方。注意能量必须写成5 * MeV而不是5这一点在 Geant4 里非常关键。官方所有示例代码都用这种方式目的就是让你显式地指定单位避免一不留心用错单位制。5.7 编译运行与结果解读工程文件都准备好之后编译运行mkdir build cd build cmake .. make ./firstGeant4程序跑完会输出一堆初始化信息和每一事件的处理状态。因为我们在 main 里没有加统计输出所以 Geant4 只会打印最基础的处理信息告诉你模拟跑完了。想要输出能量沉积就需要把第 4 节的可选类加进来比如写一个简单的 RunAction在EndOfRunAction里通过G4AccumulableManager拿到总沉积能量。这一步做通之后你就已经跑通了“几何 物理 粒子源”的最小闭环后面的路就比较顺了。6. 常见问题与排查技巧实录6.1 单位没乘结果离谱得没法看这是新手最容易犯、也最隐蔽的问题。Geant4 内部默认单位是毫米mm、纳秒ns、兆电子伏MeV但你在写代码时不写单位数字就只是一个裸数值。比如你写SetParticleEnergy(5)它会被认为是 5 MeV如果你本意是 5 GeV那结果就差了一千倍。反过来你写new G4Box(box, 10, 10, 10)那不是 10 米而是 10 毫米。解决方法是永远显式地乘上单位比如10 * mm、5 * MeV、0.1 * cm。Geant4 的G4SystemOfUnits.hh里定义了这些单位常量include 一下就能用。我后来复查代码的时候第一步永远是检查有没有裸数字凡是没乘单位的基本都能揪出问题。6.2 世界体太小粒子直接飞出边界被杀死粒子在模拟中跑出世界体边界之后就会被标记为“逃逸”并停止追踪。如果你发现很多粒子消失得很快、物理过程没跑完很可能就是世界体建小了。尤其是模拟高能粒子时次级粒子可能飞得很远世界体至少应该覆盖你关心的区域再往外留一些余量。另外几何重叠也会引发告警。Geant4 会在初始化阶段做几何检查如果两个物理体积在空间上有重叠它会打印G4Exception警告。这种情况会导致粒子在边界处的行为不可预测必须回头改几何不要硬着头皮跑下去。6.3 物理列表选错粒子像幽灵一样穿透如果你发现粒子穿过了探测器但几乎没有能量沉积先别怀疑代码检查一下物理列表。某些物理列表只包含特定能量范围的过程如果能量范围严重不匹配粒子就几乎不发生相互作用。入门阶段我建议直接记住这几个常用组合一般粒子物理实验用FTFP_BERT医学物理中电子光子输运常用G4EmStandardPhysics_option4更精确的电磁过程中子相关用QGSP_BERT或HP系列。不要自己手动逐个加过程除非你确实清楚每个过程的有效能量范围。6.4 多线程模式下统计量对不上从 Geant4 10.0 版本开始默认开启了多线程MT一个 Run 会被拆成多个线程并行处理。如果每个线程的 EventAction 都维护自己的累加变量那最后汇总时如果不做归并统计结果就会对不上。Geant4 提供了G4AccumulableManager来帮你做线程间归并建议直接用。用起来也很简单在 RunAction 的构造函数里注册你要统计的量然后在对应的地方通过Accumulable对象累加结束时管理器会自动帮你合并所有线程的数据。如果不想碰多线程可以在 main 里设置G4MTRunManager之前先设定线程数为 1G4int nThreads 1; G4RunManager::SetNumberOfThreads(nThreads);这样虽然牺牲了并行性能但统计逻辑简单适合入门调试。6.5 编译报错链接库没找到、头文件路径不对Geant4 的报错信息默认很详细但太多了反而让人找不着重点。我的排查习惯是先看第一条 error不要往下翻。最常见的两类问题是第一fatal error: G4RunManager.hh: No such file or directory说明 CMake 的include(${Geant4_USE_FILE})没有生效检查find_package是否真的找到了 Geant4。第二undefined reference to DetectorConstruction::Construct()说明对应的.cc文件没有加进add_executable或者头文件里有声明但源文件没实现。还有一个小技巧如果你同时在多个工程之间切换记得每次重新 build 之前rm -rf build再mkdir build因为 Geant4 的 CMake 配置如果找不到库会把错误状态缓存下来不清缓存的话怎么改都报同样的错。最后再分享一点个人小体会。我见过很多初学者一上来就抱着官方文档几百页慢慢啃啃到一半就放弃了。入门 Geant4 最快的路径其实是一条直线先把最小示例跑通再回头逐个理解概念。我第一次跑通那个 5 MeV 电子打铝靶的程序时根本记不住那么多类的细节但脑子里已经有了“RunManager 接三个强制类”的主干后面不管学什么新功能都是往这个主干上挂分支。记住这个节奏别急慢慢来你会看到模拟数据出来那一刻还是很值得的。
返回列表