[go: up one dir, main page]

File: Polyhedron.cpp

package info (click to toggle)
libformfactor 0.3.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,288 kB
  • sloc: cpp: 17,289; python: 382; makefile: 15
file content (319 lines) | stat: -rw-r--r-- 11,282 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
//  ************************************************************************************************
//
//  libformfactor: efficient and accurate computation of scattering form factors
//
//! @file      ff/Polyhedron.cpp
//! @brief     Implements class Polyhedron.
//!
//! @homepage  https://jugit.fz-juelich.de/mlz/libformfactor
//! @license   GNU General Public License v3 or higher (see LICENSE)
//! @copyright Forschungszentrum Jülich GmbH 2022
//! @author    Joachim Wuttke, Scientific Computing Group at MLZ (see CITATION)
//
//  ************************************************************************************************

//! The mathematics implemented here is described in full detail in a paper
//! by Joachim Wuttke, entitled
//! "Numerically stable form factor of any polygon and polyhedron"

#include "ff/Polyhedron.h"
#include "ff/Edge.h"
#include "ff/EulerOperations.h"
#include "ff/Face.h"
#include "ff/Topology.h"
#include <algorithm>
#include <fstream>
#include <stdexcept>
#define _USE_MATH_DEFINES
#include <math.h>
#ifdef ALGORITHM_DIAGNOSTIC_LEVEL2
#include <boost/format.hpp>
#endif

namespace {

const double eps = 2e-16;
const double q_limit_series = 1e-2;
const int n_limit_series = 20;

std::vector<R3> shift_vertices(const std::vector<R3>& vertices, const R3& location)
{
    std::vector<R3> result;
    result.reserve(vertices.size());
    for (const R3 v : vertices)
        result.emplace_back(v + location);
    return result;
}

std::vector<ff::Face> precompute_faces(const ff::Topology& topology,
                                       const std::vector<R3>& vertices)
{
    if (topology.faces.size() < 4)
        throw std::runtime_error("Invalid polyhedron: less than four non-vanishing faces");
    if (topology.symmetry_Ci) {
        std::vector<ff::Face> faces;
        size_t N = topology.faces.size();
        if (N & 1)
            throw std::runtime_error("Invalid polyhedron: odd #faces violates symmetry Ci");
        for (size_t k = 0; k < N / 2; ++k) {
            const ff::FacialTopology& tf = topology.faces[k];
            std::vector<R3> corners; // of one face
            corners.reserve(N / 2);
            for (int i : topology.faces[k].vertexIndices)
                corners.push_back(vertices[i]);
            faces.emplace_back(corners, tf.symmetry_S2);
            // check against counterface
            const ff::FacialTopology& tfc = topology.faces[N - 1 - k];
            corners.clear();
            corners.reserve(N / 2);
            for (int i : tfc.vertexIndices)
                corners.push_back(vertices[i]);
            ff::Face counterface(corners, tf.symmetry_S2);
            faces[k].assert_Ci(counterface);
        }
        return faces;
    }
    std::vector<ff::Face> faces;
    for (const ff::FacialTopology& tf : topology.faces) {
        std::vector<R3> corners; // of one face
        for (int i : tf.vertexIndices)
            corners.push_back(vertices[i]);
        faces.emplace_back(corners, tf.symmetry_S2);
    }
    return faces;
}

double precompute_radius(const std::vector<R3>& vertices)
{
    return std::max_element(vertices.begin(), vertices.end(),
                            [](const R3& a, const R3& b) { return a.mag() < b.mag(); })
        ->mag();
}

double precompute_volume(const std::vector<ff::Face>& faces, bool symmetry_Ci)
{
    double volume = 0;
    for (const ff::Face& Gk : faces)
        volume += Gk.pyramidalVolume();
    if (symmetry_Ci)
        volume = 2 * volume;
    return volume;
}

//! Returns true if a ray defined by r0 -> r1 passes through a plane defined by its
//! origin p0 and normal vector n.

bool ray_intersects_plane(R3 r0, R3 r1, R3 p0, R3 n)
{
    return (r1 - r0).dot(n) != 0;
}

//! Returns the intersection point of a ray with a plane.

R3 ray_plane_intersection(R3 r0, R3 r1, R3 p0, R3 n)
{
    if ((r1 - r0).dot(n) == 0)
        throw std::runtime_error("Invalid call to libformfactor, function ray_plane_intersection: "
                                 "ray is parallel to plane");
    double d = (p0 - r0).dot(n) / ((r1 - r0).dot(n)); // projection onto ray
    return r0 + d * (r1 - r0);
}

//! Returns weather i lies in the positive eay extension defined by r0 -> r1

bool intersects_in_positive_halfspace(const R3& r0, const R3& r1, const R3& i)
{
    return (r1 - r0).dot(i - r0) > 0;
}

//! Returns the number of faces crossed by the ray r0 -> r1. Set sym to true to mirror the faces

int ray_crossings(const R3& r0, const R3& r1, const std::vector<ff::Face>& faces, bool sym)
{
    int crossings = 0;
    int sign = sym ? -1 : 1;
    for (const ff::Face& polygon : faces) {
        const R3 origin = sym ? -polygon.edges()[0].R() : polygon.edges()[0].R();
        if (!ray_intersects_plane(r0, r1, origin, (sign * polygon.normal())))
            continue;
        const R3 i = ray_plane_intersection(r0, r1, origin, (sign * polygon.normal()));
        if (!intersects_in_positive_halfspace(r0, r1, i))
            continue;
        if (polygon.is_inside(i))
            crossings++;
    }
    return crossings;
}

//! Uses the fibonacci sphere algorithm to evenly generate points on as sphere
//! See: https://stackoverflow.com/questions/9600801/evenly-distributing-n-points-on-a-sphere
//! Or for more details: González, https://doi.org/10.1007/s11004-009-9257-x (2010)
std::vector<R3> fibonacci_sphere(const R3& origin, int nsamples)
{
    static const double phi = M_PI * (3.0 - sqrt(5.0));

    std::vector<R3> result(nsamples);
    for (int i = 0; i < nsamples; i++) {
        const double y = 1 - 1 * ((double)i / (nsamples - 1)) * 2;
        const double radius = sqrt(1 - y * y);
        const double theta = phi * i;
        const double x = cos(theta) * radius;
        const double z = sin(theta) * radius;
        result[i] = R3(x, y, z) + origin;
    }
    return result;
}

} // namespace


ff::Polyhedron::Polyhedron(const Topology& topology, const std::vector<R3>& vertices,
                           const R3& location)
    : IBody(location)
    , m_topology(std::make_unique<const Topology>(topology))
    , m_vertices(vertices)
    , m_absolute_vertices(shift_vertices(vertices, location))
    , m_faces(precompute_faces(topology, vertices))
    , m_radius(precompute_radius(vertices))
    , m_volume(precompute_volume(m_faces, m_topology->symmetry_Ci))
{
}

ff::Polyhedron::~Polyhedron() = default;

const ff::Topology& ff::Polyhedron::topology() const
{
    return *m_topology;
}

const std::vector<ff::Face>& ff::Polyhedron::faces() const
{
    return m_faces;
}

//! Returns the form factor F(q) of this polyhedron, with origin at z=0.

complex_t ff::Polyhedron::formfactor_at_center(const C3& q) const
{
    const bool sym_Ci = m_topology->symmetry_Ci;
    const double q_red = m_radius * q.mag();
#ifdef ALGORITHM_DIAGNOSTIC
    polyhedralDiagnosis.reset();
#endif
    if (q_red == 0)
        return m_volume;
    if (q_red < q_limit_series) {
        // summation of power series
#ifdef ALGORITHM_DIAGNOSTIC
        polyhedralDiagnosis.algo = 100;
#endif
        complex_t sum = 0;
        complex_t n_fac = (sym_Ci ? -2 : -1) / q.mag2();
        int count_return_condition = 0;
        for (int n = 2; n < n_limit_series; ++n) {
            if (sym_Ci && n & 1)
                continue;
#ifdef ALGORITHM_DIAGNOSTIC
            polyhedralDiagnosis.order = std::max(polyhedralDiagnosis.order, n);
#endif
            complex_t term = 0;
            for (const Face& Gk : m_faces)
                term += Gk.ff_n(n + 1, q);
            term *= n_fac;
#ifdef ALGORITHM_DIAGNOSTIC_LEVEL2
            polyhedralDiagnosis.msg +=
                boost::str(boost::format("  + term(n=%2i) = %23.17e+i*%23.17e\n") % n % term.real()
                           % term.imag());
#endif
            sum += term;
            if (std::abs(term) <= eps * std::abs(sum) || std::abs(sum) < eps * m_volume)
                ++count_return_condition;
            else
                count_return_condition = 0;
            if (count_return_condition > 2)
                return m_volume + sum; // regular exit
            n_fac = sym_Ci ? -n_fac : mul_I(n_fac);
        }
        throw std::runtime_error("Numeric failure in polyhedron: series F(q) not converged");
    }

    // direct evaluation of analytic formula (coefficients may involve series)
#ifdef ALGORITHM_DIAGNOSTIC
    polyhedralDiagnosis.algo = 200;
#endif
    complex_t sum = 0;
    for (const Face& Gk : m_faces) {
        complex_t qn = Gk.normalProjectionConj(q); // conj(q)*normal
        if (std::abs(qn) < eps * q.mag())
            continue;
        complex_t term = qn * Gk.ff(q, sym_Ci);
#ifdef ALGORITHM_DIAGNOSTIC //_LEVEL2
        polyhedralDiagnosis.msg += boost::str(boost::format("  + face_ff = %23.17e+i*%23.17e\n")
                                              % term.real() % term.imag());
#endif
        sum += term;
    }
#ifdef ALGORITHM_DIAGNOSTIC //_LEVEL2
    polyhedralDiagnosis.msg +=
        boost::str(boost::format(" -> raw sum = %23.17e+i*%23.17e; divisor = %23.17e\n")
                   % sum.real() % sum.imag() % q.mag2());
#endif
    return sum / I / q.mag2();
}

//! Returns true if a vertex v is located inside the polygon by counting odd vs even ray cast
//! intersections. Uses fibonacci_sphere as sampling method and determines the result by majority
//! vote.

bool ff::Polyhedron::is_inside(const R3& v, int nsamples) const
{
    const R3 v_rel = v - location();
    if ((v_rel).mag() > m_radius)
        return false;
    if (nsamples < 2)
        throw std::runtime_error(
            "Invalid call to libformfactor, function is_inside: nsamples < 2 is "
            "out of range.");
    int n_inside = 0; // number of rays that cross an odd number of faces
    std::vector<R3> samples = fibonacci_sphere(v_rel, nsamples);
    for (const R3& sample : samples) {
        int crossings = ray_crossings(v_rel, sample, m_faces, false);
        if (m_topology->symmetry_Ci)
            crossings += ray_crossings(v_rel, sample, m_faces, true);
        if (crossings % 2 != 0)
            n_inside++;
    }
    return n_inside > nsamples / 2;
}

R3 ff::Polyhedron::center_of_mass() const
{
    if (m_topology->symmetry_Ci)
        return {};
    R3 result;
    for (const ff::Face& face : m_faces)
        result += face.center_of_polygon() * face.pyramidalVolume();
    return result * (3. / 4) / m_volume;
}

double ff::Polyhedron::z_bottom() const
{
    return std::min_element(m_vertices.begin(), m_vertices.end(),
                            [](const R3& a, const R3& b) { return a.z() < b.z(); })
        ->z();
}

//! Returns a clipped copy of this Polyhedron,
//! zMin is the lower clipping planes offset,
//! zMax is the upper clipping planes offset,
//! The new Polyhedron consists of the volume between zMin and zMax
ff::Polyhedron* ff::Polyhedron::clipped(double zMin, double zMax) const
{
    if (zMin > zMax)
        throw std::runtime_error("Invalid call to libformfactor, function clipped: zMin > zMax");
    if (zMin == zMax)
        throw std::runtime_error("Invalid call to libformfactor, function clipped: zMin == zMax");
    std::unique_ptr<Polyhedron> result(ff::atomic::z_clip(*this, zMin,false));
    result.reset(ff::atomic::z_clip(*result, zMax,true));
    return result.release();
}