Skip to content

Commit 2b58c3f

Browse files
committed
add padua nodes
1 parent 61c731c commit 2b58c3f

3 files changed

Lines changed: 170 additions & 2 deletions

File tree

examples/plot-padua-nodes.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
4+
import modepy as mp
5+
6+
7+
def _first_padua_curve(n, t):
8+
return np.vstack([-np.cos((n + 1) * t), -np.cos(n * t)])
9+
10+
11+
def _second_padua_curve(n, t):
12+
return np.vstack([-np.cos(n * t), -np.cos((n + 1) * t)])
13+
14+
15+
def _third_padua_curve(n, t):
16+
return np.vstack([np.cos((n + 1) * t), np.cos(n * t)])
17+
18+
19+
def _fourth_padua_curve(n, t):
20+
return np.vstack([np.cos(n * t), np.cos((n + 1) * t)])
21+
22+
23+
def plot_padua_nodes(alpha, beta, order, family):
24+
if family == "first":
25+
curve_fn = _first_padua_curve
26+
elif family == "second":
27+
curve_fn = _second_padua_curve
28+
elif family == "third":
29+
curve_fn = _third_padua_curve
30+
elif family == "fourth":
31+
curve_fn = _fourth_padua_curve
32+
33+
t = np.linspace(0, np.pi, 512)
34+
curve = curve_fn(order, t)
35+
nodes = mp.padua_jacobi_nodes(alpha, beta, order, family)
36+
assert nodes.shape[1] == ((order + 1) * (order + 2) // 2)
37+
38+
fig = plt.figure()
39+
ax = fig.gca()
40+
ax.grid()
41+
42+
ax.plot(curve[0], curve[1])
43+
for i, xi in enumerate(nodes.T):
44+
ax.plot(nodes[0], nodes[1], "ko", markersize=8)
45+
ax.text(*xi, str(i), color="k", fontsize=24, fontweight="bold")
46+
47+
ax.set_xlim([-1, 1])
48+
ax.set_ylim([-1, 1])
49+
ax.set_aspect("equal")
50+
fig.savefig(f"padua_nodes_order_{order:02d}_family_{family}")
51+
plt.show()
52+
53+
54+
if __name__ == "__main__":
55+
import argparse
56+
57+
parser = argparse.ArgumentParser()
58+
parser.add_argument("--order", type=int, default=5)
59+
parser.add_argument("--family", default="first",
60+
choices=["first", "second", "third", "fourth"])
61+
parser.add_argument("--alpha", type=float, default=-0.5)
62+
parser.add_argument("--beta", type=float, default=-0.5)
63+
args = parser.parse_args()
64+
65+
plot_padua_nodes(args.alpha, args.beta, args.order, args.family)

modepy/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
THE SOFTWARE.
2222
"""
2323

24+
from pytools import MovedFunctionDeprecationWrapper
2425

2526
from modepy.matrices import (
2627
diff_matrices,
@@ -58,6 +59,8 @@
5859
legendre_gauss_lobatto_tensor_product_nodes,
5960
legendre_gauss_tensor_product_nodes,
6061
node_tuples_for_space,
62+
padua_jacobi_nodes,
63+
padua_nodes,
6164
random_nodes_for_shape,
6265
tensor_product_nodes,
6366
warp_and_blend_nodes,
@@ -164,6 +167,8 @@
164167
"nodal_quad_mass_matrix_for_face",
165168
"node_tuples_for_space",
166169
"orthonormal_basis_for_space",
170+
"padua_jacobi_nodes",
171+
"padua_nodes",
167172
"quadrature_for_space",
168173
"random_nodes_for_shape",
169174
"resampling_matrix",
@@ -178,7 +183,5 @@
178183
"warp_and_blend_nodes",
179184
]
180185

181-
from pytools import MovedFunctionDeprecationWrapper
182-
183186

184187
get_warp_and_blend_nodes = MovedFunctionDeprecationWrapper(warp_and_blend_nodes)

modepy/nodes.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
.. autofunction:: tensor_product_nodes
2929
.. autofunction:: legendre_gauss_tensor_product_nodes
3030
.. autofunction:: legendre_gauss_lobatto_tensor_product_nodes
31+
32+
.. autofunction:: padua_jacobi_nodes
33+
.. autofunction:: padua_nodes
3134
"""
3235

3336
__copyright__ = "Copyright (C) 2009, 2010, 2013 Andreas Kloeckner, " \
@@ -425,6 +428,103 @@ def legendre_gauss_lobatto_tensor_product_nodes(dims: int, n: int) -> np.ndarray
425428
# }}}
426429

427430

431+
# {{{ Padua nodes
432+
433+
def _make_padua_grid_nodes(
434+
alpha: float, beta: float, order: int
435+
) -> Tuple[np.ndarray, np.ndarray]:
436+
from modepy.quadrature.jacobi_gauss import jacobi_gauss_lobatto_nodes
437+
mu = jacobi_gauss_lobatto_nodes(alpha, beta, order)
438+
eta = jacobi_gauss_lobatto_nodes(alpha, beta, order + 1)
439+
440+
return mu, eta
441+
442+
443+
def _make_padua_jacobi_nodes(
444+
mu: np.ndarray, eta: np.ndarray, odd_or_even: int
445+
) -> np.ndarray:
446+
nodes = np.stack(np.meshgrid(mu, eta, indexing="ij"))
447+
indices = np.sum(
448+
np.meshgrid(np.arange(mu.size), np.arange(eta.size), indexing="ij"),
449+
axis=0)
450+
451+
return nodes[:, indices % 2 == odd_or_even].reshape(2, -1)
452+
453+
454+
def _first_padua_jacobi_nodes(alpha: float, beta: float, order: int) -> np.ndarray:
455+
mu, eta = _make_padua_grid_nodes(alpha, beta, order)
456+
return _make_padua_jacobi_nodes(mu, eta, 0)
457+
458+
459+
def _second_padua_jacobi_nodes(alpha: float, beta: float, order: int) -> np.ndarray:
460+
# NOTE: these are just "rotated" by pi/2 from the first family
461+
mu, eta = _make_padua_grid_nodes(alpha, beta, order)
462+
return _make_padua_jacobi_nodes(eta, mu, 0)
463+
464+
465+
def _third_padua_jacobi_nodes(alpha: float, beta: float, order: int) -> np.ndarray:
466+
# NOTE: these are just "rotated" by pi from the first family
467+
mu, eta = _make_padua_grid_nodes(alpha, beta, order)
468+
return _make_padua_jacobi_nodes(mu, eta, 1)
469+
470+
471+
def _fourth_padua_jacobi_nodes(alpha: float, beta: float, order: int) -> np.ndarray:
472+
# NOTE: these are just "rotated" by 2 pi/3 from the first family
473+
mu, eta = _make_padua_grid_nodes(alpha, beta, order)
474+
return _make_padua_jacobi_nodes(eta, mu, 1)
475+
476+
477+
def padua_jacobi_nodes(
478+
alpha: float, beta: float, order: int,
479+
family: str = "first") -> np.ndarray:
480+
r"""Generalized Padua-Jacobi nodes.
481+
482+
The Padua-Jacobi nodes are constructed from an interlaced grid of
483+
standard Jacobi-Gauss-Lobatto nodes, making use of
484+
:func:`~modepy.quadrature.jacobi_gauss.jacobi_gauss_lobatto_nodes`.
485+
This construction is detailed in
486+
487+
M. Briani, A. Sommariva, M. Vianello,
488+
*Computing Fekete and Lebesgue Points: Simplex, Square, Disk*,
489+
Journal of Computational and Applied Mathematics, Vol. 236,
490+
pp. 2477--2486, 2012, `DOI <http://dx.doi.org/10.1016/j.cam.2011.12.006>`_.
491+
492+
The values of the parameters :math:`(\alpha, \beta)` can have an effect
493+
on the Lebesgue constant of the resulting set, but all of them have
494+
optimal growth of :math:`\mathcal{O}(\log^2 n)`.
495+
496+
The Padua-Jacobi nodes are not rotationally symmetric.
497+
498+
:arg family: one of the four families of Padua-Jacobi nodes. The three
499+
additional families are :math:`90^\circ` rotations of the first one.
500+
"""
501+
502+
if family == "first":
503+
nodes = _first_padua_jacobi_nodes(alpha, beta, order)
504+
elif family == "second":
505+
nodes = _second_padua_jacobi_nodes(alpha, beta, order)
506+
elif family == "third":
507+
nodes = _third_padua_jacobi_nodes(alpha, beta, order)
508+
elif family == "fourth":
509+
nodes = _fourth_padua_jacobi_nodes(alpha, beta, order)
510+
else:
511+
raise ValueError(f"unknown Padua-Jacobi node family: '{family}'")
512+
513+
return nodes
514+
515+
516+
def padua_nodes(order: int, family: str = "first") -> np.ndarray:
517+
r"""Standard Padua nodes.
518+
519+
Padua nodes are Padua-Jacobi nodes with :math:`\alpha = \beta = -0.5`,
520+
i.e. they are constructed from the Chebyshev-Gauss-Lobatto nodes.
521+
"""
522+
523+
return padua_jacobi_nodes(-0.5, -0.5, order, family=family)
524+
525+
# }}}
526+
527+
428528
# {{{ space-based interface
429529

430530
@singledispatch

0 commit comments

Comments
 (0)