Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 42 additions & 128 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,146 +1,60 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## About

ATTPCROOT is a ROOT/FairRoot-based C++ framework for simulation and analysis of Active Target Time Projection Chamber (AT-TPC) detector data. It integrates with FairSoft (provides ROOT, Geant4, VMC) and FairRoot (provides the task/run framework).

## Environment Setup

Before building or running anything, the environment must be loaded. The VSCode terminal auto-sources this on startup:

```bash
source build/config.sh
```

This sets `LD_LIBRARY_PATH`, `ROOTSYS`, `VMCWORKDIR`, `ROOT_INCLUDE_PATH`, and Geant4 data paths. The key install paths on this machine are:
- FairRoot: `~/fair_install/FairRootInstall`
- FairSoft: `~/fair_install/FairSoftInstall`

For CMake configuration (not the build itself), the following env vars are needed:
```bash
export FAIRROOTPATH=~/fair_install/FairRootInstall
export SIMPATH=~/fair_install/FairSoftInstall
```

## Build Commands

```bash
# Configure (from repo root, out-of-source into build/)
cmake -S . -B build -DCMAKE_PREFIX_PATH=~/fair_install/hdf5

# Build (10 parallel jobs)
cmake --build build -j10

# Build a specific target
cmake --build build --target AtSimulationData -j10

# Run all unit tests
cd build && ctest -V

# Run a specific test binary directly
./build/tests/AtSimulationDataTests
./build/tests/AtGeneratorsTests
./build/tests/AtToolsTests
```

Tests are built by default (`BUILD_TESTS=ON`). Test binaries are placed in `build/tests/`.

## Code Formatting

The project uses `clang-format-17` with the config in `.clang-format` (based on LLVM style, 3-space indent, 120-char column limit). Format on save is configured in VSCode. To format manually:
ATTPCROOT is a ROOT/FairRoot-based C++ framework for simulation and analysis of Active Target Time Projection Chamber (AT-TPC) detector data.

## Documentation

Full developer documentation lives in `docs/`. See [docs/index.md](../docs/index.md) for the full map. Quick topic links:

| Topic | File |
|-------|------|
| First-time install | [tooling/installation.md](../docs/tooling/installation.md) |
| Daily use (build/test) | [tooling/daily-use.md](../docs/tooling/daily-use.md) |
| Testing patterns | [tooling/testing.md](../docs/tooling/testing.md) |
| Contributor guide | [contributing/guide.md](../docs/contributing/guide.md) |
| Adding a new module | [contributing/new-module.md](../docs/contributing/new-module.md) |
| Code style | [contributing/code-style.md](../docs/contributing/code-style.md) |
| Module overview | [reference/modules.md](../docs/reference/modules.md) |
| Data model | [reference/data-model.md](../docs/reference/data-model.md) |
| Branch I/O contracts | [reference/branch-io-contracts.md](../docs/reference/branch-io-contracts.md) |
| Simulation pipeline | [subsystems/simulation-pipeline.md](../docs/subsystems/simulation-pipeline.md) |
| Reconstruction pipeline | [subsystems/reconstruction-pipeline.md](../docs/subsystems/reconstruction-pipeline.md) |
| Event generators | [subsystems/generators.md](../docs/subsystems/generators.md) |
| Pulse shape analysis | [subsystems/psa.md](../docs/subsystems/psa.md) |
| Energy loss | [subsystems/energy-loss.md](../docs/subsystems/energy-loss.md) |

## Quick Reference: Build & Test

```bash
clang-format-17 -i <file>
# Or format all changed files:
scripts/formatAll.sh
source build/config.sh # load environment (do this first)
cmake --build build -j10 # build everything
cd build && ctest -V # run all unit tests
```

Static analysis uses `clang-tidy` (config in `.clang-tidy`): modernize-* and cppcoreguidelines-* checks.

## Architecture

The framework follows FairRoot's task pipeline pattern: data flows through a chain of `FairTask` subclasses, persisted as ROOT TClonesArrays in a TTree.

### Module Overview

| Module | Purpose |
|--------|---------|
| `AtSimulationData` | MC truth data: `AtStack`, `AtMCTrack`, `AtMCPoint`, `AtVertexPropagator` |
| `AtGenerators` | FairGenerator subclasses for beam/reaction/decay event generation |
| `AtDetectors` | Geant4 sensitive detector implementations (AT-TPC, GADGET-II, SpecMAT, etc.) |
| `AtDigitization` | Converts MC points → simulated pad signals (`AtClusterize`, `AtPulse`) |
| `AtUnpack` | Unpacks raw experimental data (GET/GRAW, HDF5, ROOT formats) |
| `AtData` | Core data classes: `AtRawEvent`, `AtEvent`, `AtHit`, `AtPad`, `AtTrack` |
| `AtMap` | Pad mapping between electronics channels and detector geometry |
| `AtParameter` | FairRuntimeDb parameter containers |
| `AtReconstruction` | PSA, pattern recognition, fitting tasks |
| `AtTools` | Utilities: energy loss (CATIMA), kinematics, space charge, hit sampling |
| `AtAnalysis` | High-level analysis tasks and `AtRunAna` |
| `AtS800` | S800 spectrograph data handling |
| `AtEventDisplay` | ROOT Eve-based event display |

### Data Flow

**Simulation pipeline** (macros in `macro/Simulation/`):
1. `FairPrimaryGenerator` with an `AtReactionGenerator` subclass → particle stack
2. Geant4/VMC transport → `AtMCPoint` hits in sensitive volumes
3. `AtClusterizeTask` → electron clusters from ionization
4. `AtPulseTask` → simulated pad signals (`AtRawEvent`)

**Reconstruction pipeline** (macros in `macro/Unpack_*/` and `macro/Analysis/`):
1. `AtUnpackTask` → `AtRawEvent` (raw pad traces)
2. `AtFilterTask` → filtered `AtRawEvent`
3. `AtPSAtask` (Pulse Shape Analysis) → `AtEvent` with `AtHit` objects
4. `AtSampleConsensusTask` / `AtPRAtask` → `AtPatternEvent` with `AtTrack` objects
5. `AtMCFitterTask` / `AtFitterTask` → fitted tracks with kinematics

### Key Design Patterns

**FairTask subclasses**: Each processing step is a `FairTask`. They retrieve input branches via `TClonesArray*` from `FairRootManager` in `Init()` and process them in `Exec()`.

**AtReactionGenerator**: Abstract base for all reaction generators. Subclasses implement `GenerateReaction()`. The `ReadEvent()` method is `final` and handles the beam/reaction event alternation via `AtVertexPropagator`. Generators can be chained for sequential decays using `SetSequentialDecay(true)`.

**AtVertexPropagator**: Singleton (`AtVertexPropagator::Instance()`) that communicates vertex, momentum, and kinematics between chained generators and downstream tasks. Alternates beam/reaction events via `EndEvent()`. Use `ResetForTesting()` in unit tests.

**PSA (Pulse Shape Analysis)**: `AtPSA` is the abstract base. Concrete implementations (`AtPSAMax`, `AtPSAFull`, `AtPSADeconv`, etc.) extract hits from pad traces. `AtPSAComposite` allows chaining PSA methods.

**Energy loss**: `AtELossModel` is the base; `AtELossCATIMA` wraps the CATIMA library (fetched automatically if not installed). Used via `AtELossManager`.

### Adding a New Module Library

Each module's `CMakeLists.txt` follows this pattern:
```cmake
set(LIBRARY_NAME MyModule)
set(SRCS file1.cxx file2.cxx)
set(DEPENDENCIES ATTPCROOT::AtData ROOT::Core ...)
set(TEST_SRCS MyModuleTest.cxx)
attpcroot_generate_tests(${LIBRARY_NAME}Tests SRCS ${TEST_SRCS} DEPS ${LIBRARY_NAME})
generate_target_and_root_library(${LIBRARY_NAME} LINKDEF ${LIBRARY_NAME}LinkDef.h SRCS ${SRCS} DEPS_PUBLIC ${DEPENDENCIES})
```
## Code-Writing Rules

ROOT dictionary generation requires a `*LinkDef.h` file listing every class that needs ROOT reflection. Every class with `ClassDef` in its header must appear in the LinkDef.
These rules apply whenever editing or adding C++ code in this repo.

**LinkDef streamer suffix** — the suffix after the class name controls whether ROOT generates a full I/O streamer:
### LinkDef Streamer Suffixes

- `ClassName +;` — generates a full streamer. Use this **only** for classes that are written to a ROOT file (i.e. stored as a branch in a `TClonesArray` or `TTree`). Examples: `AtHit`, `AtEvent`, `AtRawEvent`, `AtMCPoint`.
- `ClassName -!;` — registers the class for reflection (usable in interpreted macros, usable as a pointer type) but **suppresses the streamer**. Use this for every class that is never written to disk: tasks, algorithms, models, samplers, etc. Examples: `AtELossModel`, `AtSpaceChargeModel`, `AtSample` subclasses.
Every class in a `*LinkDef.h` file must use the correct suffix:

Generating an unnecessary streamer bloats the dictionary and binary with unused I/O code, so the default should be `-!` unless disk persistence is actually required.
- `ClassName +;` — generates a full I/O streamer. **Only** for classes written to a ROOT file (stored in a `TClonesArray` or `TTree` branch). Examples: `AtHit`, `AtEvent`, `AtRawEvent`, `AtMCPoint`.
- `ClassName -!;` — reflection only, no streamer. Use for **everything else**: tasks, algorithms, models, samplers. Examples: `AtPSAMax`, `AtELossModel`, `AtFitterTask`.

**C++ vs ROOT typedefs** — use plain C++ types (`bool`, `int`, `double`, `std::string`) in all classes that are not written to a ROOT file. Only use ROOT's portability typedefs (`Bool_t`, `Int_t`, `Double_t`, etc.) in classes that are persisted to disk, where ROOT's cross-platform I/O guarantees matter. Mixing ROOT types into purely algorithmic or task classes is unnecessary overhead.
Default to `-!` unless disk persistence is actually required.

### Unit Tests
### C++ vs ROOT Typedefs

Tests use Google Test, placed alongside source files (e.g., `AtVertexPropagatorTest.cxx` in `AtSimulationData/`). Register them in the module's `CMakeLists.txt` via `attpcroot_generate_tests()`. Tests must not access external files or network resources.
- Non-persisted classes (tasks, algorithms, models): use `bool`, `int`, `double`, `std::string`.
- Persisted data classes only: use `Bool_t`, `Int_t`, `Double_t`, etc.

### Macros
### Test Isolation

ROOT macros (`.C` files) in `macro/` are the primary user interface for running simulations and analyses. They are not compiled into libraries—they run interpreted by ROOT. Integration tests live in `macro/tests/`.
Unit tests must not access external files or network resources. Hardcode test data inline.

## Contributing

- PRs must target the `develop` branch and apply cleanly (fast-forward, no merge commits).
- Commit messages: present imperative mood, ≤72 characters summary.
- All PRs are checked by `clang-format`, `clang-tidy`, and the unit test suite.
- PRs target the `develop` branch; fast-forward only (no merge commits).
- Commit messages: present imperative mood, ≤72 characters.
- All PRs must pass `clang-format`, `clang-tidy`, and unit tests.
2 changes: 1 addition & 1 deletion .github/workflows/CI-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
container: anthoak13/attpcroot-deps
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6

- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v6
- name: Run clang-format
uses: DoozyX/clang-format-lint-action@v0.17
# These are optional (defaults displayed)
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.pdf
*.swp
*.root
*.log
Expand Down Expand Up @@ -57,3 +58,6 @@ macros/data
.DS_Store
.nfs*
compiled/E20009Analysis/.NabinWorkDir/

.claude/plans/*

60 changes: 60 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# AGENTS.md

ATTPCROOT is a ROOT/FairRoot-based C++ framework for simulation and analysis of Active Target Time Projection Chamber (AT-TPC) detector data.

## Documentation

Full developer documentation lives in `docs/`. See [docs/index.md](docs/index.md) for the full map. Quick topic links:

| Topic | File |
|-------|------|
| First-time install | [tooling/installation.md](docs/tooling/installation.md) |
| Daily use (build/test) | [tooling/daily-use.md](docs/tooling/daily-use.md) |
| Testing patterns | [tooling/testing.md](docs/tooling/testing.md) |
| Contributor guide | [contributing/guide.md](docs/contributing/guide.md) |
| Adding a new module | [contributing/new-module.md](docs/contributing/new-module.md) |
| Code style | [contributing/code-style.md](docs/contributing/code-style.md) |
| Module overview | [reference/modules.md](docs/reference/modules.md) |
| Data model | [reference/data-model.md](docs/reference/data-model.md) |
| Branch I/O contracts | [reference/branch-io-contracts.md](docs/reference/branch-io-contracts.md) |
| Simulation pipeline | [subsystems/simulation-pipeline.md](docs/subsystems/simulation-pipeline.md) |
| Reconstruction pipeline | [subsystems/reconstruction-pipeline.md](docs/subsystems/reconstruction-pipeline.md) |
| Event generators | [subsystems/generators.md](docs/subsystems/generators.md) |
| Pulse shape analysis | [subsystems/psa.md](docs/subsystems/psa.md) |
| Energy loss | [subsystems/energy-loss.md](docs/subsystems/energy-loss.md) |

## Quick Reference: Build & Test

```bash
source build/config.sh # load environment (do this first)
cmake --build build -j10 # build everything
cd build && ctest -V # run all unit tests
```

## Code-Writing Rules

These rules apply whenever editing or adding C++ code in this repo.

### LinkDef Streamer Suffixes

Every class in a `*LinkDef.h` file must use the correct suffix:

- `ClassName +;` — generates a full I/O streamer. **Only** for classes written to a ROOT file (stored in a `TClonesArray` or `TTree` branch). Examples: `AtHit`, `AtEvent`, `AtRawEvent`, `AtMCPoint`.
- `ClassName -!;` — reflection only, no streamer. Use for **everything else**: tasks, algorithms, models, samplers. Examples: `AtPSAMax`, `AtELossModel`, `AtFitterTask`.

Default to `-!` unless disk persistence is actually required.

### C++ vs ROOT Typedefs

- Non-persisted classes (tasks, algorithms, models): use `bool`, `int`, `double`, `std::string`.
- Persisted data classes only: use `Bool_t`, `Int_t`, `Double_t`, etc.

### Test Isolation

Unit tests must not access external files or network resources. Hardcode test data inline.

## Contributing

- PRs target the `develop` branch; fast-forward only (no merge commits).
- Commit messages: present imperative mood, ≤72 characters.
- All PRs must pass `clang-format`, `clang-tidy`, and unit tests.
45 changes: 36 additions & 9 deletions AtTools/AtELossBetheBloch.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,24 @@ void AtELossBetheBloch::BuildSpline(double E_min_MeV, double E_max_MeV, int nPoi
dXdE[i] = (dedxValues[i] > 0) ? 1.0 / dedxValues[i] : 1e10;

fdXdE = tk::spline(energies, dXdE);

// Build Ω²(E) spline: Bohr range variance accumulated from 0 to E.
// ω²_unit [MeV²/mm] = K · z² · (Z/A) · ρ[g/mm³] · mₑc²
// (kK is in MeV cm²/mol; ×100 → MeV mm²/mol; density g/cm³ → g/mm³ = /1000; combined: /10)
double z = IsElectron() ? 1.0 : fPart_q;
double omega2_unit = kK * z * z * (static_cast<double>(fMat_Z) / fMat_A) * (fDensity / 10.0) * kM_e;

// Build spline of the integrand dΩ²/dE = 1/|dEdx|³, then integrate segment-by-segment using
// Simpson's rule (exact for cubics, O(h⁴)) rather than the O(h²) trapezoidal rule.
std::vector<double> integrandValues(nPoints);
for (int i = 0; i < nPoints; ++i)
integrandValues[i] = (dedxValues[i] > 0) ? 1.0 / (dedxValues[i] * dedxValues[i] * dedxValues[i]) : 0.0;
tk::spline integrand(energies, integrandValues);

std::vector<double> rangeVar(nPoints, 0.0);
for (int i = 1; i < nPoints; ++i)
rangeVar[i] = rangeVar[i - 1] + omega2_unit * integrand.integrate(energies[i - 1], energies[i]);
fRangeVariance = tk::spline(energies, rangeVar);
}

double AtELossBetheBloch::GetdEdx_formula(double energy) const
Expand Down Expand Up @@ -199,23 +217,32 @@ double AtELossBetheBloch::GetEnergy(double energyIni, double distance) const

double AtELossBetheBloch::GetElossStraggling(double energyIni, double energyFin) const
{
double dx_mm = GetRange(energyIni, energyFin);
if (dx_mm <= 0)
if (energyIni <= energyFin)
return 0;

// Bohr approximation: σ² = K·z²·(Z/A)·ρ·Δx[cm]·mₑc²
double z = IsElectron() ? 1.0 : fPart_q;
double dx_cm = dx_mm * 0.1;
double sigma2 = kK * z * z * (static_cast<double>(fMat_Z) / fMat_A) * fDensity * dx_cm * kM_e;
return std::sqrt(sigma2);
double omega2 = GetRangeVariance(energyIni) - GetRangeVariance(energyFin);
if (omega2 <= 0)
return 0;
return std::abs(GetdEdx(energyFin)) * std::sqrt(omega2);
}

double AtELossBetheBloch::GetdEdxStraggling(double energyIni, double energyFin) const
{
double dx_mm = GetRange(energyIni, energyFin);
if (dx_mm <= 0)
return 0;
return GetElossStraggling(energyIni, energyFin) / dx_mm;
double omega2 = GetRangeVariance(energyIni) - GetRangeVariance(energyFin);
if (omega2 <= 0)
return 0;
return std::abs(GetdEdx(energyFin)) * std::sqrt(omega2) / dx_mm;
}

double AtELossBetheBloch::GetRangeVariance(double energy) const
{
if (energy <= fRangeVariance.get_x_min())
return 0;
if (energy >= fRangeVariance.get_x_max())
return fRangeVariance(fRangeVariance.get_x_max());
return fRangeVariance(energy);
}

} // namespace AtTools
4 changes: 3 additions & 1 deletion AtTools/AtELossBetheBloch.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class AtELossBetheBloch : public AtELossModel {
int fMat_A; // target mass number (g/mol)
double fI_MeV; // mean excitation energy in MeV

tk::spline fdXdE; // spline of dx/dE vs. energy; integral cached for O(log n) GetRange
tk::spline fdXdE; // spline of dx/dE vs. energy; integral cached for O(log n) GetRange
tk::spline fRangeVariance; // spline of Ω²(E) [mm²] vs. energy; accumulated Bohr range variance

public:
/**
Expand Down Expand Up @@ -57,6 +58,7 @@ class AtELossBetheBloch : public AtELossModel {
virtual double GetEnergy(double energyIni, double distance) const override;
virtual double GetElossStraggling(double energyIni, double energyFin) const override;
virtual double GetdEdxStraggling(double energyIni, double energyFin) const override;
virtual double GetRangeVariance(double energy) const override;

private:
bool IsElectron() const { return std::abs(fPart_mass - kM_e) < 0.01; }
Expand Down
Loading
Loading