Skip to content

Add SCECorrection IPCTransform with pluggable ISCEField (SBND)#479

Merged
HaiwangYu merged 3 commits into
WireCell:matchfrom
abhatfnal:sce-via-iscefield
Jun 16, 2026
Merged

Add SCECorrection IPCTransform with pluggable ISCEField (SBND)#479
HaiwangYu merged 3 commits into
WireCell:matchfrom
abhatfnal:sce-via-iscefield

Conversation

@abhatfnal

@abhatfnal abhatfnal commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Adds an SBND-ready 3D SCE correction to the toolkit as an IPCTransform composed from a new ISCEField interface. T0 correction is built in; the SCE backward displacements (X, optionally Y / Z) are delegated to whichever ISCEField implementation is wired in via configuration. This PR ships one such implementation, SCEFieldTH3, backed by per-TPC TH3F histograms loaded from a ROOT file (e.g. the SBND dualmap SCEoffsets_SBND_E500_dualmap_CV_voxelTH3.root model).

Validation evidence: WireCell/wcp-porting-validation#9 (Stage 1, merged) and WireCell/wcp-porting-validation#12 (Stage 3a, transverse).

Architecture

  • iface/inc/WireCellIface/ISCEField.h — pure-virtual IComponent<ISCEField> with displacement_x(int apa, double x, double y, double z) const (required) plus optional displacement_y / displacement_z (default no-op, so maps without transverse components are unaffected). WCT-internal mm in, mm out.
  • root/inc/WireCellRoot/SCEFieldTH3.{h,cxx} — ROOT-backed ISCEField + IConfigurable. Loads up to six TH3Fs (X / Y / Z × East / West) from a single ROOT file via a shared interp() helper; transverse histograms soft-load (skipped without error if absent). Handles cm ↔ mm axis units, sign convention, and TH3::Interpolate's open-interval edge clamping.
  • clus/inc/WireCellClus/SCECorrection.h + clus/src/PCTransforms.cxxSCECorrection does T0 (per-APA drift_speed from DetectorVolumes metadata) plus the optional 3D SCE displacement via an injected ISCEField::pointer. PCTransformSet::configure looks up the field by TypeName from the DetectorVolumes metadata key sce_field via Factory::find_tn. If sce_field is absent, SCECorrection runs as T0-only.

The interface lives in iface/, the ROOT-backed implementation in root/, the transform in clus/. clus/wscript_build is untouched — clus/ remains ROOT-free.

Behavior change (intentional): SCECorrection's output_scope is {x_sce, y_sce, z_sce}; stored_array_names likewise. Downstream configs that cluster on the SCE scope therefore cluster in fully-corrected 3D.

Validation

50 SBND crossing-muon DETSIM events, 226 721 / 228 762 points paired by charge (99.1 %; 0.9 % dropped on ambiguous q), comparing the reco output to the input TH3 backward map at the corrected (x_sce, y_sce, z_sce) coordinates:

quantity East West
Δx rms (cm) 1.83 × 10⁻⁴ 2.23 × 10⁻⁴
Δy rms (cm) 2.63 × 10⁻⁴ 1.65 × 10⁻⁴
Δz rms (cm) 3.35 × 10⁻⁴ 2.99 × 10⁻⁴
max residual any (cm) 9.91 × 10⁻⁴ 9.91 × 10⁻⁴
pooled mean|Δx| (cm) 0.4026 0.5340

Pooled W / E |Δx| = 1.326 — consistent with the map volume-average (1.276) and the patched 0.33-era reco (1.271). The implementation reproduces the TH3 backward map to interpolation precision (≈ 2 µm) in all three components.

Configuration

Minimal jsonnet wiring for the SBND dualmap (3D):

local sce_field = {
  type: "SCEFieldTH3",
  name: "sbnd_dualmap",
  data: {
    sce_map_file: "/cvmfs/.../SCEoffsets_SBND_E500_dualmap_CV_voxelTH3.root",
    th3_name_E:   "TrueBkwd_Displacement_X_E",
    th3_name_W:   "TrueBkwd_Displacement_X_W",
    th3_name_E_y: "TrueBkwd_Displacement_Y_E",  // optional
    th3_name_W_y: "TrueBkwd_Displacement_Y_W",  // optional
    th3_name_E_z: "TrueBkwd_Displacement_Z_E",  // optional
    th3_name_W_z: "TrueBkwd_Displacement_Z_W",  // optional
    axis_unit_mm: false,  // SBND TH3 axes are in cm
    sign:         -1,     // backward map
  },
};
// in the DetectorVolumes face metadata:
//   { ..., sce_field: wc.tn(sce_field) }
// and add sce_field to the PCTransformSet's `uses` list.

Files

5 files, +466 / 0 across 3 commits:

file status
iface/inc/WireCellIface/ISCEField.h new
root/inc/WireCellRoot/SCEFieldTH3.h new
root/src/SCEFieldTH3.cxx new
clus/inc/WireCellClus/SCECorrection.h new
clus/src/PCTransforms.cxx modified

cc @HaiwangYu

abhatfnal added 3 commits June 5, 2026 16:26
ISCEField is a pure-virtual IComponent providing per-TPC backward
X-displacement queries.  SCEFieldTH3 implements it backed by per-TPC
TH3F histograms loaded from a ROOT file -- for example, the SBND
dualmap SCEoffsets_SBND_E500_dualmap_CV_voxelTH3.root model.

Separating the interface from the ROOT-backed implementation lets
SCECorrection in clus/ depend only on the interface, keeping the
clus/ subpackage ROOT-free.
SCECorrection is an IPCTransform that composes a T0 correction
[per-APA drift_speed from DetectorVolumes metadata] with an optional
SCE backward displacement delegated to an externally-provided
ISCEField.  This keeps clus/ ROOT-free; the ROOT-backed displacement
field lives in root/ as SCEFieldTH3.

PCTransforms looks up the ISCEField by TypeName from the
DetectorVolumes metadata key 'sce_field' via Factory::find_tn and
passes it to the SCECorrection constructor.  If 'sce_field' is not
present in the metadata, SCECorrection runs as T0-only -- the SCE
step is a no-op.

Validated on 50 SBND crossing-muon DETSIM events against the dualmap
SCE model: per-point residual rms 1.83e-04 cm East, 2.23e-04 cm West,
max 8.42e-04 cm; pooled mean|dx| ratio W/E = 1.327.  The reco
reproduces the TH3 backward map to interpolation precision.
…ments

Adds the transverse components to the SCECorrection chain introduced
in this PR.  ISCEField gains optional displacement_y / displacement_z
[default no-op, so maps without transverse components are unaffected].
SCEFieldTH3 soft-loads the TrueBkwd_Displacement_{Y,Z}_{E,W}
histograms and routes all three components through a single shared
interp() helper.  SCECorrection now outputs the fully-corrected triple.

Behavior change [intentional]: SCECorrection output_scope goes from
{x_sce, y, z} to {x_sce, y_sce, z_sce}; stored_array_names likewise.
Downstream configs that cluster on the SCE scope therefore cluster in
fully-corrected 3D.

Validated end-to-end on 50 SBND crossing-muon DETSIM events: per-point
Dx residual vs the TH3 map is unchanged from the prior baseline
[1.83 / 2.23 um E/W], and the new Dy / Dz residuals close at the same
interpolation precision -- Dy 2.63 / 1.65 um, Dz 3.35 / 2.99 um;
max <= 9.91 um across all three components.  Evidence in the
companion wcp-porting-validation PR (sbnd-sce-036-stage3a-yz branch).
@HaiwangYu

Copy link
Copy Markdown
Member

Review: SCE correction (SCECorrection / ISCEField / SCEFieldTH3)

Adds SBND Space-Charge-Effect correction: a new ISCEField interface (iface), a ROOT-backed SCEFieldTH3 implementation (root), and an SCECorrection IPCTransform (clus) wired into PCTransformSet. Reviewed against two criteria: new functionality compiles, and the change does not affect existing functionality.

✅ Compilation — passes

Verified against a full build log of this branch:

  • root/src/SCEFieldTH3.cxx and clus/src/PCTransforms.cxx both compile.
  • Full build finishes successfully (all targets linked).
  • root/src/*.cxx is auto-globbed by smplpkg, so no wscript change is needed.
  • Dependency direction is clean (root → clus → iface). ISCEField.h is header-only in iface, so clus stays ROOT-free and resolves the field at runtime via Factory::find_tn. Good separation.

✅ No impact on existing functionality

  • Transforms are selected by name via pc_transform(name). This PR only adds a "SCECorrection" map entry; the existing "T0Correction" path is untouched.
  • The sce_field discovery loop is a correct no-op when the metadata key is absent: sce_field stays null and SCECorrection falls back to T0-only. Merely building/merging this PR changes nothing for current configs.
  • ISCEField and SCEFieldTH3 are new symbols; nothing else references them.

Test segfaults in the build log — pre-existing, not from this PR

The test run shows 2 doctest crashes (SIGSEGV):

  • wcdoctest-sigproc (l1sp_kernel) — sigproc is not touched by this PR, so this is environmental/pre-existing (log also shows HDF5, libtorch/CUDA, and missing-data-file errors).
  • wcdoctest-clus crashes in doctest_clustering_prototype.cxx:301 ("clustering prototype pca") — a clustering-PCA test that does not exercise PCTransforms/SCECorrection. No test files are modified by this PR.

Recommend confirming both also fail on master in the same environment to fully close the loop, but the evidence indicates they are not introduced here.

Non-blocking notes (relevant when the feature is enabled)

  1. backward() is an approximate inverse of forward() when an SCE field is active — it evaluates the displacement at the corrected position rather than solving the inverse (documented as "Approximate"). Forward→backward round-trips will not be exact with the field on. The T0-only path (null field) is still exact.
  2. Downstream scope must match. Unlike T0Correction (stores only x_t0cor), SCECorrection declares all three {x_sce, y_sce, z_sce} in output_scope()/stored_array_names(). Any consumer (e.g. ClusteringRetile) must be configured with the matching scope when the feature is enabled.
  3. Single shared sce_field assumption. The discovery loop breaks at the first APA carrying the sce_field key, so all APAs share one field TypeName. Correct for SBND (E/W routing lives inside the field), but a latent assumption if reused elsewhere.
  4. Minor: detached TH3F histograms (SetDirectory(nullptr)) are never explicitly deleted — a negligible program-lifetime leak. Missing drift_speed metadata yields 0 and silently disables T0, but this matches existing T0Correction behavior (not a regression).

Verdict

Both requirements are met: the PR compiles cleanly and is additive / name-gated, so it cannot affect existing behavior unless explicitly enabled via metadata + jsonnet. The two test segfaults are unrelated/pre-existing. The notes above are quality considerations for when the feature is turned on, not blockers.

@HaiwangYu HaiwangYu merged commit 0d196f4 into WireCell:match Jun 16, 2026
@brettviren

Copy link
Copy Markdown
Member

@calcuttj perhaps interesting for you to note given your presentation yesterday about the VD non-uniform drift field issue.

@HaiwangYu

Copy link
Copy Markdown
Member

The match branch PR479 is targeting is obsolete.
Now I am cherry-picking PR479 commits into master

848de71
0b16a47
a33e2c6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants