Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-09 08:30:24

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #include "ActsPlugins/Json/SurfaceMaterialJsonConverter.hpp"
0010 
0011 #include "Acts/Material/BinnedSurfaceMaterial.hpp"
0012 #include "Acts/Material/GridSurfaceMaterial.hpp"
0013 #include "Acts/Material/HomogeneousSurfaceMaterial.hpp"
0014 #include "Acts/Material/MaterialSlab.hpp"
0015 #include "Acts/Material/MergedMaterialMarker.hpp"
0016 #include "Acts/Material/ProtoSurfaceMaterial.hpp"
0017 #include "Acts/Utilities/BinUtility.hpp"
0018 #include "Acts/Utilities/IAxis.hpp"
0019 #include "Acts/Utilities/IMultiAxis.hpp"
0020 #include "ActsPlugins/Json/AxisSpecJsonConverter.hpp"
0021 #include "ActsPlugins/Json/GridJsonConverter.hpp"
0022 #include "ActsPlugins/Json/MaterialJsonConverter.hpp"
0023 #include "ActsPlugins/Json/UtilitiesJsonConverter.hpp"
0024 #include "ActsPlugins/Json/detail/MaterialJsonContext.hpp"
0025 
0026 #include <array>
0027 #include <cstddef>
0028 #include <memory>
0029 #include <stdexcept>
0030 #include <string>
0031 #include <type_traits>
0032 #include <utility>
0033 #include <variant>
0034 #include <vector>
0035 
0036 namespace {
0037 
0038 using namespace Acts;
0039 
0040 using EncodeContext = SurfaceMaterialJsonConverter::EncodeContext;
0041 using DecodeContext = SurfaceMaterialJsonConverter::DecodeContext;
0042 
0043 /// Payload type tags, shared between the encoder and the decoder
0044 constexpr const char* kHomogeneousTag = "homogeneous";
0045 constexpr const char* kBinnedTag = "binned";
0046 constexpr const char* kProtoTag = "proto";
0047 constexpr const char* kProtoGridTag = "proto-grid";
0048 constexpr const char* kMergedMarkerTag = "merged-material-marker";
0049 constexpr const char* kGridTag = "grid";
0050 
0051 /// Grid accessor tags
0052 constexpr const char* kDirectAccessorTag = "direct";
0053 constexpr const char* kIndexedAccessorTag = "indexed";
0054 constexpr const char* kGloballyIndexedAccessorTag = "globally_indexed";
0055 
0056 nlohmann::json homogeneousToJson(const HomogeneousSurfaceMaterial& material,
0057                                  EncodeContext& /*ctx*/) {
0058   nlohmann::json jMaterial;
0059   jMaterial[jsonKey().typekey] = kHomogeneousTag;
0060   jMaterial[jsonKey().maptype] = nlohmann::json(material.mappingType());
0061   jMaterial[jsonKey().mapkey] = true;
0062   nlohmann::json jSlab(material.materialSlab());
0063   jMaterial[jsonKey().datakey] = nlohmann::json::array({
0064       nlohmann::json::array({
0065           jSlab,
0066       }),
0067   });
0068   return jMaterial;
0069 }
0070 
0071 nlohmann::json binnedToJson(const BinnedSurfaceMaterial& material,
0072                             EncodeContext& /*ctx*/) {
0073   nlohmann::json jMaterial;
0074   jMaterial[jsonKey().typekey] = kBinnedTag;
0075   jMaterial[jsonKey().maptype] = nlohmann::json(material.mappingType());
0076   jMaterial[jsonKey().mapkey] = true;
0077 
0078   nlohmann::json jMatrix = nlohmann::json::array();
0079   for (const auto& mpVector : material.fullMaterial()) {
0080     nlohmann::json jVector = nlohmann::json::array();
0081     for (const auto& mp : mpVector) {
0082       jVector.push_back(nlohmann::json(mp));
0083     }
0084     jMatrix.push_back(std::move(jVector));
0085   }
0086   jMaterial[jsonKey().datakey] = std::move(jMatrix);
0087   jMaterial[jsonKey().binkey] = nlohmann::json(material.binUtility());
0088   return jMaterial;
0089 }
0090 
0091 nlohmann::json protoToJson(const ProtoSurfaceMaterial& material,
0092                            EncodeContext& /*ctx*/) {
0093   nlohmann::json jMaterial;
0094   jMaterial[jsonKey().typekey] = kProtoTag;
0095   jMaterial[jsonKey().maptype] = nlohmann::json(material.mappingType());
0096   // A proto material without any actual binning is not mapped onto
0097   jMaterial[jsonKey().mapkey] = false;
0098   const BinUtility& bUtility = material.binning();
0099   for (const auto& bData : bUtility.binningData()) {
0100     if (bData.bins() > 1) {
0101       jMaterial[jsonKey().mapkey] = true;
0102       break;
0103     }
0104   }
0105   if (material.materialKey()) {
0106     jMaterial["material_key"] = *material.materialKey();
0107   }
0108   jMaterial[jsonKey().binkey] = nlohmann::json(bUtility);
0109   return jMaterial;
0110 }
0111 
0112 nlohmann::json protoGridToJson(const ProtoGridSurfaceMaterial& material,
0113                                EncodeContext& /*ctx*/) {
0114   nlohmann::json jMaterial;
0115   jMaterial[jsonKey().typekey] = kProtoGridTag;
0116   jMaterial[jsonKey().maptype] = nlohmann::json(material.mappingType());
0117   jMaterial[jsonKey().mapkey] = true;
0118   if (material.materialKey()) {
0119     jMaterial["material_key"] = *material.materialKey();
0120   }
0121   jMaterial["axis_specs"] =
0122       MultiAxisSpecJsonConverter::toJson(material.binning());
0123   return jMaterial;
0124 }
0125 
0126 nlohmann::json mergedMarkerToJson(const MergedMaterialMarker& material,
0127                                   EncodeContext& /*ctx*/) {
0128   nlohmann::json jMaterial;
0129   jMaterial[jsonKey().typekey] = kMergedMarkerTag;
0130   if (!material.origins().empty()) {
0131     jMaterial["origins"] = nlohmann::json::array();
0132     for (const auto& origin : material.origins()) {
0133       nlohmann::json entry{{"geometry_id", origin.geometryId.value()}};
0134       if (origin.materialKey) {
0135         entry["material_key"] = *origin.materialKey;
0136       }
0137       jMaterial["origins"].push_back(std::move(entry));
0138     }
0139   }
0140   // Flag as "mapped" so the reader does not discard it
0141   jMaterial[jsonKey().mapkey] = true;
0142   return jMaterial;
0143 }
0144 
0145 /// Convert the axes of a resolved multi-axis
0146 nlohmann::json axesToJson(const IMultiAxis2D& multiAxis) {
0147   nlohmann::json jAxes = nlohmann::json::array();
0148   for (std::size_t ia = 0u; ia < multiAxis.getNAxes(); ++ia) {
0149     jAxes.push_back(AxisJsonConverter::toJson(multiAxis.getAxis(ia)));
0150   }
0151   return jAxes;
0152 }
0153 
0154 /// Write the per bin payload as [local bins, value] entries over the regular
0155 /// bins. The local bins are 1-based, the storage is addressed by global bin.
0156 template <typename value_at_t>
0157 nlohmann::json gridDataToJson(const IMultiAxis2D& multiAxis,
0158                               value_at_t&& valueAt) {
0159   nlohmann::json jData = nlohmann::json::array();
0160   IMultiAxis2D::LocalBins nBins = multiAxis.getNBins();
0161   for (std::size_t ib0 = 1u; ib0 <= nBins[0u]; ++ib0) {
0162     for (std::size_t ib1 = 1u; ib1 <= nBins[1u]; ++ib1) {
0163       IMultiAxis2D::LocalBins lBin{ib0, ib1};
0164       jData.push_back(nlohmann::json::array(
0165           {std::array<std::size_t, 2u>{ib0, ib1},
0166            valueAt(multiAxis.getGlobalBinFromLocalBins(lBin))}));
0167     }
0168   }
0169   return jData;
0170 }
0171 
0172 nlohmann::json slabsToJson(const std::vector<MaterialSlab>& slabs) {
0173   nlohmann::json jSlabs = nlohmann::json::array();
0174   for (const auto& msl : slabs) {
0175     jSlabs.push_back(nlohmann::json(msl));
0176   }
0177   return jSlabs;
0178 }
0179 
0180 /// Encoder for the whole grid material family. The storage backend is
0181 /// resolved at runtime through the storage variant, so the concrete axis
0182 /// types never appear here.
0183 nlohmann::json gridMaterialToJson(const GridSurfaceMaterial& material,
0184                                   EncodeContext& ctx) {
0185   nlohmann::json jMaterial;
0186   jMaterial[jsonKey().typekey] = kGridTag;
0187   jMaterial[jsonKey().mapkey] = true;
0188 
0189   const IMultiAxis2D& multiAxis = material.multiAxis();
0190   nlohmann::json jGrid;
0191   jGrid["axes"] = axesToJson(multiAxis);
0192 
0193   nlohmann::json jAccessor;
0194   std::visit(
0195       [&]<typename storage_t>(const storage_t& storage) {
0196         if constexpr (std::is_same_v<storage_t, GridSurfaceMaterial::Direct>) {
0197           jAccessor["type"] = kDirectAccessorTag;
0198           jGrid["data"] = gridDataToJson(multiAxis, [&](std::size_t bin) {
0199             return nlohmann::json(storage.at(bin));
0200           });
0201         } else {
0202           constexpr bool isLocal =
0203               std::is_same_v<storage_t, GridSurfaceMaterial::Indexed>;
0204           jAccessor["type"] =
0205               isLocal ? kIndexedAccessorTag : kGloballyIndexedAccessorTag;
0206           // The slab store travels with the payload, so that a globally
0207           // indexed grid can be read back on its own
0208           if constexpr (isLocal) {
0209             jAccessor["storage_vector"] = slabsToJson(storage.material);
0210           } else {
0211             if (storage.material == nullptr) {
0212               throw std::invalid_argument(
0213                   "SurfaceMaterialJsonConverter: globally indexed material "
0214                   "without a slab store");
0215             }
0216             if (ctx.storeTableEnabled()) {
0217               // The store lives once in the document, the entry only
0218               // references it
0219               jAccessor["store"] = ctx.storeId(storage.material);
0220             } else {
0221               // Standalone payload, inline the store to keep it
0222               // self-contained
0223               jAccessor["storage_vector"] = slabsToJson(*storage.material);
0224             }
0225           }
0226           jGrid["data"] = gridDataToJson(multiAxis, [&](std::size_t bin) {
0227             return nlohmann::json(storage.indices.at(bin));
0228           });
0229         }
0230       },
0231       material.storage());
0232 
0233   jAccessor["grid"] = std::move(jGrid);
0234   jMaterial["accessor"] = std::move(jAccessor);
0235   return jMaterial;
0236 }
0237 
0238 MappingType readMappingType(const nlohmann::json& jMaterial) {
0239   MappingType mappingType = MappingType::Default;
0240   if (jMaterial.contains(jsonKey().maptype) &&
0241       !jMaterial.at(jsonKey().maptype).is_null()) {
0242     from_json(jMaterial.at(jsonKey().maptype), mappingType);
0243   }
0244   return mappingType;
0245 }
0246 
0247 std::unique_ptr<const ISurfaceMaterial> homogeneousFromJson(
0248     const nlohmann::json& jMaterial, const DecodeContext& /*ctx*/) {
0249   MaterialSlabMatrix matrix;
0250   from_json(jMaterial.at(jsonKey().datakey), matrix);
0251   if (matrix.empty() || matrix[0].empty()) {
0252     throw std::invalid_argument(
0253         "SurfaceMaterialJsonConverter: homogeneous material without data");
0254   }
0255   return std::make_unique<const HomogeneousSurfaceMaterial>(
0256       matrix[0][0], 1., readMappingType(jMaterial));
0257 }
0258 
0259 std::unique_ptr<const ISurfaceMaterial> binnedFromJson(
0260     const nlohmann::json& jMaterial, const DecodeContext& /*ctx*/) {
0261   BinUtility bUtility;
0262   from_json(jMaterial.at(jsonKey().binkey), bUtility);
0263   MaterialSlabMatrix matrix;
0264   from_json(jMaterial.at(jsonKey().datakey), matrix);
0265   return std::make_unique<const BinnedSurfaceMaterial>(
0266       bUtility, std::move(matrix), 1., readMappingType(jMaterial));
0267 }
0268 
0269 std::unique_ptr<const ISurfaceMaterial> protoFromJson(
0270     const nlohmann::json& jMaterial, const DecodeContext& /*ctx*/) {
0271   BinUtility bUtility;
0272   if (jMaterial.contains(jsonKey().binkey) &&
0273       !jMaterial.at(jsonKey().binkey).is_null()) {
0274     from_json(jMaterial.at(jsonKey().binkey), bUtility);
0275   }
0276   return std::make_unique<const ProtoSurfaceMaterial>(
0277       bUtility, readMappingType(jMaterial),
0278       jMaterial.contains("material_key")
0279           ? std::make_optional(jMaterial.at("material_key").get<std::string>())
0280           : std::nullopt);
0281 }
0282 
0283 std::unique_ptr<const ISurfaceMaterial> protoGridFromJson(
0284     const nlohmann::json& jMaterial, const DecodeContext& /*ctx*/) {
0285   MultiAxisSpec spec =
0286       MultiAxisSpecJsonConverter::fromJson(jMaterial.at("axis_specs"));
0287   if (spec.size() != 2u) {
0288     throw std::invalid_argument(
0289         "SurfaceMaterialJsonConverter: proto grid material needs exactly two "
0290         "axis specs");
0291   }
0292   MultiAxisSpec2D spec2D{
0293       std::array<AxisSpec, 2u>{spec.axisSpec(0u), spec.axisSpec(1u)}};
0294   return std::make_unique<const ProtoGridSurfaceMaterial>(
0295       spec2D, readMappingType(jMaterial),
0296       jMaterial.contains("material_key")
0297           ? std::make_optional(jMaterial.at("material_key").get<std::string>())
0298           : std::nullopt);
0299 }
0300 
0301 std::unique_ptr<const ISurfaceMaterial> mergedMarkerFromJson(
0302     const nlohmann::json& jMaterial, const DecodeContext& /*ctx*/) {
0303   std::vector<MergedMaterialMarker::Origin> origins;
0304   if (jMaterial.contains("origins")) {
0305     for (const auto& entry : jMaterial.at("origins")) {
0306       std::optional<std::string> key;
0307       if (entry.contains("material_key")) {
0308         key = entry.at("material_key").get<std::string>();
0309         if (key->empty()) {
0310           throw std::invalid_argument("Empty material key in merge provenance");
0311         }
0312       }
0313       origins.emplace_back(
0314           GeometryIdentifier(entry.at("geometry_id").get<std::uint64_t>()),
0315           std::move(key));
0316     }
0317   }
0318   return std::make_unique<MergedMaterialMarker>(std::move(origins));
0319 }
0320 
0321 /// Read the 2D grid payload in column major order, i.e. [i0][i1], from the
0322 /// 1-based local bins the writer emits
0323 template <typename value_t>
0324 std::vector<std::vector<value_t>> gridPayload2D(const nlohmann::json& jGrid,
0325                                                 std::size_t nBins0,
0326                                                 std::size_t nBins1,
0327                                                 const value_t& fill) {
0328   std::vector<std::vector<value_t>> payload(nBins0,
0329                                             std::vector<value_t>(nBins1, fill));
0330   for (const auto& jEntry : jGrid.at("data")) {
0331     std::array<std::size_t, 2u> lBin = jEntry.at(0u);
0332     if (jEntry.at(1u).is_null() || lBin[0u] < 1u || lBin[0u] > nBins0 ||
0333         lBin[1u] < 1u || lBin[1u] > nBins1) {
0334       continue;
0335     }
0336     payload[lBin[0u] - 1u][lBin[1u] - 1u] =
0337         jEntry.at(1u).template get<value_t>();
0338   }
0339   return payload;
0340 }
0341 
0342 std::vector<MaterialSlab> slabsFromJson(const nlohmann::json& jSlabs) {
0343   std::vector<MaterialSlab> slabs;
0344   slabs.reserve(jSlabs.size());
0345   for (const auto& jSlab : jSlabs) {
0346     MaterialSlab slab = MaterialSlab::Nothing();
0347     from_json(jSlab, slab);
0348     slabs.push_back(slab);
0349   }
0350   return slabs;
0351 }
0352 
0353 std::unique_ptr<const ISurfaceMaterial> gridFromJson(
0354     const nlohmann::json& jMaterial, const DecodeContext& ctx) {
0355   const nlohmann::json& jAccessor = jMaterial.at("accessor");
0356   const nlohmann::json& jGrid = jAccessor.at("grid");
0357   const nlohmann::json& jAxes = jGrid.at("axes");
0358   std::string accessorType = jAccessor.at("type").get<std::string>();
0359 
0360   if (jAxes.size() != 2u) {
0361     throw std::invalid_argument(
0362         "SurfaceMaterialJsonConverter: grid material needs exactly two axes");
0363   }
0364   std::unique_ptr<IAxis> axis0 = AxisJsonConverter::fromJson(jAxes.at(0u));
0365   std::unique_ptr<IAxis> axis1 = AxisJsonConverter::fromJson(jAxes.at(1u));
0366 
0367   if (accessorType == kDirectAccessorTag) {
0368     return GridSurfaceMaterial::createDirect(
0369         *axis0, *axis1,
0370         gridPayload2D<MaterialSlab>(jGrid, axis0->getNBins(), axis1->getNBins(),
0371                                     MaterialSlab::Nothing()));
0372   }
0373 
0374   auto indices = gridPayload2D<std::size_t>(jGrid, axis0->getNBins(),
0375                                             axis1->getNBins(), std::size_t{0u});
0376   if (accessorType == kIndexedAccessorTag) {
0377     return GridSurfaceMaterial::createIndexed(
0378         *axis0, *axis1, slabsFromJson(jAccessor.at("storage_vector")), indices);
0379   }
0380   if (accessorType == kGloballyIndexedAccessorTag) {
0381     detail::MaterialSlabStore store;
0382     if (jAccessor.contains("store")) {
0383       // Resolved through the document store table, so that grids referencing
0384       // the same id keep sharing one allocation
0385       store = ctx.store(jAccessor.at("store").get<std::size_t>());
0386     } else {
0387       store = std::make_shared<std::vector<MaterialSlab>>(
0388           slabsFromJson(jAccessor.at("storage_vector")));
0389     }
0390     return GridSurfaceMaterial::createGloballyIndexed(
0391         *axis0, *axis1, std::move(store), indices);
0392   }
0393   throw std::invalid_argument(
0394       "SurfaceMaterialJsonConverter: unsupported grid material accessor: " +
0395       accessorType);
0396 }
0397 
0398 SurfaceMaterialJsonConverter::Config makeDefaultConfig() {
0399   using Config = SurfaceMaterialJsonConverter::Config;
0400   Config cfg;
0401 
0402   cfg.encoder.registerFunction(homogeneousToJson);
0403   cfg.encoder.registerFunction(binnedToJson);
0404   cfg.encoder.registerFunction(protoToJson);
0405   cfg.encoder.registerFunction(protoGridToJson);
0406   cfg.encoder.registerFunction(mergedMarkerToJson);
0407   // One concrete class covers the whole grid material family, the storage
0408   // backend is a runtime variant rather than a template parameter
0409   cfg.encoder.registerFunction(gridMaterialToJson);
0410 
0411   cfg.decoder.registerKind(kHomogeneousTag, homogeneousFromJson);
0412   cfg.decoder.registerKind(kBinnedTag, binnedFromJson);
0413   cfg.decoder.registerKind(kProtoTag, protoFromJson);
0414   cfg.decoder.registerKind(kProtoGridTag, protoGridFromJson);
0415   cfg.decoder.registerKind(kMergedMarkerTag, mergedMarkerFromJson);
0416   cfg.decoder.registerKind(kGridTag, gridFromJson);
0417 
0418   return cfg;
0419 }
0420 
0421 }  // namespace
0422 
0423 const Acts::SurfaceMaterialJsonConverter::Config&
0424 Acts::SurfaceMaterialJsonConverter::Config::defaultConfig() {
0425   static const Config cfg = makeDefaultConfig();
0426   return cfg;
0427 }
0428 
0429 nlohmann::json Acts::SurfaceMaterialJsonConverter::toJson(
0430     const ISurfaceMaterial& material, const Config& config,
0431     EncodeContext* context) {
0432   // Without a document context the encoders inline their slab stores
0433   EncodeContext inlineContext;
0434   return config.encoder(material,
0435                         context != nullptr ? *context : inlineContext);
0436 }
0437 
0438 std::unique_ptr<const Acts::ISurfaceMaterial>
0439 Acts::SurfaceMaterialJsonConverter::fromJson(const nlohmann::json& jMaterial,
0440                                              const Config& config,
0441                                              const DecodeContext* context) {
0442   // Surfaces that are flagged out of the mapping carry no material
0443   if (jMaterial.contains(jsonKey().mapkey) &&
0444       jMaterial.at(jsonKey().mapkey) == false) {
0445     return nullptr;
0446   }
0447   // Without a document context a payload referencing a store is an error
0448   const DecodeContext emptyContext;
0449   return config.decoder(jMaterial,
0450                         context != nullptr ? *context : emptyContext);
0451 }