3.1 DSSD Energy Calibration¶

DSSD 探测器¶

  • Detectors: S4

  • 128(Pie) x 128(Ring)

  • Pie: $1.4^{\circ}$/条, HV=-110V

  • Ring: 0.43 mm 条宽,~0.03 mm 条间距, HV=0V

  • 厚度1014 $\mu$m

放射源¶

  • $\alpha-$Sources: $^{232}U$

  • $\alpha$粒子从Pie面入射,考虑放射源表面的金膜和Si前表面的死层后,最终沉积在Si里的能量分别为 $5.5658$ MeV, $6.1748$ MeV, $6.6708$ MeV, $8.6931$ MeV.

  • 使用下图中标红色的4个峰进行刻度.

  • 数据: s4.root
  • 数据是用Digital modules的自触发模式记录的, 数据截取了pie和ring的前48条信息。
 Int_t pe[48];//Pie 0-48
 Int_t re[48];//Ring 0-48
In [2]:
TFile *f = new TFile("s4.root");
TTree *tree = (TTree*)f->Get("tree");
TCanvas *c1 = new TCanvas;
In [3]:
tree->Draw("pe[0]>>hx0(1000, 0, 2000)");//单条
tree->Draw("pe[1]>>hx1(1000, 0, 2000)");//单条
TH1I *hx0 = (TH1I*)gROOT->FindObject("hx0");
TH1I *hx1 = (TH1I*)gROOT->FindObject("hx1");
hx0->SetLineColor(kGreen);
hx1->Draw();
hx0->Draw("same");
c1->Draw();
In [4]:
tree->Draw("pe>>(1000,0,2000)");//所有条pe[0-48]累积
c1->Draw();
In [5]:
tree->Draw("re[0]>>hy0(1000, 0, 2000)");//单条
tree->Draw("re[1]>>hy1(1000, 0, 2000)");//单条
TH1I *hy0 = (TH1I*)gROOT->FindObject("hy0");
TH1I *hy1 = (TH1I*)gROOT->FindObject("hy1");
gPad->SetLogy();
hy0->SetLineColor(kGreen);
hy1->Draw();
hy0->Draw("same");
c1->Draw();

TSpectrum¶

TSpectrum 用于一维谱分析中的几个核心问题:本底估计、平滑、反卷积和寻峰。在刻度问题中,最直接相关的是本底估计和寻峰。(zhihuanli.github.io)

创建对象时需要先给出允许搜索的最大峰数,例如: 创建对象时需要先给出允许搜索的最大峰数,例如:

TSpectrum *s = new TSpectrum(500);

这个数字不是实际峰数,而是允许搜索的上限。(zhihuanli.github.io)

Background¶

用于本底估计的接口可以写成:

TH1 *TSpectrum::Background(const TH1 *h,
                           Int_t niter = 20,
                           Option_t *option = "");

其中 niter 控制本底的平滑程度。一般来说,niter 越大,本底越平滑,也越容易压低;但本底减除不能过度,否则会侵入真实峰区,甚至在峰区附近产生不合理的负值。这里的原则是让本底足够平滑,同时尽量不破坏真实峰形。

先由 Background 构造本底,再从原谱中减去本底,得到更适合后续寻峰的谱。

In [7]:
TFile *f = new TFile("gamma.root");
TH1F *h0 = (TH1F*)f->Get("h0");

TSpectrum *s = new TSpectrum(500);
TH1F *hbg = (TH1F*)s->Background(h0, 30, "same");
TH1F *hpeak  = (TH1F*)h0->Clone("hh");
hpeak->Add(hpeak, hbg, 1, -1);
hpeak->SetName("hpeak");
c1->Clear();
h0->SetLineColor(kBlack);
hbg->SetLineColor(kRed);
hpeak->SetLineColor(kBlue);
hpeak->Draw();
h0->Draw("same");
hbg->Draw("same");
gPad->SetLogy();
c1->Draw();

Search¶

用于自动找峰的接口可以写成:

Int_t TSpectrum::Search(const TH1 *hin,
                        Double_t sigma = 2,
                        Option_t *option = "",
                        Double_t threshold = 0.05);

其中 sigma 反映待寻找峰的大致宽度,threshold 是一个相对阈值,用于丢弃低于最高峰一定比例的小峰。减小 threshold 会明显增加候选峰数,因此它控制的不是“真峰还是假峰”的绝对分界,而是“候选峰集”的大小。

In [9]:
Double_t *xpeaks = nullptr;
Double_t *ypeaks = nullptr;

Int_t nfound = s->Search(hpeak, 2, "", 0.01);

xpeaks = s->GetPositionX();
ypeaks = s->GetPositionY();

cout << nfound << endl;
for(int i=0; i<nfound; i++)
    cout << i << ": " << int(xpeaks[i]) 
         << ", " << ypeaks[i] << endl;
hpeak->Draw();
c1->Draw();
18
0: 332, 984052
1: 135, 786091
2: 101, 664539
3: 322, 538447
4: 67, 355974
5: 288, 331300
6: 239, 171454
7: 688, 161288
8: 1217, 167250
9: 843, 152510
10: 356, 139327
11: 265, 136479
12: 968, 127081
13: 71, 117777
14: 946, 100460
15: 406, 53913
16: 762, 46709.3
17: 379, 38620.6

std::vector¶

自动找峰之后,候选峰的个数并不固定,因此不适合用固定长度数组存放。这里使用 std::vector,把每一条谱上找到的候选峰位收集起来。它和数组一样可以按下标访问,但长度可变,更适合寻峰结果。

常用写法如下:

#include <vector>
#include <algorithm> 
using namespace std;

vector<Double_t> pe;
pe.push_back(993);
pe.push_back(1079);

for (int i=0; i<pe.size(); i++) {
    cout << i << " " << pe[i] << endl;
}

for (auto it = pe.begin(); it != pe.end(); ++it) {
    cout << *it << endl;
}

在这一节,vector<Double_t> 用来保存某一条 Pie 谱上全部候选峰的位置,供后续匹配和拟合使用。

std::map 与 std::multimap¶

找到候选峰之后,下一步不是按返回顺序直接使用,而是要按某个更有物理意义的量重新组织。这里用到 map 和 multimap。它们都会按 key 自动排序,其中 map 的 key 不能重复,而 multimap 允许多个相同 key。

  • 在 map 或 multimap 中插入元素可用 insert(make_pair(key, value)),也可用 emplace(key, value);后者直接在容器内构造元素,写法更简洁,在解释环境中也通常更稳。

基本形式可以写成:

#include <map>
using namespace std;

map<Int_t, Double_t> m1;
multimap<Int_t, Double_t> mm1;

// 插入元素可用 insert(make_pair(key, value))
m1.insert(make_pair(762, 121.78));

// 也可用 emplace(key, value)
mm1.emplace(1560, 1079.0);
mm1.emplace(1550,  993.0);
mm1.emplace(1494, 1147.0);

//常见操作包括 `insert`、`find`、正向遍历和反向遍历:

```cpp
auto im1 = m1.find(762);
if(im1 != m1.end()) {
    cout << im1->first << " " << im1->second << endl;
}

for(auto it = mm1.begin(); it != mm1.end(); ++it) {
    cout << it->first << " " << it->second << endl;
}

for(auto it = mm1.rbegin(); it != mm1.rend(); ++it) {
    cout << it->first << " " << it->second << endl;
}

在刻度问题中,最自然的做法是把峰高作为 key、峰位作为 value,写成 multimap<Int_t, Double_t>。这样即使多个峰高度接近甚至相同,也都能保留下来;再通过反向遍历,就能按峰高从大到小提取候选峰。

寻峰函数 peaks.C¶

为了把自动找峰写成一个可复用步骤,可以把它整理成一个函数。输入是直方图名字,输出是一个 vector<Double_t>;其中峰位按峰高从大到小保存。函数内部用 TSpectrum::Search 寻峰,用 multimap 完成“峰高—峰位”的重排,并把峰位标到图上。

TH1F *h = NULL, *hb = NULL;

void peaks(TString hname, vector<Double_t> &pe, Double_t thres=0.05, int backsub=1)
{
  Int_t nfound;
  Double_t *xpeaks = NULL, *ypeaks = NULL;
  pe.clear();

  multimap<Double_t, Double_t> me;   // key: ypeaks, value: xpeaks

  h = (TH1F*)gROOT->FindObject(hname);
  if(!h) {
    cout << "Histogram " << hname << " not found." << endl;
    return;
  }

  TSpectrum *sp = new TSpectrum(500);

  if(backsub) {
    hb = (TH1F*)sp->Background(h, 80);
    h->Add(h, hb, 1, -1);
  }

  nfound = sp->Search(h, 2, "", thres);

  TPolyMarker *pm = (TPolyMarker*)h->GetListOfFunctions()->FindObject("TPolyMarker");
  if(pm) {
    pm->SetMarkerStyle(32);
    pm->SetMarkerColor(kGreen);
    pm->SetMarkerSize(0.4);
  }

  xpeaks = sp->GetPositionX();
  ypeaks = sp->GetPositionY();

  for(int j=0; j<nfound; j++) {
    me.emplace(ypeaks[j], xpeaks[j]);

    TLatex *tex = new TLatex(xpeaks[j], ypeaks[j], Form("%.0f", xpeaks[j]));
    tex->SetTextFont(13);
    tex->SetTextSize(0.02);
    tex->SetTextAlign(12);
    tex->SetTextAngle(90);
    tex->SetTextColor(kRed);
    tex->Draw();
  }

  for(auto ie = me.rbegin(); ie != me.rend(); ++ie) {
    cout << ie->second << " " << ie->first << endl;
    pe.push_back(ie->second);
  }

  delete sp;
}

这一步非常关键。TSpectrum 负责“找到所有可能峰”,vector 负责“保存个数不固定的结果”,multimap 负责“按峰高重新组织候选峰”。三者在这里第一次组成一个完整的分析单元。

In [12]:
cout<<hpeak->GetName()<<endl;
hpeak
In [13]:
gROOT->ProcessLine(".L peaks.C");
vector<Double_t> ge;
peaks("hpeak",ge);
gPad->SetLogy(0);
c1->Draw();
332.7 984184
135.9 786218
101.7 664645
322.9 538568
67.1 356220
288.1 331398
239.1 171570
1217.1 167254
688.3 161309
843.9 152524
356.1 139455
265.9 136580
968.3 127103
71.9 117995
946.3 100477
406.7 53964

DSSD Energy Calibration¶

对某一条 Pie 谱执行:

In [2]:
TFile *f = new TFile("s4.root");
TTree *tree = (TTree*)f->Get("tree");
tree->Draw("pe[1]>>hx1(1000, 0, 2000)");//单条
gROOT->ProcessLine(".L peaks.C");//可以在代码内部直接使用,等价于[ROOT >] .L peaks.C
vector<Double_t> pe;
Double_t e[4] = {5.5658, 6.1748, 6.6708, 8.6931};//alpha energy
Info in <TCanvas::MakeDefCanvas>:  created default TCanvas with name c1
In [9]:
peaks("hx1", pe);
gPad->SetLogy(0);
c1->Draw();
941 1750.78
1079 1558.87
993 1549.81
1147 1493.93
957 1358.79
1429 604.009
1047 479.849
277 238.949
1405 133.009

此时得到的是一组候选峰位。对示例谱而言,较显著的峰位包括 941、957、993、1079、1147、1429 等。这里真正的问题不是“有没有找到峰”,而是“哪 4 个峰与已知的 4 个 $\alpha$ 能量对应”。

在这条谱中,993、1079、1147、1429 是最可能与已知$\alpha$ 能量匹配的一组,但仅凭峰高并不能完全确定顺序。原因有两点:一是这些峰的高度相差不大;二是当分辨较差时,941 和 957 还可能并成一个峰。因此,不能把寻峰结果直接等同于最终刻度峰。

候选峰与已知能量的匹配¶

对当前谱,可以先采用一个针对谱形的经验策略:取前 6 个显著峰,按峰位排序,再取其中 4 个与已知能量做匹配。 这样得到的峰位是 993、1079、1147、1429。对应的思路是:先利用寻峰和峰高排序缩小候选范围,再结合谱形和已知刻度点选出最合理的一组。

更一般的策略是:把 TSpectrum 给出的候选峰与 4 个已知 $\alpha$ 能量进行组合,分别做线性拟合,比较每一组的 $\chi^2/\mathrm{NDF}$,取最小的一组作为实际的峰位—能量组合。这种策略不依赖某一条谱的具体形状,更适合推广到全部 48 条。

In [11]:
sort(pe.begin(),pe.begin()+6); 
Double_t mpe[4], cpe[4];//peak,count
for(int i=0; i<4; i++) {
    mpe[i] = pe[2+i];
    cpe[i] = hx1->GetBinContent(hx1->FindBin(mpe[i]));
    cout << mpe[i] << " " << cpe[i] << endl;
}
993 1549.81
1079 1558.87
1147 1493.93
1429 604.009

粗刻度¶

选定 4 个候选峰位以后,可以先做一轮峰位—能量线性拟合,得到粗刻度关系:

In [14]:
TGraph *gr = new TGraph(4, mpe, e);
gr->Draw("A*");
gr->Fit("pol1");
TF1 *f1 = gr->GetFunction("pol1");
c1->Draw();
****************************************
Minimizer is Linear / Migrad
Chi2                      =  4.40562e-05
NDf                       =            2
p0                        =     -1.56557   +/-   0.0168397   
p1                        =   0.00717874   +/-   1.43506e-05 

通过残差图评估拟合结果¶

Residual Plot¶

得到线性拟合后,还要快速检查拟合是否自然。这里引入残差

$$ e_i = y_i - f(x_i|\alpha). $$

如果模型和匹配都合理,残差应当围绕 0 随机分布,不应表现出明显的系统趋势。

In [17]:
Double_t p0 = f1->GetParameter(0);
Double_t p1 = f1->GetParameter(1);
Double_t dpe[4];
for(int i=0; i<4; i++) 
    dpe[i] = p0 + p1 * mpe[i] - e[i];
TGraph *rgr = new TGraph(4,mpe,dpe);
rgr->Draw("A*");
c1->Draw();

Gaussian Fit¶

粗刻度之后,不能直接把 TSpectrum 返回的峰位当作最终峰心。更合理的做法是对每个峰分别进行高斯拟合,用拟合得到的均值作为最终峰位。

在每个峰附近设置初值:

In [ ]:
hx1->Draw();
c1->Draw();
In [21]:
double par[4][3];//peak,sigma,chi2/ndf
double ge[4];
TF1 *fg[4];
TFitResultPtr fr;
for(int i=0; i<4; i++) {
    fg[i] = new TF1(Form("fg%d", i), "gaus");
    fg[i]->SetParameters(cpe[i], mpe[i], 4);//constant,mean, sigma
    fr = hx1->Fit(fg[i], "SQ+", "", mpe[i]-5, mpe[i]+8);//superimpose TF1 to hx1
    par[i][0] = fg[i]->GetParameter(1);
    par[i][1] = fg[i]->GetParameter(2);
    par[i][2] = fr->Chi2()/fr->Ndf();
    ge[i] = par[i][0];
    cout << Form("peaks = %4.1f, sigma = %.2f, chi2/ndf = %.2f", 
                 par[i][0], par[i][1], par[i][2]) << endl;
}
c1->Draw();
peaks = 992.9, sigma = 3.98, chi2/ndf = 12.92
peaks = 1078.3, sigma = 4.00, chi2/ndf = 11.31
peaks = 1147.3, sigma = 3.98, chi2/ndf = 13.36
peaks = 1429.0, sigma = 4.83, chi2/ndf = 1.27
In [23]:
TGraph *gr1 = new TGraph(4,ge,e);
gr1->Draw("A*");
gr1->Fit("pol1");
TF1 *f2 = gr->GetFunction("pol1");
c1->Draw();
****************************************
Minimizer is Linear / Migrad
Chi2                      =  5.94481e-06
NDf                       =            2
p0                        =     -1.55862   +/-   0.00618078  
p1                        =   0.00717354   +/-   5.26767e-06 
In [25]:
p0 = f2->GetParameter(0);
p1 = f1->GetParameter(1);
for(int i=0; i<4; i++) 
    dpe[i] = p0 + p1 * ge[i] - e[i];
TGraph *rgr1 = new TGraph(4, ge, dpe);
rgr1->Draw("A*");
c1->Draw();
In [27]:
tree->Draw(Form("pe[0]*%f+%f>>hx1e(1000, -1, 10)", p1, p0));//单条
hx1e->Draw();
c1->Draw();//存在锯齿,需要在刻度前进行连续化。