Skip to content

Commit 6d21c46

Browse files
Copilotpancetta
andauthored
Add FullyImplicitDAE-compatible Stokes classes; update run_convergence.py with 4-variant comparison
Co-authored-by: pancetta <7158893+pancetta@users.noreply.github.com> Agent-Logs-Url: https://github.com/Parallel-in-Time/pySDC/sessions/caaa10d2-854c-4d03-8f8f-986aa9a48220
1 parent 289e581 commit 6d21c46

2 files changed

Lines changed: 339 additions & 76 deletions

File tree

pySDC/playgrounds/Stokes_Poiseuille_1D_FD/Stokes_Poiseuille_1D_FD.py

Lines changed: 261 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,21 @@
5252
* ``u.diff[:]`` – velocity on :math:`N` interior grid points.
5353
* ``u.alg[0]`` – pressure gradient :math:`G` (Lagrange multiplier).
5454
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.
5765
5866
Without constraint lifting (class :class:`stokes_poiseuille_1d_fd`), the
5967
constraint :math:`B\mathbf{u} = q(t)` has a time-dependent right-hand side,
6068
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`.
6270
6371
Constraint lifting (class :class:`stokes_poiseuille_1d_fd_lift`)
6472
-----------------------------------------------------------------
@@ -95,11 +103,13 @@
95103
-------
96104
stokes_poiseuille_1d_fd
97105
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`).
99108
100109
stokes_poiseuille_1d_fd_lift
101110
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.
103113
"""
104114

105115
import numpy as np
@@ -307,6 +317,72 @@ def _schur_solve(self, rhs_eff, v_approx, factor, constraint_rhs):
307317
U = w + G_new * v0
308318
return U, float(G_new)
309319

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+
310386

311387
# ---------------------------------------------------------------------------
312388
# Case 1: No lifting – constraint B·u = q(t)
@@ -617,3 +693,183 @@ def du_exact(self, t):
617693
me.diff[:] = np.sin(np.pi * self.xvalues) * np.cos(t) - self._lift_dot(t)
618694
me.alg[0] = -np.sin(t)
619695
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

Comments
 (0)