Skip to content

fix(detray): allow homogeneous material on a sparse set of surfaces - #5815

Open
paulgessinger wants to merge 3 commits into
acts-project:mainfrom
paulgessinger:detray-sparse-material
Open

fix(detray): allow homogeneous material on a sparse set of surfaces#5815
paulgessinger wants to merge 3 commits into
acts-project:mainfrom
paulgessinger:detray-sparse-material

Conversation

@paulgessinger

Copy link
Copy Markdown
Member

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 by surface index - smallest surface index:

std::size_t sf_offset{*std::ranges::min_element(m_indices)};
for (auto [i, sf] : detray::views::pick(surfaces, m_indices)) {
  std::size_t sf_idx{i - sf_offset};
  const material<scalar_type> &mat = m_materials.at(sf_idx);

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 of m_materials and throws out_of_range. So in practice only detectors where every surface carried material could be built.

The DetrayPayloadConverter worked 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, and consistency_checker throws on any slab it rejects. So the padding used thickness = 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 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. Surfaces carrying material no longer have to be contiguous.
  • DetrayPayloadConverter: drop the dummy-slab padding. Also stop pinning index_in_coll to the surface index — that field is the position in the detector's material collection, and forcing the two to match made insert_in_container size 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 a static_assert otherwise), 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, so DetrayGeometryConverter::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.

  • Without the factory fix: vector::_M_range_check: __n (which is 3) >= this->size() (which is 2)
  • With it: passes.

DetrayPayloadConverterTests passes, including check_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_coll unset.

🤖 Generated with Claude Code

https://claude.ai/code/session_015ZfKcVzPeWvRKHFGzVzXqc

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
@paulgessinger
paulgessinger marked this pull request as ready for review July 31, 2026 15:09
@paulgessinger
paulgessinger requested review from paradajzblond and removed request for niermann999 July 31, 2026 15:09
@paulgessinger paulgessinger changed the title fix: allow Detray homogeneous material on a sparse set of surfaces fix(detray): allow homogeneous material on a sparse set of surfaces Jul 31, 2026
@github-actions github-actions Bot added Component - Plugins Affects one or more Plugins Component - Detray Affects the Detray project labels Jul 31, 2026
@github-actions github-actions Bot added this to the next milestone Jul 31, 2026
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
@paulgessinger
paulgessinger force-pushed the detray-sparse-material branch from cf97d5b to ca4614c Compare July 31, 2026 16:39
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Public API surface diff

+1 added, 0 breaking.

➕ Added public API

New call signatures (incl. defaulted-arg overloads) (1)
  • Acts::AnyTrackStateProxy::addComponents(TrackStatePropMask)

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

📊: Physics performance monitoring for ab503df

Full contents

physmon summary

❗️: Downstream build failure

  • eic-shell EICrecon (cc @acts-project/epic-contacts)

The DetrayGeometryConverter gating already handles metadata types
without homogeneous material support, so ITk does not need it enabled.
@paulgessinger

Copy link
Copy Markdown
Member Author

@niermann999 what do we do with this?

@sonarqubecloud

sonarqubecloud Bot commented Aug 7, 2026

Copy link
Copy Markdown

@niermann999 niermann999 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)) {

@niermann999 niermann999 Aug 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this, so that it becomes clearer which is the running index and which is the data? So, e.g.

Suggested change
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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

Comment on lines +184 to +189
/// 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really relevant to know in the future?

Suggested change
/// 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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about checking the same access that the navigator uses?

Suggested change
EXPECT_NE(mat_link.id(), material_id::e_material_slab);
EXPECT_FALSE(sf_desc.has_material());

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

Labels

Component - Detray Affects the Detray project Component - Plugins Affects one or more Plugins Public API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants