Skip to content

Latest commit

 

History

History
248 lines (203 loc) · 12.7 KB

gdml-to-wires.md

File metadata and controls

248 lines (203 loc) · 12.7 KB

gdml-to-wires: converting LArSoft GDML geometry to WCT wires schema

wcpy util gdml-to-wires -d <detector> -o <out.json[.gz|.bz2]> <input.gdml> converts a LArSoft detector GDML file into a Wire-Cell Toolkit (WCT) "wires schema" JSON file.

This document is the durable guide for extending the command to new geometries. It is written for both human and LLM maintainers. If you are an agent adding support for a new detector/geometry, read all of it, do the work, then update it per the checklist at the end.


What the command actually has to reproduce

The command must produce a wires file whose wire coordinates, wire idents, channel numbers and segments match LArSoft exactly, so the geometry can be used interchangeably with LArSoft-produced data.

Two facts shape the whole design:

  1. Wire endpoints come straight from the GDML wire tube solids. LArSoft's standard wire builder (geo::WireReadoutGeomBuilderStandard) makes one geo::WireGeo per volTPCWire* physvol; its endpoints are the tube end-caps (center ± halfLength along the local tube axis) pushed through the placement transform. The command's extract_wires already reproduces these byte-for-byte (verified to 0.000 mm on geometries whose wires live in the GDML). You almost never need to recompute wire positions.

  2. The hard part is the numbering, i.e. the per-plane wire ident ordering and the global channel assignment. These are not in the GDML — they are defined by whichever LArSoft wire-readout class and TPC/wire sorter the geometry is configured to use. Extending the command to a new geometry is therefore mostly: identify the LArSoft classes, port their ordering and channel arithmetic, and validate against a LArSoft dump.

A corollary: the WCT wires files shipped in wire-cell-data are only valid references for the exact geometry revision they were generated from. Wire counts and coordinates drift between revisions, so never assume a similarly named *.json.bz2 matches your GDML — confirm by wire count, or regenerate a dump (below).


General procedure for adding a geometry

  1. Inspect the GDML naming. Find the logical-volume name patterns for the wire, plane, face (TPC), and detector (cryostat) roles. These become the role_patterns regexes of a new entry in BUILTIN_CONFIGS (wirecell/util/wires/gdml.py). re.fullmatch is used, so patterns must match the entire LV name. "Refactored" DUNE GDMLs reuse a single volTPC/volTPCPlaneU LV placed many times; older ones number every LV.

  2. Identify the authoritative LArSoft wire-readout class for the geometry (see "Which LArSoft class?" below). This dictates channel numbering and whether induction strips are shared/wrapped.

  3. Identify the TPC sorter and wire sorter the geometry uses. The TPC sorter sets the CRU/TPC iteration order (hence channel blocks); the wire sorter sets the per-plane wire ident. Port both exactly (they are small, self-contained comparators).

  4. Decide the anode/face model the WCT file should use (one-face-per-CRU vs open-book two-face anodes, segments present or all zero). This follows from the LArSoft class, and you can confirm it by inspecting a matching reference wires file's structure (faces-per-anode, segment histogram, channel count).

  5. Implement a convert-mode branch that applies the ported ordering and channel arithmetic and emits the WCT schema.Store. Keep wire endpoints from extract_wires; only assign ident/channel/segment and group into anodes/faces/planes.

  6. Generate a LArSoft dump for the exact geometry (driver below) and validate wire-for-wire: same channel per (tpc,plane,wire), same endpoints. Also cross-check your ported wire sorter by re-sorting a genuine reference wires file's wires and confirming you recover its stored idents.

  7. Add an integration test (see wirecell/util/test/test_gdml_integration_*) and update this document's table.


Which LArSoft class? (reading the dispatch)

The wire-readout class is chosen by dunecore's DUNEWireReadout service (dunecore/Geometry/DUNEWireReadout_service.cc). Two mechanisms:

  • An explicit WireReadoutClass fhicl parameter, if present, wins.

  • Otherwise the class is derived from the geometry's detector Name (geo::GeometryCore::DetectorName()), by substring match. Current mapping of the substrings that matter for DUNE:

    detector Name contains wire-readout class
    dunevd10kt geo::WireReadoutCRUGeom
    dune10kt / lbne10kt APA channel map (useApaMap)
    dunevdcb1 geo::ColdBoxWireReadoutGeom
    dunecrpcb geo::CRPWireReadoutGeom
    protodunevd geo::CRPWireReadoutGeom
    protodunev7 / protodunev8 geo::ProtoDUNEWireReadoutGeom{v7,v8}
    protodune (other) geo::ProtoDUNEWireReadoutGeom
    dunedphase* / *dphase geo::WireReadoutCRMGeom

The TPC sorter is set separately by the geometry service's SortingParameters: { tool_type: ... } (e.g. GeoObjectSorterCRU, GeoObjectSorterCRU60D, GeoObjectSorterAPA). Gotcha: some geometry fcl blocks inherit a base block and do not re-set SortingParameters; confirm the effective value rather than assuming. The wire sorter is set inside the wire-readout class constructor (e.g. WireReadoutCRUGeom installs WireReadoutSorterCRU; CRPWireReadoutGeom installs its own CRPWireReadoutSorter).

Always confirm the class/sorter against the actual fcl configuration used to produce your reference — the name heuristic can be overridden, and it has changed across dunecore versions.


Driving the LArSoft dump (the reference generator)

Run inside a LArSoft job configured with the target geometry (the same services your production uses). This prints one FLAG line per wire giving the channel, the (cryostat, tpc, plane, wire) indices, and both endpoints. Treat its output as ground truth.

#include "larcore/Geometry/Geometry.h"
// ... inside an art module/analyzer ...

art::ServiceHandle<geo::Geometry> geom;
double xyz[3];   // wire start endpoint
double abc[3];   // wire end endpoint
int chan;
int cryo = geom->Ncryostats();
for (int c = 0; c < cryo; ++c) {
  int tpc = geom->NTPC(c);
  for (int t = 0; t < tpc; ++t) {
    int Nplanes = geom->Nplanes(t, c);
    for (int p = 0; p < Nplanes; ++p) {
      int Nwires = geom->Nwires(p, t, c);
      for (int w = 0; w < Nwires; ++w) {
        geom->WireEndPoints(c, t, p, w, xyz, abc);
        chan = geom->PlaneWireToChannel(p, w, t, c);
        std::cout << "FLAG " << chan << " "
                  << c << " " << t << " " << p << " " << w << " "
                  << xyz[0] << " " << xyz[1] << " " << xyz[2] << " "
                  << abc[0] << " " << abc[1] << " " << abc[2] << std::endl;
      }
    }
  }
}

Validation recipe: parse the FLAG lines, group by channel, and compare to the command's output — every wire's channel and endpoints must match (endpoints up to a tolerance of ~0.01 mm; note LArSoft may apply a PlaneGeo::shouldFlipWire flip that swaps which endpoint is "start" vs "end" without moving the wire). Units: LArSoft WireEndPoints are in cm; WCT wires schema is in mm.


Geometry summary table

connectivity_mode is the command's internal algorithm selector (in BUILTIN_CONFIGS), chosen to reproduce the LArSoft class in the last column.

Config (-d) Detector GDML naming faces/anode, segments LArSoft wire-readout class TPC sorter / wire sorter Channel scheme
protodunevd_v4 ProtoDUNE-VD volTPC\d+, volTPCPlane[UVZ]\d+, volTPCWire[UVZ]\d+ 2 (open-book) CRPWireReadoutGeom GeoObjectSorterCRU60D / CRPWireReadoutSorter shared induction across faces
protodunevd_v5 ProtoDUNE-VD v5 LV suffixes (volTPCWireU0_0, volTPCWireZ0) 2 (open-book) CRPWireReadoutGeom GeoObjectSorterCRU60D / CRPWireReadoutSorter shared induction across faces
protodunehd_v8 ProtoDUNE-HD volTPCWire(…)(Inner|Outer) etc. 2, wrapped segments 0/1/2 ProtoDUNE wire readout (verify by Name) APA-family sorter wrapped-wire clusters at Z boundaries
fdvd DUNE FD VD (dunevd10kt, workspace e.g. 1x8x14) refactored: single volTPC, volTPCPlane[UVZ], volTPCWireZ + numbered U/V 1 face/CRU, all segment 0 WireReadoutCRUGeom GeoObjectSorterCRU (Z↑ then Y↑) / WireReadoutSorterCRU hierarchical, no sharing: running counter over CRU→plane(U,V,Z)→wire

Per-geometry notes

  • fdvd (DUNE FD vertical drift). The authoritative class for dunevd10kt is WireReadoutCRUGeom — hierarchical, no induction sharing, one single-face anode per CRU, every wire segment 0, channels a dense running counter so a workspace subset is numbered 0..N-1 with no gaps. CRUs sort by center Z ascending then Y ascending (GeoObjectSorterCRU); within a plane, wires sort by WireReadoutSorterCRU::compareWires (both induction views primary center-Z ascending with a Y tie-break; collection wires, which run along Y, sort by center-Z ascending). Planes map U→0, V→1, Z→2.
    • Important distinction: the full FD-VD detector wires file (dunevd10kt_3view_30deg_v*_refactored.json.bz2, hundreds of CRUs) is a different convention — open-book two-face anodes with segments 0/1 and shared induction, i.e. a CRPWireReadoutGeom-style map — whereas the workspace subsets (…1x8x14…, …1x8x6…) use the WireReadoutCRUGeom hierarchical map above. Decide which one your task targets and confirm with a dump; do not mix them.
  • ProtoDUNE-VD (protodunevd_*). Open-book: two faces per anode share induction channels where strips meet across the face boundary; vd connectivity_mode matches induction endpoints within a tolerance.
  • ProtoDUNE-HD (protodunehd_v8). Wrapped induction wires: a strip is several geometric wires (segments 0/1/2) across Inner/Outer TPCs, connected at Z boundaries; hd mode detects the boundary clusters and does the topological segment/channel assignment. The exact LArSoft class should be re-verified via the Name-based dispatch for the specific geometry.
  • DUNE FD HD (dune10kt, future work). Dispatches to the APA channel map (useApaMap), not any CRU/CRP class — expect an APA-style sorter and numbering, closer to the ProtoDUNE-HD wrapped model than to VD.

Validation checklist (per geometry)

  • Wire counts per plane match a LArSoft dump of the same revision.
  • Every wire's channel matches the dump (PlaneWireToChannel).
  • Endpoints match the dump within ~0.01 mm (mind cm→mm and the flip).
  • Ported wire sorter recovers stored idents when re-sorting a genuine reference wires file.
  • Structural invariants hold (faces/anode, segment histogram, channel count and density) and are asserted in an integration test.

Instructions for future LLM agents extending this command

When you extend gdml-to-wires to a new geometry, you MUST also update this document, in the same change:

  1. Add a row to the geometry summary table with the new -d config name, detector, GDML naming, anode/segment model, the LArSoft wire-readout class, the TPC and wire sorters, and the channel scheme.
  2. Add a per-geometry note capturing anything non-obvious: naming quirks, open-book vs single-face, wrapped segments, workspace-vs-full differences, tolerances, and any place the Name-based class dispatch is overridden or surprising.
  3. If you discovered a new reusable fact (e.g. a new sorter, a new channel arithmetic, a dispatch change in a newer dunecore), add it to the relevant general section ("Which LArSoft class?", "What the command has to reproduce") so the next agent benefits.
  4. Keep it area-agnostic. Do NOT put absolute paths, machine names, build directories, or anything specific to a particular working checkout in this file. Refer to packages and source files by their in-repo relative names (e.g. dunecore/Geometry/DUNEWireReadout_service.cc, wirecell/util/wires/gdml.py), not their location on disk.
  5. Record how you validated (which dump, which reference) in the integration test and, if useful, as a one-line note in the table — but keep the concrete artifact out of the repo.
  6. Prefer reuse. New geometries often share a LArSoft class with an existing one; if so, reuse the ported sorter/numbering and only add a config + notes, rather than duplicating logic.

If a geometry's convention cannot be validated against any available reference of the matching revision, say so explicitly in the note and mark the config as provisional until a LArSoft dump confirms it.