From b262e89b717a90eb5130ef48e0acc47716fdd433 Mon Sep 17 00:00:00 2001 From: Joey Dumont Date: Tue, 6 May 2014 10:42:43 -0400 Subject: [PATCH] First commit of working code. Needs cleanup. --- fortranLinkage.h | 10 + testWigner.cpp | 220 ++++++++++++ wignerSymbols.cpp | 463 +++++++++++++++++++++++++ wignerSymbols.f | 867 ++++++++++++++++++++++++++++++++++++++++++++++ wignerSymbols.h | 166 +++++++++ wignerTest.f90 | 11 + wigner_wrap.f90 | 51 +++ 7 files changed, 1788 insertions(+) create mode 100644 fortranLinkage.h create mode 100644 testWigner.cpp create mode 100644 wignerSymbols.cpp create mode 100644 wignerSymbols.f create mode 100644 wignerSymbols.h create mode 100644 wignerTest.f90 create mode 100644 wigner_wrap.f90 diff --git a/fortranLinkage.h b/fortranLinkage.h new file mode 100644 index 0000000..be4973e --- /dev/null +++ b/fortranLinkage.h @@ -0,0 +1,10 @@ +#ifndef FORTRANLINKAGE_H +#define FORTRANLINKAGE_H + +extern "C" +{ + extern void drc3jj_wrap(double,double,double,double,double*,double*,double*,int,int*); + extern void drc6j_wrap(double,double,double,double,double*,double*,double*,int,int*); +} + +#endif // FORTRANLINKAGE_H \ No newline at end of file diff --git a/testWigner.cpp b/testWigner.cpp new file mode 100644 index 0000000..f3e8d27 --- /dev/null +++ b/testWigner.cpp @@ -0,0 +1,220 @@ +#include "wignerSymbols.h" + +#include +#include +#include +#include + +#define EPS 1.0e-10 + +using namespace std; +using namespace arma; + +// Orthogonality relations and sum rules for the 3j symbols [Brink & Satchler, pp. 136+139]. +double firstOrthoRelation3j(double l1, double l2, double l3, double m3); +double sumOverM23j(double l1, double l2, double l3, double m3); + +// Orthogonality relations for the 6j symbols [Brink & Satchler pp. 142-3]. +double firstSumOverL3(double l1, double l2, double l6); +double seconSumOverL3(double l1, double l2, double l6); + +// Timing computation of Wigner symbols. +double timingWignerSymbols(double l2, double l3, double m1, double m2, double m3); +double timingWignerSymbolsF(double l2, double l3, double m1, double m2, double m3); + +int main(int argc, char* argv[]) +{ + // We time the computation for different sizes. + int N = 500; + double l2(500.0),m1(-10.0),m2(60.0),m3(-50.0); + mat times(N,2); + mat timesF(N,2); + + for (int i=0;i::max(); + double srhuge = sqrt(huge); + double tiny = std::numeric_limits::min(); + double srtiny = sqrt(tiny); + double eps = std::numeric_limits::epsilon(); + + // We enforce the selection rules. + bool select(true); + select = ( + std::fabs(m1+m2+m3)(1); + + // We compute the limits of l1. + double l1min = std::max(std::fabs(l2-l3),std::fabs(m1)); + double l1max = l2+l3; + + // We compute the size of the resulting array. + int size = (int)std::floor(l1max-l1min+1.0+eps); + arma::colvec thrcof(size); + + // If l1min=l1max, we have an analytical formula. + if (size==1) + { + thrcof(0) = pow(-1.0,std::floor(std::fabs(l2+m2-l3+m3)))/sqrt(l1min+l2+l3+1.0); + } + + // Another special case where the recursion relation fails. + else + { + // We start with an arbitrary value. + thrcof(0) = srtiny; + + // From now on, we check the variation of |alpha(l1)|. + double alphaOld, alphaNew, beta, l1(l1min); + if (l1min==0.0) + alphaNew = -(m3-m2+2.0*c_wigner3j_auxB(l1,l2,l3,m1,m2,m3))/c_wigner3j_auxA(1.0,l2,l3,m1,m2,m3); + else + alphaNew = -c_wigner3j_auxB(l1min,l2,l3,m1,m2,m3) + /(l1min*c_wigner3j_auxA(l1min+1.0,l2,l3,m1,m2,m3)); + + // We compute the two-term recursion. + thrcof(1) = alphaNew*thrcof(0); + + // If size > 2, we start the forward recursion. + if (size>2) + { + // We start with an arbitrary value. + thrcof(0) = srtiny; + + // From now on, we check the variation of |alpha(l1)|. + double alphaOld, alphaNew, beta, l1(l1min); + if (l1min==0.0) + alphaNew = -(m3-m2+2.0*c_wigner3j_auxB(l1,l2,l3,m1,m2,m3))/c_wigner3j_auxA(1.0,l2,l3,m1,m2,m3); + else + alphaNew = -c_wigner3j_auxB(l1min,l2,l3,m1,m2,m3) + /(l1min*c_wigner3j_auxA(l1min+1.0,l2,l3,m1,m2,m3)); + + // We compute the two-term recursion. + thrcof(1) = alphaNew*thrcof(0); + + // We compute the rest of the recursion. + int i = 1; + bool alphaVar = false; + do + { + // Bookkeeping: + i++; // Next term in recursion + alphaOld = alphaNew; // Monitoring of |alpha(l1)|. + l1 += 1.0; // l1 = l1+1 + + // New coefficients in recursion. + alphaNew = -c_wigner3j_auxB(l1,l2,l3,m1,m2,m3) + /(l1*c_wigner3j_auxA(l1+1.0,l2,l3,m1,m2,m3)); + + beta = -(l1+1.0)*c_wigner3j_auxA(l1,l2,l3,m1,m2,m3) + /(l1*c_wigner3j_auxA(l1+1.0,l2,l3,m1,m2,m3)); + + // Application of the recursion. + thrcof(i) = alphaNew*thrcof(i-1)+beta*thrcof(i-2); + + // We check if we are overflowing. + if (std::fabs(thrcof(i))>srhuge) + { + std::cout << "We renormalized the forward recursion." << std::endl; + thrcof(arma::span(0,i)) /= srhuge; + } + + // This piece of code checks whether we have reached + // the classical region. If we have, the second if + // sets alphaVar to true and we break this loop at the + // next iteration because we need thrcof(l1mid+1) to + // compute the scalar. + if (alphaVar) break; + + if (std::fabs(alphaNew)-std::fabs(alphaOld)>0.0) + alphaVar=true; + + } while (i<(size-1)); // Loop stops when we have computed all values. + + // If this is the case, we have stumbled upon a classical region. + // We start the backwards recursion. + if (i!=size-1) + { + // We keep the two terms around l1mid to compute the factor later. + double l1midm1(thrcof(i-2)),l1mid(thrcof(i-1)),l1midp1(thrcof(i)); + + // We compute the backward recursion by providing an arbitrary + // startint value. + thrcof(size-1) = srtiny; + + // We compute the two-term recursion. + l1 = l1max; + alphaNew = -c_wigner3j_auxB(l1,l2,l3,m1,m2,m3) + /((l1+1.0)*c_wigner3j_auxA(l1,l2,l3,m1,m2,m3)); + thrcof(size-2) = alphaNew*thrcof(size-1); + + // We compute the rest of the backward recursion. + int j = size-2; + do + { + // Bookkeeping + j--; // Previous term in recursion. + l1 -= 1.0; // l1 = l1-1 + + // New coefficients in recursion. + alphaNew = -c_wigner3j_auxB(l1,l2,l3,m1,m2,m3) + /((l1+1.0)*c_wigner3j_auxA(l1,l2,l3,m1,m2,m3)); + beta = -l1*c_wigner3j_auxA(l1+1.0,l2,l3,m1,m2,m3) + /((l1+1.0)*c_wigner3j_auxA(l1,l2,l3,m1,m2,m3)); + + // Application of the recursion. + thrcof(j) = alphaNew*thrcof(j+1)+beta*thrcof(j+2); + + // We check if we are overflowing. + if (std::fabs(thrcof(j)>srhuge)) + { + std::cout << "We renormalized the backward recursion." << std::endl; + thrcof(arma::span(j,size-1)) /= srhuge; + } + + } while (j>(i-2)); // Loop stops when we are at l1=l1mid-1. + + // We now compute the scaling factor for the forward recursion. + double lambda = (l1midp1*thrcof(j+2)+l1mid*thrcof(j+1)+l1midm1*thrcof(j)) + /(l1midp1*l1midp1+l1mid*l1mid+l1midm1*l1midm1); + + // We scale the forward recursion. + thrcof(arma::span(0,j-1)) *= lambda; + } + } + } + + // We compute the overall factor. + double sum = 0.0; + for (int k=0;k= std::fabs(l1-l2) + && l3 <= l1+l2 + && std::fabs(m1) <= l1 + && std::fabs(m2) <= l2 + && std::fabs(m3) <= l3 + ); + + if (!select) return 0.0; + + // We compute l1min and the position of the array we will want. + double l1min = std::max(std::fabs(l2-l3),std::fabs(m1)); + + // We fetch the proper value in the array. + int index = (int)l1-l1min; + + return c_wigner3j(l2,l3,m1,m2,m3)(index); +} + +arma::colvec c_wigner6j(double l2, double l3, + double l4, double l5, double l6) +{ + // We compute the numeric limits of double precision. + double huge = std::numeric_limits::max(); + double srhuge = sqrt(huge); + double tiny = std::numeric_limits::min(); + double srtiny = sqrt(tiny); + double eps = std::numeric_limits::epsilon(); + + // We enforce the selection rules. + bool select(true); + + // Triangle relations for the four tryads + select = ( + std::fabs(l4-l2) <= l6 && l6 <= l4+l2 + && std::fabs(l4-l5) <= l3 && l3 <= l4+l5 + ); + + // Sum rule of the tryads + select = ( + std::floor(l4+l2+l6)==(l4+l2+l6) + && std::floor(l4+l5+l3)==(l4+l5+l3) + ); + + if (!select) return arma::zeros(1); + + // We compute the limits of l1. + double l1min = std::max(std::fabs(l2-l3),std::fabs(l5-l6)); + double l1max = std::min(l2+l3,l5+l6); + + // We compute the size of the resulting array. + unsigned int size = (int)std::floor(l1max-l1min+1.0+eps); + arma::colvec sixcof(size); + + // If l1min=l1max, we have an analytical formula. + if (size==1) + { + sixcof(0) = 1.0/sqrt((l1min+l1min+1.0)*(l4+l4+1.0)); + sixcof(0) *= ((int)std::floor(l2+l3+l5+l6+eps) & 1 ? -1.0 : 1.0); + } + + // Otherwise, we start the forward recursion. + else + { + // We start with an arbitrary value. + sixcof(0) = srtiny; + + // From now on, we check the variation of |alpha(l1)|. + double alphaOld, alphaNew, beta, l1(l1min); + + if (l1min==0) + alphaNew = -(l2*(l2+1.0)+l3*(l3+1.0)+l5*(l5+1.0)+l6*(l6+1.0)-2.0*l4*(l4+1.0))/c_wigner6j_auxA(1.0,l2,l3,l4,l5,l6); + + else + alphaNew = -c_wigner6j_auxB(l1,l2,l3,l4,l5,l6) + /(l1min*c_wigner6j_auxA(l1+1.0,l2,l3,l4,l5,l6)); + + // We compute the two-term recursion. + sixcof(1) = alphaNew*sixcof(0); + + if (size>2) + { + // We start with an arbitrary value. + sixcof(0) = srtiny; + + // From now on, we check the variation of |alpha(l1)|. + double alphaOld, alphaNew, beta, l1(l1min); + if (l1min==0) + alphaNew = -(l2*(l2+1.0)+l3*(l3+1.0)+l5*(l5+1.0)+l6*(l6+1.0)-2.0*l4*(l4+1.0))/c_wigner6j_auxA(1.0,l2,l3,l4,l5,l6); + + else + alphaNew = -c_wigner6j_auxB(l1,l2,l3,l4,l5,l6) + /(l1min*c_wigner6j_auxA(l1+1.0,l2,l3,l4,l5,l6)); + + // We compute the two-term recursion. + sixcof(1) = alphaNew*sixcof(0); + + // We compute the rest of the recursion. + int i = 1; + bool alphaVar = false; + do + { + // Bookkeeping: + i++; // Next term in recursion + alphaOld = alphaNew; // Monitoring of |alpha(l1)|. + l1 += 1.0; // l1 = l1+1 + + // New coefficients in recursion. + alphaNew = -c_wigner6j_auxB(l1,l2,l3,l4,l5,l6) + /(l1*c_wigner6j_auxA(l1+1.0,l2,l3,l4,l5,l6)); + + beta = -(l1+1.0)*c_wigner6j_auxA(l1,l2,l3,l4,l5,l6) + /(l1*c_wigner6j_auxA(l1+1.0,l2,l3,l4,l5,l6)); + + // Application of the recursion. + sixcof(i) = alphaNew*sixcof(i-1)+beta*sixcof(i-2); + + // We check if we are overflowing. + if (std::fabs(sixcof(i)>srhuge)) + { + std::cout << "We renormalized the forward recursion." << std::endl; + sixcof(arma::span(0,i)) /= srhuge; + } + + // This piece of code checks whether we have reached + // the classical region. If we have, the second if + // sets alphaVar to true and we break this loop at the + // next iteration because we need sixcof(l1mid+1) to + // compute the scalar. + if (alphaVar) break; + + if (std::fabs(alphaNew)-std::fabs(alphaOld)>0.0) + alphaVar=true; + + } while (i<(size-1)); // Loop stops when we have computed all values. + + // If this is the case, we have stumbled upon a classical region. + // We start the backwards recursion. + if (i!=size-1) + { + // We keep the two terms around l1mid to compute the factor later. + double l1midm1(sixcof(i-2)),l1mid(sixcof(i-1)),l1midp1(sixcof(i)); + + // We compute the backward recursion by providing an arbitrary + // startint value. + sixcof(size-1) = srtiny; + + // We compute the two-term recursion. + l1 = l1max; + alphaNew = -c_wigner6j_auxB(l1,l2,l3,l4,l5,l6) + /((l1+1.0)*c_wigner6j_auxA(l1,l2,l3,l4,l5,l6)); + sixcof(size-2) = alphaNew*sixcof(size-1); + + // We compute the rest of the backward recursion. + int j = size-2; + do + { + // Bookkeeping + j--; // Previous term in recursion. + l1 -= 1.0; // l1 = l1-1 + + // New coefficients in recursion. + alphaNew = -c_wigner6j_auxB(l1,l2,l3,l4,l5,l6) + /((l1+1.0)*c_wigner6j_auxA(l1,l2,l3,l4,l5,l6)); + beta = -l1*c_wigner6j_auxA(l1+1.0,l2,l3,l4,l5,l6) + /((l1+1.0)*c_wigner6j_auxA(l1,l2,l3,l4,l5,l6)); + + // Application of the recursion. + sixcof(j) = alphaNew*sixcof(j+1)+beta*sixcof(j+2); + + // We check if we are overflowing. + if (std::fabs(sixcof(j)>srhuge)) + { + std::cout << "We renormalized the backward recursion." << std::endl; + sixcof(arma::span(j,size-1)) /= srhuge; + } + + } while (j>(i-2)); // Loop stops when we are at l1=l1mid-1. + + // We now compute the scaling factor for the forward recursion. + double lambda = (l1midp1*sixcof(j+2)+l1mid*sixcof(j+1)+l1midm1*sixcof(j)) + /(l1midp1*l1midp1+l1mid*l1mid+l1midm1*l1midm1); + + // We scale the forward recursion. + sixcof(arma::span(0,j-1)) *= lambda; + } + } + } + + // We compute the overall factor. + double sum = 0.0; + for (int k=0;k + * + * \since 2013-08-16 + * + * \brief Defines utility functions for the evaluation of Wigner-3j and -6j symbols. + * + * We use modified SLATEC (http://netlib.org/slatec) Fortran subroutines to compute + * Wigner-3j and -6j symbols. We modified the subroutines so that they do not depend + * d1mach, r1mach or i1mach as these are obsolete routines. They have been replaced + * by intrinsics such as huge(), tiny(), epsilon() and spacing(), which are guaranteed + * to work. The files wignerSymbols.f90 and utils.f contain the subroutines and their + * dependencies. + * + * We rely on the ISO C Binding to bind the Fortran subroutines to C++. This method + * of working insures that proper type casts are performed, among other things. We + * then use the same method as we did before (extern "C"). + * + */ + +#include +#include +#include +#include + +namespace varPhase { +/** @name Fortran Linkage + * We link the Fortran subroutines to our C++ code. + * Because of the ISO C Binding (see file wigner_wrap.f90), + * some arguments are passed by value rather than by reference. + */ +///@{ +extern "C" +{ + /*! Wigner-3j symbols. */ + extern void drc3jj_wrap(double,double,double,double,double*,double*,double*,int,int*); + + /*! Wigner-6j symbols. */ + extern void drc6j_wrap(double,double,double,double,double,double*,double*,double*,int,int*); +} +///@} + +/*! @name Evaluation of Wigner-3j and -6j symbols. + * We implement the Fortran subroutines in C++. + */ +///@{ +arma::colvec c_wigner3j(double l2, double l3, + double m1, double m2, double m3); + +double c_wigner3j(double l1, double l2, double l3, + double m1, double m2, double m3); + +double c_wigner3j_auxA(double l1, double l2, double l3, + double m1, double m2, double m3); +double c_wigner3j_auxB(double l1, double l2, double l3, + double m1, double m2, double m3); + +arma::colvec c_wigner6j(double l2, double l3, + double l4, double l5, double l6); + +double c_wigner6j(double l1, double l2, double l3, + double l4, double l5, double l6); + +double c_wigner6j_auxA(double l1, double l2, double l3, + double l4, double l5, double l6); + +double c_wigner6j_auxB(double l1, double l2, double l3, + double l4, double l5, double l6); + +/*! Computes the Wigner-3j symbol for given l1,l2,l3,m1,m2,m3. We + * explicitly enforce the selection rules. */ +inline double wigner3j(double l1, double l2, double l3, + double m1, double m2, double m3) +{ + // We enforce the selection rules. + bool select(true); + select = ( + m1+m2+m3==0.0 + && std::floor(l1+l2+l3)==(l1+l2+l3) + && l3 >= std::fabs(l1-l2) + && l3 <= l1+l2 + && std::fabs(m1) <= l1 + && std::fabs(m2) <= l2 + && std::fabs(m3) <= l3 + ); + + if (!select) return 0.0; + + // We compute the size of the resulting array. + int size = (int)std::ceil(l2+l3-std::max(std::fabs(l2-l3),std::fabs(m1)))+1; + + // We prepare the output values. + double l1min, l1max; + double thrcof [size]; + int ierr; + + // External function call. + drc3jj_wrap(l2,l3,m2,m3,&l1min,&l1max,thrcof,size,&ierr); + + // We fetch and return the value with the proper l1 value. + int index = (int)l1-l1min; + return thrcof[index]; +} + +/*! Computes the Clebsch-Gordan coefficient by relating it to the + * Wigner 3j symbol. It sometimes eases the notation to use the + * Clebsch-Gordan coefficients directly. */ +inline double clebschGordan(double l1, double l2, double l3, + double m1, double m2, double m3) +{ + // We simply compute it via the 3j symbol. + return (pow(-1.0,l1-l2+m3)*sqrt(2.0*l3+1.0)*wigner3j(l1,l2,l3,m1,m2,-m3)); +} + +/*! Computes the Wigner-6j symbol for given, l1, l2, l3, l4, l5, l6. + * We explicitly enforce the selection rules. */ +inline double wigner6j(double l1, double l2, double l3, + double l4, double l5, double l6) +{ + // We enforce the selection rules. + bool select(true); + + // Triangle relations for the four tryads + select = ( + std::fabs(l1-l2) <= l3 && l3 <= l1+l2 + && std::fabs(l1-l5) <= l6 && l6 <= l1+l5 + && std::fabs(l4-l2) <= l6 && l6 <= l4+l2 + && std::fabs(l4-l5) <= l3 && l3 <= l4+l5 + ); + + // Sum rule of the tryads + select = ( + std::floor(l1+l2+l3)==(l1+l2+l3) + && std::floor(l1+l5+l6)==(l1+l5+l6) + && std::floor(l4+l2+l6)==(l4+l2+l6) + && std::floor(l4+l5+l3)==(l4+l5+l3) + ); + + if (!select) return 0.0; + + // We compute the size of the resulting array. + int size = (int)std::ceil(std::min(l2+l3,l5+l6)-std::max(std::fabs(l2-l3),std::fabs(l5-l6)))+1; + + // We prepare the output values + double l1min, l1max; + double sixcof [size]; + int ierr; + + // External function call + drc6j_wrap(l2,l3,l4,l5,l6,&l1min,&l1max,sixcof,size,&ierr); + + // We fetch and return the coefficient with the proper l1 value. + int index = (int)l1-l1min; + return sixcof[index]; +} + +template double sgn(T val) { + return (T(0) < val) - (val < T(0)); +} +} + +#endif // WIGNER_SYMBOLS_H \ No newline at end of file diff --git a/wignerTest.f90 b/wignerTest.f90 new file mode 100644 index 0000000..81b161b --- /dev/null +++ b/wignerTest.f90 @@ -0,0 +1,11 @@ + PROGRAM WIGNER +! Declarations for the main program. + INTEGER IER, I1 + DOUBLE PRECISION L1MIN, L1MAX, THRCOF(200), L2, L3, M1, M2, M3 + READ *, L2, L3, M1, M2, M3 + CALL DRC3JJ(L2,L3,M2,M3,L1MIN,L1MAX,THRCOF,200,IER) + + DO 10 I = 1,15 + PRINT *, THRCOF(I) +10 CONTINUE + END \ No newline at end of file diff --git a/wigner_wrap.f90 b/wigner_wrap.f90 new file mode 100644 index 0000000..c6b32ed --- /dev/null +++ b/wigner_wrap.f90 @@ -0,0 +1,51 @@ +! ---------------------------------------------------------------- +! - Author: Joey Dumont - +! - Date created: 2013-08-15 - +! - Date modded: 2013-08-15 - +! - Desc.: ISO C Binding wrapper for Wigner-3j and - +! - -6j symbols. This will allow the use of - +! - Fortran subroutines drc3jj, drc3jm and - +! - drc6jj. - +! ---------------------------------------------------------------- + +subroutine drc3jj_wrap(l2, l3, m2, m3, l1min, l1max, thrcof, ndim, ier) bind(C) + + use iso_c_binding + implicit none + + real(c_double), value, intent(in) :: l2, l3, m2, m3 + real(c_double), intent(out) :: l1min, l1max + real(c_double), dimension(ndim), intent(out):: thrcof + integer (c_int), value, intent(in) :: ndim + integer (c_int), intent(out) :: ier + + interface + SUBROUTINE DRC3JJ (L2, L3, M2, M3, L1MIN, L1MAX, THRCOF, NDIM, IER) + INTEGER NDIM, IER + DOUBLE PRECISION L2, L3, M2, M3, L1MIN, L1MAX, THRCOF(NDIM) + end SUBROUTINE DRC3JJ + end interface + + call DRC3JJ(l2, l3, m2, m3, l1min, l1max, thrcof, ndim, ier) + +end subroutine drc3jj_wrap + +subroutine drc6j_wrap(l2, l3, l4, l5, l6, l1min, l1max, sixcof, ndim, ier) bind(C) + use iso_c_binding + implicit none + + real(c_double), value, intent(in) :: l2, l3, l4, l5, l6 + real(c_double), intent(out) :: l1min, l1max + real(c_double), dimension(ndim), intent(out):: sixcof + integer(c_int), value, intent(in) :: ndim + integer(c_int), intent(out) :: ier + + interface + SUBROUTINE DRC6J(L2, L3, L4, L5, L6, L1MIN, L1MAX, SIXCOF, NDIM, IER) + INTEGER NDIM, IER + DOUBLE PRECISION L2, L3, L4, L5, L6, L1MIN, L1MAX, SIXCOF(NDIM) + END SUBROUTINE DRC6J + end interface + + call DRC6J(l2, l3, l4, l5, l6, l1min, l1max, sixcof, ndim, ier) +end subroutine drc6j_wrap \ No newline at end of file