ROOT:面向对象的数据分析框架

作者:Fons Rademakers

ROOT 是一个用于大规模数据分析和数据挖掘的系统。它最初是为粒子物理数据分析而开发的,但也同样适用于其他需要处理大量数据的领域。

在开发 PAW 和 PIAF (见资源) 等交互式数据分析系统多年的经验之后,我们意识到这些产品 (用 FORTRAN 编写并使用 20 年前的库) 的增长和可维护性已达到极限。尽管这些系统在物理学界仍然很受欢迎,但它们无法应对下一代粒子加速器,即目前正在瑞士日内瓦 CERN 建造的大型强子对撞机 (LHC) 所带来的挑战。LHC 预计每年产生的数据量将达到数拍字节 (1PB = 1,000,000GB) 级别。这比当前一代加速器产生的数据量多出两到三个数量级。

因此,在 1995 年初,Rene Brun 和我开始开发一个系统,旨在克服之前这些程序的缺点。我们做出的首批决定之一是遵循面向对象的分析和设计方法,并使用 C++ 作为我们的实现语言。尽管我们之前的所有编程经验都是 FORTRAN,但我们很快意识到 OO 和 C++ 的强大之处,经过一些最初的“抛弃式”原型设计后,ROOT 系统开始成型。

1995 年 11 月,我们在 CERN 首次公开展示了 ROOT,同时通过 Web 发布了 0.5 版本。那时,Nenad Buncic 和 Valery Fine 加入了我们的团队。

自最初发布以来,用户数量不断增加。为了回应评论和反馈,我们定期发布包含错误修复和新功能的新版本。1997 年 1 月,发布了 1.0 版本,1998 年 3 月发布了 2.0 版本。自 1.0 版本发布以来,已从我们的网站下载了超过 9,300 份 ROOT 二进制文件,约有 500 人注册为 ROOT 用户,并且该网站每月访问量高达 100,000 次。

ROOT 目前被应用于许多不同的领域,如物理学、天文学、生物学、遗传学、金融、保险、制药等。

可以从 ROOT 网站 (http://root.cern.ch/) 下载适用于许多不同平台的源代码和二进制文件。当前版本可以免费使用和分发,前提是给予适当的致谢并保留版权声明。对于商业用途,作者希望得到通知。

ROOT 的主要特点

ROOT 系统的主要组件包括

  • 分层面向对象数据库 (机器无关,高度压缩,支持模式演化和对象版本控制)

  • C++ 解释器

  • 高级统计分析工具 (用于多维直方图、拟合和最小化的类)

  • 可视化工具 (用于 2D 和 3D 图形的类,包括 OpenGL 接口)

  • 一组丰富的容器类,它们完全了解 I/O (列表、排序列表、映射、btree、哈希表、对象数组等)

  • 一套广泛的 GUI 类 (窗口、按钮、组合框、选项卡、菜单、项目列表、图标框、工具栏、状态栏和许多其他)

  • 自动 HTML 文档生成工具

  • 运行时对象检查功能

  • 客户端/服务器网络类

  • 共享内存支持

  • 远程数据库访问,可以通过特殊守护程序或通过 Apache Web 服务器

  • 已移植到所有已知的 UNIX 和 Linux 系统,以及 Windows 95 和 NT

整个系统由大约 450,000 行 C++ 和 80,000 行 C 代码组成。大约有 310 个类,分为 24 个不同的框架,每个类都由其自己的共享库表示。

CINT C/C++ 解释器

ROOT 系统的关键组件之一是 CINT C/C++ 解释器。CINT 由 Hewlett Packard Japan 的 Masaharu Goto 编写,涵盖了 95% 的 ANSI C 和大约 85% 的 C++。模板支持正在开发中,异常处理仍然缺失。CINT 足够完整,可以解释其自身的 70,000 行 C 代码,并让解释后的解释器解释一个小程序。

C/C++ 解释器的优点在于它允许快速原型设计,因为它消除了典型的耗时编辑/编译/链接周期。一旦脚本或程序完成,您可以使用标准 C/C++ 编译器 (gcc) 将其编译为机器代码,并享受完整的机器性能。由于 CINT 非常高效 (例如,for/while 循环是动态字节码编译的),因此完全可以在解释器中运行小型程序。在大多数情况下,CINT 的性能优于其他解释器,如 Perl 和 Python。

现有的 C 和 C++ 库可以很容易地与解释器接口。这是通过从函数和类定义生成字典来完成的。字典为 CINT 提供了所有必要的信息,以便能够调用函数、创建对象和调用成员函数。字典很容易通过程序 rootcint 生成,该程序使用库头文件作为输入,并生成一个包含字典的 C++ 文件作为输出。您编译字典并将其与库代码链接到一个共享库中。在运行时,您动态链接共享库,然后可以通过解释器调用库代码。这可以是一种非常方便的方式来快速测试一些特定的库函数。您只需直接从解释器提示符调用函数,而无需编写小型测试程序。

CINT 解释器完全嵌入到 ROOT 系统中。它允许 ROOT 命令行、脚本和编程语言相同。嵌入式解释器字典提供了必要的信息,以自动创建 GUI 元素,如每个类特有的上下文弹出菜单,以及生成完全超链接的 HTML 类文档。此外,字典信息还提供了完整的运行时类型信息 (RTTI) 和运行时对象自省功能。

安装

ROOT 的二进制文件和源代码可以从 http://root.cern.ch/root/Version200.html 下载。下载后,在您的主目录 (或系统范围的位置,如 /opt) 中解压缩和解档 (使用 tar) 文件 root_v2.00.Linux.2.0.33.tar.gz。此过程将生成目录 /root。此目录包含以下文件和子目录

  • AA_README:开始前阅读此文件

  • bin:包含可执行文件的目录

  • include:包含 ROOT 头文件的目录

  • lib:包含 ROOT 库的目录 (共享库格式)

  • macros:包含系统宏的目录 (例如,GL.C 用于加载 OpenGL 库)

  • icons:包含 xpm 图标的目录

  • test:一些 ROOT 测试程序

  • tutorials:可以由 bin/root 模块执行的示例宏

在使用系统之前,您必须将环境变量 ROOTSYS 设置为根目录,例如,export ROOTSYS=/home/rdm/root,并且您必须将 $ROOTSYS/bin 添加到您的路径中。完成后,您就可以开始使用 ROOT 了。

首次交互式会话

在第一个会话中,启动 ROOT 交互式程序 root。此程序通过命令行提示符提供对所有可用 ROOT 类的访问。通过在提示符下键入 C++ 语句,您可以创建对象、调用函数、执行脚本等。转到目录 $ROOTSYS/tutorials 并键入

bash$ root
root [0] 1+sqrt(9)
(double)4.000000000000e+00
root [1] for (int i = 0; i < 5; i++)<\n>
printf("Hello %d\n", i)
Hello 0
Hello 1
Hello 2
Hello 3
Hello 4
root [2] .q

如您所见,如果您了解 C 或 C++,您可以使用 ROOT。无需学习新的命令行或脚本语言。要退出,请使用 .q,这是少数几个“原始”解释器命令之一。点是解释器转义符号。还有一些点命令可以调试脚本 (step、step over、set breakpoint 等) 或加载和执行脚本。

现在让我们尝试一些更有趣的东西。再次启动 root

bash$ root
root [0] TF1 f1("func1", "sin(x)/x", 0, 10)
root [1] f1.Draw()
root [2] f1.Dump()
root [3] f1.Inspect()
 // Select File/Close Canvas
root [4] .q

图 1. f1.Draw() 的输出

在这里,您创建了一个 TF1 类的对象,这是一个一维函数。在构造函数中,您为对象指定一个名称 (如果对象存储在数据库中,则使用该名称)、函数以及 x 的上限和下限值。创建函数对象后,您可以例如通过执行 TF1::Draw 成员函数来绘制对象。图 1 显示了此函数的外观。现在,将鼠标移动到图片上,并查看当您跨越对象时光标的形状如何变化。在任何时候,您都可以按鼠标右键弹出上下文菜单,其中显示当前对象的可用成员函数。例如,将光标移动到函数上,使其变成指向手指,然后按右键。上下文菜单显示对象的类和名称。选择项目 SetRange 并在对话框字段中输入 -10, 10。(这相当于从命令行提示符执行成员函数 f1.SetRange(-10,10),然后执行 f1.Draw()。) 使用 Dump 成员函数 (每个 ROOT 类都从基本 ROOT 类 TObject 继承该函数),您可以查看内存中当前对象的完整状态。Inspect 函数在图形窗口中显示相同的信息。

直方图和拟合

让我们再次启动 root 并运行以下两个宏

bash$ root
root [0] .x hsimple.C
root [1] .x ntuple1.C
 // interact with the pictures in the canvas
root [2] .q

注意:如果上述操作不起作用,请确保您位于 tutorials 目录中。

图 2. ntuple1.C 的输出

宏 hsimple.C (参见 $ROOTSYS/tutorials/hsimple.C) 创建一些 1D 和 2D 直方图以及一个 Ntuple 对象。(Ntuple 是元组的集合;元组是一组数字。) 直方图和 Ntuple 通过执行 25,000 次循环填充随机数。在填充期间,1D 直方图在画布中绘制,并且每填充 1,000 次更新一次。在宏的末尾,直方图和 Ntuple 对象存储在 ROOT 数据库中。

ntuple1.C 宏使用在上一个宏中创建的数据库。它创建一个画布对象和四个图形 pad。在四个 pad 中的每一个中,都绘制了不同 Ntuple 数量的分布。通常,数据分析是通过在直方图中绘制其中一个元组数量来完成的,前提是其他一些数量通过了特定条件。例如,我们的 Ntuple 包含数量 px、py、pz、random 和 i。命令

ntuple->Draw("px", "pz < 1")

将填充一个直方图,其中包含所有满足 pz < 1 条件的元组的 px 值分布。将此示例中使用的抽象数量替换为名称、性别、年龄、长度等数量,您可以很容易地理解 Ntuple 可以以多种不同的方式使用。包含 25,000 个元组的 Ntuple 非常小。在典型的物理分析情况下,Ntuple 可以包含数百万个元组。除了简单的 Ntuple 之外,ROOT 系统还提供了一个 Tree。Tree 是一个 Ntuple,它被推广到完整的对象。也就是说,Tree 可以存储对象集,而不是元组集。对象属性可以以与元组数量相同的方式进行分析。有关 Tree 的更多信息,请参阅 ROOT HOWTOs,网址为 http://root.cern.ch/root/Howto.html。

在数据分析期间,您通常需要使用假设来测试数据。假设是描述模型的理论/经验函数。为了查看数据是否与模型匹配,您可以使用最小化技术来调整模型参数,以便函数与数据最佳匹配;这称为拟合。ROOT 允许您将标准函数 (如多项式、高斯指数函数或自定义函数) 拟合到您的数据。在图 2 的右上角 pad 中,数据已使用二阶多项式 (红色曲线) 进行拟合。这是通过调用直方图对象的 Fit 成员函数来完成的

hprofs->Fit("pol2")

将光标移动到画布上,您可以与不同的对象进行交互。例如,可以通过单击鼠标左键并移动光标来旋转右下角的 3D 图。

GUI 类和对象浏览器

ROOT 系统中嵌入了一套广泛的 GUI 类。GUI 类提供了一个完整的 OO-GUI 框架,而不是围绕 GUI (如 Motif) 的简单包装器。所有 GUI 元素都通过 TGXW 低级图形抽象基类进行绘制。根据您运行 ROOT 的平台,具体的图形类 (从 TGXW 继承) 可以是 TGX11 或 TGWin32。所有 GUI 小部件都是从“第一性原理”创建的,即它们仅使用 DrawLineFillRectangleCopyPixmap 等例程,因此,TGX11 实现只需要 X11 和 Xpm 库。抽象基类方法的优点在于,将 GUI 类移植到新的非 X11/Win32 平台只需要实现 TGXW (以及用于 OS 接口的 TSystem) 的适当版本。

所有 GUI 类都是完全可脚本化的,并且可以通过解释器访问。这允许小部件布局的快速原型设计。

GUI 类基于 David Barth 和 Hector Peraza 编写的 XClass'95 库。这些小部件具有众所周知的 Windows 95 外观和风格。有关 XClass'95 的更多信息,请参阅 ftp://mitac11.uia.ac.be/html-test/xclass.html。

图 3. ROOT 对象浏览器

使用 ROOT 对象浏览器,可以浏览和检查 ROOT 系统中的所有对象。要创建浏览器对象,请键入

root [0] TBrowser *b = new TBrowser

浏览器,如图 3 所示,在左窗格中显示可浏览的 ROOT 集合,在右窗格中显示所选集合中的对象。双击对象将执行与对象类关联的默认操作。双击直方图对象将绘制直方图。双击 Ntuple 数量将生成一个直方图,其中显示通过循环遍历 Ntuple 中的所有元组而得出的数量分布。右键单击对象将弹出一个上下文菜单 (就像在画布中一样)。

将您自己的类集成到 ROOT 中

在本节中,我将提供一个逐步方法,用于将您自己的类集成到 ROOT 中。集成后,您可以将类的实例保存在 ROOT 数据库中,在运行时检查对象,通过解释器创建和操作对象,生成 HTML 文档等。 列表 1 中显示了一个描述某些人员属性的非常简单的类。 列表 2 中显示了 Person 实现文件 Person.cxx。

ClassDefClassImp 提供了一些成员函数,这些函数允许类访问其解释器字典信息。从 ROOT 基本对象 TObject 的继承提供了与数据库和检查服务的接口。

现在运行 rootcint 程序来创建字典,包括 Person 类的特殊 I/O 流和检查方法

bash$ rootcint -f dict.cxx -c Person.h

接下来,将类和字典的源代码编译并链接到一个共享库中

bash$ g++ -fPIC -I$ROOTSYS/include -c dict.cxx
bash$ g++ -fPIC -I$ROOTSYS/include -c Person.cxx
bash$ g++ -shared -o Person.so Person.o dict.o
现在启动 ROOT 交互式程序,看看我们如何使用 CINT C++ 解释器创建和操作 Person 类的对象
bash$ root
root [0] gSystem->Load("Person.so")
root [1] Person rdm(37, 181.0)
root [2] rdm.get_age()
(int)37
root [3] rdm.get_height()
(float)1.810000000000e+02
root [4] TFile db("test.root","new")
root [5] rdm.Write("rdm") // Write is inherited from the
TObject class
root [6] db.ls()
TFile** test.root
 TFile* test.root
 KEY: Person rdm;1
root [7] .q
在这里,关键语句是动态加载包含您的类代码和类字典的共享库的命令。

在下一个会话中,我们访问刚刚存储在数据库 test.root 上的 rdm 对象

bash$ root
root [0] gSystem->Load("Person.so")
root [1] TFile db("test.root")
root [2] rdm->get_age()
(int)37
root [3] rdm->Dump() // Dump is inherited from the TObject
class"
age     37      age of person
height  181     height of person
fUniqueID       0       object unique identifier
fBits   50331648        bit field status word
root [4] .class Person
[follows listing of full dictionary of class Person]
root [5] .q

列表 3 中显示了一个在数据库中创建和存储 1000 人的 C++ 宏。要执行此宏,请执行以下操作

bash$ root
root [0] .x fill.C
root [1] .q

这种存储对象的方法仅适用于数千个对象。特殊的 Tree 对象容器应用于存储数百万个相同类的对象。

列表 4 是一个 C++ 宏,它查询数据库并打印某个年龄段的所有人员。要执行此宏,请执行以下操作

bash$ root
root [0] .x find.C(77,80)
age = 77, height = 10077.000000
age = 78, height = 10078.000000
age = 79, height = 10079.000000
age = 80, height = 10080.000000
NULL
root [1] find(888,895)
age = 888, height = 10888.000000
age = 889, height = 10889.000000
age = 890, height = 10890.000000
age = 891, height = 10891.000000
age = 892, height = 10892.000000
age = 893, height = 10893.000000
age = 894, height = 10894.000000
age = 895, height = 10895.000000
root [2] .q

如果 Person 对象存储在 Tree 中,则可以使用单个命令完成此类分析。

最后, 列表 5 中显示了一个小型 C++ 宏,它使用字典中存储的信息打印 Person 类中定义的所有方法。要执行此宏,请键入

bash$ root
root [0] .x method.C
class Person Person(int a = 0, float h = 0)
int get_age()
float get_height()
void set_age(int a)
void set_height(float h)
const char* DeclFileName()
int DeclFileLine()
const char* ImplFileName()
int ImplFileLine()
Version_t Class_Version()
class TClass* Class()
void Dictionary()
class TClass* IsA()
void ShowMembers(class TMemberInspector& insp, char* parent)
void Streamer(class TBuffer& b)
class Person Person(class Person&)
void ~Person()
root [1] .q

以上示例证明了当您通过几个简单的步骤将您的类集成到 ROOT 框架中时可以获得的功能。

Linux 是科学计算中日益增长的力量

分析超过 9,300 次 ROOT 二进制文件下载的 FTP 日志,揭示了主要科学界中不同计算平台的普及程度。图 4 显示了每个平台下载的 ROOT 二进制文件数量。

图 4. ROOT 下载统计

Linux 是明显的领导者,其次是 Microsoft 平台 (Windows 95 和 NT 加起来与 Linux 相当)。其他 UNIX 机器的结果可能需要稍微修正一下,因为许多机器是多用户机器,系统管理员的单次下载将覆盖多个用户。Linux 和 Windows 是典型的单用户环境。

摘要

在本文中,我概述了 ROOT 数据处理系统的一些主要功能。但是,系统的许多方面和功能仍然未被涵盖,例如客户端/服务器类 (TSocket、TServerSocket、TMonitor 和 TMessage 类)、如何自动生成 HTML 文档 (使用 THtml 类)、远程数据库访问 (通过 rootd 守护程序)、高级 3D 图形等。有关这些主题的更多信息,请访问 ROOT 网站。

资源

致谢

ROOT: An Object-Oriented Data Analysis Framework
Fons Rademakers 获得了阿姆斯特丹大学粒子物理学博士学位。自 1988 年以来,他一直在 CERN 工作,开发数据库、数据分析和图形软件。Fons 于 1993 年开始使用 Linux,并一直倡导使用它。除了开发 ROOT 之外,他还在构建多个用于物理数据处理的 Linux PC 集群 (与 Hewlett Packard 的联合项目)。不编程时,他会参加卡丁车比赛和骑越野自行车。可以通过电子邮件 Fons.Rademakers@cern.ch 与他联系。

ROOT: An Object-Oriented Data Analysis Framework
Rene Brun 获得了法国克莱蒙费朗大学的博士学位。他于 1973 年加入 CERN。Rene 为 CERN 程序库做出了重大贡献,创建并协调了 GEANT 和 PAW 等主要软件项目的开发。1989 年,他因其对核物理和粒子物理通用探测器模拟框架的贡献而获得了 IEEE/CANPS 奖。可以通过电子邮件 Rene.Brun@cern.ch 与他联系。
加载 Disqus 评论