File indexing completed on 2026-09-29 08:24:13
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <boost/test/unit_test.hpp>
0010
0011 #include "Acts/Material/BinnedSurfaceMaterial.hpp"
0012 #include "Acts/Material/GridSurfaceMaterial.hpp"
0013 #include "Acts/Material/HomogeneousSurfaceMaterial.hpp"
0014 #include "Acts/Material/Material.hpp"
0015 #include "Acts/Material/MaterialSlab.hpp"
0016 #include "Acts/Material/MergedMaterialMarker.hpp"
0017 #include "Acts/Material/ProtoSurfaceMaterial.hpp"
0018 #include "Acts/Utilities/AxisDefinitions.hpp"
0019 #include "Acts/Utilities/AxisSpec.hpp"
0020 #include "Acts/Utilities/BinUtility.hpp"
0021 #include "Acts/Utilities/IAxis.hpp"
0022 #include "Acts/Utilities/MultiAxisSpec.hpp"
0023 #include "ActsPlugins/Json/SurfaceMaterialJsonConverter.hpp"
0024 #include "ActsPlugins/Json/detail/MaterialJsonContext.hpp"
0025 #include "ActsTests/CommonHelpers/FloatComparisons.hpp"
0026
0027 #include <array>
0028 #include <memory>
0029 #include <numbers>
0030 #include <stdexcept>
0031 #include <variant>
0032 #include <vector>
0033
0034 #include <nlohmann/json.hpp>
0035
0036 using namespace Acts;
0037
0038 namespace ActsTests {
0039
0040 namespace {
0041
0042
0043 std::vector<MaterialSlab> testSlabs() {
0044 std::vector<MaterialSlab> material;
0045 material.emplace_back(Material::Vacuum(), 0.0);
0046 material.emplace_back(Material::fromMolarDensity(1.0, 2.0, 3.0, 4.0, 5.0),
0047 1.0);
0048 material.emplace_back(
0049 Material::fromMolarDensity(11.0, 12.0, 13.0, 14.0, 15.0), 2.0);
0050 material.emplace_back(
0051 Material::fromMolarDensity(21.0, 22.0, 23.0, 24.0, 25.0), 3.0);
0052 return material;
0053 }
0054
0055
0056 std::unique_ptr<IAxis> testAxis0() {
0057 return IAxis::createEquidistant(AxisBoundaryType::Bound, -1., 1., 2);
0058 }
0059
0060 std::unique_ptr<IAxis> testAxis1() {
0061 return IAxis::createEquidistant(AxisBoundaryType::Closed, -std::numbers::pi,
0062 std::numbers::pi, 4);
0063 }
0064
0065
0066
0067 std::vector<std::vector<std::size_t>> testIndexPayload() {
0068 return {std::vector<std::size_t>{1u, 1u, 0u, 2u},
0069 std::vector<std::size_t>{0u, 3u, 3u, 0u}};
0070 }
0071
0072 std::unique_ptr<GridSurfaceMaterial> makeIndexed() {
0073 auto axis0 = testAxis0();
0074 auto axis1 = testAxis1();
0075 return GridSurfaceMaterial::createIndexed(*axis0, *axis1, testSlabs(),
0076 testIndexPayload());
0077 }
0078
0079 std::unique_ptr<GridSurfaceMaterial> makeGloballyIndexed(
0080 std::shared_ptr<std::vector<MaterialSlab>> store = nullptr) {
0081 if (store == nullptr) {
0082 store = std::make_shared<std::vector<MaterialSlab>>(testSlabs());
0083 }
0084 auto axis0 = testAxis0();
0085 auto axis1 = testAxis1();
0086 return GridSurfaceMaterial::createGloballyIndexed(
0087 *axis0, *axis1, std::move(store), testIndexPayload());
0088 }
0089
0090 std::unique_ptr<GridSurfaceMaterial> makeDirect() {
0091 auto slabs = testSlabs();
0092 std::vector<std::vector<MaterialSlab>> payload{
0093 {slabs[1], slabs[1], slabs[0], slabs[2]},
0094 {slabs[0], slabs[3], slabs[3], slabs[0]}};
0095 auto axis0 = testAxis0();
0096 auto axis1 = testAxis1();
0097 return GridSurfaceMaterial::createDirect(*axis0, *axis1, payload);
0098 }
0099
0100
0101 std::vector<Vector2> testPoints() {
0102 return {{-0.5, -std::numbers::pi * 0.75}, {-0.5, -std::numbers::pi / 4.},
0103 {-0.5, std::numbers::pi / 4.}, {-0.5, std::numbers::pi * 0.75},
0104 {0.5, -std::numbers::pi * 0.75}, {0.5, -std::numbers::pi / 4.},
0105 {0.5, std::numbers::pi / 4.}, {0.5, std::numbers::pi * 0.75}};
0106 }
0107
0108 BinUtility testBinUtility2D() {
0109 BinUtility bUtility(2, -1., 1., open, AxisDirection::AxisX);
0110 bUtility += BinUtility(3, -3., 3., open, AxisDirection::AxisY);
0111 return bUtility;
0112 }
0113
0114
0115 MaterialSlabMatrix testMatrix2D() {
0116 auto slabs = testSlabs();
0117 MaterialSlabMatrix matrix;
0118 for (std::size_t i1 = 0; i1 < 3; ++i1) {
0119 MaterialSlabVector row;
0120 for (std::size_t i0 = 0; i0 < 2; ++i0) {
0121 row.push_back(slabs[(i1 * 2 + i0) % slabs.size()]);
0122 }
0123 matrix.push_back(std::move(row));
0124 }
0125 return matrix;
0126 }
0127
0128
0129 std::unique_ptr<const ISurfaceMaterial> roundTrip(
0130 const ISurfaceMaterial& material) {
0131 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(material);
0132 return SurfaceMaterialJsonConverter::fromJson(jMaterial);
0133 }
0134
0135 }
0136
0137 BOOST_AUTO_TEST_SUITE(JsonSuite)
0138
0139 BOOST_AUTO_TEST_CASE(HomogeneousSurfaceMaterialRoundTrip) {
0140 HomogeneousSurfaceMaterial hsm(
0141 MaterialSlab(Material::fromMolarDensity(1.0, 2.0, 3.0, 4.0, 5.0), 1.5),
0142 1., MappingType::PostMapping);
0143
0144 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(hsm);
0145 BOOST_CHECK_EQUAL(jMaterial["type"], "homogeneous");
0146
0147 auto read = roundTrip(hsm);
0148 BOOST_REQUIRE(read != nullptr);
0149 const auto* typed =
0150 dynamic_cast<const HomogeneousSurfaceMaterial*>(read.get());
0151 BOOST_REQUIRE(typed != nullptr);
0152 BOOST_CHECK(typed->mappingType() == MappingType::PostMapping);
0153 CHECK_CLOSE_ABS(typed->materialSlab(Vector2{0., 0.}).thickness(), 1.5, 1e-5);
0154 }
0155
0156 BOOST_AUTO_TEST_CASE(BinnedSurfaceMaterialRoundTrip) {
0157 BinnedSurfaceMaterial bsm(testBinUtility2D(), testMatrix2D(), 1.,
0158 MappingType::Sensor);
0159
0160 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(bsm);
0161 BOOST_CHECK_EQUAL(jMaterial["type"], "binned");
0162
0163 auto read = roundTrip(bsm);
0164 BOOST_REQUIRE(read != nullptr);
0165 const auto* typed = dynamic_cast<const BinnedSurfaceMaterial*>(read.get());
0166 BOOST_REQUIRE(typed != nullptr);
0167 BOOST_CHECK(typed->mappingType() == MappingType::Sensor);
0168 BOOST_CHECK(typed->binUtility() == bsm.binUtility());
0169
0170 for (double x : {-0.5, 0.5}) {
0171 for (double y : {-2., 0., 2.}) {
0172 Vector2 lp{x, y};
0173 CHECK_CLOSE_ABS(typed->materialSlab(lp).thickness(),
0174 bsm.materialSlab(lp).thickness(), 1e-5);
0175 }
0176 }
0177 }
0178
0179 BOOST_AUTO_TEST_CASE(ProtoSurfaceMaterialRoundTrip) {
0180 ProtoSurfaceMaterial psm(testBinUtility2D(), MappingType::PreMapping);
0181
0182 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(psm);
0183 BOOST_CHECK_EQUAL(jMaterial["type"], "proto");
0184 BOOST_CHECK_EQUAL(jMaterial["mapMaterial"], true);
0185
0186 auto read = roundTrip(psm);
0187 BOOST_REQUIRE(read != nullptr);
0188 const auto* typed = dynamic_cast<const ProtoSurfaceMaterial*>(read.get());
0189 BOOST_REQUIRE(typed != nullptr);
0190 BOOST_CHECK(typed->mappingType() == MappingType::PreMapping);
0191 BOOST_CHECK(typed->binning() == psm.binning());
0192 }
0193
0194 BOOST_AUTO_TEST_CASE(ProtoGridSurfaceMaterialRoundTrip) {
0195 MultiAxisSpec2D spec{std::array<AxisSpec, 2u>{
0196 AxisSpec::Equidistant(4u, -1., 1., AxisBoundaryType::Bound,
0197 AxisDirection::AxisX),
0198 AxisSpec::DeferredVariable({0., 0.25, 1.}, AxisBoundaryType::Bound,
0199 AxisDirection::AxisY)}};
0200 ProtoGridSurfaceMaterial pgsm(spec, MappingType::PostMapping);
0201
0202 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(pgsm);
0203 BOOST_CHECK_EQUAL(jMaterial["type"], "proto-grid");
0204
0205 auto read = roundTrip(pgsm);
0206 BOOST_REQUIRE(read != nullptr);
0207 const auto* typed = dynamic_cast<const ProtoGridSurfaceMaterial*>(read.get());
0208 BOOST_REQUIRE(typed != nullptr);
0209 BOOST_CHECK(typed->mappingType() == MappingType::PostMapping);
0210 BOOST_CHECK(typed->binning() == pgsm.binning());
0211 }
0212
0213 BOOST_AUTO_TEST_CASE(MergedMaterialMarkerRoundTrip) {
0214 MergedMaterialMarker marker;
0215
0216 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(marker);
0217 BOOST_CHECK_EQUAL(jMaterial["type"], "merged-material-marker");
0218
0219 auto read = roundTrip(marker);
0220 BOOST_REQUIRE(read != nullptr);
0221 BOOST_CHECK(dynamic_cast<const MergedMaterialMarker*>(read.get()) != nullptr);
0222 }
0223
0224 BOOST_AUTO_TEST_CASE(IndexedGridMaterialRoundTrip) {
0225 auto ism = makeIndexed();
0226
0227 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(*ism);
0228 BOOST_CHECK_EQUAL(jMaterial["type"], "grid");
0229 BOOST_CHECK_EQUAL(jMaterial["accessor"]["type"], "indexed");
0230 BOOST_CHECK_EQUAL(jMaterial["accessor"]["grid"]["axes"].size(), 2u);
0231
0232 BOOST_CHECK(!jMaterial.contains("bound_to_grid_local"));
0233 BOOST_CHECK(!jMaterial.contains("global_to_grid_local"));
0234
0235 auto read = roundTrip(*ism);
0236 BOOST_REQUIRE(read != nullptr);
0237 const auto* typed = dynamic_cast<const GridSurfaceMaterial*>(read.get());
0238 BOOST_REQUIRE(typed != nullptr);
0239 const auto& indexed =
0240 std::get<GridSurfaceMaterial::Indexed>(typed->storage());
0241 BOOST_CHECK_EQUAL(indexed.material.size(), testSlabs().size());
0242
0243 for (const Vector2& lp : testPoints()) {
0244 CHECK_CLOSE_ABS(typed->materialSlab(lp).thickness(),
0245 ism->materialSlab(lp).thickness(), 1e-5);
0246 }
0247 }
0248
0249 BOOST_AUTO_TEST_CASE(GloballyIndexedGridMaterialRoundTrip) {
0250 auto gism = makeGloballyIndexed();
0251
0252 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(*gism);
0253 BOOST_CHECK_EQUAL(jMaterial["type"], "grid");
0254 BOOST_CHECK_EQUAL(jMaterial["accessor"]["type"], "globally_indexed");
0255
0256
0257 BOOST_CHECK(jMaterial["accessor"].contains("storage_vector"));
0258 BOOST_CHECK(!jMaterial["accessor"].contains("store"));
0259
0260 auto read = roundTrip(*gism);
0261 BOOST_REQUIRE(read != nullptr);
0262 const auto* typed = dynamic_cast<const GridSurfaceMaterial*>(read.get());
0263 BOOST_REQUIRE(typed != nullptr);
0264
0265 const auto& global =
0266 std::get<GridSurfaceMaterial::GloballyIndexed>(typed->storage());
0267 BOOST_REQUIRE(global.material != nullptr);
0268 BOOST_CHECK_EQUAL(global.material->size(), testSlabs().size());
0269
0270 for (const Vector2& lp : testPoints()) {
0271 CHECK_CLOSE_ABS(typed->materialSlab(lp).thickness(),
0272 gism->materialSlab(lp).thickness(), 1e-5);
0273 }
0274 }
0275
0276 BOOST_AUTO_TEST_CASE(GloballyIndexedSharedStoreThroughContext) {
0277 auto store = std::make_shared<std::vector<MaterialSlab>>(testSlabs());
0278 auto first = makeGloballyIndexed(store);
0279 auto second = makeGloballyIndexed(store);
0280
0281 auto encodeContext =
0282 SurfaceMaterialJsonConverter::EncodeContext::withStoreTable();
0283 const auto& config = SurfaceMaterialJsonConverter::Config::defaultConfig();
0284 nlohmann::json jFirst =
0285 SurfaceMaterialJsonConverter::toJson(*first, config, &encodeContext);
0286 nlohmann::json jSecond =
0287 SurfaceMaterialJsonConverter::toJson(*second, config, &encodeContext);
0288
0289
0290 BOOST_REQUIRE_EQUAL(encodeContext.stores().size(), 1u);
0291 BOOST_CHECK(encodeContext.stores()[0] == store);
0292 BOOST_CHECK_EQUAL(jFirst["accessor"]["store"], 0u);
0293 BOOST_CHECK_EQUAL(jSecond["accessor"]["store"], 0u);
0294 BOOST_CHECK(!jFirst["accessor"].contains("storage_vector"));
0295 BOOST_CHECK(!jSecond["accessor"].contains("storage_vector"));
0296
0297 SurfaceMaterialJsonConverter::DecodeContext decodeContext;
0298 decodeContext.setStores(
0299 {std::make_shared<std::vector<MaterialSlab>>(testSlabs())});
0300
0301 auto readFirst =
0302 SurfaceMaterialJsonConverter::fromJson(jFirst, config, &decodeContext);
0303 auto readSecond =
0304 SurfaceMaterialJsonConverter::fromJson(jSecond, config, &decodeContext);
0305 BOOST_REQUIRE(readFirst != nullptr);
0306 BOOST_REQUIRE(readSecond != nullptr);
0307
0308 const auto& globalFirst = std::get<GridSurfaceMaterial::GloballyIndexed>(
0309 dynamic_cast<const GridSurfaceMaterial&>(*readFirst).storage());
0310 const auto& globalSecond = std::get<GridSurfaceMaterial::GloballyIndexed>(
0311 dynamic_cast<const GridSurfaceMaterial&>(*readSecond).storage());
0312
0313 BOOST_CHECK(globalFirst.material == globalSecond.material);
0314 BOOST_CHECK(globalFirst.material == decodeContext.store(0u));
0315 }
0316
0317 BOOST_AUTO_TEST_CASE(DistinctStoresGetSequentialIds) {
0318 auto storeA = std::make_shared<std::vector<MaterialSlab>>(testSlabs());
0319
0320 auto storeB = std::make_shared<std::vector<MaterialSlab>>(testSlabs());
0321
0322 auto ctx = SurfaceMaterialJsonConverter::EncodeContext::withStoreTable();
0323 const auto& config = SurfaceMaterialJsonConverter::Config::defaultConfig();
0324 nlohmann::json jA = SurfaceMaterialJsonConverter::toJson(
0325 *makeGloballyIndexed(storeA), config, &ctx);
0326 nlohmann::json jB = SurfaceMaterialJsonConverter::toJson(
0327 *makeGloballyIndexed(storeB), config, &ctx);
0328 nlohmann::json jA2 = SurfaceMaterialJsonConverter::toJson(
0329 *makeGloballyIndexed(storeA), config, &ctx);
0330
0331 BOOST_CHECK_EQUAL(jA["accessor"]["store"], 0u);
0332 BOOST_CHECK_EQUAL(jB["accessor"]["store"], 1u);
0333 BOOST_CHECK_EQUAL(jA2["accessor"]["store"], 0u);
0334 BOOST_REQUIRE_EQUAL(ctx.stores().size(), 2u);
0335 BOOST_CHECK(ctx.stores()[0] == storeA);
0336 BOOST_CHECK(ctx.stores()[1] == storeB);
0337 }
0338
0339 BOOST_AUTO_TEST_CASE(StoreReferenceWithoutTableThrows) {
0340 auto store = std::make_shared<std::vector<MaterialSlab>>(testSlabs());
0341 auto ctx = SurfaceMaterialJsonConverter::EncodeContext::withStoreTable();
0342 const auto& config = SurfaceMaterialJsonConverter::Config::defaultConfig();
0343 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(
0344 *makeGloballyIndexed(store), config, &ctx);
0345
0346
0347 BOOST_CHECK_THROW(SurfaceMaterialJsonConverter::fromJson(jMaterial),
0348 std::invalid_argument);
0349
0350
0351 SurfaceMaterialJsonConverter::DecodeContext empty;
0352 empty.setStores({});
0353 BOOST_CHECK_THROW(
0354 SurfaceMaterialJsonConverter::fromJson(jMaterial, config, &empty),
0355 std::invalid_argument);
0356
0357 nlohmann::json jOutOfRange = jMaterial;
0358 jOutOfRange["accessor"]["store"] = 7u;
0359 SurfaceMaterialJsonConverter::DecodeContext oneEntry;
0360 oneEntry.setStores({store});
0361 BOOST_CHECK_THROW(
0362 SurfaceMaterialJsonConverter::fromJson(jOutOfRange, config, &oneEntry),
0363 std::invalid_argument);
0364 }
0365
0366 BOOST_AUTO_TEST_CASE(DirectGridMaterialRoundTrip) {
0367 auto gsm = makeDirect();
0368
0369 nlohmann::json jMaterial = SurfaceMaterialJsonConverter::toJson(*gsm);
0370 BOOST_CHECK_EQUAL(jMaterial["type"], "grid");
0371 BOOST_CHECK_EQUAL(jMaterial["accessor"]["type"], "direct");
0372 BOOST_CHECK(!jMaterial["accessor"].contains("storage_vector"));
0373
0374 auto read = roundTrip(*gsm);
0375 BOOST_REQUIRE(read != nullptr);
0376 const auto* typed = dynamic_cast<const GridSurfaceMaterial*>(read.get());
0377 BOOST_REQUIRE(typed != nullptr);
0378 BOOST_CHECK(
0379 std::holds_alternative<GridSurfaceMaterial::Direct>(typed->storage()));
0380
0381 for (const Vector2& lp : testPoints()) {
0382 CHECK_CLOSE_ABS(typed->materialSlab(lp).thickness(),
0383 gsm->materialSlab(lp).thickness(), 1e-5);
0384 }
0385 }
0386
0387 BOOST_AUTO_TEST_CASE(EncoderCoversAllSurfaceMaterials) {
0388 const auto& cfg = SurfaceMaterialJsonConverter::Config::defaultConfig();
0389
0390 std::vector<std::shared_ptr<const ISurfaceMaterial>> materials;
0391 materials.push_back(std::make_shared<const HomogeneousSurfaceMaterial>(
0392 MaterialSlab(Material::fromMolarDensity(1.0, 2.0, 3.0, 4.0, 5.0), 1.)));
0393 materials.push_back(std::make_shared<const BinnedSurfaceMaterial>(
0394 testBinUtility2D(), testMatrix2D()));
0395 materials.push_back(
0396 std::make_shared<const ProtoSurfaceMaterial>(testBinUtility2D()));
0397 materials.push_back(std::make_shared<const ProtoGridSurfaceMaterial>(
0398 MultiAxisSpec2D{std::array<AxisSpec, 2u>{
0399 AxisSpec::Equidistant(2u, 0., 1., AxisBoundaryType::Bound,
0400 AxisDirection::AxisX),
0401 AxisSpec::Equidistant(2u, 0., 1., AxisBoundaryType::Bound,
0402 AxisDirection::AxisY)}}));
0403 materials.push_back(std::make_shared<const MergedMaterialMarker>());
0404 materials.push_back(makeIndexed());
0405 materials.push_back(makeGloballyIndexed());
0406 materials.push_back(makeDirect());
0407
0408 for (std::size_t im = 0; im < materials.size(); ++im) {
0409 const auto& material = materials[im];
0410 BOOST_TEST_CONTEXT("material " << im) {
0411
0412 BOOST_CHECK(cfg.encoder.hasFunction(*material));
0413 nlohmann::json jMaterial;
0414 BOOST_REQUIRE_NO_THROW(
0415 jMaterial = SurfaceMaterialJsonConverter::toJson(*material));
0416
0417 BOOST_CHECK(cfg.decoder.hasKind(jMaterial["type"].get<std::string>()));
0418 }
0419 }
0420
0421
0422 BOOST_CHECK(cfg.encoder.hasFunction<HomogeneousSurfaceMaterial>());
0423 BOOST_CHECK(cfg.encoder.hasFunction<BinnedSurfaceMaterial>());
0424 BOOST_CHECK(cfg.encoder.hasFunction<ProtoSurfaceMaterial>());
0425 BOOST_CHECK(cfg.encoder.hasFunction<ProtoGridSurfaceMaterial>());
0426 BOOST_CHECK(cfg.encoder.hasFunction<MergedMaterialMarker>());
0427 BOOST_CHECK(cfg.encoder.hasFunction<GridSurfaceMaterial>());
0428 BOOST_CHECK_EQUAL(cfg.encoder.size(), 6u);
0429 BOOST_CHECK_EQUAL(cfg.decoder.size(), 6u);
0430 }
0431
0432 BOOST_AUTO_TEST_CASE(MissingAndUnknownTypeTagThrow) {
0433 nlohmann::json jMissing;
0434 jMissing["mapMaterial"] = true;
0435 BOOST_CHECK_THROW(SurfaceMaterialJsonConverter::fromJson(jMissing),
0436 std::invalid_argument);
0437
0438 nlohmann::json jUnknown;
0439 jUnknown["mapMaterial"] = true;
0440 jUnknown["type"] = "not-a-material";
0441 BOOST_CHECK_THROW(SurfaceMaterialJsonConverter::fromJson(jUnknown),
0442 std::invalid_argument);
0443 }
0444
0445 BOOST_AUTO_TEST_CASE(UnmappedMaterialYieldsNoMaterial) {
0446 nlohmann::json jMaterial;
0447 jMaterial["type"] = "proto";
0448 jMaterial["mapMaterial"] = false;
0449 BOOST_CHECK(SurfaceMaterialJsonConverter::fromJson(jMaterial) == nullptr);
0450
0451
0452 ProtoSurfaceMaterial psm{BinUtility{}};
0453 nlohmann::json jProto = SurfaceMaterialJsonConverter::toJson(psm);
0454 BOOST_CHECK_EQUAL(jProto["mapMaterial"], false);
0455 BOOST_CHECK(SurfaceMaterialJsonConverter::fromJson(jProto) == nullptr);
0456 }
0457
0458 BOOST_AUTO_TEST_SUITE_END()
0459
0460 }