-
Notifications
You must be signed in to change notification settings - Fork 3
feat(ms)!: Add configurable iron-block muon shield (FairShip TRY_2026) #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
084c088
42b93a9
54490ea
8e914b5
4145f54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| # SPDX-License-Identifier: LGPL-3.0-or-later | ||
| # Copyright (C) CERN for the benefit of the SHiP Collaboration | ||
|
|
||
| # ship_add_toml_config(<target> <toml_file> <prefix>) | ||
| # | ||
| # Wires a subsystem's TOML config file into <target>, factoring out the setup | ||
| # shared by every config-driven subsystem (calorimeter, muon shield, neutrino | ||
| # detector): | ||
| # * links toml++ as a build-time-only (BUILD_INTERFACE) private dependency, | ||
| # so it stays out of the install export set; | ||
| # * stages <toml_file> into both the subsystem build dir and the top-level | ||
| # build dir, so tests find it whether run from either; | ||
| # * defines <prefix>_TOML_DEFAULT_PATH (source tree) and | ||
| # <prefix>_TOML_INSTALL_PATH (installed data dir) for resolveConfigPath(); | ||
| # * installs <toml_file> into the shared SHiPGeometry data dir. | ||
| # | ||
| # Call after ship_add_subsystem(<target> ...), which creates the target. | ||
| function(ship_add_toml_config target toml_file prefix) | ||
| find_package(tomlplusplus REQUIRED) | ||
|
|
||
| # toml++ is used only inside the parser .cpp, never in a public header; | ||
| # BUILD_INTERFACE keeps it out of the install export set (tomlplusplus is | ||
| # not itself exportable, so CMake would otherwise refuse to install target). | ||
| target_link_libraries( | ||
| ${target} | ||
| PRIVATE $<BUILD_INTERFACE:tomlplusplus::tomlplusplus> | ||
| ) | ||
|
|
||
| configure_file( | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/${toml_file} | ||
| ${CMAKE_CURRENT_BINARY_DIR}/${toml_file} | ||
| COPYONLY | ||
| ) | ||
| configure_file( | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/${toml_file} | ||
| ${CMAKE_BINARY_DIR}/${toml_file} | ||
| COPYONLY | ||
| ) | ||
|
|
||
| # Absolute fallbacks so the parser finds the file when the CWD does not have | ||
| # it. FULL_DATADIR is correct even when CMAKE_INSTALL_DATADIR is absolute. | ||
| target_compile_definitions( | ||
| ${target} | ||
| PRIVATE | ||
| ${prefix}_TOML_DEFAULT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/${toml_file}" | ||
| ${prefix}_TOML_INSTALL_PATH="${CMAKE_INSTALL_FULL_DATADIR}/SHiPGeometry/${toml_file}" | ||
| ) | ||
|
|
||
| install( | ||
| FILES ${CMAKE_CURRENT_SOURCE_DIR}/${toml_file} | ||
| DESTINATION ${CMAKE_INSTALL_DATADIR}/SHiPGeometry | ||
| ) | ||
| endfunction() |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move the known keys check and |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // SPDX-License-Identifier: LGPL-3.0-or-later | ||
| // Copyright (C) CERN for the benefit of the SHiP Collaboration | ||
|
|
||
| #pragma once | ||
|
|
||
| // Small shared helpers for the subsystem TOML config parsers (calorimeter, | ||
| // muon shield, neutrino detector). Factors out the numeric conversion and the | ||
| // fixed-length numeric-array parsing that were duplicated across parsers. Each | ||
| // caller passes its own error prefix (e.g. "MuonShieldConfig") so messages stay | ||
| // self-identifying. | ||
|
|
||
| #include <array> | ||
| #include <cstddef> | ||
| #include <optional> | ||
| #include <stdexcept> | ||
| #include <string> | ||
| #include <toml++/toml.h> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to be careful with this header, as it's only a BUILD_INTERFACE dependency and not a public dependency for downstream packages. If we include this header in public headers, this will cause breakage downstream. |
||
|
|
||
| namespace SHiPGeometry::tomlconfig { | ||
|
|
||
| // Extract a TOML numeric (integer or float) as a double, if the node holds one. | ||
| inline std::optional<double> asDouble(const toml::node* node) { | ||
| if (node) { | ||
| if (auto d = node->value<double>()) | ||
| return *d; | ||
| if (auto i = node->value<int64_t>()) | ||
| return static_cast<double>(*i); | ||
| } | ||
| return std::nullopt; | ||
| } | ||
|
|
||
| // Read a fixed-length numeric array (e.g. size = [x, y, z]) from a table. | ||
| // Accepts TOML integers or floats. Throws (with the caller's @p who prefix) on | ||
| // a missing required key, a wrong-length array, or a non-numeric element. | ||
| template <std::size_t N> | ||
| std::array<double, N> readNumericArray(const toml::table& table, const char* key, | ||
| const std::string& path, bool required, | ||
| const std::array<double, N>& fallback, const char* who) { | ||
| auto node = table[key]; | ||
| if (!node) { | ||
| if (required) | ||
| throw std::runtime_error(std::string(who) + ": missing required '" + std::string(key) + | ||
| "' in " + path); | ||
| return fallback; | ||
| } | ||
| const toml::array* arr = node.as_array(); | ||
| if (!arr || arr->size() != N) | ||
| throw std::runtime_error(std::string(who) + ": '" + std::string(key) + "' must be a " + | ||
| std::to_string(N) + "-element array in " + path); | ||
| std::array<double, N> out{}; | ||
| for (std::size_t i = 0; i < N; ++i) { | ||
| if (auto v = asDouble(arr->get(i))) | ||
| out[i] = *v; | ||
| else | ||
| throw std::runtime_error(std::string(who) + ": '" + std::string(key) + | ||
| "' values must be numbers in " + path); | ||
| } | ||
| return out; | ||
| } | ||
|
|
||
| } // namespace SHiPGeometry::tomlconfig | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,22 +48,24 @@ GeoPhysVol* SHiPGeometryBuilder::build() { | |
| // Note: These are relative to the cave origin | ||
| placeChild(world, target, "/SHiP/target", 1, GeoTrf::Translate3D(0.0, -14.45 * cm, 43.25 * cm)); | ||
|
|
||
| // Build and place MuonShieldArea | ||
| // GDML z range: 204–3148.66 cm → centre: 1676.33 cm = 16763.3 mm from world origin | ||
| // Build the muon shield, with the neutrino detector embedded inside it. | ||
| // | ||
| // The SND is an independent subsystem, but in volume terms it is a daughter | ||
| // of the muon-shield container: it sits within the shield region (WARM SND | ||
| // slot 26.40–31.50 m → centre 28.95 m). We build it first, then nest it in | ||
| // the shield container. The muon-shield block list must leave this slot | ||
| // free of iron. | ||
| MuonShieldFactory muonShieldFactory(materials); | ||
| GeoPhysVol* muonShield = muonShieldFactory.build(); | ||
| placeChild(world, muonShield, "/SHiP/muon_shield", 2, | ||
| GeoTrf::Translate3D(0.0, 0.0, 16763.3 * mm)); | ||
|
|
||
| // Build and place the Scattering and Neutrino Detector (SND). | ||
| // Z: 26.40 to 31.50 m (WARM muon-shield configuration) → centre 28.95 m. | ||
| // The SND sits within the downstream end of the muon-shield region, so its | ||
| // envelope overlaps the muon-shield container by design (see test_consistency). | ||
| NeutrinoDetectorFactory neutrinoDetectorFactory(materials); | ||
| GeoPhysVol* neutrinoDetector = neutrinoDetectorFactory.build(); | ||
| placeChild(world, neutrinoDetector, "/SHiP/neutrino_detector", 9, | ||
| GeoTrf::Translate3D(0.0, 0.0, 28.95 * m)); | ||
| muonShieldFactory.embedDaughter(neutrinoDetector, 28.95 * 1000.0, "/SHiP/neutrino_detector"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we take this number from the config instead of hardcoding it? It's also z-only. The reservation (if it's called, see other comment) supports 3d-positioning and rotation, which could cause a mismatch. |
||
|
|
||
| // The container is built centred on its own origin, so it is placed at the | ||
| // envelope centre reported by the factory after build(). | ||
| GeoPhysVol* muonShield = muonShieldFactory.build(); | ||
| placeChild(world, muonShield, "/SHiP/muon_shield", 2, | ||
| GeoTrf::Translate3D(0.0, 0.0, muonShieldFactory.centreZ_mm())); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd concur with coderabbit that it seems that the space is never reserved. Should the reserveSpace function be called first?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct, thank you, it wasn't being called at all. The builder now does reserveSpace(snd.centre_mm, snd.size_mm, snd.rotation_deg) before build(). OverlapCheck confirms the SND now sits in a carved cavity rather than solid iron. |
||
| // Build and place UpstreamTagger (sensitive scintillator slab) | ||
| // Z: 32.52 to 32.92 m → centre: 32.72 m | ||
| SHiPUBTManager ubtManager; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,50 +1,11 @@ | ||
| # SPDX-License-Identifier: LGPL-3.0-or-later | ||
| # Copyright (C) CERN for the benefit of the SHiP Collaboration | ||
|
|
||
| # toml++ — header-only TOML parser used by CalorimeterConfig's parsing | ||
| # internals. Available on conda-forge (`tomlplusplus`) and in most distros. | ||
| find_package(tomlplusplus REQUIRED) | ||
|
|
||
| # Stage calo.toml into the build directory so tests running from there find it. | ||
| configure_file( | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/calo.toml | ||
| ${CMAKE_CURRENT_BINARY_DIR}/calo.toml | ||
| COPYONLY | ||
| ) | ||
| # Also stage into the top-level build dir for test_builder and OverlapCheck. | ||
| configure_file( | ||
| ${CMAKE_CURRENT_SOURCE_DIR}/calo.toml | ||
| ${CMAKE_BINARY_DIR}/calo.toml | ||
| COPYONLY | ||
| ) | ||
|
|
||
| # Library + test (all src/*.cpp); the test runs from the build dir so it finds | ||
| # the staged calo.toml. | ||
| ship_add_subsystem(Calorimeter TEST_WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) | ||
|
|
||
| target_link_libraries( | ||
| Calorimeter | ||
| # toml++ is a build-time-only dependency: it's used by | ||
| # CalorimeterConfig.cpp's parser internals and never appears in any | ||
| # public header. Wrapping in $<BUILD_INTERFACE:...> keeps it out of | ||
| # the install-time export set (otherwise CMake refuses to install | ||
| # Calorimeter, since tomlplusplus isn't itself exportable). | ||
| PRIVATE $<BUILD_INTERFACE:tomlplusplus::tomlplusplus> | ||
| ) | ||
|
|
||
| # Bake the absolute source-tree path as a compile-time fallback so the factory | ||
| # can always find calo.toml even when CWD doesn't contain it. | ||
| target_compile_definitions( | ||
| Calorimeter | ||
| PRIVATE | ||
| # Source-tree fallback (always valid during development and CI builds). | ||
| CALO_TOML_DEFAULT_PATH="${CMAKE_CURRENT_SOURCE_DIR}/calo.toml" | ||
| # Install-time fallback: set to the installed data directory so that | ||
| # deployed builds (where the source tree is absent) can find calo.toml. | ||
| CALO_TOML_INSTALL_PATH="${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/SHiPGeometry/calo.toml" | ||
| ) | ||
|
|
||
| install( | ||
| FILES ${CMAKE_CURRENT_SOURCE_DIR}/calo.toml | ||
| DESTINATION ${CMAKE_INSTALL_DATADIR}/SHiPGeometry | ||
| ) | ||
| # Calorimeter parses calo.toml via CalorimeterConfig (toml++): link toml++, | ||
| # stage and install calo.toml, and define CALO_TOML_DEFAULT_PATH / | ||
| # CALO_TOML_INSTALL_PATH. | ||
| ship_add_toml_config(Calorimeter calo.toml CALO) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,9 @@ | ||
| # SPDX-License-Identifier: LGPL-3.0-or-later | ||
| # Copyright (C) CERN for the benefit of the SHiP Collaboration | ||
|
|
||
| # Shared recipe: library (globs src/*.cpp), include dirs, kernel link, Catch test. | ||
| ship_add_subsystem(MuonShield) | ||
|
|
||
| # MuonShield parses MS.toml via MuonShieldConfig (toml++): link toml++, stage | ||
| # and install MS.toml, and define MS_TOML_DEFAULT_PATH / MS_TOML_INSTALL_PATH. | ||
| ship_add_toml_config(MuonShield MS.toml MS) |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe muon_shield.toml would be better? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # SPDX-License-Identifier: LGPL-3.0-or-later | ||
| # Copyright (C) CERN for the benefit of the SHiP Collaboration | ||
| # | ||
| # Muon shield geometry — solid-block approximation of the FairShip TRY_2026 | ||
| # magnet layout (FairShip PR #1334). Each magnet is one solid iron block from | ||
| # its outer envelope (dX*(1+ratio)+midGap+ceil(max(100/dY,gap)) x dY+dY_yoke); | ||
| # the aperture, yoke shape and field are dropped (field handled elsewhere). | ||
| # | ||
| # The muon shield is defined INDEPENDENTLY of the neutrino detector. The SND | ||
| # reservation is declared in NeutrinoDetector/SD.toml and subtracted from the | ||
| # iron at build time (A - B), so this file needs no knowledge of the SND. | ||
| # | ||
| # The FairShip absorber (row 0) is the magnetised hadron stopper and lives in | ||
| # the Target subsystem (2.14-4.44 m); the shield front is at 2.14 m, so magnet 1 | ||
| # begins at 4.59 m. | ||
|
Comment on lines
+13
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not true yet. We'd need to add the hadron absorber there. |
||
|
|
||
| block_material = "Iron" | ||
|
|
||
| envelope_half_x_mm = 1760.0 | ||
| envelope_half_y_mm = 1320.0 | ||
| envelope_z_start_m = 4.5400 | ||
| envelope_z_end_m = 32.0800 | ||
|
Comment on lines
+21
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since all other values are in mm, maybe we can just make these mm as well to avoid mistakes? This would also make a few unit conversions in MuonShieldConfig unnecessary. |
||
|
|
||
| # magnet 1 | ||
| [[block]] | ||
| start = [0.0, 0.0, 4590.0] | ||
| size = [2720.0, 1600.0, 3000.0] | ||
|
|
||
| # magnet 2 | ||
| [[block]] | ||
| start = [0.0, 0.0, 7740.0] | ||
| size = [2980.0, 1780.0, 4500.0] | ||
|
|
||
| # magnet 3 | ||
| [[block]] | ||
| start = [0.0, 0.0, 12400.0] | ||
| size = [3104.0, 1700.0, 4500.0] | ||
|
|
||
| # magnet 4 | ||
| [[block]] | ||
| start = [0.0, 0.0, 17050.0] | ||
| size = [3184.0, 1200.0, 4500.0] | ||
|
|
||
| # magnet 5 | ||
| [[block]] | ||
| start = [0.0, 0.0, 21710.0] | ||
| size = [2680.0, 1040.0, 2620.0] | ||
|
|
||
| # magnet 6 | ||
| [[block]] | ||
| start = [0.0, 0.0, 24480.0] | ||
| size = [3000.0, 1260.0, 3360.0] | ||
|
|
||
|
|
||
| # magnet 7 | ||
| [[block]] | ||
| start = [0.0, 0.0, 28030.0] | ||
| size = [3420.0, 2540.0, 4000.0] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we please not prefix files and classes with Ship?