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.