1.4 TTree 的 MakeClass 方法¶
- MakeClass方法提供了访问ROOT文件的TTree内部数据所需的类(class)的结构
1. 手动编写逐事件读取ROOT文件分析¶
- 用户需要手动管理内存变量与磁盘数据分支(Branch)的映射, 这种方式在分支较少时可行。。
void Analyze() {
// 1. 声明本地工作区变量
Int_t raw_pid;
Double_t raw_tL, raw_tR;
// 2. 手动绑定地址:将磁盘 Branch 映射到内存变量地址
tIn->SetBranchAddress("pid", &raw_pid);
tIn->SetBranchAddress("tL", &raw_tL);
// ... 若有几十个分支,此处将极其冗长
}
- 但当处理复杂的 ROOT 文件(如包含多维数组或数十个物理量分支)时,手动编写变量声明和
SetBranchAddress既繁琐又极易出错。
TFile *ipf = new TFile("run0005.root");
TTree *tree = (TTree*) ipf->Get("tree");
tree->Print();
****************************************************************************** *Tree :tree : tree must2 * *Entries : 511950 : Total = 917722277 bytes File Size = 306046164 * * : : Tree compression factor = 3.00 * ****************************************************************************** *Br 0 :beamTrig : beamTrig/I * *Entries : 511950 : Total Size= 2049732 bytes File Size = 93041 * *Baskets : 17 : Basket Size= 519680 bytes Compression= 22.02 * *............................................................................* *Br 1 :must2Trig : must2Trig[8]/I * *Entries : 511950 : Total Size= 16389351 bytes File Size = 1021856 * *Baskets : 69 : Basket Size= 1369600 bytes Compression= 16.04 * *............................................................................* *Br 2 :PPACF5 : PPACF5[5][5]/F * *Entries : 511950 : Total Size= 51211689 bytes File Size = 21646784 * *Baskets : 176 : Basket Size= 3433472 bytes Compression= 2.37 * *............................................................................* *Br 3 :PPACF8 : PPACF8[5][5]/F * *Entries : 511950 : Total Size= 51211689 bytes File Size = 27255795 * *Baskets : 176 : Basket Size= 3433472 bytes Compression= 1.88 * *............................................................................* *Br 4 :PPACF9 : PPACF9[5][5]/F * *Entries : 511950 : Total Size= 51211689 bytes File Size = 14832700 * *Baskets : 176 : Basket Size= 3433472 bytes Compression= 3.45 * *............................................................................* *Br 5 :FPPosition : FPPosition[12][4]/F * *Entries : 511950 : Total Size= 98325682 bytes File Size = 30590368 * *Baskets : 319 : Basket Size= 6226944 bytes Compression= 3.21 * *............................................................................* *Br 6 :F5PPACRawData : F5PPACRawData[5][10]/F * *Entries : 511950 : Total Size= 102423551 bytes File Size = 32627987 * *Baskets : 332 : Basket Size= 6470144 bytes Compression= 3.14 * *............................................................................* *Br 7 :F8PPACRawData : F8PPACRawData[5][10]/F * *Entries : 511950 : Total Size= 102423551 bytes File Size = 40257142 * *Baskets : 332 : Basket Size= 6470144 bytes Compression= 2.54 * *............................................................................* *Br 8 :F9PPACRawData : F9PPACRawData[5][10]/F * *Entries : 511950 : Total Size= 102423551 bytes File Size = 27991240 * *Baskets : 332 : Basket Size= 6470144 bytes Compression= 3.66 * *............................................................................* *Br 9 :F10PPACRawData : F10PPACRawData[5][10]/F * *Entries : 511950 : Total Size= 102423887 bytes File Size = 28125061 * *Baskets : 332 : Basket Size= 6470656 bytes Compression= 3.64 * *............................................................................* *Br 10 :F11PPACRawData : F11PPACRawData[5][10]/F * *Entries : 511950 : Total Size= 102423887 bytes File Size = 30127047 * *Baskets : 332 : Basket Size= 6470656 bytes Compression= 3.40 * *............................................................................* *Br 11 :F3PlaRawData : F3PlaRawData[4]/F * *Entries : 511950 : Total Size= 8195004 bytes File Size = 3689434 * *Baskets : 35 : Basket Size= 884224 bytes Compression= 2.22 * *............................................................................* *Br 12 :F7PlaRawData : F7PlaRawData[4]/F * *Entries : 511950 : Total Size= 8195004 bytes File Size = 3170206 * *Baskets : 35 : Basket Size= 884224 bytes Compression= 2.58 * *............................................................................* *Br 13 :F11PlaRawData : F11PlaRawData[4]/F * *Entries : 511950 : Total Size= 8195043 bytes File Size = 2787872 * *Baskets : 35 : Basket Size= 884224 bytes Compression= 2.94 * *............................................................................* *Br 14 :F11ICRawData : F11ICRawData[10]/F * *Entries : 511950 : Total Size= 20486459 bytes File Size = 6282467 * *Baskets : 82 : Basket Size= 1612800 bytes Compression= 3.26 * *............................................................................* *Br 15 :TOF : TOF[8]/F * *Entries : 511950 : Total Size= 16388913 bytes File Size = 6714974 * *Baskets : 69 : Basket Size= 1369600 bytes Compression= 2.44 * *............................................................................* *Br 16 :AoQ : AoQ[9][4]/F * *Entries : 511950 : Total Size= 73743159 bytes File Size = 28803633 * *Baskets : 245 : Basket Size= 4768768 bytes Compression= 2.56 * *............................................................................*
2. MakeClass:自动化代码生成器¶
MakeClass 的核心作用是自动化数据映射。它通过扫描 TTree 的拓扑结构,自动生成一个包含所有变量定义、底层数据连接以及分析循环框架的 C++ 类。
生成方式: 在 ROOT 终端执行:
tree->MakeClass("ppac");
Info in <TTreePlayer::MakeClass>: Files: ppac.h and ppac.C generated from TTree: tree
ROOT 会生成 ppac.h 和 ppac.C。类 ppac 定义了该实验数据的结构及其操作接口,可以被视为一个自定义的高级数据类型。
3. ppac.h:类的声明与成员定义¶
ppac.h 负责定义数据的“模样”。根据源代码,其实际结构如下:
A. 成员变量(数据存放区) 类内部自动声明了与 Tree 分支完全对应的变量。相比传统做法,它能自动处理复杂的数组维度。
#ifndef ppac_h
#define ppac_h
#include <TROOT.h>
#include <TChain.h>
#include <TFile.h>
class ppac {
public :
TTree *fChain; //! 指向当前 TTree 或 TChain 的指针
Int_t fCurrent; //! 当前 Tree 在 Chain 中的编号
// --- 自动生成的 Leaf (叶子) 变量 ---
Int_t beamTrig;
Int_t must2Trig[8];
Float_t F8PPACRawData[5][10]; // 自动处理了数组维度
Float_t AoQ[9][4];
// ...
// 【用户扩展】:你可以在此处手动添加自定义变量
// 例如:Double_t x_cal; 它们将成为 ppac 类型的一部分
B. 成员函数(操作接口声明) 类不仅包含数据,还定义了操作数据的函数:
// --- 自动生成的成员函数 ---
ppac(TTree *tree=0); // 构造函数:用于初始化
virtual ~ppac(); // 析构函数:用于清理资源
virtual Int_t GetEntry(Long64_t entry); // 读取数据
virtual void Init(TTree *tree); // 核心:执行地址绑定
virtual void Loop(); // 主分析循环
virtual void Show(Long64_t entry = -1); // 打印数据
// 【用户扩展】:你可以在此处声明新的成员函数,例如 virtual void Calibrate();
};
构造函数与 Init()¶
构造函数 (Constructor)
在 ppac.h 的实现部分(#ifdef ppac_cxx),定义了类的构造函数。
- 含义:它是类诞生的“初始化程序”。
- 触发时机:一旦类被实例化(创建对象),构造函数就会立即自动运行。
- 源代码实现:如果实例化时不传入任何参数(tree == 0),构造函数会默认去打开生成该类时的原始文件(run0005.root),并立即调用 Init(tree) 自动完成所有的 SetBranchAddress 映射工作。
ppac::ppac(TTree *tree) : fChain(0)
{
if (tree == 0) {
// 自动连接生成代码时的原始文件 run0005.root
TFile *f = (TFile*)gROOT->GetListOfFiles()->FindObject("run0005.root");
if (!f || !f->IsOpen()) { f = new TFile("run0005.root"); }
f->GetObject("tree",tree);
}
Init(tree); // 实例化时立即执行关键的地址绑定
}
Init() 函数
Init() 实现了传统做法中所有的 SetBranchAddress 操作,将磁盘数据流导向类成员变量。
void ppac::Init(TTree *tree)
{
if (!tree) return;
fChain = tree;
fChain->SetMakeClass(1);
// 自动生成的地址绑定代码:将 Branch 名称与类成员变量地址关联
fChain->SetBranchAddress("beamTrig", &beamTrig, &b_beamTrig);
fChain->SetBranchAddress("F8PPACRawData", F8PPACRawData, &b_F8PPACRawData);
// ...
}
fChain:它是类内部访问 Tree 的全局接口。所有的读取操作(如 GetEntry)最终都通过 fChain 执行。
Loop() 函数
- 读入事件信息进行进一步处理。其具体实现在
ppac.C
4. ppac.C:物理逻辑实现¶
ppac.C 提供了物理逻辑的存放位置。
- #include "ppac.h":在
ppac.C开头引入头文件。这意味着ppac.C获得了ppac.h中定义的所有变量和函数接口。
原始 Loop() 框架
生成的原始代码是一个空循环,展示了标准的数据读取流程:
#define ppac_cxx
#include "ppac.h"
void ppac::Loop() {
if (fChain == 0) return;
Long64_t nentries = fChain->GetEntriesFast(); // 获取总事件数
for (Long64_t jentry=0; jentry<nentries;jentry++) {
Long64_t ientry = LoadTree(jentry); // 确定当前 Entry 在哪个 Tree
if (ientry < 0) break;
fChain->GetEntry(jentry); // 核心:将该行数据真正读入 .h 中声明的变量中
// 用户在此处扩展代码...
}
}
用户物理逻辑扩展:
用户通过在 Loop() 中添加输出流和计算逻辑,将原始数据转化为物理量:
下列代码保存到ppac1.C中
#define ppac_cxx
#include "ppac.h"
#include <TH2.h>
#include <TStyle.h>
#include <TCanvas.h>
TH1D *h1 = new TH1D("h1", "h1", 2000, 0, 2000);
void ppac::Loop()
{
//new tree and root file
TFile *opf = new TFile("ppac.root", "recreate");
TTree *tree = new TTree("tree", "ppac");
Double_t txl, txr, tyd, tyu, ta;
Double_t x, y;
tree->Branch("txl", &txl, "txl/D");
tree->Branch("txr", &txr, "txr/D");
tree->Branch("tyd", &tyd, "tyd/D");
tree->Branch("tyu", &tyu, "tyu/D");
tree->Branch("ta", &ta, "ta/D");
tree->Branch("x", &x, "x/D");
tree->Branch("y", &y, "y/D");
//TTree *fChain - Pointer pointing to the tree
if (fChain == 0) return;
Long64_t nentries = fChain->GetEntriesFast();
Long64_t nbytes = 0, nb = 0;
for (Long64_t jentry = 0; jentry < nentries; jentry++) {
Long64_t ientry = LoadTree(jentry);
if (ientry < 0) break;
nb = fChain->GetEntry(jentry); nbytes += nb;
//user code
if(jentry % 10 ==0)
h1->Fill(F8PPACRawData[0][1] + F8PPACRawData[0][0] - 2*F8PPACRawData[0][4]);
txl = F8PPACRawData[0][0];
txr = F8PPACRawData[0][1];
tyd = F8PPACRawData[0][2];
tyu = F8PPACRawData[0][3];
ta = F8PPACRawData[0][4];
bool bx = txl<4900 && txr<4900;
bool by = tyu<4900 && tyd<4900;
bool ba = ta<600 && ta>250;
x =- 5000;
y =- 5000;
if(bx && by && ba) {
x = txr - txl;
y = tyu - tyd;
tree->Fill();
}
if(jentry%100000 == 0) cout << "processing " << jentry << endl;
}
tree->Write();
opf->Close();
}
5. 运行流程:从“定义”到“执行”¶
在 ROOT 交互式终端中,操作逻辑如下:
- 加载定义:
.L ppac1.C
- 实例化(Instantiation):
ppac是自定义数据类型,t是该类型的对象实例。- 关键机制:运行此行时,构造函数自动运行。它自动执行了
Init()。此时t内部的变量(如F8PPACRawData)已经与磁盘数据连接完毕。 - 如果没有参数(root文件名),默认去打开生成该类时的原始文件(run0005.root)
- 如果指定参数:
ppac t("run0006.root"),则打开指定的文件run0006.root。
ppac t
(ppac &) @0x107db80a8
- 调用方法:
- 调用内置成员函数
Show,快速打印第 13 个事件的原始数据。
t.Show(13);
======> EVENT:13
beamTrig = 1
must2Trig = 0,
0, 0, 0, 0, 0, 0, 0
PPACF5 = 63.2696,
-7.9987, -418.7, -410.1, 75.8291, 62.9437,
-8.3079, -381.3, -389.9, 63.184, 72.3041,
-10.2965, 231.3, 239.9, 58.0741, 64.6791,
-9.92849, 268.7, 260.1, 58.4691
PPACF8 = 4.79569,
-39.693, -1750.7, -1742.1, 43.7375, 4.19174,
-34.1586, -1713.3, -1721.9, 54.5781, 3.61046,
-32.6964, -1250.7, -1242.1, 58.1063, 4.50848,
-31.5826, -1213.3, -1221.9, 51.2145
PPACF9 = 1.18403,
26.8555, -18.7, -10.1, 423.968, 0.852346,
-1000, 18.7, 10.1, 406.757, -1000,
18.8675, 681.3, 689.9, 415.817, 2.42226,
18.6448, 718.7, 710.1, 420.787
FPPosition = 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, -1000, -1000, -1000, -1000,
0, 0, 0, 0
F5PPACRawData = 608,
1220, 734, 663, 540, 0,
0, 0, 0, 0, 634,
1269, 709, 797, 445, 0,
0, 0, 0, 0
F8PPACRawData = 870,
903, 402, 878, 298, 0,
0, 0, 0, 0, 881,
932, 525, 807, 368, 0,
0, 0, 0, 0
F9PPACRawData = 3016,
2988, 3086, 2719, 2927, 0,
0, 0, 0, 0, 3044,
2978, 2855, 2649, 2887, 0,
0, 0, 0, 0
F10PPACRawData = 3008,
2931, 2807, 2955, 2763, 0,
0, 0, 0, 0, 2878,
2901, 2632, 2840, 2850, 0,
0, 0, 0, 0
F11PPACRawData = 3031,
3123, 2917, 2713, 3554, 0,
0, 0, 0, 0, 3090,
2854, 2938, 2568, 3465, 0,
0, 0, 0, 0
F3PlaRawData = 1643,
1507, 0, 0
F7PlaRawData = 785,
733, 0, 0
F11PlaRawData = 2329,
2116, 0, 0
F11ICRawData = 0,
1418, 1348, 1358, 1299, 1229,
0, 0, 0, 0
TOF = -1000,
-1000, -1000, -1000, -1000, -1000,
-1000, -1000
AoQ = 7.03884,
-1000, -1000, -1000, 7.03884, -1000,
-1000, -1000, 6.85817, -1000, -1000,
-1000, 6.8509, -1000, -1000, -1000,
7.03884, -1000, -1000, -1000
- 调用对象
t内部封装的Loop方法,开始事件处理循环。
t.Loop();
processing 0 processing 100000 processing 200000 processing 300000 processing 400000 processing 500000
TCanvas *c1 = new TCanvas;
h1->Draw();
c1->Draw();
TFile *ipf1 = new TFile("ppac.root");
TTree *tree1 = (TTree*) ipf1->Get("tree");
tree1->Draw("x:y>>(1000,-800,800,1000,-800,800)","","colz");
c1->Draw();
TChain:处理多文件数据链¶
在物理实验中,数据通常分散在多个 run 文件中(如 run0001.root 到 run0010.root)。
TChain 对象是一个包含相同结构 TTree 的多个 ROOT 文件的列表。
由于 TChain 类继承自 TTree 类,在任何需要 TTree* 指针的地方(包括 ppac 类的构造函数),都可以直接使用 TChain* 指针。
组装 TChain 示例:
TChain *chain = new TChain("tree"); // "tree" 是文件内部的 TTree 名称
chain->Add("run0001.root");
chain->Add("run0002.root");
chain->Add("run0003.root");
// TChain 可直接调用 TTree 的方法,例如跨文件画图:
// chain->Draw("beamTrig");
由于 ppac 类已经定义好了固定的数据结构,只要其他文件的内部 TTree 结构与 ppac 内定义的完全一致,我们就可以直接复用这个类来分析新文件,而无需重新生成代码。
使用 TChain 处理文件链¶
结合第 4 节的内容,将整个 TChain 传给分析类,实现自动化批处理。
root [0] .L ppac.C
// ... (此处省略定义 chain 和 Add 文件的代码)
root [1] ppac t(chain); // 实例化:将包含了多个文件的链条传给构造函数
root [2] t.Loop(); // 自动跨文件遍历整个链条的所有事件
机制解析:此时类内部的 fChain 指针指向整个文件链。在 Loop() 循环内部调用的 LoadTree(entry) 函数,会在当前文件读取完毕时,自动、无缝地打开下一个文件并更新地址状态。
总结¶
- 封装性:
ppac类将数百个变量和Init、Loop等方法封装在一起,形成一个独立的数据处理单元。 - 可扩展性:用户可以在
ppac.h中添加成员变量/函数,在ppac.C中实现逻辑。 - 自动化:通过构造函数机制,实现了“实例化即绑定数据”,极大简化了分析流程。
作业:¶
- 用MakeClass方法读入1.2作业中生成的ROOT文件,将实验变量(已进行walk修正,幅度增益修正)的
tL,tR,AL,AR,转换成4096道的ADC和TDC参数保存成新的ROOT文件。 - 读入新的ROOT文件,用
itL,itR等数字化参数选择合适的变量范围计算tx等参数。 - 对数字化参数进行连续化重复上一步,对比计算结果。