QwAnalysis
QwMagneticField.cc
Go to the documentation of this file.
1 //
2 // date: Sun Nov 29 22:28:23 CST 2009
3 //
4 // jpan: QTOR field map reading class.
5 // Modified from the Qweak geant4 simulation code
6 //
7 
8 #include "QwMagneticField.h"
9 
10 // Boost headers
11 #ifdef __USE_BOOST_IOSTREAMS
12 // Boost IOstreams headers
13 // There is support for gzipped iostreams as magnetic field maps. Compile with
14 // -D __USE_BOOST_IOSTREAMS
15 #include <boost/iostreams/filtering_stream.hpp>
16 #include <boost/iostreams/filtering_streambuf.hpp>
17 #include <boost/iostreams/copy.hpp>
18 #include <boost/iostreams/filter/gzip.hpp>
19 #include <boost/iostreams/device/file.hpp>
20 #endif
21 
22 // ROOT headers
23 #include "TMath.h"
24 
25 // Qweak headers
26 #include "QwLog.h"
27 #include "QwUnits.h"
28 
29 
30 /**
31  * Method to print arrays conveniently
32  * @param stream Output stream
33  * @param v Array of 3 doubles
34  * @return Output stream
35  */
36 inline std::ostream& operator<< (std::ostream& stream, const double v[3])
37 {
38  return stream << "(" << v[0] << "," << v[1] << "," << v[2] << ")";
39 }
40 
41 /**
42  * Default constructor with optional field map
43  */
44 QwMagneticField::QwMagneticField(QwOptions& options, const bool suppress_read_field_map)
45 {
46  // Check number of field components
47  if (value_n < 3) {
48  QwError << "Number of field components should be at least three!" << QwLog::endl;
49  }
50 
51  // No field map yet
52  fField = 0;
53 
54  // Initialize parameters
55  SetReferenceCurrent(8960.0);
56  SetActualCurrent(8921.0);
57  SetTranslation(0.0);
58  SetRotation(0.0);
59 
60  // Process options
61  ProcessOptions(options);
62 
63  // Read field map
64  if (! suppress_read_field_map) ReadFieldMap();
65 }
66 
67 /**
68  * Destructor
69  */
71 {
72  if (fField) delete fField;
73 }
74 
75 /**
76  * Define the options for this subsystem
77  * @param options Options object
78  */
80 {
81  // Options
82  options.AddOptions("Magnetic field map")
83  ("QwMagneticField.mapfile",po::value<std::string>()->default_value("peiqing_2011.dat"),
84  "Field map file");
85 
86  options.AddOptions("Magnetic field map")
87  ("QwMagneticField.current",po::value<double>()->default_value(0.0),
88  "Actual current of run to analyze");
89  options.AddOptions("Magnetic field map")
90  ("QwMagneticField.reference",po::value<double>()->default_value(8960.0),
91  "Reference current of field map");
92 
93  options.AddOptions("Magnetic field map")
94  ("QwMagneticField.trans",po::value<double>()->default_value(0),
95  "Translation [cm]");
96  options.AddOptions("Magnetic field map")
97  ("QwMagneticField.rot",po::value<double>()->default_value(0),
98  "Rotation [deg]");
99  options.AddOptions("Magnetic field map")
100  ("QwMagneticField.zmin",po::value<double>()->default_value(-250),
101  "Minimum of z [cm]");
102  options.AddOptions("Magnetic field map")
103  ("QwMagneticField.zmax",po::value<double>()->default_value(+250),
104  "Maximum of z [cm]");
105  options.AddOptions("Magnetic field map")
106  ("QwMagneticField.zstep",po::value<double>()->default_value(2),
107  "Step size of z [cm]");
108  options.AddOptions("Magnetic field map")
109  ("QwMagneticField.rmin",po::value<double>()->default_value(2),
110  "Minimum of r [cm]");
111  options.AddOptions("Magnetic field map")
112  ("QwMagneticField.rmax",po::value<double>()->default_value(260),
113  "Maximum of r [cm]");
114  options.AddOptions("Magnetic field map")
115  ("QwMagneticField.rstep",po::value<double>()->default_value(2),
116  "Step size of r [cm]");
117  options.AddOptions("Magnetic field map")
118  ("QwMagneticField.phimin",po::value<double>()->default_value(0),
119  "Minimum of phi [deg]");
120  options.AddOptions("Magnetic field map")
121  ("QwMagneticField.phimax",po::value<double>()->default_value(360),
122  "Maximum of phi [deg]");
123  options.AddOptions("Magnetic field map")
124  ("QwMagneticField.phistep",po::value<double>()->default_value(1),
125  "Step size of phi [deg]");
126  options.AddOptions("Magnetic field map")
127  ("QwMagneticField.phiwrap",po::value<int>()->default_value(1),
128  "Wrap-around of phi (number of equivalent grid points)");
129 }
130 
131 /**
132  * Process the options for this subsystem
133  * @param options Options object
134  */
136 {
137  // Field currents
138  SetActualCurrent(options.GetValue<double>("QwMagneticField.current"));
139  SetReferenceCurrent(options.GetValue<double>("QwMagneticField.reference"));
140  // Override if necessary
141  LoadBeamProperty("beam_property.map");
142 
143  // Translation and rotation
144  double trans = Qw::cm * options.GetValue<double>("QwMagneticField.trans");
145  double rot = Qw::deg * options.GetValue<double>("QwMagneticField.rot");
146  SetTranslation(trans);
147  SetRotation(rot);
148 
149  // Grid options
150  double zmin = Qw::cm * options.GetValue<double>("QwMagneticField.zmin");
151  double zmax = Qw::cm * options.GetValue<double>("QwMagneticField.zmax");
152  double zstep = Qw::cm * options.GetValue<double>("QwMagneticField.zstep");
153  double rmin = Qw::cm * options.GetValue<double>("QwMagneticField.rmin");
154  double rmax = Qw::cm * options.GetValue<double>("QwMagneticField.rmax");
155  double rstep = Qw::cm * options.GetValue<double>("QwMagneticField.rstep");
156  double phimin = Qw::deg * options.GetValue<double>("QwMagneticField.phimin");
157  double phimax = Qw::deg * options.GetValue<double>("QwMagneticField.phimax");
158  double phistep = Qw::deg * options.GetValue<double>("QwMagneticField.phistep");
159  int phiwrap = options.GetValue<int>("QwMagneticField.phiwrap");
160 
161  // The order is z, r, phi (no wrapping in r or z)
162  fMin.push_back(zmin); fMax.push_back(zmax); fStep.push_back(zstep); fWrap.push_back(0);
163  fMin.push_back(rmin); fMax.push_back(rmax); fStep.push_back(rstep); fWrap.push_back(0);
164  fMin.push_back(phimin); fMax.push_back(phimax); fStep.push_back(phistep); fWrap.push_back(phiwrap);
165 
166  // Determine magnetic field file from environment variables
167  std::string fieldmap =
168  options.GetValue<std::string>("QwMagneticField.mapfile");
169  SetFilename(fieldmap);
170 }
171 
172 /**
173  * Read the magnetic field map
174  * @return True if read successfully
175  */
177 {
178  // Delete existing magnetic field
179  if (fField) delete fField;
180 
181  // Create new magnetic field
184 
185  // Add path to filename
186  std::string filename = getenv_safe_string("QW_FIELDMAP") + "/" + fFilename;
187 
188  // Depending on form of filename, read zipped/regular/binary field map
189  bool status = false;
190  if (filename.find(".dat") != std::string::npos) {
191  status = ReadFieldMapFile(filename);
192  }
193  if (filename.find(".dat.gz") != std::string::npos) {
194  status = ReadFieldMapZip(filename);
195  }
196  if (filename.find(".bin") != std::string::npos) {
197  status = fField->ReadBinaryFile(filename);
198  // Try the regular map as a fallback option
199  if (status == false) {
200  filename.replace(filename.find("bin"),3,"dat");
201  status = ReadFieldMapFile(filename);
202  }
203  }
204 
205  // Check whether we succeeded
206  if (status == false) {
207  QwError << "Could not load magnetic field map!" << QwLog::endl;
208  QwWarning << "Filename: " << filename << QwLog::endl;
209  }
210 
211  return status;
212 }
213 
214 /**
215  * Test the field map value at a specific point to make sure nothing went wrong
216  * @return True if read successfully
217  */
219 {
220  bool status = true;
221 
222  // Test field value at exact grid point
223  // (r = 100*cm, z = 100*cm, phi = 22.5*degree)
224  double point[3] = {100.0 * Qw::cm, 0.0 * Qw::cm, 100.0 * Qw::cm};
225  double exact[3] = {-0.0499845 * Qw::kG, 3.28516 * Qw::kG, -0.0112704 * Qw::kG};
226 
227  // Calculate field value
228  QwMessage << "Calculating test field value at cartesian position "
229  << "(" << point[0]/Qw::cm << "," << point[1]/Qw::cm << "," << point[2]/Qw::cm << ") cm "
230  << QwLog::endl;
231  double value[3] = {0.0, 0.0, 0.0};
232  GetFieldValue(point,value);
233 
234  // Calculate difference
235  double diff[3] = {0.0, 0.0, 0.0};
236  double norm = 0.0;
237  for (size_t i = 0; i < 3; i++) {
238  diff[i] = value[i] - exact[i];
239  norm += diff[i] * diff[i];
240  }
241  norm = sqrt(norm);
242 
243  // Output
244  QwMessage << " Value = "
245  << "(" << value[0]/Qw::kG << ", " << value[1]/Qw::kG << ", " << value[2]/Qw::kG << ") kG."
246  << QwLog::endl;
247  QwMessage << " Exact = "
248  << "(" << exact[0]/Qw::kG << ", " << exact[1]/Qw::kG << ", " << exact[2]/Qw::kG << ") kG."
249  << QwLog::endl;
250  QwMessage << " Diff = "
251  << "(" << diff[0]/Qw::kG << ", " << diff[1]/Qw::kG << ", " << diff[2]/Qw::kG << ") kG."
252  << QwLog::endl;
253 
254  // Test difference (0.1 kG is of the order of 2%)
255  if (norm > 0.1 * Qw::kG) {
256  QwWarning << "Magnetic field is different from expected value by " << norm/Qw::kG << " kG."
257  << QwLog::endl;
258  status = false;
259  }
260 
261  // Check validity
262  if (status == false) {
263  QwError << "Fieldmap did not satisfy consistency checks!" << QwLog::endl;
264  }
265 
266  // Nothing is tested, just return true
267  return status;
268 }
269 
270 
271 /**
272  * Read the magnetic field from an ANSYS map file in text format
273  * @param filename ANSYS map file name
274  * @return True if read successfully
275  */
276 bool QwMagneticField::ReadFieldMapFile(const std::string& filename)
277 {
278  // Open the field map file
279  std::ifstream input;
280  input.open(filename.c_str(), std::ios_base::in);
281  // Check for success
282  if (!input.good()) {
283  QwError << "Could not open field map file!" << QwLog::endl;
284  QwError << "File name: " << filename << QwLog::endl;
285  return false;
286  }
287 
288  // Read the input field map stream
289  return ReadFieldMapStream(input);
290 }
291 
292 /**
293  * Read the magnetic field from an ANSYS map file in gzipped text format
294  * @param filename ANSYS map file name
295  * @return True if read successfully
296  */
297 bool QwMagneticField::ReadFieldMapZip(const std::string& filename)
298 {
299 #ifdef __USE_BOOST_IOSTREAMS
300  // Create a gzip filter for the field map file
301  boost::iostreams::filtering_istream input;
302  input.push(boost::iostreams::gzip_decompressor());
303  input.push(boost::iostreams::file_source(filename));
304 
305  // Read the input field map stream
306  return ReadFieldMapStream(input);
307 #else
308  QwWarning << "Compressed input files not supported!" << QwLog::endl;
309  return false;
310 #endif
311 }
312 
313 /**
314  * Read the magnetic field from an ANSYS map text stream
315  * @param input Input stream
316  * @return True if read successfully
317  */
318 bool QwMagneticField::ReadFieldMapStream(std::istream& input)
319 {
320  // Check whether field map exists
321  if (fField == 0) {
322  QwWarning << "Trying to read field map without object to put values in."
323  << QwLog::endl;
324  return false;
325  }
326 
327  // Declare variables
328  double r, z, phi;
329  field_t bx, by, bz, bx_new, by_new;
330 
331  // Check for stream
332  if (!input.good()) {
333  QwError << "Error: Could not open field map stream!" << QwLog::endl;
334  return false;
335  }
336 
337  QwMessage << "Reading magnetic field map ";
338 
339  // Loop over stream until end-of-file
340  // Note: input.good() only says whether next operation *might* succeed
341  while (! input.fail()) {
342 
343  // Progress bar
344  if (fField->GetCurrentEntries() % (fField->GetMaximumEntries() / 10) == 0) {
345  int pct = fField->GetCurrentEntries() / (fField->GetMaximumEntries() / 100);
346  QwMessage << pct << "%" << QwLog::flush;
347  }
348  if (fField->GetCurrentEntries() % (fField->GetMaximumEntries() / 10) != 0
349  && fField->GetCurrentEntries() % (fField->GetMaximumEntries() / 40) == 0) {
350  QwMessage << "." << QwLog::flush;
351  }
352 
353  // Read a line with three position coordinates and three field components
354  input >> r >> z >> phi >> bx >> by >> bz;
355  if (! input.good()) continue;
356 
357  // Fix the units
358  r *= Qw::cm; z *= Qw::cm; phi *= Qw::deg;
359  bx *= Qw::kG; by *= Qw::kG; bz *= Qw::kG;
360 
361  // Correct for translation along z
362  z -= fTranslation;
363 
364  // Correct for rotation around z
365  phi -= fRotation;
366  if (phi < 0.0*Qw::pi) phi += 2.0*Qw::pi;
367  if (phi > 2.0*Qw::pi) phi -= 2.0*Qw::pi;
368  bx_new = bx * fRotationCos + by * fRotationSin;
369  by_new = -bx * fRotationSin + by * fRotationCos;
370  bx = bx_new; by = by_new;
371 
372  // Construct the coordinate
373  double coord[3] = {z, r, phi};
374 
375  // Construct the field vector
376  field_t field[value_n];
377  field[0] = bx;
378  field[1] = by;
379  field[2] = bz;
380 
381  if (value_n == 5) {
382  // Calculate the radial and azimuthal field components
383  double br = bx * cos(phi) + by * sin(phi);
384  double bphi = -bx * sin(phi) + by * cos(phi);
385 
386  // Construct the field vector
387  field[3] = br;
388  field[4] = bphi;
389  }
390 
391  bool status = fField->Set(coord, field);
392  if (! status) {
393  QwError << "Problem assigning field to coordinate!" << QwLog::endl;
394  QwError << coord[0] << "," << coord[1] << "," << coord[2] << QwLog::endl;
395  }
396  }
398 
399  if (abs(fField->GetCurrentEntries() / fField->GetMaximumEntries() - 1) > 0.00001) {
400  QwWarning << "Expected " << fField->GetMaximumEntries() << " entries, "
401  << "but only read " << fField->GetCurrentEntries() << "." << QwLog::endl;
402  QwMessage << "Coverage of the z bins:" << QwLog::endl;
403  fField->PrintCoverage(0);
404  QwMessage << "Coverage of the r bins:" << QwLog::endl;
405  fField->PrintCoverage(1);
406  QwMessage << "Coverage of the phi bins:" << QwLog::endl;
407  fField->PrintCoverage(2);
408  }
409 
410  return true;
411 }
412 
413 /**
414  * Get a field value
415  * @param coord_xyz[] Cartesian coordinates (x,y,z)
416  * @param field[] Field components (x,y,z,r,phi) (return)
417  */
419  const double coord_xyz[3],
420  double field[value_n]) const
421 {
422  // Convert from cartesian to cylindrical coordinates
423  double z = coord_xyz[2];
424  double r = sqrt(coord_xyz[0] * coord_xyz[0] + coord_xyz[1] * coord_xyz[1]);
425  double phi = atan2(coord_xyz[1], coord_xyz[0]); if (phi < 0.0) phi += 2.0*Qw::pi;
426 
427  // The ordering is important! It has to agree with how the ordering is
428  // defined on initialization of the field map. Since tracks are in fairly
429  // constant phi planes, it makes sense to keep phi as the most significant
430  // index. The largest changes happen in z, so that should be the least
431  // significant index. This will allow us to use caching more efficiently.
432  double coord_zrf[3] = {z, r, phi};
433 
434  // The magnetic field object was not defined, return zero field and complain
435  if (!this) {
436  QwWarning << "No field map defined: assuming zero field!" << QwLog::endl;
437  for (unsigned int i = 0; i < value_n; i++) field[i] = 0.0;
438  return;
439  }
440 
441  // Retrieve field value
442  bool status = fField->GetValue(coord_zrf,field);
443 
444  // Apply scale factor
445  for (unsigned int i = 0; i < value_n; i++)
446  field[i] *= fScaleFactor;
447 
448  // Warn if the coordinate was inside the field boundaries,
449  // but we still encountered a problem.
450  if (status == false && fField->InBounds(coord_zrf)) {
451  QwWarning << "Could not get field value at (z,r,phi) = " << coord_zrf << QwLog::endl;
452  QwWarning << "Will use field value (x,y,z) = " << field << QwLog::endl;
453  }
454 }
455 
456 
457 void QwMagneticField::LoadBeamProperty(const TString & map) {
458  QwParameterFile mapstr(map.Data());
459  while (mapstr.ReadNextLine()) {
460  mapstr.TrimComment(); // Remove everything after a comment character.
461  mapstr.TrimWhitespace(); // Get rid of leading and trailing spaces.
462  if (mapstr.LineIsEmpty())
463  continue;
464 
465  string varname, varvalue;
466  if (mapstr.HasVariablePair("=", varname, varvalue)) {
467  if (varname == "QTOR") {
468  SetActualCurrent(atof(varvalue.c_str()));
469  }
470  }
471  }
472 }
static const double pi
Angles: base unit is radian.
Definition: QwUnits.h:102
#define QwMessage
Predefined log drain for regular messages.
Definition: QwLog.h:50
std::string fFilename
File name.
std::ostream & operator<<(std::ostream &out, const QwColor &color)
Output stream operator which uses the enum-to-escape-code mapping.
Definition: QwColor.h:153
static const double in
Definition: QwUnits.h:66
void PrintCoverage(const unsigned int dim)
Print coverage map for all bins in one dimension.
An options class.
Definition: QwOptions.h:133
void SetFilename(const std::string &filename)
Set the filename.
unsigned int GetCurrentEntries() const
Get the current number of entries.
static const double deg
Definition: QwUnits.h:106
QwMagneticField(QwOptions &options, const bool suppress_read_field_map=false)
Default constructor.
void TrimComment(const char commentchar)
std::vector< double > fStep
double fRotation
Field rotation and translation with respect to the z axis.
static const unsigned int value_n
bool ReadFieldMapStream(std::istream &input)
Read the field map input stream.
void SetActualCurrent(const double current)
Set the actual current.
void ProcessOptions(QwOptions &options)
Process command line and config file options.
po::options_description_easy_init AddOptions(const std::string &blockname="Specialized options")
Add an option to a named block or create new block.
Definition: QwOptions.h:164
QwInterpolator< field_t, value_n > * fField
Field map.
static const double kG
Definition: QwUnits.h:113
bool Set(const coord_t &coord, const value_t &value)
Set a single value at a coordinate (false if not possible)
T GetValue(const std::string &key)
Get a templated value.
Definition: QwOptions.h:240
void SetWrapCoordinate(const unsigned int dim, const size_t wrap=1)
Set wrapping coordinate.
bool ReadBinaryFile(const std::string &filename)
Read the grid values from binary file.
A logfile class, based on an identical class in the Hermes analyzer.
std::vector< double > fMin
Field map grid min, max, step, wrap.
void SetReferenceCurrent(const double current)
Set the reference current.
bool ReadFieldMapZip(const std::string &filename)
Read a field map input gzip file.
std::vector< size_t > fWrap
std::vector< double > fMax
void LoadBeamProperty(const TString &map)
Load beam property.
bool ReadFieldMapFile(const std::string &filename)
Read a field map input file.
void GetFieldValue(const double point[3], double field[value_n]) const
Get the field value.
bool InBounds(const coord_t *coord) const
Return true if the coordinate is in bounds.
static std::ostream & endl(std::ostream &)
End of the line.
Definition: QwLog.cc:299
const std::string getenv_safe_string(const char *name)
Definition: QwOptions.h:37
static void DefineOptions(QwOptions &options)
Define command line and config file options.
void SetTranslation(const double translation)
Set the field translation along z.
#define QwWarning
Predefined log drain for warnings.
Definition: QwLog.h:45
unsigned int GetMaximumEntries() const
Get the maximum number of entries.
void SetRotation(const double rotation)
Set the field rotation around z (with QwUnits)
bool TestFieldMap()
Test the field map.
virtual ~QwMagneticField()
Virtual destructor.
#define QwError
Predefined log drain for errors.
Definition: QwLog.h:40
value_t GetValue(const coord_t &coord) const
Get the interpolated value at coordinate (zero if out of bounds)
static std::ostream & flush(std::ostream &)
Flush the streams.
Definition: QwLog.cc:319
static const double cm
Length units: base unit is mm.
Definition: QwUnits.h:61
bool ReadFieldMap()
Read a field map.