|
56 | 56 |
|
57 | 57 | * :class:`~pySDC.projects.DAE.sweepers.semiImplicitDAE.SemiImplicitDAE` |
58 | 58 | (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). |
60 | 61 |
|
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. |
65 | 63 |
|
66 | 64 | Without constraint lifting (class :class:`stokes_poiseuille_1d_fd`), the |
67 | 65 | constraint :math:`B\mathbf{u} = q(t)` has a time-dependent right-hand side, |
68 | 66 | 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). |
70 | 68 |
|
71 | 69 | Constraint lifting (class :class:`stokes_poiseuille_1d_fd_lift`) |
72 | 70 | ----------------------------------------------------------------- |
|
95 | 93 | - \dot{\mathbf{u}}_\ell(t)\bigr] |
96 | 94 | + G\,\mathbf{1}. |
97 | 95 |
|
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. |
101 | 113 |
|
102 | 114 | Classes |
103 | 115 | ------- |
104 | 116 | 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`. |
108 | 119 |
|
109 | 120 | stokes_poiseuille_1d_fd_lift |
110 | 121 | 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). |
113 | 137 | """ |
114 | 138 |
|
115 | 139 | import numpy as np |
@@ -247,6 +271,12 @@ def _q(self, t): |
247 | 271 | """ |
248 | 272 | return self.C_B * np.sin(t) |
249 | 273 |
|
| 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 | + |
250 | 280 | def _forcing(self, t): |
251 | 281 | r""" |
252 | 282 | 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): |
383 | 413 | U = w + factor * G_prime * v0 |
384 | 414 | return U, float(G_prime) |
385 | 415 |
|
| 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 | + |
386 | 478 |
|
387 | 479 | # --------------------------------------------------------------------------- |
388 | 480 | # 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): |
873 | 965 | me.diff[:] = U |
874 | 966 | me.alg[0] = G_prime |
875 | 967 | 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