class: slide-title # G4OCCT .subtitle[CAD-to-Simulation: Geant4 meets Open CASCADE Technology] --- **Geant4** drives particle-physics detector simulations worldwide. **OCCT** is the premier open-source CAD geometry kernel. **G4OCCT** bridges them — zero geometry re-entry, zero discrepancy. .meta[ [github.com/eic/G4OCCT](https://github.com/eic/G4OCCT) · LGPL-2.1-or-later ] --- # The Problem .col2[ .box.box-gold[ ## Status Quo - Geant4 geometry: hand-coded CSG *(boxes, spheres, tubes, …)* - Engineering designs: STEP/CAD files - **Two separate worlds**, maintained by different teams - Every design change needs **manual re-implementation** - CSG can't express fillets, swept surfaces, blends ] .box.box-blue[ ## Consequences - Geometry discrepancies between CAD and simulation - Maintenance bottleneck slows detector studies - Tessellated meshes lose precision at boundaries - Long iteration cycles delay physics results - Risk of undetected design-vs-simulation drift ] ] --- # Why This Matters High-energy physics detectors like **ePIC** (EIC) and future collider experiments: - Contain **tens of thousands** of placed volumes - Require geometry to match engineering drawings **exactly** - Undergo frequent **incremental design revisions** - Demand **nanosecond-level** navigation throughput > *"The real detector looks like the CAD. The simulation should too."* Current approaches (GDML hand-conversion, TGeo export, tessellated meshes) all introduce manual steps that are error-prone and time-consuming. --- # Introducing G4OCCT .col2[ .box.box-green[ ## What It Does - Wraps OCCT **BRep shapes** as `G4VSolid` objects - Imports STEP files — **single shape** or **full assembly** - Bridges **material names** from STEP metadata - Plugs into the **unmodified** Geant4 navigator - Ships as a standard **CMake package** ] .box[ ## What It Is Not - Not a replacement navigator - Not a GDML exporter - Not a GPU geometry engine *(yet)* - Not a material database - Not a full MCAD import stack All listed items are explicitly **out of scope for v0.x**, keeping the library focused and auditable. ] ] --- class: slide-section # Architecture .section-sub[Thin wrapper. Zero Geant4 changes.] --- # Design Philosophy ### Thin wrapper, not a replacement OCCT shapes live **inside** Geant4 constructs. The navigator, scoring, and visualisation remain untouched. ```mermaid flowchart TD GN["Geant4 Navigator\nG4SafetyHelper · G4VoxelNavigator · G4PropagatorInField"] GN -->|"G4VSolid virtual interface"| GOS["G4OCCTSolid ← new in G4OCCT"] GOS --> TDS["TopoDS_Shape ← OCCT BRep handle"] ``` Navigation queries (`Inside`, `DistanceToIn/Out`, `SurfaceNormal`) are **delegated to OCCT BRep algorithms**. Everything else is Geant4. --- # Class Hierarchy | G4OCCT class | Inherits from | Embeds | Role | |---|---|---|---| | `G4OCCTSolid` | `G4VSolid` | `TopoDS_Shape` | Solid geometry queries | | `G4OCCTLogicalVolume` | `G4LogicalVolume` | `TopoDS_Shape` (opt.) | Volume with material | | `G4OCCTPlacement` | `G4PVPlacement` | `TopLoc_Location` | Placed instance | | `G4OCCTAssemblyVolume` | — | STEP hierarchy | Multi-shape assembly | | `G4OCCTMaterialMap` | — | `std::map` | STEP name → `G4Material*` | Standard downstream usage: ```cmake find_package(G4OCCT REQUIRED) target_link_libraries(myApp PRIVATE G4OCCT::G4OCCT) ``` Dependencies (`Geant4 ≥ 11.3`, `OCCT ≥ 7.8`) propagate automatically. --- # Navigation: Query Mapping Each `G4VSolid` virtual method maps to an OCCT algorithm: | `G4VSolid` method | OCCT algorithm | Notes | |---|---|---| | `Inside(p)` | Multi-stage: inscribed sphere → ray-parity → `BRepClass3d_SolidClassifier` fallback | Per-thread `G4Cache`; classifier only for degenerate rays | | `DistanceToIn(p, v)` | Per-face `IntCurvesFace_Intersector` loop + AABB prefilter | Per-thread cached; no NCollection heap alloc | | `DistanceToOut(p, v)` | Per-face `IntCurvesFace_Intersector` loop + AABB prefilter | Precomputed outward normals for planar faces | | `DistanceToIn(p)` | BVH triangle-set lower bound (`PointToMeshDistance`) | Exact `BRepExtrema` only as fallback | | `DistanceToOut(p)` | BVH triangle-set / plane-distance lower bound | Plane-distance for all-planar solids | | `SurfaceNormal(p)` | `GeomAPI_ProjectPointOnSurf` | UV projection + retry on seam | | `BoundingLimits` | `fCachedBounds` | Cached at construction; O(1) per call | --- # STEP Assembly Import Multi-shape STEP files are imported via **OCCT's XDE framework**: ```cpp auto assembly = G4OCCTAssemblyVolume::FromSTEP( "detector.step", materialMap, // STEP name → G4Material* worldLogical); ``` .col2[ .box[ ### XDE Traversal - `STEPCAFControl_Reader` with name + material + colour modes - Depth-first traversal of XDE label tree - Shape → `G4OCCTSolid` + `G4OCCTLogicalVolume` - Placement → `G4OCCTPlacement` with `TopLoc_Location` ] .box[ ### Prototype Caching - Identical sub-shapes (same OCCT label) → **single** `G4LogicalVolume` - Multiple `G4PVPlacement` instances share one solid - Memory-efficient for assemblies with repeated components - Prototype key: XDE entry label string ] ] --- # Material Bridging STEP files carry material names as metadata strings. G4OCCT maps these to `G4Material*` objects — **no silent fallbacks**. ```cpp G4OCCTMaterialMap materials; materials.Add("Aluminum6061", G4NistManager::Instance()->FindOrBuildMaterial("G4_Al")); materials.Add("StainlessSteel", G4NistManager::Instance()->FindOrBuildMaterial("G4_STAINLESS-STEEL")); // Unmapped name → G4Exception FatalException (not a warning) auto assembly = G4OCCTAssemblyVolume::FromSTEP("part.step", materials, world); ``` **Policy: fail loudly.** Every unmapped material name terminates the run. Preferred alternative: use a GDML overlay (`G4GDMLParser`) to inject materials. --- class: slide-section # Thread Safety & Performance .section-sub[Multi-threaded Geant4 · OCCT algorithm caching] --- # Thread Safety Geant4 runs **N worker threads** sharing one geometry (read-only). OCCT algorithm objects maintain **mutable internal state** — they can't be shared. **Solution: `G4Cache
` per-thread caching** ```cpp class G4OCCTSolid : public G4VSolid { mutable G4Cache
fClassifierCache; // one per thread (fallback only) mutable G4Cache
fIntersectorCache; // one per thread (per-face intersectors) mutable G4Cache
fSphereCache; // one per thread (inscribed spheres) std::atomic
fShapeGeneration{0}; }; ``` - O(N_faces) initialisation paid **once per thread**, not per call - `ClassifierCache` is only invoked as a fallback for degenerate rays - `SetOCCTShape()` atomically increments generation → lazy per-thread rebuild - `TopoDS_Shape` is a reference-counted handle → safe to copy across threads --- # Bounding Volume Hierarchy (BVH) **BVH** — a spatial tree where each node's bounding box contains all geometry in its subtree. Built at construction time over the **triangulated faces** of the solid. .col2[ .box[ ### The Problem `DistanceToIn(p)` and `DistanceToOut(p)` need the **nearest surface point** to a query point. Naïve approach: test every face — O(N_faces) OCCT calls per query. ] .box[ ### BVH Solution Descend the tree; at each node compare the point against the **bounding box**. Subtrees whose box is farther than the current best are **pruned** — O(log T) work. Built via OCCT's `BRepExtrema_TriangleSet`; corrected by mesh deflection δ. ] ]
--- # Performance Profile | Method | Native Geant4 | G4OCCTSolid (current) | Notes | |---|---|---|---| | `Inside` | O(1) analytic | O(1) sphere fast-path / O(`N_faces`) ray-parity | Classifier fallback for degenerate rays only ✅ | | `DistanceToIn(p, v)` | O(1) analytic | O(k) ray test (k = unrejected faces) | AABB prefilter reduces k ≪ `N_faces` ✅ | | `DistanceToOut(p, v)` | O(1) analytic | O(k) ray test (k = unrejected faces) | Precomputed normals for planar ✅ | | `DistanceToIn(p)` | O(1) analytic | O(log T) BVH lower bound | T = triangle count; exact only as fallback ✅ | | `DistanceToOut(p)` | O(1) analytic | O(`N_faces`) plane-distance or O(log T) BVH | All-planar: exact plane distance ✅ | | `BoundingLimits` | O(1) stored | O(1) cached at construction | — ✅ | > For shapes with **≤ ~20 faces**, performance is within a small constant factor > of native solids. Complex STEP parts (100s–1000s of faces) will be slower — > this is a fundamental property of general BRep navigation, not a deficiency. --- # Benchmarking The `bench_navigator` tool compares native Geant4 vs. G4OCCTSolid for every registered fixture: ```bash cmake -S . -B build -DBUILD_BENCHMARKS=ON -DCMAKE_BUILD_TYPE=Release cmake --build build -- -j$(nproc) ./build/bench_navigator 2048 # 2048 random rays per fixture ``` Methods benchmarked per fixture: .col2[ .box[ - `DistanceToIn(p, v)` — directional ray - `DistanceToOut(p, v)` — directional ray - Exit normals — from `DistanceToOut` - `Inside(p)` — point classification ] .box[ - `DistanceToIn(p)` — safety from outside - `DistanceToOut(p)` — safety from inside - `SurfaceNormal(p)` — at hit points ] ] Output: per-method wall-clock time, **imported/native speed ratio**, correctness mismatches. CI runs benchmarks in every PR — results published in [Benchmark Results](reports/bench-results.md) (CI-generated). --- # Optimisation Roadmap | # | Optimisation | Status | |---|---|---| | 1 | Cache `BRepClass3d_SolidClassifier` per thread |
✅ Done
| | 2 | Per-face `IntCurvesFace_Intersector` loop + AABB prefilter (replaces ShapeIntersector) |
✅ Done
| | 3 | Cache bounding box at construction |
✅ Done
| | 4 | Adaptive inscribed-sphere fast path for `Inside(p)` |
✅ Done
| | 5 | Ray-parity `Inside(p)` (replaces BRepClass3d as primary path) |
✅ Done
| | 6 | BVH triangle-set lower bound for `DistanceToIn/Out(p)` |
✅ Done
| | 7 | Plane-distance safety for all-planar solids |
✅ Done
| | 8 | Direct ray-plane fast path for planar faces in `DistanceToIn/Out(p,v)` |
🔨 In flight (PR #233)
| | 9 | Cache `G4Polyhedron` in `CreatePolyhedron` |
🔲 Planned
| --- class: slide-section # Validation & CI .section-sub[Correctness first. Performance second.] --- # Test Suite Tests live in `src/tests/` and are driven by **CTest + GoogleTest**: .col2[ .box[ ### Geometry Fixtures - All Geant4 primitive shapes: `G4Box`, `G4Sphere`, `G4Tubs`, `G4Cons`, `G4Trd`, `G4Trap`, … - Each fixture: native solid vs. `G4OCCTSolid`-wrapped BRep equivalent - NIST CTC STEP files for real-world shapes - Navigation agreement verified to **numerical precision** ] .box[ ### What Is Validated - `Inside` classification matches on 2048 random points - `DistanceToIn/Out(p, v)` agrees within tolerance - `SurfaceNormal` is consistent with reported hit points - Assembly import reproduces correct placement hierarchy - Expected-failure allowlist tracks known edge cases ] ] See [Geometry Test Status](geometry_test_status.md) for per-fixture pass/fail summary. --- # CI Pipeline Two jobs run on every PR in the **EIC shell** container (`eic_xl:nightly`): .col2[ .box[ ### `build-test-benchmark` ``` Release build -DBUILD_TESTING=ON -DBUILD_BENCHMARKS=ON -Werror ``` 1. Configure & build 2. `ctest --output-on-failure` 3. Run `bench_navigator` (2048 rays) 4. Run examples (B1, B4c) 5. Publish JUnit + Markdown reports ] .box[ ### `sanitizer` ``` RelWithDebInfo build -DUSE_ASAN=ON -DUSE_UBSAN=ON ``` 1. Configure & build 2. `ctest --output-on-failure` Suppression files: `.github/asan.supp` `.github/lsan.supp` `.github/ubsan.supp` ] ] --- # Examples Two end-to-end examples demonstrate the complete workflow: .col2[ .box.box-blue[ ### B1 — Water Phantom Geant4 basic example B1 adapted for STEP import: ```cpp auto solid = G4OCCTSolid::FromSTEP("phantom.step", "WaterBox"); auto logical = new G4OCCTLogicalVolume( solid, water, "WaterBox"); ``` Gamma beam through a water phantom — exact geometry from a CAD file. ] .box.box-blue[ ### B4c — Sampling Calorimeter Multi-layer calorimeter from STEP assembly: ```cpp auto assembly = G4OCCTAssemblyVolume::FromSTEP( "calorimeter.step", materialMap, world); ``` Demonstrates full assembly import with automatic material bridging and placement hierarchy. ] ] --- class: slide-section # Direction .section-sub[Where G4OCCT is headed] --- # Roadmap | Milestone | Status | Description | |---|---|---| | **v0.1** |
✅ Complete
| CMake skeleton, stub classes, CI, docs | | **v0.2** |
✅ Complete
| `Inside` via `BRepClass3d_SolidClassifier` | | **v0.3** |
✅ Complete
| `DistanceToIn/Out` via `IntCurvesFace_ShapeIntersector` | | **v0.4** |
✅ Complete
| STEP import end-to-end example (B1) | | **v0.5** |
🔨 In progress
| Full test suite passing for all G4 primitives | | **v0.6** |
🔲 Planned
| Multi-shape STEP assembly (`G4OCCTAssemblyBuilder`) | | **v1.0** |
🔲 Planned
| Production performance, material bridging, docs | --- # Future Directions .col2[ .box[ ### Near Term - **Direct ray-plane for planar faces** — eliminate IntCurvesFace_Intersector overhead for all-planar geometries (in-flight PR #233) - **Full primitive coverage** — pass all NIST CTC fixtures - **Assembly builder** — higher-level API for constructing STEP-driven detector geometries ] .box[ ### Longer Term - **GDML round-trip** — export OCCT geometry back to GDML - **Magnetic field integration** — curved tracks in BRep volumes - **GPU navigation** — VecGeom or AdePT with OCCT-defined shapes - **Material database** — automatic G4NIST lookup from STEP names ] ] --- # How to Get Involved .col2[ .box.box-green[ ### Use It ```bash git clone https://github.com/eic/G4OCCT cmake -S G4OCCT -B build \ -DBUILD_TESTING=ON \ -DBUILD_BENCHMARKS=ON cmake --build build -- -j$(nproc) ctest --test-dir build ``` Requirements: **CMake ≥ 3.16**, **Geant4 ≥ 11.3**, **OCCT ≥ 7.8** ] .box.box-blue[ ### Contribute - Read [AGENTS.md](../AGENTS.md) for coding conventions - Run pre-commit hooks: `pip install pre-commit && pre-commit install` - Code style: LLVM, C++20, 100-col limit - Every PR needs SPDX headers, Doxygen on public API - Open issues or PRs on GitHub ] ] --- # Summary .col3[ .box.box-gold[ ### Problem CAD geometry and Geant4 simulation geometry are maintained separately, causing discrepancies and high maintenance overhead. ] .box[ ### Solution G4OCCT wraps OCCT BRep shapes as `G4VSolid` objects, importing STEP files directly into Geant4 with no geometry re-entry. ] .box.box-green[ ### Status Core navigation works. Per-thread caching eliminates dominant overheads. Test suite covers all G4 primitives. Assembly import ready. ] ] --- **Slides:** [eic.github.io/G4OCCT/slides.html](https://eic.github.io/G4OCCT/slides.html) **Docs:** [eic.github.io/G4OCCT](https://eic.github.io/G4OCCT) **Repo:** [github.com/eic/G4OCCT](https://github.com/eic/G4OCCT) **License:** LGPL-2.1-or-later