Complex Bessel functions using amos.

Not working yet, probably because of unset machine constants in i2mach.f, d1mach.f.


Former-commit-id: 94a15b3bbbcb26b21533866c984076aeefcecb5e
This commit is contained in:
Marek Nečada 2019-03-20 20:46:47 +02:00
parent 959bb09526
commit 6ee4518bb1
11 changed files with 227 additions and 18 deletions

View File

@ -12,6 +12,11 @@ macro(use_c99)
endif () endif ()
endmacro(use_c99) endmacro(use_c99)
# We need the amos libraries to be compiled with -fPIC
# but at the same time, we do not want to make a .so file,
# so distutils package it with the rest of qpms c/cython lib.
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set (QPMS_VERSION_MAJOR 0) set (QPMS_VERSION_MAJOR 0)
#set (QPMS_VERSION_MINOR 3) #set (QPMS_VERSION_MINOR 3)
cmake_add_fortran_subdirectory (amos cmake_add_fortran_subdirectory (amos

View File

@ -13,8 +13,12 @@ just run ``apt-get install libgsl-dev`` under root. Alternatively,
you can `get the source you can `get the source
<https://www.gnu.org/software/gsl/>`_ get the source and compile it yourself. <https://www.gnu.org/software/gsl/>`_ get the source and compile it yourself.
You also need a fresh enough version of ``cmake``.
After GSL is installed, you can install qpms to your local python library using:: After GSL is installed, you can install qpms to your local python library using::
cmake .
make amos
python3 setup.py install --user python3 setup.py install --user
If GSL is not installed the standard library path on your system, you might If GSL is not installed the standard library path on your system, you might

View File

@ -1,10 +1,10 @@
enable_language (Fortran) enable_language (Fortran)
include(FortranCInterface) include(FortranCInterface)
FortranCInterface_HEADER(amos.h FortranCInterface_HEADER(amos_mangling.h
MACRO_NAMESPACE "AMOS_" MACRO_NAMESPACE "AMOS_"
SYMBOL_NAMESPACE "amos_" SYMBOL_NAMESPACE "amos_"
SYMBOLS zbesj zbesy zbesi zbesk zsqrt SYMBOLS zbesj zbesy zbesh zbesi zbesk zsqrt
) )
add_library(amos add_library(amos
@ -14,3 +14,5 @@ add_library(amos
xerror.f zairy.f zbesy.f zbunk.f zlog.f zseri.f zuchk.f zuni2.f zwrsk.f xerror.f zairy.f zbesy.f zbunk.f zlog.f zseri.f zuchk.f zuni2.f zwrsk.f
) )
target_include_directories (amos PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

42
amos/amos.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef AMOS_H
#define AMOS_H
#include "amos_mangling.h"
#define INTEGER_t int
#define DOUBLE_PRECISION_t double
void amos_zbesj(const DOUBLE_PRECISION_t *zr,
const DOUBLE_PRECISION_t *zi,
const DOUBLE_PRECISION_t *fnu,
const INTEGER_t *kode,
const INTEGER_t *n,
DOUBLE_PRECISION_t *cyr,
DOUBLE_PRECISION_t *cyi,
INTEGER_t *nz,
INTEGER_t *ierr);
void amos_zbesy(const DOUBLE_PRECISION_t *zr,
const DOUBLE_PRECISION_t *zi,
const DOUBLE_PRECISION_t *fnu,
const INTEGER_t *kode,
const INTEGER_t *n,
DOUBLE_PRECISION_t *cyr,
DOUBLE_PRECISION_t *cyi,
INTEGER_t *nz,
DOUBLE_PRECISION_t *cwrkr,
DOUBLE_PRECISION_t *cwrki,
INTEGER_t *ierr);
void amos_zbesh(const DOUBLE_PRECISION_t *zr,
const DOUBLE_PRECISION_t *zi,
const DOUBLE_PRECISION_t *fnu,
const INTEGER_t *kode,
const INTEGER_t *m,
const INTEGER_t *n,
DOUBLE_PRECISION_t *cyr,
DOUBLE_PRECISION_t *cyi,
INTEGER_t *nz,
INTEGER_t *ierr);
#endif

View File

@ -8,7 +8,8 @@ set (DIRS ${GSL_INCLUDE_DIRS} ${GSLCBLAS_INCLUDE_DIRS})
include_directories(${DIRS}) include_directories(${DIRS})
add_library (qpms translations.c tmatrices.c vecprint.c vswf.c wigner.c add_library (qpms translations.c tmatrices.c vecprint.c vswf.c wigner.c
lattices2d.c gaunt.c error.c legendre.c symmetries.c vecprint.c) lattices2d.c gaunt.c error.c legendre.c symmetries.c vecprint.c
bessel.c)
use_c99() use_c99()
set(LIBS ${LIBS} ${GSL_LIBRARIES} ${GSLCBLAS_LIBRARIES}) set(LIBS ${LIBS} ${GSL_LIBRARIES} ${GSLCBLAS_LIBRARIES})

View File

@ -7,6 +7,7 @@
#include <gsl/gsl_sf_bessel.h> #include <gsl/gsl_sf_bessel.h>
#include <complex.h> #include <complex.h>
#include "qpms_error.h" #include "qpms_error.h"
#include <amos.h>
#ifndef M_LN2 #ifndef M_LN2
#define M_LN2 0.69314718055994530942 /* log_e 2 */ #define M_LN2 0.69314718055994530942 /* log_e 2 */
@ -16,6 +17,36 @@ static inline complex double ipow(int x) {
return cpow(I,x); return cpow(I,x);
} }
#if 0
// Inspired by scipy/special/_spherical_bessel.pxd
static inline complex double spherical_jn(qpms_l_t l, complex double z) {
if (isnan(creal(z)) || isnan(cimag(z))) return NAN+I*NAN;
if (l < 0) QPMS_WTF;
if (fpclassify(creal(z)) == FP_INFINITE)
if(0 == cimag(z))
return 0;
else
return INFINITY + I * INFINITY;
if (z == 0)
if (l == 0) return 1;
else return 0;
return csqrt(M_PI_2/z) * zbesj(l + .5, z);
}
static inline complex double spherical_yn(qpms_l_t l, complex double z) {
if (isnan(creal(z)) || isnan(cimag(z))) return NAN+I*NAN;
if (l < 0) QPMS_WTF;
if (fpclassify(creal(z)) == FP_INFINITE)
if(0 == cimag(z))
return 0;
else
return INFINITY + I * INFINITY;
if (z == 0)
return NAN;
return csqrt(M_PI_2/z) * zbesy(l + .5, z);
}
#endif
// There is a big issue with gsl's precision of spherical bessel function; these have to be implemented differently // There is a big issue with gsl's precision of spherical bessel function; these have to be implemented differently
qpms_errno_t qpms_sph_bessel_realx_fill(qpms_bessel_t typ, qpms_l_t lmax, double x, complex double *result_array) { qpms_errno_t qpms_sph_bessel_realx_fill(qpms_bessel_t typ, qpms_l_t lmax, double x, complex double *result_array) {
int retval; int retval;
@ -50,12 +81,60 @@ qpms_errno_t qpms_sph_bessel_realx_fill(qpms_bessel_t typ, qpms_l_t lmax, double
assert(0); assert(0);
} }
qpms_errno_t qpms_sph_bessel_fill(qpms_bessel_t typ, qpms_l_t lmax, complex double x, complex double *result_array) { // TODO DOC
int retval = qpms_sph_bessel_realx_fill(typ, lmax, creal(x), result_array); qpms_errno_t qpms_sph_bessel_fill(qpms_bessel_t typ, qpms_l_t lmax, complex double x, complex double *res) {
if(!cimag(x)) if(!cimag(x))
return retval; return qpms_sph_bessel_realx_fill(typ, lmax, creal(x), res);
else if (isnan(creal(x)) || isnan(cimag(x)))
for(qpms_l_t l = 0; l <= lmax; ++l) res[l] = NAN + I*NAN;
else if (lmax < 0) QPMS_WTF;
else if (fpclassify(creal(x)) == FP_INFINITE)
for(qpms_l_t l = 0; l <= lmax; ++l) res[l] = INFINITY + I * INFINITY;
else {
const DOUBLE_PRECISION_t zr = creal(x), zi = cimag(x), fnu = 0.5;
const INTEGER_t n = lmax + 1, kode = 1 /* No exponential scaling */;
DOUBLE_PRECISION_t cyr[n], cyi[n];
INTEGER_t ierr, nz;
unsigned int kindchar; // Only for error output
const complex double prefac = csqrt(M_PI_2/x);
switch(typ) {
case QPMS_BESSEL_REGULAR:
kindchar = 'j';
amos_zbesj(&zr, &zi, &fnu, &kode, &n, cyr, cyi, &nz, &ierr);
break;
case QPMS_BESSEL_SINGULAR:
kindchar = 'y';
{
DOUBLE_PRECISION_t cwrkr[lmax + 1], cwrki[lmax + 1];
amos_zbesy(&zr, &zi, &fnu, &kode, &n, cyr, cyi, &nz, cwrkr, cwrki, &ierr);
}
break;
case QPMS_HANKEL_PLUS:
case QPMS_HANKEL_MINUS:
kindchar = 'h';
{
const INTEGER_t m = (typ == QPMS_HANKEL_PLUS) ? 1 : 2;
amos_zbesh(&zr, &zi, &fnu, &kode, &m, &n, cyr, cyi, &nz, &ierr);
}
break;
default:
QPMS_WTF;
}
// TODO check for underflows? (nz != 0)
if (ierr == 0 || ierr == 3) {
for (qpms_l_t l = 0; l <= lmax; ++l)
res[l] = prefac * (cyr[l] + I * cyi[l]);
if (ierr == 3)
QPMS_WARN("Amos's zbes%c computation done but losses of significance "
"by argument reduction produce less than half of machine accuracy.",
kindchar);
return QPMS_SUCCESS; //TODO maybe something else if ierr == 3
}
else else
QPMS_NOT_IMPLEMENTED("complex argument of Bessel function."); QPMS_PR_ERROR("Amos's zbes%c failed with ierr == %d.",
kindchar, (int) ierr);
}
return QPMS_SUCCESS;
} }
static inline ptrdiff_t akn_index(qpms_l_t n, qpms_l_t k) { static inline ptrdiff_t akn_index(qpms_l_t n, qpms_l_t k) {
@ -84,10 +163,10 @@ static inline qpms_errno_t qpms_sbessel_calculator_ensure_lMax(qpms_sbessel_calc
} }
} }
complex double qpms_sbessel_calc_h1(qpms_sbessel_calculator_t *c, qpms_l_t n, double x) { complex double qpms_sbessel_calc_h1(qpms_sbessel_calculator_t *c, qpms_l_t n, complex double x) {
if(QPMS_SUCCESS != qpms_sbessel_calculator_ensure_lMax(c, n)) if(QPMS_SUCCESS != qpms_sbessel_calculator_ensure_lMax(c, n))
abort(); abort();
complex double z = I/x; // FIXME this should be imaginary double, but gcc is broken? complex double z = I/x;
complex double result = 0; complex double result = 0;
for (qpms_l_t k = n; k >= 0; --k) for (qpms_l_t k = n; k >= 0; --k)
// can we use fma for complex? // can we use fma for complex?
@ -98,14 +177,14 @@ complex double qpms_sbessel_calc_h1(qpms_sbessel_calculator_t *c, qpms_l_t n, do
} }
qpms_errno_t qpms_sbessel_calc_h1_fill(qpms_sbessel_calculator_t * const c, qpms_errno_t qpms_sbessel_calc_h1_fill(qpms_sbessel_calculator_t * const c,
const qpms_l_t lMax, const double x, complex double * const target) { const qpms_l_t lMax, const complex double x, complex double * const target) {
if(QPMS_SUCCESS != qpms_sbessel_calculator_ensure_lMax(c, lMax)) if(QPMS_SUCCESS != qpms_sbessel_calculator_ensure_lMax(c, lMax))
abort(); abort();
memset(target, 0, sizeof(complex double) * lMax); memset(target, 0, sizeof(complex double) * lMax);
complex double kahancomp[lMax]; complex double kahancomp[lMax];
memset(kahancomp, 0, sizeof(complex double) * lMax); memset(kahancomp, 0, sizeof(complex double) * lMax);
for(qpms_l_t k = 0; k <= lMax; ++k){ for(qpms_l_t k = 0; k <= lMax; ++k){
double xp = pow(x, -k-1); double xp = cpow(x, -k-1);
for(qpms_l_t l = k; l <= lMax; ++l) for(qpms_l_t l = k; l <= lMax; ++l)
ckahanadd(target + l, kahancomp + l, c->akn[akn_index(l,k)] * xp * ipow(k-l-1)); ckahanadd(target + l, kahancomp + l, c->akn[akn_index(l,k)] * xp * ipow(k-l-1));
} }

View File

@ -44,6 +44,18 @@ void qpms_pr_error_at_flf(const char *filename, unsigned int linenum,
abort(); abort();
} }
void qpms_warn_at_flf(const char *filename, unsigned int linenum,
const char *func,
const char *fmt, ...) {
fprintf(stderr, "%s:%u, %s: ", filename, linenum, func);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
fflush(stderr);
}
void qpms_pr_debug_at_flf(const char *filename, unsigned int linenum, void qpms_pr_debug_at_flf(const char *filename, unsigned int linenum,
const char *func, const char *func,
const char *fmt, ...) { const char *fmt, ...) {

View File

@ -13,18 +13,28 @@ void qpms_pr_error_at_flf(const char *filename, unsigned int linenum,
const char *func, const char *func,
const char *fmt, ...); const char *fmt, ...);
/// Print a warning message to stderr and flush the buffer. Don't call this directly, use QPMS_WARN().
void qpms_warn_at_flf(const char *filename, unsigned int linenum,
const char *func,
const char *fmt, ...);
void qpms_pr_debug_at_flf(const char *filename, unsigned int linenum, void qpms_pr_debug_at_flf(const char *filename, unsigned int linenum,
const char *func, const char *func,
const char *fmt, ...); const char *fmt, ...);
//void qpms_error_at_line(const char *filename, unsigned int linenum, //void qpms_error_at_line(const char *filename, unsigned int linenum,
// const char *fmt, ...); // const char *fmt, ...);
#define QPMS_WARN(msg, ...) qpms_warn_at_flf(__FILE__,__LINE__,__func__,msg, ##__VA_ARGS__)
#define QPMS_CRASHING_MALLOC(pointer, size) {(pointer) = malloc(size); if(!pointer && (size)) qpms_pr_debug_at_flf(__FILE__,__LINE__,__func__, "Allocation of %zd bytes for " #pointer " failed.", (size_t) (size));} #define QPMS_CRASHING_MALLOC(pointer, size) {(pointer) = malloc(size); if(!pointer && (size)) qpms_pr_debug_at_flf(__FILE__,__LINE__,__func__, "Allocation of %zd bytes for " #pointer " failed.", (size_t) (size));}
#define QPMS_CRASHING_REALLOC(pointer, size) {(pointer) = realloc(pointer, size); if(!pointer && (size)) qpms_pr_debug_at_flf(__FILE__,__LINE__,__func__, "Rellocation of %zd bytes for " #pointer " failed.", (size_t) (size));} #define QPMS_CRASHING_REALLOC(pointer, size) {(pointer) = realloc(pointer, size); if(!pointer && (size)) qpms_pr_debug_at_flf(__FILE__,__LINE__,__func__, "Rellocation of %zd bytes for " #pointer " failed.", (size_t) (size));}
#define QPMS_WTF qpms_pr_error_at_flf(__FILE__,__LINE__,__func__,"Unexpected error.") #define QPMS_WTF qpms_pr_error_at_flf(__FILE__,__LINE__,__func__,"Unexpected error.")
#define QPMS_PR_ERROR(msg, ...) qpms_pr_error_at_flf(__FILE__,__LINE__,__func__,msg, ##__VA_ARGS__)
#define QPMS_ENSURE_SUCCESS(x) {if(x) QPMS_WTF;} #define QPMS_ENSURE_SUCCESS(x) {if(x) QPMS_WTF;}
#define QPMS_ENSURE(x, msg, ...) {if(!(x)) qpms_pr_error_at_flf(__FILE__,__LINE__,__func__,msg, ##__VA_ARGS__); } #define QPMS_ENSURE(x, msg, ...) {if(!(x)) qpms_pr_error_at_flf(__FILE__,__LINE__,__func__,msg, ##__VA_ARGS__); }

View File

@ -24,9 +24,9 @@ void qpms_sbessel_calculator_pfree(qpms_sbessel_calculator_t *c);
qpms_errno_t qpms_sbessel_calc_fill(qpms_sbessel_calculator_t *c, qpms_bessel_t typ, qpms_l_t lmax, qpms_errno_t qpms_sbessel_calc_fill(qpms_sbessel_calculator_t *c, qpms_bessel_t typ, qpms_l_t lmax,
double x, complex double *result_array); double x, complex double *result_array);
complex double qpms_sbessel_calc_h1(qpms_sbessel_calculator_t *c, qpms_l_t n, double x); complex double qpms_sbessel_calc_h1(qpms_sbessel_calculator_t *c, qpms_l_t n, complex double x);
qpms_errno_t qpms_sbessel_calc_h1_fill(qpms_sbessel_calculator_t *c, qpms_l_t lmax, qpms_errno_t qpms_sbessel_calc_h1_fill(qpms_sbessel_calculator_t *c, qpms_l_t lmax,
double x, complex double *result_array); complex double x, complex double *result_array);
/****************************************************************************** /******************************************************************************

View File

@ -1,5 +1,6 @@
#include <math.h> #include <math.h>
#include "qpms_types.h" #include "qpms_types.h"
#include "qpms_specfunc.h"
#include "gaunt.h" #include "gaunt.h"
#include "translations.h" #include "translations.h"
#include "indexing.h" // TODO replace size_t and int with own index types here #include "indexing.h" // TODO replace size_t and int with own index types here
@ -89,6 +90,7 @@ static inline int gauntB_Q_max(int M, int n, int mu, int nu) {
return imin(n, imin(nu, (n+nu+1-abs(M+mu))/2)); return imin(n, imin(nu, (n+nu+1-abs(M+mu))/2));
} }
#if 0 //Apparently, this is duplicit to that in bessel.c (which also support complex x)
int qpms_sph_bessel_fill(qpms_bessel_t typ, int lmax, double x, complex double *result_array) { int qpms_sph_bessel_fill(qpms_bessel_t typ, int lmax, double x, complex double *result_array) {
int retval; int retval;
double tmparr[lmax+1]; double tmparr[lmax+1];
@ -121,6 +123,7 @@ int qpms_sph_bessel_fill(qpms_bessel_t typ, int lmax, double x, complex double *
} }
assert(0); assert(0);
} }
#endif
static inline double qpms_trans_normlogfac(qpms_normalisation_t norm, static inline double qpms_trans_normlogfac(qpms_normalisation_t norm,
int m, int n, int mu, int nu) { int m, int n, int mu, int nu) {

View File

@ -17,6 +17,50 @@ print("You might want to add additional library path to LD_LIBRARY_PATH (especia
if("LD_LIBRARY_PATH" in os.environ): if("LD_LIBRARY_PATH" in os.environ):
print(os.environ['LD_LIBRARY_PATH'].split(':')) print(os.environ['LD_LIBRARY_PATH'].split(':'))
amos_sources = [
'amos/d1mach.f',
'amos/dgamln.f',
'amos/i1mach.f',
'amos/xerror.f',
'amos/zabs.f',
'amos/zacai.f',
'amos/zacon.f',
'amos/zairy.f',
'amos/zasyi.f',
'amos/zbesh.f',
'amos/zbesi.f',
'amos/zbesj.f',
'amos/zbesk.f',
'amos/zbesy.f',
'amos/zbinu.f',
'amos/zbiry.f',
'amos/zbknu.f',
'amos/zbuni.f',
'amos/zbunk.f',
'amos/zdiv.f',
'amos/zexp.f',
'amos/zkscl.f',
'amos/zlog.f',
'amos/zmlri.f',
'amos/zmlt.f',
'amos/zrati.f',
'amos/zs1s2.f',
'amos/zseri.f',
'amos/zshch.f',
'amos/zsqrt.f',
'amos/zuchk.f',
'amos/zunhj.f',
'amos/zuni1.f',
'amos/zuni2.f',
'amos/zunik.f',
'amos/zunk1.f',
'amos/zunk2.f',
'amos/zuoik.f',
'amos/zwrsk.f',
]
qpms_c = Extension('qpms_c', qpms_c = Extension('qpms_c',
sources = ['qpms/qpms_c.pyx', #'qpms/hexpoints_c.pyx', sources = ['qpms/qpms_c.pyx', #'qpms/hexpoints_c.pyx',
'qpms/gaunt.c',#'qpms/gaunt.h','qpms/vectors.h','qpms/translations.h', 'qpms/gaunt.c',#'qpms/gaunt.h','qpms/vectors.h','qpms/translations.h',
@ -28,7 +72,9 @@ qpms_c = Extension('qpms_c',
'qpms/vswf.c', # FIXME many things from vswf.c are not required by this module, but they have many dependencies (following in this list); maybe I want to move all the "basespec stuff" 'qpms/vswf.c', # FIXME many things from vswf.c are not required by this module, but they have many dependencies (following in this list); maybe I want to move all the "basespec stuff"
'qpms/legendre.c', 'qpms/legendre.c',
'qpms/tmatrices.c', 'qpms/tmatrices.c',
'qpms/error.c'], 'qpms/error.c',
'qpms/bessel.c',
],
extra_compile_args=['-std=c99','-ggdb', '-O3', extra_compile_args=['-std=c99','-ggdb', '-O3',
'-DQPMS_COMPILE_PYTHON_EXTENSIONS', # this is required '-DQPMS_COMPILE_PYTHON_EXTENSIONS', # this is required
#'-DQPMS_USE_OMP', #'-DQPMS_USE_OMP',
@ -37,17 +83,22 @@ qpms_c = Extension('qpms_c',
], ],
libraries=['gsl', 'lapacke', 'blas', 'gslcblas', 'pthread', #'omp' libraries=['gsl', 'lapacke', 'blas', 'gslcblas', 'pthread', #'omp'
# TODO resolve the problem with openblas (missing gotoblas symbol) and preferable use other blas library # TODO resolve the problem with openblas (missing gotoblas symbol) and preferable use other blas library
#('amos', dict(sources=amos_sources) ),
], ],
runtime_library_dirs=os.environ['LD_LIBRARY_PATH'].split(':') if 'LD_LIBRARY_PATH' in os.environ else [] include_dirs=['amos'],
extra_link_args=['amos/libamos.a'],
runtime_library_dirs=os.environ['LD_LIBRARY_PATH'].split(':') if 'LD_LIBRARY_PATH' in os.environ else [],
#extra_objects = ['amos/libamos.a'], # FIXME apparently, I would like to eliminate the need to cmake/make first
) )
setup(name='qpms', setup(name='qpms',
version = "0.2.995", version = "0.2.995",
packages=['qpms'], packages=['qpms'],
# libraries = [('amos', {'sources': amos_sources} )],
setup_requires=['cython>=0.28',], setup_requires=['cython>=0.28',],
install_requires=['cython>=0.28','quaternion','spherical_functions','scipy>=0.18.0', 'sympy>=1.2'], install_requires=['cython>=0.28','quaternion','spherical_functions','scipy>=0.18.0', 'sympy>=1.2'],
dependency_links=['https://github.com/moble/quaternion/archive/v2.0.tar.gz','https://github.com/moble/spherical_functions/archive/master.zip'], dependency_links=['https://github.com/moble/quaternion/archive/v2.0.tar.gz','https://github.com/moble/spherical_functions/archive/master.zip'],
ext_modules=cythonize([qpms_c], include_path=['qpms'], gdb_debug=True), ext_modules=cythonize([qpms_c], include_path=['qpms', 'amos'], gdb_debug=True),
cmdclass = {'build_ext': build_ext}, cmdclass = {'build_ext': build_ext},
) )