BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
IPeakShape.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Sample/Correlations/IPeakShape.cpp
6 //! @brief Implements the interface IPeakShape and subclasses.
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 
16 #include "Base/Math/Bessel.h"
17 #include "Base/Math/Constants.h"
18 #include "Base/Math/IntegratorGK.h"
19 #include <limits>
20 
21 namespace {
22 
23 const double maxkappa = std::log(1.0 / std::numeric_limits<double>::epsilon()) / 2.0;
24 const double maxkappa2 = std::log(std::numeric_limits<double>::max());
25 
26 double FisherDistribution(double x, double kappa)
27 {
28  if (kappa <= 0.0) {
29  return 1.0 / (4.0 * M_PI);
30  }
31  double prefactor = kappa / (4.0 * M_PI);
32  if (kappa > maxkappa) {
33  return 2.0 * prefactor * std::exp(kappa * (x - 1.0));
34  }
35  return prefactor * std::exp(kappa * x) / std::sinh(kappa);
36 }
37 
38 double FisherPrefactor(double kappa)
39 {
40  if (kappa <= 0.0) {
41  return 1.0 / (4.0 * M_PI);
42  }
43  if (kappa > maxkappa) {
44  return kappa / 2.0 / M_PI;
45  } else {
46  return kappa * std::exp(kappa) / 4.0 / M_PI / std::sinh(kappa);
47  }
48 }
49 
50 double MisesPrefactor(double kappa)
51 {
52  if (kappa <= 0.0) {
53  return 1.0 / (2.0 * M_PI);
54  }
55  if (kappa > maxkappa2) {
56  return std::sqrt(kappa / 2.0 / M_PI) / (1.0 + 1.0 / (8.0 * kappa));
57  } else {
58  return std::exp(kappa) / (2.0 * M_PI * Math::Bessel::I0(kappa));
59  }
60 }
61 
62 double Gauss3D(double q2, double domainsize)
63 {
64  double norm_factor = std::pow(domainsize / std::sqrt(M_TWOPI), 3.0);
65  double exponent = -q2 * domainsize * domainsize / 2.0;
66  return norm_factor * std::exp(exponent);
67 }
68 
69 double Cauchy3D(double q2, double domainsize)
70 {
71  double lorentz1 = domainsize / (1.0 + q2 * domainsize * domainsize) / M_PI;
72  return domainsize * lorentz1 * lorentz1;
73 }
74 
75 } // namespace
76 
77 // ************************************************************************************************
78 // interface IPeakShape
79 // ************************************************************************************************
80 
81 IPeakShape::IPeakShape(const NodeMeta& meta, const std::vector<double>& PValues)
82  : ISampleNode(meta, PValues)
83 {
84 }
85 
86 IPeakShape::~IPeakShape() = default;
87 
88 // ************************************************************************************************
89 // class IsotropicGaussPeakShape
90 // ************************************************************************************************
91 
92 IsotropicGaussPeakShape::IsotropicGaussPeakShape(double max_intensity, double domainsize)
93  : m_max_intensity(max_intensity), m_domainsize(domainsize)
94 {
95 }
96 
98 
100 {
102 }
103 
105 {
106  double q_norm = q.mag2();
107  return m_max_intensity * Gauss3D(q_norm, m_domainsize);
108 }
109 
110 double IsotropicGaussPeakShape::evaluate(const kvector_t q, const kvector_t q_lattice_point) const
111 {
112  return evaluate(q - q_lattice_point);
113 }
114 
115 // ************************************************************************************************
116 // class IsotropicLorentzPeakShape
117 // ************************************************************************************************
118 
119 IsotropicLorentzPeakShape::IsotropicLorentzPeakShape(double max_intensity, double domainsize)
120  : m_max_intensity(max_intensity), m_domainsize(domainsize)
121 {
122 }
123 
125 
127 {
129 }
130 
132 {
133  double q_norm = q.mag2();
134  return m_max_intensity * Cauchy3D(q_norm, m_domainsize);
135 }
136 
137 double IsotropicLorentzPeakShape::evaluate(const kvector_t q, const kvector_t q_lattice_point) const
138 {
139  return evaluate(q - q_lattice_point);
140 }
141 
142 // ************************************************************************************************
143 // class GaussFisherPeakShape
144 // ************************************************************************************************
145 
146 GaussFisherPeakShape::GaussFisherPeakShape(double max_intensity, double radial_size, double kappa)
147  : m_max_intensity(max_intensity), m_radial_size(radial_size), m_kappa(kappa)
148 {
149 }
150 
152 
154 {
156 }
157 
158 double GaussFisherPeakShape::evaluate(const kvector_t q, const kvector_t q_lattice_point) const
159 {
160  const double q_r = q.mag();
161  const double q_lat_r = q_lattice_point.mag();
162  const double dq2 = (q_r - q_lat_r) * (q_r - q_lat_r);
163  if (q_lat_r == 0.0)
164  return m_max_intensity * Gauss3D(dq2, m_radial_size);
165  const double norm_factor = m_radial_size / std::sqrt(M_TWOPI);
166  const double radial_part = norm_factor * std::exp(-dq2 * m_radial_size * m_radial_size / 2.0);
167  double angular_part = 1.0;
168  if (q_r * q_lat_r > 0.0) {
169  const double dot_norm = q.dot(q_lattice_point) / q_r / q_lat_r;
170  angular_part = FisherDistribution(dot_norm, m_kappa) / (q_r * q_r);
171  }
172  return m_max_intensity * radial_part * angular_part;
173 }
174 
175 // ************************************************************************************************
176 // class LorentzFisherPeakShape
177 // ************************************************************************************************
178 
179 LorentzFisherPeakShape::LorentzFisherPeakShape(double max_intensity, double radial_size,
180  double kappa)
181  : m_max_intensity(max_intensity), m_radial_size(radial_size), m_kappa(kappa)
182 {
183 }
184 
186 
188 {
190 }
191 
192 double LorentzFisherPeakShape::evaluate(const kvector_t q, const kvector_t q_lattice_point) const
193 {
194  const double q_r = q.mag();
195  const double q_lat_r = q_lattice_point.mag();
196  const double dq2 = (q_r - q_lat_r) * (q_r - q_lat_r);
197  if (q_lat_r == 0.0)
198  return m_max_intensity * Cauchy3D(dq2, m_radial_size);
199  const double radial_part = m_radial_size / (1.0 + dq2 * m_radial_size * m_radial_size) / M_PI;
200  double angular_part = 1.0;
201  if (q_r * q_lat_r > 0.0) {
202  const double dot_norm = q.dot(q_lattice_point) / q_r / q_lat_r;
203  angular_part = FisherDistribution(dot_norm, m_kappa) / (q_r * q_r);
204  }
205  return m_max_intensity * radial_part * angular_part;
206 }
207 
208 // ************************************************************************************************
209 // class MisesFisherGaussPeakShape
210 // ************************************************************************************************
211 
212 MisesFisherGaussPeakShape::MisesFisherGaussPeakShape(double max_intensity, double radial_size,
213  kvector_t zenith, double kappa_1,
214  double kappa_2)
215  : m_max_intensity(max_intensity)
216  , m_radial_size(radial_size)
217  , m_zenith(zenith.unit())
218  , m_kappa_1(kappa_1)
219  , m_kappa_2(kappa_2)
220 {
221 }
222 
224 
226 {
228  m_kappa_2);
229 }
230 
231 double MisesFisherGaussPeakShape::evaluate(const kvector_t q, const kvector_t q_lattice_point) const
232 {
233  // radial part
234  const double q_r = q.mag();
235  const double q_lat_r = q_lattice_point.mag();
236  const double dq2 = (q_r - q_lat_r) * (q_r - q_lat_r);
237  if (q_lat_r == 0.0 || q_r == 0.0)
238  return m_max_intensity * Gauss3D(dq2, m_radial_size);
239  const double norm_factor = m_radial_size / std::sqrt(M_TWOPI);
240  const double radial_part = norm_factor * std::exp(-dq2 * m_radial_size * m_radial_size / 2.0);
241  // angular part
242  m_uy = m_zenith.cross(q_lattice_point);
243  kvector_t zxq = m_zenith.cross(q);
244  m_up = q_lattice_point.unit();
245  if (m_uy.mag2() <= 0.0 || zxq.mag2() <= 0.0) {
246  const double x = q.unit().dot(m_up);
247  const double angular_part = FisherDistribution(x, m_kappa_1);
248  return m_max_intensity * radial_part * angular_part;
249  }
250  m_uy = m_uy.unit();
251  m_ux = m_uy.cross(m_zenith);
252  kvector_t q_ortho = q - q.dot(m_zenith) * m_zenith;
253  m_phi = std::acos(q_ortho.unit().dot(m_ux));
254  m_theta = std::acos(q.unit().dot(m_zenith));
255  const double pre_1 = FisherPrefactor(m_kappa_1);
256  const double pre_2 = MisesPrefactor(m_kappa_2);
257  const double integral = RealIntegrator().integrate(
258  [&](double phi) -> double { return integrand(phi); }, 0.0, M_TWOPI);
259  return m_max_intensity * radial_part * pre_1 * pre_2 * integral;
260 }
261 
262 double MisesFisherGaussPeakShape::integrand(double phi) const
263 {
264  kvector_t u_q = std::sin(m_theta) * std::cos(phi) * m_ux
265  + std::sin(m_theta) * std::sin(phi) * m_uy + std::cos(m_theta) * m_zenith;
266  const double fisher = std::exp(m_kappa_1 * (u_q.dot(m_up) - 1.0));
267  const double mises = std::exp(m_kappa_2 * (std::cos(m_phi - phi) - 1.0));
268  return fisher * mises;
269 }
270 
271 // ************************************************************************************************
272 // class MisesGaussPeakShape
273 // ************************************************************************************************
274 
275 MisesGaussPeakShape::MisesGaussPeakShape(double max_intensity, double radial_size, kvector_t zenith,
276  double kappa)
277  : m_max_intensity(max_intensity)
278  , m_radial_size(radial_size)
279  , m_zenith(zenith.unit())
280  , m_kappa(kappa)
281 {
282 }
283 
285 
287 {
289 }
290 
291 double MisesGaussPeakShape::evaluate(const kvector_t q, const kvector_t q_lattice_point) const
292 {
293  m_uy = m_zenith.cross(q_lattice_point);
294  kvector_t zxq = m_zenith.cross(q);
295  if (m_uy.mag2() <= 0.0 || zxq.mag2() <= 0.0) {
296  const double dq2 = (q - q_lattice_point).mag2();
297  return m_max_intensity * Gauss3D(dq2, m_radial_size);
298  }
299  m_qr = q.mag();
300  m_p = q_lattice_point;
301  m_uy = m_uy.unit();
302  m_ux = m_uy.cross(m_zenith);
303  kvector_t q_ortho = q - q.dot(m_zenith) * m_zenith;
304  m_phi = std::acos(q_ortho.unit().dot(m_ux));
305  m_theta = std::acos(q.unit().dot(m_zenith));
306  const double pre = MisesPrefactor(m_kappa);
307  const double integral = RealIntegrator().integrate(
308  [&](double phi) -> double { return integrand(phi); }, 0.0, M_TWOPI);
309  return m_max_intensity * pre * integral;
310 }
311 
312 double MisesGaussPeakShape::integrand(double phi) const
313 {
314  kvector_t q_rot = m_qr
315  * (std::sin(m_theta) * std::cos(phi) * m_ux
316  + std::sin(m_theta) * std::sin(phi) * m_uy + std::cos(m_theta) * m_zenith);
317  const double dq2 = (q_rot - m_p).mag2();
318  const double gauss = Gauss3D(dq2, m_radial_size);
319  const double mises = std::exp(m_kappa * (std::cos(m_phi - phi) - 1.0));
320  return gauss * mises;
321 }
Defines Bessel functions in namespace Math.
Defines M_PI and some more mathematical constants.
#define M_TWOPI
Definition: Constants.h:54
#define M_PI
Definition: Constants.h:44
Defines the interface IPeakShape and subclasses.
Defines classes RealIntegrator, ComplexIntegrator.
double mag2() const
Returns magnitude squared of the vector.
auto dot(const BasicVector3D< U > &v) const
Returns dot product of vectors (antilinear in the first [=self] argument).
BasicVector3D< T > unit() const
Returns unit vector in direction of this. Throws for null vector.
double mag() const
Returns magnitude of the vector.
auto cross(const BasicVector3D< U > &v) const
Returns cross product of vectors (linear in both arguments).
A peak shape that is Gaussian in the radial direction and uses the Mises-Fisher distribution in the a...
Definition: IPeakShape.h:89
GaussFisherPeakShape(double max_intensity, double radial_size, double kappa)
Definition: IPeakShape.cpp:146
double evaluate(const kvector_t q, const kvector_t q_lattice_point) const override
Evaluates the peak shape at q from a reciprocal lattice point at q_lattice_point.
Definition: IPeakShape.cpp:158
GaussFisherPeakShape * clone() const override
Returns a clone of this ISampleNode object.
Definition: IPeakShape.cpp:153
~GaussFisherPeakShape() override
virtual ~IPeakShape()
IPeakShape()=default
Abstract base class for sample components and properties related to scattering.
Definition: ISampleNode.h:28
Class that implements an isotropic Gaussian peak shape of a Bragg peak.
Definition: IPeakShape.h:46
double evaluate(const kvector_t q, const kvector_t q_lattice_point) const override
Evaluates the peak shape at q from a reciprocal lattice point at q_lattice_point.
Definition: IPeakShape.cpp:110
IsotropicGaussPeakShape * clone() const override
Returns a clone of this ISampleNode object.
Definition: IPeakShape.cpp:99
~IsotropicGaussPeakShape() override
IsotropicGaussPeakShape(double max_intensity, double domainsize)
Definition: IPeakShape.cpp:92
An isotropic Lorentzian peak shape of a Bragg peak.
Definition: IPeakShape.h:67
IsotropicLorentzPeakShape(double max_intensity, double domainsize)
Definition: IPeakShape.cpp:119
IsotropicLorentzPeakShape * clone() const override
Returns a clone of this ISampleNode object.
Definition: IPeakShape.cpp:126
~IsotropicLorentzPeakShape() override
double evaluate(const kvector_t q, const kvector_t q_lattice_point) const override
Evaluates the peak shape at q from a reciprocal lattice point at q_lattice_point.
Definition: IPeakShape.cpp:137
A peak shape that is Lorentzian in the radial direction and uses the Mises-Fisher distribution in the...
Definition: IPeakShape.h:113
LorentzFisherPeakShape(double max_intensity, double radial_size, double kappa)
Definition: IPeakShape.cpp:179
~LorentzFisherPeakShape() override
LorentzFisherPeakShape * clone() const override
Returns a clone of this ISampleNode object.
Definition: IPeakShape.cpp:187
double evaluate(const kvector_t q, const kvector_t q_lattice_point) const override
Evaluates the peak shape at q from a reciprocal lattice point at q_lattice_point.
Definition: IPeakShape.cpp:192
A peak shape that is Gaussian in the radial direction and a convolution of a Mises-Fisher distributio...
Definition: IPeakShape.h:137
MisesFisherGaussPeakShape * clone() const override
Returns a clone of this ISampleNode object.
Definition: IPeakShape.cpp:225
double integrand(double phi) const
Definition: IPeakShape.cpp:262
double evaluate(const kvector_t q, const kvector_t q_lattice_point) const override
Evaluates the peak shape at q from a reciprocal lattice point at q_lattice_point.
Definition: IPeakShape.cpp:231
MisesFisherGaussPeakShape(double max_intensity, double radial_size, kvector_t zenith, double kappa_1, double kappa_2)
Definition: IPeakShape.cpp:212
~MisesFisherGaussPeakShape() override
A peak shape that is a convolution of a Mises-Fisher distribution with a 3d Gaussian.
Definition: IPeakShape.h:166
~MisesGaussPeakShape() override
MisesGaussPeakShape(double max_intensity, double radial_size, kvector_t zenith, double kappa)
Definition: IPeakShape.cpp:275
double evaluate(const kvector_t q, const kvector_t q_lattice_point) const override
Evaluates the peak shape at q from a reciprocal lattice point at q_lattice_point.
Definition: IPeakShape.cpp:291
MisesGaussPeakShape * clone() const override
Returns a clone of this ISampleNode object.
Definition: IPeakShape.cpp:286
kvector_t m_zenith
Definition: IPeakShape.h:182
double integrand(double phi) const
Definition: IPeakShape.cpp:312
To integrate a real function of a real variable.
Definition: IntegratorGK.h:28
double integrate(const std::function< double(double)> &f, double lmin, double lmax)
double I0(double x)
Modified Bessel function of the first kind and order 0.
Definition: Bessel.cpp:177
static constexpr double gauss
Definition: Units.h:50
Metadata of one model node.
Definition: INode.h:38