Coverage for pySDC/implementations/problem_classes/GrayScott_MPIFFT.py: 99%

267 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-29 09:02 +0000

1import scipy.sparse as sp 

2from mpi4py import MPI 

3from mpi4py_fft import PFFT 

4 

5from pySDC.core.Errors import ProblemError 

6from pySDC.core.Problem import ptype, WorkCounter 

7from pySDC.implementations.datatype_classes.mesh import mesh, imex_mesh, comp2_mesh 

8from pySDC.implementations.problem_classes.generic_MPIFFT_Laplacian import IMEX_Laplacian_MPIFFT 

9 

10from mpi4py_fft import newDistArray 

11 

12 

13class grayscott_imex_diffusion(IMEX_Laplacian_MPIFFT): 

14 r""" 

15 The Gray-Scott system [1]_ describes a reaction-diffusion process of two substances :math:`u` and :math:`v`, 

16 where they diffuse over time. During the reaction :math:`u` is used up with overall decay rate :math:`B`, 

17 whereas :math:`v` is produced with feed rate :math:`A`. :math:`D_u,\, D_v` are the diffusion rates for 

18 :math:`u,\, v`. Here, the process is described by the :math:`N`-dimensional model 

19 

20 .. math:: 

21 \frac{\partial u}{\partial t} = D_u \Delta u - u v^2 + A (1 - u), 

22 

23 .. math:: 

24 \frac{\partial v}{\partial t} = D_v \Delta v + u v^2 - B u 

25 

26 in :math:`x \in \Omega:=[-L/2, L/2]^N` with :math:`N=2,3`. Spatial discretization is done by using 

27 Fast Fourier transformation for solving the linear parts provided by ``mpi4py-fft`` [2]_, see also 

28 https://mpi4py-fft.readthedocs.io/en/latest/. 

29 

30 This class implements the problem for *semi-explicit* time-stepping (diffusion is treated implicitly, and reaction 

31 is computed in explicit fashion). 

32 

33 Parameters 

34 ---------- 

35 nvars : tuple of int, optional 

36 Spatial resolution, i.e., number of degrees of freedom in space. Should be a tuple, e.g. ``nvars=(127, 127)``. 

37 Du : float, optional 

38 Diffusion rate for :math:`u`. 

39 Dv: float, optional 

40 Diffusion rate for :math:`v`. 

41 A : float, optional 

42 Feed rate for :math:`v`. 

43 B : float, optional 

44 Overall decay rate for :math:`u`. 

45 spectral : bool, optional 

46 If True, the solution is computed in spectral space. 

47 L : int, optional 

48 Denotes the period of the function to be approximated for the Fourier transform. 

49 comm : COMM_WORLD, optional 

50 Communicator for ``mpi4py-fft``. 

51 

52 Attributes 

53 ---------- 

54 fft : PFFT 

55 Object for parallel FFT transforms. 

56 X : mesh-grid 

57 Grid coordinates in real space. 

58 ndim : int 

59 Number of spatial dimensions. 

60 Ku : matrix 

61 Laplace operator in spectral space (u component). 

62 Kv : matrix 

63 Laplace operator in spectral space (v component). 

64 

65 References 

66 ---------- 

67 .. [1] Autocatalytic reactions in the isothermal, continuous stirred tank reactor: Isolas and other forms 

68 of multistability. P. Gray, S. K. Scott. Chem. Eng. Sci. 38, 1 (1983). 

69 .. [2] Lisandro Dalcin, Mikael Mortensen, David E. Keyes. Fast parallel multidimensional FFT using advanced MPI. 

70 Journal of Parallel and Distributed Computing (2019). 

71 .. [3] https://www.chebfun.org/examples/pde/GrayScott.html 

72 """ 

73 

74 def __init__(self, Du=1.0, Dv=0.01, A=0.09, B=0.086, **kwargs): 

75 kwargs['L'] = 2.0 

76 super().__init__(dtype='d', alpha=1.0, x0=-kwargs['L'] / 2.0, **kwargs) 

77 

78 # prepare the array with two components 

79 shape = (2,) + (self.init[0]) 

80 self.iU = 0 

81 self.iV = 1 

82 self.ncomp = 2 # needed for transfer class 

83 self.init = (shape, self.comm, self.xp.dtype('float')) 

84 

85 self._makeAttributeAndRegister('Du', 'Dv', 'A', 'B', localVars=locals(), readOnly=True) 

86 

87 # prepare "Laplacians" 

88 self.Ku = -self.Du * self.K2 

89 self.Kv = -self.Dv * self.K2 

90 

91 def eval_f(self, u, t): 

92 """ 

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

94 

95 Parameters 

96 ---------- 

97 u : dtype_u 

98 Current values of the numerical solution. 

99 t : float 

100 Current time of the numerical solution is computed. 

101 

102 Returns 

103 ------- 

104 f : dtype_f 

105 The right-hand side of the problem. 

106 """ 

107 

108 f = self.dtype_f(self.init) 

109 

110 if self.spectral: 

111 f.impl[0, ...] = self.Ku * u[0, ...] 

112 f.impl[1, ...] = self.Kv * u[1, ...] 

113 tmpu = newDistArray(self.fft, False) 

114 tmpv = newDistArray(self.fft, False) 

115 tmpu[:] = self.fft.backward(u[0, ...], tmpu) 

116 tmpv[:] = self.fft.backward(u[1, ...], tmpv) 

117 tmpfu = -tmpu * tmpv**2 + self.A * (1 - tmpu) 

118 tmpfv = tmpu * tmpv**2 - self.B * tmpv 

119 f.expl[0, ...] = self.fft.forward(tmpfu) 

120 f.expl[1, ...] = self.fft.forward(tmpfv) 

121 

122 else: 

123 u_hat = self.fft.forward(u[0, ...]) 

124 lap_u_hat = self.Ku * u_hat 

125 f.impl[0, ...] = self.fft.backward(lap_u_hat, f.impl[0, ...]) 

126 u_hat = self.fft.forward(u[1, ...]) 

127 lap_u_hat = self.Kv * u_hat 

128 f.impl[1, ...] = self.fft.backward(lap_u_hat, f.impl[1, ...]) 

129 f.expl[0, ...] = -u[0, ...] * u[1, ...] ** 2 + self.A * (1 - u[0, ...]) 

130 f.expl[1, ...] = u[0, ...] * u[1, ...] ** 2 - self.B * u[1, ...] 

131 

132 self.work_counters['rhs']() 

133 return f 

134 

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

136 """ 

137 Simple FFT solver for the diffusion part. 

138 

139 Parameters 

140 ---------- 

141 rhs : dtype_f 

142 Right-hand side for the linear system. 

143 factor : float 

144 Abbrev. for the node-to-node stepsize (or any other factor required). 

145 u0 : dtype_u 

146 Initial guess for the iterative solver (not used here so far). 

147 t : float 

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

149 

150 Returns 

151 ------- 

152 me : dtype_u 

153 Solution. 

154 """ 

155 

156 me = self.dtype_u(self.init) 

157 if self.spectral: 

158 me[0, ...] = rhs[0, ...] / (1.0 - factor * self.Ku) 

159 me[1, ...] = rhs[1, ...] / (1.0 - factor * self.Kv) 

160 

161 else: 

162 rhs_hat = self.fft.forward(rhs[0, ...]) 

163 rhs_hat /= 1.0 - factor * self.Ku 

164 me[0, ...] = self.fft.backward(rhs_hat, me[0, ...]) 

165 rhs_hat = self.fft.forward(rhs[1, ...]) 

166 rhs_hat /= 1.0 - factor * self.Kv 

167 me[1, ...] = self.fft.backward(rhs_hat, me[1, ...]) 

168 

169 return me 

170 

171 def u_exact(self, t): 

172 r""" 

173 Routine to compute the exact solution at time :math:`t = 0`, see [3]_. 

174 

175 Parameters 

176 ---------- 

177 t : float 

178 Time of the exact solution. 

179 

180 Returns 

181 ------- 

182 me : dtype_u 

183 Exact solution. 

184 """ 

185 assert t == 0.0, 'Exact solution only valid as initial condition' 

186 assert self.ndim == 2, 'The initial conditions are 2D for now..' 

187 

188 me = self.dtype_u(self.init, val=0.0) 

189 

190 # This assumes that the box is [-L/2, L/2]^2 

191 if self.spectral: 

192 tmp = 1.0 - self.xp.exp(-80.0 * ((self.X[0] + 0.05) ** 2 + (self.X[1] + 0.02) ** 2)) 

193 me[0, ...] = self.fft.forward(tmp) 

194 tmp = self.xp.exp(-80.0 * ((self.X[0] - 0.05) ** 2 + (self.X[1] - 0.02) ** 2)) 

195 me[1, ...] = self.fft.forward(tmp) 

196 else: 

197 me[0, ...] = 1.0 - self.xp.exp(-80.0 * ((self.X[0] + 0.05) ** 2 + (self.X[1] + 0.02) ** 2)) 

198 me[1, ...] = self.xp.exp(-80.0 * ((self.X[0] - 0.05) ** 2 + (self.X[1] - 0.02) ** 2)) 

199 

200 return me 

201 

202 

203class grayscott_imex_linear(grayscott_imex_diffusion): 

204 r""" 

205 The Gray-Scott system [1]_ describes a reaction-diffusion process of two substances :math:`u` and :math:`v`, 

206 where they diffuse over time. During the reaction :math:`u` is used up with overall decay rate :math:`B`, 

207 whereas :math:`v` is produced with feed rate :math:`A`. :math:`D_u,\, D_v` are the diffusion rates for 

208 :math:`u,\, v`. The model with linear (reaction) part is described by the :math:`N`-dimensional model 

209 

210 .. math:: 

211 \frac{d u}{d t} = D_u \Delta u - u v^2 + A, 

212 

213 .. math:: 

214 \frac{d v}{d t} = D_v \Delta v + u v^2 

215 

216 in :math:`x \in \Omega:=[-L/2, L/2]^N` with :math:`N=2,3`. Spatial discretization is done by using 

217 Fast Fourier transformation for solving the linear parts provided by ``mpi4py-fft`` [2]_, see also 

218 https://mpi4py-fft.readthedocs.io/en/latest/. 

219 

220 This class implements the problem for *semi-explicit* time-stepping (diffusion is treated implicitly, and linear 

221 part is computed in an explicit way). 

222 """ 

223 

224 def __init__(self, **kwargs): 

225 super().__init__(**kwargs) 

226 self.Ku -= self.A 

227 self.Kv -= self.B 

228 

229 def eval_f(self, u, t): 

230 """ 

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

232 

233 Parameters 

234 ---------- 

235 u : dtype_u 

236 Current values of the numerical solution. 

237 t : float 

238 Current time of the numerical solution is computed. 

239 

240 Returns 

241 ------- 

242 f : dtype_f 

243 The right-hand side of the problem. 

244 """ 

245 

246 f = self.dtype_f(self.init) 

247 

248 if self.spectral: 

249 f.impl[0, ...] = self.Ku * u[0, ...] 

250 f.impl[1, ...] = self.Kv * u[1, ...] 

251 tmpu = newDistArray(self.fft, False) 

252 tmpv = newDistArray(self.fft, False) 

253 tmpu[:] = self.fft.backward(u[0, ...], tmpu) 

254 tmpv[:] = self.fft.backward(u[1, ...], tmpv) 

255 tmpfu = -tmpu * tmpv**2 + self.A 

256 tmpfv = tmpu * tmpv**2 

257 f.expl[0, ...] = self.fft.forward(tmpfu) 

258 f.expl[1, ...] = self.fft.forward(tmpfv) 

259 

260 else: 

261 u_hat = self.fft.forward(u[0, ...]) 

262 lap_u_hat = self.Ku * u_hat 

263 f.impl[0, ...] = self.fft.backward(lap_u_hat, f.impl[0, ...]) 

264 u_hat = self.fft.forward(u[1, ...]) 

265 lap_u_hat = self.Kv * u_hat 

266 f.impl[1, ...] = self.fft.backward(lap_u_hat, f.impl[1, ...]) 

267 f.expl[0, ...] = -u[0, ...] * u[1, ...] ** 2 + self.A 

268 f.expl[1, ...] = u[0, ...] * u[1, ...] ** 2 

269 

270 self.work_counters['rhs']() 

271 return f 

272 

273 

274class grayscott_mi_diffusion(grayscott_imex_diffusion): 

275 r""" 

276 The Gray-Scott system [1]_ describes a reaction-diffusion process of two substances :math:`u` and :math:`v`, 

277 where they diffuse over time. During the reaction :math:`u` is used up with overall decay rate :math:`B`, 

278 whereas :math:`v` is produced with feed rate :math:`A`. :math:`D_u,\, D_v` are the diffusion rates for 

279 :math:`u,\, v`. Here, the process is described by the :math:`N`-dimensional model 

280 

281 .. math:: 

282 \frac{\partial u}{\partial t} = D_u \Delta u - u v^2 + A (1 - u), 

283 

284 .. math:: 

285 \frac{\partial v}{\partial t} = D_v \Delta v + u v^2 - B u 

286 

287 in :math:`x \in \Omega:=[-L/2, L/2]^N` with :math:`N=2,3`. Spatial discretization is done by using 

288 Fast Fourier transformation for solving the linear parts provided by ``mpi4py-fft`` [2]_, see also 

289 https://mpi4py-fft.readthedocs.io/en/latest/. 

290 

291 This class implements the problem for *multi-implicit* time-stepping, i.e., both diffusion and reaction part will be treated 

292 implicitly. 

293 

294 Parameters 

295 ---------- 

296 nvars : tuple of int, optional 

297 Spatial resolution, i.e., number of degrees of freedom in space. Should be a tuple, e.g. ``nvars=(127, 127)``. 

298 Du : float, optional 

299 Diffusion rate for :math:`u`. 

300 Dv: float, optional 

301 Diffusion rate for :math:`v`. 

302 A : float, optional 

303 Feed rate for :math:`v`. 

304 B : float, optional 

305 Overall decay rate for :math:`u`. 

306 spectral : bool, optional 

307 If True, the solution is computed in spectral space. 

308 L : int, optional 

309 Denotes the period of the function to be approximated for the Fourier transform. 

310 comm : COMM_WORLD, optional 

311 Communicator for ``mpi4py-fft``. 

312 

313 Attributes 

314 ---------- 

315 fft : PFFT 

316 Object for parallel FFT transforms. 

317 X : mesh-grid 

318 Grid coordinates in real space. 

319 ndim : int 

320 Number of spatial dimensions. 

321 Ku : matrix 

322 Laplace operator in spectral space (u component). 

323 Kv : matrix 

324 Laplace operator in spectral space (v component). 

325 

326 References 

327 ---------- 

328 .. [1] Autocatalytic reactions in the isothermal, continuous stirred tank reactor: Isolas and other forms 

329 of multistability. P. Gray, S. K. Scott. Chem. Eng. Sci. 38, 1 (1983). 

330 .. [2] Lisandro Dalcin, Mikael Mortensen, David E. Keyes. Fast parallel multidimensional FFT using advanced MPI. 

331 Journal of Parallel and Distributed Computing (2019). 

332 """ 

333 

334 dtype_f = comp2_mesh 

335 

336 def __init__( 

337 self, 

338 newton_maxiter=100, 

339 newton_tol=1e-12, 

340 **kwargs, 

341 ): 

342 """Initialization routine""" 

343 super().__init__(**kwargs) 

344 # This may not run in parallel yet.. 

345 assert self.comm.Get_size() == 1 

346 self.work_counters['newton'] = WorkCounter() 

347 self.Ku = -self.Du * self.K2 

348 self.Kv = -self.Dv * self.K2 

349 self._makeAttributeAndRegister('newton_maxiter', 'newton_tol', localVars=locals(), readOnly=False) 

350 

351 def eval_f(self, u, t): 

352 """ 

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

354 

355 Parameters 

356 ---------- 

357 u : dtype_u 

358 Current values of the numerical solution. 

359 t : float 

360 Current time of the numerical solution is computed. 

361 

362 Returns 

363 ------- 

364 f : dtype_f 

365 The right-hand side of the problem. 

366 """ 

367 

368 f = self.dtype_f(self.init) 

369 

370 if self.spectral: 

371 f.comp1[0, ...] = self.Ku * u[0, ...] 

372 f.comp1[1, ...] = self.Kv * u[1, ...] 

373 tmpu = newDistArray(self.fft, False) 

374 tmpv = newDistArray(self.fft, False) 

375 tmpu[:] = self.fft.backward(u[0, ...], tmpu) 

376 tmpv[:] = self.fft.backward(u[1, ...], tmpv) 

377 tmpfu = -tmpu * tmpv**2 + self.A * (1 - tmpu) 

378 tmpfv = tmpu * tmpv**2 - self.B * tmpv 

379 f.comp2[0, ...] = self.fft.forward(tmpfu) 

380 f.comp2[1, ...] = self.fft.forward(tmpfv) 

381 

382 else: 

383 u_hat = self.fft.forward(u[0, ...]) 

384 lap_u_hat = self.Ku * u_hat 

385 f.comp1[0, ...] = self.fft.backward(lap_u_hat, f.comp1[0, ...]) 

386 u_hat = self.fft.forward(u[1, ...]) 

387 lap_u_hat = self.Kv * u_hat 

388 f.comp1[1, ...] = self.fft.backward(lap_u_hat, f.comp1[1, ...]) 

389 f.comp2[0, ...] = -u[0, ...] * u[1, ...] ** 2 + self.A * (1 - u[0, ...]) 

390 f.comp2[1, ...] = u[0, ...] * u[1, ...] ** 2 - self.B * u[1, ...] 

391 

392 self.work_counters['rhs']() 

393 return f 

394 

395 def solve_system_1(self, rhs, factor, u0, t): 

396 """ 

397 Solver for the first component, can just call the super function. 

398 

399 Parameters 

400 ---------- 

401 rhs : dtype_f 

402 Right-hand side for the linear system. 

403 factor : float 

404 Abbrev. for the node-to-node stepsize (or any other factor required). 

405 u0 : dtype_u 

406 Initial guess for the iterative solver (not used here so far). 

407 t : float 

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

409 

410 Returns 

411 ------- 

412 me : dtype_u 

413 The solution as mesh. 

414 """ 

415 

416 me = super().solve_system(rhs, factor, u0, t) 

417 return me 

418 

419 def solve_system_2(self, rhs, factor, u0, t): 

420 """ 

421 Newton-Solver for the second component. 

422 

423 Parameters 

424 ---------- 

425 rhs : dtype_f 

426 Right-hand side for the linear system. 

427 factor float 

428 Abbrev. for the node-to-node stepsize (or any other factor required). 

429 u0 : dtype_u 

430 Initial guess for the iterative solver (not used here so far). 

431 t : float 

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

433 

434 Returns 

435 ------- 

436 me : dtype_u 

437 The solution as mesh. 

438 """ 

439 u = self.dtype_u(u0) 

440 

441 if self.spectral: 

442 tmpu = newDistArray(self.fft, False) 

443 tmpv = newDistArray(self.fft, False) 

444 tmpu[:] = self.fft.backward(u[0, ...], tmpu) 

445 tmpv[:] = self.fft.backward(u[1, ...], tmpv) 

446 tmprhsu = newDistArray(self.fft, False) 

447 tmprhsv = newDistArray(self.fft, False) 

448 tmprhsu[:] = self.fft.backward(rhs[0, ...], tmprhsu) 

449 tmprhsv[:] = self.fft.backward(rhs[1, ...], tmprhsv) 

450 

451 else: 

452 tmpu = u[0, ...] 

453 tmpv = u[1, ...] 

454 tmprhsu = rhs[0, ...] 

455 tmprhsv = rhs[1, ...] 

456 

457 # start newton iteration 

458 n = 0 

459 res = 99 

460 while n < self.newton_maxiter: 

461 # print(n, res) 

462 # form the function g with g(u) = 0 

463 tmpgu = tmpu - tmprhsu - factor * (-tmpu * tmpv**2 + self.A * (1 - tmpu)) 

464 tmpgv = tmpv - tmprhsv - factor * (tmpu * tmpv**2 - self.B * tmpv) 

465 

466 # if g is close to 0, then we are done 

467 res = max(self.xp.linalg.norm(tmpgu, self.xp.inf), self.xp.linalg.norm(tmpgv, self.xp.inf)) 

468 if res < self.newton_tol: 

469 break 

470 

471 # assemble dg 

472 dg00 = 1 - factor * (-(tmpv**2) - self.A) 

473 dg01 = -factor * (-2 * tmpu * tmpv) 

474 dg10 = -factor * (tmpv**2) 

475 dg11 = 1 - factor * (2 * tmpu * tmpv - self.B) 

476 

477 # interleave and unravel to put into sparse matrix 

478 dg00I = self.xp.ravel(self.xp.kron(dg00, self.xp.array([1, 0]))) 

479 dg01I = self.xp.ravel(self.xp.kron(dg01, self.xp.array([1, 0]))) 

480 dg10I = self.xp.ravel(self.xp.kron(dg10, self.xp.array([1, 0]))) 

481 dg11I = self.xp.ravel(self.xp.kron(dg11, self.xp.array([0, 1]))) 

482 

483 # put into sparse matrix 

484 dg = sp.diags(dg00I, offsets=0) + sp.diags(dg11I, offsets=0) 

485 dg += sp.diags(dg01I, offsets=1, shape=dg.shape) + sp.diags(dg10I, offsets=-1, shape=dg.shape) 

486 

487 # interleave g terms to apply inverse to it 

488 g = self.xp.kron(tmpgu.flatten(), self.xp.array([1, 0])) + self.xp.kron( 

489 tmpgv.flatten(), self.xp.array([0, 1]) 

490 ) 

491 # invert dg matrix 

492 b = sp.linalg.spsolve(dg, g) 

493 # update real space vectors 

494 tmpu[:] -= b[::2].reshape(self.nvars) 

495 tmpv[:] -= b[1::2].reshape(self.nvars) 

496 

497 # increase iteration count 

498 n += 1 

499 self.work_counters['newton']() 

500 

501 if self.xp.isnan(res) and self.stop_at_nan: 

502 raise ProblemError('Newton got nan after %i iterations, aborting...' % n) 

503 elif self.xp.isnan(res): 

504 self.logger.warning('Newton got nan after %i iterations...' % n) 

505 

506 if n == self.newton_maxiter: 

507 self.logger.warning('Newton did not converge after %i iterations, error is %s' % (n, res)) 

508 

509 me = self.dtype_u(self.init) 

510 if self.spectral: 

511 me[0, ...] = self.fft.forward(tmpu) 

512 me[1, ...] = self.fft.forward(tmpv) 

513 else: 

514 me[0, ...] = tmpu 

515 me[1, ...] = tmpv 

516 return me 

517 

518 

519class grayscott_mi_linear(grayscott_imex_linear): 

520 r""" 

521 The original Gray-Scott system [1]_ describes a reaction-diffusion process of two substances :math:`u` and :math:`v`, 

522 where they diffuse over time. During the reaction :math:`u` is used up with overall decay rate :math:`B`, 

523 whereas :math:`v` is produced with feed rate :math:`A`. :math:`D_u,\, D_v` are the diffusion rates for 

524 :math:`u,\, v`. The model with linear (reaction) part is described by the :math:`N`-dimensional model 

525 

526 .. math:: 

527 \frac{\partial u}{\partial t} = D_u \Delta u - u v^2 + A, 

528 

529 .. math:: 

530 \frac{\partial v}{\partial t} = D_v \Delta v + u v^2 

531 

532 in :math:`x \in \Omega:=[-L/2, L/2]^N` with :math:`N=2,3`. Spatial discretization is done by using 

533 Fast Fourier transformation for solving the linear parts provided by ``mpi4py-fft`` [2]_, see also 

534 https://mpi4py-fft.readthedocs.io/en/latest/. 

535 

536 The problem in this class will be treated in a *multi-implicit* way for time-stepping, i.e., for the system containing 

537 the diffusion part will be solved by FFT, and for the linear part a Newton solver is used. 

538 """ 

539 

540 dtype_f = comp2_mesh 

541 

542 def __init__( 

543 self, 

544 newton_maxiter=100, 

545 newton_tol=1e-12, 

546 **kwargs, 

547 ): 

548 """Initialization routine""" 

549 super().__init__(**kwargs) 

550 # This may not run in parallel yet.. 

551 assert self.comm.Get_size() == 1 

552 self.work_counters['newton'] = WorkCounter() 

553 self.Ku = -self.Du * self.K2 - self.A 

554 self.Kv = -self.Dv * self.K2 - self.B 

555 self._makeAttributeAndRegister('newton_maxiter', 'newton_tol', localVars=locals(), readOnly=False) 

556 

557 def eval_f(self, u, t): 

558 """ 

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

560 

561 Parameters 

562 ---------- 

563 u : dtype_u 

564 Current values of the numerical solution. 

565 t : float 

566 Current time of the numerical solution is computed. 

567 

568 Returns 

569 ------- 

570 f : dtype_f 

571 The right-hand side of the problem. 

572 """ 

573 

574 f = self.dtype_f(self.init) 

575 

576 if self.spectral: 

577 f.comp1[0, ...] = self.Ku * u[0, ...] 

578 f.comp1[1, ...] = self.Kv * u[1, ...] 

579 tmpu = newDistArray(self.fft, False) 

580 tmpv = newDistArray(self.fft, False) 

581 tmpu[:] = self.fft.backward(u[0, ...], tmpu) 

582 tmpv[:] = self.fft.backward(u[1, ...], tmpv) 

583 tmpfu = -tmpu * tmpv**2 + self.A 

584 tmpfv = tmpu * tmpv**2 

585 f.comp2[0, ...] = self.fft.forward(tmpfu) 

586 f.comp2[1, ...] = self.fft.forward(tmpfv) 

587 

588 else: 

589 u_hat = self.fft.forward(u[0, ...]) 

590 lap_u_hat = self.Ku * u_hat 

591 f.comp1[0, ...] = self.fft.backward(lap_u_hat, f.comp1[0, ...]) 

592 u_hat = self.fft.forward(u[1, ...]) 

593 lap_u_hat = self.Kv * u_hat 

594 f.comp1[1, ...] = self.fft.backward(lap_u_hat, f.comp1[1, ...]) 

595 f.comp2[0, ...] = -u[0, ...] * u[1, ...] ** 2 + self.A 

596 f.comp2[1, ...] = u[0, ...] * u[1, ...] ** 2 

597 

598 self.work_counters['rhs']() 

599 return f 

600 

601 def solve_system_1(self, rhs, factor, u0, t): 

602 """ 

603 Solver for the first component, can just call the super function. 

604 

605 Parameters 

606 ---------- 

607 rhs : dtype_f 

608 Right-hand side for the linear system. 

609 factor : float 

610 Abbrev. for the node-to-node stepsize (or any other factor required). 

611 u0 : dtype_u 

612 Initial guess for the iterative solver (not used here so far). 

613 t : float 

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

615 

616 Returns 

617 ------- 

618 me : dtype_u 

619 The solution as mesh. 

620 """ 

621 

622 me = super(grayscott_mi_linear, self).solve_system(rhs, factor, u0, t) 

623 return me 

624 

625 def solve_system_2(self, rhs, factor, u0, t): 

626 """ 

627 Newton-Solver for the second component. 

628 

629 Parameters 

630 ---------- 

631 rhs : dtype_f 

632 Right-hand side for the linear system. 

633 factor : float 

634 Abbrev. for the node-to-node stepsize (or any other factor required). 

635 u0 : dtype_u 

636 Initial guess for the iterative solver (not used here so far). 

637 t : float 

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

639 

640 Returns 

641 ------- 

642 u : dtype_u 

643 The solution as mesh. 

644 """ 

645 u = self.dtype_u(u0) 

646 

647 if self.spectral: 

648 tmpu = newDistArray(self.fft, False) 

649 tmpv = newDistArray(self.fft, False) 

650 tmpu[:] = self.fft.backward(u[0, ...], tmpu) 

651 tmpv[:] = self.fft.backward(u[1, ...], tmpv) 

652 tmprhsu = newDistArray(self.fft, False) 

653 tmprhsv = newDistArray(self.fft, False) 

654 tmprhsu[:] = self.fft.backward(rhs[0, ...], tmprhsu) 

655 tmprhsv[:] = self.fft.backward(rhs[1, ...], tmprhsv) 

656 

657 else: 

658 tmpu = u[0, ...] 

659 tmpv = u[1, ...] 

660 tmprhsu = rhs[0, ...] 

661 tmprhsv = rhs[1, ...] 

662 

663 # start newton iteration 

664 n = 0 

665 res = 99 

666 while n < self.newton_maxiter: 

667 # print(n, res) 

668 # form the function g with g(u) = 0 

669 tmpgu = tmpu - tmprhsu - factor * (-tmpu * tmpv**2 + self.A) 

670 tmpgv = tmpv - tmprhsv - factor * (tmpu * tmpv**2) 

671 

672 # if g is close to 0, then we are done 

673 res = max(self.xp.linalg.norm(tmpgu, self.xp.inf), self.xp.linalg.norm(tmpgv, self.xp.inf)) 

674 if res < self.newton_tol: 

675 break 

676 

677 # assemble dg 

678 dg00 = 1 - factor * (-(tmpv**2)) 

679 dg01 = -factor * (-2 * tmpu * tmpv) 

680 dg10 = -factor * (tmpv**2) 

681 dg11 = 1 - factor * (2 * tmpu * tmpv) 

682 

683 # interleave and unravel to put into sparse matrix 

684 dg00I = self.xp.ravel(self.xp.kron(dg00, self.xp.array([1, 0]))) 

685 dg01I = self.xp.ravel(self.xp.kron(dg01, self.xp.array([1, 0]))) 

686 dg10I = self.xp.ravel(self.xp.kron(dg10, self.xp.array([1, 0]))) 

687 dg11I = self.xp.ravel(self.xp.kron(dg11, self.xp.array([0, 1]))) 

688 

689 # put into sparse matrix 

690 dg = sp.diags(dg00I, offsets=0) + sp.diags(dg11I, offsets=0) 

691 dg += sp.diags(dg01I, offsets=1, shape=dg.shape) + sp.diags(dg10I, offsets=-1, shape=dg.shape) 

692 

693 # interleave g terms to apply inverse to it 

694 g = self.xp.kron(tmpgu.flatten(), self.xp.array([1, 0])) + self.xp.kron( 

695 tmpgv.flatten(), self.xp.array([0, 1]) 

696 ) 

697 # invert dg matrix 

698 b = sp.linalg.spsolve(dg, g) 

699 # update real-space vectors 

700 tmpu[:] -= b[::2].reshape(self.nvars) 

701 tmpv[:] -= b[1::2].reshape(self.nvars) 

702 

703 # increase iteration count 

704 n += 1 

705 self.work_counters['newton']() 

706 

707 if self.xp.isnan(res) and self.stop_at_nan: 

708 raise ProblemError('Newton got nan after %i iterations, aborting...' % n) 

709 elif self.xp.isnan(res): 

710 self.logger.warning('Newton got nan after %i iterations...' % n) 

711 

712 if n == self.newton_maxiter: 

713 self.logger.warning('Newton did not converge after %i iterations, error is %s' % (n, res)) 

714 

715 me = self.dtype_u(self.init) 

716 if self.spectral: 

717 me[0, ...] = self.fft.forward(tmpu) 

718 me[1, ...] = self.fft.forward(tmpv) 

719 else: 

720 me[0, ...] = tmpu 

721 me[1, ...] = tmpv 

722 return me