Source code for seemps.analysis.expansion.legendre
from __future__ import annotations
import numpy as np
from ...state import MPS
from ...operators import MPO
from ...typing import Vector
from ..mesh import array_affine
from ..factories import mps_affine
from ..operators import mpo_affine
from .expansion import PolynomialExpansion, ScalarFunction
LEGENDRE_ORTHOGONALITY_DOMAIN = (-1.0, 1.0)
class LegendreExpansion(PolynomialExpansion):
r"""
Expansion in the Legendre basis.
The Legendre polynomials :math:`P_k(x)` are orthogonal on the interval
:math:`[−1, 1]` with respect to the uniform weight :math:`w(x)=1`.
They are widely used in approximation theory since truncated Legendre series
minimize the error in the :math:`L^2([-1,1])` norm.
See https://en.wikipedia.org/wiki/Legendre_polynomials for more information.
"""
approximation_domain: tuple[float, float]
def __init__(self, coefficients: Vector, approximation_domain: tuple[float, float]):
super().__init__(
coefficients=coefficients,
orthogonality_domain=LEGENDRE_ORTHOGONALITY_DOMAIN,
)
self.approximation_domain = approximation_domain
[docs]
def recurrence_coefficients(self, k: int) -> tuple[float, float, float]:
"""
Returns the three-term coefficients of the Legendre recursion:
.. math::
(k+1) P_{k+1}(x) = (2k+1) x P_k(x) - k P_{k-1}(x)
"""
return ((2 * k + 1) / (k + 1), 0.0, k / (k + 1))
[docs]
def rescale_mps(self, mps: MPS) -> MPS:
orig = self.approximation_domain
dest = self.orthogonality_domain
return mps_affine(mps, orig, dest)
[docs]
def rescale_mpo(self, mpo: MPO) -> MPO:
orig = self.approximation_domain
dest = self.orthogonality_domain
return mpo_affine(mpo, orig, dest)
[docs]
@classmethod
def project(
cls,
func: ScalarFunction,
order: int,
approximation_domain: tuple[float, float] = (-1.0, 1.0),
) -> LegendreExpansion:
"""
Project a scalar function onto the Legendre basis on the given approximation domain.
The approximation domain must contain the full range of arguments on which the expansion
will be evaluated; otherwise, rescaling maps the argument outside the orthogonality domain
where the basis is not defined, leading to large errors.
"""
x, w = np.polynomial.legendre.leggauss(order)
x_affine = array_affine(
x,
orig=LEGENDRE_ORTHOGONALITY_DOMAIN,
dest=approximation_domain,
)
P = np.vstack(
[np.polynomial.legendre.legval(x, [0] * k + [1]) for k in range(order)]
)
coefficients = 0.5 * (2 * np.arange(order) + 1) * (P * func(x_affine)).dot(w)
return cls(coefficients, approximation_domain=approximation_domain)