Coverage for pySDC/projects/DAE/sweepers/SemiImplicitDAE.py: 98%
51 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-29 09:02 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-29 09:02 +0000
1from pySDC.core.Errors import ParameterError
2from pySDC.projects.DAE.sweepers.fully_implicit_DAE import fully_implicit_DAE
5class SemiImplicitDAE(fully_implicit_DAE):
6 r"""
7 Custom sweeper class to implement SDC for solving semi-explicit DAEs of the form
9 .. math::
10 u' = f(u, z, t),
12 .. math::
13 0 = g(u, z, t)
15 with :math:`u(t), u'(t) \in\mathbb{R}^{N_d}` the differential variables and their derivates,
16 algebraic variables :math:`z(t) \in\mathbb{R}^{N_a}`, :math:`f(u, z, t) \in \mathbb{R}^{N_d}`,
17 and :math:`g(u, z, t) \in \mathbb{R}^{N_a}`. :math:`N = N_d + N_a` is the dimension of the whole
18 system of DAEs.
20 It solves a collocation problem of the form
22 .. math::
23 U = f(\vec{U}_0 + \Delta t (\mathbf{Q} \otimes \mathbf{I}_{n_d}) \vec{U}, \vec{z}, \tau),
25 .. math::
26 0 = g(\vec{U}_0 + \Delta t (\mathbf{Q} \otimes \mathbf{I}_{n_d}) \vec{U}, \vec{z}, \tau),
28 where
30 - :math:`\tau=(\tau_1,..,\tau_M) in \mathbb{R}^M` the vector of collocation nodes,
31 - :math:`\vec{U}_0 = (u_0,..,u_0) \in \mathbb{R}^{MN_d}` the vector of initial condition spread to each node,
32 - spectral integration matrix :math:`\mathbf{Q} \in \mathbb{R}^{M \times M}`,
33 - :math:`\vec{U}=(U_1,..,U_M) \in \mathbb{R}^{MN_d}` the vector of unknown derivatives of differential variables
34 :math:`U_m \approx U(\tau_m) = u'(\tau_m) \in \mathbb{R}^{N_d}`,
35 - :math:`\vec{z}=(z_1,..,z_M) \in \mathbb{R}^{MN_a}` the vector of unknown algebraic variables
36 :math:`z_m \approx z(\tau_m) \in \mathbb{R}^{N_a}`,
37 - and identity matrix :math:`\mathbf{I}_{N_d} \in \mathbb{R}^{N_d \times N_d}`.
39 This sweeper treats the differential and the algebraic variables differently by only integrating the differential
40 components. Solving the nonlinear system, :math:`{U,z}` are the unknowns.
42 The sweeper implementation is based on the ideas mentioned in the KDC publication [1]_.
44 Parameters
45 ----------
46 params : dict
47 Parameters passed to the sweeper.
49 Attributes
50 ----------
51 QI : np.2darray
52 Implicit Euler integration matrix.
54 References
55 ----------
56 .. [1] J. Huang, J. Jun, M. L. Minion. Arbitrary order Krylov deferred correction methods for differential algebraic
57 equation. J. Comput. Phys. Vol. 221 No. 2 (2007).
59 Note
60 ----
61 The right-hand side of the problem DAE classes using this sweeper has to be exactly implemented in the way, the
62 semi-explicit DAE is defined. Define :math:`\vec{x}=(y, z)^T`, :math:`F(\vec{x})=(f(\vec{x}), g(\vec{x}))`, and the
63 matrix
65 .. math::
66 A = \begin{matrix}
67 I & 0 \\
68 0 & 0
69 \end{matrix}
71 then, the problem can be reformulated as
73 .. math::
74 A\vec{x}' = F(\vec{x}).
76 Then, setting :math:`F_{new}(\vec{x}, \vec{x}') = A\vec{x}' - F(\vec{x})` defines a DAE of fully-implicit form
78 .. math::
79 0 = F_{new}(\vec{x}, \vec{x}').
81 Hence, the method ``eval_f`` of problem DAE classes of semi-explicit form implements the right-hand side in the way of
82 returning :math:`F(\vec{x})`, whereas ``eval_f`` of problem classes of fully-implicit form return the right-hand side
83 :math:`F_{new}(\vec{x}, \vec{x}')`.
84 """
86 def __init__(self, params):
87 """Initialization routine"""
89 if 'QI' not in params:
90 params['QI'] = 'IE'
92 # call parent's initialization routine
93 super().__init__(params)
95 msg = f"Quadrature type {self.params.quad_type} is not implemented yet. Use 'RADAU-RIGHT' instead!"
96 if self.coll.left_is_node:
97 raise ParameterError(msg)
99 self.QI = self.get_Qdelta_implicit(coll=self.coll, qd_type=self.params.QI)
101 def integrate(self):
102 r"""
103 Returns the solution by integrating its gradient (fundamental theorem of calculus) at each collocation node.
104 ``level.f`` stores the gradient of solution ``level.u``.
106 Returns
107 -------
108 me : list of lists
109 Integral of the gradient at each collocation node.
110 """
112 # get current level and problem description
113 L = self.level
114 P = L.prob
115 M = self.coll.num_nodes
117 me = []
118 for m in range(1, M + 1):
119 # new instance of dtype_u, initialize values with 0
120 me.append(P.dtype_u(P.init, val=0.0))
121 for j in range(1, M + 1):
122 me[-1].diff[:] += L.dt * self.coll.Qmat[m, j] * L.f[j].diff[:]
124 return me
126 def update_nodes(self):
127 r"""
128 Updates the values of solution ``u`` and their gradient stored in ``f``.
129 """
131 L = self.level
132 P = L.prob
134 # only if the level has been touched before
135 assert L.status.unlocked
136 M = self.coll.num_nodes
138 integral = self.integrate()
139 # build the rest of the known solution u_0 + del_t(Q - Q_del)U_k
140 for m in range(1, M + 1):
141 for j in range(1, m + 1):
142 integral[m - 1].diff[:] -= L.dt * self.QI[m, j] * L.f[j].diff[:]
143 integral[m - 1].diff[:] += L.u[0].diff
145 # do the sweep
146 for m in range(1, M + 1):
147 u_approx = P.dtype_u(integral[m - 1])
148 for j in range(1, m):
149 u_approx.diff[:] += L.dt * self.QI[m, j] * L.f[j].diff[:]
151 def implSystem(unknowns):
152 """
153 Build implicit system to solve in order to find the unknowns.
155 Parameters
156 ----------
157 unknowns : dtype_u
158 Unknowns of the system.
160 Returns
161 -------
162 sys :
163 System to be solved as implicit function.
164 """
166 unknowns_mesh = P.dtype_f(unknowns)
168 local_u_approx = P.dtype_u(u_approx)
169 local_u_approx.diff[:] += L.dt * self.QI[m, m] * unknowns_mesh.diff[:]
170 local_u_approx.alg[:] = unknowns_mesh.alg[:]
172 sys = P.eval_f(local_u_approx, unknowns_mesh, L.time + L.dt * self.coll.nodes[m - 1])
173 return sys
175 u0 = P.dtype_u(P.init)
176 u0.diff[:], u0.alg[:] = L.f[m].diff[:], L.u[m].alg[:]
177 u_new = P.solve_system(implSystem, u0, L.time + L.dt * self.coll.nodes[m - 1])
178 # ---- update U' and z ----
179 L.f[m].diff[:] = u_new.diff[:]
180 L.u[m].alg[:] = u_new.alg[:]
182 # Update solution approximation
183 integral = self.integrate()
184 for m in range(M):
185 L.u[m + 1].diff[:] = L.u[0].diff[:] + integral[m].diff[:]
187 # indicate presence of new values at this level
188 L.status.updated = True
190 return None