From 803dd92968502a4ce7bceb4bfd9d18dd17c6ea9b Mon Sep 17 00:00:00 2001 From: Oliver Lantwin Date: Thu, 9 Jul 2026 13:41:41 +0200 Subject: [PATCH 1/2] feat: add canonical unit vocabulary via mp-units Introduce SHiP/Units.hpp (canonical quantity types: mm, ns, GeV, GeV/c, tesla, with momentum and time-of-flight units derived from the exact speed of light) and SHiP/QuantityView.hpp (free-function views that convert the plain-double data classes to and from quantities), exported as the headers-only SHiP::SHiPUnits target. The persisted structs are unchanged: ROOT cannot stream mp-units quantity members (TStreamerInfo::Build fails and the member is silently zeroed on readback), so quantities stop at the I/O boundary by design. Computation code works in quantity types; conversions to and from storage are bitwise because the storage unit is the quantity unit. --- CMakeLists.txt | 18 ++++ cmake/SHiPDataModelConfig.cmake.in | 1 + include/SHiP/QuantityView.hpp | 131 +++++++++++++++++++++++++++++ include/SHiP/Units.hpp | 92 ++++++++++++++++++++ pixi.lock | 31 +++++++ pixi.toml | 2 + tests/CMakeLists.txt | 5 ++ tests/test_units.cpp | 106 +++++++++++++++++++++++ 8 files changed, 386 insertions(+) create mode 100644 include/SHiP/QuantityView.hpp create mode 100644 include/SHiP/Units.hpp create mode 100644 tests/test_units.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f7e526..e17692d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) find_package(ROOT REQUIRED COMPONENTS Core RIO) +find_package(mp-units 2.5 CONFIG REQUIRED) add_library(SHiPDataModel SHARED) target_sources( @@ -24,6 +25,22 @@ target_sources( target_link_libraries(SHiPDataModel PUBLIC ROOT::Core) set_target_properties(SHiPDataModel PROPERTIES LINKER_LANGUAGE CXX) +# Canonical unit vocabulary (mp-units quantity types + views over the data +# classes). Headers-only and deliberately separate from SHiPDataModel: the +# quantity types must never enter the ROOT dictionary (ROOT cannot stream +# them), and units-only consumers must not pay for a ROOT linkage. +add_library(SHiPUnits INTERFACE) +add_library(SHiP::SHiPUnits ALIAS SHiPUnits) +target_sources( + SHiPUnits + INTERFACE + FILE_SET headers + TYPE HEADERS + BASE_DIRS include + FILES include/SHiP/Units.hpp include/SHiP/QuantityView.hpp +) +target_link_libraries(SHiPUnits INTERFACE mp-units::mp-units) + root_generate_dictionary( G__SHiPDataModel SHiP/MCParticle.hpp @@ -45,6 +62,7 @@ install( LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} FILE_SET headers ) +install(TARGETS SHiPUnits EXPORT SHiPDataModelTargets FILE_SET headers) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/libSHiPDataModel_rdict.pcm diff --git a/cmake/SHiPDataModelConfig.cmake.in b/cmake/SHiPDataModelConfig.cmake.in index aaccb62..93945ee 100644 --- a/cmake/SHiPDataModelConfig.cmake.in +++ b/cmake/SHiPDataModelConfig.cmake.in @@ -2,6 +2,7 @@ include(CMakeFindDependencyMacro) find_dependency(ROOT COMPONENTS Core RIO) +find_dependency(mp-units 2.5 CONFIG) include("${CMAKE_CURRENT_LIST_DIR}/SHiPDataModelTargets.cmake") diff --git a/include/SHiP/QuantityView.hpp b/include/SHiP/QuantityView.hpp new file mode 100644 index 0000000..b063d79 --- /dev/null +++ b/include/SHiP/QuantityView.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include "SHiP/MCParticle.hpp" +#include "SHiP/RecParticle.hpp" +#include "SHiP/SimHit.hpp" +#include "SHiP/SimParticle.hpp" +#include "SHiP/Units.hpp" + +/// Quantity views over the persisted data classes. +/// +/// The data classes stay plain-double PODs (ROOT streaming cannot handle +/// mp-units members); these free functions convert to and from the canonical +/// quantity types so computation code never touches an implicit unit. All +/// conversions are bitwise (the storage unit is the quantity's unit). +namespace ship::view { + +// --- MCParticle ----------------------------------------------------------- +[[nodiscard]] inline Vec3 vertex(SHiP::MCParticle const& p) { + return vecOf(p.vertex); +} +[[nodiscard]] inline Vec3 momentum(SHiP::MCParticle const& p) { + return vecOf(p.momentum); +} +/// Total energy. +[[nodiscard]] inline Energy energy(SHiP::MCParticle const& p) { + return quantityOf(p.energy); +} +[[nodiscard]] inline Time time(SHiP::MCParticle const& p) { + return quantityOf