GaussJacobiQuad

About

A permissively licensed modern implementation of Gauss-Jacobi quadrature which returns the weights and nodes over the standard interval [-1, 1].

Usage

The most automated approach is to use the conda environment and fpm build:

micromamba create -f environment.yml
micromamba activate gaussjacquad
fpm build

An analytic result can be obtained from the scripts folder.

python scripts/sympy_gauss_jac.py --npts <npoints> --alpha <alpha> --beta <beta> --n_dig <precision>
fpm test

Running the implemented recursion based Gauss-Jacobi can be done via:

fpm run gjp_quad_rec -- <npoints> <alpha> <beta>
fpm run gjp_quad -- <npoints> <alpha> <beta> <method>

Currently the only supported methods are "rec" and "gw" (Golub Welsch).

Meson Support

A meson build backend is also present, which makes it easier to incorporate as subprojects of projects other than those supported by fpm.

meson setup bbdir
meson compile -C bbdir
./bbdir/gjp_quad <npoints> <alpha> <beta> <method>

External Fortran libaries

We support and encourage users to generate single file versions of the algorithms here to include in their code bases. This can be done with the scripts/add_headers.py script:

# Strips comments by default
python scripts/export_single.py --modulename "gjp_algo665"
python scripts/export_single.py --modulename "gjp_gw" --keep-comments

These can be dropped into any code base or compiled as is into a shared library.

gfortran dist/gjp_algo665_single.f90 --shared -fPIC -o libgjp_algo665.so

Interfaces

C/C++ header interface

We provide a header only interface, which bypasses passing strings between Fortran and C. In order to do this, there is some duplication logic in GaussJacobiQuad and GaussJacobiQuadCInterp.

There is also a CLI interface to the C bound interface, c_cli_gjpq. However, this will not be compiled by fpm.

meson setup bbdir
meson compile -C bbdir
./bbdir/c_cli_gjpq <npoints> <alpha> <beta> <method>

The C CLI might be more pleasant in that decimals do not need to be provided explicitly for alpha and beta.

f2py generated interface

The ISO_C_BINDING variant of the code is also used for a python interface generated with f2py. It is easiest to use with the new meson back-end:

cd interfaces/PyInterface
f2py -c --backend meson gjquadpy.pyf \
--dep lapack \
../../src/GaussJacobiQuadCCompat.f90 \
../../src/GaussJacobiQuad.f90 \
../../src/gjp_constants.f90 \
../../src/gjp_types.f90 \
../../src/gjp_rec.f90 \
../../src/gjp_common.f90 \
../../src/gjp_lapack.f90 \
../../src/gjp_gw.f90 \
../../src/gjp_algo665.f90

Once compiled, the gjpquad_cli.py script can be used to run the code:

python gjquad_cli.py --npts 5 --alpha 2 --beta 3
Root: -6.90457750126761027E-01 Weight: 2.74101780663370022E-02
Root: -3.26519931349000647E-01 Weight: 2.12917860603648035E-01
Root: 8.23378495520349085E-02 Weight: 4.39084379443950568E-01
Root: 4.75178870612831761E-01 Weight: 3.22206565472217876E-01
Root: 7.92794294644228348E-01 Weight: 6.50476830805121059E-02

Notes

  • The rec method fails for high values of beta so the gw method

should be used in such situations.

  • algo665 is an in-place variant of gw and is much faster when many points are needed

Benchmarks

A very preliminary set can be run once all the interfaces have been compiled:

# From $GITROOT
mv interfaces/PyInterface/gjquadpy*.so .
hyperfine --warmup 10 --export-markdown gjp_benchmarks.md \
'bbdir/gjp_quad 5 2. 3. "gw"' \
'bbdir/c_cli_gjpq 5 2 3 gw' \
'PYTHONPATH=$(pwd) python interfaces/PyInterface/gjquad_cli.py --npts 5 --alpha 2 --beta 3' \
'python scripts/scipy_gauss_jac.py --npts 5 --alpha 2 --beta 3' \
'python scripts/sympy_gauss_jac.py --npts 5 --alpha 2 --beta 3 --n_dig 15'

Which gives:

ummary
bbdir/c_cli_gjpq 5 2 3 gw ran
1.03 ± 0.45 times faster than bbdir/gjp_quad 5 2. 3. "gw"
39.08 ± 11.48 times faster than PYTHONPATH=$(pwd) python interfaces/PyInterface/gjquad_cli.py --npts 5 --alpha 2 --beta 3
55.71 ± 16.51 times faster than python scripts/scipy_gauss_jac.py --npts 5 --alpha 2 --beta 3
86.73 ± 24.86 times faster than python scripts/sympy_gauss_jac.py --npts 5 --alpha 2 --beta 3 --n_dig 15
hyperfine --warmup 10 --export-markdown gjp_benchmarks.md 32.65s user 61.31s system 469% cpu 20.004 total

Or in other words:

Command Mean [ms] Min [ms] Max [ms] Relative
gjp_quad 5 2. 3. "gw" 2.6 ± 0.8 1.8 7.8 1.03 ± 0.45
c_cli_gjpq 5 2 3 gw 2.5 ± 0.7 1.8 8.4 1.00
gjquad_cli.py --npts 5 --alpha 2 --beta 3 97.5 ± 6.3 95.4 130.8 39.08 ± 11.48
scipy_gauss_jac.py --npts 5 --alpha 2 --beta 3 139.0 ± 10.6 132.4 173.4 55.71 ± 16.51
sympy_gauss_jac.py --npts 5 --alpha 2 --beta 3 --n_dig 15 216.4 ± 1.7 214.7 219.7 86.73 ± 24.86

Which suggests what one might suspect, that there is a large overhead in calling python , and that the C and Fortran variants are almost exactly as fast as each other. However, the f2py variant is still way faster than the existing python implementations.

hyperfine --warmup 10 --export-markdown gjp_benchmarks.md \
'PYTHONPATH=$(pwd) python interfaces/PyInterface/gjquad_cli.py --npts 5 --alpha 2 --beta 3' \
'python scripts/scipy_gauss_jac.py --npts 5 --alpha 2 --beta 3'
Summary
PYTHONPATH=$(pwd) python interfaces/PyInterface/gjquad_cli.py --npts 5 --alpha 2 --beta 3 ran
1.38 ± 0.11 times faster than python scripts/scipy_gauss_jac.py --npts 5 --alpha 2 --beta 3

Development

Developing locally

A pre-commit job is setup on CI to enforce consistent styles, so it is best to set it up locally as well (using pipx for isolation):

# Run before commiting
pipx run pre-commit run --all-files
# Or install the git hook to enforce this
pipx run pre-commit install

Updating licenses

When the headers in the sources need to be updated modify add_headers.py and run:

python scripts/add_headers.py --dirs src/ interfaces/ --ftypes "f90,f77" --cchar '!'
python scripts/add_headers.py --dirs interfaces --ftypes "c,h" --cchar '//'
python scripts/add_headers.py --dirs interfaces scripts --ftypes "py" --cchar '#'

Remember to do this before exporting the code into other projects (e.g. featom).

License

MIT.

Citation

If you use this library (including the interfaces) please remember to cite it as:

Rohit Goswami. (2023). HaoZeke/GaussJacobiQuad: GaussJacobiQuad I (v0.1.0). Zenodo. https://doi.org/10.5281/ZENODO.8285112

Or use the bibtex entry:

@software{rgGaussQuad23,
author = {Rohit Goswami},
title = {HaoZeke/GaussJacobiQuad: GaussJacobiQuad I},
month = aug,
year = 2023,
publisher = {Zenodo},
version = {v0.1.0},
doi = {10.5281/zenodo.8285112},
url = {https://doi.org/10.5281/zenodo.8285112}
}

An ArXiv –> JOSS paper is in the works.