|
28 | 28 | .. autofunction:: tensor_product_nodes |
29 | 29 | .. autofunction:: legendre_gauss_tensor_product_nodes |
30 | 30 | .. autofunction:: legendre_gauss_lobatto_tensor_product_nodes |
| 31 | +
|
| 32 | +.. autofunction:: padua_jacobi_nodes |
| 33 | +.. autofunction:: padua_nodes |
31 | 34 | """ |
32 | 35 |
|
33 | 36 | __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 |
425 | 428 | # }}} |
426 | 429 |
|
427 | 430 |
|
| 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 | + |
428 | 528 | # {{{ space-based interface |
429 | 529 |
|
430 | 530 | @singledispatch |
|
0 commit comments