Skip to content

Commit fd6ef0b

Browse files
authored
SDC-DAE sweeper for semi-explicit DAEs (#414)
* Added SI-SDC-DAE sweeper * Starte with test for SemiImplicitDAE * Test for SI-SDC sweeper * Clean-up * Removed parameter from function * Removed test + changed range of loop in SI-sweeper
1 parent 18989d3 commit fd6ef0b

File tree

5 files changed

+546
-4
lines changed

5 files changed

+546
-4
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
from pySDC.core.Errors import ParameterError
2+
from pySDC.projects.DAE.sweepers.fully_implicit_DAE import fully_implicit_DAE
3+
4+
5+
class SemiImplicitDAE(fully_implicit_DAE):
6+
r"""
7+
Custom sweeper class to implement SDC for solving semi-explicit DAEs of the form
8+
9+
.. math::
10+
u' = f(u, z, t),
11+
12+
.. math::
13+
0 = g(u, z, t)
14+
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.
19+
20+
It solves a collocation problem of the form
21+
22+
.. math::
23+
U = f(\vec{U}_0 + \Delta t (\mathbf{Q} \otimes \mathbf{I}_{n_d}) \vec{U}, \vec{z}, \tau),
24+
25+
.. math::
26+
0 = g(\vec{U}_0 + \Delta t (\mathbf{Q} \otimes \mathbf{I}_{n_d}) \vec{U}, \vec{z}, \tau),
27+
28+
where
29+
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}`.
38+
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.
41+
42+
The sweeper implementation is based on the ideas mentioned in the KDC publication [1]_.
43+
44+
Parameters
45+
----------
46+
params : dict
47+
Parameters passed to the sweeper.
48+
49+
Attributes
50+
----------
51+
QI : np.2darray
52+
Implicit Euler integration matrix.
53+
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).
58+
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
64+
65+
.. math::
66+
A = \begin{matrix}
67+
I & 0 \\
68+
0 & 0
69+
\end{matrix}
70+
71+
then, the problem can be reformulated as
72+
73+
.. math::
74+
A\vec{x}' = F(\vec{x}).
75+
76+
Then, setting :math:`F_{new}(\vec{x}, \vec{x}') = A\vec{x}' - F(\vec{x})` defines a DAE of fully-implicit form
77+
78+
.. math::
79+
0 = F_{new}(\vec{x}, \vec{x}').
80+
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+
"""
85+
86+
def __init__(self, params):
87+
"""Initialization routine"""
88+
89+
if 'QI' not in params:
90+
params['QI'] = 'IE'
91+
92+
# call parent's initialization routine
93+
super().__init__(params)
94+
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)
98+
99+
self.QI = self.get_Qdelta_implicit(coll=self.coll, qd_type=self.params.QI)
100+
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``.
105+
106+
Returns
107+
-------
108+
me : list of lists
109+
Integral of the gradient at each collocation node.
110+
"""
111+
112+
# get current level and problem description
113+
L = self.level
114+
P = L.prob
115+
M = self.coll.num_nodes
116+
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[:]
123+
124+
return me
125+
126+
def update_nodes(self):
127+
r"""
128+
Updates the values of solution ``u`` and their gradient stored in ``f``.
129+
"""
130+
131+
L = self.level
132+
P = L.prob
133+
134+
# only if the level has been touched before
135+
assert L.status.unlocked
136+
M = self.coll.num_nodes
137+
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
144+
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[:]
150+
151+
def implSystem(unknowns):
152+
"""
153+
Build implicit system to solve in order to find the unknowns.
154+
155+
Parameters
156+
----------
157+
unknowns : dtype_u
158+
Unknowns of the system.
159+
160+
Returns
161+
-------
162+
sys :
163+
System to be solved as implicit function.
164+
"""
165+
166+
unknowns_mesh = P.dtype_f(unknowns)
167+
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[:]
171+
172+
sys = P.eval_f(local_u_approx, unknowns_mesh, L.time + L.dt * self.coll.nodes[m - 1])
173+
return sys
174+
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[:]
181+
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[:]
186+
187+
# indicate presence of new values at this level
188+
L.status.updated = True
189+
190+
return None

pySDC/projects/DAE/sweepers/fully_implicit_DAE.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,14 @@ def update_nodes(self):
7474
assert L.status.unlocked
7575

7676
M = self.coll.num_nodes
77-
u_0 = L.u[0]
7877

7978
# get QU^k where U = u'
8079
integral = self.integrate()
8180
# build the rest of the known solution u_0 + del_t(Q - Q_del)U_k
8281
for m in range(1, M + 1):
8382
for j in range(1, M + 1):
8483
integral[m - 1] -= L.dt * self.QI[m, j] * L.f[j]
85-
integral[m - 1] += u_0
84+
integral[m - 1] += L.u[0]
8685

8786
# do the sweep
8887
for m in range(1, M + 1):
@@ -125,7 +124,7 @@ def implSystem(params):
125124
# Update solution approximation
126125
integral = self.integrate()
127126
for m in range(M):
128-
L.u[m + 1] = u_0 + integral[m]
127+
L.u[m + 1] = L.u[0] + integral[m]
129128

130129
# indicate presence of new values at this level
131130
L.status.updated = True

pySDC/projects/PinTSimE/battery_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ def plotSolution(u_num, prob_cls_name, use_adaptivity, use_detection): # pragma
495495
ax.set_xlabel(r'$t$', fontsize=16)
496496
ax.set_ylabel(r'$u(t)$', fontsize=16)
497497

498-
fig.savefig('data/{}_model_solution.png'.format(prob_cls_name), dpi=300, bbox_inches='tight')
498+
fig.savefig(f'data/{prob_cls_name}_model_solution.png', dpi=300, bbox_inches='tight')
499499
plt_helper.plt.close(fig)
500500

501501

0 commit comments

Comments
 (0)