ROOT:一个高能物理实验数据分析框架
这篇文章简要介绍了ROOT这一高能物理实验数据分析框架。也提供一些安装上的参考。
目录
展开/折叠
基本信息
- 关键词:高能物理、统计学、数据库、数据分析、数据可视化、类库
- 受支持平台:GNU/Linux、Windows、MacOS
- 语言接口:C++、Python
- 开发语言:C++
- 状态:活跃
- 自由软件
- 许可证:一般为GNU LGPL 2.1,如果链接了GSL则为GNU GPL 3.0
- 代码库:https://github.com/root-project/root
简介
ROOT是一个在高能物理中被广泛使用的类库(class library),主要作为高能物理实验的数据库框架与数据分析框架,也提供了其他一些功能,包括数学库、GUI等。利用ROOT可以一站式地完成数据存储、数据获取、数据分析、绘制图表等多个步骤。
ROOT作为数据库
ROOT最关键的功能之一是基于ROOT文件实现的数据存储。ROOT文件适合用于保存海量的数据,其通过保存压缩过的数据对象的二进制表示节省了很多空间,并与大量其他ROOT功能特性配合实现了许多方便特性。利用ROOT文件可以轻松地以表格(spread sheet,即TTree
、RNTuple
)、多维直方图(multi-dimensional histogram,即TH1
及其派生类,如TH1F
、TH3D
等)、多维关联直方图(multi-dimensional profile histogram,即TProfile
及其派生类,如TProfile2D
、TProfile3D
等)等类型保存尚待分析或已分析好的数据。ROOT默认采用LZMA算法(基于XZ Utils)执行数据压缩。相比将数据保存为纯文本类型(如CSV文件),带压缩的ROOT文件节省了大量空间,为存储更多的高能物理实验数据提供了方便。
除了访问本地的数据,ROOT也支持操作远程数据。ROOT通过内部实现的HTTP后端或者基于XRootD实现远程数据文件的访问,访问远程数据和访问本地数据一样容易。
ROOT作为数据分析软件
除此之外,ROOT的另一重要功能是方便的数据分析。高能物理实验领域不同于其他实验学科的一个显著特点是数据量相对庞大,因此数据分析难以通过手工完成。数据分析流程中,哪怕是极少的人工也会被数据量放大到不可接受的程度。因此数据分析总是要通过设计算法并编写代码让计算机批量完成。
在语言上,与现在Python、R、Julia这些适合实施数据分析和简单科学计算的语言方兴未艾的场面不同,在ROOT刚出现的那个年代,这些语言还没出现或是还没流行开来。当时市面上达到工程标准的语言屈指可数,Java都还只是个刚出生的小孩。要达到高能物理实验强度的高性能和稳定性,可选的语言基本只有C++一种。但C++是编译型语言,在那个连代码编辑都费劲、计算机性能低下的年代,只写一段短小代码也要处理一堆依赖、每改一下代码就要去敲一段命令行编译一下看看效果的这种工作流程是让高能物理学家们不满意的。为此,开发ROOT的物理学家们,同时作为出色的软件工程师,写出了一个C++解释器(更准确地说是JIT编译器)CINT,此后演变为Cling,这让C++代码一行一行地执行成为了可能。这样一来,只要用C++写一段数据分析的算法,然后用Cling执行就能得到结果。并且可以直接享受到ROOT提供的所有功能,不用劳神费心处理外部依赖。在已有了更方便编程语言的现在,这一特点仍有其意义。除此之外,ROOT也逐渐完善了其Python接口,使得更现代化的数据分析得以更轻松地开展。
在功能上,在高能物理领域多年的磨练让ROOT在高能物理实验数据分析上练就了一身本领。除了基础的作统计图、统计量分析、线性拟合/非线性拟合之外,ROOT还能实施更复杂的多变量分析,甚至实现了神经网络(虽然在实践中用Pytorch、TensorFlow这些库就好)。除此之外,ROOT提供的大量的数学功能,如函数的微分、积分、求函数极值、随机数生成和分布抽样等也方便了数据分析的开展。虽然这些功能的实现通常都并非最优,但足以成为一个方便的选项。另一方面,通过DataFrame,利用TBrowser
、RBrowser
,用户可以轻松地对保存在ROOT文件中的数据做可视化、进行拟合等。基于此,ROOT也能作出可发表品质的图表(虽然无法达到Nature的标准,这对ROOT这一大龄软件来说比较难)。如果去翻看高能物理领域的文章,很容易认出不少图就出自ROOT。
ROOT作为数学软件、GUI开发器……等等
ROOT实现其强大数据分析功能的一个基础是相对完备的数学库。ROOT的数学模块下属有作为统计分析基础的拟合与参数估计、拟合优度分析、随机数生成等,还有一些常见的数值分析功能,如数据内插、特殊函数、求函数极值、求零点、数值微分、数值积分等。其中统计分析相关的内容多由ROOT独立实现(MathCore),数值分析模块则主要通过封装成熟的GNU Scientific Library(GSL)实现(MathMore)。值得注意的是,一旦您采用了依赖GSL的模块,那么如果您需要发布您的软件,则它必须开源,且通过GPL 3.0开源。ROOT数学库的最大优点在于功能全面,非常适合用于快速实现设想,从而也就非常匹配高能物理实验物理分析的需求,也适合于各种算法的快速验证等场合。但它和大部分功能大而全的软件具有相同的缺陷——它无法面面具到,自然也就缺乏了专门性。如果需要进行高性能计算软件的开发,需要极高强度地使用某一特定数值功能,采用更专门的库通常是更好的做法。
ROOT甚至还提供了不错的GUI开发功能。这一功能适合用于构建简单的GUI程序,即可以利用ROOT的传统GUI编辑器编写桌面应用程序,也可以利用JSROOT编写网页。其产出分别类似于TBrowser
和RBrowser
。它们很容易与ROOT的核心功能互相配合,从而可以方便地实现数据的可视化。在大型粒子物理实验的控制室,墙上的屏幕实时显示着探测器的运行状态,这些页面就可以用ROOT的GUI功能实现。此外值得注意的一点是,如果要开发桌面应用程序,使用ROOT的传统GUI编辑器在当下已经不是太好,它已经有些陈旧。一方面其风格充满了怀旧气息,另一方面其易用性和稳定性也可能比不过更现代的成熟方案。如果要做桌面开发,更推荐的做法是采用业界通用的GUI框架。但更通常的情况下,有一个网页也就能够满足绝大部分需求了,毕竟很难想象现在哪个机器上还没有个浏览器!因此也并非必须做成一个独立的应用程序。这个时候JSROOT是个非常不错的选择。
ROOT面临的问题
ROOT距今已有接近30年的历史。其功能和架构具有较重的历史包袱,多为独立实现,老模块和新模块混合在一起。在ROOT中保留的这些陈旧模块中,有些仍然有生命力,而另一些则只是“堪用”而非“好用”。尤其是其中比较偏门的模块,它们可能存在设计缺陷或性能问题。另一方面,ROOT的功能大而全,这有利有弊。它提供了非常多的功能,但并非全都是高品质的,因此应用在严肃的场合时应当谨慎甄别。
例如TVector3
和TMatrixT
。它们的共有缺陷是继承了非空基类TObject
。如果考虑当时的设计思路,继承TObject
可能是为了保持一致性,但这样做的后果是损害了这两个线性代数类的性能,使之在事实上不堪大用。对于TVector3
,其非空基类一方面引入了额外的空间开销,另一方面并干扰了编译器的优化(如向量化),导致生成的代码无法充分运用处理器的能力。TMatrixT
也存在类似问题,并且还有存在问题的小矩阵优化——这一优化采用了定长数组实现,导致无论实际要用的矩阵维数是多少、元素开在栈上还是堆上,这一段数组的空间一直占着内存,不论是否被真正使用。另一方面,TMatrixT
的成员函数多为虚的,在操作小矩阵时(比如3阶以下的)如果大量涉及虚调用,必定产生可观察的性能开销。
另一个例子是ROOT的几何模块(TGeoManager
)。这一模块在设计之初似乎是为了兼容Geant3,其采用的单位制和几何类型在它们之间是能够互相打通的。但自从Geant4发布之后,这一模块就不对劲了:单位不兼容,几何体类型也不完全通用,并且ROOT也没有打算跟进的意思。但好在还有通过GDML导入的这个方案,使得从Geant4输入几何依旧可能。就目前的状态来看,ROOT的几何用于渲染图片还是不错的,但想要通过封装它的API统一几何界面就有难度了(在这方面也确实有尝试,我之前路过一个项目时看到有人在做这方面的工作。但网页我想不起来了,见到了再补)。
但是也不能过度责备——ROOT的生态局限在了高能物理内部,其开发者和用户几乎全为物理学出身,使得这一软件始终没能达到其最好的状态。当然一部分原因可能是ROOT没赶上移动互联网/大数据这个浪潮,始终只在物理学领域内发光发热。以其潜力本可以将业务范围拓展到更通用的数据分析上去,并吸引更多的优秀软件开发者投入其中。但我们还是可以期待一下后续会不会有令人惊喜的新变化。也确实在变化了。
实践上,在开发中碰上它们的缺陷时,应及时寻找ROOT内或外的更好的替代品。一般来说ROOT只是为后向兼容性而保留了这些问题模块,在ROOT内部通常就能找到好的替代;如果确实遇上了真正的瓶颈,用好其他更精美的库是更好的。
小结
ROOT作为高能物理实验数据分析领域的一个重要框架,值得好好付诸一用。ROOT也正在开发第7个大版本,让更多功能更加易用,代码风格更加清晰明快,采用了真正现代化的C++,使用标准的C++特性,让开发也更加轻松。还引入了更多令人激动的新特性,如RDataFrame
、RNTuple
,解决了TTree
长久以来的困难,让人迫不及待地想要应用在最先进的软件上。我们拭目以待。
教程和手册
安装
可以按照官方提供的指引安装。如果您对此毫无头绪,可以参考以下的详细步骤。我提供了在GNU/Linux上的安装方法以供参考。
通常来说,按是否在本地编译区分,ROOT的安装方法分为两种。如果您没有特殊需求,可以直接安装ROOT的二进制分发版。如果您需要以特定C++标准编译的版本,或者需要二进制分发版中默认不提供的一些模块,又或者希望榨干硬件的性能,请多花些精力手工编译。
安装二进制分发版
这是最轻松的安装方法,大部分依赖都被打包在ROOT分发的二进制文件中,您所需要做的只是下载、解压、并配置环境变量。
下载
- 前往ROOT官网,往下滑,在右下角Lateset Releases框框里选自己需要的版本。一般选最新版即可。
- 现在您处在对应版本的下载页面,页面中央是下载类型的选择。可供下载的有源代码(source),以及在它下方的对应不同操作系统的二进制分发版。在靠下方的二进制分发版当中按照您的操作系统分发版选择对应版本并下载。
解压到安装目录
安装目录的选择
现在需要选择一个安装目录,下一步将已下好的压缩包内的文件全部提取到这一目录。我对安装目录的建议是:
- 如果您使用的是公用的电脑,即这台机器上同时存在两个或两个以上的用户账户,则安装在自己的家目录以下(即
/home/<您的用户名>/
或~
这一目录的某一下级目录)。如果您使用的是常见的分发版,那么我的建议是安装在~/Public/apps/
这一目录。也可以选择其他类似位置,但一定要在家目录以下。 - 如果您正在使用您的个人电脑,则可以安装在系统目录以下或者家目录以下。如果安装在系统目录,建议安装在
/opt/
,如果安装在家目录,则建议安装于~/Public/apps/
或其他类似的地方。其选择规则为:- 如果您的家目录与系统目录挂载的硬盘相同,则直接安装在家目录之下即可。
- 如果不同,则安装在性能更好的硬盘对应的目录之下。
- 如果对这一问题没有头绪,安装在家目录之下。
另外需要注意的一点是,如果安装在系统目录,ROOT文件夹是只读的。意味着ROOT的所有文件,包括PyROOT模块,不能被正常地修改(不会真的有人用sudo
跑计算吧)。在使用某些Python模块时应当注意。例如之前(大约版本6.22左右)使用PyROOT+Jupyter这个组合时就存在些许权限问题。但也不必过于担心,遇到问题的话直接移动ROOT文件夹的位置就好。
解压
将下载的压缩包解压到选好的安装目录。打开一个终端,输入如下命令,注意不要输错: 1
2cd <安装目录>
tar xvf <下载的压缩包所在的路径>~/Downloads/
目录,其路径为~/Downloads/root-6.26.06.xxx.tar.gz
,并希望安装到~/Public/apps/
,则按如下输入: 1
2cd ~/Public/apps/
tar xf ~/Downloads/root-6.26.06.xxx.tar.gz
如果终端没有返回任何意见,则说明一切顺利。如果您要安装在系统目录下,比如/opt/
,则需要用sudo
提权,否则会报权限不足。即按如下输入: 1
2cd <安装目录(是一个系统目录)>
sudo tar xvf <下载的压缩包所在的路径>
以上的动作将压缩包的内容提取到了<安装目录>
下,这时如果一切正常,<安装目录>
下多出且仅多出了一个名为root
的文件夹(直到6.26/06版本这个文件夹的名字都是root
,如果有变更,以压缩包下的最高一级文件夹名称为准)。
此时解压已经完成。
设置环境变量
编辑~/.bashrc
(用vi
、gedit
都可以,这里用gedit
),在终端输入 1
gedit ~/.bashrc
1
source <安装目录>/root/bin/thisroot.sh
<安装目录>
换为前面所说的安装目录,注意应当是绝对路径。例如,您安装到了~/Public/apps/
,那么请在~/.bashrc
末尾追加: 1
source ${HOME}/Public/apps/root/bin/thisroot.sh
${HOME}
与~
几乎具有同等效力,${HOME}
展开为您的家目录的绝对路径。
或者说,您安装到了/opt/
,那么请在~/.bashrc
末尾追加: 1
source /opt/root/bin/thisroot.sh
注意不需要改动~/.bashrc
文件中的其他内容。在追加环境变量时,加个注释是有好处的。这样做的话,追加之后的~/.bashrc
为: 1
2
3
4... (~/.bashrc原有的内容)
# ROOT
source <安装目录>/root/bin/thisroot.shroot
是还会报“未找到命令”,因为它的shell的环境变量没有更新。关闭原终端,另开一个终端,在其中执行root
,此时应当显示ROOT的主界面,至此安装完成。
手工编译
这是进阶的安装方法。如果您需要以特定C++标准编译的版本,或者需要二进制分发版中默认不提供的一些模块,又或者希望榨干硬件的性能,可以自己编译。否则安装ROOT提供的二进制分发版就好。下面假定您已经相对了解GNU/Linux的使用方法,因此不会详述所有细节。
前置依赖
ROOT的依赖关系比较复杂,不同模块对外部库的依赖不完全相同,其映射关系相对复杂,完整的模块依赖并没有非常明确的文档给出,但如果需要某些指定模块时,仔细阅读CMake日志并按照指引装对应依赖库即可。如果只是简单的使用,那无需完全理清ROOT的依赖关系;如果是基于ROOT做深入的开发,一段时间后也就清楚了。
在官网的这个页面中,ROOT详细列出了在一些常用GNU/Linux分发版上利用对应包管理器(例如APT等)安装依赖库的方案(但需要注意有些已经过时或不完整,如果您使用的是Debian系GNU/Linux,推荐采用下面的方案)。利用好包管理器可以免除自己编译的麻烦,并且稳定性也有保障,为快速部署提供了便利。
这里整理了一份适用于Debian系GNU/Linux(最知名地如Ubuntu)上的安装依赖的方案,安装这部分依赖库足以满足按后面的流程完成编译的要求。打开终端执行以下命令安装即可: 1
2
3
4sudo apt update
sudo apt upgrade
sudo apt install build-essential cmake ninja-build libx11-dev libxpm-dev libxft-dev libxext-dev libssl-dev python3 python3-dev python3-pip libpcre3-dev libglu1-mesa-dev libglew-dev libftgl-dev libmysqlclient-dev libfftw3-dev libcfitsio-dev libavahi-compat-libdnssd-dev libldap2-dev libxml2-dev libkrb5-dev libopenblas-dev libopenblas64-dev libtbb2-dev libgsl-dev git
sudo pip install numpy
这里值得注意的一点是,按照这一流程继续下去,将会安装依赖于GSL的MathMore模块,这将导致编译出的ROOT需要受到GNU GPL 3.0的限制。这一限制主要体现在链接上,如果您打算将ROOT链接到和GNU GPL 3.0不兼容的软件(例如,所有的专有软件,或者使用了不兼容许可证的开源软件)上去,那么您需要避免使用MathMore。如果确实如此,那您不需要安装GSL,在上面的命令中去掉libgsl-dev
这项即可。包括此项,这里提供的安装方案引入的任何明显的或隐含的法律风险都需要您自行承担。
下载源代码
- 前往ROOT官网,往下滑,在右下角Lateset Releases框框里选自己需要的版本。一般选最新版即可。
- 下载source对应的那一项。这个是源代码。
编译源代码
直接解压源代码到您喜欢的某个目录,解压之后应当产生一个名为root-<版本号>
的文件夹。它里面装的就是源代码。进入这一目录,在其中打开一个终端,或者开一个终端然后cd
过去。
首先新建一个用于存放编译产物的文件夹,并在其中进行编译。在打开的终端中,执行 1
2mkdir build
cd buildbuild
文件夹并进入,这样做的目的是分离源代码和编译期间生成的文件。很多项目不允许在源代码内部直接编译,另开一个用于构建的文件夹是通行的做法。
配置
然后配置CMake。需要编译的ROOT模块通过CMake选项调整。这里是一份ROOT提供的列表,后面将参照此完成编译。
使用默认配置
默认情况下,ROOT已经启用了大部分常用模块的编译,并会根据当前机器上依赖库的安装情况、C++标准等调整具体要编译哪些模块。如果您没什么特别的要求,只是想手工编译一份ROOT,那在build
文件夹中执行下面这行命令就好。之前已经安装了不少依赖库,因此默认启用的模块足以覆盖绝大部分需求。 1
cmake -G Ninja .. -DCMAKE_INSTALL_PREFIX=<安装路径> -DCMAKE_BUILD_TYPE=<构建类型>
<安装路径>
替换为您想安装ROOT的路径,例如/opt/root
、~/Public/apps/root
等。如果您需要一些建议那么可以看这里。
CMake是用于生成编译脚本(如Makefile、build.ninja等)的工具。参数-G
指定采用的生成器,这里使用的是Ninja。如果您不指定参数-G
,那么在GNU/Linux上通常默认为Make。
在其后的..
代表源代码所在的位置,在这里就是上一级目录,因此为..
。
剩下的参数为-DXXX=YYY
的格式,这是CMake特有的用于配置编译的方式。-D
后面紧跟的是需要指定的项(称为CMake的缓存变量),它们具体有哪些是由CMake以及待编译的软件指定,通常可在文档中找到。=
后紧跟的是这一项的值,可用的值有哪些通常也可在对应的文档中找到。例如在这里指定了CMake用于控制安装位置的CMAKE_INSTALL_PREFIX
以及用于控制构建类型的CMAKE_BUILD_TYPE
。
<构建类型>
用于控制编译采用的优化级别以及是否包含调试信息。一般有四种选择:"Debug"
、"Release"
、"RelWithDebInfo
、"MinSizeRel"
。它们的功能如下:
"Debug"
:禁用优化,包含调试信息。"Release"
:启用绝大多数优化,不包含调试信息(如无特殊需求请用此项)。"RelWithDebInfo
:启用部分优化,包含调试信息。"MinSizeRel"
:启用减小译后体积的优化,不包含调试信息。
例如,如果使用的编译器是GCC,那么"Debug"
在命令行参数上的表现为-O0 -g
,"Release"
表现为-O3 -DNDEBUG
,等等。更具体的表现请参阅CMake、生成器、以及编译器的文档。如果您需要使用调试器(例如GDB)在ROOT内部调试,那您需要使用"Debug"
。一般情况下采用"Release"
即可。
例如,如果希望安装在/opt/root
下,并采用优化的构建,安装ROOT默认的模块,则执行 1
cmake -G Ninja .. -DCMAKE_INSTALL_PREFIX=/opt/root -DCMAKE_BUILD_TYPE="Release"
自定义ROOT模块
如果有特殊需求,在装好对应的依赖之后,参照这里补充CMake选项即可。
例如,如果需要ROOT支持Qt6的Web显示组件,则需要先安装Qt6::WebEngineCore
以及Qt6::WebEngineWidgets
,并在CMake命令行上补充-Dqt6web=ON
即可。即 1
cmake -G Ninja .. -DCMAKE_INSTALL_PREFIX=<安装路径> -DCMAKE_BUILD_TYPE=<构建类型> -Dqt6web=ON
指定C++标准
C++标准通过CMAKE_CXX_STANDARD
指定。可用的值和C++标准匹配,如14
、17
、20
等。 1
cmake -G Ninja .. -DCMAKE_INSTALL_PREFIX=<安装路径> -DCMAKE_BUILD_TYPE=<构建类型> -DCMAKE_CXX_STANDARD=<C++标准>
ROOT非常好心地在源代码中插入了标准库功能的一些实现,用以在旧的编译标准下提供新特性。但非常离谱的一点是它把实现写在了std
命名空间中(准确来说是std::__ROOT
,但__ROOT
是内联命名空间,事实上没有差别),导致在链接以不同标准编译出的ROOT时,标准库会与ROOT的实现疯狂撞车。因此经常需要以特定C++标准编译出来的ROOT。
其他CMake选项
其他CMake选项请参阅ROOT的文档以及CMake的文档。
检查输出
如果一切顺利的话,CMake不应该报告任何错误。在CMake日志的最后,ROOT会总结将要采用的编译设置,您可以对照检查是否有误。没有问题就可以开始编译。
编译
到此为止,最麻烦的部分已经结束了。只要在build
文件夹中执行 1
ninja
-jN
设置并行编译的进程数。对于普通的笔记本电脑,编译时间的量级为一个小时,坐和放宽。
编译完成后,执行 1
ninja install
sudo
提权。
设置环境变量
最后请看这里设置环境变量。至此安装完成。