|
27 | 27 |
|
28 | 28 | .. autofunction:: tensor_product_nodes |
29 | 29 | .. autofunction:: legendre_gauss_lobatto_tensor_product_nodes |
| 30 | +
|
| 31 | +.. autofunction:: padua_jacobi_nodes |
| 32 | +.. autofunction:: padua_nodes |
30 | 33 | """ |
31 | 34 |
|
32 | 35 | __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 |
397 | 400 | # }}} |
398 | 401 |
|
399 | 402 |
|
| 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 | + |
400 | 500 | # {{{ space-based interface |
401 | 501 |
|
402 | 502 | @singledispatch |
|
0 commit comments