Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
CsvBFieldWriter.cpp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file CsvBFieldWriter.cpp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2022 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
10 
18 
19 #include <iomanip>
20 #include <ostream>
21 #include <stdexcept>
22 #include <vector>
23 
24 #include <dfe/dfe_io_dsv.hpp>
25 
26 namespace ActsExamples {
27 template <CsvBFieldWriter::CoordinateType Coord, bool Grid>
30  ACTS_LOCAL_LOGGER(Acts::getDefaultLogger("CsvBFieldWriter", level));
31 
32  // Some helper typedefs, which will make our life easier down the line.
33  using ConfigType = std::decay_t<decltype(config)>;
34  using FieldType = typename decltype(ConfigType::bField)::element_type;
36 
37  FieldType& field = *config.bField;
38 
39  // Determine the columns to write to the CSV file. For Cartesian coordinates,
40  // we give the x, y, and z coordinates for both the position and the field
41  // vector. For cylindrical coordinates, we give r and z.
42  std::vector<std::string> fields;
43 
44  if constexpr (Coord == CoordinateType::XYZ) {
45  fields = {"x", "y", "z", "Bx", "By", "Bz"};
46  } else {
47  fields = {"r", "z", "Br", "Bz"};
48  }
49 
50  // Initialize a CSV writer to the specified filename using the specified
51  // column names.
52  dfe::io_dsv_impl::DsvWriter<','> writer(fields, config.fileName);
53 
54  // We proceed by finding the number of bins, as well as the minimum and
55  // maximum coordinates. This process depends quite heavily on the structure
56  // of the magnetic field, so we need some compile-time conditionals.
57  std::array<std::size_t, ConfigType::NDims> bins{};
58  Vector min, max;
59 
60  if constexpr (Grid) {
61  // First, we should check whether the Bfield actually has a
62  // three-dimensional bin count and size.
63  if (bins.size() != ConfigType::NDims ||
64  field.getMin().size() != ConfigType::NDims ||
65  field.getMax().size() != ConfigType::NDims) {
66  throw std::invalid_argument("Incorrect input B-field dimensionality.");
67  }
68 
69  // If our magnetic field is grid-like, we know that it has a built-in size
70  // and bin count. We can use these, but the user may want to override the
71  // values with custom values.
72  for (std::size_t i = 0; i < ConfigType::NDims; ++i) {
73  // For each dimension, we first get the corresponding value from the
74  // grid-based vector field...
75  bins[i] = field.getNBins()[i];
76  min[i] = field.getMin()[i];
77  max[i] = field.getMax()[i];
78 
79  // ...and then we override them with the optional user-supplied values.
80  if (config.bins[i]) {
81  bins[i] = *config.bins[i];
82  }
83 
84  if (config.range[i][0]) {
85  min[i] = *config.range[i][0];
86  }
87 
88  if (config.range[i][1]) {
89  max[i] = *config.range[i][1];
90  }
91  }
92  } else {
93  // If the vector field is not grid based, then there is no side and we are
94  // forced to use the user-supplied data. Remember that in this case, the
95  // values are not optional.
96  for (std::size_t i = 0; i < ConfigType::NDims; ++i) {
97  bins[i] = config.bins[i];
98  min[i] = config.range[i][0];
99  max[i] = config.range[i][1];
100  }
101  }
102 
103  // Next, we calculate the size (in physical space) of each bin.
104  Vector delta;
105 
106  for (std::size_t i = 0; i < ConfigType::NDims; ++i) {
107  delta[i] = (max[i] - min[i]) / (bins[i] - 1);
108  }
109 
110  // Create the appropriate magnetic field context and cache to interact with
111  // the B fields.
113  typename FieldType::Cache cache = field.makeCache(mctx);
114 
115  // Finally, we can begin to fill the output file with data from our B field.
116  // Again, the procedure is slightly different depending on whether we are
117  // working with Cartesian or cylindrical coordinates.
118  if constexpr (Coord == CoordinateType::XYZ) {
119  ACTS_INFO("Writing XYZ field of size " << bins[0] << " x " << bins[1]
120  << " x " << bins[2] << " to file "
121  << config.fileName << "...");
122 
123  std::size_t total_items = bins[0] * bins[1] * bins[2];
124 
125  // For Cartesian coordinates, iterate over bins in the x, y, and z
126  // directions. Note that we iterate one additional time because we are
127  // writing the _edges_ of the bins, and the final bin needs to be closed.
128  for (std::size_t x = 0; x < bins[0]; ++x) {
129  for (std::size_t y = 0; y < bins[1]; ++y) {
130  for (std::size_t z = 0; z < bins[2]; ++z) {
131  // Compute the geometric position of this bin, then request the
132  // magnetic field vector at that position.
133  Acts::Vector3 pos = {x * delta[0] + min[0], y * delta[1] + min[1],
134  z * delta[2] + min[2]};
135 
137  if (auto fieldMap =
138  dynamic_cast<const Acts::InterpolatedMagneticField*>(
139  &field)) {
140  // InterpolatedMagneticField::getField() returns an error for the
141  // final point (upper edge), which is just outside the field volume.
142  // So we use getFieldUnchecked instead.
143  bField = fieldMap->getFieldUnchecked(pos);
144  } else {
145  Acts::Result<Acts::Vector3> flx = field.getField(pos, cache);
146 
147  // The aforementioned method is not guaranteed to succeed, so we
148  // must check for a valid result, and then write it to disk. If the
149  // result is invalid, throw an exception.
150  if (flx.ok()) {
151  bField = *flx;
152  } else {
153  throw std::runtime_error("B-field returned a non-extant value!");
154  }
155  }
156 
157  writer.append(pos[0] / Acts::UnitConstants::mm,
158  pos[1] / Acts::UnitConstants::mm,
159  pos[2] / Acts::UnitConstants::mm,
160  bField[0] / Acts::UnitConstants::T,
161  bField[1] / Acts::UnitConstants::T,
162  bField[2] / Acts::UnitConstants::T);
163 
164  // This final part is some diagnostic to convince the user that the
165  // program is still running. We periodically provide the user with
166  // some useful data.
167  std::size_t idx = (x * bins[1] * bins[2]) + (y * bins[2]) + z + 1;
168 
169  if (idx % 10000 == 0 || idx == total_items) {
170  ACTS_VERBOSE("Wrote " << idx << " out of " << total_items
171  << " items (" << std::setprecision(3)
172  << ((100.f * idx) / total_items) << "%).");
173  }
174  }
175  }
176  }
177  } else {
178  ACTS_INFO("Writing RZ field of size " << bins[0] << " x " << bins[1]
179  << " to file " << config.fileName
180  << "...");
181 
182  std::size_t total_items = bins[0] * bins[1];
183 
184  // For cylindrical coordinates, we only need to iterate over the r and z
185  // coordinates, because we assume rotational cylindrical symmetry. This
186  // makes the procedure quite a bit faster, too. Great!
187  for (std::size_t r = 0; r < bins[0]; ++r) {
188  for (std::size_t z = 0; z < bins[1]; ++z) {
189  // Calculate the position (still in three dimensions), assuming that
190  // the phi coordinate is zero. Then grab the field.
191  Acts::Vector3 pos(min[0] + r * delta[0], 0.f, min[1] + z * delta[1]);
192 
194  if (auto fieldMap =
195  dynamic_cast<const Acts::InterpolatedMagneticField*>(&field)) {
196  // InterpolatedMagneticField::getField() returns an error for the
197  // final point (upper edge), which is just outside the field volume.
198  // So we use getFieldUnchecked instead.
199  bField = fieldMap->getFieldUnchecked(pos);
200  } else {
201  Acts::Result<Acts::Vector3> flx = field.getField(pos, cache);
202 
203  // Check the result, then write to disk. We write the r and z
204  // positions as they are, then we write the z component of the result
205  // vector as is, and we compute the r-value from the other components
206  // of the vector.
207  if (flx.ok()) {
208  bField = *flx;
209  } else {
210  throw std::runtime_error("B-field returned a non-extant value!");
211  }
212  }
213 
214  writer.append(
217  bField[2] / Acts::UnitConstants::T);
218 
219  // As before, print some progress reports for the user to enjoy while
220  // they wait.
221  std::size_t idx = (r * bins[1]) + z + 1;
222 
223  if (idx % 10000 == 0 || idx == total_items) {
224  ACTS_VERBOSE("Wrote " << idx << " out of " << total_items
225  << " items (" << std::setprecision(3)
226  << ((100.f * idx) / total_items) << "%).");
227  }
228  }
229  }
230  }
231 }
232 
233 // Note that this is a C++ source file, and not a header file. The reason for
234 // this is that we can easily enumerate the different template parameter
235 // combinations for the function above, and so we want to speed up compilation
236 // times by just compiling these symbols once into the object file
237 // corresponding to this TU.
238 template void CsvBFieldWriter::run<CsvBFieldWriter::CoordinateType::XYZ, true>(
239  const Config<CoordinateType::XYZ, true>&, Acts::Logging::Level);
240 template void CsvBFieldWriter::run<CsvBFieldWriter::CoordinateType::RZ, true>(
241  const Config<CoordinateType::RZ, true>&, Acts::Logging::Level);
242 template void CsvBFieldWriter::run<CsvBFieldWriter::CoordinateType::XYZ, false>(
243  const Config<CoordinateType::XYZ, false>&, Acts::Logging::Level);
244 template void CsvBFieldWriter::run<CsvBFieldWriter::CoordinateType::RZ, false>(
245  const Config<CoordinateType::RZ, false>&, Acts::Logging::Level);
246 } // namespace ActsExamples