Skip to content

HaoZeke/GaussJacobiQuad

Repository files navigation

GaussJacobiQuad https://zenodo.org/badge/667604312.svg https://github.com/HaoZeke/GaussJacobiQuad/actions/workflows/build_test.yml/badge.svg https://github.com/HaoZeke/GaussJacobiQuad/actions/workflows/build_docs.yml/badge.svg

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:

CommandMean [ms]Min [ms]Max [ms]Relative
~gjp_quad 5 2. 3. “gw”~2.6 ± 0.81.87.81.03 ± 0.45
c_cli_gjpq 5 2 3 gw2.5 ± 0.71.88.41.00
gjquad_cli.py --npts 5 --alpha 2 --beta 397.5 ± 6.395.4130.839.08 ± 11.48
scipy_gauss_jac.py --npts 5 --alpha 2 --beta 3139.0 ± 10.6132.4173.455.71 ± 16.51
sympy_gauss_jac.py --npts 5 --alpha 2 --beta 3 --n_dig 15216.4 ± 1.7214.7219.786.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.