Skip to content

Commit 870f342

Browse files
Copilotpancetta
andauthored
Add differentiated-constraint classes achieving full 2M-1 RADAU order for velocity and pressure
Co-authored-by: pancetta <7158893+pancetta@users.noreply.github.com> Agent-Logs-Url: https://github.com/Parallel-in-Time/pySDC/sessions/93f40bfb-629c-47d8-8e13-7f5d9f451f22
1 parent 6d21c46 commit 870f342

2 files changed

Lines changed: 428 additions & 138 deletions

File tree

pySDC/playgrounds/Stokes_Poiseuille_1D_FD/Stokes_Poiseuille_1D_FD.py

Lines changed: 327 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,15 @@
5656
5757
* :class:`~pySDC.projects.DAE.sweepers.semiImplicitDAE.SemiImplicitDAE`
5858
(U-formulation): velocity order :math:`M+1`, pressure order :math:`M`
59-
(or increasing toward :math:`M+1` with lifting).
59+
(standard), approaching :math:`M+1` (lifted), or full :math:`2M-1`
60+
(differentiated constraint).
6061
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.
62+
Three constraint treatments are provided — see Classes section below.
6563
6664
Without constraint lifting (class :class:`stokes_poiseuille_1d_fd`), the
6765
constraint :math:`B\mathbf{u} = q(t)` has a time-dependent right-hand side,
6866
which causes order reduction in :math:`G` to order :math:`M` (= 3 for
69-
3 RADAU-RIGHT nodes) when using :class:`SemiImplicitDAE`.
67+
3 RADAU-RIGHT nodes).
7068
7169
Constraint lifting (class :class:`stokes_poiseuille_1d_fd_lift`)
7270
-----------------------------------------------------------------
@@ -95,21 +93,47 @@
9593
- \dot{\mathbf{u}}_\ell(t)\bigr]
9694
+ G\,\mathbf{1}.
9795
98-
Because the constraint :math:`B\tilde{\mathbf{v}} = 0` no longer contains a
99-
time-dependent right-hand side, the Lagrange multiplier :math:`G` is expected
100-
to converge at the full collocation order :math:`2M-1`, matching the velocity.
96+
Because the constraint :math:`B\tilde{\mathbf{v}} = 0` is autonomous, the
97+
order reduction in :math:`G` is reduced (pressure approaches :math:`M+1`)
98+
but not fully eliminated in the SDC context.
99+
100+
Differentiated constraint (class :class:`stokes_poiseuille_1d_fd_diffconstr`)
101+
------------------------------------------------------------------------------
102+
Instead of enforcing the algebraic constraint
103+
:math:`B\mathbf{u}_m = q(\tau_m)` at each SDC stage, enforce its time
104+
derivative :math:`B\mathbf{U}_m = q'(\tau_m)`. The Schur formula becomes
105+
106+
.. math::
107+
108+
G_m = \frac{q'(\tau_m) - B\mathbf{w}}{B\mathbf{v}_0},
109+
110+
and the stage pressure error reduces from :math:`\mathcal{O}(\Delta t^M)`
111+
to :math:`\mathcal{O}(\Delta t^{M+1})`, restoring the full RADAU
112+
collocation order :math:`2M-1` for **both** velocity and pressure.
101113
102114
Classes
103115
-------
104116
stokes_poiseuille_1d_fd
105-
No lifting; constraint :math:`B\mathbf{u} = q(t)` (time-dependent).
106-
Compatible with **SemiImplicitDAE** (pressure order :math:`M`) and
107-
**FullyImplicitDAE** (pressure order :math:`2M-1`).
117+
No lifting; algebraic constraint :math:`B\mathbf{u} = q(t)`.
118+
SemiImplicitDAE: vel :math:`M+1`, pres :math:`M`.
108119
109120
stokes_poiseuille_1d_fd_lift
110121
Constraint lifting; homogeneous :math:`B\tilde{\mathbf{v}} = 0`.
111-
Compatible with both sweepers; FullyImplicitDAE restores full
112-
:math:`2M-1` order cleanly.
122+
SemiImplicitDAE: vel :math:`M+1`, pres approaching :math:`M+1`.
123+
124+
stokes_poiseuille_1d_fd_diffconstr
125+
Differentiated constraint :math:`B\mathbf{U}_m = q'(\tau_m)`.
126+
SemiImplicitDAE: **vel and pres both at** :math:`2M-1`.
127+
128+
stokes_poiseuille_1d_fd_lift_diffconstr
129+
Lifting + differentiated :math:`B\tilde{\mathbf{U}} = 0`.
130+
Equivalent to :class:`stokes_poiseuille_1d_fd_lift` at the fixed point.
131+
132+
stokes_poiseuille_1d_fd_full
133+
No lifting, FullyImplicitDAE (same fixed point as standard).
134+
135+
stokes_poiseuille_1d_fd_lift_full
136+
Lifting, FullyImplicitDAE (same fixed point as lifted).
113137
"""
114138

115139
import numpy as np
@@ -247,6 +271,12 @@ def _q(self, t):
247271
"""
248272
return self.C_B * np.sin(t)
249273

274+
def _q_prime(self, t):
275+
r"""
276+
Time derivative of the flow-rate RHS: :math:`q'(t) = C_B\,\cos(t)`.
277+
"""
278+
return self.C_B * np.cos(t)
279+
250280
def _forcing(self, t):
251281
r"""
252282
Manufactured forcing consistent with :math:`u_\text{ex}` and
@@ -383,6 +413,68 @@ def _schur_solve_full_implicit(self, rhs_eff, v_approx, factor, constraint_rhs):
383413
U = w + factor * G_prime * v0
384414
return U, float(G_prime)
385415

416+
def _schur_solve_diffconstr(self, rhs_eff, factor, q_prime_val):
417+
r"""
418+
Schur-complement solve using the **differentiated constraint**
419+
:math:`B\mathbf{U} = q'(t)`.
420+
421+
Instead of enforcing the original algebraic constraint
422+
:math:`B(\mathbf{v} + \alpha\mathbf{U}) = q(t)`, this method uses
423+
the differentiated form :math:`B\mathbf{U} = q'(t)`. The key
424+
formula is
425+
426+
.. math::
427+
428+
G = \frac{q'(t) - B\mathbf{w}}{B\mathbf{v}_0},
429+
430+
where
431+
:math:`\mathbf{w} = (I - \alpha\nu A)^{-1}\mathbf{r}_\text{eff}` and
432+
:math:`\mathbf{v}_0 = (I - \alpha\nu A)^{-1}\mathbf{1}`.
433+
434+
**Why this gives higher-order pressure**: At the SDC fixed point the
435+
stage velocities satisfy :math:`\mathbf{u}_m - \mathbf{u}(\tau_m) =
436+
\mathcal{O}(\Delta t^{M+1})` (collocation accuracy). The
437+
differentiated-constraint error propagates as
438+
439+
.. math::
440+
441+
e_{G_m} = G_m - G(\tau_m) = -\frac{B A\,e_{\mathbf{u}_m}}{s}
442+
= \mathcal{O}(\Delta t^{M+1}),
443+
444+
whereas the original algebraic constraint gives only
445+
:math:`\mathcal{O}(\Delta t^M)`.
446+
447+
Parameters
448+
----------
449+
rhs_eff : numpy.ndarray
450+
Effective velocity RHS
451+
:math:`\nu A\mathbf{v}_\text{approx} + \mathbf{f}_\text{net}(t)`.
452+
factor : float
453+
Implicit prefactor :math:`\alpha = \Delta t\,\tilde{q}_{mm}`.
454+
q_prime_val : float
455+
Value of :math:`q'(t)` at the current stage time.
456+
457+
Returns
458+
-------
459+
U : numpy.ndarray
460+
Velocity derivative at the node.
461+
G_new : float
462+
Pressure gradient satisfying :math:`B\mathbf{U} = q'(t)`.
463+
"""
464+
M_mat = sp.eye(self.nvars, format='csc') - factor * self.A
465+
w = spsolve(M_mat, rhs_eff)
466+
v0 = spsolve(M_mat, self.ones)
467+
468+
Bw = self._B_dot(w)
469+
Bv0 = self._B_dot(v0)
470+
assert abs(Bv0) > 0.0, (
471+
f'_schur_solve_diffconstr: B·v₀ = {Bv0:.3e} is zero; factor = {factor}'
472+
)
473+
G_new = (q_prime_val - Bw) / Bv0
474+
475+
U = w + G_new * v0
476+
return U, float(G_new)
477+
386478

387479
# ---------------------------------------------------------------------------
388480
# Case 1: No lifting – constraint B·u = q(t)
@@ -873,3 +965,224 @@ def solve_system(self, impl_sys, v_approx_mesh, factor, u0, t):
873965
me.diff[:] = U
874966
me.alg[0] = G_prime
875967
return me
968+
969+
970+
# ---------------------------------------------------------------------------
971+
# Case 5: No lifting – differentiated constraint B·U = q'(t)
972+
# ---------------------------------------------------------------------------
973+
974+
class stokes_poiseuille_1d_fd_diffconstr(stokes_poiseuille_1d_fd):
975+
r"""
976+
1-D Stokes/Poiseuille DAE using the **differentiated constraint**
977+
:math:`B\mathbf{u}' = q'(t)` at every SDC stage.
978+
979+
This is a remedy for the order reduction in the pressure gradient.
980+
Rather than enforcing the original algebraic constraint
981+
:math:`B\mathbf{u}_m = q(\tau_m)` (which limits :math:`G` to order
982+
:math:`M`), each stage solve uses
983+
984+
.. math::
985+
986+
B\,\mathbf{U}_m = q'(\tau_m),
987+
988+
where :math:`\mathbf{U}_m = \mathbf{u}'(\tau_m)` is the velocity
989+
derivative. The corresponding Schur-complement formula is
990+
991+
.. math::
992+
993+
G_m = \frac{q'(\tau_m) - B\mathbf{w}}{B\mathbf{v}_0},
994+
995+
giving pressure error
996+
:math:`e_{G_m} = -BA\,e_{\mathbf{u}_m}/s = \mathcal{O}(\Delta t^{M+1})`
997+
(one order higher than the algebraic constraint).
998+
999+
``eval_f`` is also modified to check :math:`B\mathbf{u}' - q'(t) = 0`
1000+
(so that the SDC residual converges to machine precision at the fixed
1001+
point of the differentiated-constraint iteration).
1002+
1003+
.. note::
1004+
1005+
The original constraint :math:`B\mathbf{u} = q(t)` is satisfied
1006+
approximately: since :math:`B\mathbf{u}(t_n) = q(t_n)` (consistent
1007+
IC) and :math:`B\mathbf{U}_m = q'(\tau_m)` at every stage,
1008+
the quadrature formula gives
1009+
:math:`B\mathbf{u}_{n+1} - q(t_{n+1}) = \mathcal{O}(\Delta t^{2M})`
1010+
at the endpoint (Gauss quadrature error) and
1011+
:math:`\mathcal{O}(\Delta t^{M+1})` at interior nodes.
1012+
1013+
Parameters
1014+
----------
1015+
nvars : int
1016+
Number of interior grid points (default 127; must be ≥ 5).
1017+
nu : float
1018+
Kinematic viscosity (default 1.0).
1019+
newton_tol : float
1020+
Unused; passed to base class (default 1e-10).
1021+
"""
1022+
1023+
def eval_f(self, u, du, t):
1024+
r"""
1025+
Fully-implicit DAE residual using the **differentiated constraint**:
1026+
1027+
.. math::
1028+
1029+
F_\text{diff} = \mathbf{u}' - \nu A\,\mathbf{u}
1030+
- G\,\mathbf{1} - \mathbf{f}(t),
1031+
1032+
.. math::
1033+
1034+
F_\text{alg} = B\,\mathbf{u}' - q'(t).
1035+
1036+
The second equation uses the velocity **derivative** ``du.diff``
1037+
instead of the velocity state ``u.diff``, making the SDC residual
1038+
exactly zero (machine precision) when :meth:`solve_system` has
1039+
enforced :math:`B\mathbf{U}_m = q'(\tau_m)`.
1040+
"""
1041+
f = self.dtype_f(self.init, val=0.0)
1042+
u_vel = np.asarray(u.diff)
1043+
du_vel = np.asarray(du.diff)
1044+
G = float(u.alg[0])
1045+
1046+
f.diff[:] = du_vel - (self.A.dot(u_vel) + G * self.ones + self._forcing(t))
1047+
f.alg[0] = self._B_dot(du_vel) - self._q_prime(t)
1048+
1049+
self.work_counters['rhs']()
1050+
return f
1051+
1052+
def solve_system(self, impl_sys, u_approx, factor, u0, t):
1053+
r"""
1054+
Schur-complement solve using the differentiated constraint
1055+
:math:`B\mathbf{U} = q'(t)`.
1056+
1057+
Parameters
1058+
----------
1059+
impl_sys : callable
1060+
Unused; system solved directly.
1061+
u_approx : MeshDAE
1062+
Current velocity approximation at the node.
1063+
factor : float
1064+
Implicit prefactor :math:`\alpha`.
1065+
u0 : MeshDAE
1066+
Unused (direct solver).
1067+
t : float
1068+
Current time.
1069+
1070+
Returns
1071+
-------
1072+
me : MeshDAE
1073+
``me.diff[:]`` = velocity derivative :math:`\mathbf{U}_m`
1074+
satisfying :math:`B\mathbf{U}_m = q'(t)`,
1075+
``me.alg[0]`` = pressure gradient :math:`G_m`.
1076+
"""
1077+
me = self.dtype_u(self.init, val=0.0)
1078+
v_approx = np.asarray(u_approx.diff).copy()
1079+
1080+
rhs_eff = self.A.dot(v_approx) + self._forcing(t)
1081+
U, G_new = self._schur_solve_diffconstr(rhs_eff, factor, self._q_prime(t))
1082+
1083+
me.diff[:] = U
1084+
me.alg[0] = G_new
1085+
return me
1086+
1087+
1088+
# ---------------------------------------------------------------------------
1089+
# Case 6: Lifting + differentiated constraint B·Ũ = 0
1090+
# ---------------------------------------------------------------------------
1091+
1092+
class stokes_poiseuille_1d_fd_lift_diffconstr(stokes_poiseuille_1d_fd_lift):
1093+
r"""
1094+
1-D Stokes/Poiseuille DAE with **constraint lifting** and the
1095+
**differentiated constraint** :math:`B\tilde{\mathbf{u}}' = 0`.
1096+
1097+
Combines:
1098+
1099+
* The homogeneous constraint :math:`B\tilde{\mathbf{v}} = 0` from
1100+
:class:`stokes_poiseuille_1d_fd_lift` (reduces order reduction).
1101+
* The differentiated constraint :math:`B\tilde{\mathbf{U}} = 0` in
1102+
the stage solve, analogous to :class:`stokes_poiseuille_1d_fd_diffconstr`.
1103+
1104+
.. note::
1105+
1106+
For the lifted problem the original constraint is
1107+
:math:`B\tilde{\mathbf{v}} = 0` (homogeneous, constant in time).
1108+
Its time derivative is :math:`B\tilde{\mathbf{v}}' = 0`, which is
1109+
the same condition. The differentiated-constraint Schur solve
1110+
therefore reduces to
1111+
:math:`G = -B\mathbf{w}_\text{net} / (B\mathbf{v}_0)`,
1112+
which is **equivalent to the original lifted Schur solve at the fixed
1113+
point** (when :math:`B\tilde{\mathbf{v}} \approx 0`). Both this class
1114+
and :class:`stokes_poiseuille_1d_fd_lift` therefore converge to the
1115+
same fixed point and give identical convergence orders. This class is
1116+
included for completeness and to confirm the equivalence.
1117+
1118+
Parameters
1119+
----------
1120+
nvars : int
1121+
Number of interior grid points (default 127; must be ≥ 5).
1122+
nu : float
1123+
Kinematic viscosity (default 1.0).
1124+
newton_tol : float
1125+
Unused; passed to base class (default 1e-10).
1126+
"""
1127+
1128+
def eval_f(self, u, du, t):
1129+
r"""
1130+
Fully-implicit DAE residual using the differentiated homogeneous
1131+
constraint :math:`B\tilde{\mathbf{u}}' = 0`:
1132+
1133+
.. math::
1134+
1135+
F_\text{diff} = \tilde{\mathbf{u}}' - \nu A\,\tilde{\mathbf{v}}
1136+
- G\,\mathbf{1} - \mathbf{f}_\text{net}(t),
1137+
1138+
.. math::
1139+
1140+
F_\text{alg} = B\,\tilde{\mathbf{u}}' - 0.
1141+
"""
1142+
f = self.dtype_f(self.init, val=0.0)
1143+
v_tilde = np.asarray(u.diff)
1144+
dv_tilde = np.asarray(du.diff)
1145+
G = float(u.alg[0])
1146+
1147+
f.diff[:] = dv_tilde - (self.A.dot(v_tilde) + G * self.ones + self._net_forcing(t))
1148+
f.alg[0] = self._B_dot(dv_tilde) # B·ũ' = 0
1149+
1150+
self.work_counters['rhs']()
1151+
return f
1152+
1153+
def solve_system(self, impl_sys, u_approx, factor, u0, t):
1154+
r"""
1155+
Schur-complement solve using the differentiated homogeneous
1156+
constraint :math:`B\tilde{\mathbf{U}} = 0`.
1157+
1158+
Parameters
1159+
----------
1160+
impl_sys : callable
1161+
Unused; system solved directly.
1162+
u_approx : MeshDAE
1163+
Current velocity approximation :math:`(\tilde{\mathbf{v}}, G)`.
1164+
factor : float
1165+
Implicit prefactor :math:`\alpha`.
1166+
u0 : MeshDAE
1167+
Unused (direct solver).
1168+
t : float
1169+
Current time.
1170+
1171+
Returns
1172+
-------
1173+
me : MeshDAE
1174+
``me.diff[:]`` = lifted velocity derivative
1175+
:math:`\tilde{\mathbf{U}}_m` (satisfies
1176+
:math:`B\tilde{\mathbf{U}}_m = 0`),
1177+
``me.alg[0]`` = pressure gradient :math:`G_m`.
1178+
"""
1179+
me = self.dtype_u(self.init, val=0.0)
1180+
v_approx = np.asarray(u_approx.diff).copy()
1181+
1182+
rhs_eff = self.A.dot(v_approx) + self._net_forcing(t)
1183+
# Differentiated homogeneous constraint: B·U_tilde = 0 → q'_eff = 0
1184+
U, G_new = self._schur_solve_diffconstr(rhs_eff, factor, 0.0)
1185+
1186+
me.diff[:] = U
1187+
me.alg[0] = G_new
1188+
return me

0 commit comments

Comments
 (0)