Coverage for pySDC/implementations/problem_classes/TestEquation_0D.py: 100%

101 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 12:30 +0000

1import numpy as np 

2import scipy.sparse as nsp 

3 

4from pySDC.core.problem import Problem, WorkCounter 

5from pySDC.implementations.datatype_classes.mesh import mesh, imex_mesh 

6from pySDC.helpers.fieldsIO import Scalar 

7 

8 

9class testequation0d(Problem): 

10 r""" 

11 Dahlquist test equation for many values of :math:`\lambda` at once, treated fully implicitly. 

12 

13 It is of the form 

14 

15 .. math:: 

16 \frac{d u(t)}{dt} = A u(t) 

17 

18 for :math:`A = diag(\lambda_1, .. ,\lambda_n)`. 

19 

20 Parameters 

21 ---------- 

22 lambdas : sequence of array_like, optional 

23 List of lambda parameters. 

24 u0 : sequence of array_like, optional 

25 Initial condition. 

26 useGPU : bool, optional 

27 Run on the GPU with CuPy instead of on the CPU with NumPy. 

28 

29 Attributes 

30 ---------- 

31 A : scipy.sparse.csc_matrix 

32 Diagonal matrix containing :math:`\lambda_1,..,\lambda_n`. 

33 """ 

34 

35 xp = np 

36 xsp = nsp 

37 dtype_u = mesh 

38 dtype_f = mesh 

39 

40 def setup_GPU(self): 

41 """ 

42 Switch to GPU modules 

43 """ 

44 from pySDC.implementations.datatype_classes.cupy_mesh import cupy_mesh 

45 import cupy as cp 

46 import cupyx.scipy.sparse as csp 

47 

48 self.xp = cp 

49 self.xsp = csp 

50 self.dtype_u = cupy_mesh 

51 self.dtype_f = cupy_mesh 

52 

53 def __init__(self, lambdas=None, u0=0.0, useGPU=False): 

54 """Initialization routine""" 

55 if useGPU: 

56 self.setup_GPU() 

57 

58 if lambdas is None: 

59 re = self.xp.linspace(-30, 19, 50) 

60 im = self.xp.linspace(-50, 49, 50) 

61 lambdas = self.xp.array([[complex(re[i], im[j]) for i in range(len(re))] for j in range(len(im))]).reshape( 

62 (len(re) * len(im)) 

63 ) 

64 lambdas = self.xp.asarray(lambdas) 

65 assert lambdas.ndim == 1, f'expect flat list here, got {lambdas}' 

66 nvars = lambdas.size 

67 assert nvars > 0, 'expect at least one lambda parameter here' 

68 

69 # invoke super init, passing number of dofs, dtype_u and dtype_f 

70 super().__init__(init=(nvars, None, self.xp.dtype('complex128'))) 

71 

72 lambdas = self.xp.array(lambdas) 

73 self.A = self.xsp.diags(lambdas) 

74 self._makeAttributeAndRegister('nvars', 'lambdas', 'u0', 'useGPU', localVars=locals(), readOnly=True) 

75 self.work_counters['rhs'] = WorkCounter() 

76 

77 def eval_f(self, u, t): 

78 """ 

79 Routine to evaluate the right-hand side of the problem. 

80 

81 Parameters 

82 ---------- 

83 u : dtype_u 

84 Current values of the numerical solution. 

85 t : float 

86 Current time of the numerical solution is computed. 

87 

88 Returns 

89 ------- 

90 f : dtype_f 

91 The right-hand side of the problem. 

92 """ 

93 

94 f = self.dtype_f(self.init) 

95 f[:] = u 

96 f *= self.lambdas 

97 self.work_counters['rhs']() 

98 return f 

99 

100 def solve_system(self, rhs, factor, u0, t): 

101 r""" 

102 Simple linear solver for :math:`(I-factor\cdot A)\vec{u}=\vec{rhs}`. 

103 

104 Parameters 

105 ---------- 

106 rhs : dtype_f 

107 Right-hand side for the linear system. 

108 factor : float 

109 Abbrev. for the local stepsize (or any other factor required). 

110 u0 : dtype_u 

111 Initial guess for the iterative solver. 

112 t : float 

113 Current time (e.g. for time-dependent BCs). 

114 

115 Returns 

116 ------- 

117 me : dtype_u 

118 The solution as mesh. 

119 """ 

120 me = self.dtype_u(self.init) 

121 L = 1 - factor * self.lambdas 

122 L[L == 0] = 1 # to avoid potential divisions by zeros 

123 me[:] = rhs 

124 me /= L 

125 return me 

126 

127 def u_exact(self, t, u_init=None, t_init=None): 

128 """ 

129 Routine to compute the exact solution at time t. 

130 

131 Parameters 

132 ---------- 

133 t : float 

134 Time of the exact solution. 

135 u_init : pySDC.problem.testequation0d.dtype_u 

136 Initial solution. 

137 t_init : float 

138 The initial time. 

139 

140 Returns 

141 ------- 

142 me : dtype_u 

143 The exact solution. 

144 """ 

145 

146 u_init = (self.u0 if u_init is None else u_init) * 1.0 

147 t_init = 0.0 if t_init is None else t_init * 1.0 

148 

149 me = self.dtype_u(self.init) 

150 me[:] = u_init * self.xp.exp((t - t_init) * self.lambdas) 

151 return me 

152 

153 def getOutputFile(self, fileName): 

154 r""" 

155 Set up a ``Scalar`` output file for complex values, with one variable per :math:`\lambda`. 

156 

157 Parameters 

158 ---------- 

159 fileName : str 

160 Name of the output file. 

161 

162 Returns 

163 ------- 

164 fOut : pySDC.helpers.fieldsIO.Scalar 

165 The initialized output file. 

166 """ 

167 fOut = Scalar(np.complex128, fileName=fileName) 

168 fOut.setHeader(self.lambdas.size) 

169 fOut.initialize() 

170 return fOut 

171 

172 def processSolutionForOutput(self, u): 

173 """ 

174 Flatten the solution for output. 

175 

176 Parameters 

177 ---------- 

178 u : dtype_u 

179 Solution to be written. 

180 

181 Returns 

182 ------- 

183 np.1darray 

184 The flattened solution. 

185 """ 

186 return u.flatten() 

187 

188 

189class test_equation_IMEX(Problem): 

190 r""" 

191 Test equation with the right-hand side split into an implicit and an explicit part 

192 

193 .. math:: 

194 \frac{d u(t)}{dt} = A_I u(t) + A_E u(t) 

195 

196 for :math:`A_I = diag(\lambda_{I,1}, .. ,\lambda_{I,n})` and :math:`A_E = diag(\lambda_{E,1}, .. ,\lambda_{E,n})`. 

197 For IMEX time-stepping, :math:`A_I u` is treated implicitly and :math:`A_E u` explicitly. 

198 

199 Parameters 

200 ---------- 

201 lambdas_implicit : sequence of array_like, optional 

202 Flat list of the parameters :math:`\lambda_{I,k}` of the implicit part. ``None`` means 2500 complex values 

203 :math:`a + b i` with integers :math:`a \in [-30, 19]` and :math:`b \in [-50, 49]`. 

204 lambdas_explicit : sequence of array_like, optional 

205 Flat list of the parameters :math:`\lambda_{E,k}` of the explicit part, of the same shape as 

206 ``lambdas_implicit``. ``None`` means a copy of ``lambdas_implicit``. 

207 u0 : sequence of array_like, optional 

208 Initial condition at :math:`t = 0`, either a scalar or one value per equation. 

209 

210 Attributes 

211 ---------- 

212 A : scipy.sparse.dia_matrix 

213 Diagonal matrix containing the implicit parameters :math:`\lambda_{I,1},..,\lambda_{I,n}`. 

214 """ 

215 

216 dtype_f = imex_mesh 

217 dtype_u = mesh 

218 xp = np 

219 xsp = nsp 

220 

221 def __init__(self, lambdas_implicit=None, lambdas_explicit=None, u0=0.0): 

222 """Initialization routine""" 

223 

224 if lambdas_implicit is None: 

225 re = self.xp.linspace(-30, 19, 50) 

226 im = self.xp.linspace(-50, 49, 50) 

227 lambdas_implicit = self.xp.array( 

228 [[complex(re[i], im[j]) for i in range(len(re))] for j in range(len(im))] 

229 ).reshape((len(re) * len(im))) 

230 if lambdas_explicit is None: 

231 lambdas_explicit = lambdas_implicit.copy() 

232 

233 lambdas_implicit = self.xp.asarray(lambdas_implicit) 

234 lambdas_explicit = self.xp.asarray(lambdas_explicit) 

235 

236 assert lambdas_implicit.ndim == 1, f'expect flat list here, got {lambdas_implicit}' 

237 assert lambdas_explicit.shape == lambdas_implicit.shape 

238 nvars = lambdas_implicit.size 

239 assert nvars > 0, 'expect at least one lambda parameter here' 

240 

241 # invoke super init, passing number of dofs, dtype_u and dtype_f 

242 super().__init__(init=(nvars, None, self.xp.dtype('complex128'))) 

243 

244 self.A = self.xsp.diags(lambdas_implicit) 

245 self._makeAttributeAndRegister( 

246 'nvars', 'lambdas_implicit', 'lambdas_explicit', 'u0', localVars=locals(), readOnly=True 

247 ) 

248 self.work_counters['rhs'] = WorkCounter() 

249 

250 def eval_f(self, u, t): 

251 """ 

252 Routine to evaluate the right-hand side of the problem. 

253 

254 Parameters 

255 ---------- 

256 u : dtype_u 

257 Current values of the numerical solution. 

258 t : float 

259 Current time of the numerical solution is computed. 

260 

261 Returns 

262 ------- 

263 f : dtype_f 

264 The right-hand side of the problem. 

265 """ 

266 

267 f = self.dtype_f(self.init) 

268 f.impl[:] = u * self.lambdas_implicit 

269 f.expl[:] = u * self.lambdas_explicit 

270 self.work_counters['rhs']() 

271 return f 

272 

273 def solve_system(self, rhs, factor, u0, t): 

274 r""" 

275 Simple linear solver for :math:`(I-factor\cdot A)\vec{u}=\vec{rhs}`. 

276 

277 Parameters 

278 ---------- 

279 rhs : dtype_f 

280 Right-hand side for the linear system. 

281 factor : float 

282 Abbrev. for the local stepsize (or any other factor required). 

283 u0 : dtype_u 

284 Initial guess for the iterative solver. 

285 t : float 

286 Current time (e.g. for time-dependent BCs). 

287 

288 Returns 

289 ------- 

290 me : dtype_u 

291 The solution as mesh. 

292 """ 

293 me = self.dtype_u(self.init) 

294 L = 1 - factor * self.lambdas_implicit 

295 L[L == 0] = 1 # to avoid potential divisions by zeros 

296 me[:] = rhs 

297 me /= L 

298 return me 

299 

300 def u_exact(self, t, u_init=None, t_init=None): 

301 """ 

302 Routine to compute the exact solution at time t. 

303 

304 Parameters 

305 ---------- 

306 t : float 

307 Time of the exact solution. 

308 u_init : pySDC.problem.testequation0d.dtype_u 

309 Initial solution. 

310 t_init : float 

311 The initial time. 

312 

313 Returns 

314 ------- 

315 me : dtype_u 

316 The exact solution. 

317 """ 

318 

319 u_init = (self.u0 if u_init is None else u_init) * 1.0 

320 t_init = 0.0 if t_init is None else t_init * 1.0 

321 

322 me = self.dtype_u(self.init) 

323 me[:] = u_init * self.xp.exp((t - t_init) * (self.lambdas_implicit + self.lambdas_explicit)) 

324 return me