BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
PolyhedralComponents.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Sample/HardParticle/PolyhedralComponents.cpp
6 //! @brief Implements classes PolyhedralEdge, PolyhedralFace
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/Functions.h"
17 #include "Base/Math/Precomputed.h"
18 #include <iomanip>
19 #include <stdexcept> // need overlooked by g++ 5.4
20 
21 namespace {
22 const double eps = 2e-16;
23 constexpr auto ReciprocalFactorialArray = Math::generateReciprocalFactorialArray<171>();
24 } // namespace
25 
26 #ifdef ALGORITHM_DIAGNOSTIC
27 void PolyhedralDiagnosis::reset()
28 {
29  order = 0;
30  algo = 0;
31  msg.clear();
32 };
33 std::string PolyhedralDiagnosis::message() const
34 {
35  std::string ret = "algo=" + std::to_string(algo) + ", order=" + std::to_string(order);
36  if (!msg.empty())
37  ret += ", msg:\n" + msg;
38  return ret;
39 }
40 bool PolyhedralDiagnosis::operator==(const PolyhedralDiagnosis& other) const
41 {
42  return order == other.order && algo == other.algo;
43 }
44 bool PolyhedralDiagnosis::operator!=(const PolyhedralDiagnosis& other) const
45 {
46  return !(*this == other);
47 }
48 #endif
49 
50 // ************************************************************************************************
51 // PolyhedralEdge implementation
52 // ************************************************************************************************
53 
55  : m_E((_Vhig - _Vlow) / 2), m_R((_Vhig + _Vlow) / 2)
56 {
57  if (m_E.mag2() == 0)
58  throw std::invalid_argument("At least one edge has zero length");
59 };
60 
61 //! Returns sum_l=0^M/2 u^2l v^(M-2l) / (2l+1)!(M-2l)! - vperp^M/M!
62 
64 {
65  complex_t u = qE(qpa);
66  complex_t v2 = m_R.dot(qpa);
67  complex_t v1 = qrperp;
68  complex_t v = v2 + v1;
69  // std::cout << std::scientific << std::showpos << std::setprecision(16) << "contrib: u=" << u
70  // << " v1=" << v1 << " v2=" << v2 << "\n";
71  if (v == 0.) { // only 2l=M contributes
72  if (M & 1) // M is odd
73  return 0.;
74  else
75  return ReciprocalFactorialArray[M] * (pow(u, M) / (M + 1.) - pow(v1, M));
76  }
77  complex_t ret = 0;
78  // the l=0 term, minus (qperp.R)^M, which cancels under the sum over E*contrib()
79  if (v1 == 0.) {
80  ret = ReciprocalFactorialArray[M] * pow(v2, M);
81  } else if (v2 == 0.) {
82  ; // leave ret=0
83  } else {
84  // binomial expansion
85  for (int mm = 1; mm <= M; ++mm) {
86  complex_t term = ReciprocalFactorialArray[mm] * ReciprocalFactorialArray[M - mm]
87  * pow(v2, mm) * pow(v1, M - mm);
88  ret += term;
89  // std::cout << "contrib mm=" << mm << " t=" << term << " s=" << ret << "\n";
90  }
91  }
92  if (u == 0.)
93  return ret;
94  for (int l = 1; l <= M / 2; ++l) {
95  complex_t term = ReciprocalFactorialArray[M - 2 * l] * ReciprocalFactorialArray[2 * l + 1]
96  * pow(u, 2 * l) * pow(v, M - 2 * l);
97  ret += term;
98  // std::cout << "contrib l=" << l << " t=" << term << " s=" << ret << "\n";
99  }
100  return ret;
101 }
102 
103 // ************************************************************************************************
104 // PolyhedralFace implementation
105 // ************************************************************************************************
106 
109 
110 //! Static method, returns diameter of circle that contains all vertices.
111 
112 double PolyhedralFace::diameter(const std::vector<kvector_t>& V)
113 {
114  double diameterFace = 0;
115  for (size_t j = 0; j < V.size(); ++j)
116  for (size_t jj = j + 1; jj < V.size(); ++jj)
117  diameterFace = std::max(diameterFace, (V[j] - V[jj]).mag());
118  return diameterFace;
119 }
120 
121 //! Sets internal variables for given vertex chain.
122 
123 //! @param V oriented vertex list
124 //! @param _sym_S2 true if face has a perpedicular two-fold symmetry axis
125 
126 PolyhedralFace::PolyhedralFace(const std::vector<kvector_t>& V, bool _sym_S2) : sym_S2(_sym_S2)
127 {
128  size_t NV = V.size();
129  if (!NV)
130  throw std::logic_error("Face with no edges");
131  if (NV < 3)
132  throw std::logic_error("Face with less than three edges");
133 
134  // compute radius in 2d and 3d
135  m_radius_2d = diameter(V) / 2;
136  m_radius_3d = 0;
137  for (const kvector_t& v : V)
138  m_radius_3d = std::max(m_radius_3d, v.mag());
139 
140  // Initialize list of 'edges'.
141  // Do not create an edge if two vertices are too close to each other.
142  // TODO This is implemented in a somewhat sloppy way: we just skip an edge if it would
143  // be too short. This leaves tiny open edges. In a clean implementation, we
144  // rather should merge adjacent vertices before generating edges.
145  for (size_t j = 0; j < NV; ++j) {
146  size_t jj = (j + 1) % NV;
147  if ((V[j] - V[jj]).mag() < 1e-14 * m_radius_2d)
148  continue; // distance too short -> skip this edge
149  edges.push_back(PolyhedralEdge(V[j], V[jj]));
150  }
151  size_t NE = edges.size();
152  if (NE < 3)
153  throw std::invalid_argument("Face has less than three non-vanishing edges");
154 
155  // compute n_k, rperp
156  m_normal = kvector_t();
157  for (size_t j = 0; j < NE; ++j) {
158  size_t jj = (j + 1) % NE;
159  kvector_t ee = edges[j].E().cross(edges[jj].E());
160  if (ee.mag2() == 0) {
161  throw std::logic_error("Two adjacent edges are parallel");
162  }
163  m_normal += ee.unit();
164  }
165  m_normal /= NE;
166  m_rperp = 0;
167  for (size_t j = 0; j < NV; ++j)
168  m_rperp += V[j].dot(m_normal);
169  m_rperp /= NV;
170  // assert that the vertices lay in a plane
171  for (size_t j = 1; j < NV; ++j)
172  if (std::abs(V[j].dot(m_normal) - m_rperp) > 1e-14 * m_radius_3d)
173  throw std::logic_error("Face is not planar");
174  // compute m_area
175  m_area = 0;
176  for (size_t j = 0; j < NV; ++j) {
177  size_t jj = (j + 1) % NV;
178  m_area += m_normal.dot(V[j].cross(V[jj])) / 2;
179  }
180  // only now deal with inversion symmetry
181  if (sym_S2) {
182  if (NE & 1)
183  throw std::logic_error("Odd #edges violates symmetry S2");
184  NE /= 2;
185  for (size_t j = 0; j < NE; ++j) {
186  if (((edges[j].R() - m_rperp * m_normal) + (edges[j + NE].R() - m_rperp * m_normal))
187  .mag()
188  > 1e-12 * m_radius_2d)
189  throw std::logic_error("Edge centers violate symmetry S2");
190  if ((edges[j].E() + edges[j + NE].E()).mag() > 1e-12 * m_radius_2d)
191  throw std::logic_error("Edge vectors violate symmetry S2");
192  }
193  // keep only half of the egdes
194  edges.erase(edges.begin() + NE, edges.end());
195  }
196 }
197 
198 //! Sets qperp and qpa according to argument q and to this polygon's normal.
199 
201 {
202  qperp = m_normal.dot(q);
203  qpa = q - qperp * m_normal;
204  // improve numeric accuracy:
205  qpa -= m_normal.dot(qpa) * m_normal;
206  if (qpa.mag() < eps * std::abs(qperp))
207  qpa = cvector_t(0., 0., 0.);
208 }
209 
210 //! Returns core contribution to f_n
211 
213 {
214  const cvector_t prevec = 2. * m_normal.cross(qpa); // complex conjugation not here but in .dot
215  complex_t ret = 0;
216  const complex_t qrperp = qperp * m_rperp;
217  for (size_t i = 0; i < edges.size(); ++i) {
218  const PolyhedralEdge& e = edges[i];
219  const complex_t vfac = prevec.dot(e.E());
220  const complex_t tmp = e.contrib(m + 1, qpa, qrperp);
221  ret += vfac * tmp;
222  // std::cout << std::scientific << std::showpos << std::setprecision(16)
223  // << "DBX ff_n_core " << m << " " << vfac << " " << tmp
224  // << " term=" << vfac * tmp << " sum=" << ret << "\n";
225  }
226  return ret;
227 }
228 
229 //! Returns contribution qn*f_n [of order q^(n+1)] from this face to the polyhedral form factor.
230 
232 {
233  complex_t qn = q.dot(m_normal); // conj(q)*normal (dot is antilinear in 'this' argument)
234  if (std::abs(qn) < eps * q.mag())
235  return 0.;
236  complex_t qperp;
237  cvector_t qpa;
238  decompose_q(q, qperp, qpa);
239  double qpa_mag2 = qpa.mag2();
240  if (qpa_mag2 == 0.) {
241  return qn * pow(qperp * m_rperp, n) * m_area * ReciprocalFactorialArray[n];
242  } else if (sym_S2) {
243  return qn * (ff_n_core(n, qpa, qperp) + ff_n_core(n, -qpa, qperp)) / qpa_mag2;
244  } else {
245  complex_t tmp = ff_n_core(n, qpa, qperp);
246  // std::cout << "DBX ff_n " << n << " " << qn << " " << tmp << " " << qpa_mag2 << "\n";
247  return qn * tmp / qpa_mag2;
248  }
249 }
250 
251 //! Returns sum of n>=1 terms of qpa expansion of 2d form factor
252 
254  double abslevel) const
255 {
256 #ifdef ALGORITHM_DIAGNOSTIC
257  polyhedralDiagnosis.algo += 1;
258 #endif
259  complex_t sum = 0;
260  complex_t n_fac = I;
261  int count_return_condition = 0;
262  for (int n = 1; n < n_limit_series; ++n) {
263 #ifdef ALGORITHM_DIAGNOSTIC
264  polyhedralDiagnosis.order = std::max(polyhedralDiagnosis.order, n);
265 #endif
266  complex_t term = n_fac * (n & 1 ? fac_odd : fac_even) * ff_n_core(n, qpa, 0) / qpa.mag2();
267  // std::cout << std::setprecision(16) << " sum=" << sum << " +term=" << term << "\n";
268  sum += term;
269  if (std::abs(term) <= eps * std::abs(sum) || std::abs(sum) < eps * abslevel)
270  ++count_return_condition;
271  else
272  count_return_condition = 0;
273  if (count_return_condition > 2)
274  return sum; // regular exit
275  n_fac = mul_I(n_fac);
276  }
277  throw std::runtime_error("Series f(q_pa) not converged");
278 }
279 
280 //! Returns core contribution to analytic 2d form factor.
281 
283 {
284  cvector_t prevec = m_normal.cross(qpa); // complex conjugation will take place in .dot
285  complex_t sum = 0;
286  complex_t vfacsum = 0;
287  for (size_t i = 0; i < edges.size(); ++i) {
288  const PolyhedralEdge& e = edges[i];
289  complex_t qE = e.qE(qpa);
290  complex_t qR = e.qR(qpa);
291  complex_t Rfac = sym_S2 ? sin(qR) : (sym_Ci ? cos(e.qR(q)) : exp_I(qR));
292  complex_t vfac;
293  if (sym_S2 || i < edges.size() - 1) {
294  vfac = prevec.dot(e.E());
295  vfacsum += vfac;
296  } else {
297  vfac = -vfacsum; // to improve numeric accuracy: qcE_J = - sum_{j=0}^{J-1} qcE_j
298  }
299  complex_t term = vfac * Math::sinc(qE) * Rfac;
300  sum += term;
301  // std::cout << std::scientific << std::showpos << std::setprecision(16)
302  // << " sum=" << sum << " term=" << term << " vf=" << vfac << " qE=" << qE
303  // << " qR=" << qR << " sinc=" << Math::sinc(qE) << " Rfac=" << Rfac << "\n";
304  }
305  return sum;
306 }
307 
308 //! Returns the contribution ff(q) of this face to the polyhedral form factor.
309 
311 {
312  complex_t qperp;
313  cvector_t qpa;
314  decompose_q(q, qperp, qpa);
315  double qpa_red = m_radius_2d * qpa.mag();
316  complex_t qr_perp = qperp * m_rperp;
317  complex_t ff0 = (sym_Ci ? 2. * I * sin(qr_perp) : exp_I(qr_perp)) * m_area;
318  if (qpa_red == 0) {
319  return ff0;
320  } else if (qpa_red < qpa_limit_series && !sym_S2) {
321  // summation of power series
322  complex_t fac_even;
323  complex_t fac_odd;
324  if (sym_Ci) {
325  fac_even = 2. * mul_I(sin(qr_perp));
326  fac_odd = 2. * cos(qr_perp);
327  } else {
328  fac_even = exp_I(qr_perp);
329  fac_odd = fac_even;
330  }
331  return ff0 + expansion(fac_even, fac_odd, qpa, std::abs(ff0));
332  } else {
333  // direct evaluation of analytic formula
334  complex_t prefac;
335  if (sym_S2)
336  prefac = sym_Ci ? -8. * sin(qr_perp) : 4. * mul_I(exp_I(qr_perp));
337  else
338  prefac = sym_Ci ? 4. : 2. * exp_I(qr_perp);
339  // std::cout << " qrperp=" << qr_perp << " => prefac=" << prefac << "\n";
340  return prefac * edge_sum_ff(q, qpa, sym_Ci) / mul_I(qpa.mag2());
341  }
342 }
343 
344 //! Two-dimensional form factor, for use in prism, from power series.
345 
347 {
348  return m_area + expansion(1., 1., qpa, std::abs(m_area));
349 }
350 
351 //! Two-dimensional form factor, for use in prism, from sum over edge form factors.
352 
354 {
355  return (sym_S2 ? 4. : 2. / I) * edge_sum_ff(qpa, qpa, false) / qpa.mag2();
356 }
357 
358 //! Returns the two-dimensional form factor of this face, for use in a prism.
359 
361 {
362  if (std::abs(qpa.dot(m_normal)) > eps * qpa.mag())
363  throw std::logic_error("ff_2D called with perpendicular q component");
364  double qpa_red = m_radius_2d * qpa.mag();
365  if (qpa_red == 0) {
366  return m_area;
367  } else if (qpa_red < qpa_limit_series && !sym_S2) {
368  return ff_2D_expanded(qpa);
369  } else {
370  return ff_2D_direct(qpa);
371  }
372 }
373 
374 //! Throws if deviation from inversion symmetry is detected. Does not check vertices.
375 
377 {
378  if (std::abs(m_rperp - other.m_rperp) > 1e-15 * (m_rperp + other.m_rperp))
379  throw std::logic_error("Faces with different distance from origin violate symmetry Ci");
380  if (std::abs(m_area - other.m_area) > 1e-15 * (m_area + other.m_area))
381  throw std::logic_error("Faces with different areas violate symmetry Ci");
382  if ((m_normal + other.m_normal).mag() > 1e-14)
383  throw std::logic_error("Faces do not have opposite orientation, violating symmetry Ci");
384 }
constexpr complex_t I
Definition: Complex.h:21
complex_t mul_I(complex_t z)
Returns product I*z, where I is the imaginary unit.
Definition: Complex.h:24
std::complex< double > complex_t
Definition: Complex.h:20
complex_t exp_I(complex_t z)
Returns exp(I*z), where I is the imaginary unit.
Definition: Complex.h:30
Defines functions in namespace Math.
bool operator!=(const Material &left, const Material &right)
Comparison operator for material wrapper (inequality check)
Definition: Material.cpp:126
bool operator==(const Material &left, const Material &right)
Comparison operator for material wrapper (equality check)
Definition: Material.cpp:113
Defines classes PolyhedralEdge, PolyhedralFace.
Defines namespace Math::Precomputed, providing precomputed constants.
BasicVector3D< double > kvector_t
Definition: Vectors3D.h:21
BasicVector3D< complex_t > cvector_t
Definition: Vectors3D.h:22
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).
One edge of a polygon, for form factor computation.
PolyhedralEdge(const kvector_t _Vlow, const kvector_t _Vhig)
kvector_t E() const
kvector_t m_R
position vector of edge midpoint
complex_t qE(cvector_t q) const
complex_t qR(cvector_t q) const
complex_t contrib(int m, cvector_t qpa, complex_t qrperp) const
Returns sum_l=0^M/2 u^2l v^(M-2l) / (2l+1)!(M-2l)! - vperp^M/M!
kvector_t m_E
vector pointing from mid of edge to upper vertex
A polygon, for form factor computation.
complex_t ff_2D_direct(cvector_t qpa) const
Two-dimensional form factor, for use in prism, from sum over edge form factors.
complex_t ff_n(int m, cvector_t q) const
Returns contribution qn*f_n [of order q^(n+1)] from this face to the polyhedral form factor.
bool sym_S2
if true, then edges obtainable by inversion are not provided
complex_t ff_n_core(int m, cvector_t qpa, complex_t qperp) const
Returns core contribution to f_n.
static int n_limit_series
static double qpa_limit_series
determines when use power series
double m_radius_2d
radius of enclosing cylinder
double m_rperp
distance of this polygon's plane from the origin, along 'm_normal'
complex_t ff_2D_expanded(cvector_t qpa) const
Two-dimensional form factor, for use in prism, from power series.
static double diameter(const std::vector< kvector_t > &V)
Static method, returns diameter of circle that contains all vertices.
complex_t ff_2D(cvector_t qpa) const
Returns the two-dimensional form factor of this face, for use in a prism.
complex_t expansion(complex_t fac_even, complex_t fac_odd, cvector_t qpa, double abslevel) const
Returns sum of n>=1 terms of qpa expansion of 2d form factor.
PolyhedralFace(const std::vector< kvector_t > &_V=std::vector< kvector_t >(), bool _sym_S2=false)
Sets internal variables for given vertex chain.
std::vector< PolyhedralEdge > edges
void decompose_q(cvector_t q, complex_t &qperp, cvector_t &qpa) const
Sets qperp and qpa according to argument q and to this polygon's normal.
kvector_t m_normal
normal vector of this polygon's plane
void assert_Ci(const PolyhedralFace &other) const
Throws if deviation from inversion symmetry is detected. Does not check vertices.
complex_t edge_sum_ff(cvector_t q, cvector_t qpa, bool sym_Ci) const
Returns core contribution to analytic 2d form factor.
complex_t ff(cvector_t q, bool sym_Ci) const
Returns the contribution ff(q) of this face to the polyhedral form factor.
double m_radius_3d
radius of enclosing sphere
double sinc(double x)
sinc function:
Definition: Functions.cpp:53
float dot(const Vector3D &v1, const Vector3D &v2)
Definition: def.cpp:59
Vector3D cross(const Vector3D &v1, const Vector3D &v2)
Definition: def.cpp:54
Some additions to standard library algorithms.
Definition: Algorithms.h:31