Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The SHiP geometry is described using GeoModel and is used by the simulation and
| DecayVolume | Approximate | Rectangular vessel (should be frustum) |
| TimingDetector | Complete | 330 scintillator bars via GeoModelXML |
| UpstreamTagger | Approximate | Monolithic slab (needs bar segmentation) |
| Trackers | Envelope only | 4 empty station boxes |
| Trackers | Complete | 4 stations, 4 stereo views each, 9600 straw tubes |
| Calorimeter | Simulation-ready | ECAL + HCAL sampling layers driven by `calo.toml` (Pb/PVT/HPL + Fe/PVT) |

## Building
Expand Down
32 changes: 32 additions & 0 deletions src/SHiPMaterials.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,38 @@ void SHiPMaterials::createMaterials() {
polystyrene->lock();
m_materials["Polystyrene"] = polystyrene;
}

// Mylar / PET (density 1.39 g/cm³): C10H8O4 — straw tube walls
// MW = 10*12.011 + 8*1.008 + 4*15.999 = 192.166 g/mol
{
const double awC = 12.011;
const double awH = 1.008;
const double awO = 15.999;
const double mw = 10.0 * awC + 8.0 * awH + 4.0 * awO;
GeoMaterial* mylar =
new GeoMaterial("Mylar", 1.39 * GeoModelKernelUnits::g / GeoModelKernelUnits::cm3);
mylar->add(m_elements["Carbon"], 10.0 * awC / mw);
mylar->add(m_elements["Hydrogen"], 8.0 * awH / mw);
mylar->add(m_elements["Oxygen"], 4.0 * awO / mw);
mylar->lock();
m_materials["Mylar"] = mylar;
}

// ArCO2_70_30 (density 1.56e-3 g/cm³): 70% Ar + 30% CO2 by mass —
// straw tube gas fill. The CO2 mass fraction is split into its C and O
// constituents (C: 12.011/44.009, O: 2*15.999/44.009 of the CO2 mass).
{
const double mwCO2 = 44.009;
const double fracAr = 0.70;
const double fracCO2 = 0.30;
GeoMaterial* arco2 = new GeoMaterial(
"ArCO2_70_30", 1.56e-3 * GeoModelKernelUnits::g / GeoModelKernelUnits::cm3);
arco2->add(m_elements["Argon"], fracAr);
arco2->add(m_elements["Carbon"], fracCO2 * 12.011 / mwCO2);
arco2->add(m_elements["Oxygen"], fracCO2 * 2.0 * 15.999 / mwCO2);
arco2->lock();
m_materials["ArCO2_70_30"] = arco2;
}
}

} // namespace SHiPGeometry
112 changes: 91 additions & 21 deletions subsystems/Trackers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,110 @@ Straw tube tracking stations.

## Description

The Trackers subsystem implements 4 tracking stations for the SHiP spectrometer. Each station currently consists of an empty air box at the correct z-position. The full implementation requires straw tube modules with individual straws, stereo views, and support structures.
The Trackers subsystem implements 4 straw tracking stations for the SHiP
spectrometer. Each station envelope is filled with 4 stereo views; each
view is a material frame (FairShip-style hollow rectangle) enclosing a
staggered double sub-layer of straw tubes.

## Geometry Tree
A straw is a Mylar-walled cylinder filled with an Ar/CO₂ gas mixture. The
four views per station are rotated about the beam axis by alternating
stereo angles so that crossing straws give a space point.

The spectrometer dipole between stations 2 and 3 is a separate subsystem
(see `subsystems/Magnet`) and is not described here.

## Geometry tree

```
TrackersContainer (Air, 6000×6860×6000 mm)
├─ TrackerStation_1 (Air, 6000×6860×1000 mm) z = 84070 mm
├─ TrackerStation_2 (Air) z = 86070 mm
├─ TrackerStation_3 (Air) z = 93070 mm
└─ TrackerStation_4 (Air) z = 95070 mm
/SHiP/trackers (Air, 3000 × 3430 × 6000 mm half-extents)
├─ /SHiP/trackers/station_<n> (Air, 3000 × 3430 × 500 mm) n = 1..4
│ └─ /SHiP/trackers/station_<n>/view_<v>/envelope (Air) v = 0..3
│ (rotated about Z by the view stereo angle)
│ ├─ .../frame_body (Aluminium, hollow rectangle = outer − aperture)
│ ├─ .../sublayer_0_body (Air slab, 300 straws)
│ └─ .../sublayer_1_body (Air slab, 300 straws, half-pitch staggered)
│ └─ .../straw_<i>
│ └─ straw_wall_<uid> (Mylar tube)
│ └─ straw_gas_<uid> (ArCO2_70_30 tube)
└─ /SHiP/trackers/tracker_magnet (Air, inert marker box)
```
Comment on lines 21 to 33
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced code block to satisfy markdownlint.

The geometry tree fence is missing a language identifier (MD040). Adding text keeps the current rendering and clears lint noise.

Proposed patch
-```
+```text
 /SHiP/trackers (Air, 3000 × 3430 × 6000 mm half-extents)
  ├─ /SHiP/trackers/station_<n> (Air, 3000 × 3430 × 500 mm)   n = 1..4
  │    └─ /SHiP/trackers/station_<n>/view_<v>/envelope (Air)   v = 0..3
@@
-```
+```
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 21-21: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@subsystems/Trackers/README.md` around lines 21 - 33, Add the language tag
"text" to the fenced code block that contains the geometry tree starting with
"/SHiP/trackers (Air, 3000 × 3430 × 6000 mm half-extents)" by changing the
opening triple backticks to ```text so the block is recognized by markdownlint
(MD040) while keeping the content unchanged.


Station Z positions (centres): 84070, 86070, 93070, 95070 mm.
Position in world: centred at z = 89570 mm (average of stations 1 and 4).
Stations 1-2 are upstream of the magnet, stations 3-4 downstream.

## Tracker magnet

`/SHiP/trackers/tracker_magnet` is an inert, air-filled marker volume for
the tracker magnet, centred at z = 86820 mm with a 460 mm Z extent.

It is **not** a physically-scaled dipole. The spectrometer dipole proper is
the separate `Magnet` subsystem (iron yoke + coils) occupying z = 87.07 to
92.07 m. The only space inside the trackers container that is clear of both
the straw stations and that yoke is the ~0.5 m gap between station 2 and the
yoke, so the marker is sized and placed to fit there without overlap.

The marker exists so the tracker magnet has a named placeholder in the
geometry; simulation/field code can locate it by its log-volume name. A
physically-scaled magnet, if required, belongs in the `Magnet` subsystem.

## Parameters

| Parameter | Value |
| --- | --- |
| Stations | 4 |
| Stereo views per station | 4 |
| Sub-layers per view | 2 (half-pitch staggered) |
| Straws per sub-layer | 300 |
| Straws total | 4 × 4 × 2 × 300 = 9600 |
| Straw outer diameter | 20 mm |
| Straw length | 4000 mm (along X) |
| Straw wall | 30 µm Mylar |
| Straw fill | Ar/CO₂ 70/30 by mass |
| View aperture | 4000 × 6000 mm (X × Y) |
| View stereo angles | +2.3°, −2.3°, +2.3°, −2.3° about Z |
| Frame bar width | 100 mm (X and Y) |
| Frame material | Aluminium |

## Materials

| Material | Density | Usage |
|----------|-------------|----------------------|
| Air | 1.29 mg/cm³ | Container & stations |
| Material | Density | Composition | Notes |
| --- | --- | --- | --- |
| Air | 1.29 mg/cm³ | already in catalog | container, station, view, sub-layer envelopes |
| Aluminium | 2.70 g/cm³ | already in catalog | view frames |
| Mylar | 1.39 g/cm³ | C₁₀H₈O₄, mass-fraction-normalised | straw walls |
| ArCO2_70_30 | 1.56 mg/cm³ | 70/30 Ar/CO₂ by mass | straw gas fill |

Mylar and ArCO2_70_30 are added by this subsystem to the central
`SHiPMaterials` catalog; Air and Aluminium were already present. All
elements required (C, H, O, Ar) were already in the element catalog.

## Tests

`test_trackers.cpp` exercises:

- `TrackersWithinEnvelope` — station 1 exists and its box stays within the
CSV envelope limits (≤ 3000 × 3500 × 500 mm half-extents).
- `TrackersHasFourStations` — all 4 station volumes are present.
- `TrackersStationHasViews` — each station holds 4 stereo views.
- `TrackersViewHasFrameAndSubLayers` — a view holds a frame plus two
sub-layers, and each sub-layer carries the full straw count.
- `TrackersHasTrackerMagnet` — the inert `tracker_magnet` marker exists and
fits in the gap before the spectrometer-magnet yoke (no overlap).

## Status

- [x] C++ implementation (envelope only)
- [ ] Implement straw tube geometry
- [ ] Add stereo views and support structures
- [ ] Verification against GDML
- [x] C++ implementation (straw-level geometry)
- [x] 4 stations × 4 stereo views
- [x] Straw tubes (Mylar wall + Ar/CO₂ gas)
- [x] Staggered double sub-layers
- [x] FairShip-style view frames
- [x] Inert TrackerMagnet marker volume
- [x] Mylar and ArCO2_70_30 materials
- [ ] Verification against a reference GDML

## TODO

- Implement straw tube modules within each station (major work)
- Individual straw tubes (mylar + gas)
- 4 views per station (Y, U, V, Y') with stereo angles
- Support frames and service volumes
- Add straw tube gas material (Ar/CO2 mixture) to SHiPMaterials
- Add mylar material to SHiPMaterials
- Verify station positions against GDML reference
- Verify straw pitch, view spacing and station positions against the GDML
reference once the tracker design is fixed.
- Add support frames and service volumes beyond the per-view frame.
119 changes: 110 additions & 9 deletions subsystems/Trackers/include/Trackers/TrackersFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,151 @@

#pragma once

#include <array>
#include <string>

class GeoPhysVol;

namespace SHiPGeometry {

class SHiPMaterials;

/**
* @brief Factory for the Trackers (straw tube tracking stations) geometry
* @brief Factory for the Trackers (straw tube tracking stations) geometry.
*
* Creates 4 tracking stations from GDML reference (statbox solid):
* Builds 4 straw tracking stations for the SHiP spectrometer. Station
* envelopes and z-positions follow the GDML reference / subsystem_envelopes.csv:
* - Station 1: Z 83.57-84.57 m → centre 84.07 m
* - Station 2: Z 85.57-86.57 m → centre 86.07 m
* - Station 3: Z 92.57-93.57 m → centre 93.07 m
* - Station 4: Z 94.57-95.57 m → centre 95.07 m
* GDML statbox: x=600 cm, y=686 cm, z=100 cm → half: 300×343×50 cm
* Station envelope (GDML statbox): half 3000 × 3430 × 500 mm.
*
* Each station envelope is filled with 4 stereo views. A view is a material
* frame (FairShip-style hollow rectangle) enclosing a staggered double
* sub-layer of straw tubes:
* - Straw: 20 mm outer diameter, 4 m long, horizontal (along X).
* - Wall: 30 µm Mylar; fill: Ar/CO₂ 70/30 by mass.
* - View stereo angles about the beam axis Z: +2.3°, -2.3°, +2.3°, -2.3°.
*
* The spectrometer dipole between stations 2 and 3 is a separate subsystem
* (see subsystems/Magnet) and is intentionally not described here.
*/
class TrackersFactory {
public:
explicit TrackersFactory(SHiPMaterials& materials);
~TrackersFactory() = default;

/**
* @brief Build the Trackers geometry
* @return Pointer to container volume with all 4 stations
* @brief Build the Trackers geometry.
* @return Pointer to the container volume holding all 4 stations.
*/
GeoPhysVol* build();

// ── Straw / view geometry constants (mm) ────────────────────────────
static constexpr int s_nStations = 4;
static constexpr int s_nViews = 4; ///< stereo views per station
static constexpr int s_nSubLayers = 2; ///< staggered straw layers per view

static constexpr double s_strawRadius = 10.0; ///< 1 cm radius (2 cm diam)
static constexpr double s_strawLength = 4000.0; ///< 4 m, along X
static constexpr double s_wallThickness = 0.030; ///< 30 µm Mylar wall

/// Active aperture inside a view frame (X = straw length region, Y = pitch).
static constexpr double s_apertureX = 4000.0;
static constexpr double s_apertureY = 6000.0;

/// Straws per sub-layer (aperture height / straw diameter).
static constexpr int s_nStraws = static_cast<int>(s_apertureY / (2.0 * s_strawRadius));

static constexpr double s_stereoAngleDeg = 2.3; ///< |stereo angle| per view

// View frame (FairShip-style hollow rectangle).
static constexpr double s_frameBarX = 100.0; ///< frame bar width in X
static constexpr double s_frameBarY = 100.0; ///< frame bar width in Y
static constexpr double s_frameHalfZ = 22.0; ///< frame half-thickness in Z

// ── Tracker magnet ──────────────────────────────────────────────────
// An inert, air-filled marker volume named "TrackerMagnet", placed in
// the clear gap between station 2 and the spectrometer-magnet yoke.
//
// NOTE: this is NOT a physically-scaled dipole. The spectrometer dipole
// proper is the separate Magnet subsystem (iron yoke + coils) occupying
// z = 87.07-92.07 m. The only free space inside the trackers container
// and clear of that yoke is a ~0.5 m gap, so this marker is sized to fit
// there. It exists so the tracker magnet has a named placeholder in the
// geometry; simulation/field code can locate it by the name
// "/SHiP/trackers/tracker_magnet".
static constexpr double s_trackerMagnetZ = 86820.0; ///< centre, mm
static constexpr double s_trackerMagnetHalfZ = 230.0; ///< half-depth, mm

private:
SHiPMaterials& m_materials;

// Station dimensions from GDML statbox (mm)
/// Frame material name in the central SHiPMaterials catalogue.
std::string m_frameMaterialName = "Aluminium";

// ── Station envelope from GDML statbox (mm) ─────────────────────────
static constexpr double s_halfX = 3000.0; // 300 cm
static constexpr double s_halfY = 3430.0; // 343 cm (GDML y=686 cm)
static constexpr double s_halfY = 3430.0; // 343 cm (GDML y = 686 cm)
static constexpr double s_halfZ = 500.0; // 50 cm

// Station Z positions (centres, in mm from origin)
// Station Z positions (centres, mm from origin).
static constexpr double s_station1Z = 84070.0; // 84.07 m
static constexpr double s_station2Z = 86070.0; // 86.07 m
static constexpr double s_station3Z = 93070.0; // 93.07 m
static constexpr double s_station4Z = 95070.0; // 95.07 m

// Container dimensions (spans all stations)
// Container dimensions (spans all stations).
static constexpr double s_containerHalfZ = (s_station4Z - s_station1Z) / 2.0 + s_halfZ;
static constexpr double s_containerCentreZ = (s_station1Z + s_station4Z) / 2.0;

// ── Internal builders ───────────────────────────────────────────────

/**
* @brief Build one station envelope and place its 4 stereo views.
* @param stationIndex 0-based station index (0..3).
* @return The station envelope physical volume.
*/
GeoPhysVol* buildStation(int stationIndex);

/**
* @brief Build one stereo view: a frame plus two staggered sub-layers.
* @param stationIndex 0-based parent station index, used for unique names.
* @param viewIndex 0-based view index within the station (0..3).
* @return The (unrotated) view physical volume.
*/
GeoPhysVol* buildView(int stationIndex, int viewIndex);

/**
* @brief Build the FairShip-style hollow-rectangle frame for one view.
* @param stationIndex 0-based parent station index, used for unique names.
* @param viewIndex 0-based view index within the station.
*/
GeoPhysVol* buildFrame(int stationIndex, int viewIndex);

/**
* @brief Build one sub-layer of s_nStraws parallel straws.
* @param stationIndex 0-based parent station index, used for unique names.
* @param viewIndex 0-based parent view index.
* @param shifted true for the half-pitch-staggered sub-layer.
*/
GeoPhysVol* buildSubLayer(int stationIndex, int viewIndex, bool shifted);

/**
* @brief Build a single straw: a Mylar wall cylinder with a gas core.
* @param uid Globally unique straw identifier, used for unique names.
*/
GeoPhysVol* buildStraw(int uid);

/**
* @brief Build the inert TrackerMagnet marker volume (air-filled box).
*
* Placed in the gap between station 2 and the spectrometer-magnet yoke.
* See the note on s_trackerMagnetZ for why this is a marker rather than
* a physically-scaled dipole.
*/
GeoPhysVol* buildTrackerMagnet();
};

} // namespace SHiPGeometry
Loading