From 43d34bd80c1ff044c285ee7f631d35c7bbcf6ec8 Mon Sep 17 00:00:00 2001
From: "Joachim Wuttke (o)" <j.wuttke@fz-juelich.de>
Date: Mon, 11 Apr 2016 16:13:46 +0200
Subject: [PATCH] imported FormFactorPolyhedron.cpp|h from sc-intern/ff

---
 Core/FormFactors/FormFactorPolyhedron.cpp | 332 ++++++++++++++++++++++
 Core/FormFactors/FormFactorPolyhedron.h   |  72 +++++
 2 files changed, 404 insertions(+)
 create mode 100644 Core/FormFactors/FormFactorPolyhedron.cpp
 create mode 100644 Core/FormFactors/FormFactorPolyhedron.h

diff --git a/Core/FormFactors/FormFactorPolyhedron.cpp b/Core/FormFactors/FormFactorPolyhedron.cpp
new file mode 100644
index 00000000000..281193b7051
--- /dev/null
+++ b/Core/FormFactors/FormFactorPolyhedron.cpp
@@ -0,0 +1,332 @@
+// Form factor computation: class implementation
+// JWu 2016
+
+// Reference:
+//   Joachim Wuttke
+//   "Form factor (Fourier shape transform) of polygon and polyhedron."
+
+#include <cmath>
+#include <iostream>
+#include <iomanip>
+#include <algorithm>
+#include <complex>
+#include <vector>
+
+#include "Precomputed.h"
+#include "BasicVector3D.h"
+
+typedef std::complex<double> cmplx;
+typedef Geometry::BasicVector3D<cmplx> cvector_t;
+typedef Geometry::BasicVector3D<double> kvector_t;
+#include "FormFactorPolyhedron.h"
+
+static cmplx I(0.,1.);
+static double eps(2e-16);
+
+#ifdef POLYHEDRAL_DIAGNOSTIC
+extern Diagnosis diagnosis;
+#endif
+
+//***************************************************************************************************
+//  PolyhedralEdge implementation
+//***************************************************************************************************
+
+//! Returns the contribution of this edge to the form factor.
+
+cmplx PolyhedralEdge::contrib(int m, cvector_t prevec, cvector_t q) const
+{
+    cmplx prefac = prevec.dot(E); // conj(prevec)*E [BasicVector3D::dot is antilinear in 'this']
+    cmplx u = E.dot(q);
+    cmplx v = R.dot(q);
+    static auto& precomputed = IPrecomputed::instance();
+    if( u==0. ) { // only l=0 contributes
+        return prefac * pow(v, m+1) / precomputed.factorial[m+1];
+    } else if( v==0. ) { // only 2l=m+1 contributes
+        if( m&1 ) // m is odd
+            return prefac * pow(u, m+1) / precomputed.factorial[m+2];
+        else
+            return 0.;
+    } else {
+        cmplx ret = 0;
+        for( int l=0; l<=(m+1)/2; ++l )
+            ret += pow(u, 2*l) * pow(v, m+1-2*l) /
+                precomputed.factorial[m+1-2*l] / precomputed.factorial[2*l+1];
+        return prefac * ret;
+    }
+}
+
+//***************************************************************************************************
+//  PolyhedralFace implementation
+//***************************************************************************************************
+
+const double PolyhedralFace::qpa_limit_series = 1e-3;
+
+//! Sets internal variables for given vertex chain.
+
+PolyhedralFace::PolyhedralFace( const std::vector<kvector_t>& V, bool _sym_S2 )
+    : sym_S2( _sym_S2 )
+{
+    // compute edges
+    size_t N = V.size();
+    for ( size_t j=0; j<N; ++j ) {
+        size_t jj = (j+1)%N;
+        edges.push_back( PolyhedralEdge(V[j], V[jj]) );
+    }
+    // compute n_k, rperp
+    normal = kvector_t();
+    for( size_t j=0; j<N; ++j ){
+        size_t jj = (j+1)%N;
+        normal += edges[j].E.cross( edges[jj].E ).unit();
+    }
+    normal /= N;
+    rperp = 0;
+    for( size_t j=0; j<N; ++j )
+        rperp += V[j].dot(normal);
+    rperp /= N;
+    // assert that the vertices lay in a plane
+    for ( size_t j=1; j<N; ++j )
+        if( std::abs(V[j].dot(normal) - rperp) > 1e-14 )
+            throw "Face is not planar";
+    // compute area
+    area = 0;
+    for ( size_t j=0; j<N; ++j ) {
+        size_t jj = (j+1)%N;
+        area += normal.dot( V[j].cross( V[jj] ) ) / 2;
+    }
+    // compute radius in 3d
+    radius_3d = 0;
+    for( const kvector_t v: V )
+        radius_3d = std::max( radius_3d, v.mag() );
+    // compute radius in 2d
+    radius_2d = 0;
+    for( const kvector_t v: V )
+        radius_2d = std::max( radius_2d, (v-rperp*normal).mag() );
+
+    // only now deal with inversion symmetry
+    if( sym_S2 ) {
+        if( N&1 )
+            throw "Odd #edges violates symmetry S2";
+        N /= 2;
+        for( size_t j=0; j<N; ++j ){
+            if( ((V[j]-rperp*normal)+(V[j+N]-rperp*normal)).mag2()>1e-24*radius_2d*radius_2d )
+                throw "Given points violate symmetry S2";
+        }
+        // keep only half of the egdes
+        edges.erase( edges.begin()+N, edges.end() );
+    }
+}
+
+//! Returns volume of pyramid spanned by the origin and this polygon
+
+double PolyhedralFace::getPyramidalVolume() const
+{
+   return rperp*area/3;
+}
+
+//! Sets qperp and qpa according to argument q and to this polygon's normal.
+
+void PolyhedralFace::decompose_q( const cvector_t q, cmplx& qperp, cvector_t& qpa ) const
+{
+    qperp = normal.dot(q);
+    qpa = q - qperp*normal;
+    // improve numeric accuracy:
+    qpa -= normal.dot(qpa)*normal;
+    if( qpa.mag()<eps*std::abs(qperp) )
+        qpa = cvector_t(0.,0.,0.);
+}
+
+//! Returns core contribution to f_n
+
+cmplx PolyhedralFace::ff_n_core( int m, const cvector_t q ) const
+{
+    cvector_t prevec = 2.*normal.cross( q ); // complex conjugation will take place in .dot
+    cmplx ret = 0;
+    for( const PolyhedralEdge& e: edges )
+        ret += e.contrib(m, prevec, q);
+    return ret;
+}
+
+//! Returns contribution qn*f_n [of order q^(n+1)] from this face to the polyhedral form factor.
+
+cmplx PolyhedralFace::ff_n( int n, const cvector_t q ) const
+{
+    cmplx qn = q.dot(normal); // conj(q)*normal (BasicVector3D::dot is antilinear in 'this' argument)
+    if ( std::abs(qn)<eps*q.mag() )
+        return 0.;
+    cmplx qperp;
+    cvector_t qpa;
+    decompose_q( q, qperp, qpa );
+    double qpa_mag2 = qpa.mag2();
+    static auto& precomputed = IPrecomputed::instance();
+    if ( qpa_mag2==0. ) {
+        return qn * pow(qperp*rperp, n) * area / precomputed.factorial[n];
+    } else if ( sym_S2 ) {
+        return qn * ( ff_n_core( n, q ) + ff_n_core( n, qperp*normal-qpa ) ) / qpa_mag2;
+    } else {
+        return qn * ff_n_core( n, q ) / qpa_mag2;
+    }
+}
+
+//! Returns the contribution of this face to the polyhedral form factor.
+
+cmplx PolyhedralFace::ff( const cvector_t q, const bool sym_Ci ) const
+{
+    cmplx qn = q.dot(normal); // conj(q)*normal (BasicVector3D::dot is antilinear in 'this' argument)
+    if ( std::abs(qn)<eps*q.mag() )
+        return 0;
+    cmplx qperp;
+    cvector_t qpa;
+    decompose_q( q, qperp, qpa );
+    double qpa_red = radius_2d * qpa.mag();
+    if ( qpa_red < qpa_limit_series ) {
+        // summation of power series
+        cmplx fac_even;
+        cmplx fac_odd;
+        if( sym_Ci ) {
+            fac_even = qn * 2. * I * sin(qperp*rperp);
+            fac_odd = qn * 2. * cos(qperp*rperp);
+        } else {
+            fac_even = qn * exp( I*qperp*rperp );
+            fac_odd = fac_even;
+        }
+#ifdef POLYHEDRAL_DIAGNOSTIC
+        diagnosis.nExpandedFaces += 1;
+#endif
+        cmplx sum = fac_even * area;
+        cmplx n_multiplier = I;
+        if( sym_S2 ) {
+            fac_even *= 2.;
+            fac_odd = 0.;
+            n_multiplier = -1.;
+        }
+        if( qpa_red==0 )
+            return sum;
+        cmplx n_fac = n_multiplier;
+        for( int n=1; n<20; ++n ) {
+            if( sym_S2 && n&1 )
+                continue;
+#ifdef POLYHEDRAL_DIAGNOSTIC
+            diagnosis.maxOrder = std::max( diagnosis.maxOrder, n );
+#endif
+            cmplx term = n_fac * ( n&1 ? fac_odd : fac_even ) * ff_n_core(n, qpa) / qpa.mag2();
+            sum += term;
+            if( !(n&1) && std::abs(term)<=eps*std::abs(sum) )
+                return sum;
+            n_fac *= n_multiplier;
+        }
+        throw "Bug in formfactor computation: series f(q_pa) not converged";
+    } else {
+        // direct evaluation of analytic formula
+        cvector_t prevec = 2.*normal.cross( qpa ); // complex conjugation will take place in .dot
+        cmplx prefac = qn;
+        if( sym_S2 )
+            prefac *= sym_Ci ? -4.*sin(qperp*rperp) : 2.*I*exp(I*qperp*rperp);
+        cmplx sum = 0;
+        for( const PolyhedralEdge& e: edges ) {
+            cmplx qE = e.E.dot(q);
+            cmplx qR = e.R.dot(q);
+            cmplx sinc_qE = qE!=0. ? sin(qE)/qE : 1.;
+            cmplx Rfac = sym_S2 ? sin(e.R.dot(qpa)) : ( sym_Ci ? 2.*cos(qR) : exp(I*qR) );
+            sum += prevec.dot(e.E) * (sinc_qE*Rfac);
+        }
+        return prefac * sum / ( I*qpa.mag2() );
+    }
+}
+
+//! Throws if deviation from inversion symmetry is detected. Does not check vertices.
+
+void PolyhedralFace::assert_Ci( const PolyhedralFace& other ) const
+{
+    if( std::abs(rperp-other.rperp)>1e-15*(rperp+other.rperp) )
+        throw "Faces with different distance from origin violate symmetry Ci";
+    if( std::abs(area-other.area)>1e-15*(area+other.area) )
+        throw "Faces with different areas violate symmetry Ci";
+    if( (normal+other.normal).mag()>1e-14 )
+        throw "Faces do not have opposite orientation, which violates symmetry Ci";
+}
+
+//***************************************************************************************************
+//  FormFactorPolyhedron implementation
+//***************************************************************************************************
+
+const double FormFactorPolyhedron::q_limit_series = 1e-6;
+
+FormFactorPolyhedron::FormFactorPolyhedron(
+    const std::vector<PolyhedralFace>& _faces, const double _z_origin, const bool _sym_Ci )
+    : z_origin(_z_origin), sym_Ci(_sym_Ci), faces(_faces)
+{
+    radius = 0;
+    volume = 0;
+    for( const PolyhedralFace& Gk: faces ) {
+        radius = std::max( radius, Gk.radius_3d );
+        volume += Gk.getPyramidalVolume();
+    }
+
+    if( sym_Ci ) {
+        if( faces.size()&1 )
+            throw "Odd #faces violates symmetry Ci";
+        size_t N = faces.size()/2;
+        // for this tests, faces must be in a specific order
+        for( size_t k=0; k<N; ++k )
+            faces[k].assert_Ci( faces[2*N-1-k] );
+        // keep only half of the faces
+        faces.erase( faces.begin()+N, faces.end() );
+    }
+}
+
+//! Returns the form factor F(q) of this polyhedron, respecting the offset z_origin.
+
+cmplx FormFactorPolyhedron::evaluate_for_q( const cvector_t q ) const
+{
+    return exp(-I*z_origin*q[2]) * evaluate_centered(q);
+}
+
+//! Returns the form factor F(q) of this polyhedron, with origin at z=0.
+
+cmplx FormFactorPolyhedron::evaluate_centered( const cvector_t q ) const
+{
+    double q_red = radius * q.mag();
+#ifdef POLYHEDRAL_DIAGNOSTIC
+    diagnosis = { 0, 0 };
+#endif
+    if( q_red==0 ) {
+        return volume;
+    } else if ( q_red < q_limit_series ) {
+        // summation of power series
+        cmplx ret = volume;
+        cmplx n_fac = ( sym_Ci ? -2 : I ) / q.mag2();
+        for( int n=1; n<20; ++n ) {
+            if( sym_Ci && n&1 )
+                continue;
+#ifdef POLYHEDRAL_DIAGNOSTIC
+            diagnosis.maxOrder = std::max( diagnosis.maxOrder, n );
+#endif
+            cmplx term = 0;
+            for( const PolyhedralFace& Gk: faces )
+                term += Gk.ff_n( n+1, q );
+            term *= n_fac;
+            ret += term;
+            if( !(n&1) && std::abs(term)<=eps*std::abs(ret) )
+                return ret;
+            n_fac *= ( sym_Ci ? -1 : I );
+        }
+        throw "Bug in formfactor computation: series F(q) not converged";
+    } else {
+        // direct evaluation of analytic formula (coefficients may involve series)
+        cmplx ret = 0;
+        for( const PolyhedralFace& Gk: faces )
+            ret += Gk.ff(q, sym_Ci );
+        return ret / (I * q.mag2());
+    }
+}
+
+//! Assertions for Platonic solid.
+
+void FormFactorPolyhedron::assert_platonic() const
+{
+    // just one test; one could do much more ...
+    double pyramidal_volume = faces[0].getPyramidalVolume();
+    for( auto Gk: faces )
+        if (std::abs(pyramidal_volume-Gk.getPyramidalVolume())>40*eps)
+            throw "Deviant pyramidal volume";
+}
diff --git a/Core/FormFactors/FormFactorPolyhedron.h b/Core/FormFactors/FormFactorPolyhedron.h
new file mode 100644
index 00000000000..3a7365a1897
--- /dev/null
+++ b/Core/FormFactors/FormFactorPolyhedron.h
@@ -0,0 +1,72 @@
+
+//! One edge of a polygon, for form factor computation.
+
+class PolyhedralEdge {
+public:
+    PolyhedralEdge( const kvector_t _Vlow, const kvector_t _Vhig ) :
+        E((_Vhig-_Vlow)/2), R((_Vhig+_Vlow)/2) {};
+    kvector_t E; //!< vector pointing from mid of edge to upper vertex
+    kvector_t R; //!< position vector of edge midpoint
+    cmplx contrib(int m, cvector_t prevec, cvector_t qpa) const;
+};
+
+
+//! A polygon, for form factor computation.
+
+class PolyhedralFace {
+protected:
+    static const double qpa_limit_series; //!< determines when use power series
+    bool sym_S2; //!< if true, then edges obtainable by inversion are not provided
+    std::vector<PolyhedralEdge> edges;
+    double area;
+    kvector_t normal; //!< normal vector of this polygon's plane
+    double rperp; //!< distance of this polygon's plane from the origin, along 'normal'
+    double radius_2d; //!< radius of enclosing cylinder
+    void decompose_q( const cvector_t q, cmplx& qperp, cvector_t& qpa ) const;
+    cmplx ff_n_core( int m, const cvector_t qpa ) const;
+public:
+    double radius_3d; //!< radius of enclosing sphere
+    PolyhedralFace( const std::vector<kvector_t>& _V, bool _sym_S2=false );
+    double getPyramidalVolume() const;
+    cmplx ff_n( int m, const cvector_t q ) const;
+    cmplx ff( const cvector_t q, const bool sym_Ci ) const;
+    void assert_Ci( const PolyhedralFace& other ) const;
+};
+
+
+//! A polyhedron, for form factor computation.
+
+class FormFactorPolyhedron {
+protected:
+    double z_origin;
+    bool sym_Ci; //!< if true, then faces obtainable by inversion are not provided
+    double radius;
+    double volume;
+    static const double q_limit_series;
+    std::vector<PolyhedralFace> faces;
+    virtual cmplx evaluate_centered( cvector_t q ) const;
+public:
+    FormFactorPolyhedron( const std::vector<PolyhedralFace>& _faces,
+                          const double _z_origin, const bool _sym_Ci=false );
+    cmplx evaluate_for_q( cvector_t q ) const;
+    double getVolume() const { return volume; }
+
+    // test methods:
+    void assert_platonic() const;
+};
+
+
+#ifdef POLYHEDRAL_DIAGNOSTIC
+//! Information about the latest form factor evaluation. Not thread-safe.
+//! Used only by external test program.
+class Diagnosis {
+public:
+    int maxOrder;
+    int nExpandedFaces;
+    bool operator!=( const Diagnosis& other ) const {
+        return maxOrder!=other.maxOrder || nExpandedFaces!=other.nExpandedFaces; }
+    friend std::ostream& operator<< (std::ostream& stream, const Diagnosis& diag) {
+        return stream<<" ["<<diag.nExpandedFaces<<":"<<diag.maxOrder<<"]";
+    }
+};
+#endif
-- 
GitLab