Zapis klasy

Na poprzedniej lekcji zapisywaliśmy dane w TNtuple które jest uproszczonym odpowiednikiem drzew ROOTowych. Teraz przejdziemy do drzew ROOTowych. Jak wspomniałem drzewa umożliwiaja zapis danych w formie bardziej zaawansowanych struktur - tzn. tutaj jedno wejście nie odpowiada strukturze jak w TNtuple ale klasie bądź grupie klas.

Zacznijmy od relatywnie prostego przykładu - chcemy zapisać sobie cząstki typu V0 w formie obiektu TParticle, cząstki takie rozpadają się na dwie córki, które również chcemy sobie zapisać. Przykładowe makro zapisujące je w drzewie może wyglądać tak:

void tree_w(){
    TFile *file  = new TFile("data.root","recreate");
    TTree *tree = new TTree("data","data");
    TParticle *pNeu = new TParticle();
    TParticle *pPos = new TParticle();
    TParticle *pNeg = new TParticle();
    tree->Branch("v0", &pNeu);
    tree->Branch("pos",&pPos);
    tree->Branch("neg",&pNeg);
    for(int i=0;i<100;i++){
        pNeu->SetPdgCode(3122);
        pNeu->SetMomentum(2, 2, 2, 2);
        pPos->SetPdgCode(2212);
        pPos->SetMomentum(1,1,1,1);
        pNeg->SetPdgCode(211);
        pNeg->SetMomentum(1,1,1,1);
        tree->Fill();
    }
    tree->Write();
    file->Close();
}

Natomiast po zapisie nasze drzewo wygląda tak:

Rys. 1. Plikt z drzewem i obiektami klasy TParticle. Pola z wykrzyknikiem nie są fizycznie zapisywane a są wyliczane.

Zauważyć tu można, że mimo iż zapisywaliśmy obiekty tej samej klasy (TParticle) mamy osobne nazwy branchy, co pozwala na łatwą identyfikację który TParticle to która cząstka (V0 czy któraś cząstka-córka). Odczytu takiego drzewa można dokonać w formie makra:

void tree_r(){
    TFile *file  = new TFile("data.root");
    TTree *tree = (TTree*)file->Get("data");
    TParticle *pNeu = nullptr;
    TParticle *pPos = nullptr;
    TParticle *pNeg = nullptr;
    tree->SetBranchAddress("v0", &pNeu);
    tree->SetBranchAddress("pos",&pPos);
    tree->SetBranchAddress("neg",&pNeg);
    for(int i=0;i<100;i++){
        tree->GetEntry(i);
        cout<GetPdgCode()<<" "<GetPdgCode()<<" "<<
                pNeg->GetPdgCode()<<endl;
    }
}

Tablice klonów

Poprzedni przykład zakładał że mamy zawsze 3 cząstki. Co jednak jeśli zapisujemy zderzenie i raz mamy 10,  a raz 20 cząstek? W tym przypadku przydałaby się jakaś tablica z TParticle. W tym przypadku możemy to zrobić na trzy sposoby:

  • zapisać wektor naszych obiektów
  • "zwinąć" nasze obiekty do jakieś klasy reprezentującej zderzenie
  • zapisać nasze obiekty w tablicy klonów

Dwie pierwsze opcje wymagają trochę bardziej złożonych operacji i zostaną omówione później. Zobaczmy jak można więc zapisywać tablice o różnych rozmiarach przy pomocy tablicy klonów:

void DoStuff(TLorentzVector *p){
    p->SetXYZT(gRandom->Gaus(0,1),gRandom->Gaus(0,1),gRandom->Gaus(0,1),0);
}
void tree_wtc(){
    TFile *file  = new TFile("data.root","recreate");
    TTree *tree = new TTree("tree","title");
    TClonesArray *tracks = new TClonesArray("TLorentzVector",100);
    tree->Branch("tracks", &tracks);
    for(int i=0;i<100;i++){
        tracks->Clear();
        Int_t n = (Int_t)(gRandom->Uniform(5,11));
        for(int j=0;j<n;j++){
            TLorentzVector *p = (TLorentzVector*)tracks->ConstructedAt(j);
            DoStuff(p);
        }
        tree->Fill();
    }
    tree->Write();
    file->Close();
}

Odczyt takiego pliku wyglądał będzie następująco:

void tree_wtcR(){
    TFile *file  = new TFile("data.root");
    TTree *tree = (TTree*)file->Get("tree");
    TClonesArray *tracks = nullptr;
    tree->SetBranchAddress("tracks", &tracks);
    for(int i=0;i<100;i++){
        tree->GetEntry(i);
        //liczba jest wypelnaniana przez TTree::GetEntry
        //nie trzeba wiec uzywac wolniejszej metody GetEntries
        Int_t n = tracks->GetEntriesFast();
        for(int j=0;j<n;j++){
            //tablica jest wypelniana przy TTree::GetEntry wiec nie trzeba alokowac
            //pamieci
            TLorentzVector *p = (TLorentzVector*)tracks->UncheckedAt(j);
            p->Print();
        }
    }
    tree->Write();
    file->Close();
}

Można tworzyć oczywiście kilka równoległych tablic klonów w jednym drzewie, przykładowo możemy stworzyć osobno branch na primary i global tracks:

void tree_wtcR2(){
    TFile *file  = new TFile("data.root","recreate");
    TTree *tree = new TTree("tree","title");
    TClonesArray *tracksG = new TClonesArray("TLorentzVector",100);
    TClonesArray *tracksP = new TClonesArray("TLorentzVector",100);
    tree->Branch("tracks_global", &tracksG);
    tree->Branch("tracks_primary", &tracksP);
    for(int i=0;i<100;i++){
        tracksP->Clear();
        tracksG->Clear();
        Int_t n = (Int_t)(gRandom->Uniform(5,11));
        for(int j=0;j<n;j++){
            TLorentzVector *p = (TLorentzVector*)tracksP->ConstructedAt(j);
            DoStuff(p);
        }
        n = (Int_t)(gRandom->Uniform(10,21));
        for(int j=0;j<n;j++){
            TLorentzVector *p = (TLorentzVector*)tracksG->ConstructedAt(j);
            DoStuff(p);
        }
        tree->Fill();
    }
    tree->Write();
    file->Close();
}

TChain

TChain to swojego rodzaju analogia do drzewa ale rozmieszczonego w różnych plikach. Dzięki temu zamiast analizować każdy plik oddzielnie, można je połączyć (bez fizycznego łączenia plików) w jeden wirtualny plik. Przykładowe użycie TChain wygląda tak:

void tree_wtcR3(){
    TChain *tree = new TChain("tree");

    for(int i=1;i<=3;i++){
        tree->AddFile(Form("data_%i.root",i));
    }
    TClonesArray *tracksG = nullptr;
    TClonesArray *tracksP = nullptr;

    tree->SetBranchAddress("tracks_primary", &tracksP);
    tree->SetBranchAddress("tracks_global", &tracksG);


    for(int i=0;iGetEntries();i++){
        tree->GetEntry(i);
        Int_t nPrim = tracksP->GetEntriesFast();
        Int_t nGlob = tracksG->GetEntriesFast();
        if(nPrim>=10)
            cout<<Form("Ev %i Nprim = %i    Nglob = %i",i,nPrim,nGlob)<<endl;
    }
}

 

SetBranchStatus

W przykładzie wyżej analizowaliśmy zderzenia zawierające global i primar tracki, interesowały nas tylko te zderzenia które miały więcej niż 9 primary tracków. Ponieważ alternatywna wersja kodu do podobnej analizy:

void tree_wtcR(){
    TChain *tree = new TChain("tree");

    for(int i=1;i<=3;i++){
        tree->AddFile(Form("data_%i.root",i));
    }
    TClonesArray *tracksG = new TClonesArray("TLorentzVector",10);
    TClonesArray *tracksP = nullptr;

    tree->SetBranchAddress("tracks_primary", &tracksP);
    tree->SetBranchAddress("tracks_global", &tracksG);

    tree->SetBranchStatus("tracks_global*",0);
    tree->SetBranchStatus("tracks_primary*",1);

    for(int i=0;iGetEntries();i++){
        tree->GetEntry(i,0);
        Int_t nPrim = tracksP->GetEntriesFast();
        Int_t nGlob = 0;;

        if(nPrim>=10){
            tree->GetEntry(i,1);
            nGlob = tracksG->GetEntriesFast();
            cout<<Form("Ev %i Nprim = %i    Nglob = %i",i,nPrim,nGlob)<<endl;
        }
    }
}

Kod jest bardzo podobny jednak występują pewne różnice:

  • pojawia się SetBranchStatus, metoda ta ustawia status brancha, branche o statusie 1 są zawsze wczytywane (tzn. gdy zrobimy TChain::GetEntry(i) lub TChain::GetEntry(i,0)), branche o statusie 0 są wczytywane tylko na żądanie (tj. poprzez TChain::GetEntry(i,1))
  • w SetBranchStatus używamy "tracks_global*" gwiazdka oznacza, że zablokowany zostanie odczyt jakichkolwiek danych które  się zaczynaja na "tracks_global*" (bez gwiazdki nie zablokujemy odczytu global tracków), gwiazdka działa trochę jak wyrażenia regularne np. SetBranchStatus("*",0) blokuje odczyt wszystkich branchy
  • alokujemy miejsce pod tracksG (ponieważ gałąź domyślnie nie jest ładowana musimy sami zarezerwować miejsce)
  • gdy mamy więcej niż 9 primary tracków chcemy mieć global tracki - dlatego tutaj ładujemy wszystkie branche metodą TChain::GetEntry(i,1)

Podobną "zabawę" z blokowaniem branchy można przeprowadzić na obiektach TTree.