Make Class czyli trochę inżynieria wsteczna

Make class to ficzer ROOTa który umożliwia stworzenie klasy do odczytu danych na podstawie drzewa ROOTowego. Dzięki temu możemy czytać drzewo bez posiadania kodu klas, które zostały w nim zapisane. Przykładowo spróbujmy rozpakować nasze drzewo z poprzedniej lekcji, zawierało ono dwie tablice TClonesArray (primary i global tracks) z obiektami klasy TLorentzVector. Aby to zrobić możemy użyć następujących komendy

TFile *f = new TFile("drzewo.root");

TTree *t = f->Get("tree");

t->MakeClass("reversed");

Wygeruje ona kod do odczytu drzewa tree. Należy tu jednak zaznaczyć że nie jest to idealne rozwiązane, ale zwykle wystarczające do odczytu danych. Tutaj skupię się na omówieniu jedynie fragmentu wygenerowanego pliku reversed.h.

   static constexpr Int_t kMaxtracks_global = 20;
   static constexpr Int_t kMaxtracks_primary = 10;

   // Declaration of leaf types
   Int_t           tracks_global_;
   UInt_t          tracks_global_fUniqueID[kMaxtracks_global];   //[tracks_global_]
   UInt_t          tracks_global_fBits[kMaxtracks_global];   //[tracks_glInt_t tracobal_]
   TVector3        tracks_global_fP[kMaxtracks_global];
   Double_t        tracks_global_fE[kMaxtracks_global];   //[tracks_global_]
   Int_t           tracks_primary_;
   UInt_t          tracks_primary_fUniqueID[kMaxtracks_primary];   //[tracks_primary_]
   UInt_t          tracks_primary_fBits[kMaxtracks_primary];   //[tracks_primary_]
   TVector3        tracks_primary_fP[kMaxtracks_primary];
   Double_t        tracks_primary_fE[kMaxtracks_primary];   //[tracks_primary_]

   // List of branches
   TBranch        *b_tracks_global_;   //!
   TBranch        *b_tracks_global_fUniqueID;   //!
   TBranch        *b_tracks_global_fBits;   //!
   TBranch        *b_tracks_global_fP;   //!
   TBranch        *b_tracks_global_fE;   //!
   TBranch        *b_tracks_primary_;   //!
   TBranch        *b_tracks_primary_fUniqueID;   //!
   TBranch        *b_tracks_primary_fBits;   //!
   TBranch        *b_tracks_primary_fP;   //!
   TBranch        *b_tracks_primary_fE;   //!

Pierwszy element to deklaracja pól, możemy tu wymienić następujące rodzaje danych:

  • ilość wejść w danym evencie w danej tablicy (tracks_global_,tracks_primary_) tj. rozmiary TClonesArray
  • tablice - odpowiadają one danym zawartym w TLorentzVector (w TClonesArray), można tu wyróżnić
    • wewnętrzne dane roota (*fUniqueID, *fBits)
    • dane zawierające dane fizyczne (*_fP, *_fE), rozbicie tych danych jest wynikiem tego, że ROOT trzyma pędy w TVector3 a energię w polu typu Double_t

Należy tu uważać na pewną pułapkę - TClonesArray samo sobie dostosowuje rozmiar tablicy - tutaj jednak mamy dwa "wbite na stałe" rozmiary tablic kMaxtracks_global i kMaxtracks_primary. Są one wyznaczane na podstawie pierwszego wejścia, jeśli nasze drzewo zawiera wejście, gdzie jest więcej primary/global tracków to makru zabraknie pamięci i się wysypie, dlatego lepiej tę wartość trochę powiększyć.

Druga część wygenerowanego kodu to "podłączanie" się do danych, ponieważ nie używamy TLorentzVector do każdego "liścia" danych podłączamy się "osobno" w wyniku tego kod jest dosyć długi bo podłączamy się osobno do niemal każdego int'a czy double'a w drzewie zamiast do dwóch branchy:

   fChain->SetBranchAddress("tracks_global", &tracks_global_, &b_tracks_global_);
   fChain->SetBranchAddress("tracks_global.fUniqueID", tracks_global_fUniqueID, &b_tracks_global_fUniqueID);
   fChain->SetBranchAddress("tracks_global.fBits", tracks_global_fBits, &b_tracks_global_fBits);
   fChain->SetBranchAddress("tracks_global.fP", tracks_global_fP, &b_tracks_global_fP);
   fChain->SetBranchAddress("tracks_global.fE", tracks_global_fE, &b_tracks_global_fE);
   fChain->SetBranchAddress("tracks_primary", &tracks_primary_, &b_tracks_primary_);
   fChain->SetBranchAddress("tracks_primary.fUniqueID", tracks_primary_fUniqueID, &b_tracks_primary_fUniqueID);
   fChain->SetBranchAddress("tracks_primary.fBits", tracks_primary_fBits, &b_tracks_primary_fBits);
   fChain->SetBranchAddress("tracks_primary.fP", tracks_primary_fP, &b_tracks_primary_fP);
   fChain->SetBranchAddress("tracks_primary.fE", tracks_primary_fE, &b_tracks_primary_fE);

Warto zwrócić uwagę, że wszystkie pola klasy reversed zawierające dane są publiczne - dzięki temu możemy tego kodu użyć w makrze analizującym bez głębszych przeróbek.