LDMX Software
BFieldXYZUtils.h
1#pragma once
2
3#include <fstream>
4#include <functional>
5#include <iostream>
6
7#include "Acts/Definitions/Algebra.hpp"
8#include "Acts/MagneticField/BFieldMapUtils.hpp"
9#include "Acts/MagneticField/InterpolatedBFieldMap.hpp"
10#include "Acts/MagneticField/MagneticFieldContext.hpp"
11#include "Acts/Utilities/AxisDefinitions.hpp"
12#include "Acts/Utilities/Grid.hpp"
13#include "Acts/Utilities/Interpolation.hpp"
14#include "Acts/Utilities/Result.hpp"
15#include "Framework/Exception/Exception.h"
16
17static const double DIPOLE_OFFSET = 400.; // 400 mm
18
19using InterpolatedMagneticField3 = Acts::InterpolatedBFieldMap<
20 Acts::Grid<Acts::Vector3, Acts::Axis<Acts::AxisType::Equidistant>,
21 Acts::Axis<Acts::AxisType::Equidistant>,
22 Acts::Axis<Acts::AxisType::Equidistant>>>;
23
24using GenericTransformPos = std::function<Acts::Vector3(const Acts::Vector3&)>;
25using GenericTransformBField =
26 std::function<Acts::Vector3(const Acts::Vector3&, const Acts::Vector3&)>;
27
36Acts::Vector3 defaultTransformPos(const Acts::Vector3& pos_);
37
44Acts::Vector3 defaultTransformBField(const Acts::Vector3& field,
45 const Acts::Vector3& /*pos_*/);
46
47void testField(const std::shared_ptr<Acts::MagneticFieldProvider> bfield,
48 const Acts::Vector3& eval_pos,
49 const Acts::MagneticFieldContext& bctx);
50
65 Acts::Vector3 translation_{Acts::Vector3::Zero()};
67 Acts::Vector3 rotation_{Acts::Vector3::Zero()};
69 Acts::Vector3 pivot_{-DIPOLE_OFFSET, 0., 0.};
71 double scale_{1.};
72
74 Acts::RotationMatrix3 rotationMatrix() const {
75 return (Acts::AngleAxis3(rotation_(2), Acts::Vector3::UnitZ()) *
76 Acts::AngleAxis3(rotation_(1), Acts::Vector3::UnitY()) *
77 Acts::AngleAxis3(rotation_(0), Acts::Vector3::UnitX()))
78 .toRotationMatrix();
79 }
80
86 bool isNominal() const {
87 return translation_.isZero(0.) && rotation_.isZero(0.) && scale_ == 1.;
88 }
89};
90
96size_t localToGlobalBinXyz(std::array<size_t, 3> bins,
97 std::array<size_t, 3> sizes);
98
99inline InterpolatedMagneticField3 rotateFieldMapXYZ(
100 const std::function<size_t(std::array<size_t, 3> binsXYZ,
101 std::array<size_t, 3> nBinsXYZ)>&
102 localToGlobalBin,
103 std::vector<double> xPos, std::vector<double> yPos,
104 std::vector<double> zPos, std::vector<Acts::Vector3> bField,
105 double lengthUnit, double BFieldUnit, bool firstOctant,
106 GenericTransformPos transformPosition,
107 GenericTransformBField transformMagneticField) {
108 // [1] Create Grid
109 // Sort the values
110 std::sort(xPos.begin(), xPos.end());
111 std::sort(yPos.begin(), yPos.end());
112 std::sort(zPos.begin(), zPos.end());
113
114 // Get unique values
115 xPos.erase(std::unique(xPos.begin(), xPos.end()), xPos.end());
116 yPos.erase(std::unique(yPos.begin(), yPos.end()), yPos.end());
117 zPos.erase(std::unique(zPos.begin(), zPos.end()), zPos.end());
118 xPos.shrink_to_fit();
119 yPos.shrink_to_fit();
120 zPos.shrink_to_fit();
121
122 // get the number of bins
123 size_t n_bins_x = xPos.size();
124 size_t n_bins_y = yPos.size();
125 size_t n_bins_z = zPos.size();
126
127 // get the minimum and maximum
128 auto min_max_x = std::minmax_element(xPos.begin(), xPos.end());
129 auto min_max_y = std::minmax_element(yPos.begin(), yPos.end());
130 auto min_max_z = std::minmax_element(zPos.begin(), zPos.end());
131 // Create the axis for the grid
132 // get minima
133 double x_min = *min_max_x.first;
134 double y_min = *min_max_y.first;
135 double z_min = *min_max_z.first;
136 // get maxima
137 double x_max = *min_max_x.second;
138 double y_max = *min_max_y.second;
139 double z_max = *min_max_z.second;
140 // calculate maxima (add one last bin, because bin value always corresponds to
141 // left boundary)
142 double step_z = std::fabs(z_max - z_min) / (n_bins_z - 1);
143 double step_y = std::fabs(y_max - y_min) / (n_bins_y - 1);
144 double step_x = std::fabs(x_max - x_min) / (n_bins_x - 1);
145 x_max += step_x;
146 y_max += step_y;
147 z_max += step_z;
148
149 // If only the first octant is given
150 if (firstOctant) {
151 x_min = -*min_max_x.second;
152 y_min = -*min_max_y.second;
153 z_min = -*min_max_z.second;
154 n_bins_x = 2 * n_bins_x - 1;
155 n_bins_y = 2 * n_bins_y - 1;
156 n_bins_z = 2 * n_bins_z - 1;
157 }
158 Acts::Axis<Acts::AxisType::Equidistant> x_axis(x_min * lengthUnit,
159 x_max * lengthUnit, n_bins_x);
160 Acts::Axis<Acts::AxisType::Equidistant> y_axis(y_min * lengthUnit,
161 y_max * lengthUnit, n_bins_y);
162 Acts::Axis<Acts::AxisType::Equidistant> z_axis(z_min * lengthUnit,
163 z_max * lengthUnit, n_bins_z);
164 // Create the grid
165 using Grid_t =
166 Acts::Grid<Acts::Vector3, Acts::Axis<Acts::AxisType::Equidistant>,
167 Acts::Axis<Acts::AxisType::Equidistant>,
168 Acts::Axis<Acts::AxisType::Equidistant>>;
169 Grid_t grid(
170 std::make_tuple(std::move(x_axis), std::move(y_axis), std::move(z_axis)));
171
172 // [2] Set the bField values
173 for (size_t i = 1; i <= n_bins_x; ++i) {
174 for (size_t j = 1; j <= n_bins_y; ++j) {
175 for (size_t k = 1; k <= n_bins_z; ++k) {
176 Grid_t::index_t indices = {{i, j, k}};
177 std::array<size_t, 3> n_indices = {
178 {xPos.size(), yPos.size(), zPos.size()}};
179 if (firstOctant) {
180 // std::vectors begin with 0 and we do not want the user needing to
181 // take underflow or overflow bins in account this is why we need to
182 // subtract by one
183 size_t m = std::abs(int(i) - (int(xPos.size())));
184 size_t n = std::abs(int(j) - (int(yPos.size())));
185 size_t l = std::abs(int(k) - (int(zPos.size())));
186 Grid_t::index_t indices_first_octant = {{m, n, l}};
187
188 grid.atLocalBins(indices) =
189 bField.at(localToGlobalBin(indices_first_octant, n_indices)) *
190 BFieldUnit;
191
192 } else {
193 // std::vectors begin with 0 and we do not want the user needing to
194 // take underflow or overflow bins in account this is why we need to
195 // subtract by one
196 grid.atLocalBins(indices) =
197 bField.at(localToGlobalBin({{i - 1, j - 1, k - 1}}, n_indices)) *
198 BFieldUnit;
199 }
200 }
201 }
202 }
203 grid.setExteriorBins(Acts::Vector3::Zero());
204
205 // [3] Create the transformation for the position
206 // map (z,x,y) -> (x,y,z)
207
208 /*
209 auto transformPos = [](const Acts::Vector3& pos_, float offset=400.) {
210
211 Acts::Vector3 rot_pos;
212 rot_pos(0)=pos_(1);
213 rot_pos(1)=pos_(2);
214 rot_pos(2)=pos_(0) + offset;
215
216 return rot_pos;
217 };
218
219 */
220
221 // [4] Create the transformation for the bfield
222 // map (Bx,By,Bz) -> (Bx,By,Bz)
223
224 // auto transformBField = [](const Acts::Vector3& field,
225 // const Acts::Vector3& /*pos_*/) {
226 //
227 //
228 // Acts::Vector3 rot_field;
229 //
230 // rot_field(0) = field(2);
231 // rot_field(1) = field(0);
232 // rot_field(2) = field(1);
233
234 // return rot_field;
235 //};
236
237 // [5] Create the mapper and BField Service
238 // with the transformations passed from main producer
239 return Acts::InterpolatedBFieldMap<Grid_t>(
240 {transformPosition, transformMagneticField, std::move(grid)});
241}
242
243// This is a copy of
244// https://github.com/acts-project/acts/blob/main/Examples/Detectors/MagneticField/src/FieldMapTextIo.cpp
245// with additional rotateAxes flag to rotate the axes and field to be in the
246// tracking (ACTS) Frame
247
248inline InterpolatedMagneticField3 makeMagneticFieldMapXyzFromText(
249 std::function<size_t(std::array<size_t, 3> binsXYZ,
250 std::array<size_t, 3> nBinsXYZ)>
251 localToGlobalBin,
252 GenericTransformPos transformPosition,
253 GenericTransformBField transformMagneticField,
254 const std::string& fieldMapFile, double lengthUnit, double BFieldUnit,
255 bool firstOctant, bool rotateAxes) {
257 // Grid position points in x, y and z
258 std::vector<double> x_pos;
259 std::vector<double> y_pos;
260 std::vector<double> z_pos;
261 // components of magnetic field on grid points
262 std::vector<Acts::Vector3> b_field;
263
264 constexpr size_t k_default_size = 1 << 15;
265 // reserve estimated size
266 x_pos.reserve(k_default_size);
267 y_pos.reserve(k_default_size);
268 z_pos.reserve(k_default_size);
269 b_field.reserve(k_default_size);
270 // [1] Read in file and fill values
271 std::ifstream map_file(fieldMapFile.c_str(), std::ios::in);
272 if (!map_file.is_open()) {
273 EXCEPTION_RAISE("BadConf", "BFieldXYZUtils: cannot open field map file '" +
274 fieldMapFile + "'");
275 }
276 std::string line;
277 double pos_x = 0., pos_y = 0., pos_z = 0.;
278 double bx = 0., by = 0., bz = 0.;
279
280 bool header_found = false;
281
282 while (std::getline(map_file, line)) {
283 if (line.empty() || line[0] == '%' || line[0] == '#' || line[0] == ' ' ||
284 line.find_first_not_of(' ') == std::string::npos || !header_found) {
285 if (line.find("Header") != std::string::npos) header_found = true;
286 continue;
287 }
288 std::istringstream tmp(line);
289 tmp >> pos_x >> pos_y >> pos_z >> bx >> by >> bz;
290
291 x_pos.push_back(pos_x);
292 y_pos.push_back(pos_y);
293 z_pos.push_back(pos_z);
294 b_field.push_back(Acts::Vector3(bx, by, bz));
295 }
296 map_file.close();
297
298 if (!header_found) {
299 EXCEPTION_RAISE("BadConf",
300 "BFieldXYZUtils: no 'Header' line found in field map "
301 "file '" +
302 fieldMapFile + "'");
303 }
304 if (b_field.empty()) {
305 EXCEPTION_RAISE("BadConf", "BFieldXYZUtils: no field data read from '" +
306 fieldMapFile + "'");
307 }
308
309 x_pos.shrink_to_fit();
310 y_pos.shrink_to_fit();
311 z_pos.shrink_to_fit();
312 b_field.shrink_to_fit();
313
314 if (rotateAxes) {
315 return rotateFieldMapXYZ(localToGlobalBin, x_pos, y_pos, z_pos, b_field,
316 lengthUnit, BFieldUnit, firstOctant,
317 transformPosition, transformMagneticField);
318 } else
319 return Acts::fieldMapXYZ(localToGlobalBin, x_pos, y_pos, z_pos, b_field,
320 lengthUnit, BFieldUnit, firstOctant);
321}
322
323inline InterpolatedMagneticField3 loadDefaultBField(
324 const std::string& fieldMapFile, GenericTransformPos transformPosition,
325 GenericTransformBField transformMagneticField) {
326 // std::function<Acts::Vector3(const Acts::Vector3&, float)>
327 // transformPosition, std::function<Acts::Vector3(const Acts::Vector3&,const
328 // Acts::Vector3&)> transformMagneticField
329
330 return makeMagneticFieldMapXyzFromText(
331 std::move(localToGlobalBinXyz), transformPosition, transformMagneticField,
332 fieldMapFile,
333 1. * Acts::UnitConstants::mm, // default scale for axes length
334 1000. * Acts::UnitConstants::T, // The map is in kT, so scale it to T
335 false, // not symmetrical
336 true // rotate the axes to tracking frame
337 );
338}
339
340// R =
341//
342// 0 0 1
343// 1 0 0
344// 0 1 0
A deliberate mis-placement of the reconstruction magnetic field, used to quantify how well we need to...
double scale_
overall scaling of the field strength
Acts::Vector3 pivot_
centre of rotation [mm], default the field-map origin
Acts::Vector3 rotation_
rotation about x, y, z through pivot [rad]
Acts::RotationMatrix3 rotationMatrix() const
Rz(gamma) * Ry(beta) * Rx(alpha)
Acts::Vector3 translation_
displacement of the magnet [mm]
bool isNominal() const
Exact comparison on purpose: the nominal case reuses the default transforms unchanged,...