Analiza eventów, monitory pól

Teraz czas napisać pierwszą analizę. Użyjemy naszego zaawansowanego generatora. Kod główny będzie wyglądał  tak:

void glauber_ana() {
  auto run       = new Hal::AnalysisManager();
  auto source    = new HalOTF::Source(10000);
  auto generator = new CustomGenerator;
  TH2D h("spec", "spec", 1, -1, 1, 1, 0, 1);
  h.SetBinContent(1, 1, 1);
  generator->SetSpiecies(h, Hal::Const::PionPlusPID());
  generator->SetFixMult(100);
  generator->SetA(197);

  source->AddEventGenerator(generator);
  // source->Register();
  run->SetOutput("/opt/temp/data.root");
  run->SetSource(source);
  HalOTF::Reader* reader = new HalOTF::Reader();
  run->AddTask(reader);

  Hal::EventAna* ana = new Hal::EventAna();
  ana->SetFormatOption(Hal::EventAna::EFormatOption::kReaderAccess);
  Hal::EventFieldMonitorX prop1(Hal::DataFieldID::Event::EMc::kB + Hal::DataFieldID::ImStep);
  Hal::EventFieldMonitorX prop2(Hal::DataFieldID::Event::EBasic::kTracksNo + Hal::DataFieldID::ImStep);
  prop1.SetXaxis(100, 0, 20);
  prop2.SetXaxis(200, 0, 1500);
  Hal::EventFieldMonitorXY prop(Hal::DataFieldID::Event::EMc::kB + Hal::DataFieldID::ImStep,
                                Hal::DataFieldID::Event::EBasic::kTracksNo + Hal::DataFieldID::ImStep);
  prop.SetXaxis(100, 0, 20);
  prop.SetYaxis(200, 0, 1500);
  ana->AddCutMonitor(prop);
  ana->AddCutMonitor(prop1);
  ana->AddCutMonitor(prop2);
  run->AddTask(ana);
  run->Init();
  run->Run();
}

Nasze makro rozpoczyna się od stworzenia menedżera analizy Hal::AnalysisManager. Następnie tworzymy źródło danych - HalOTF::Source. W liniach 5-9 konfigurujemy nasz generator. Następnie dodajemy go do source.

W linii 15 tworzymy reader. HAL może łykać dowolne dane ale musi je sobie tłumaczyć na swój format. Format HAL-a musi dziedziczyc bo Hal::Event. Format który jest produkowany przez nasz generator niestety nie dziedzyczy po tej klasie. Dlatego tworzymy sobie klasę HalOTF::Reader - to specjalna klasa, która konwertuje zderzenia i zapisuje je do branchu "HalEvent.", jeśli naszemu taskowi do analizy nie powiemy jakiego formatu ma używać i w jaki sposób (opcje zaawansowane rzadziej używane) to taki task poszuka właśnie branchu "HalEvent." i z niego będzie pobierał dane. Ponieważ task powinien widzieć branch już na początku analizy, reader zasadniczo powinien być pierwszym taskiem w ciągu analiz.  Domyślnie dane generowane przez reader są zapisywane w branch "wirtualnym" tj. takim który jest widoczny podczas analizy, ale nie jest zapisywany w pliku wyjściowym.

Kolejne linie to tworzenie i konfiguracja naszej analizy. Interesują nas analizy zderzeń więc używamy klasy EventAna, robi ona tylko pętle po zderzeniach. W linii 19 konfigurujemy opcję odczytu jako reader - od nowszej wersji (styczeń 2025) ta linia nie jest już potrzebna, gdyż taski domyślnie używają danych z readera.

Następne linie to tworzenie "EventFieldMonitorów" - są to klasy do monitorowania "pól" eventów. Jak nazwa wskazuje zawieraja one histogramy z rozkładami wartości "pól" eventów czyli pól z danymi o zderzeniu takich jak np. parametr zderzenia itd. Parametrami konstruktorów są ID pól (Hal::DataFieldID::). W naszym wypadku chcemy zobaczyć rozkład parametru zderzenia, liczby cząstek w zderzeniu oraz korelację obu tych rozkładów. 

Gdybyśmy analizowali dane czysto MC (np. z generatora zderzeń typu UrQMD to konstruktory tych klas zawierałyby tylko zmienne Hal::DataFieldID::Event::EMc::kB (parametr zderzenia) oraz Hal::DataFieldID::Event::EBasic::kTracksNo (liczba cząstek w zderzeniu). Nasz generator generuje jednak zderzenia w formacie "zespolonym". Format zespolony oznacza, że zderzenie składa się tak naprawdę z dwóch typów zderzeń - część "rzeczywista" to dane zrekonstruowane, a część "urojona" to dane z generator MC. Jeśli chcemy monitorować część MC musimy do ID pól dodać Hal::DataFieldID::ImStep co powoduje odpowiednie przełączenie na część MC. Można zapytać co się stanie jeśli zapomnimy o tym "magicznym" przesunięciu - otóż wtedy HAL spróbuje monitorować po prostu event (zwykle właściwości eventu są kopiowane z części zrekonstruowanej), jeśli dana właściwość będzie to monitor się przełączy, jeśli nie to po prostu monitor zostanie zablokowany. 

Linie 28-30 to dodawanie monitorów pól do analizy. Następnie sama analiza jest dodawa do analysis managera, wywołana zostaje metoda Init i Run która rozpoczyna analizę.

Otwieranie plików

Gdy makro się zakończy możemy zobaczyć że wygenerowany został pewien plik. Można się do jego zawartości dodać na kilka sposób np. poprzez takie makro:

void glauber_show() {
  auto file  = new Hal::AnaFile("/opt/temp/data.root");
  auto h1    = file->GetHistogramPassed(Hal::ECutUpdate::kEvent, 0, 0);
  auto h2    = file->GetHistogramPassed(Hal::ECutUpdate::kEvent, 0, 1);
  auto h3    = file->GetHistogramPassed(Hal::ECutUpdate::kEvent, 0, 2);
  TCanvas* c = new TCanvas();
  c->Divide(2, 2);
  c->cd(1);
  h1->Draw("colz");
  c->cd(2);
  h2->Draw();
  c->cd(3);
  h3->Draw();
}

Funkcja GetHistogramPassed przyjmuje 3 parametry: pierwsze to flaga która mówi jaki rodzaj monitora chcemy pobrać (czy dla eventu czy dla cząstki), drugi mówi którą kolekcję danych chcemy użyć (tu mamy 1 kolekcje więc będzie to zero), trzecia flaga mówi, który numer cut monitora chcemy. Jest też funkcja GetHistogramFailed - zwraca ona histogram z danymi, które zostały wyrzucone z analizy.

Na histogramie widzimy 3 rozkłady: krotność (liczba cząstek) vs parametr zderzenia, parametr zderzenia oraz krotność. Jak widać parametr zderzenia ma rozkłąd trójkątny, nasze założenie odnośnie jego wychodziło z rozważań czysto geometrycznych, ale było dosyć dobre, nawet w zaawansowanych modelach typu UrQMD możemy zobaczyć tego typu rozkład. Pozostałe dwa rozkłady o dziwo jakościowo również dość dobrze opisują to co produkują modele/mierzą eksperymenty.

Zauważyć możemy tutaj, że grupując dane wg krotności możemy w pewnym stopniu grupować dane wg. parametru zderzenia. Nie jest to jednak ścisła zależność bo nasz "wąż" w rozkładzie krotności vs parametr zderzenia jest dosyć szeroki, oznacza to że nawet wybierając określoną krotność będziemy mieli zawsze zderzenia o pewnym rozrzucie parametrów zderzenia. Ale tym zajmiemy się w następnej części tutorialu.