Home

Awesome

<p align="center"> <a href="https://github.com/nschloe/orthopy"><img alt="orthopy" src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/orthopy-logo-with-text.png" width="30%"></a> <p align="center">All about orthogonal polynomials.</p> </p>

PyPi Version PyPI pyversions GitHub stars Downloads

<!--[![PyPi downloads](https://img.shields.io/pypi/dm/orthopy.svg?style=flat-square)](https://pypistats.org/packages/orthopy)-->

Discord orthogonal

orthopy provides various orthogonal polynomial classes for lines, triangles, disks, spheres, n-cubes, the nD space with weight function exp(-r<sup>2</sup>) and more. All computations are done using numerically stable recurrence schemes. Furthermore, all functions are fully vectorized and can return results in exact arithmetic.

Installation

Install orthopy from PyPI with

pip install orthopy

How to get a license

Licenses for personal and academic use can be purchased here. You'll receive a confirmation email with a license key. Install the key with

plm add <your-license-key>

on your machine and you're good to go.

For commercial use, please contact support@mondaytech.com.

Basic usage

The main function of all submodules is the iterator Eval which evaluates the series of orthogonal polynomials with increasing degree at given points using a recurrence relation, e.g.,

import orthopy

x = 0.5

evaluator = orthopy.c1.legendre.Eval(x, "classical")
for _ in range(5):
     print(next(evaluator))
1.0          # P_0(0.5)
0.5          # P_1(0.5)
-0.125       # P_2(0.5)
-0.4375      # P_3(0.5)
-0.2890625   # P_4(0.5)

Other ways of getting the first n items are

<!--pytest.mark.skip-->
evaluator = Eval(x, "normal")
vals = [next(evaluator) for _ in range(n)]

import itertools
vals = list(itertools.islice(Eval(x, "normal"), n))

Instead of evaluating at only one point, you can provide any array for x; the polynomials will then be evaluated for all points at once. You can also use sympy for symbolic computation:

import itertools
import orthopy
import sympy

x = sympy.Symbol("x")

evaluator = orthopy.c1.legendre.Eval(x, "classical")
for val in itertools.islice(evaluator, 5):
     print(sympy.expand(val))
1
x
3*x**2/2 - 1/2
5*x**3/2 - 3*x/2
35*x**4/8 - 15*x**2/4 + 3/8

All Eval methods have a scaling argument which can have three values:

For univariate ("one-dimensional") integrals, every new iteration contains one function. For bivariate ("two-dimensional") domains, every level will contain one function more than the previous, and similarly for multivariate families. See the tree plots below.

Line segment (-1, +1) with weight function (1-x)<sup>α</sup> (1+x)<sup>β</sup>

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/legendre.svg" width="100%"><img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/chebyshev1.svg" width="100%"><img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/chebyshev2.svg" width="100%">
LegendreChebyshev 1Chebyshev 2

Jacobi, Gegenbauer (α=β), Chebyshev 1 (α=β=-1/2), Chebyshev 2 (α=β=1/2), Legendre (α=β=0) polynomials.

<!--pytest.mark.skip-->
import orthopy

orthopy.c1.legendre.Eval(x, "normal")
orthopy.c1.chebyshev1.Eval(x, "normal")
orthopy.c1.chebyshev2.Eval(x, "normal")
orthopy.c1.gegenbauer.Eval(x, "normal", lmbda)
orthopy.c1.jacobi.Eval(x, "normal", alpha, beta)

The plots above are generated with

import orthopy

orthopy.c1.jacobi.show(5, "normal", 0.0, 0.0)
# plot, savefig also exist

Recurrence coefficients can be explicitly retrieved by

import orthopy

rc = orthopy.c1.jacobi.RecurrenceCoefficients(
    "monic",  # or "classical", "normal"
    alpha=0, beta=0, symbolic=True
)
print(rc.p0)
for k in range(5):
    print(rc[k])
1
(1, 0, None)
(1, 0, 1/3)
(1, 0, 4/15)
(1, 0, 9/35)
(1, 0, 16/63)

1D half-space with weight function x<sup>α</sup> exp(-r)

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/e1r.svg" width="45%">

(Generalized) Laguerre polynomials.

<!--pytest.mark.skip-->
evaluator = orthopy.e1r.Eval(x, alpha=0, scaling="normal")

1D space with weight function exp(-r<sup>2</sup>)

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/e1r2.svg" width="45%">

Hermite polynomials come in two standardizations:

<!--pytest.mark.skip-->
evaluator = orthopy.e1r2.Eval(
    x,
    "probabilists",  # or "physicists"
    "normal"
)

Associated Legendre "polynomials"

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/associated-legendre.svg" width="45%">

Not all of those are polynomials, so they should really be called associated Legendre functions. The <i>k</i>th iteration contains 2k+1 functions, indexed from -k to k. (See the color grouping in the above plot.)

<!--pytest.mark.skip-->
evaluator = orthopy.c1.associated_legendre.Eval(
    x, phi=None, standardization="natural", with_condon_shortley_phase=True
)

Triangle (T<sub>2</sub>)

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/triangle-tree.png" width="40%">

orthopy's triangle orthogonal polynomials are evaluated in terms of barycentric coordinates, so the X.shape[0] has to be 3.

import orthopy

bary = [0.1, 0.7, 0.2]
evaluator = orthopy.t2.Eval(bary, "normal")

Disk (S<sub>2</sub>)

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/disk-xu-tree.png" width="70%"><img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/disk-zernike-tree.png" width="70%"><img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/disk-zernike2-tree.png" width="70%">
XuZernikeZernike 2

orthopy contains several families of orthogonal polynomials on the unit disk: After Xu, Zernike, and a simplified version of Zernike polynomials.

import orthopy

x = [0.1, -0.3]

evaluator = orthopy.s2.xu.Eval(x, "normal")
# evaluator = orthopy.s2.zernike.Eval(x, "normal")
# evaluator = orthopy.s2.zernike2.Eval(x, "normal")

Sphere (U<sub>3</sub>)

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/sph-tree.png" width="50%">

Complex-valued spherical harmonics, (black=zero, green=real positive, pink=real negative, blue=imaginary positive, yellow=imaginary negative). The functions in the middle are real-valued. The complex angle takes n turns on the <i>n</i>th level.

<!--pytest.mark.skip-->
evaluator = orthopy.u3.EvalCartesian(
    x,
    scaling="quantum mechanic"  # or "acoustic", "geodetic", "schmidt"
)

evaluator = orthopy.u3.EvalSpherical(
    theta_phi,  # polar, azimuthal angles
    scaling="quantum mechanic"  # or "acoustic", "geodetic", "schmidt"
)
<!-- To generate the above plot, write the tree mesh to a file --> <!----> <!-- ```python --> <!-- import orthopy --> <!----> <!-- orthopy.u3.write_tree("u3.vtk", 5, "quantum mechanic") --> <!-- ``` --> <!----> <!-- and open it with [ParaView](https://www.paraview.org/). Select the _srgb1_ data set and --> <!-- turn off _Map Scalars_. -->

n-Cube (C<sub>n</sub>)

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/c1.svg" width="100%"><img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/c2.png" width="100%"><img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/c3.png" width="100%">
C<sub>1</sub> (Legendre)C<sub>2</sub>C<sub>3</sub>

Jacobi product polynomials. All polynomials are normalized on the n-dimensional cube. The dimensionality is determined by X.shape[0].

<!--pytest.mark.skip-->
evaluator = orthopy.cn.Eval(X, alpha=0, beta=0)
values, degrees = next(evaluator)

<i>n</i>D space with weight function exp(-r<sup>2</sup>) (E<sub>n</sub><sup>r<sup>2</sup></sup>)

<img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/e1r2.svg" width="100%"><img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/e2r2.png" width="100%"><img src="https://raw.githubusercontent.com/sigma-py/orthopy/assets/e3r2.png" width="100%">
E<sub>1</sub><sup>r<sup>2</sup></sup>E<sub>2</sub><sup>r<sup>2</sup></sup>E<sub>3</sub><sup>r<sup>2</sup></sup>

Hermite product polynomials. All polynomials are normalized over the measure. The dimensionality is determined by X.shape[0].

<!--pytest.mark.skip-->
evaluator = orthopy.enr2.Eval(
    x,
    standardization="probabilists"  # or "physicists"
)
values, degrees = next(evaluator)

Other tools

Relevant publications