fix(detray): allow homogeneous material on a sparse set of surfaces - #5815
fix(detray): allow homogeneous material on a sparse set of surfaces#5815paulgessinger wants to merge 3 commits into
Conversation
DetrayGeometryConverter::convert() unconditionally instantiated the homogeneous material reader, which hard-fails a static_assert for metadata types that carry no material slabs or rods. Guard the reader instantiation with `if constexpr` on has_material_slabs/has_material_rods, and check slab and rod support separately against what the payload actually contains, so a geometry only converts material the target metadata can hold. Only allocate the homogeneous material payload when a volume actually contributes homogeneous material, so a null payload now means "this geometry has none" rather than requiring callers to inspect the contents. Also enable homogeneous material in the generated ITk metadata, which otherwise supports only grid-based material maps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ZfKcVzPeWvRKHFGzVzXqc
The homogeneous material factory indexed its own data by (surface index - smallest surface index), which assumes the surfaces carrying material form a contiguous block. For material on a non-contiguous set of surfaces that index ran past the end of the factory's material vector and threw out_of_range, so only detectors where every surface carried material could be built. Index the factory data by position in the sequence instead: the n-th entries of m_indices, m_materials, m_thickness and m_links all describe the same material instance, while m_indices holds the surface it belongs to. Add a regression test covering material on a non-contiguous subset. With that, the Detray payload converter no longer has to pad every surface with a dummy material slab to keep the block contiguous. The padding gave every material-free surface a bogus slab (thickness 42, X0 42), which no valid filler could replace: material_slab::operator bool() rejects vacuum, zero thickness and zero density alike, and the consistency checker throws on any slab it rejects. Also stop pinning index_in_coll to the surface index. It is the position in the detector's material collection, and forcing the two to match made detray size that collection to the largest surface index and default-fill the gaps with invalid material. Leaving it unset packs the collection in payload order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ZfKcVzPeWvRKHFGzVzXqc
cf97d5b to
ca4614c
Compare
Public API surface diff+1 added, 0 breaking. ➕ Added public APINew call signatures (incl. defaulted-arg overloads) (1)
|
The DetrayGeometryConverter gating already handles metadata types without homogeneous material support, so ITk does not need it enabled.
|
@niermann999 what do we do with this? |
|
niermann999
left a comment
There was a problem hiding this comment.
Looks good from the detray side, just some minor comments
| // is indexed by the position in that sequence, while @c m_indices holds | ||
| // the index of the surface the material belongs to. The surfaces that | ||
| // carry material therefore do not have to be contiguous in the volume. | ||
| for (const auto [n, i] : detray::views::enumerate(m_indices)) { |
There was a problem hiding this comment.
Can we rename this, so that it becomes clearer which is the running index and which is the data? So, e.g.
| for (const auto [n, i] : detray::views::enumerate(m_indices)) { | |
| for (const auto [i, sf_idx] : detray::views::enumerate(m_indices)) { |
and then current i->sf_idx and n->i
| DETRAY_DEBUG_HOST("-> sf_offset=" << sf_offset); | ||
| // Add the material to the surfaces that the data links against. | ||
| // | ||
| // The n-th entries of @c m_indices, @c m_materials, @c m_thickness and |
There was a problem hiding this comment.
| // The n-th entries of @c m_indices, @c m_materials, @c m_thickness and | |
| // The i-th entries of @c m_indices, @c m_materials, @c m_thickness and |
| /// Regression test: the material factory used to index its own data by | ||
| /// (surface index - smallest surface index), which silently assumed that the | ||
| /// surfaces carrying material form a contiguous block. For material on a | ||
| /// non-contiguous set of surfaces that index ran past the end of the factory's | ||
| /// material vector, so only detectors where every surface carried material | ||
| /// could be built. |
There was a problem hiding this comment.
Not really relevant to know in the future?
| /// Regression test: the material factory used to index its own data by | |
| /// (surface index - smallest surface index), which silently assumed that the | |
| /// surfaces carrying material form a contiguous block. For material on a | |
| /// non-contiguous set of surfaces that index ran past the end of the factory's | |
| /// material vector, so only detectors where every surface carried material | |
| /// could be built. |
| using mat_factory_t = homogeneous_material_factory<detector_t>; | ||
|
|
||
| vecmem::host_memory_resource host_mr; | ||
| detector_t d(host_mr); |
There was a problem hiding this comment.
Can we add a dummy volume with a few surfaces here, so that we make sure the local surface indexing works?
| } | ||
| } else { | ||
| // Surfaces that were not given material must not have any | ||
| EXPECT_NE(mat_link.id(), material_id::e_material_slab); |
There was a problem hiding this comment.
How about checking the same access that the navigator uses?
| EXPECT_NE(mat_link.id(), material_id::e_material_slab); | |
| EXPECT_FALSE(sf_desc.has_material()); |



What
Lets the Detray conversion produce material on a sparse set of surfaces, and removes the workaround that was needed because it could not.
Why
homogeneous_material_factory::operator()indexed its own data bysurface index - smallest surface index:This silently assumes the surfaces carrying material form a contiguous block. Since
max(sf_idx) = max(m_indices) - min(m_indices) >= n-1, with equality only when contiguous, any gap runs past the end ofm_materialsand throwsout_of_range. So in practice only detectors where every surface carried material could be built.The
DetrayPayloadConverterworked around this by padding every volume with a dummy slab per surface (// @HACK: Detray does not like homoegeneous material only on SOME surfaces.). The filler could not be anything inert:material_slab::operator bool()rejects vacuum, zero thickness,thickness == max(), and zero density alike, andconsistency_checkerthrows on any slab it rejects. So the padding usedthickness = 42, mat = {42, ...}— roughly a radiation length of fictitious material on every surface that ACTS considered material-free, including surfaces whose real material is a grid.Changes
homogeneous_material_factory: index the factory data by position in the sequence. The n-th entries ofm_indices,m_materials,m_thicknessandm_linksall describe the same material instance, whilem_indicesholds the surface it belongs to. Surfaces carrying material no longer have to be contiguous.DetrayPayloadConverter: drop the dummy-slab padding. Also stop pinningindex_in_collto the surface index — that field is the position in the detector's material collection, and forcing the two to match madeinsert_in_containersize the collection to the largest surface index and default-fill the gaps with invalid material. Left unset, the collection packs in payload order.DetrayGeometryConverter: only instantiate the homogeneous material reader when the target metadata supports slabs or rods (it hard-fails astatic_assertotherwise), and check slab and rod support separately against what the payload actually contains. Only allocate the homogeneous payload when a volume contributes material, so a null payload means "none" rather than requiring callers to inspect contents.itk_metadata: enable homogeneous material, which the generated ITk metadata otherwise lacks (it supported only grid-based maps, soDetrayGeometryConverter::convert()could not be instantiated for it).Tests
New regression test
homogeneous_material_on_sparse_surfaces: five surfaces, material on #1 and #4 — a gap, not starting at the first surface, not a trailing block.vector::_M_range_check: __n (which is 3) >= this->size() (which is 2)DetrayPayloadConverterTestspasses, includingcheck_consistency. On the same geometry the slab count goes from 2517 (one per surface, i.e. all padding) to 2500 — 17 surfaces now correctly carry no homogeneous material, and detray accepts the sparse result.Its assertions encoded the padding (
volumes.size() == 6, "exactly one slab per surface") and were replaced with structural invariants: every registered volume exists and is non-empty, at most one entry per surface, each surface link in range and unique,index_in_collunset.🤖 Generated with Claude Code
https://claude.ai/code/session_015ZfKcVzPeWvRKHFGzVzXqc