Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-06 08:10:46

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2022 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 http://mozilla.org/MPL/2.0/.
0008 
0009 #include "ActsExamples/Io/Csv/CsvBFieldWriter.hpp"
0010 
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/Definitions/Units.hpp"
0013 #include "Acts/MagneticField/InterpolatedBFieldMap.hpp"
0014 #include "Acts/MagneticField/MagneticFieldContext.hpp"
0015 #include "Acts/Utilities/Logger.hpp"
0016 #include "Acts/Utilities/Result.hpp"
0017 #include "Acts/Utilities/VectorHelpers.hpp"
0018 
0019 #include <iomanip>
0020 #include <ostream>
0021 #include <stdexcept>
0022 #include <vector>
0023 
0024 #include <dfe/dfe_io_dsv.hpp>
0025 
0026 namespace ActsExamples {
0027 template <CsvBFieldWriter::CoordinateType Coord, bool Grid>
0028 void CsvBFieldWriter::run(const Config<Coord, Grid>& config,
0029                           Acts::Logging::Level level) {
0030   ACTS_LOCAL_LOGGER(Acts::getDefaultLogger("CsvBFieldWriter", level));
0031 
0032   // Some helper typedefs, which will make our life easier down the line.
0033   using ConfigType = std::decay_t<decltype(config)>;
0034   using FieldType = typename decltype(ConfigType::bField)::element_type;
0035   using Vector = Acts::ActsVector<ConfigType::NDims>;
0036 
0037   FieldType& field = *config.bField;
0038 
0039   // Determine the columns to write to the CSV file. For Cartesian coordinates,
0040   // we give the x, y, and z coordinates for both the position and the field
0041   // vector. For cylindrical coordinates, we give r and z.
0042   std::vector<std::string> fields;
0043 
0044   if constexpr (Coord == CoordinateType::XYZ) {
0045     fields = {"x", "y", "z", "Bx", "By", "Bz"};
0046   } else {
0047     fields = {"r", "z", "Br", "Bz"};
0048   }
0049 
0050   // Initialize a CSV writer to the specified filename using the specified
0051   // column names.
0052   dfe::io_dsv_impl::DsvWriter<','> writer(fields, config.fileName);
0053 
0054   // We proceed by finding the number of bins, as well as the minimum and
0055   // maximum coordinates. This process depends quite heavily on the structure
0056   // of the magnetic field, so we need some compile-time conditionals.
0057   std::array<std::size_t, ConfigType::NDims> bins{};
0058   Vector min, max;
0059 
0060   if constexpr (Grid) {
0061     // First, we should check whether the Bfield actually has a
0062     // three-dimensional bin count and size.
0063     if (bins.size() != ConfigType::NDims ||
0064         field.getMin().size() != ConfigType::NDims ||
0065         field.getMax().size() != ConfigType::NDims) {
0066       throw std::invalid_argument("Incorrect input B-field dimensionality.");
0067     }
0068 
0069     // If our magnetic field is grid-like, we know that it has a built-in size
0070     // and bin count. We can use these, but the user may want to override the
0071     // values with custom values.
0072     for (std::size_t i = 0; i < ConfigType::NDims; ++i) {
0073       // For each dimension, we first get the corresponding value from the
0074       // grid-based vector field...
0075       bins[i] = field.getNBins()[i];
0076       min[i] = field.getMin()[i];
0077       max[i] = field.getMax()[i];
0078 
0079       // ...and then we override them with the optional user-supplied values.
0080       if (config.bins[i]) {
0081         bins[i] = *config.bins[i];
0082       }
0083 
0084       if (config.range[i][0]) {
0085         min[i] = *config.range[i][0];
0086       }
0087 
0088       if (config.range[i][1]) {
0089         max[i] = *config.range[i][1];
0090       }
0091     }
0092   } else {
0093     // If the vector field is not grid based, then there is no side and we are
0094     // forced to use the user-supplied data. Remember that in this case, the
0095     // values are not optional.
0096     for (std::size_t i = 0; i < ConfigType::NDims; ++i) {
0097       bins[i] = config.bins[i];
0098       min[i] = config.range[i][0];
0099       max[i] = config.range[i][1];
0100     }
0101   }
0102 
0103   // Next, we calculate the size (in physical space) of each bin.
0104   Vector delta;
0105 
0106   for (std::size_t i = 0; i < ConfigType::NDims; ++i) {
0107     delta[i] = (max[i] - min[i]) / (bins[i] - 1);
0108   }
0109 
0110   // Create the appropriate magnetic field context and cache to interact with
0111   // the B fields.
0112   Acts::MagneticFieldContext mctx{};
0113   typename FieldType::Cache cache = field.makeCache(mctx);
0114 
0115   // Finally, we can begin to fill the output file with data from our B field.
0116   // Again, the procedure is slightly different depending on whether we are
0117   // working with Cartesian or cylindrical coordinates.
0118   if constexpr (Coord == CoordinateType::XYZ) {
0119     ACTS_INFO("Writing XYZ field of size " << bins[0] << " x " << bins[1]
0120                                            << " x " << bins[2] << " to file "
0121                                            << config.fileName << "...");
0122 
0123     std::size_t total_items = bins[0] * bins[1] * bins[2];
0124 
0125     // For Cartesian coordinates, iterate over bins in the x, y, and z
0126     // directions. Note that we iterate one additional time because we are
0127     // writing the _edges_ of the bins, and the final bin needs to be closed.
0128     for (std::size_t x = 0; x < bins[0]; ++x) {
0129       for (std::size_t y = 0; y < bins[1]; ++y) {
0130         for (std::size_t z = 0; z < bins[2]; ++z) {
0131           // Compute the geometric position of this bin, then request the
0132           // magnetic field vector at that position.
0133           Acts::Vector3 pos = {x * delta[0] + min[0], y * delta[1] + min[1],
0134                                z * delta[2] + min[2]};
0135 
0136           Acts::Vector3 bField;
0137           if (auto fieldMap =
0138                   dynamic_cast<const Acts::InterpolatedMagneticField*>(
0139                       &field)) {
0140             // InterpolatedMagneticField::getField() returns an error for the
0141             // final point (upper edge), which is just outside the field volume.
0142             // So we use getFieldUnchecked instead.
0143             bField = fieldMap->getFieldUnchecked(pos);
0144           } else {
0145             Acts::Result<Acts::Vector3> flx = field.getField(pos, cache);
0146 
0147             // The aforementioned method is not guaranteed to succeed, so we
0148             // must check for a valid result, and then write it to disk. If the
0149             // result is invalid, throw an exception.
0150             if (flx.ok()) {
0151               bField = *flx;
0152             } else {
0153               throw std::runtime_error("B-field returned a non-extant value!");
0154             }
0155           }
0156 
0157           writer.append(pos[0] / Acts::UnitConstants::mm,
0158                         pos[1] / Acts::UnitConstants::mm,
0159                         pos[2] / Acts::UnitConstants::mm,
0160                         bField[0] / Acts::UnitConstants::T,
0161                         bField[1] / Acts::UnitConstants::T,
0162                         bField[2] / Acts::UnitConstants::T);
0163 
0164           // This final part is some diagnostic to convince the user that the
0165           // program is still running. We periodically provide the user with
0166           // some useful data.
0167           std::size_t idx = (x * bins[1] * bins[2]) + (y * bins[2]) + z + 1;
0168 
0169           if (idx % 10000 == 0 || idx == total_items) {
0170             ACTS_VERBOSE("Wrote " << idx << " out of " << total_items
0171                                   << " items (" << std::setprecision(3)
0172                                   << ((100.f * idx) / total_items) << "%).");
0173           }
0174         }
0175       }
0176     }
0177   } else {
0178     ACTS_INFO("Writing RZ field of size " << bins[0] << " x " << bins[1]
0179                                           << " to file " << config.fileName
0180                                           << "...");
0181 
0182     std::size_t total_items = bins[0] * bins[1];
0183 
0184     // For cylindrical coordinates, we only need to iterate over the r and z
0185     // coordinates, because we assume rotational cylindrical symmetry. This
0186     // makes the procedure quite a bit faster, too. Great!
0187     for (std::size_t r = 0; r < bins[0]; ++r) {
0188       for (std::size_t z = 0; z < bins[1]; ++z) {
0189         // Calculate the position (still in three dimensions), assuming that
0190         // the phi coordinate is zero. Then grab the field.
0191         Acts::Vector3 pos(min[0] + r * delta[0], 0.f, min[1] + z * delta[1]);
0192 
0193         Acts::Vector3 bField;
0194         if (auto fieldMap =
0195                 dynamic_cast<const Acts::InterpolatedMagneticField*>(&field)) {
0196           // InterpolatedMagneticField::getField() returns an error for the
0197           // final point (upper edge), which is just outside the field volume.
0198           // So we use getFieldUnchecked instead.
0199           bField = fieldMap->getFieldUnchecked(pos);
0200         } else {
0201           Acts::Result<Acts::Vector3> flx = field.getField(pos, cache);
0202 
0203           // Check the result, then write to disk. We write the r and z
0204           // positions as they are, then we write the z component of the result
0205           // vector as is, and we compute the r-value from the other components
0206           // of the vector.
0207           if (flx.ok()) {
0208             bField = *flx;
0209           } else {
0210             throw std::runtime_error("B-field returned a non-extant value!");
0211           }
0212         }
0213 
0214         writer.append(
0215             pos[0] / Acts::UnitConstants::mm, pos[2] / Acts::UnitConstants::mm,
0216             Acts::VectorHelpers::perp(bField) / Acts::UnitConstants::T,
0217             bField[2] / Acts::UnitConstants::T);
0218 
0219         // As before, print some progress reports for the user to enjoy while
0220         // they wait.
0221         std::size_t idx = (r * bins[1]) + z + 1;
0222 
0223         if (idx % 10000 == 0 || idx == total_items) {
0224           ACTS_VERBOSE("Wrote " << idx << " out of " << total_items
0225                                 << " items (" << std::setprecision(3)
0226                                 << ((100.f * idx) / total_items) << "%).");
0227         }
0228       }
0229     }
0230   }
0231 }
0232 
0233 // Note that this is a C++ source file, and not a header file. The reason for
0234 // this is that we can easily enumerate the different template parameter
0235 // combinations for the function above, and so we want to speed up compilation
0236 // times by just compiling these symbols once into the object file
0237 // corresponding to this TU.
0238 template void CsvBFieldWriter::run<CsvBFieldWriter::CoordinateType::XYZ, true>(
0239     const Config<CoordinateType::XYZ, true>&, Acts::Logging::Level);
0240 template void CsvBFieldWriter::run<CsvBFieldWriter::CoordinateType::RZ, true>(
0241     const Config<CoordinateType::RZ, true>&, Acts::Logging::Level);
0242 template void CsvBFieldWriter::run<CsvBFieldWriter::CoordinateType::XYZ, false>(
0243     const Config<CoordinateType::XYZ, false>&, Acts::Logging::Level);
0244 template void CsvBFieldWriter::run<CsvBFieldWriter::CoordinateType::RZ, false>(
0245     const Config<CoordinateType::RZ, false>&, Acts::Logging::Level);
0246 }  // namespace ActsExamples