|
sipm-characterisation 0.1.0
SiPM characterisation for ePIC — IV/DCR/gain, laser, readout, irradiation
|
Project-wide design reference + hub. This file is the main entry point for anyone reading the architectural thinking behind sipm-characterisation. It also holds the triage queue produced by the first post-consolidation code audit (see TODOs). When a component grows enough to warrant its own design notes, add a satellite DISCUSSION.md under that directory and index it from the hub table below.
| Section | What it holds | Removal trigger |
|---|---|---|
| Satellite discussions | Pointers to per-area DISCUSSION.md files. None yet — everything is in this hub. | Satellite file added / removed → update the hub. |
| Triage taxonomy | Convention for tagging items (Design / TODO / Attention / Feature). | Taxonomy change → update the chapter. |
| Design discussions | Open questions — decision needed before code change. Each D-XX is self-contained. | Decision made → entry deleted or moved to TODOs. |
| TODOs | Concrete code-work items (audit bugs). No design pending. | Fix lands on main → row removed. |
| Attention points | Latent caveats that don't need a design discussion but do need a heads-up. | Caveat resolved or captured as a design question / TODO. |
| Feature queue | Discussed-but-not-designed additions. | Feature shipped or promoted to D-XX. |
| Coding conventions | Naming + style reference. | Reference material — no removal trigger. |
No satellites yet. Add a row here when one lands. Convention: one satellite per top-level component (laser/, readout/, irradiation/, …), holding the narrative (why, options, history); the hub just indexes it.
Items in this log carry one of four tags:
| Tag | Lives in | Means |
|---|---|---|
| D-XX | Design discussions | Architectural question; decision needed before code. |
| TODO | TODOs | Concrete fix; no design pending. |
| Attention | Attention points | Latent caveat; not actionable yet, but readers should know. |
| F-XX | Feature queue | Future addition; not yet designed enough to be D-XX. |
When in doubt: if it changes the layout or how components are configured, it's D-XX. If it's "this line is wrong, fix it", it's a TODO. If it's "watch out,
this looks fine but isn't", it's Attention.
Files: source/database.C (database::basedir), readout/analysis_utils.h (ALCOR_DATA_DIR / ALCOR_WORK_DIR), plus many macros with hardcoded /Users/nrubini/... absolute paths (see TODO-1).
Observation: three different mechanisms coexist for locating data — a global basedir string, environment variables, and hardcoded absolute paths. A new user cannot run most macros without editing source, and readout/ silently falls back to ./ when the env vars are unset (see Attention-1).
Options:
| Option | Behaviour | Trade-off |
|---|---|---|
| A — env vars everywhere | One SIPM_DATA_DIR, read in one helper | Consistent; requires touching every entry point |
| B — single config file | A conf/paths.toml (or similar) read at startup | Explicit + reproducible; adds a parser dependency |
| C — required parameter | Every macro takes data_dir as an argument (the core already does) | No global state; verbose at call sites |
Recommendation: Option A for a quick unification (the get_environment_variable helper already exists in readout/analysis_utils.h), migrating the hardcoded paths first. Decision needed: single env var name + whether to keep basedir as a thin wrapper over it.
Files: laser/tgraphs/macros/tgraphs_to_ttree.C vs …_ALL.C (~220 near-identical lines each); daq/macros/IV.C draw functions; the ALCOR enum type_t / hit struct redeclared in slewrate/macros/{deltat,signal_basic,signal_aps}.C, alcor.h, finetune.h (and they have already diverged — alcor.h uses t_hit, the others hit_t); the four config maps duplicated across macros/IV_plots_for_paper.C and macros/my_test_macro_on_Tdep.C.
Observation: the duplication is now a correctness risk, not just bloat — the ALCOR hit definitions differ between copies.
Options: (A) leave as-is — macros are independent and rarely touched; (B) hoist shared types into one header per area (slewrate/macros/alcor.h already exists and is the natural home) and parameterise the near-duplicate pipelines on their one differing knob.
Recommendation: B for the ALCOR hit/enum definitions (genuine divergence hazard) and the tgraphs_to_ttree pair; defer the cosmetic macro-config duplication. Decision needed: confirm alcor.h as the single source of truth for the hit model.
Files: legacy/ (salvaged macros: proton_scan.C, annealing_scan.C, get_board_history.C, check_overvoltage_noise.C, temp_scan_test_fit.C, memmert_noise.C, finetune.h).
Observation: these are unique macros salvaged from retired forks. They are reference-only, may not compile against the current source//utils/, and are not wired into anything.
Options: (A) keep indefinitely as reference; (B) port the still-useful ones (proton_scan, annealing_scan) into macros/ and delete the rest; (C) drop the directory entirely once their content is confirmed superseded.
Recommendation: B — promote what's still wanted, drop the throwaways. Decision needed: which macros are still scientifically relevant.
Surfaced by the first post-consolidation audit (2026-06). Grouped by kind; each row is a self-contained fix.
| # | File:line | Problem |
|---|---|---|
| C-1 | macros/TIFPA_2023_results.C:147,153 | TCanvas/TH1F re-declared in the same scope (already declared L80/L86) — does not compile. |
| C-2 | macros/quick_look.C:3 | Parameter std::string database shadows the database:: namespace used at L9–11 — does not compile. Rename the parameter. |
| C-3 | macros/annealing_excursus.C:66 | vector::push_back(a, b) — two args; should be push_back({a, b}). |
| C-4 | macros/my_test_macro_on_pPDE_meas.C:9–12 | Malformed stub (bare expressions, empty for ()). |
| C-5 | daq/keithley/stability.py:34, transition.py:45 | Call ky.source_config(...) which does not exist (only source_measure_config) → immediate AttributeError. |
| C-6 | daq/keithley/keithley.py:201 | query_source_config(name) references undefined name → NameError when verbose=True. |
| C-7 | laser/waveform/timewalk.C:1 (+ signal.C) | #include "qGaus.C" — file absent from the tree; macros won't load. Vendor qGaus or remove the dependency. |
| C-8 | slewrate/macros/finetune.h:53–67 | get_guesstimate_correction_from_pulser uses undeclared Tree/nev/current_hit.channel, push_back on a std::pair, inverted !std::find(...)!=end() — abandoned scaffolding; finish or delete. |
| # | File:line | Problem |
|---|---|---|
| P-1 | macros/IV_plots_for_paper.C:1061, macros/my_test_macro_on_Tdep.C:19,81 | Uncertainty on ln(I) computed as log(val)·(err/val); correct is err/val. Distorts Arrhenius-fit errors. |
| P-2 | source/database.C:328 | substr(find("TEMP")+4, find("TEMP")+7) — 2nd arg is a length, not an end index; over-reads the temperature tag. Intended length is 3. |
| P-3 | source/database.C:1096–1098 | RMS written to the average graph index 0+iSensor*2 (should be 1+…); the average point is overwritten by the RMS. |
| P-4 | source/database_laser.C:518 | bkg:: stability graph filled with current_pPDE instead of current_bkg (copy-paste) — background plot actually shows pPDE. |
| P-5 | macros/temperature_scan.C:128–133 | Error loop never resets iTer, so every iteration reads the same last element — RMS computed from one point repeated. |
| P-6 | utils/graphutils.C:101 | eval_with_errors loops < GetN()-1, never evaluating the last interval; overvoltages near the top of a scan silently return -1. |
| P-7 | readout/fine_analysis.C:340–341 | Unconditional break on the first line of the offset-calibration loop body — the refinement never runs; offset never computed. (L352 convergence if(){} is also an empty body.) |
| # | File:line | Problem |
|---|---|---|
| R-1 | source/database_laser.C:325–335 | Get("hDelta") used with no null check → null-deref; TFile leaked on the zombie path. |
| R-2 | utils/graphutils.C:211–219 | themin[ibin-1] where ibin = TProfile::Fill(...) returns -1 on under/overflow → out-of-bounds write. Guard ibin >= 1. |
| R-3 | utils/utility.h:465–478 | get_intercept_tf1 is an infinite while(true) (no break); get_intercept / get_intercept_pol1_pol1 return zero-initialised results → measure_breakdown_0 intercept always {0,0}. |
| R-4 | daq/keithley/keithley.py:97–98 | sock.recv() returns bytes; terminator test ‘data[-1] != ’' (int vs str) is always true. Decode or compare againstb'' . \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> R-5 \ilinebr </td> <td class="markdownTableBodyNone">irradiation/src/DetectorConstruction.hh:56\ilinebr </td> <td class="markdownTableBodyNone">mSystemAngleuninitialised but read inConstruct()(L271) before any messenger sets it → garbage rotation. Init= 0.. \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> R-6 \ilinebr </td> <td class="markdownTableBodyNone">irradiation/src/RootIO.hh:41\ilinebr </td> <td class="markdownTableBodyNone">mSensorInstanceIDuninitialised; compared inFill()beforeConstruct()sets it. Init= -1. \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> R-7 \ilinebr </td> <td class="markdownTableBodyNone">irradiation/src/main.cc:18,44\ilinebr </td> <td class="markdownTableBodyNone">G4UIExecutiveconstructed twice; the first leaks and may grab the terminal. Construct only in the interactive branch. \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> R-8 \ilinebr </td> <td class="markdownTableBodyNone">readout/fastMiniFrame.C:25–27,noise_analysis.C:27,readout/fine_analysis.C:73\ilinebr </td> <td class="markdownTableBodyNone">TFile::Open/Get("alcor")dereferenced with no null check → segfault on missing file/tree. \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> R-9 \ilinebr </td> <td class="markdownTableBodyNone">slewrate/macros/alcor.h:68–85,dcalib.C:66\ilinebr </td> <td class="markdownTableBodyNone">load_alcor_data_treeno null check onfin/tin;dcalib.Cdivides bynwhich can be 0 (NaN/inf). \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> R-10 \ilinebr </td> <td class="markdownTableBodyNone">daq/macros/IV.C:437–504\ilinebr </td> <td class="markdownTableBodyNone">Vbdfitwhile(true)loops have no iteration cap (non-converging fit hangs);method 2/default silently returns0.;std::sqrtof negative reverse current → NaN. \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> R-11 \ilinebr </td> <td class="markdownTableBodyNone">source/ureadout_dcr_get.C:6,source/makeiv.C:14–31,readout/fine_analysis.h:82\ilinebr </td> <td class="markdownTableBodyNone">TFileopened and never closed (leak per call); the open-to-test-existence pattern also prints a ROOT error each call — prefergSystem->AccessPathName(). \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> R-12 \ilinebr </td> <td class="markdownTableBodyNone">laser/waveform/lib/wavedesc.h:175\ilinebr </td> <td class="markdownTableBodyNone">file.read(&DATA, WAVE_ARRAY_1)reads a header-supplied (signed) length into a fixed 1 MiB buffer with no bound check — buffer overflow on a corrupt header. Validate0 ≤ WAVE_ARRAY_1 ≤ sizeof(DATA). \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> R-13 \ilinebr </td> <td class="markdownTableBodyNone">laser/tgraphs/macros/tgraphs_to_ttree.C:32(+_ALL) \ilinebr </td> <td class="markdownTableBodyNone">for (iPnt … iPnt <= GetN())reads one past the last point. Should be< GetN(). \ilinebr </td> </tr> <tr class="markdownTableRowEven"> <td class="markdownTableBodyNone"> R-14 \ilinebr </td> <td class="markdownTableBodyNone">readout/fine_analysis.h:467–473\ilinebr </td> <td class="markdownTableBodyNone"> DegeneratekCalibrationMin == kCalibrationMax == 0(skipped channel) → division by zero in the calibrated-phase normalisation. GuardkNormalisation == 0`. |
| # | File:line | Problem |
|---|---|---|
| N-1 | utils/utility.h:74 | round_digits uses std::ceil (always rounds up). If round-to-nearest is intended, use std::round; else rename. |
| N-2 | utils/utility.h:165 | Form("%s_gausfilter_n%i_a%i", …) passes 2 args for 3 specifiers (alpha_parameter not forwarded) — undefined output. |
| N-3 | laser/waveform/lib/utility.h:84–85 | get_graph_max seeds result_x/result_y from GetY()/GetX() (transposed) — wrong for a 1-point graph. |
| N-4 | readout/analysis_utils.h:70 | rollover_to_coarse accumulated in int (coarse + rollover*32768) overflows for long spills — use Long64_t. |
| N-5 | readout/fine_analysis.C:288, analysis_example.C:86 | chips[4]/chips[5] via operator[] insert phantom map entries before the size check — use count()/find(). |
| N-6 | source/database.C:355 | Annealing-minutes parse assumes an ‘'h’precedes'm'; pure-minutes tags compute a garbage offset vianpos. \ilinebr </td> </tr> <tr class="markdownTableRowOdd"> <td class="markdownTableBodyNone"> N-7 \ilinebr </td> <td class="markdownTableBodyNone">readout/fine_analysis.h:434,486\ilinebr </td> <td class="markdownTableBodyNone"> Comment@Chiara …is parsed by Doxygen as an unknown command (build warning). Reword toChiaraor@Chiara`. |
./ data fallback. readout/analysis_utils.h:60–63 returns "./" when ALCOR_DATA_DIR / ALCOR_WORK_DIR are unset, so input paths silently become relative and "no data found" looks like an empty run. Emit a warning when the variable is missing.TCanvas/TH*/TGraph and leave TFiles open by ROOT-idiom. Harmless for interactive use, but any macro promoted to a reusable function must SetDirectory(0) histograms kept after the file closes and manage lifetimes.source/database_laser.C:516–519 reads GetPointY(2) assuming ≥3 vbias points; fewer points read out of range (ROOT returns a silent sentinel). Guard GetN() before fixed-index access.irradiation/CMakeLists.txt minimum is 2.8, far below what modern Geant4 / C++17 needs. Bump to ≥ 3.16 and move cmake_minimum_required before project(). (Tracked as F-04.)daq/keithley/keithley.py global socket/state. The module uses module-level sock/commands globals — non-reentrant and untestable. (Tracked as F-03.)scripts/ driver (or a rootlogon.C that sets include paths) would lower the barrier for new users.tgraphs_to_ttree.C / …_ALL.C and the IV.C draw functions on their single differing knob (see D-02).keithley.py with a Keithley object holding the socket; pass it to the scan scripts. Makes the DAQ reentrant and testable.irradiation/CMakeLists.txt to a realistic CMake/Geant4 baseline; consider lazy creation of the 100×5 absorber messenger objects.numpy.linspace for scan grids. daq/keithley/ivscan.py:66 uses np.arange with a float step (endpoint instability); fwdscan.py already patches around it. Unify on linspace for reproducible bias grids..C / .h), Python 3 for DAQ, Geant4 C++ for the simulation. No top-level build — components are independent.database::, graphutils::, utility::. Do not shadow these with local variables (see C-2).SetDirectory(0) if they outlive their TFile; close every TFile you open; null-check every TFile::Open and Get.@file block; public functions get @brief / @param / @return. The API site is built by .github/workflows/docs.yml.dev/feature branches; squash-merge one meaningful commit per feature onto main. Full pre-v1 history is kept only until the first v1.0.0 tag, then a clean-root reset.