5 Best Ways to Evaluate a 2D Legendre Series on the Cartesian Product of x and y in Python

πŸ’‘ Problem Formulation: In mathematical and computational fields, it’s common to work with orthogonal polynomials such as Legendre polynomials. Evaluating a two-dimensional (2D) Legendre series on the Cartesian product of two domains, x and y, involves computing values over a grid defined by these two variables. This article aims to illustrate Python methods to perform this task effectively. An example input would be two 1D arrays representing the x and y domains, along with the coefficients of a 2D Legendre series; the desired output is the evaluated grid of values.

Method 1: Using NumPy’s Polynomial Library

NumPy’s polynomial library includes a subpackage numpy.polynomial.Legendre that can evaluate Legendre polynomials. The 2D series evaluation requires creating a meshgrid from the x and y domain arrays and then using the legval2d() function which takes in the x and y grid arrays along with the series coefficients to evaluate the polynomial over the 2D space.

Here’s an example:

import numpy as np

# Define the domains and coefficients
x = np.linspace(-1, 1, 100)
y = np.linspace(-1, 1, 100)
c = np.array([[0, 1], [1, 0]])  # Coefficients for the Legendre series

# Create the meshgrid for the 2D domain
X, Y = np.meshgrid(x, y)

# Evaluate the 2D Legendre series
values = np.polynomial.legendre.legval2d(X, Y, c)

Output will be a grid of values evaluated at points in the Cartesian product of x and y.

This method is straightforward and leverages the high-performance computing capabilities of NumPy. However, performance might become an issue for very high-degree polynomials or extremely fine grids due to computational complexity.

Method 2: Using SciPy’s Special Functions

SciPy’s special module offers functions to compute orthogonal polynomials. For evaluating a 2D Legendre series, one can use the eval_legendre() function from scipy.special in conjunction with a double loop over the x and y arrays to compute the series value at each point.

Here’s an example:

import numpy as np
from scipy.special import eval_legendre

# Define the domains
x = np.linspace(-1, 1, 100)
y = np.linspace(-1, 1, 100)
c = [0,1]  # Coefficients for the univariate Legendre series

# Compute the values over the grid
values = np.zeros((len(x), len(y)))
for i, xi in enumerate(x):
    for j, yj in enumerate(y):
        values[i, j] = eval_legendre(c[0], xi) * eval_legendre(c[1], yj)

Output will be a 2D array of the Legendre series evaluated on the grid.

While this method provides great control over the evaluation process and allows for custom modifications, it is less efficient than vectorized operations and is not recommended for large grid sizes or high-degree polynomials due to significant computational time.

Method 3: Exploiting Symmetry Properties of Legendre Polynomials

Legendre polynomials possess certain symmetry properties that can simplify computations, especially for specific coefficients. By utilizing these properties within a manual loop approachβ€”much like Method 2β€”one can optimize the series evaluation for certain symmetrical cases, reducing computational complexity.

Here’s an example:

import numpy as np
from scipy.special import eval_legendre

# Define the domains
x = np.linspace(-1, 1, 50)
y = np.linspace(-1, 1, 50)

# Assuming a simple case with symmetric coefficients
c = [1, 0]

# Compute the values using symmetry properties
values = np.zeros((len(x), len(y)))
for i in range(len(x)):
    for j in range(len(y)):
        # Use symmetry: P_n(-x) = (-1)^n * P_n(x)
        values[i, j] = np.sign(x[i] * y[j]) * eval_legendre(c[0], abs(x[i])) * eval_legendre(c[1], abs(y[j]))

The output will be a symmetric 2D array with the evaluated Legendre series at each point.

This method is useful when dealing with certain symmetric cases, thereby optimizing the evaluation. However, it becomes cumbersome to manually handle different types of symmetry and is limited in its applicability.

Method 4: Tensor Product of 1D Legendre Polynomials

Since a 2D Legendre polynomial can be considered as a tensor product of two 1D Legendre polynomials, one for each variable, this method involves separately evaluating the 1D polynomials for x and y and then combining the results to form the 2D evaluation grid.

Here’s an example:

import numpy as np
from numpy.polynomial.legendre import legval

# Define the domains
x = np.linspace(-1, 1, 100)
y = np.linspace(-1, 1, 100)
c_x = [0, 1]  # Coefficients for x
c_y = [1, 0]  # Coefficients for y

# Evaluate 1D Legendre polynomials separately
Lx = legval(x, c_x)
Ly = legval(y, c_y)

# Compute the outer product to get the 2D series
values = np.outer(Lx, Ly)

Output is a 2D array representing the evaluated Legendre series on the x-y grid.

This method takes advantage of the separability of 2D Legendre polynomials and is a vectorized approach, offering efficient computation. However, constructing the coefficients for the series may require additional considerations when extending to higher dimensions or more complex series.

Bonus One-Liner Method 5: Combine NumPy Functions

In this one-liner approach, we utilize NumPy’s comprehensive library to combine different functions into a single efficient line, evaluating the Legendre series by using a meshgrid along with broadcasting.

Here’s an example:

import numpy as np

# Define the domains and coefficients
x = np.linspace(-1, 1, 100)
y = np.linspace(-1, 1, 100)
c = np.array([[0, 1], [1, 0]])

# Evaluate the series in a one-liner
values = np.polynomial.legendre.leggrid2d(x, y, c)

The output is a grid of values as with the other methods.

By combining different NumPy’s functions, this one-liner approach is concise, highly readable, and leverages NumPy’s efficient implementation. Nevertheless, understanding how this works might require a deeper knowledge of NumPy’s broadcasting and meshgrid capabilities.

Summary/Discussion

  • Method 1: NumPy’s Polynomial Library. Fast and efficient for moderate-sized problems. May be slow for very fine grids or high-degree polynomials.
  • Method 2: SciPy’s Special Functions. Offers precise control but is computationally intensive for large-scale problems or high polynomial degrees.
  • Method 3: Exploiting Symmetry Properties. Optimizes computation for symmetric polynomials but is limited in scope and requires manual handling of symmetries.
  • Method 4: Tensor Product of 1D Polynomials. Efficient and vectorized, suitable for separable problems. Construction of coefficients can be complex for larger problems.
  • Method 5: One-Liner Using NumPy. Extremely concise and efficient but less straightforward to those unfamiliar with advanced NumPy features.