From 809b378e51249733567ae24bffe6cdffa9058503 Mon Sep 17 00:00:00 2001 From: Stephen Nicholas Swatman Date: Thu, 29 Aug 2024 09:37:24 +0200 Subject: [PATCH] feat: Add covfie magnetic field plugin (#3479) This commit adds a new Acts plugin that adds support for magnetic fields implemented using the covfie library. This commit is based on #3117. Closes #3117. Depends on #3478. Virtually all credit goes to @fredevb. Co-authored-by: Fred <92879080+fredevb@users.noreply.github.com> --- Examples/Python/CMakeLists.txt | 3 + Examples/Python/src/Covfie.cpp | 55 +++++ Examples/Python/src/CovfieStub.cpp | 13 ++ Examples/Python/src/ModuleEntry.cpp | 2 + Examples/Python/tests/helpers/__init__.py | 7 + Examples/Python/tests/test_covfie.py | 63 +++++ Plugins/CMakeLists.txt | 1 + Plugins/Covfie/CMakeLists.txt | 16 ++ .../Acts/Plugins/Covfie/FieldConversion.hpp | 64 ++++++ Plugins/Covfie/src/FieldConversion.cpp | 197 ++++++++++++++++ Tests/DownstreamProject/CMakeLists.txt | 10 +- Tests/UnitTests/Plugins/CMakeLists.txt | 1 + Tests/UnitTests/Plugins/Covfie/CMakeLists.txt | 2 + .../Covfie/CovfieFieldConversionTest.cpp | 217 ++++++++++++++++++ cmake/ActsConfig.cmake.in | 4 + 15 files changed, 653 insertions(+), 2 deletions(-) create mode 100644 Examples/Python/src/Covfie.cpp create mode 100644 Examples/Python/src/CovfieStub.cpp create mode 100644 Examples/Python/tests/test_covfie.py create mode 100644 Plugins/Covfie/CMakeLists.txt create mode 100644 Plugins/Covfie/include/Acts/Plugins/Covfie/FieldConversion.hpp create mode 100644 Plugins/Covfie/src/FieldConversion.cpp create mode 100644 Tests/UnitTests/Plugins/Covfie/CMakeLists.txt create mode 100644 Tests/UnitTests/Plugins/Covfie/CovfieFieldConversionTest.cpp diff --git a/Examples/Python/CMakeLists.txt b/Examples/Python/CMakeLists.txt index 1ab76a804a2..5ed407bd889 100644 --- a/Examples/Python/CMakeLists.txt +++ b/Examples/Python/CMakeLists.txt @@ -97,8 +97,11 @@ endif() if(ACTS_BUILD_PLUGIN_TRACCC) target_link_libraries(ActsPythonBindings PUBLIC ActsPluginDetray) target_sources(ActsPythonBindings PRIVATE src/Detray.cpp) + target_link_libraries(ActsPythonBindings PUBLIC ActsPluginCovfie) + target_sources(ActsPythonBindings PRIVATE src/Covfie.cpp) else() target_sources(ActsPythonBindings PRIVATE src/DetrayStub.cpp) + target_sources(ActsPythonBindings PRIVATE src/CovfieStub.cpp) endif() if(ACTS_BUILD_PLUGIN_ACTSVG) diff --git a/Examples/Python/src/Covfie.cpp b/Examples/Python/src/Covfie.cpp new file mode 100644 index 00000000000..d4ad5d30b00 --- /dev/null +++ b/Examples/Python/src/Covfie.cpp @@ -0,0 +1,55 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2021 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/Covfie/FieldConversion.hpp" +#include "Acts/Plugins/Python/Utilities.hpp" + +#include +#include + +namespace py = pybind11; +using namespace pybind11::literals; + +namespace Acts::Python { + +namespace { +template +void declareCovfieField(py::module& m, const std::string& fieldName) { + using view_t = typename field_t::view_t; + m.def("toView", + [](const field_t& field) { return typename field_t::view_t(field); }); + py::class_>(m, fieldName.c_str()); + py::class_>( + m, (fieldName + std::string("View")).c_str()) + .def("at", &view_t::template at); +} +} // namespace + +void addCovfie(Context& ctx) { + auto main = ctx.get("main"); + auto m = main.def_submodule("covfie", "Submodule for covfie conversion"); + + declareCovfieField(m, + "CovfieConstantField"); + declareCovfieField( + m, "CovfieAffineLinearStridedField"); + + m.def("makeCovfieField", + py::overload_cast( + &Acts::CovfiePlugin::covfieField)); + m.def("makeCovfieField", py::overload_cast( + &Acts::CovfiePlugin::covfieField)); + m.def("makeCovfieField", + py::overload_cast&, + const Acts::Vector3&, const Acts::Vector3&>( + &Acts::CovfiePlugin::covfieField)); +} + +} // namespace Acts::Python diff --git a/Examples/Python/src/CovfieStub.cpp b/Examples/Python/src/CovfieStub.cpp new file mode 100644 index 00000000000..bb2ea09bfe1 --- /dev/null +++ b/Examples/Python/src/CovfieStub.cpp @@ -0,0 +1,13 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2021 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/Python/Utilities.hpp" + +namespace Acts::Python { +void addCovfie(Context& /* ctx */) {} +} // namespace Acts::Python diff --git a/Examples/Python/src/ModuleEntry.cpp b/Examples/Python/src/ModuleEntry.cpp index 53e5d0dd3af..3cb00f50bd3 100644 --- a/Examples/Python/src/ModuleEntry.cpp +++ b/Examples/Python/src/ModuleEntry.cpp @@ -82,6 +82,7 @@ void addSvg(Context& ctx); void addObj(Context& ctx); void addOnnx(Context& ctx); void addOnnxNeuralCalibrator(Context& ctx); +void addCovfie(Context& ctx); } // namespace Acts::Python @@ -148,4 +149,5 @@ PYBIND11_MODULE(ActsPythonBindings, m) { addSvg(ctx); addOnnx(ctx); addOnnxNeuralCalibrator(ctx); + addCovfie(ctx); } diff --git a/Examples/Python/tests/helpers/__init__.py b/Examples/Python/tests/helpers/__init__.py index 67f337609c7..386c370f487 100644 --- a/Examples/Python/tests/helpers/__init__.py +++ b/Examples/Python/tests/helpers/__init__.py @@ -58,6 +58,13 @@ except ImportError: onnxEnabled = False +try: + from acts import covfie + + covfieEnabled = True +except ImportError: + covfieEnabled = False + try: import acts.examples diff --git a/Examples/Python/tests/test_covfie.py b/Examples/Python/tests/test_covfie.py new file mode 100644 index 00000000000..3c1d7ca0d3d --- /dev/null +++ b/Examples/Python/tests/test_covfie.py @@ -0,0 +1,63 @@ +import pathlib, acts, acts.examples +import pytest + +from helpers import covfieEnabled + + +@pytest.mark.skipif(not covfieEnabled, reason="Covfie plugin not available") +def test_constant_field_conversion(): + from acts import covfie + + v = acts.Vector3(1, 2, 3) + af = acts.ConstantBField(v) + cf = covfie.makeCovfieField(af) + view = covfie.toView(cf) + points = [(0, 0, 1), (1, 1, 1), (1, 0, 2)] + for x, y, z in points: + assert view.at(x, y, z) == [1, 2, 3] + + +@pytest.mark.skipif(not covfieEnabled, reason="Covfie plugin not available") +def test_root_field_conversion(): + from acts import covfie + + current_file_path = pathlib.Path(__file__).resolve().parent + p = ( + current_file_path.parent.parent.parent + / "thirdparty" + / "OpenDataDetector" + / "data" + / "odd-bfield.root" + ) + + af = acts.examples.MagneticFieldMapXyz(str(p)) + bc = acts.MagneticFieldContext() + fc = af.makeCache(bc) + + cf = covfie.makeCovfieField(af) + view = covfie.toView(cf) + points = [ + (9300.0, 4700.0, 11200.0), + (9999.0, 9999.0, 14300.0), + (-2900.0, -4700.0, 5200.0), + (-2900.0, -4800.0, 9100.0), + (-2900.0, -5200.0, -8800.0), + (-4400.0, 4800.0, -12700.0), + (-6600.0, 1900.0, 7700.0), + (-9700.0, -900.0, 12700.0), + (-9999.0, -9999.0, -13000.0), + (9999.0, 0, 14900.0), + ] + + error_margin_half_width = 0.0001 + for x, y, z in points: + val = af.getField(acts.Vector3(x, y, z), fc) + Bx1, By1, Bz1 = val[0], val[1], val[2] + + Bx2, By2, Bz2 = tuple(view.at(x, y, z)) + + assert ( + abs(Bx1 - Bx2) < error_margin_half_width + and abs(By1 - By2) < error_margin_half_width + and abs(Bz1 - Bz2) < error_margin_half_width + ) diff --git a/Plugins/CMakeLists.txt b/Plugins/CMakeLists.txt index b0d006a61c1..ab6d375a27b 100644 --- a/Plugins/CMakeLists.txt +++ b/Plugins/CMakeLists.txt @@ -11,6 +11,7 @@ add_component_if(Legacy PluginLegacy ACTS_BUILD_PLUGIN_LEGACY) add_component_if(Onnx PluginOnnx ACTS_BUILD_PLUGIN_ONNX) add_component_if(ExaTrkX PluginExaTrkX ACTS_BUILD_PLUGIN_EXATRKX) add_component_if(Detray PluginDetray ACTS_BUILD_PLUGIN_TRACCC) +add_component_if(Covfie PluginCovfie ACTS_BUILD_PLUGIN_TRACCC) # dependent plugins. depend either on a independent plugins or on one another add_component_if(TGeo PluginTGeo ACTS_BUILD_PLUGIN_TGEO) diff --git a/Plugins/Covfie/CMakeLists.txt b/Plugins/Covfie/CMakeLists.txt new file mode 100644 index 00000000000..0245904b035 --- /dev/null +++ b/Plugins/Covfie/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(ActsPluginCovfie SHARED src/FieldConversion.cpp) + +target_include_directories( + ActsPluginCovfie + PUBLIC + $ + $ +) +target_link_libraries(ActsPluginCovfie PUBLIC ActsCore covfie::core) + +install( + TARGETS ActsPluginCovfie + EXPORT ActsPluginCovfieTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) +install(DIRECTORY include/Acts DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/Plugins/Covfie/include/Acts/Plugins/Covfie/FieldConversion.hpp b/Plugins/Covfie/include/Acts/Plugins/Covfie/FieldConversion.hpp new file mode 100644 index 00000000000..cfb1d6b516c --- /dev/null +++ b/Plugins/Covfie/include/Acts/Plugins/Covfie/FieldConversion.hpp @@ -0,0 +1,64 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// acts includes +#include "Acts/MagneticField/BFieldMapUtils.hpp" +#include "Acts/MagneticField/ConstantBField.hpp" +#include "Acts/MagneticField/MagneticFieldProvider.hpp" + +namespace Acts::CovfiePlugin { + +using BuilderBackend = + covfie::backend::strided>; + +using InterpolatedField = covfie::field>>>; + +using ConstantField = covfie::field< + covfie::backend::constant>; + +/// @brief Creates a covfie field from an interpolated magnetic field. +/// @param magneticField The acts interpolated magnetic field. +/// @return An affine linear strided covfie field. +InterpolatedField covfieField( + const Acts::InterpolatedMagneticField& magneticField); + +/// @brief Creates a covfie field from a constant B field. +/// @param magneticField The acts constant magnetic field. +/// @return A constant covfie field. +ConstantField covfieField(const Acts::ConstantBField& magneticField); + +/// @brief Creates a covfie field from a magnetic field provider by sampling it. +/// The field must be defined within min (inclusive) and max (inclusive). +/// @param magneticField The acts magnetic field provider. +/// @param cache The acts cache. +/// @param nPoints 3D array of containing the number of bins for each axis. +/// @param min (min_x, min_y, min_z) +/// @param max (max_x, max_y, max_z) +/// @return An affine linear strided covfie field. +InterpolatedField covfieField(const Acts::MagneticFieldProvider& magneticField, + Acts::MagneticFieldProvider::Cache& cache, + const std::array& nPoints, + const Acts::Vector3& min, + const Acts::Vector3& max); + +} // namespace Acts::CovfiePlugin diff --git a/Plugins/Covfie/src/FieldConversion.cpp b/Plugins/Covfie/src/FieldConversion.cpp new file mode 100644 index 00000000000..6f182f25885 --- /dev/null +++ b/Plugins/Covfie/src/FieldConversion.cpp @@ -0,0 +1,197 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include "Acts/Plugins/Covfie/FieldConversion.hpp" + +#include "Acts/Utilities/Axis.hpp" +#include "Acts/Utilities/AxisFwd.hpp" +#include "Acts/Utilities/Grid.hpp" + +#include +#include +#include + +namespace Acts::CovfiePlugin { + +/// @brief Creates a strided covfie field that stores the values of the +/// magnetic field in the volume given by min and max using a fixed sample +/// spacing (determined by nPoints). +/// +/// @param magneticField The acts magnetic field. +/// @param cache The acts cache. +/// @param nPoints 3D array of containing the number of bins for each axis. +/// @param min (min_x, min_y, min_z) +/// @param max (max_x, max_y, max_z) +/// @return A strided covfie field. +template +auto newBuilder(const magnetic_field_t& magneticField, + typename magnetic_field_t::Cache& cache, + const std::array& nPoints, + const Acts::Vector3& min, const Acts::Vector3& max) { + using Field = covfie::field; + + // Hack to avoid the fact that the domain of ACTS magnetic fields is defined + // as a half-open interval. Has the potential to introduce very minor + // floating point errors, but no easy way to fix this right now. + // TODO: Fix the aforementioned problem. + std::vector maxima = { + std::nexttoward(max[0], -std::numeric_limits::infinity()), + std::nexttoward(max[1], -std::numeric_limits::infinity()), + std::nexttoward(max[1], -std::numeric_limits::infinity()), + }; + + Field field(covfie::make_parameter_pack( + Field::backend_t::configuration_t{nPoints[0], nPoints[1], nPoints[2]})); + + Field::view_t view(field); + + std::array sampleSpacing = { + (max.x() - min.x()) / (nPoints[0] - 1), + (max.y() - min.y()) / (nPoints[1] - 1), + (max.z() - min.z()) / (nPoints[2] - 1)}; + + for (std::size_t x = 0; x < nPoints[0]; x++) { + for (std::size_t y = 0; y < nPoints[1]; y++) { + for (std::size_t z = 0; z < nPoints[2]; z++) { + Acts::Vector3 position{ + std::min(x * sampleSpacing[0] + min[0], maxima[0]), + std::min(y * sampleSpacing[1] + min[1], maxima[1]), + std::min(z * sampleSpacing[2] + min[2], maxima[2])}; + + Field::view_t::output_t& p = view.at(x, y, z); + Result result = magneticField.getField(position, cache); + + if (!result.ok()) { + throw std::runtime_error("Field lookup failed!"); + } + + Acts::Vector3 rv = *result; + p[0] = static_cast(rv[0]); + p[1] = static_cast(rv[1]); + p[2] = static_cast(rv[2]); + } + } + } + + return field; +} + +/// @brief Generate the affine covfie configuration (scaling and rotation) +/// given the size of the field (min and max) +/// +/// @param nPoints 3D array of containing the number of bins for each axis. +/// @param min (min_x, min_y, min_z) +/// @param max (max_x, max_y, max_z) +/// @return The affine field configuration. +template +typename backend_t::configuration_t affineConfiguration( + const std::array& nPoints, const Acts::Vector3& min, + const Acts::Vector3& max) { + auto scaling = covfie::algebra::affine<3>::scaling( + static_cast((nPoints[0] - 1) / (max[0] - min[0])), + static_cast((nPoints[1] - 1) / (max[1] - min[1])), + static_cast((nPoints[2] - 1) / (max[2] - min[2]))); + + auto translation = covfie::algebra::affine<3>::translation( + static_cast(-min[0]), static_cast(-min[1]), + static_cast(-min[2])); + + return {scaling * translation}; +} + +/// @brief Uses std::nextafter to generates a clamp backend +/// configuration where arguments min and max hold floating point values. +/// @param min (min_x, min_y, min_z) +/// @param max (max_x, max_y, max_z) +/// @return The clamp field configuration. +template +typename backend_t::configuration_t clampConfigurationFloat( + const Acts::Vector3& min, const Acts::Vector3& max) { + return {{std::nextafter(static_cast(min[0]), + std::numeric_limits::infinity()), + std::nextafter(static_cast(min[1]), + std::numeric_limits::infinity()), + std::nextafter(static_cast(min[2]), + std::numeric_limits::infinity())}, + {std::nextafter(static_cast(max[0]), + -std::numeric_limits::infinity()), + std::nextafter(static_cast(max[1]), + -std::numeric_limits::infinity()), + std::nextafter(static_cast(max[2]), + -std::numeric_limits::infinity())}}; +} + +/// @brief Creates a covfie field from a generic magnetic field. +/// @param magneticField The generic magnetic field. +/// @param cache The cache. +/// @param nPoints 3D array of containing the number of bins for each axis. +/// @param min (min_x, min_y, min_z) +/// @param max (max_x, max_y, max_z) +/// @return A clamp affine linear strided covfie field. +template +InterpolatedField covfieFieldLinear(const magnetic_field_t& magneticField, + typename magnetic_field_t::Cache& cache, + const std::array& nPoints, + const Acts::Vector3& min, + const Acts::Vector3& max) { + auto builder = newBuilder(magneticField, cache, nPoints, min, max); + InterpolatedField field(covfie::make_parameter_pack( + clampConfigurationFloat(min, max), + affineConfiguration(nPoints, min, + max), + InterpolatedField::backend_t::backend_t::backend_t::configuration_t{}, + builder.backend())); + + return field; +} + +/// @brief Creates a covfie field from a magnetic field provider by sampling it. +/// @param magneticField The acts magnetic field provider. +/// @param cache The acts cache. +/// @param nPoints 3D array of containing the number of bins for each axis. +/// @param min (min_x, min_y, min_z) +/// @param max (max_x, max_y, max_z) +/// @return A clamp affine linear strided covfie field. +InterpolatedField covfieField(const Acts::MagneticFieldProvider& magneticField, + Acts::MagneticFieldProvider::Cache& cache, + const std::array& nPoints, + const Acts::Vector3& min, + const Acts::Vector3& max) { + return covfieFieldLinear(magneticField, cache, nPoints, min, max); +} + +/// @brief Creates a covfie field from an interpolated magnetic field. +/// @param magneticField The acts interpolated magnetic field. +/// @return A clamp affine linear strided covfie field. +InterpolatedField covfieField( + const Acts::InterpolatedMagneticField& magneticField) { + Acts::MagneticFieldContext ctx; + auto cache = magneticField.makeCache(ctx); + const std::vector& old_min = magneticField.getMin(); + const std::vector& old_max = magneticField.getMax(); + const std::vector& old_nbins = magneticField.getNBins(); + Acts::Vector3 min{old_min.at(0), old_min.at(1), old_min.at(2)}; + Acts::Vector3 max{old_max.at(0), old_max.at(1), old_max.at(2)}; + std::array nPoints{old_nbins.at(0), old_nbins.at(1), + old_nbins.at(2)}; + return covfieFieldLinear(magneticField, cache, nPoints, min, max); +} + +/// @brief Creates a covfie field from a constant B field. +/// @param magneticField The acts constant magnetic field. +/// @return A constant covfie field. +ConstantField covfieField(const Acts::ConstantBField& magneticField) { + auto B = magneticField.getField(); + ConstantField field( + covfie::make_parameter_pack(ConstantField::backend_t::configuration_t{ + static_cast(B[0]), static_cast(B[1]), + static_cast(B[2])})); + return field; +} + +} // namespace Acts::CovfiePlugin diff --git a/Tests/DownstreamProject/CMakeLists.txt b/Tests/DownstreamProject/CMakeLists.txt index 27f2f10583b..d41277017a5 100644 --- a/Tests/DownstreamProject/CMakeLists.txt +++ b/Tests/DownstreamProject/CMakeLists.txt @@ -7,7 +7,7 @@ find_package( Acts CONFIG REQUIRED - COMPONENTS Core Fatras PluginJson PluginLegacy PluginTGeo + COMPONENTS Core Fatras PluginJson PluginLegacy PluginTGeo PluginCovfie ) # place artifacts in GNU-like paths, e.g. binaries in `/bin` @@ -24,7 +24,13 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY add_executable(ShowActsVersion ShowActsVersion.cpp) target_link_libraries( ShowActsVersion - PRIVATE ActsCore ActsFatras ActsPluginJson ActsPluginLegacy ActsPluginTGeo + PRIVATE + ActsCore + ActsFatras + ActsPluginJson + ActsPluginLegacy + ActsPluginTGeo + ActsPluginCovfie ) option(DD4HEP "Build with DD4hep" ON) diff --git a/Tests/UnitTests/Plugins/CMakeLists.txt b/Tests/UnitTests/Plugins/CMakeLists.txt index a3ce9f373ef..0b48b9782f8 100644 --- a/Tests/UnitTests/Plugins/CMakeLists.txt +++ b/Tests/UnitTests/Plugins/CMakeLists.txt @@ -9,3 +9,4 @@ add_subdirectory_if(TGeo ACTS_BUILD_PLUGIN_TGEO) add_subdirectory_if(EDM4hep ACTS_BUILD_PLUGIN_EDM4HEP) add_subdirectory_if(FpeMonitoring ACTS_BUILD_PLUGIN_FPEMON) add_subdirectory_if(Podio ACTS_BUILD_PLUGIN_PODIO) +add_subdirectory_if(Covfie ACTS_BUILD_PLUGIN_TRACCC) diff --git a/Tests/UnitTests/Plugins/Covfie/CMakeLists.txt b/Tests/UnitTests/Plugins/Covfie/CMakeLists.txt new file mode 100644 index 00000000000..a95e6b65e25 --- /dev/null +++ b/Tests/UnitTests/Plugins/Covfie/CMakeLists.txt @@ -0,0 +1,2 @@ +set(unittest_extra_libraries ActsPluginCovfie) +add_unittest(CovfieFieldConversion CovfieFieldConversionTest.cpp) diff --git a/Tests/UnitTests/Plugins/Covfie/CovfieFieldConversionTest.cpp b/Tests/UnitTests/Plugins/Covfie/CovfieFieldConversionTest.cpp new file mode 100644 index 00000000000..3bf85cccf22 --- /dev/null +++ b/Tests/UnitTests/Plugins/Covfie/CovfieFieldConversionTest.cpp @@ -0,0 +1,217 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2023 CERN for the benefit of the Acts project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Acts include(s) +#include "Acts/Definitions/Units.hpp" +#include "Acts/MagneticField/ConstantBField.hpp" +#include "Acts/MagneticField/MagneticFieldContext.hpp" +#include "Acts/MagneticField/MagneticFieldProvider.hpp" +#include "Acts/MagneticField/SolenoidBField.hpp" + +// Covfie Plugin include(s) +#include "Acts/Plugins/Covfie/FieldConversion.hpp" + +// System include(s) +#include +#include +#include +#include + +// Boost include(s) +#include + +using namespace Acts::UnitLiterals; + +template +void checkMagneticFieldEqual(const Acts::MagneticFieldProvider& fieldProvider, + Acts::MagneticFieldProvider::Cache& cache, + view_t view, iterator_t points, + float error_margin_half_width) { + for (auto point : points) { + auto x = point[0], y = point[1], z = point[2]; + + auto lookupResult = fieldProvider.getField(Acts::Vector3{x, y, z}, cache); + if (!lookupResult.ok()) { + throw std::runtime_error{"Field lookup failure"}; + } + auto actsValueX = (*lookupResult)[0], actsValueY = (*lookupResult)[1], + actsValueZ = (*lookupResult)[2]; + + auto covfieValues = view.at(x, y, z); + auto covfieValueX = covfieValues[0], covfieValueY = covfieValues[1], + covfieValueZ = covfieValues[2]; + + auto isEqual = + std::abs(covfieValueX - actsValueX) <= error_margin_half_width && + std::abs(covfieValueY - actsValueY) <= error_margin_half_width && + std::abs(covfieValueZ - actsValueZ) <= error_margin_half_width; + + std::stringstream ss; + ss << "Fields are not equal at position (" << x << ", " << y << ", " << z + << "). Acts: (" << actsValueX << ", " << actsValueY << ", " << actsValueZ + << "), Covfie: (" << covfieValueX << ", " << covfieValueY << ", " + << covfieValueZ << ")" << std::endl; + + BOOST_CHECK_MESSAGE(isEqual, ss.str()); + } +} + +BOOST_AUTO_TEST_SUITE(CovfiePlugin) + +BOOST_AUTO_TEST_CASE(InterpolatedMagneticField1) { + auto localToGlobalBin_xyz = [](std::array binsXYZ, + std::array nBinsXYZ) { + return (binsXYZ.at(0) * (nBinsXYZ.at(1) * nBinsXYZ.at(2)) + + binsXYZ.at(1) * nBinsXYZ.at(2) + binsXYZ.at(2)); + }; + + std::vector xPos = {0., 1., 2., 3.}; + std::vector yPos = {0., 1., 2., 3.}; + std::vector zPos = {0., 1., 2., 3.}; + + std::vector bField_xyz; + for (int i = 0; i < 64; i++) { + bField_xyz.push_back(Acts::Vector3(i, i, i)); + } + + Acts::MagneticFieldContext fieldContext; + auto actsField = Acts::fieldMapXYZ(localToGlobalBin_xyz, xPos, yPos, zPos, + bField_xyz, 1, 1, false); + Acts::MagneticFieldProvider::Cache cache = actsField.makeCache(fieldContext); + + Acts::CovfiePlugin::InterpolatedField field = + Acts::CovfiePlugin::covfieField(actsField); + typename Acts::CovfiePlugin::InterpolatedField::view_t view(field); + + std::array, 14> points = {{ + {0.f, 0.f, 0.f}, + {1.f, 1.f, 1.f}, + {2.f, 2.f, 2.f}, + {2.9f, 2.9f, 2.9f}, + {1.2f, 2.5f, 0.8f}, + {0.7f, 1.9f, 2.3f}, + {2.1f, 0.3f, 1.5f}, + {0.4f, 2.8f, 2.9f}, + {1.6f, 1.2f, 0.5f}, + {2.3f, 0.6f, 2.2f}, + {1.1f, 2.7f, 1.3f}, + {0.9f, 1.4f, 2.7f}, + {2.4f, 1.8f, 0.9f}, + {0.6f, 2.2f, 2.1f}, + }}; + + checkMagneticFieldEqual(actsField, cache, view, points, 0.0001); +} + +BOOST_AUTO_TEST_CASE(InterpolatedMagneticField2) { + auto localToGlobalBin_xyz = [](std::array binsXYZ, + std::array nBinsXYZ) { + return (binsXYZ.at(0) * (nBinsXYZ.at(1) * nBinsXYZ.at(2)) + + binsXYZ.at(1) * nBinsXYZ.at(2) + binsXYZ.at(2)); + }; + + std::vector xPos = {8., 12., 16., 20.}; + std::vector yPos = {8., 12., 16., 20.}; + std::vector zPos = {8., 12., 16., 20.}; + + std::vector bField_xyz; + for (int i = 0; i < 64; i++) { + bField_xyz.push_back(Acts::Vector3(i, i * i * 0.01, i)); + } + + Acts::MagneticFieldContext fieldContext; + auto actsField = Acts::fieldMapXYZ(localToGlobalBin_xyz, xPos, yPos, zPos, + bField_xyz, 1, 1, false); + Acts::MagneticFieldProvider::Cache cache = actsField.makeCache(fieldContext); + + Acts::CovfiePlugin::InterpolatedField field = + Acts::CovfiePlugin::covfieField(actsField); + typename Acts::CovfiePlugin::InterpolatedField::view_t view(field); + + std::array, 14> points = {{ + {8.f, 8.f, 8.f}, + {12.f, 12.f, 12.f}, + {16.f, 16.f, 16.f}, + {19.9, 19.9, 19.9}, + {8.1f, 10.2f, 12.3f}, + {9.4f, 11.5f, 13.6f}, + {10.7f, 12.8f, 14.9f}, + {11.0f, 13.1f, 15.2f}, + {12.3f, 14.4f, 16.5f}, + {13.6f, 15.7f, 17.8f}, + {14.9f, 16.0f, 18.1f}, + {16.2f, 17.3f, 19.4f}, + {17.5f, 18.6f, 19.7f}, + {18.8f, 19.9f, 14.0f}, + }}; + + checkMagneticFieldEqual(actsField, cache, view, points, 0.0001f); +} + +BOOST_AUTO_TEST_CASE(ConstantMagneticField1) { + Acts::ConstantBField actsField(Acts::Vector3{1.3f, 2.5f, 2.f}); + Acts::MagneticFieldContext ctx; + Acts::MagneticFieldProvider::Cache cache = actsField.makeCache(ctx); + + Acts::CovfiePlugin::ConstantField field = + Acts::CovfiePlugin::covfieField(actsField); + typename Acts::CovfiePlugin::ConstantField::view_t view(field); + + std::array, 13> points = {{ + {8.f, 8.f, 8.f}, + {12.f, 12.f, 12.f}, + {16.f, 16.f, 16.f}, + {8.1f, 10.2f, 12.3f}, + {9.4f, 11.5f, 13.6f}, + {10.7f, 12.8f, 14.9f}, + {11.0f, 13.1f, 15.2f}, + {12.3f, 14.4f, 16.5f}, + {13.6f, 15.7f, 17.8f}, + {14.9f, 16.0f, 18.1f}, + {16.2f, 17.3f, 19.4f}, + {17.5f, 18.6f, 19.7f}, + {18.8f, 19.9f, 14.0f}, + }}; + + checkMagneticFieldEqual(actsField, cache, view, points, 0.0001f); +} + +BOOST_AUTO_TEST_CASE(SolenoidBField1) { + Acts::SolenoidBField::Config cfg{}; + cfg.length = 5.8_m; + cfg.radius = (2.56 + 2.46) * 0.5 * 0.5_m; + cfg.nCoils = 1154; + cfg.bMagCenter = 2_T; + Acts::SolenoidBField actsField(cfg); + Acts::MagneticFieldContext ctx; + Acts::MagneticFieldProvider::Cache cache = actsField.makeCache(ctx); + + Acts::CovfiePlugin::InterpolatedField field = Acts::CovfiePlugin::covfieField( + actsField, cache, {21UL, 21UL, 21UL}, {0., 0., 0.}, {20., 20., 20.}); + typename Acts::CovfiePlugin::InterpolatedField::view_t view(field); + + std::array, 13> points = {{ + {8.f, 8.f, 8.f}, + {12.f, 12.f, 12.f}, + {16.f, 16.f, 16.f}, + {8.1f, 10.2f, 12.3f}, + {9.4f, 11.5f, 13.6f}, + {10.7f, 12.8f, 14.9f}, + {11.0f, 13.1f, 15.2f}, + {12.3f, 14.4f, 16.5f}, + {13.6f, 15.7f, 17.8f}, + {14.9f, 16.0f, 18.1f}, + {16.2f, 17.3f, 19.4f}, + {17.5f, 18.6f, 19.7f}, + {18.8f, 19.9f, 14.0f}, + }}; + + checkMagneticFieldEqual(actsField, cache, view, points, 0.0001); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/cmake/ActsConfig.cmake.in b/cmake/ActsConfig.cmake.in index 127d8b14786..907e0469cc2 100644 --- a/cmake/ActsConfig.cmake.in +++ b/cmake/ActsConfig.cmake.in @@ -103,6 +103,10 @@ if(PluginDetray IN_LIST Acts_COMPONENTS) find_dependency(detray @detray_VERSION@ CONFIG EXACT) endif() +if (PluginCovfie IN_LIST Acts_COMPONENTS) + find_dependency(covfie @covfie_VERSION@ CONFIG EXACT) +endif() + # load **all** available components. we can not just include the requested # components since there can be interdependencies between them. if(NOT Acts_FIND_QUIETLY)