ANASEN_analysis/cross_section/README.md
Vignesh Sitaraman d63a422a41 modified: TrackRecon.C
new folder:   cross_section, this folder allows for cross section calculations for the ANASEN data, a readme and some example files are provided
	modified:   run_tr.sh
2026-10-17 12:38:35 -04:00

197 lines
12 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Cross-section pipeline (ANASEN active target)
This folder turns data into a cross section for any reaction in the ANASEN active target. There are three steps,
one script each, and one config file per study shared by all three:
```
root -l -b -q 'extract_yields.C+("examples/27Al_ap_runA.toml")' # 1. ROOT -> yields_<det>.csv
~/myenv/bin/python -B geometric_efficiency.py examples/27Al_ap_runA.toml # 2. detectors.py -> eps_<det>.csv ~10 s
~/myenv/bin/python -B cross_section.py examples/27Al_ap_runA.toml # 3. -> xs_<det>.csv, xs.png ~3 s
```
Run them in WSL from this folder (`~/ANASEN_analysis/cross_section`). Step 1 needs ROOT; steps 2 and 3 need
`~/myenv/bin/python`, not ROOT.
| file | what it does |
|---|---|
| `extract_yields.C` | **Step 1, the only step that reads the data.** It reads a TH1/TH2, or fills a histogram from a TTree with a cut, and writes a plain CSV histogram. |
| `geometric_efficiency.py` | Step 2: geometric efficiency per bin, a toy Monte Carlo of the reaction, with every hit rule taken from `detectors.py`. |
| `detectors.py` | **The detectors as they are now** (Run A): QQQ3, the four analysed SX3s, the counter, the beam tube and the PCB hole, plus the hit rules. To change the geometry, edit this file. |
| `cross_section.py` | Step 3: reads only CSVs (the yields and an efficiency) and computes σ(E_cm) or dσ/dΩ(θ_cm). |
| `common.py` | What the three scripts share: the config, the beam track E(z), the bins. |
| `examples/` | One commented config per study: `27Al_ap_runA.toml` and `paap_ta_runs_hist.toml`. |
| `examples/reference/` | Barnard 1964 (EXFOR A1113) for p + ⁴He. |
| `inputs/` | The beam track (`beam_tracks.csv`) and the TALYS states (`talys_27Al_ap.csv`). The p + ⁴He track is read from `../eloss_calculations/`. |
| `out/<study>/` | Each study's outputs: `yields_<det>.csv`, `eps_<det>.csv`, `xs_<det>.csv`, `xs.png`. |
## Starting a new study
1. Copy the closest example in `examples/` (excitation function: `27Al_ap_runA.toml`; angular distribution:
`paap_ta_runs_hist.toml`) and give it its own `out_dir`.
2. Fill in `[extract]` for your histogram or tree, then run step 1 and look at `yields_<det>.csv`.
3. Fill in `[reaction]`, `[beam]` and `[bins]`, then run step 2. Check that `eps_<det>.csv` is non-zero where you
expect events.
4. Fill in `[target]`, `[acceptance]` and any `[[reference]]`, then run step 3.
If the detectors have changed, edit `detectors.py`, not the configs.
![27Al(a,p)](out/27Al_ap/xs.png)
## The formula
| mode | result | formula |
|---|---|---|
| `excitation` | σ(E_cm), mb | Y / (N_b · n · dz · ε) |
| `angular` | dσ/dΩ(θ_cm) in one E_cm (or z) window, mb/sr | Y / (N_b · n · dz · ε · ΔΩ) |
- **Y:** the yield in the bin.
- **N_b:** the number of beam particles.
- **n:** target atoms per cm² per mm of path.
- **dz:** the length of beam path over which the beam's E_cm lies in the bin, read off the beam track E(z).
- **ε:** the efficiency of the bin.
## Step 1: `extract_yields.C`, data to CSV
`[extract] source` says where the events are:
- `"hist"`: a TH1 or TH2 in a ROOT file. A TH2 is projected on the axis that holds `variable`, or kept 2D with
`angle_axis` for the angular mode.
- `"tree"`: a ROOT TTree (`file` may be a glob or a list of runs), filled into `bins` after `cut`.
Cuts use `and` / `or` (the macro turns them into `&&` / `||`) and ROOT's TTree::Draw syntax.
**Histogram paths** are `"folder/name"`.
- **They are case-sensitive.** Production files of different vintages spell the detector differently: the 27Al
outputs use `_m27Alax+misc_qqq/...`, the proton runs' `Output_p/Al_protons.root` uses `elastic+misc_QQQ_a/...`.
Put the spelling in `[det_vars]`:
```toml
dets = ["qqq", "sx3"] # the names detectors.py knows
[det_vars.qqq]
DET = "QQQ"
[det_vars.sx3]
DET = "SX3"
[extract]
hist = "elastic+misc_{DET}_a/elastic_VertexReconZ_a_{DET}"
```
- **A name that itself contains `/`** (e.g. `dE3_E_AnodeQQQR_A1C2_(vertex_fix_z/100)=0.000000`) is found too.
- **If the path doesn't exist,** the macro stops and names the closest histogram paths in the file.
The CSV it writes, `<out_dir>/yields_<det>.csv`, is the hand-off between the steps. `cross_section.py` reads nothing
else, so a yield made any other way works too as long as it is in this format:
```
1D: <var>_lo, <var>_hi, counts, variance
2D: <var>_lo, <var>_hi, theta_cm_lo, theta_cm_hi, counts, variance (angular mode)
```
`<var>` is `z` (vertex z, mm), `e_beam` (MeV) or `ecm` (MeV). The bins may be finer than the analysis bins and need
not line up with them. Step 3 shares each one out by overlap.
It is run like this:
```
root -l -b -q 'extract_yields.C+("examples/27Al_ap_runA.toml")'
root -l -b -q 'extract_yields.C+("examples/27Al_ap_runA.toml", "out/elsewhere")' # optional output folder
```
- **Trees:** it fills the histogram with `TTree::Draw` on a `TChain`, so `file` may be a glob or a list of runs.
- **Cuts:** write them with `and` / `or` (e.g. `"det == 1 and e_si > 0.5"`). The macro turns them into `&&` / `||`.
- **TOML:** the macro has a small reader that handles only what these configs use: strings, numbers, one-line lists
and `{start, stop, step}` tables. It stops with an error on anything else.
- **Errors:** a missing file or tree, or a tree with no entries, stops it with exit code 1 rather than writing zeros.
- **One edge case:** an entry exactly on a tree histogram's last upper edge goes to ROOT's overflow.
## Step 2: `geometric_efficiency.py`, ε from the detector geometry
The toy runs once per bin:
- vertices uniform over the bin's path (angular mode: over the window);
- beam energy at each vertex from the nominal track;
- the residual state drawn from fixed weights, or from TALYS state cross sections at the bin's E_cm;
- isotropic CM emission (angular mode: inside the θ_cm bin);
- relativistic two-body kinematics and straight tracks.
The particle the silicon sees can be the ejectile or the recoil (`detect`). θ_cm is always the ejectile's, so with
`detect = "recoil"` the bins are still in the ejectile's CM angle. For p + ⁴He that is the proton's angle, Barnard's
convention, which TrackRecon's `elastic_VertexZ_vs_ThetaCM` histograms also use. The output has one column per
condition, each times the azimuthal coverage:
| column | the particle... |
|---|---|
| `eps_si` | reaches the analysed silicon (solid angle) |
| `eps_si_thr` | ...and has a vertex energy above the Si threshold. This is the ch5 "solid angle" convention: a kinematic cut, not geometry. |
| `eps_counter` | `eps_si`, and it crosses the guard, anode and cathode radii inside the counter |
| **`eps_geometric`** | `eps_counter`, and it clears the beam tube and the PCB hole. **This is the geometric efficiency.** |
Each column has a Monte Carlo error, `<column>_rel_err`. Gas energy loss, Si thresholds after the gas and the counter's
deposit threshold are **not** included, because they are not geometry. That is why the adopted `eps_tube` of
`../scratch/xs_27Al_lowE_2026-10-15` is lower than `eps_geometric`; to use it instead, see the commented
`[acceptance]` block in the example. The angular distribution is isotropic.
## Step 3: `cross_section.py`
`[acceptance] source` picks ε:
- `"geometric"`: step 2's file; `column` chooses which efficiency.
- `"csv"`: any file, matched on a bin edge or interpolated.
- `"value"`: one number for every bin.
- `"none"`: ε = 1, which gives σ_app.
`[[acceptance.extra]]` multiplies in further factors, such as an intrinsic efficiency. The output `xs_<det>.csv` keeps
every intermediate number: the bins, z ranges, dz, counts, σ_app, ε, σ and its errors (stat, ε, total), and the
references. The header lines record every input. The N_b and n uncertainties are a common scale, so the header gives
them as a percentage and they stay out of the error columns.
## The config (TOML)
Paths are relative to the config file, and `~` is the home folder. `{det}` is replaced by each entry of `dets`, and
so is every key of `[det_vars.<det>]`. Commented templates in `examples/`:
- `27Al_ap_runA.toml`: a TH2 from the merged Run A production file, all three steps.
- `paap_ta_runs_hist.toml`: p + ⁴He elastic **straight from a TrackRecon.C production file**, as for 27Al. It reads
the TH2 `elastic_VertexZ_vs_ThetaCM_cath_exgate_a_<DET>` (vertex z × proton θ_cm). `protonAlphaElastic_core` fills
that histogram since 2026-10-16 in every species folder, in four variants: all, `_cath`, `_exgate`, `_cath_exgate`.
- Input: `~/ANASEN_analysis/Output_p/results_run022.root` (run 22 alone, run_tr.sh block 5 with CUTLIST),
N_b = 1.35 pA × 672.65 s = 5.67 × 10⁹, vertex window z = −170 to −53 mm.
- On 2026-10-16 the merged runs 18–22 agreed with a per-hit route to 2–4% in QQQ3 counts and about 3% in dσ/dΩ.
| section | used by | keys |
|---|---|---|
| top | all | `title`, `mode` (`excitation` / `angular`), `dets` (names in `detectors.py`: `qqq`, `sx3`), `out_dir` |
| `[extract]` histogram | 1 | `source = "hist"`, `file`, `hist`, `variable`, `axis` (the axis holding `variable`), `angle_axis` (keep it 2D), `other_range` |
| `[extract]` tree | 1 | `source = "tree"`, `file` (glob or list), `tree`, `variable`, `branch`, `bins`, `cut`, `weight_branch`, `angle_branch` + `angle_bins` |
| `[reaction]` | 2, 3 | `beam_mass_u`, `target_mass_u`; for step 2 also `ejectile_mass_u`, `recoil_mass_u`, `detect`, and `states` *or* `states_csv`; optional `ecm_per_ebeam` |
| `[beam]` | 2, 3 | track: `track_csv` + `track_e_col` (+ `nominal_e_col`, `track_z_col`), *or* `eloss_table` + `e_entrance_mev` + `z_entrance_mm`. N_b: `n_beam` *or* `charge_c` *or* `current_a` + `time_s`; `charge_state`, `n_beam_rel_err`. For Run A runs, `../runlog/beam_exposure.py` gives `n_beam` for any set of runs from the elog's cup readings |
| `[bins]` | 2, 3 | `ecm_edges` (a list or `{start, stop, step}`); angular: `theta_edges_deg` and `window_ecm` *or* `window_z_mm` |
| `[efficiency]` | 2 | `n_per_bin` (default 400 000), `seed` |
| `[target]` | 3 | `pressure_torr`, `temperature_k`, `mole_fraction`, `atoms_per_molecule`, `rel_err`; *or* `n_per_cm2_per_mm` |
| `[yield]` | 3 | `csv`: only to read yields from somewhere other than step 1's output |
| `[acceptance]` | 3 | `source` and its keys (see above), `[[acceptance.extra]]` |
| `[[reference]]` | 3 | `csv`, `x_col`, `y_col`, `label`, `scale`, `dets` |
| `[plot]` | 3 | `yscale`, `xlim`, `ylim`, `xlabel`, `ylabel` |
## Example: p + ⁴He elastic scattering, Ta run 22 (`examples/paap_ta_runs_hist.toml`)
```
root -l -b -q 'extract_yields.C+("examples/paap_ta_runs_hist.toml")'
~/myenv/bin/python -B geometric_efficiency.py examples/paap_ta_runs_hist.toml
~/myenv/bin/python -B cross_section.py examples/paap_ta_runs_hist.toml
```
dσ/dΩ(θ_cm) from the α recoils (`detect = "recoil"`, bins in the proton's θ_cm, Barnard's convention), against
Barnard, Jones & Weil (1964). Histogram: `elastic_VertexZ_vs_ThetaCM_cath_exgate_a_<DET>` (cathode topologies,
graphical α cut, |E_x| < 1 MeV). The merged runs 18–22 gave QQQ3 0.44–0.54 × Barnard at 132–156°; SX3 is at 1–3%.
Not in `detectors.py`: the Ta holder rod (~28% of the azimuth downstream of each foil) and Si thresholds after the gas.
## Checks (run in the Windows repo, `/mnt/c/Users/sengi/ANASEN_analysis/cross_section`; the scripts are not in this copy)
- 27Al(α,p), Run A: yields, dz and nominal E_cm equal the earlier study's in every bin; ε_si_thr agrees with its
independent toy within the MC errors.
- Synthetic events with a known σ, ε and dσ/dΩ are returned with unit-Gaussian pulls.
- `extract_yields.C` and the Python version gave byte-identical yields on every example.
**Not verified:** the physics inputs are whatever the config and `detectors.py` say. The checks show the arithmetic and
the bookkeeping are right, and that `detectors.py` reproduces the geometry of `anasen_detectability.py`. They cannot
show that the geometry, a track or an N_b is right. In particular:
- The dE/dx scale of a `.dat` table is not written in the file. The live 250 Torr / 3% tables were regenerated on
2026-10-16 and match the 0.962 track to 3 keV.
- `detectors.py` gives QQQ3 0.967 of the azimuth, but TrackRecon drops QQQ pixels without a calibration, which leaves
about 0.675. QQQ3 ε is therefore about 1.4× too high, and its cross sections about 1.4× too low.
- Where only a sliver of solid angle is open, ε is tiny and σ explodes. Example: QQQ3 at E_cm 5.4 MeV, ε = 1.5 × 10⁻⁵,
behind the beam tube. The error bar shows it; no ε cut is applied.