5 лекция Изучение программ, обеспечивающих накопление экспериментальных данных, их обработку и анализ, графическое представление ROOT Элементы С++ Структура пакета ROOT Принятые обозначения, типы данных Скрипты: неименованные именованные Преобразование файлов PAW в ROOT-файлы ROOT PAW реализован на Fortran. К середине 90-х этот язык программирования начинает устаревать В это же время набирает силу парадигма объектно-ориентированного программирования и язык программирования C++. Создание ускорителя LHC требует программного обеспечения, которое будет способно справиться с колоссальными объемами данных В 1994 году сотрудники Европейской организации ядерных исследований (CERN) Рене Бран и Фонс Ридмайкерс приступают к созданию программного пакета, реализованного на принципах ООП. Итогом этой работы становится программный пакет ROOT An Object Oriented Data Analysis Framework Объектно-ориентированная среда для анализа данных В настоящее время ROOT стал практически стандартом программного обеспечения для современных ускорительных экспериментов ROOT: справочная информация Web-сайт проекта http://root.cern.ch ано в о енд нию м о р ек р очт е кп Руководство пользователя http://root.cern.ch/root/doc/RootDoc.html Справочное руководство (описание классов ROOT) http://root.cern.ch/root/Reference.html Раздел HOWTO’s http://root.cern.ch/root/HowTo.html ООП и классы С++ Общая идея объектно-ориентированного программирования: моделирование окружающего мира как совокупности объектов, взаимодействующих друг с другом Поддержка ООП в С++ реализуется с помощью классов Класс — это тип данных, определяемый пользователем Класс представляет собой модель реального объекта в виде данных и функций для работы с этими данными Функции класса называются методами, а данные — полями Принадлежность метода конкретному классу обозначается так: MyClass::DoSomething DoSomething это метод класса MyClass Объект — это конкретный экземпляр, представитель данного класса Указатели При работе с объектами часто используются указатели Указателем называется переменная, в которой хранится адрес памяти, по которому располагается другая переменная Создание и определение указателя часто осуществляется с помощью операции new Создадим объект класса MyClass и указатель pointer на этот объект MyClass *pointer = new MyClass(…); Обращение к методам класса через указатель производится с помощью операции "->". Предположим, класс MyClass имеет метод DoSomething(…) Тогда обращение к этому методу через указатель pointer осуществляется следующим образом: pointer->DoSomething(…); Структура ROOT ROOT реализован как набор библиотек классов, обеспечивающих необходимую функциональность для работы с гистограммами, функциями, графиками, деревьми и т. д. В состав ROOT входит также интерпретатор CINT, который воспринимает команды ROOT и выражения С/C++ Два варианта использования ROOT работа в программе root.exe включение библиотек классов ROOT в собственные программы Принятые в ROOT обозначения Имена классов начинаются с T Переменные типа «не класс» заканчиваются на _t Fill(), Draw() Константы начинаются с k fIntegral Методы начинаются с прописной Int_t Поля начинаются с f TF1, TFile kRed Глобальные переменные начинаются с g gStyle Машинно-независимые типы данных В ROOT используются машинно-независимые типы данных Наиболее употребляемые типы: Char_t знаковый символьный 1 байт Int_t знаковый целый 4 байта Float_t вещественный 4 байта Double_t вещественный 8 байт Bool_t логический (0 — ложь, 1 — истина) ROOT: начало работы Чтобы запустить ROOT наберите в консоли $ root версия ROOT дата компиляции и тип ОС приглашение командной строки Завершается сеанс работы командой .q root[] .q C/C++ интерпретатор CINT CINT воспринимает три типа команд 1. Собственно команды CINT начинаются с "." 2. C/C++ выражения в соответствии с синтаксисом языка вывести список возможных команд .L <filename> загрузить файл filename .x <filename> загрузить и выполнить файл filename .? Int_t a = 8 a++ Int_t b=a*2 3. Команды SHELL начинаются с ".!" .! pwd Пример простейшей сессии: построение гистограммы значений, распределенных по Гауссу root [0] TH1F *h1 = new TH1F("h1","Random gauss",100,-2,2) root [1] h1->FillRandom("gaus",1000) root [2] h1->Draw() <TCanvas::MakeDefCanvas>: created default TCanvas with name c1 Скрипты: неименованные скрипты В ROOT существует два типа скриптов: именованные и неименованные Неименованный скрипт представляет собой простую последовательность команд. Тело скрипта должно быть заключено в фигурные скобки Пример простейшего скрипта. Файл MyMacro.C { #include <iostream> using namespace std; for (Int_t i=0; i<10; i++) { cout<<i<<endl; } } Чтобы выполнить неименованный скрипт в интерактивной сессии root [] .x MyMacro.C ROOT будет искать скрипт в текущей директории, а также в директории $ROOTSYS/macros Можно задать полный путь к файлу, например root [] .x /home/user1/macros/MyMacro.C Скрипты: именованные скрипты Чтобы иметь возможность включить в тело скрипта функцию, следует пользоваться именованными скриптами Пример именованного скрипта, содержащего функцию drawhist(). Файл MyNamedMacro.C void drawhist() { TH1F *h1 = new TH1F("h1","simplest histo",10,0,10); h1->Fill(3,3); h1->Fill(4,2); h1->Fill(5,1); h1->Draw(); } Чтобы выполнить функцию drawhist(), следует сначала загрузить скрипт в память ROOT, затем вызвать функцию root [] .L MyNamedMacro.C root [] drawhist() Преобразование HBOOK/PAW в ROOT Утилита h2root, входящая в состав дистрибутива ROOT автоматически преобразует гистограммы, ntuples, содержащиеся в HBOOK-файле, в соответствующие объекты ROOT и записывает их в новый файл. Синтаксис утилиты h2root (в консольной строке ОС Linux) $ h2root <hbook file> <root file> hbook file имя файла HBOOK root file имя выходного ROOT-файла 6 лекция Гистограммы в ROOT Создание и заполнение гистограмм Рисование гистограмм, параметры рисования Немного о TCanvas Сложение, умножение, деление гистограмм Прочие аспекты работы с гистограммами: Клонирование Интеграл и нормировка Ребиннинг Фитирование гистограмм Гистограммы в ROOT Гистограммы в ROOT реализованы как иерархия классов, наследующих корневому классу TH1 Наиболее употребляемым на практике классом является TH1F, это класс одномерных гистограмм, на содержимое бина (столбца) которых отводится 4 байта (максимальная точность 7 знаков) ROOT поддерживает также двумерные и трехмерные гистограммы Создание гистограммы Общий синтаксис создания гистограммы TH1F *h1 = new TH1F("HistName","Histogram title",Nbins,xmin,xmax) Создается гистограмма (объект класса TH1F) и указатель h1 на этот объект 5 параметров, передаваемых конструктору гистограммы HistName имя гистограммы, без пробелов Histogram title заголовок гистограммы Nbins число бинов в гистограмме (целая величина) xmin, xmax диапазон изменения гистограммируемой величины Внесение значений в гистограмму Для внесения значений в гистограмму используется метод Fill(value), где value — значение, которое требуется занести h1->Fill(24.7) Метод Fill(value) увеличивает содержимое бина, к которому принадлежит значение value, на единицу Чтобы внести значение с некоторым весом w, воспользуйтесь методом Fill(value, w) Внести значение 7.2 трижды: h1->Fill(7.2,3) Вес может быть отрицательной величиной Рисование гистограммы, опции рисования Для рисования гистограммы используется метод Draw(): h1->Draw() В методе Draw() можно задавать различные опции рисования Например, h1->Draw("C") С — провести кривую через значения бинов P — нарисовать маркеры для каждого значения E — нарисовать погрешности значений Опции можно совмещать, безо всяких пробелов: h1->Draw("CP") Чтобы нарисовать две гистограммы на одном рисунке, следует задать опцию same для второй: h1->Draw() h2->Draw("same") Пример создания, заполнения и рисования гистограммы Создадим гистограмму TH1F *h1 = new TH1F("h1", "Example histo",16,-4,4) Заполним гистограмму, последовательно применяя методы Fill(value) и Fill(value, w) h1->Fill(-2.3) h1->Fill(-0.9) h1->Fill(0,3) h1->Fill(1.1,2) h1->Fill(2.7) Нарисуем гистограмму: h1->Draw() Информация, выводимая по умолчанию заголовок гистограммы окно стат. информации h1 имя объекта Entries число вхождений Mean среднее значение RMS оценка стандартного отклонения NB RMS не является среднеквадратичным значением! Поле для рисования canvas Поле, на которое выводятся графические объекты в ROOT, называется canvas (класс TCanvas) Объект, например гистограмма, рисуется на текущем активном canvas. Если canvas не существует, то он создается автоматически и имеет по умолчанию имя c1 Чтобы разделить canvas на несколько частей, можно воспользоваться методом TCanvas::Divide(k,l), где k и l число разбиений по горизонтали и вертикали соответственно Чтобы выбрать, на какой части canvas’а следует рисовать объект, следует применить метод TCanvas::cd(n), где n — номер части. Разделы нумеруются слева направа, сверху вниз Пример. Сохранение изображения на диск Пример TCanvas *MyC = new TCanvas ("MyC", "Test canvas", 1); MyC->Divide(2,2) MyC->cd(1) h1->Draw() Чтобы сохранить текущее изображение на canvas на диск, следует выбрать курсором мыши File menu/Save As… и далее указать желаемый формат файла и его имя Сложение, деление и умножение гистограмм Методы Add(), Divide() и Multiply() позволяют складывать, делить и умножать гистограммы. Чтобы добавить к гистограмме h1 гистограмму h2 h1->Add(h2) Можно добавить гистограмму с некоторым весом w h1->Add(h2,w) Вычесть одну гистограмму из другой h1->Add(h2,-1) Складывать, делить и умножать можно только гистограммы с одинаковым числом бинов! Деление и умножение осуществляется аналогично h1->Divide(h2) h1->Multiply(h2) Прочие аспекты работы с гистограммами Создать идентичную копию (клон) гистограммы TH1F *h1_clone = (TH1F*)h1->Clone() Копия будет иметь то же имя, чтобы его изменить h1_clone->SetTitle("h1_clone") Получить интеграл гистограммы h1->Integral() Нормировка гистограммы на величину norm Double_t scale = norm/h1->Integral() h1->Scale(norm) Ребиннинг гистограммы h1->Rebin(N) По умолчанию сливаются два соседних бина Дать имена X и Y осям гистограммы h1->GetXaxis()->SetTitle("X axis title") h1->GetYaxis()->SetTitle("Y axis title") Фитирование гистограмм: FitPanel Фитирование с помощью GUI: FitPanel Чтобы запустить FitPanel, кликните правой кнопкой мыши по линии гистограммы и выберите пункт FitPanel выбор фитирующей функции различные опции фита опции рисования ползунок для варьирования диапазонов фита произвести фит Вывод результатов фита Вывод результатов фита происходит в окне ROOT Пример типичного вывода (частично) NO. NAME 1 Constant 2 Mean 3 Sigma VALUE ERROR 1.61450e+01 7.33397e-01 -3.80369e-02 3.24946e-02 8.81972e-01 3.13356e-02 NO номер параметра NAME имя параметра VALUE найденное значение параметра ERROR погрешность значения Команды фитирования Для фитирования используется метод TH1F::Fit() Фитирование встроенной функцией h1->Fit("gaus") Встроенные функции gaus функция Гаусса: f(x) = p0*exp(-0.5*((x-p1)/p2)^2)) exp экспонента: f(x) = exp(p0+p1*x) polN полином степени N: f(x) = p0 + p1*x + p2*x2 +...pN*xN landau функция Ландау Фитирование функцией, определенной пользователем Функции в ROOT реализованы классом TF1 Создание функции x*sin(x), определенной на интервале (0,10) Создание функции с параметрами TF1 *f2 = new TF1("f2","[0]*sin(x)*exp(-[1]*x)",0,10) [0] и [1] — свободные параметры функции Чтобы фитировать гистограмму TF1 *f1 = new TF1("f1","x*sin(x)",0,10) h1->Fit(f1) Доступ к результатам фита Double_t chi2 = f1->GetChisquare() Double_t par1 = f1->GetParameter(0) Double_t par1_error = f1->GetParError(0) 7 лекция Гистограммы в ROOT (продолжение) Сохранение гистограмм на диск работа с файлами Чтение гистограмм из файла Двумерные гистограммы Различные возможности рисования Графики опции рисования график с погрешностями значений Сохранение гистограмм на диск Чтобы сохранить гистограмму (или другие объекты) в файл, следует, прежде всего, создать файл (или открыть существующий) Работу с файлами обеспечивает класс TFile Создание файла (объекта класса TFile) TFile f("histos.root","new") histos.root имя файла new или create recreate update read создать файл; если файл с таким именем уже существует, он не будет открыт создать файл; если файл с таким именем уже существует, он будет перезаписан открыть файл для записи; если файла с таким именем не существует, он будет создан открыть файл для чтения (по умолчанию) Работа с файлами: ROOT-директория Файл после создания становится текущей ROOT-директорией Изначальная текущая директория — сессия ROOT Последний созданный файл есть текущая директория Глобальная переменная, указывающая на текущую директорию Показать текущую директорию gDirectory gDirectory->pwd() или .pwd Показать содержимое текущей директории gDirectory->ls() или .ls Сменить директорию TFile::cd() Закрыть файл TFile::Close() Работа с файлами Пример root [] .pwd Current directory: Rint:/ root [] TFile f1("file1.root","recreate") root [] .pwd Current directory: file1.root:/ root [] TFile f2("file2.root","recreate") root [] .pwd Current directory: file2.root:/ root [] f1.cd() root [] .pwd Current directory: file1.root:/ root [] f1.Close() root [] .pwd Current directory: Rint:/ Сохранение гистограмм в файл Чтобы осуществить запись гистограммы в файл, используется метод TH1F::Write() Пример записи гистограммы TFile f("histos.root","new") TH1F *h1 = new TH1F("hgaus","histo from a gaussian",100,-3,3) h1->FillRandom("gaus",10000) h1->Write() Чтобы записать все объекты в текущей директории Чтение гистограммы (объекта) f->Write() TFile::Get("name") возвращает указатель на объект с именем name Пример TFile f("histos.root") //открываем файл для чтения TH1F *h = (TH1F*)f.Get("hgaus") //берем указатель на hgaus Необходим cast к нужному типу Двумерные гистограммы Работа с двумерной гистограммой аналогична одномерному случаю Основной класс TH2F При создании гистограммы следует указать число бинов как по оси X, так и по оси Y, а также соответствующие диапазоны изменения величин TH2F *h2 = new TH2F("h2","h2 title",NbinsX,xmin,xmax,NbinsY,ymin,ymax) При заполнении следует передавать два значения (и, опционально, вес) h2->Fill(Xvalue,Yvalue) h2->Fill(Xvalue,Yvalue,w) Рисование гистограммы осуществляется точно также h2->Draw() По умолчанию 2D-гистограмма изображается как «облако» точек, плотность которого пропорциональна содержимому клетки Пример работы с 2D-гистограммой TH2F *h1 = new TH2F("h1","2D histo",20,-10,10,20,-10,10) h1->FillRandom("gaus") h1->Draw() Параметры рисования Методу Draw() гистограмм. можно передавать различные опции рисования LEGO — нарисовать трехмерное изображение в виде столбцов SURF — нарисовать ячеистую поверхность TEXT — напечатать значения содержимого клеток h1->Draw("LEGO") gStyle->SetPalette(1) h1->Draw("SURF1") h1->Draw("TEXT") Графики Работа с графиками обеспечивается классом TGraph Для создания графика нужно определить два массива, содержащих n значений абсцисс и ординат точек. Пример. График функции y(x)=10*sin(x+0.2) Int_t n = 20; Double_t x[n], y[n]; for (Int_t i=0; i<n; i++) { x[i] = i*0.1; y[i] = 10*sin(x[i]+0.2); } TGraph *gr1 = new TGraph (n, x, y); Рисование графика gr1->Draw("ACP") Графики. Опции рисования Различные опции рисования для графиков A нарисовать координатные оси L провести через точки ломаную C провести плавную кривую через точки графика * нарисовать значок звездочки в каждой точке P нарисовать маркер текущего стиля в каждой точке Изменить стиль маркера можно командной gr1->SetMarkerStyle(N) Изменить цвет маркера gr1->SetMarkerColor(M) Изменить цвет соединяющей линии gr1->SetLineColor(M) Изменить ширину соединяющей линии gr1->SetLineWidth(K) Изменить стиль соединяющей линии gr1->SetLineStyle(L) Свойства линии и маркера можно редактировать в режиме GUI, кликнув правой кнопкой мыши по кривой и выбрав SetLineAttributes Таблицы цветов и стилей маркеров Таблица цветов ROOT Таблица стилей маркеров Варианты рисования gr1->SetMarkerStyle(22) gr1->SetMarkerColor(4) gr1->SetLineColor(2) gr1->Draw("ACP") gr1->SetLineColor(4) gr1->SetMarkerColor(4) gr1->Draw("AL*") Два графика на одной картинке Чтобы нарисовать два графика на одном и том же canvas, следует опустить параметр "A" у второго графика Пример скрипта { Int_t n = 20; Double_t x[n], y[n], x1[n], y1[n]; for (Int_t i=0; i<n; i++) { x[i] = i*0.5; y[i] = 5*cos(x[i]+0.2); x1[i] = i*0.5; y1[i] = 5*sin(x[i]+0.2); } TGraph *gr1 = new TGraph(n,x,y); TGraph *gr2 = new TGraph(n,x1,y1); TCanvas *c1 = new TCanvas("c1","Two Graphs",200,10,600,400); gr1->SetLineColor(4); gr1->Draw("AC*"); gr2->SetLineWidth(3); gr2->SetMarkerStyle(21); gr2->SetLineColor(2); gr2->Draw("CP"); } График с погрешностями Для графиков с погрешностями используется класс TGraphErrors Пример скрипта { Int_t n = 10; Float_t x[n] = {-.22,.05,.25,.35,.5,.61,.7,.85,.89,.95}; Float_t y[n] = {1,2.9,5.6,7.4,9,9.6,8.7,6.3,4.5,1}; Float_t ex[n] = {.05,.1,.07,.07,.04,.05,.06,.07,.08,.05}; Float_t ey[n] = {.8,.7,.6,.5,.4,.4,.5,.6,.7,.8}; TGraphErrors *gr = new TGraphErrors(n,x,y,ex,ey); gr->SetTitle("TGraphErrors Example"); gr->SetMarkerColor(4); gr->SetMarkerStyle(21); gr->Draw("ALP"); } График с погрешностями Результат работы скрипта 8 лекция Деревья Создание Добавление ветвей Сохранение в файл Просмотр содержимого Чтение данных дерева Анализ данных Критерии выборки Деревья Дерево (tree) в ROOT является аналогом ntuple в PAW В ROOT деревья реализуются классом TTree Напомним: ntuple можно рассматривать как таблицу, каждая строка которой соответствует одному вхождению (событию), а столбцы — конкретным переменным Для дерева столбцы такой таблицы называются ветвями (branch) Ветви реализуются классом TBranch Принципиальное отличие дерева от ntuple заключается в том, что содержимое ntuple ограничено данными типа float. Ветви дерева могут содержать переменные других типов, вплоть до объектов Чаще всего каждому вхождению соответствует физическое событие (реальное или смоделированное). Однако существуют и другие варианты организации дерева (например, заполнение по трекам) Класс TNtuple — это TTree, ограниченное значениями типа Float_t Создание дерева и ветвей Создать объект класса TTree Конструктору дерева передается два параметра t1 имя дерева Simple tree заголовок дерева Чтобы добавить к дереву ветвь TTree::Branch t1.Branch("px", &px, "px/F") Три параметра, определяющие ветвь TTree t1("t1", "Simple Tree") Имя ветви Адрес, по которому будет считываться значение переменной. Напоминание: & — операция взятия адреса в С Тип листа в формате имя/тип. Наиболее употребляемые типы F Float_t, I Int_t Чтобы занести значения в дерево, используется метод TTree::Fill Создание простейшего дерева Скрипт, создающий простое дерево и записывающий его в файл tree1.root { TFile f("tree1.root", "recreate"); //создаем файл TTree t1("t1", "Simple Tree"); //создаем дерево Float_t px, py, pz; //определяем необходимые переменные Int_t ev; t1.Branch("px", &px, "px/F"); //создаем три ветви t1.Branch("py", &py, "py/F"); //содержащие значения Float_t t1.Branch("pz", &pz, "pz/F"); t1.Branch("ev", &ev, "ev/I"); //и одну со значениями Int_t for (Int_t i=0; i<10000; i++) { //заполнение дерева в цикле gRandom->Rannor(px,py); pz = px*px + py*py; ev = i; t1.Fill(); //по команде Fill значения переменных } //заносятся в дерево t1.Write(); //записываем дерево в файл } Информация о дереве Вывести общую информацию о дереве t1->Print() root [] t1->Print() информация о дереве в целом ****************************************************************************** *Tree :t1 *Entries : * : : Simple Tree 10000 : Total = * 163008 bytes : Tree compression factor = File Size = 126349 * 1.28 * ****************************************************************************** *Br 0 :px *Entries : информация о ветвях *Baskets : : px/F 10000 : Total * Size= 40675 bytes File Size = 1 : Basket Size= 32000 bytes Compression= 29710 * 1.08 * *............................................................................* ... *Br 3 :ev *Entries : *Baskets : : ev/I 10000 : Total * Size= 40675 bytes File Size = 1 : Basket Size= 32000 bytes Compression= 11231 * 2.85 * *............................................................................* Информация об i-ом вхождении в дерево Вывести все значения, записанные в i-ом вхождении (событии) t1->Show(i) root [] t1->Show(151) ======> EVENT:151 px = -2.81273 py = -0.944246 pz = 8.80302 ev = 151 Просмотр содержимого дерева с помощью TBrowser Чтобы запустить TBrowser, создайте объект этого типа: root[] TBrowser b ветви дерева t1 дерево t1 Чтение дерева Прежде всего, следует описать переменные, в которые будут считываться значения Затем указать адреса переменных, в которые будут считываться ветви с помощью метода TTree::SetBranchAddress SetBranchAddress("px",&px) Два параметра метода Имя ветви Адрес переменной, в которую следует записывать считанные данные Общее число вхождений в дерево TTree::GetEntries() Чтение переменных происходит по команде TTree::GetEntry(i) i — номер вхождения, которое необходимо считать Следующий скрипт иллюстрирует процесс чтения дерева Чтение дерева. Пример скрипта Скрипт, считывающий данные дерева t1, сохраненного в файле tree1.root { TFile *f = new TFile("tree1.root"); TTree *t1 = (TTree*)f->Get("t1"); Float_t px, py, pz; Int_t ev; t1->SetBranchAddress("px",&px); t1->SetBranchAddress("py",&py); t1->SetBranchAddress("pz",&pz); t1->SetBranchAddress("ev",&ev); TH2F *hpxpy = new TH2F("hpxpy","py vs px",30,-3,3,30,-3,3); Int_t nentries = (Int_t)t1->GetEntries(); for (Int_t i=0; i<nentries; i++) { t1->GetEntry(i); hpxpy->Fill(px,py); } hpxpy->Draw(); } Результат выполнения скрипта По выполнении скрипта будет нарисована двумерная гистограмма Анализ данных дерева: метод TTree::Draw Чтобы построить гистограмму значений какой-либо переменной дерева t1->Draw("px") Чтобы построить двумерное распределение значений переменных t1->Draw("px:pz") В методе Draw() можно указывать определенные критерии построения Построить распределение значений pz только для тех событий, в которых значение px было меньше 0.5 t1->Draw("pz", "px<0.5") Построить распределение для событий, удовлетворяющих некоторому условию t1->Draw("var1", "iscut==0"); Можно задавать сложные критерии, используя С-операторы && и || t1->Draw("var1", "iscut1==0 && iscut2==0");