BornAgain  1.18.0
Simulate and fit neutron and x-ray scattering at grazing incidence
SimpleUnitConverters.cpp
Go to the documentation of this file.
1 // ************************************************************************** //
2 //
3 // BornAgain: simulate and fit scattering at grazing incidence
4 //
5 //! @file Device/Detector/SimpleUnitConverters.cpp
6 //! @brief Implements IUnitConverter classes.
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************** //
14 
17 #include "Base/Const/Units.h"
18 #include "Device/Beam/Beam.h"
23 #include "Device/Unit/AxisNames.h"
24 #include <algorithm>
25 #include <cmath>
26 #include <stdexcept>
27 
28 namespace
29 {
30 double getQ(double wavelength, double angle)
31 {
32  return 4.0 * M_PI * std::sin(angle) / wavelength;
33 }
34 } // namespace
35 
37  : m_wavelength(beam.getWavelength()), m_alpha_i(-beam.getAlpha()), m_phi_i(beam.getPhi())
38 {
39 }
40 
42 {
43  return m_axis_data_table.size();
44 }
45 
46 void UnitConverterSimple::addAxisData(std::string name, double min, double max,
47  Axes::Units default_units, size_t nbins)
48 {
49  AxisData axis_data{name, min, max, default_units, nbins};
50  m_axis_data_table.push_back(axis_data);
51 }
52 
53 double UnitConverterSimple::calculateMin(size_t i_axis, Axes::Units units_type) const
54 {
55  checkIndex(i_axis);
56  units_type = substituteDefaultUnits(units_type);
57  const auto& axis_data = m_axis_data_table[i_axis];
58  if (units_type == Axes::Units::NBINS) {
59  return 0.0;
60  }
61  return calculateValue(i_axis, units_type, axis_data.min);
62 }
63 
64 double UnitConverterSimple::calculateMax(size_t i_axis, Axes::Units units_type) const
65 {
66  checkIndex(i_axis);
67  units_type = substituteDefaultUnits(units_type);
68  const auto& axis_data = m_axis_data_table[i_axis];
69  if (units_type == Axes::Units::NBINS) {
70  return static_cast<double>(axis_data.nbins);
71  }
72  return calculateValue(i_axis, units_type, axis_data.max);
73 }
74 
75 size_t UnitConverterSimple::axisSize(size_t i_axis) const
76 {
77  checkIndex(i_axis);
78  return m_axis_data_table[i_axis].nbins;
79 }
80 
81 std::vector<Axes::Units> UnitConverterSimple::availableUnits() const
82 {
83  return {Axes::Units::NBINS, Axes::Units::RADIANS, Axes::Units::DEGREES};
84 }
85 
86 std::unique_ptr<IAxis> UnitConverterSimple::createConvertedAxis(size_t i_axis,
87  Axes::Units units) const
88 {
89  const double min = calculateMin(i_axis, units);
90  const double max = calculateMax(i_axis, units);
91  const auto& axis_name = axisName(i_axis, units);
92  const auto axis_size = axisSize(i_axis);
93  return std::make_unique<FixedBinAxis>(axis_name, axis_size, min, max);
94 }
95 
97  : m_axis_data_table(other.m_axis_data_table), m_wavelength(other.m_wavelength),
98  m_alpha_i(other.m_alpha_i), m_phi_i(other.m_phi_i)
99 {
100 }
101 
102 void UnitConverterSimple::addDetectorAxis(const IDetector& detector, size_t i_axis)
103 {
104  const auto& axis = detector.getAxis(i_axis);
105  const auto* p_roi = detector.regionOfInterest();
106  const auto& axis_name = axisName(i_axis);
107  if (p_roi) {
108  auto P_roi_axis = p_roi->clipAxisToRoi(i_axis, axis);
109  addAxisData(axis_name, P_roi_axis->getMin(), P_roi_axis->getMax(), defaultUnits(),
110  P_roi_axis->size());
111  } else {
112  addAxisData(axis_name, axis.getMin(), axis.getMax(), defaultUnits(), axis.size());
113  }
114 }
115 
116 /* SphericalConverter **********************************************/
117 
119  : UnitConverterSimple(beam)
120 {
121  if (detector.dimension() != 2)
122  throw std::runtime_error("Error in SphericalConverter constructor: "
123  "detector has wrong dimension: "
124  + std::to_string(static_cast<int>(detector.dimension())));
125  addDetectorAxis(detector, 0);
126  addDetectorAxis(detector, 1);
127 }
128 
130 
132 {
133  return new SphericalConverter(*this);
134 }
135 
136 std::vector<Axes::Units> SphericalConverter::availableUnits() const
137 {
138  auto result = UnitConverterSimple::availableUnits();
139  result.push_back(Axes::Units::QSPACE);
140  return result;
141 }
142 
144 {
145  return Axes::Units::DEGREES;
146 }
147 
149 {
150 }
151 
152 double SphericalConverter::calculateValue(size_t i_axis, Axes::Units units_type, double value) const
153 {
154  switch (units_type) {
155  case Axes::Units::RADIANS:
156  return value;
157  case Axes::Units::DEGREES:
158  return Units::rad2deg(value);
159  case Axes::Units::QSPACE: {
161  if (i_axis == 0) {
162  const auto k_f = vecOfLambdaAlphaPhi(m_wavelength, 0.0, value);
163  return (k_i - k_f).y();
164  } else if (i_axis == 1) {
165  const auto k_f = vecOfLambdaAlphaPhi(m_wavelength, value, 0.0);
166  return (k_f - k_i).z();
167  }
168  throw std::runtime_error("Error in SphericalConverter::calculateValue: "
169  "incorrect axis index: "
170  + std::to_string(static_cast<int>(i_axis)));
171  }
172  case Axes::Units::QXQY: {
174  if (i_axis == 0) {
175  const auto k_f = vecOfLambdaAlphaPhi(m_wavelength, 0.0, value);
176  return (k_i - k_f).y();
177  } else if (i_axis == 1) {
178  const auto k_f = vecOfLambdaAlphaPhi(m_wavelength, value, 0.0);
179  return (k_f - k_i).x();
180  }
181  throw std::runtime_error("Error in SphericalConverter::calculateValue: "
182  "incorrect axis index: "
183  + std::to_string(static_cast<int>(i_axis)));
184  }
185  default:
186  throwUnitsError("SphericalConverter::calculateValue", availableUnits());
187  }
188 }
189 
190 std::vector<std::map<Axes::Units, std::string>> SphericalConverter::createNameMaps() const
191 {
192  std::vector<std::map<Axes::Units, std::string>> result;
193  result.push_back(AxisNames::InitSphericalAxis0());
194  result.push_back(AxisNames::InitSphericalAxis1());
195  return result;
196 }
197 
198 /* RectangularConverter **********************************************/
199 
201  : UnitConverterSimple(beam)
202 {
203  if (detector.dimension() != 2)
204  throw std::runtime_error("Error in RectangularConverter constructor: "
205  "detector has wrong dimension: "
206  + std::to_string(static_cast<int>(detector.dimension())));
207  addDetectorAxis(detector, 0);
208  addDetectorAxis(detector, 1);
209  mP_detector_pixel.reset(detector.regionOfInterestPixel());
210 }
211 
213 
215 {
216  return new RectangularConverter(*this);
217 }
218 
219 std::vector<Axes::Units> RectangularConverter::availableUnits() const
220 {
221  auto result = UnitConverterSimple::availableUnits();
222  result.push_back(Axes::Units::QSPACE);
223  result.push_back(Axes::Units::MM);
224  return result;
225 }
226 
228 {
229  return Axes::Units::MM;
230 }
231 
233  : UnitConverterSimple(other), mP_detector_pixel(other.mP_detector_pixel->clone())
234 {
235 }
236 
237 double RectangularConverter::calculateValue(size_t i_axis, Axes::Units units_type,
238  double value) const
239 {
240  if (units_type == Axes::Units::MM)
241  return value;
242  const auto k00 = mP_detector_pixel->getPosition(0.0, 0.0);
243  const auto k01 = mP_detector_pixel->getPosition(0.0, 1.0);
244  const auto k10 = mP_detector_pixel->getPosition(1.0, 0.0);
245  const auto& max_pos = i_axis == 0 ? k10 : k01; // position of max along given axis
246  const double shift = value - m_axis_data_table[i_axis].min;
247  const auto k_f = normalizeToWavelength(k00 + shift * (max_pos - k00).unit());
248  switch (units_type) {
249  case Axes::Units::RADIANS:
250  return axisAngle(i_axis, k_f);
251  case Axes::Units::DEGREES:
252  return Units::rad2deg(axisAngle(i_axis, k_f));
253  case Axes::Units::QSPACE: {
255  if (i_axis == 0) {
256  return (k_i - k_f).y();
257  } else if (i_axis == 1) {
258  return (k_f - k_i).z();
259  }
260  throw std::runtime_error("Error in RectangularConverter::calculateValue: "
261  "incorrect axis index: "
262  + std::to_string(static_cast<int>(i_axis)));
263  }
264  case Axes::Units::QXQY: {
266  if (i_axis == 0) {
267  return (k_i - k_f).y();
268  } else if (i_axis == 1) {
269  return (k_f - k_i).x();
270  }
271  throw std::runtime_error("Error in RectangularConverter::calculateValue: "
272  "incorrect axis index: "
273  + std::to_string(static_cast<int>(i_axis)));
274  }
275  default:
276  throwUnitsError("RectangularConverter::calculateValue", availableUnits());
277  }
278 }
279 
280 std::vector<std::map<Axes::Units, std::string>> RectangularConverter::createNameMaps() const
281 {
282  std::vector<std::map<Axes::Units, std::string>> result;
283  result.push_back(AxisNames::InitRectangularAxis0());
284  result.push_back(AxisNames::InitRectangularAxis1());
285  return result;
286 }
287 
289 {
290  if (m_wavelength <= 0.0)
291  throw std::runtime_error("Error in RectangularConverter::normalizeToWavelength: "
292  "wavelength <= 0");
293  double K = M_TWOPI / m_wavelength;
294  return vector.unit() * K;
295 }
296 
297 double RectangularConverter::axisAngle(size_t i_axis, kvector_t k_f) const
298 {
299  if (i_axis == 0) {
300  return k_f.phi();
301  } else if (i_axis == 1) {
302  return M_PI_2 - k_f.theta();
303  }
304  throw std::runtime_error("Error in RectangularConverter::axisAngle: "
305  "incorrect axis index: "
306  + std::to_string(static_cast<int>(i_axis)));
307 }
308 
309 /* OffSpecularConverter **********************************************/
310 
312  const IAxis& alpha_axis)
313  : UnitConverterSimple(beam)
314 {
315  if (detector.dimension() != 2)
316  throw std::runtime_error("Error in OffSpecularConverter constructor: "
317  "detector has wrong dimension: "
318  + std::to_string(static_cast<int>(detector.dimension())));
319  addAxisData(axisName(0), alpha_axis.getMin(), alpha_axis.getMax(), defaultUnits(),
320  alpha_axis.size());
321  addDetectorYAxis(detector);
322 }
323 
325 
327 {
328  return new OffSpecularConverter(*this);
329 }
330 
332 {
333  return Axes::Units::DEGREES;
334 }
335 
337  : UnitConverterSimple(other)
338 {
339 }
340 
341 double OffSpecularConverter::calculateValue(size_t, Axes::Units units_type, double value) const
342 {
343  switch (units_type) {
344  case Axes::Units::RADIANS:
345  return value;
346  case Axes::Units::DEGREES:
347  return Units::rad2deg(value);
348  default:
349  throwUnitsError("OffSpecularConverter::calculateValue", availableUnits());
350  }
351 }
352 
353 std::vector<std::map<Axes::Units, std::string>> OffSpecularConverter::createNameMaps() const
354 {
355  std::vector<std::map<Axes::Units, std::string>> result;
356  result.push_back(AxisNames::InitOffSpecAxis0());
357  result.push_back(AxisNames::InitOffSpecAxis1());
358  return result;
359 }
360 
362 {
363  const auto& axis = detector.getAxis(1);
364  const auto* p_roi = detector.regionOfInterest();
365  const auto& axis_name = axisName(1);
366  std::unique_ptr<IAxis> P_new_axis;
367  if (p_roi) {
368  P_new_axis = p_roi->clipAxisToRoi(1, axis);
369  } else {
370  P_new_axis.reset(axis.clone());
371  }
372  if (!P_new_axis)
373  throw std::runtime_error("Error in OffSpecularConverter::addDetectorYAxis: "
374  "could not retrieve the y-axis of the detector");
375  if (const auto* P_rect_det = dynamic_cast<const RectangularDetector*>(&detector)) {
376  std::unique_ptr<RectangularPixel> P_det_pixel(P_rect_det->regionOfInterestPixel());
377  const auto k00 = P_det_pixel->getPosition(0.0, 0.0);
378  const auto k01 = P_det_pixel->getPosition(0.0, 1.0);
379  const double alpha_f_min = M_PI_2 - k00.theta();
380  const double alpha_f_max = M_PI_2 - k01.theta();
381  addAxisData(axis_name, alpha_f_min, alpha_f_max, defaultUnits(), P_new_axis->size());
382  } else if (dynamic_cast<const SphericalDetector*>(&detector)) {
383  const double alpha_f_min = P_new_axis->getMin();
384  const double alpha_f_max = P_new_axis->getMax();
385  addAxisData(axis_name, alpha_f_min, alpha_f_max, defaultUnits(), P_new_axis->size());
386  } else {
387  throw std::runtime_error("Error in OffSpecularConverter::addDetectorYAxis: "
388  "wrong detector type");
389  }
390 }
391 
392 /* DepthProbeConverter **********************************************/
393 
394 const std::string z_axis_name = "Position [nm]";
395 
396 DepthProbeConverter::DepthProbeConverter(const Beam& beam, const IAxis& alpha_axis,
397  const IAxis& z_axis)
398  : UnitConverterSimple(beam)
399 {
400  const auto& alpha_axis_name = axisName(0);
401  const auto& z_axis_name = axisName(1);
402  addAxisData(alpha_axis_name, alpha_axis.getMin(), alpha_axis.getMax(), defaultUnits(),
403  alpha_axis.size());
404  addAxisData(z_axis_name, z_axis.getMin(), z_axis.getMax(), defaultUnits(), z_axis.size());
405 }
406 
408 
410 {
411  return new DepthProbeConverter(*this);
412 }
413 
414 std::vector<Axes::Units> DepthProbeConverter::availableUnits() const
415 {
416  auto result = UnitConverterSimple::availableUnits();
417  result.push_back(Axes::Units::QSPACE);
418  return result;
419 }
420 
422  : UnitConverterSimple(other)
423 {
424 }
425 
426 double DepthProbeConverter::calculateValue(size_t i_axis, Axes::Units units_type,
427  double value) const
428 {
429  checkUnits(units_type);
430  if (i_axis == 1)
431  return value; // unit conversions are not applied to sample position axis
432  switch (units_type) {
433  case Axes::Units::DEGREES:
434  return Units::rad2deg(value);
435  case Axes::Units::QSPACE:
436  return getQ(m_wavelength, value);
437  default:
438  return value;
439  }
440 }
441 
442 std::vector<std::map<Axes::Units, std::string>> DepthProbeConverter::createNameMaps() const
443 {
444  std::vector<std::map<Axes::Units, std::string>> result;
445  result.push_back(AxisNames::InitSpecAxis());
446  result.push_back(AxisNames::InitSampleDepthAxis());
447  return result;
448 }
449 
451 {
452  const auto& available_units = availableUnits();
453  if (std::find(available_units.begin(), available_units.end(), units_type)
454  == available_units.cend())
455  throwUnitsError("DepthProbeConverter::checkUnits", available_units);
456 }
Defines namespace AxisNames.
BasicVector3D< double > vecOfLambdaAlphaPhi(double _lambda, double _alpha, double _phi)
Creates a vector<double> as a wavevector with given wavelength and angles.
Defines class Beam.
Defines M_PI and some more mathematical constants.
#define M_TWOPI
Definition: MathConstants.h:49
#define M_PI_2
Definition: MathConstants.h:40
#define M_PI
Definition: MathConstants.h:39
Defines class RectangularDetector.
Defines class RectangularPixel.
Defines class RegionOfInterest.
const std::string z_axis_name
Defines interface UnitConverterSimple and its subclasses.
Defines class SphericalDetector.
Defines some unit conversion factors and other constants in namespace Units.
BasicVector3D< T > unit() const
Returns unit vector in direction of this. Throws for null vector.
double theta() const
Returns polar angle.
double phi() const
Returns azimuth angle.
Beam defined by wavelength, direction and intensity.
Definition: Beam.h:27
DepthProbeConverter class handles the unit translations for depth probe simulations Its default units...
Axes::Units defaultUnits() const final
double calculateValue(size_t, Axes::Units units_type, double value) const final
std::vector< std::map< Axes::Units, std::string > > createNameMaps() const final
DepthProbeConverter(const Beam &beam, const IAxis &alpha_axis, const IAxis &z_axis)
std::vector< Axes::Units > availableUnits() const final
Returns the list of all available units.
~DepthProbeConverter() final
DepthProbeConverter * clone() const final
void checkUnits(Axes::Units units_type) const
Interface for one-dimensional axes.
Definition: IAxis.h:25
virtual double getMin() const =0
Returns value of first point of axis.
virtual size_t size() const =0
retrieve the number of bins
virtual double getMax() const =0
Returns value of last point of axis.
Abstract 2D detector interface.
Definition: IDetector2D.h:31
const RegionOfInterest * regionOfInterest() const override
Returns region of interest if exists.
Definition: IDetector2D.cpp:43
Abstract detector interface.
Definition: IDetector.h:36
size_t dimension() const
Returns actual dimensionality of the detector (number of defined axes)
Definition: IDetector.cpp:44
const IAxis & getAxis(size_t index) const
Definition: IDetector.cpp:54
virtual const RegionOfInterest * regionOfInterest() const =0
Returns region of interest if exists.
Axes::Units substituteDefaultUnits(Axes::Units units) const
void checkIndex(size_t i_axis) const
virtual Axes::Units defaultUnits() const =0
void throwUnitsError(std::string method, std::vector< Axes::Units > available) const
std::string axisName(size_t i_axis, Axes::Units units_type=Axes::Units::DEFAULT) const
IUnitConverter class that handles the unit translations for off-specular simulations with a spherical...
OffSpecularConverter(const IDetector2D &detector, const Beam &beam, const IAxis &alpha_axis)
Axes::Units defaultUnits() const final
std::vector< std::map< Axes::Units, std::string > > createNameMaps() const final
double calculateValue(size_t i_axis, Axes::Units units_type, double value) const final
OffSpecularConverter * clone() const final
void addDetectorYAxis(const IDetector2D &detector)
~OffSpecularConverter() final
IUnitConverter class that handles the unit translations for rectangular detectors Its default units a...
kvector_t normalizeToWavelength(kvector_t vector) const
std::vector< std::map< Axes::Units, std::string > > createNameMaps() const final
RectangularConverter(const RectangularDetector &detector, const Beam &beam)
double axisAngle(size_t i_axis, kvector_t k_f) const
~RectangularConverter() final
RectangularConverter * clone() const final
double calculateValue(size_t i_axis, Axes::Units units_type, double value) const final
Axes::Units defaultUnits() const final
std::unique_ptr< RectangularPixel > mP_detector_pixel
std::vector< Axes::Units > availableUnits() const final
Returns the list of all available units.
A flat rectangular detector with axes and resolution function.
RectangularPixel * regionOfInterestPixel() const
IUnitConverter class that handles the unit translations for spherical detectors Its default units are...
SphericalConverter(const SphericalDetector &detector, const Beam &beam)
std::vector< std::map< Axes::Units, std::string > > createNameMaps() const final
SphericalConverter * clone() const final
~SphericalConverter() final
double calculateValue(size_t i_axis, Axes::Units units_type, double value) const final
std::vector< Axes::Units > availableUnits() const final
Returns the list of all available units.
Axes::Units defaultUnits() const final
A spherical detector with axes and resolution function.
Interface for objects that provide axis translations to different units for IDetector objects.
void addDetectorAxis(const IDetector &detector, size_t i_axis)
double calculateMax(size_t i_axis, Axes::Units units_type) const final
double calculateMin(size_t i_axis, Axes::Units units_type) const final
void addAxisData(std::string name, double min, double max, Axes::Units default_units, size_t nbins)
std::vector< AxisData > m_axis_data_table
size_t axisSize(size_t i_axis) const final
virtual size_t dimension() const final
virtual double calculateValue(size_t i_axis, Axes::Units units_type, double value) const =0
std::unique_ptr< IAxis > createConvertedAxis(size_t i_axis, Axes::Units units) const final
UnitConverterSimple(const Beam &beam)
std::vector< Axes::Units > availableUnits() const override
Returns the list of all available units.
std::map< Axes::Units, std::string > InitOffSpecAxis1()
Definition: AxisNames.cpp:74
std::map< Axes::Units, std::string > InitRectangularAxis0()
Definition: AxisNames.cpp:42
std::map< Axes::Units, std::string > InitRectangularAxis1()
Definition: AxisNames.cpp:53
std::map< Axes::Units, std::string > InitSphericalAxis0()
Definition: AxisNames.cpp:21
std::map< Axes::Units, std::string > InitOffSpecAxis0()
Definition: AxisNames.cpp:66
std::map< Axes::Units, std::string > InitSphericalAxis1()
Definition: AxisNames.cpp:31
std::map< Axes::Units, std::string > InitSpecAxis()
Definition: AxisNames.cpp:83
std::map< Axes::Units, std::string > InitSampleDepthAxis()
Definition: AxisNames.cpp:109
double rad2deg(double angle)
Definition: Units.h:43
double getQ(double wavelength, double angle)