|
52 | 52 | * ``u.diff[:]`` – velocity on :math:`N` interior grid points. |
53 | 53 | * ``u.alg[0]`` – pressure gradient :math:`G` (Lagrange multiplier). |
54 | 54 |
|
55 | | -The compatible sweeper is |
56 | | -:class:`~pySDC.projects.DAE.sweepers.semiImplicitDAE.SemiImplicitDAE`. |
| 55 | +Two sweepers are supported: |
| 56 | +
|
| 57 | +* :class:`~pySDC.projects.DAE.sweepers.semiImplicitDAE.SemiImplicitDAE` |
| 58 | + (U-formulation): velocity order :math:`M+1`, pressure order :math:`M` |
| 59 | + (or increasing toward :math:`M+1` with lifting). |
| 60 | +
|
| 61 | +* :class:`~pySDC.projects.DAE.sweepers.fullyImplicitDAE.FullyImplicitDAE` |
| 62 | + (collocation-consistent): treats :math:`G` as a differential variable |
| 63 | + updated by the same quadrature as the velocity, restoring the full |
| 64 | + collocation order :math:`2M-1` for both. |
57 | 65 |
|
58 | 66 | Without constraint lifting (class :class:`stokes_poiseuille_1d_fd`), the |
59 | 67 | constraint :math:`B\mathbf{u} = q(t)` has a time-dependent right-hand side, |
60 | 68 | which causes order reduction in :math:`G` to order :math:`M` (= 3 for |
61 | | -3 RADAU-RIGHT nodes). |
| 69 | +3 RADAU-RIGHT nodes) when using :class:`SemiImplicitDAE`. |
62 | 70 |
|
63 | 71 | Constraint lifting (class :class:`stokes_poiseuille_1d_fd_lift`) |
64 | 72 | ----------------------------------------------------------------- |
|
95 | 103 | ------- |
96 | 104 | stokes_poiseuille_1d_fd |
97 | 105 | No lifting; constraint :math:`B\mathbf{u} = q(t)` (time-dependent). |
98 | | - Pressure converges at order :math:`M`. |
| 106 | + Compatible with **SemiImplicitDAE** (pressure order :math:`M`) and |
| 107 | + **FullyImplicitDAE** (pressure order :math:`2M-1`). |
99 | 108 |
|
100 | 109 | stokes_poiseuille_1d_fd_lift |
101 | 110 | Constraint lifting; homogeneous :math:`B\tilde{\mathbf{v}} = 0`. |
102 | | - Expected to restore full order :math:`2M-1` in the pressure. |
| 111 | + Compatible with both sweepers; FullyImplicitDAE restores full |
| 112 | + :math:`2M-1` order cleanly. |
103 | 113 | """ |
104 | 114 |
|
105 | 115 | import numpy as np |
@@ -307,6 +317,72 @@ def _schur_solve(self, rhs_eff, v_approx, factor, constraint_rhs): |
307 | 317 | U = w + G_new * v0 |
308 | 318 | return U, float(G_new) |
309 | 319 |
|
| 320 | + def _schur_solve_full_implicit(self, rhs_eff, v_approx, factor, constraint_rhs): |
| 321 | + r""" |
| 322 | + Schur-complement saddle-point solve for use with |
| 323 | + :class:`~pySDC.projects.DAE.sweepers.fullyImplicitDAE.FullyImplicitDAE`. |
| 324 | +
|
| 325 | + Finds :math:`(\mathbf{U}, G')` satisfying |
| 326 | +
|
| 327 | + .. math:: |
| 328 | +
|
| 329 | + (I - \alpha\nu A)\,\mathbf{U} - \alpha G'\,\mathbf{1} |
| 330 | + = \mathbf{r}_\text{eff}, |
| 331 | +
|
| 332 | + .. math:: |
| 333 | +
|
| 334 | + B(\mathbf{v}_\text{approx} + \alpha\,\mathbf{U}) = c, |
| 335 | +
|
| 336 | + where :math:`G' = \mathrm{d}G/\mathrm{d}t` is the **derivative** of |
| 337 | + the pressure gradient and :math:`c` is ``constraint_rhs``. |
| 338 | +
|
| 339 | + Compared to :meth:`_schur_solve`, here ``rhs_eff`` must already |
| 340 | + include the :math:`G_0\,\mathbf{1}` term (the current pressure |
| 341 | + estimate from ``u_approx.alg[0]``), and the denominator carries an |
| 342 | + extra :math:`\alpha` factor: |
| 343 | +
|
| 344 | + .. math:: |
| 345 | +
|
| 346 | + G' = \frac{c - B\mathbf{v} - \alpha B\mathbf{w}} |
| 347 | + {\alpha^2 B\mathbf{v}_0}, |
| 348 | +
|
| 349 | + Parameters |
| 350 | + ---------- |
| 351 | + rhs_eff : numpy.ndarray |
| 352 | + Effective velocity RHS :math:`\nu A\mathbf{v} + G_0\mathbf{1} |
| 353 | + + \mathbf{f}_\text{net}`. |
| 354 | + v_approx : numpy.ndarray |
| 355 | + Current velocity approximation at the node. |
| 356 | + factor : float |
| 357 | + Implicit prefactor :math:`\alpha = \Delta t\,\tilde{q}_{mm}`. |
| 358 | + constraint_rhs : float |
| 359 | + RHS of the constraint :math:`c` (``q(t)`` standard; ``0`` lifted). |
| 360 | +
|
| 361 | + Returns |
| 362 | + ------- |
| 363 | + U : numpy.ndarray |
| 364 | + Velocity derivative at the node. |
| 365 | + G_prime : float |
| 366 | + Derivative of the pressure gradient :math:`G'`. |
| 367 | + """ |
| 368 | + M = sp.eye(self.nvars, format='csc') - factor * self.A |
| 369 | + w = spsolve(M, rhs_eff) |
| 370 | + v0 = spsolve(M, self.ones) |
| 371 | + |
| 372 | + Bw = self._B_dot(w) |
| 373 | + Bv0 = self._B_dot(v0) |
| 374 | + Bv = self._B_dot(v_approx) |
| 375 | + # Extra factor of alpha in denominator vs _schur_solve |
| 376 | + denom = factor**2 * Bv0 |
| 377 | + assert abs(denom) > 0.0, ( |
| 378 | + f'_schur_solve_full_implicit: denominator factor²·B·v₀ = {denom:.3e} is zero; ' |
| 379 | + f'factor = {factor}, B·v₀ = {Bv0:.3e}' |
| 380 | + ) |
| 381 | + G_prime = (constraint_rhs - Bv - factor * Bw) / denom |
| 382 | + |
| 383 | + U = w + factor * G_prime * v0 |
| 384 | + return U, float(G_prime) |
| 385 | + |
310 | 386 |
|
311 | 387 | # --------------------------------------------------------------------------- |
312 | 388 | # Case 1: No lifting – constraint B·u = q(t) |
@@ -617,3 +693,183 @@ def du_exact(self, t): |
617 | 693 | me.diff[:] = np.sin(np.pi * self.xvalues) * np.cos(t) - self._lift_dot(t) |
618 | 694 | me.alg[0] = -np.sin(t) |
619 | 695 | return me |
| 696 | + |
| 697 | + |
| 698 | +# --------------------------------------------------------------------------- |
| 699 | +# Case 3: FullyImplicitDAE – no lifting |
| 700 | +# --------------------------------------------------------------------------- |
| 701 | + |
| 702 | +class stokes_poiseuille_1d_fd_full(stokes_poiseuille_1d_fd): |
| 703 | + r""" |
| 704 | + 1-D Stokes/Poiseuille DAE **without** constraint lifting, compatible with |
| 705 | + :class:`~pySDC.projects.DAE.sweepers.fullyImplicitDAE.FullyImplicitDAE`. |
| 706 | +
|
| 707 | + The key difference from :class:`stokes_poiseuille_1d_fd` is in |
| 708 | + ``solve_system``: here the unknown is the full derivative |
| 709 | + :math:`(\mathbf{U}, G') = (\mathbf{u}', G')`, i.e. ``me.alg[0]`` is the |
| 710 | + *derivative* of the pressure gradient. The pressure gradient at the |
| 711 | + node is then recovered by quadrature: |
| 712 | +
|
| 713 | + .. math:: |
| 714 | +
|
| 715 | + G_m = G_0 + \Delta t \sum_{j=1}^{M} Q_{mj}\,G'_j, |
| 716 | +
|
| 717 | + which gives the pressure the same collocation structure as |
| 718 | + the velocity (both recovered via the quadrature formula). |
| 719 | +
|
| 720 | + .. note:: |
| 721 | +
|
| 722 | + In practice, :class:`FullyImplicitDAE` and |
| 723 | + :class:`~.semiImplicitDAE.SemiImplicitDAE` converge to the |
| 724 | + **same collocation fixed point** for this index-1 DAE: the |
| 725 | + :math:`\mathcal{O}(\Delta t^M)` errors in the stage pressure |
| 726 | + values break the :math:`2M-1` superconvergence, and both sweepers |
| 727 | + achieve velocity order :math:`M+1` and pressure order :math:`M` |
| 728 | + (standard) or increasing toward :math:`M+1` (lifted). The |
| 729 | + :class:`FullyImplicitDAE` formulation is included for completeness |
| 730 | + and pedagogical comparison. |
| 731 | +
|
| 732 | + The Schur-complement solve for the unknown :math:`(\mathbf{U}, G')`: |
| 733 | +
|
| 734 | + .. math:: |
| 735 | +
|
| 736 | + (I - \alpha\nu A)\,\mathbf{U} - \alpha G'\,\mathbf{1} |
| 737 | + = \nu A\mathbf{v} + G_0\mathbf{1} + \mathbf{f}(t), |
| 738 | +
|
| 739 | + .. math:: |
| 740 | +
|
| 741 | + B(\mathbf{v} + \alpha\,\mathbf{U}) = q(t), |
| 742 | +
|
| 743 | + yields |
| 744 | +
|
| 745 | + .. math:: |
| 746 | +
|
| 747 | + G' = \frac{q(t) - B\mathbf{v} - \alpha B\mathbf{w}}{\alpha^2 B\mathbf{v}_0}, |
| 748 | + \quad |
| 749 | + \mathbf{U} = \mathbf{w} + \alpha G'\,\mathbf{v}_0, |
| 750 | +
|
| 751 | + where :math:`\mathbf{w} = (I-\alpha\nu A)^{-1}(\nu A\mathbf{v} |
| 752 | + + G_0\mathbf{1} + \mathbf{f})` and |
| 753 | + :math:`\mathbf{v}_0 = (I-\alpha\nu A)^{-1}\mathbf{1}`. |
| 754 | +
|
| 755 | + Parameters |
| 756 | + ---------- |
| 757 | + nvars : int |
| 758 | + Number of interior grid points (default 127; must be ≥ 5). |
| 759 | + nu : float |
| 760 | + Kinematic viscosity (default 1.0). |
| 761 | + newton_tol : float |
| 762 | + Unused; passed to base class (default 1e-10). |
| 763 | + """ |
| 764 | + |
| 765 | + def solve_system(self, impl_sys, u_approx, factor, u0, t): |
| 766 | + r""" |
| 767 | + Schur-complement solve for |
| 768 | + :class:`~pySDC.projects.DAE.sweepers.fullyImplicitDAE.FullyImplicitDAE`. |
| 769 | +
|
| 770 | + Returns the **derivative** :math:`(\mathbf{U}, G')`. |
| 771 | + ``me.alg[0]`` = :math:`G'` (pressure-gradient time derivative). |
| 772 | +
|
| 773 | + Parameters |
| 774 | + ---------- |
| 775 | + impl_sys : callable |
| 776 | + Unused; system solved directly. |
| 777 | + u_approx : MeshDAE |
| 778 | + Approximation :math:`(\mathbf{v}, G_0)` at the current node. |
| 779 | + factor : float |
| 780 | + Implicit prefactor :math:`\alpha`. |
| 781 | + u0 : MeshDAE |
| 782 | + Unused (direct solver). |
| 783 | + t : float |
| 784 | + Current time. |
| 785 | +
|
| 786 | + Returns |
| 787 | + ------- |
| 788 | + me : MeshDAE |
| 789 | + ``me.diff[:]`` = :math:`\mathbf{U}`, |
| 790 | + ``me.alg[0]`` = :math:`G'`. |
| 791 | + """ |
| 792 | + me = self.dtype_u(self.init, val=0.0) |
| 793 | + v_approx = np.asarray(u_approx.diff).copy() |
| 794 | + G0 = float(u_approx.alg[0]) |
| 795 | + |
| 796 | + # rhs_eff includes current G0: FullyImplicitDAE treats G as a differential |
| 797 | + # variable (G = G0 + factor*G'), so G0 enters the RHS (unlike SemiImplicitDAE |
| 798 | + # where u_approx.alg = 0 and G is purely algebraic in the local solve). |
| 799 | + rhs_eff = self.A.dot(v_approx) + G0 * self.ones + self._forcing(t) |
| 800 | + U, G_prime = self._schur_solve_full_implicit(rhs_eff, v_approx, factor, self._q(t)) |
| 801 | + |
| 802 | + me.diff[:] = U |
| 803 | + me.alg[0] = G_prime |
| 804 | + return me |
| 805 | + |
| 806 | + |
| 807 | +# --------------------------------------------------------------------------- |
| 808 | +# Case 4: FullyImplicitDAE – with lifting |
| 809 | +# --------------------------------------------------------------------------- |
| 810 | + |
| 811 | +class stokes_poiseuille_1d_fd_lift_full(stokes_poiseuille_1d_fd_lift): |
| 812 | + r""" |
| 813 | + 1-D Stokes/Poiseuille DAE **with** constraint lifting, compatible with |
| 814 | + :class:`~pySDC.projects.DAE.sweepers.fullyImplicitDAE.FullyImplicitDAE`. |
| 815 | +
|
| 816 | + Combines the homogeneous constraint :math:`B\tilde{\mathbf{v}} = 0` |
| 817 | + from :class:`stokes_poiseuille_1d_fd_lift` with the |
| 818 | + :class:`FullyImplicitDAE`-consistent ``solve_system`` from |
| 819 | + :class:`stokes_poiseuille_1d_fd_full`. |
| 820 | +
|
| 821 | + .. note:: |
| 822 | +
|
| 823 | + :class:`FullyImplicitDAE` and :class:`SemiImplicitDAE` converge |
| 824 | + to the same collocation fixed point for this DAE: velocity order |
| 825 | + :math:`M+1` and pressure order increasing toward :math:`M+1` |
| 826 | + (same as :class:`stokes_poiseuille_1d_fd_lift`). This class is |
| 827 | + provided for pedagogical comparison. |
| 828 | +
|
| 829 | + Parameters |
| 830 | + ---------- |
| 831 | + nvars : int |
| 832 | + Number of interior grid points (default 127; must be ≥ 5). |
| 833 | + nu : float |
| 834 | + Kinematic viscosity (default 1.0). |
| 835 | + newton_tol : float |
| 836 | + Unused; passed to base class (default 1e-10). |
| 837 | + """ |
| 838 | + |
| 839 | + def solve_system(self, impl_sys, v_approx_mesh, factor, u0, t): |
| 840 | + r""" |
| 841 | + Schur-complement solve for |
| 842 | + :class:`~pySDC.projects.DAE.sweepers.fullyImplicitDAE.FullyImplicitDAE` |
| 843 | + with the **homogeneous** constraint :math:`B\tilde{\mathbf{v}} = 0`. |
| 844 | +
|
| 845 | + Returns :math:`(\tilde{\mathbf{U}}, G')`. |
| 846 | +
|
| 847 | + Parameters |
| 848 | + ---------- |
| 849 | + impl_sys : callable |
| 850 | + Unused; system solved directly. |
| 851 | + v_approx_mesh : MeshDAE |
| 852 | + Approximation :math:`(\tilde{\mathbf{v}}, G_0)` at the node. |
| 853 | + factor : float |
| 854 | + Implicit prefactor :math:`\alpha`. |
| 855 | + u0 : MeshDAE |
| 856 | + Unused (direct solver). |
| 857 | + t : float |
| 858 | + Current time. |
| 859 | +
|
| 860 | + Returns |
| 861 | + ------- |
| 862 | + me : MeshDAE |
| 863 | + ``me.diff[:]`` = :math:`\tilde{\mathbf{U}}`, |
| 864 | + ``me.alg[0]`` = :math:`G'`. |
| 865 | + """ |
| 866 | + me = self.dtype_u(self.init, val=0.0) |
| 867 | + vv = np.asarray(v_approx_mesh.diff).copy() |
| 868 | + G0 = float(v_approx_mesh.alg[0]) |
| 869 | + |
| 870 | + rhs_eff = self.A.dot(vv) + G0 * self.ones + self._net_forcing(t) |
| 871 | + U, G_prime = self._schur_solve_full_implicit(rhs_eff, vv, factor, 0.0) |
| 872 | + |
| 873 | + me.diff[:] = U |
| 874 | + me.alg[0] = G_prime |
| 875 | + return me |
0 commit comments