23 const double eps = 2e-16;
24 constexpr
auto ReciprocalFactorialArray = Precomputed::GenerateReciprocalFactorialArray<171>();
32 : m_E((_Vhig - _Vlow) / 2), m_R((_Vhig + _Vlow) / 2)
35 throw std::invalid_argument(
"At least one edge has zero length");
42 complex_t u = qE(qpa);
43 complex_t v2 = m_R.
dot(qpa);
44 complex_t v1 = qrperp;
45 complex_t v = v2 + v1;
46 #ifdef POLYHEDRAL_DIAGNOSTIC
47 if (diagnosis.debmsg >= 5)
48 std::cout <<
std::scientific << std::showpos << std::setprecision(16) <<
"contrib: u=" << u
49 <<
" v1=" << v1 <<
" v2=" << v2 <<
"\n";
55 return ReciprocalFactorialArray[M] * (pow(u, M) / (M + 1.) - pow(v1, M));
60 ret = ReciprocalFactorialArray[M] * pow(v2, M);
61 }
else if (v2 == 0.) {
65 for (
int mm = 1; mm <= M; ++mm) {
66 complex_t term = ReciprocalFactorialArray[mm] * ReciprocalFactorialArray[M - mm]
67 * pow(v2, mm) * pow(v1, M - mm);
69 #ifdef POLYHEDRAL_DIAGNOSTIC
70 if (diagnosis.debmsg >= 6)
71 std::cout <<
"contrib mm=" << mm <<
" t=" << term <<
" s=" << ret <<
"\n";
77 for (
int l = 1; l <= M / 2; ++l) {
78 complex_t term = ReciprocalFactorialArray[M - 2 * l] * ReciprocalFactorialArray[2 * l + 1]
79 * pow(u, 2 * l) * pow(v, M - 2 * l);
81 #ifdef POLYHEDRAL_DIAGNOSTIC
82 if (diagnosis.debmsg >= 6)
83 std::cout <<
"contrib l=" << l <<
" t=" << term <<
" s=" << ret <<
"\n";
93 double PolyhedralFace::qpa_limit_series = 3e-2;
94 int PolyhedralFace::n_limit_series = 20;
100 double diameterFace = 0;
101 for (
size_t j = 0; j < V.size(); ++j)
102 for (
size_t jj = j + 1; jj < V.size(); ++jj)
103 diameterFace = std::max(diameterFace, (V[j] - V[jj]).mag());
107 #ifdef POLYHEDRAL_DIAGNOSTIC
108 void PolyhedralFace::setLimits(
double _qpa,
int _n)
110 qpa_limit_series = _qpa;
122 size_t NV = V.size();
124 throw std::logic_error(
"Face with no edges");
126 throw std::logic_error(
"Face with less than three edges");
132 m_radius_3d = std::max(m_radius_3d, v.mag());
139 for (
size_t j = 0; j < NV; ++j) {
140 size_t jj = (j + 1) % NV;
141 if ((V[j] - V[jj]).mag() < 1e-14 * m_radius_2d)
145 size_t NE = edges.size();
147 throw std::invalid_argument(
"Face has less than three non-vanishing edges");
151 for (
size_t j = 0; j < NE; ++j) {
152 size_t jj = (j + 1) % NE;
153 kvector_t ee = edges[j].E().cross(edges[jj].E());
154 if (ee.
mag2() == 0) {
155 throw std::logic_error(
"Two adjacent edges are parallel");
157 m_normal += ee.
unit();
161 for (
size_t j = 0; j < NV; ++j)
162 m_rperp += V[j].dot(m_normal);
165 for (
size_t j = 1; j < NV; ++j)
166 if (std::abs(V[j].dot(m_normal) - m_rperp) > 1e-14 * m_radius_3d)
167 throw std::logic_error(
"Face is not planar");
170 for (
size_t j = 0; j < NV; ++j) {
171 size_t jj = (j + 1) % NV;
172 m_area += m_normal.
dot(V[j].cross(V[jj])) / 2;
177 throw std::logic_error(
"Odd #edges violates symmetry S2");
179 for (
size_t j = 0; j < NE; ++j) {
180 if (((edges[j].R() - m_rperp * m_normal) + (edges[j + NE].R() - m_rperp * m_normal))
182 > 1e-12 * m_radius_2d)
183 throw std::logic_error(
"Edge centers violate symmetry S2");
184 if ((edges[j].E() + edges[j + NE].E()).mag() > 1e-12 * m_radius_2d)
185 throw std::logic_error(
"Edge vectors violate symmetry S2");
188 edges.erase(edges.begin() + NE, edges.end());
196 qperp = m_normal.
dot(q);
197 qpa = q - qperp * m_normal;
199 qpa -= m_normal.
dot(qpa) * m_normal;
200 if (qpa.
mag() < eps * std::abs(qperp))
206 complex_t PolyhedralFace::ff_n_core(
int m,
cvector_t qpa, complex_t qperp)
const
210 complex_t vfacsum = 0;
211 complex_t qrperp = qperp * m_rperp;
212 for (
size_t i = 0; i < edges.size(); ++i) {
215 if (sym_S2 || i < edges.size() - 1) {
216 vfac = prevec.
dot(e.E());
221 complex_t tmp = e.
contrib(m + 1, qpa, qrperp);
223 #ifdef POLYHEDRAL_DIAGNOSTIC
224 if (diagnosis.debmsg >= 4)
226 <<
"DBX ff_n_core " << m <<
" " << vfac <<
" " << tmp
227 <<
" term=" << vfac * tmp <<
" sum=" << ret <<
"\n";
237 complex_t qn = q.
dot(m_normal);
238 if (std::abs(qn) < eps * q.
mag())
242 decompose_q(q, qperp, qpa);
243 double qpa_mag2 = qpa.
mag2();
244 if (qpa_mag2 == 0.) {
245 return qn * pow(qperp * m_rperp, n) * m_area * ReciprocalFactorialArray[n];
247 return qn * (ff_n_core(n, qpa, qperp) + ff_n_core(n, -qpa, qperp)) / qpa_mag2;
249 complex_t tmp = ff_n_core(n, qpa, qperp);
250 #ifdef POLYHEDRAL_DIAGNOSTIC
251 if (diagnosis.debmsg >= 3)
252 std::cout <<
"DBX ff_n " << n <<
" " << qn <<
" " << tmp <<
" " << qpa_mag2 <<
"\n";
254 return qn * tmp / qpa_mag2;
260 complex_t PolyhedralFace::expansion(complex_t fac_even, complex_t fac_odd,
cvector_t qpa,
261 double abslevel)
const
263 #ifdef POLYHEDRAL_DIAGNOSTIC
264 diagnosis.nExpandedFaces += 1;
268 int count_return_condition = 0;
269 for (
int n = 1; n < n_limit_series; ++n) {
270 #ifdef POLYHEDRAL_DIAGNOSTIC
271 diagnosis.maxOrder = std::max(diagnosis.maxOrder, n);
273 complex_t term = n_fac * (n & 1 ? fac_odd : fac_even) * ff_n_core(n, qpa, 0) / qpa.
mag2();
274 #ifdef POLYHEDRAL_DIAGNOSTIC
275 if (diagnosis.debmsg >= 2)
276 std::cout << std::setprecision(16) <<
" sum=" << sum <<
" +term=" << term <<
"\n";
279 if (std::abs(term) <= eps * std::abs(sum) || std::abs(sum) < eps * abslevel)
280 ++count_return_condition;
282 count_return_condition = 0;
283 if (count_return_condition > 2)
285 n_fac =
mul_I(n_fac);
287 #ifdef POLYHEDRAL_DIAGNOSTIC
288 if (!diagnosis.request_convergence)
291 throw std::runtime_error(
"Series f(q_pa) not converged");
300 complex_t vfacsum = 0;
301 for (
size_t i = 0; i < edges.size(); ++i) {
303 complex_t qE = e.qE(qpa);
304 complex_t qR = e.qR(qpa);
305 complex_t Rfac = sym_S2 ? sin(qR) : (sym_Ci ? cos(e.qR(q)) :
exp_I(qR));
307 if (sym_S2 || i < edges.size() - 1) {
308 vfac = prevec.
dot(e.E());
315 #ifdef POLYHEDRAL_DIAGNOSTIC
316 if (diagnosis.debmsg >= 2)
318 <<
" sum=" << sum <<
" term=" << term <<
" vf=" << vfac <<
" qE=" << qE
332 decompose_q(q, qperp, qpa);
333 double qpa_red = m_radius_2d * qpa.
mag();
334 complex_t qr_perp = qperp * m_rperp;
335 complex_t ff0 = (sym_Ci ? 2. * I * sin(qr_perp) :
exp_I(qr_perp)) * m_area;
338 }
else if (qpa_red < qpa_limit_series && !sym_S2) {
343 fac_even = 2. *
mul_I(sin(qr_perp));
344 fac_odd = 2. * cos(qr_perp);
346 fac_even =
exp_I(qr_perp);
349 return ff0 + expansion(fac_even, fac_odd, qpa, std::abs(ff0));
354 prefac = sym_Ci ? -8. * sin(qr_perp) : 4. *
mul_I(
exp_I(qr_perp));
356 prefac = sym_Ci ? 4. : 2. *
exp_I(qr_perp);
357 #ifdef POLYHEDRAL_DIAGNOSTIC
358 if (diagnosis.debmsg >= 2)
359 std::cout <<
" qrperp=" << qr_perp <<
" => prefac=" << prefac <<
"\n";
361 return prefac * edge_sum_ff(q, qpa, sym_Ci) /
mul_I(qpa.
mag2());
369 if (std::abs(qpa.
dot(m_normal)) > eps * qpa.
mag())
370 throw std::logic_error(
"ff_2D called with perpendicular q component");
371 double qpa_red = m_radius_2d * qpa.
mag();
374 }
else if (qpa_red < qpa_limit_series && !sym_S2) {
376 return m_area + expansion(1., 1., qpa, std::abs(m_area));
379 complex_t
ff = edge_sum_ff(qpa, qpa,
false);
380 complex_t ret = (sym_S2 ? 4. : 2. / I) *
ff / qpa.
mag2();
381 #ifdef POLYHEDRAL_DIAGNOSTIC
382 if (diagnosis.debmsg >= 2)
383 std::cout << std::setprecision(16) <<
" ret=" << ret <<
" ff=" <<
ff <<
"\n";
393 if (std::abs(m_rperp - other.m_rperp) > 1e-15 * (m_rperp + other.m_rperp))
394 throw std::logic_error(
"Faces with different distance from origin violate symmetry Ci");
395 if (std::abs(m_area - other.m_area) > 1e-15 * (m_area + other.m_area))
396 throw std::logic_error(
"Faces with different areas violate symmetry Ci");
397 if ((m_normal + other.m_normal).mag() > 1e-14)
398 throw std::logic_error(
"Faces do not have opposite orientation, violating symmetry Ci");
complex_t mul_I(complex_t z)
Returns product I*z, where I is the imaginary unit.
complex_t exp_I(complex_t z)
Returns exp(I*z), where I is the imaginary unit.
Defines namespace MathFunctions.
Defines classes PolyhedralEdge, PolyhedralFace.
Defines namespace Precomputed, providing precomputed constants.
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.
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!
A polygon, for form factor computation.
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.
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.
PolyhedralFace(const std::vector< kvector_t > &_V=std::vector< kvector_t >(), bool _sym_S2=false)
Sets internal variables for given vertex chain.
void assert_Ci(const PolyhedralFace &other) const
Throws if deviation from inversion symmetry is detected. Does not check vertices.
complex_t ff(cvector_t q, bool sym_Ci) const
Returns the contribution ff(q) of this face to the polyhedral form factor.
double sinc(double x)
sinc function:
std::string scientific(const T value, int n=10)
Returns scientific string representing given value of any numeric type.