Skip to content

Commit aa639ad

Browse files
committed
Merge pull request #53 from danielru/sdc_update_matrix
test_imexsweeper
2 parents 884b57f + 2b75318 commit aa639ad

File tree

2 files changed

+239
-2
lines changed

2 files changed

+239
-2
lines changed

examples/SWFW/ProblemClass.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def solve_system(self,rhs,factor,u0,t):
5959
me = mesh(self.nvars)
6060
for i in range(self.lambda_s.size):
6161
for j in range(self.lambda_f.size):
62-
me.values[i,j] = rhs.values[i,j]/(1-factor*self.lambda_f[j])
62+
me.values[i,j] = rhs.values[i,j]/(1.0-factor*self.lambda_f[j])
6363

6464
return me
6565

@@ -130,7 +130,7 @@ def u_exact(self,t):
130130
Returns:
131131
exact solution
132132
"""
133-
133+
134134
me = mesh(self.nvars)
135135
for i in range(self.lambda_s.size):
136136
for j in range(self.lambda_f.size):

tests/test_imexsweeper.py

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
from pySDC import Level as lvl
2+
from pySDC import Hooks as hookclass
3+
from pySDC import CollocationClasses as collclass
4+
from pySDC import Step as stepclass
5+
6+
from pySDC.datatype_classes.complex_mesh import mesh, rhs_imex_mesh
7+
from pySDC.sweeper_classes.imex_1st_order import imex_1st_order as imex
8+
from examples.SWFW.ProblemClass import swfw_scalar
9+
10+
from nose.tools import *
11+
import unittest
12+
import numpy as np
13+
14+
class TestImexSweeper(unittest.TestCase):
15+
16+
#
17+
# Some auxiliary functions which are not tests themselves
18+
#
19+
def setupLevelStepProblem(self):
20+
step = stepclass.step(params={})
21+
L = lvl.level(problem_class=swfw_scalar, problem_params=self.pparams, dtype_u=mesh, dtype_f=rhs_imex_mesh, sweeper_class=imex, sweeper_params=self.swparams, level_params={}, hook_class=hookclass.hooks, id="imextest")
22+
step.register_level(L)
23+
step.status.dt = 1.0
24+
step.status.time = 0.0
25+
u0 = step.levels[0].prob.u_exact(step.status.time)
26+
step.init_step(u0)
27+
nnodes = step.levels[0].sweep.coll.num_nodes
28+
level = step.levels[0]
29+
problem = level.prob
30+
return step, level, problem, nnodes
31+
32+
def setupQMatrices(self, level):
33+
QE = level.sweep.QE[1:,1:]
34+
QI = level.sweep.QI[1:,1:]
35+
Q = level.sweep.coll.Qmat[1:,1:]
36+
return QE, QI, Q
37+
38+
def setupSweeperMatrices(self, step, level, problem):
39+
nnodes = step.levels[0].sweep.coll.num_nodes
40+
# Build SDC sweep matrix
41+
QE, QI, Q = self.setupQMatrices(level)
42+
dt = step.status.dt
43+
LHS = np.eye(nnodes) - step.status.dt*( problem.lambda_f[0]*QI + problem.lambda_s[0]*QE )
44+
RHS = step.status.dt*( (problem.lambda_f[0]+problem.lambda_s[0])*Q - (problem.lambda_f[0]*QI + problem.lambda_s[0]*QE) )
45+
return LHS, RHS
46+
47+
#
48+
# General setUp function used by all tests
49+
#
50+
def setUp(self):
51+
self.pparams = {}
52+
self.pparams['lambda_s'] = np.array([-0.1*1j], dtype='complex')
53+
self.pparams['lambda_f'] = np.array([-1.0*1j], dtype='complex')
54+
self.pparams['u0'] = np.random.rand()
55+
self.swparams = {}
56+
self.swparams['collocation_class'] = collclass.CollGaussLobatto
57+
self.swparams['num_nodes'] = 2+np.random.randint(5)
58+
59+
# ***************
60+
# **** TESTS ****
61+
# ***************
62+
63+
#
64+
# Check that a level object can be instantiated
65+
#
66+
def test_caninstantiate(self):
67+
L = lvl.level(problem_class=swfw_scalar, problem_params=self.pparams, dtype_u=mesh, dtype_f=rhs_imex_mesh, sweeper_class=imex, sweeper_params=self.swparams, level_params={}, hook_class=hookclass.hooks, id="imextest")
68+
assert isinstance(L.sweep, imex), "sweeper in generated level is not an object of type imex"
69+
70+
#
71+
# Check that a level object can be registered in a step object (needed as prerequiste to execute update_nodes
72+
#
73+
def test_canregisterlevel(self):
74+
step = stepclass.step(params={})
75+
L = lvl.level(problem_class=swfw_scalar, problem_params=self.pparams, dtype_u=mesh, dtype_f=rhs_imex_mesh, sweeper_class=imex, sweeper_params=self.swparams, level_params={}, hook_class=hookclass.hooks, id="imextest")
76+
step.register_level(L)
77+
# At this point, it should not be possible to actually execute functions of the sweeper because the parameters set in setupLevelStepProblem are not yet initialised
78+
with self.assertRaises(Exception):
79+
step.sweep.predict()
80+
with self.assertRaises(Exception):
81+
step.sweep.update_nodes()
82+
with self.assertRaises(Exception):
83+
step.sweep.compute_end_point()
84+
85+
#
86+
# Check that the sweeper functions update_nodes and compute_end_point can be executed
87+
#
88+
def test_canrunsweep(self):
89+
90+
# After running setupLevelStepProblem, the functions predict, update_nodes and compute_end_point should run
91+
step, level, problem, nnodes = self.setupLevelStepProblem()
92+
assert level.u[0] is not None, "After init_step, level.u[0] should no longer be of type None"
93+
assert level.u[1] is None, "Before predict, level.u[1] and following should be of type None"
94+
level.sweep.predict()
95+
# Should now be able to run update nodes
96+
level.sweep.update_nodes()
97+
assert level.uend is None, "uend should be None previous to running compute_end_point"
98+
level.sweep.compute_end_point()
99+
assert level.uend is not None, "uend still None after running compute_end_point"
100+
101+
#
102+
# Make sure a sweep in matrix form is equal to a sweep in node-to-node form
103+
#
104+
def test_sweepequalmatrix(self):
105+
106+
step, level, problem, nnodes = self.setupLevelStepProblem()
107+
step.levels[0].sweep.predict()
108+
u0full = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
109+
110+
# Perform node-to-node SDC sweep
111+
level.sweep.update_nodes()
112+
113+
LHS, RHS = self.setupSweeperMatrices(step, level, problem)
114+
115+
unew = np.linalg.inv(LHS).dot( u0full + RHS.dot(u0full) )
116+
usweep = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
117+
assert np.linalg.norm(unew - usweep, np.infty)<1e-14, "Single SDC sweeps in matrix and node-to-node formulation yield different results"
118+
119+
#
120+
# Make sure the implemented update formula matches the matrix update formula
121+
#
122+
def test_updateformula(self):
123+
124+
if (self.swparams['collocation_class']==collclass.CollGaussLobatto):
125+
raise unittest.SkipTest("Needs fix of issue #52 before passing for Gauss Lobatto nodes")
126+
127+
step, level, problem, nnodes = self.setupLevelStepProblem()
128+
level.sweep.predict()
129+
u0full = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
130+
131+
# Perform update step in sweeper
132+
level.sweep.update_nodes()
133+
ustages = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
134+
135+
# Compute end value through provided function
136+
level.sweep.compute_end_point()
137+
uend_sweep = level.uend.values
138+
# Compute end value from matrix formulation
139+
uend_mat = self.pparams['u0'] + step.status.dt*level.sweep.coll.weights.dot(ustages*(problem.lambda_s[0] + problem.lambda_f[0]))
140+
assert np.linalg.norm(uend_sweep - uend_mat, np.infty)<1e-14, "Update formula in sweeper gives different result than matrix update formula"
141+
142+
#
143+
# Compute the exact collocation solution by matrix inversion and make sure it is a fixed point
144+
#
145+
def test_collocationinvariant(self):
146+
147+
step, level, problem, nnodes = self.setupLevelStepProblem()
148+
level.sweep.predict()
149+
u0full = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
150+
151+
QE, QI, Q = self.setupQMatrices(level)
152+
153+
# Build collocation matrix
154+
Mcoll = np.eye(nnodes) - step.status.dt*Q*(problem.lambda_s[0] + problem.lambda_f[0])
155+
156+
# Solve collocation problem directly
157+
ucoll = np.linalg.inv(Mcoll).dot(u0full)
158+
159+
# Put stages of collocation solution into level
160+
for l in range(0,nnodes):
161+
level.u[l+1].values = ucoll[l]
162+
level.f[l+1].impl.values = problem.lambda_f[0]*ucoll[l]
163+
level.f[l+1].expl.values = problem.lambda_s[0]*ucoll[l]
164+
165+
# Perform node-to-node SDC sweep
166+
level.sweep.update_nodes()
167+
168+
# Build matrices for matrix formulation of sweep
169+
LHS = np.eye(nnodes) - step.status.dt*( problem.lambda_f[0]*QI + problem.lambda_s[0]*QE )
170+
RHS = step.status.dt*( (problem.lambda_f[0]+problem.lambda_s[0])*Q - (problem.lambda_f[0]*QI + problem.lambda_s[0]*QE) )
171+
# Make sure both matrix and node-to-node sweep leave collocation unaltered
172+
unew = np.linalg.inv(LHS).dot( u0full + RHS.dot(ucoll) )
173+
assert np.linalg.norm( unew - ucoll, np.infty )<1e-14, "Collocation solution not invariant under matrix SDC sweep"
174+
unew_sweep = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
175+
assert np.linalg.norm( unew_sweep - ucoll, np.infty )<1e-14, "Collocation solution not invariant under node-to-node sweep"
176+
177+
178+
#
179+
# Make sure that K node-to-node sweeps give the same result as K sweeps in matrix form and the single matrix formulation for K sweeps
180+
#
181+
def test_manysweepsequalmatrix(self):
182+
step, level, problem, nnodes = self.setupLevelStepProblem()
183+
step.levels[0].sweep.predict()
184+
u0full = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
185+
186+
# Perform K node-to-node SDC sweep
187+
K = 1 + np.random.randint(6)
188+
for i in range(0,K):
189+
level.sweep.update_nodes()
190+
usweep = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
191+
192+
LHS, RHS = self.setupSweeperMatrices(step, level, problem)
193+
unew = u0full
194+
for i in range(0,K):
195+
unew = np.linalg.inv(LHS).dot( u0full + RHS.dot(unew) )
196+
197+
assert np.linalg.norm(unew - usweep, np.infty)<1e-14, "Doing multiple node-to-node sweeps yields different result than same number of matrix-form sweeps"
198+
199+
# Build single matrix representing K sweeps
200+
Pinv = np.linalg.inv(LHS)
201+
Mat_sweep = np.linalg.matrix_power(Pinv.dot(RHS), K)
202+
for i in range(0,K):
203+
Mat_sweep = Mat_sweep + np.linalg.matrix_power(Pinv.dot(RHS),i).dot(Pinv)
204+
usweep_onematrix = Mat_sweep.dot(u0full)
205+
assert np.linalg.norm( usweep_onematrix - usweep, np.infty )<1e-14, "Single-matrix multiple sweep formulation yields different result than multiple sweeps in node-to-node or matrix form form"
206+
207+
#
208+
# Make sure that update function for K sweeps computed from K-sweep matrix gives same result as K sweeps in node-to-node form plus compute_end_point
209+
#
210+
def test_maysweepupdate(self):
211+
212+
if (self.swparams['collocation_class']==collclass.CollGaussLobatto):
213+
raise unittest.SkipTest("Needs fix of issue #52 before passing for Gauss Lobatto nodes")
214+
215+
step, level, problem, nnodes = self.setupLevelStepProblem()
216+
step.levels[0].sweep.predict()
217+
u0full = np.array([ level.u[l].values.flatten() for l in range(1,nnodes+1) ])
218+
219+
# Perform K node-to-node SDC sweep
220+
K = 1 + np.random.randint(6)
221+
for i in range(0,K):
222+
level.sweep.update_nodes()
223+
# Fetch final value
224+
level.sweep.compute_end_point()
225+
uend_sweep = level.uend.values
226+
227+
LHS, RHS = self.setupSweeperMatrices(step, level, problem)
228+
# Build single matrix representing K sweeps
229+
Pinv = np.linalg.inv(LHS)
230+
Mat_sweep = np.linalg.matrix_power(Pinv.dot(RHS), K)
231+
for i in range(0,K):
232+
Mat_sweep = Mat_sweep + np.linalg.matrix_power(Pinv.dot(RHS),i).dot(Pinv)
233+
# Now build update function
234+
update = 1.0 + (problem.lambda_s[0] + problem.lambda_f[0])*level.sweep.coll.weights.dot(Mat_sweep.dot(np.ones(nnodes)))
235+
# Multiply u0 by value of update function to get end value directly
236+
uend_matrix = update*self.pparams['u0']
237+
assert abs(uend_matrix - uend_sweep)<1e-14, "Node-to-node sweep plus update yields different result than update function computed through K-sweep matrix"

0 commit comments

Comments
 (0)