Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions include/detectors/spectrometer/SpectrometerCkf.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ class SpectrometerCkf {

Acts::CombinatorialKalmanFilterOptions<TrackContainer> options(
m_geoContext, m_magContext, std::cref(m_calibContext), extensions, propOptions);
// Station material's Ar/Z (acts_geometry_provider.cpp) are
// placeholders, not verified for the Bethe-Bloch energy-loss term —
// multipleScattering (left at its true default) doesn't depend on
// them, only X0. See buildStationMaterialSlab's comment.
options.energyLoss = false;

TrackContainerBackend trackStorage;
Trajectory trajStorage;
Expand Down Expand Up @@ -243,11 +248,12 @@ class SpectrometerCkf {

// Refits one candidate's hit list with a real forward+backward-smoothed
// Acts::KalmanFitter, starting from the same straight-line `seed` used to
// find it. Unlike the CKF above (no smoother available — see
// trackProxyToFitResult's comment), this gets real smoothed residuals, via
// the same SHiP::fromACTSFitResult converter the pre-CKF single-hit fit
// used (see ToyKalmanFitter::fit) — appropriate here since a smoother and
// reference surface are both actually configured below.
// find it. Unlike the CKF's own CombinatorialKalmanFilterExtensions (only
// updater/branchStopper/createTrackStates — no smoother, so its track
// states never get a smoothed component), this gets real smoothed
// residuals, via the same SHiP::fromACTSFitResult converter the pre-CKF
// single-hit fit used (see ToyKalmanFitter::fit) — appropriate here since
// a smoother and reference surface are both actually configured below.
SHiP::TrackFitResult refit(Acts::BoundTrackParameters const& seed,
SpectrometerMeasurements const& measurements,
std::vector<ActsExamples::Index> const& hitIndices) const {
Expand Down Expand Up @@ -290,6 +296,8 @@ class SpectrometerCkf {
Acts::KalmanFitterOptions<Trajectory> options(m_geoContext, m_magContext,
std::cref(m_calibContext), ext, propOptions,
&seed.referenceSurface());
// See the matching comment in findCandidateHits() above.
options.energyLoss = false;

Acts::VectorTrackContainer trackStorage;
Trajectory trajStorage;
Expand Down
89 changes: 89 additions & 0 deletions src/acts_geometry_provider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
#include <G4GeometryManager.hh>
#include <G4LogicalVolume.hh>
#include <G4LogicalVolumeStore.hh>
#include <G4Material.hh>
#include <G4NavigationHistory.hh>
#include <G4PVPlacement.hh>
#include <G4PhysicalVolumeStore.hh>
#include <G4RegionStore.hh>
#include <G4SolidStore.hh>
#include <G4Tubs.hh>
#include <G4VPhysicalVolume.hh>
#include <G4VSolid.hh>

Expand All @@ -45,6 +47,9 @@
#include <Acts/Geometry/StaticBlueprintNode.hpp>
#include <Acts/Geometry/TrackingGeometry.hpp>
#include <Acts/Geometry/TrackingVolume.hpp>
#include <Acts/Material/HomogeneousSurfaceMaterial.hpp>
#include <Acts/Material/Material.hpp>
#include <Acts/Material/MaterialSlab.hpp>
#include <Acts/Navigation/TryAllNavigationPolicy.hpp>
#include <Acts/Surfaces/PlaneSurface.hpp>
#include <Acts/Surfaces/RectangleBounds.hpp>
Expand Down Expand Up @@ -98,6 +103,84 @@ void find_stations(G4VPhysicalVolume* pv, PlacementMap& targets, std::size_t& re
}
}

// Depth-first search for a physical volume by exact name, anywhere below
// `pv`. Simpler than find_station above: only the volume's own solid/material
// matter here, not its placement, so no transform bookkeeping is needed.
G4VPhysicalVolume* find_volume(G4VPhysicalVolume* pv, std::string const& target_name) {
if (pv->GetName() == target_name)
return pv;
auto* lv = pv->GetLogicalVolume();
for (int i = 0; i < lv->GetNoDaughters(); ++i) {
if (auto* found = find_volume(lv->GetDaughter(i), target_name))
return found;
}
return nullptr;
}

// Effective per-station material budget for multiple scattering. A track
// crossing one station's active (straw) region passes through 2 straws per
// stereo view (one per staggered sub-layer, see TrackersFactory::buildView)
// across the station's 4 views — 8 straws total (see geometry/subsystems/
// Trackers/README.md), each entered and exited through its Mylar wall (2
// wall-thicknesses) plus one ArCO2_70_30 gas column. The view frame is a
// hollow rectangle bordering the aperture (TrackersFactory::buildFrame), so
// an on-axis track crossing the straws never touches its aluminium —
// deliberately not included.
//
// Only X0/L0 come from the real G4Material here. Ar/Z below are
// placeholders: Acts::Material treats Ar<=0 as vacuum and skips all
// interaction (Material::isVacuum()), so a positive value is required, but
// multiple scattering (the Highland formula) depends on X0 alone — Ar/Z only
// feed the energy-loss (Bethe-Bloch) term, which SpectrometerCkf.hpp
// explicitly disables (energyLoss = false) rather than risk an unverified
// conversion: G4Material::GetZ()/GetA() throw for compound materials (both
// Mylar and ArCO2_70_30 have more than one element), and
// Acts::Material::fromMassDensity's own header warns its native-unit mass
// density is easy to get orders of magnitude wrong — not something caught by
// a compiler, and not verified here since this hasn't been built/run.
Acts::MaterialSlab buildStationMaterialSlab(G4VPhysicalVolume* world_pv) {
constexpr int kStrawsPerStation = 8; // 4 views x 2 staggered sub-layers

auto* wallPv = find_volume(world_pv, "/SHiP/trackers/straw_wall");
auto* gasPv = find_volume(world_pv, "/SHiP/trackers/straw_gas");
if (!wallPv || !gasPv)
throw std::runtime_error(
"acts_geometry_provider: could not find straw_wall/straw_gas in geometry");

auto* wallSolid = dynamic_cast<G4Tubs const*>(wallPv->GetLogicalVolume()->GetSolid());
auto* gasSolid = dynamic_cast<G4Tubs const*>(gasPv->GetLogicalVolume()->GetSolid());
if (!wallSolid || !gasSolid)
throw std::runtime_error(
"acts_geometry_provider: straw_wall/straw_gas are not G4Tubs as expected");

double const wallThickness = wallSolid->GetOuterRadius() - gasSolid->GetOuterRadius();
double const gasPathLength = 2.0 * gasSolid->GetOuterRadius(); // straight-through diameter

auto const* mylar = wallPv->GetLogicalVolume()->GetMaterial();
auto const* gas = gasPv->GetLogicalVolume()->GetMaterial();

double const tMylar = kStrawsPerStation * 2.0 * wallThickness; // in, then out, per straw
double const tGas = kStrawsPerStation * gasPathLength;
double const tTotal = tMylar + tGas;

// Series combination for thin scatterers: 1/X0_eff = sum(t_i / X0_i).
double const x0 = tTotal / (tMylar / mylar->GetRadlen() + tGas / gas->GetRadlen());
double const l0 = tTotal / (tMylar / mylar->GetNuclearInterLength() +
tGas / gas->GetNuclearInterLength());

// See the function comment above — placeholders, inert while energyLoss
// is off.
constexpr float kPlaceholderAr = 14.f;
constexpr float kPlaceholderZ = 7.f;
constexpr float kPlaceholderMassRho = 1.f;

Acts::Material const material = Acts::Material::fromMassDensity(
static_cast<float>(x0), static_cast<float>(l0), kPlaceholderAr, kPlaceholderZ,
kPlaceholderMassRho);

return Acts::MaterialSlab(material, static_cast<float>(tTotal));
}

std::shared_ptr<Acts::TrackingGeometry> build_tracking_geometry(G4VPhysicalVolume* world_pv) {
auto station_name = [](int i) { return "/SHiP/trackers/station_" + std::to_string(i + 1); };

Expand Down Expand Up @@ -144,6 +227,11 @@ std::shared_ptr<Acts::TrackingGeometry> build_tracking_geometry(G4VPhysicalVolum
auto worldVolume = std::make_unique<Acts::TrackingVolume>(containerTrf, containerBounds,
"SpectrometerStations");

// Same straw/view/sub-layer structure at every station, so one slab
// covers all 4 surfaces.
auto stationMaterial =
std::make_shared<Acts::HomogeneousSurfaceMaterial>(buildStationMaterialSlab(world_pv));

for (int i = 0; i < kNumStations; ++i) {
auto const& s = stations[i];
Acts::Transform3 trf = Acts::Transform3::Identity();
Expand All @@ -162,6 +250,7 @@ std::shared_ptr<Acts::TrackingGeometry> build_tracking_geometry(G4VPhysicalVolum
// before ever attempting createTrackStates. Without this, the CKF
// treats every station as non-sensitive and never calibrates a hit.
surface->assignIsSensitive(true);
surface->assignSurfaceMaterial(stationMaterial);

worldVolume->addSurface(surface);
}
Expand Down
Loading