Your First CLAS12 Analysis¶
A complete, compilable starter: open a CLAS12 HIPO file, iterate events, keep charged particles, and histogram the vertex-z distribution. Once this runs on your machine, the rest of CLAS12 analysis is just swapping in better cuts and adding banks.
What you need¶
- A CLAS12 HIPO file — any reconstructed output works, e.g.
rec_clas_*.evio.*.hipofrom the CLAS12 validation dataset. - A working
hipo4install (see Installation).
The program¶
#include "reader.h"
#include "twig.h"
#include <cstdio>
int main(int argc, char** argv) {
if (argc < 2) { std::fprintf(stderr, "usage: %s <file.hipo>\n", argv[0]); return 1; }
hipo::reader reader;
reader.open(argv[1]);
// Pull REC::Particle as a banklist so reader.next(list) fills it each event.
auto list = reader.getBanks({"REC::Particle"});
auto& parts = list[0];
// Cache column indices once — ~100x faster than repeated name lookups in the
// inner loop. See Reference / Performance for details.
const int charge_col = parts.getSchema().getEntryOrder("charge");
const int vz_col = parts.getSchema().getEntryOrder("vz");
twig::h1d hvz(120, -15.0, 5.0); // vertex-z histogram, −15 cm … +5 cm
long events = 0, charged = 0;
while (reader.next(list)) {
++events;
for (int row = 0; row < parts.getRows(); row++) {
if (parts.getInt(charge_col, row) == 0) continue; // keep charged tracks
hvz.fill(parts.getFloat(vz_col, row));
++charged;
}
}
std::printf("processed %ld events, %ld charged tracks\n", events, charged);
hvz.print(); // ASCII histogram to the terminal
return 0;
}
Build it against hipo4 via pkg-config (see
Installation — pkg-config):
g++ -std=c++17 first_analysis.cc \
$(pkg-config --cflags --libs hipo4) -o first_analysis
./first_analysis path/to/rec_clas.hipo
What the code is doing¶
- Open the file.
hipo::reader::open()reads the 72-byte file header and the dictionary, so every subsequentreader.next()knows what banks to expect. - Ask for a banklist.
reader.getBanks({"REC::Particle"})returns ahipo::banklist— a vector ofhipo::bankobjects. Inside the loop,reader.next(list)refills every bank inlistfrom the current event. - Cache column indices.
parts.getSchema().getEntryOrder("charge")turns the string name into an integer index once; reusing the integer in the hot loop is roughly 100× faster than passing the string every call. See Performance — Optimization Tips. - Filter charged tracks.
parts.getInt(charge_col, row) == 0skips neutrals (photons, neutrons). Flip the comparison for a neutrals-only selection. - Fill the histogram.
twig::h1dis a tiny built-in histogram class with an ASCIIprint(). For anything beyond eyeballing, see Integration — ROOT RDataFrame.
Common next steps¶
Replace the charge check with a CLAS12-style selection. Three representative cuts, each using cached column indices for the columns they touch:
const int pid_col = parts.getSchema().getEntryOrder("pid");
const int status_col = parts.getSchema().getEntryOrder("status");
int pid = parts.getInt(pid_col, row);
int status = std::abs(parts.getInt(status_col, row));
if (pid == 11 && status >= 2000 && status < 4000) {
// electron detected by the Forward Detector
}
const int pid_col = parts.getSchema().getEntryOrder("pid");
const int chi2pid_col = parts.getSchema().getEntryOrder("chi2pid");
int pid = parts.getInt(pid_col, row);
float chi2pid = parts.getFloat(chi2pid_col, row);
if (std::abs(pid) == 211 && std::abs(chi2pid) < 3.0f) {
// pi+ or pi- passing the timing-based PID cut
}
The CLAS12 status field encodes the detector combination that assigned the particle:
|status| in [2000, 4000) is the Forward Detector and [1000, 2000) is the Forward
Tagger. The sign distinguishes negative/positive detector assignment in some
reconstruction versions — check the CLAS12 analysis note for your pass if the exact
sign matters.
Where to go next¶
- More bank columns → The REC:: bank family
- Joining
REC::Particleto detector banks → Cross-bank linking viapindex - Filter one electron, several pions, a proton … → Recipes — Row filtering
- Scale out across many files → Recipes — Parallel processing