Skip to content

Commit 686c29c

Browse files
committed
add padua nodes
1 parent 7b8174c commit 686c29c

3 files changed

Lines changed: 169 additions & 4 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: 4 additions & 4 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_matrix_permutation, differentiation_matrices, inverse_mass_matrix,
@@ -37,7 +38,8 @@
3738
from modepy.nodes import (
3839
edge_clustered_nodes_for_space, equidistant_nodes, equispaced_nodes_for_space,
3940
legendre_gauss_lobatto_tensor_product_nodes, node_tuples_for_space,
40-
random_nodes_for_shape, tensor_product_nodes, warp_and_blend_nodes)
41+
padua_jacobi_nodes, padua_nodes, random_nodes_for_shape, tensor_product_nodes,
42+
warp_and_blend_nodes)
4143
from modepy.quadrature import (
4244
LegendreGaussTensorProductQuadrature, Quadrature, QuadratureRuleUnavailable,
4345
TensorProductQuadrature, ZeroDimensionalQuadrature, quadrature_for_space)
@@ -82,6 +84,7 @@
8284

8385
"equidistant_nodes", "warp_and_blend_nodes",
8486
"tensor_product_nodes", "legendre_gauss_lobatto_tensor_product_nodes",
87+
"padua_jacobi_nodes", "padua_nodes",
8588
"node_tuples_for_space",
8689
"edge_clustered_nodes_for_space", "equispaced_nodes_for_space",
8790
"random_nodes_for_shape",
@@ -107,9 +110,6 @@
107110
"WitherdenVincentQuadrature",
108111
]
109112

110-
from pytools import MovedFunctionDeprecationWrapper
111-
112-
113113
get_simplex_onb = MovedFunctionDeprecationWrapper(simplex_onb)
114114
get_grad_simplex_onb = MovedFunctionDeprecationWrapper(grad_simplex_onb)
115115
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
@@ -27,6 +27,9 @@
2727
2828
.. autofunction:: tensor_product_nodes
2929
.. autofunction:: legendre_gauss_lobatto_tensor_product_nodes
30+
31+
.. autofunction:: padua_jacobi_nodes
32+
.. autofunction:: padua_nodes
3033
"""
3134

3235
__copyright__ = "Copyright (C) 2009, 2010, 2013 Andreas Kloeckner, " \
@@ -397,6 +400,103 @@ def legendre_gauss_lobatto_tensor_product_nodes(dims: int, n: int) -> np.ndarray
397400
# }}}
398401

399402

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

402502
@singledispatch

0 commit comments

Comments
 (0)