Coverage for pySDC/implementations/problem_classes/AllenCahn_2D_FD.py: 96%

212 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 sp 

3import scipy.sparse.linalg as spla 

4 

5from pySDC.core.errors import ParameterError, ProblemError 

6from pySDC.core.problem import Problem, WorkCounter 

7from pySDC.helpers import problem_helper 

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

9 

10# http://www.personal.psu.edu/qud2/Res/Pre/dz09sisc.pdf 

11 

12 

13# noinspection PyUnusedLocal 

14class allencahn_fullyimplicit(Problem): 

15 r""" 

16 2D periodic Allen-Cahn equation with finite differences, fully implicit with Newton. 

17 

18 Example implementing the two-dimensional Allen-Cahn equation with periodic boundary conditions, with the two 

19 phases at :math:`u = 0` and :math:`u = 1` 

20 

21 .. math:: 

22 \frac{\partial u}{\partial t} = \Delta u 

23 + \frac{1}{2\varepsilon^2} (2u - 1)\left(1 - (2u - 1)^\nu\right) 

24 

25 for a constant :math:`\nu`, which at the default :math:`\nu = 2` is the usual 

26 :math:`\Delta u - \frac{2}{\varepsilon^2} u (1 - u)(1 - 2u)`. 

27 

28 Initial condition are circles of the form 

29 

30 .. math:: 

31 u({\bf x}, 0) = \frac{1}{2}\left(1 + \tanh\left(\frac{r - \sqrt{x_i^2 + y_j^2}} 

32 {\sqrt{2}\varepsilon}\right)\right) 

33 

34 for :math:`i, j=0,..,N-1`, where :math:`N` is the number of spatial grid points. For time-stepping, the problem is 

35 treated *fully-implicitly*, i.e., the nonlinear system is solved by Newton. 

36 

37 Parameters 

38 ---------- 

39 nvars : tuple of int, optional 

40 Number of unknowns in the problem, e.g. ``nvars=(128, 128)``. 

41 nu : int, optional 

42 Exponent of the double well; :math:`\nu = 2` is the standard Allen-Cahn nonlinearity. 

43 eps : float, optional 

44 Scaling parameter :math:`\varepsilon`. 

45 newton_maxiter : int, optional 

46 Maximum number of iterations for the Newton solver. 

47 newton_tol : float, optional 

48 Tolerance for Newton's method to terminate. 

49 lin_tol : float, optional 

50 Tolerance for linear solver to terminate. 

51 lin_maxiter : int, optional 

52 Maximum number of iterations for the linear solver. 

53 inexact_linear_ratio : float, optional 

54 Ratio of tolerance of linear solver to the Newton residual, overrides ``lin_tol`` if set. 

55 radius : float, optional 

56 Radius of the circles. 

57 order : int, optional 

58 Order of the finite difference matrix. 

59 useGPU : bool, optional 

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

61 

62 Attributes 

63 ---------- 

64 A : scipy.spdiags 

65 Second-order FD discretization of the 2D laplace operator. 

66 dx : float 

67 Distance between two spatial nodes (same for both directions). 

68 xvalues : np.1darray 

69 Spatial grid points, here both dimensions have the same grid points. 

70 newton_ncalls : int 

71 Number of calls of the Newton solver. The iterations themselves are counted in 

72 ``work_counters['newton']``, and the linear ones in ``work_counters['linear']``. 

73 lin_ncalls : int 

74 Number of calls of the linear solver. 

75 """ 

76 

77 dtype_u = mesh 

78 dtype_f = mesh 

79 

80 xp = np 

81 xsp = sp 

82 linalg = spla 

83 

84 def setup_GPU(self): 

85 """ 

86 Switch the array, sparse and solver modules and the datatypes over to CuPy. 

87 

88 This changes the class, not the instance, as everything else in pySDC that does this 

89 does: once one instance of a class runs on the GPU, they all do. 

90 """ 

91 import cupy as cp 

92 import cupyx.scipy.sparse as csp 

93 import cupyx.scipy.sparse.linalg as cspla 

94 from pySDC.implementations.datatype_classes.cupy_mesh import cupy_mesh, imex_cupy_mesh, comp2_cupy_mesh 

95 

96 self.xp = cp 

97 self.xsp = csp 

98 self.linalg = cspla 

99 self.dtype_u = cupy_mesh 

100 # .get, not [], because this runs once per instance and the class keeps what it is given 

101 GPU_versions = {mesh: cupy_mesh, imex_mesh: imex_cupy_mesh, comp2_mesh: comp2_cupy_mesh} 

102 self.dtype_f = GPU_versions.get(self.dtype_f, self.dtype_f) 

103 

104 def __init__( 

105 self, 

106 nvars=(128, 128), 

107 nu=2, 

108 eps=0.04, 

109 newton_maxiter=200, 

110 newton_tol=1e-12, 

111 lin_tol=1e-8, 

112 lin_maxiter=100, 

113 inexact_linear_ratio=None, 

114 radius=0.25, 

115 order=2, 

116 useGPU=False, 

117 ): 

118 """Initialization routine""" 

119 if useGPU: 

120 self.setup_GPU() 

121 

122 # we assert that nvars looks very particular here.. this will be necessary for coarsening in space later on 

123 if len(nvars) != 2: 

124 raise ProblemError('this is a 2d example, got %s' % nvars) 

125 if nvars[0] != nvars[1]: 

126 raise ProblemError('need a square domain, got %s' % nvars) 

127 if nvars[0] % 2 != 0: 

128 raise ProblemError('the setup requires nvars = 2^p per dimension') 

129 

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

131 super().__init__((nvars, None, np.dtype('float64'))) 

132 self._makeAttributeAndRegister( 

133 'nvars', 

134 'nu', 

135 'eps', 

136 'radius', 

137 'order', 

138 'useGPU', 

139 localVars=locals(), 

140 readOnly=True, 

141 ) 

142 self._makeAttributeAndRegister( 

143 'newton_maxiter', 

144 'newton_tol', 

145 'lin_tol', 

146 'lin_maxiter', 

147 'inexact_linear_ratio', 

148 localVars=locals(), 

149 readOnly=False, 

150 ) 

151 

152 # compute dx and get discretization matrix A 

153 self.dx = 1.0 / self.nvars[0] 

154 self.A, _ = problem_helper.get_finite_difference_matrix( 

155 derivative=2, 

156 order=self.order, 

157 stencil_type='center', 

158 dx=self.dx, 

159 size=self.nvars[0], 

160 dim=2, 

161 bc='periodic', 

162 cupy=self.useGPU, 

163 ) 

164 self.xvalues = self.xp.arange(self.nvars[0]) * self.dx - 0.5 

165 

166 self.newton_ncalls = 0 

167 self.lin_ncalls = 0 

168 

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

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

171 self.work_counters['linear'] = WorkCounter() 

172 

173 def reaction(self, u): 

174 r""" 

175 The reaction term, :math:`\frac{1}{2\varepsilon^2}(2u - 1)\left(1 - (2u - 1)^\nu\right)`. 

176 

177 The wells sit at :math:`u = 0` and :math:`u = 1`, so the double well is symmetric about 

178 :math:`2u - 1`; writing the term in that variable is what lets :math:`\nu` keep the meaning 

179 it has always had here. For the default :math:`\nu = 2` this is 

180 :math:`-\frac{2}{\varepsilon^2} u (1 - u)(1 - 2u)`. 

181 """ 

182 v = 2.0 * u - 1.0 

183 return 0.5 / self.eps**2 * v * (1.0 - v**self.nu) 

184 

185 def reaction_prime(self, u): 

186 """Derivative of :meth:`reaction`, ready to go on the diagonal of a Jacobian.""" 

187 v = 2.0 * u - 1.0 

188 return 1.0 / self.eps**2 * (1.0 - (self.nu + 1.0) * v**self.nu) 

189 

190 def reaction_cubic(self, u): 

191 r"""The stiff part of :meth:`reaction`, :math:`-\frac{1}{2\varepsilon^2}(2u - 1)^{\nu + 1}`.""" 

192 return -0.5 / self.eps**2 * (2.0 * u - 1.0) ** (self.nu + 1) 

193 

194 def reaction_cubic_prime(self, u): 

195 """Derivative of :meth:`reaction_cubic`.""" 

196 return -(self.nu + 1.0) / self.eps**2 * (2.0 * u - 1.0) ** self.nu 

197 

198 def reaction_linear(self, u): 

199 r"""The rest of :meth:`reaction`, :math:`\frac{1}{2\varepsilon^2}(2u - 1)`, so the two sum back to it.""" 

200 return 0.5 / self.eps**2 * (2.0 * u - 1.0) 

201 

202 # noinspection PyTypeChecker 

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

204 """ 

205 Simple Newton solver. 

206 

207 Parameters 

208 ---------- 

209 rhs : dtype_f 

210 Right-hand side for the nonlinear system 

211 factor : float 

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

213 u0 : dtype_u 

214 Initial guess for the iterative solver. 

215 t : float 

216 Current time (required here for the BC). 

217 

218 Returns 

219 ------- 

220 me : dtype_u 

221 The solution as mesh. 

222 """ 

223 

224 # plain arrays, not the datatype: its `__abs__` is a norm, which CuPy's `linalg.norm` trips over 

225 u = u0.view(self.xp.ndarray).flatten() 

226 b = rhs.view(self.xp.ndarray).flatten() 

227 z = self.xp.zeros_like(u) 

228 

229 Id = self.xsp.eye(self.nvars[0] * self.nvars[1]) 

230 

231 # start newton iteration 

232 n = 0 

233 res = 99 

234 while n < self.newton_maxiter: 

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

236 g = u - factor * (self.A.dot(u) + self.reaction(u)) - b 

237 

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

239 res = self.xp.linalg.norm(g, self.xp.inf) 

240 

241 # do inexactness in the linear solver 

242 if self.inexact_linear_ratio: 

243 self.lin_tol = res * self.inexact_linear_ratio 

244 

245 if res < self.newton_tol: 

246 break 

247 

248 # assemble dg 

249 dg = Id - factor * (self.A + self.xsp.diags(self.reaction_prime(u), offsets=0)) 

250 

251 # newton update: u1 = u0 - g/dg 

252 # u -= spsolve(dg, g) 

253 u -= self.linalg.cg( 

254 dg, g, x0=z, rtol=self.lin_tol, maxiter=self.lin_maxiter, atol=0, callback=self.work_counters['linear'] 

255 )[0] 

256 # increase iteration count 

257 n += 1 

258 # print(n, res) 

259 

260 self.work_counters['newton']() 

261 

262 # if n == self.newton_maxiter: 

263 # raise ProblemError('Newton did not converge after %i iterations, error is %s' % (n, res)) 

264 

265 me = self.dtype_u(self.init) 

266 me[:] = u.reshape(self.nvars) 

267 

268 self.newton_ncalls += 1 

269 

270 return me 

271 

272 def eval_f(self, u, t): 

273 """ 

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

275 

276 Parameters 

277 ---------- 

278 u : dtype_u 

279 Current values of the numerical solution. 

280 t : float 

281 Current time of the numerical solution is computed (not used here). 

282 

283 Returns 

284 ------- 

285 f : dtype_f 

286 The right-hand side of the problem. 

287 """ 

288 f = self.dtype_f(self.init) 

289 v = u.flatten() 

290 f[:] = (self.A.dot(v) + self.reaction(v)).reshape(self.nvars) 

291 

292 self.work_counters['rhs']() 

293 return f 

294 

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

296 r""" 

297 Routine to compute the exact solution at time :math:`t`. 

298 

299 Parameters 

300 ---------- 

301 t : float 

302 Time of the exact solution. 

303 u_init : dtype_u, optional 

304 Initial conditions for getting the exact solution. 

305 t_init : float, optional 

306 The starting time. 

307 

308 Returns 

309 ------- 

310 me : dtype_u 

311 The exact solution. 

312 """ 

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

314 if t > 0: 

315 

316 def eval_rhs(t, u): 

317 return self.eval_f(u.reshape(self.init[0]), t).flatten() 

318 

319 me[:] = self.generate_scipy_reference_solution(eval_rhs, t, u_init, t_init) 

320 

321 else: 

322 X, Y = self.xp.meshgrid(self.xvalues, self.xvalues) 

323 r2 = X**2 + Y**2 

324 me[:] = 0.5 * (1.0 + self.xp.tanh((self.radius - self.xp.sqrt(r2)) / (np.sqrt(2) * self.eps))) 

325 

326 return me 

327 

328 

329# noinspection PyUnusedLocal 

330class allencahn_semiimplicit(allencahn_fullyimplicit): 

331 r""" 

332 2D periodic Allen-Cahn equation with finite differences, IMEX with Laplacian implicit, reaction explicit. 

333 

334 This class implements the two-dimensional Allen-Cahn equation with periodic boundary conditions, with the two 

335 phases at :math:`u = 0` and :math:`u = 1` 

336 

337 .. math:: 

338 \frac{\partial u}{\partial t} = \Delta u 

339 + \frac{1}{2\varepsilon^2} (2u - 1)\left(1 - (2u - 1)^\nu\right) 

340 

341 for a constant :math:`\nu`, which at the default :math:`\nu = 2` is the usual 

342 :math:`\Delta u - \frac{2}{\varepsilon^2} u (1 - u)(1 - 2u)`. 

343 

344 Initial condition are circles of the form 

345 

346 .. math:: 

347 u({\bf x}, 0) = \frac{1}{2}\left(1 + \tanh\left(\frac{r - \sqrt{x_i^2 + y_j^2}} 

348 {\sqrt{2}\varepsilon}\right)\right) 

349 

350 for :math:`i, j=0,..,N-1`, where :math:`N` is the number of spatial grid points. For time-stepping, the problem is 

351 treated in a *semi-implicit* way, i.e., the linear system containing the Laplacian is solved by the conjugate gradients 

352 method, and the system containing the rest of the right-hand side is only evaluated at each time. 

353 """ 

354 

355 dtype_f = imex_mesh 

356 

357 def eval_f(self, u, t): 

358 """ 

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

360 

361 Parameters 

362 ---------- 

363 u : dtype_u 

364 Current values of the numerical solution. 

365 t : float 

366 Current time of the numerical solution is computed (not used here). 

367 

368 Returns 

369 ------- 

370 f : dtype_f 

371 The right-hand side of the problem. 

372 """ 

373 f = self.dtype_f(self.init) 

374 v = u.flatten() 

375 f.impl[:] = self.A.dot(v).reshape(self.nvars) 

376 f.expl[:] = self.reaction(v).reshape(self.nvars) 

377 

378 self.work_counters['rhs']() 

379 return f 

380 

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

382 r""" 

383 Pointwise solver for :math:`\vec{u} - factor \cdot \frac{1}{2\varepsilon^2}(2\vec{u} - 1) = \vec{rhs}`. 

384 

385 Parameters 

386 ---------- 

387 rhs : dtype_f 

388 Right-hand side for the linear system. 

389 factor : float 

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

391 u0 : dtype_u 

392 Initial guess for the iterative solver. 

393 t : float 

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

395 

396 Returns 

397 ------- 

398 me : dtype_u 

399 The solution as mesh. 

400 """ 

401 

402 me = self.dtype_u(self.init) 

403 

404 Id = self.xsp.eye(self.nvars[0] * self.nvars[1]) 

405 

406 me[:] = self.linalg.cg( 

407 Id - factor * self.A, 

408 rhs.flatten(), 

409 x0=u0.flatten(), 

410 rtol=self.lin_tol, 

411 maxiter=self.lin_maxiter, 

412 atol=0, 

413 callback=self.work_counters['linear'], 

414 )[0].reshape(self.nvars) 

415 

416 self.lin_ncalls += 1 

417 

418 return me 

419 

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

421 """ 

422 Routine to compute the exact solution at time t. 

423 

424 Parameters 

425 ---------- 

426 t : float 

427 Time of the exact solution. 

428 u_init : dtype_u, optional 

429 Initial conditions for getting the exact solution. 

430 t_init : float, optional 

431 The starting time. 

432 

433 Returns 

434 ------- 

435 me : dtype_u 

436 The exact solution. 

437 """ 

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

439 if t > 0: 

440 

441 def eval_rhs(t, u): 

442 f = self.eval_f(u.reshape(self.init[0]), t) 

443 return (f.impl + f.expl).flatten() 

444 

445 me[:] = self.generate_scipy_reference_solution(eval_rhs, t, u_init, t_init) 

446 else: 

447 me[:] = super().u_exact(t, u_init, t_init) 

448 return me 

449 

450 

451# noinspection PyUnusedLocal 

452class allencahn_semiimplicit_v2(allencahn_fullyimplicit): 

453 r""" 

454 2D periodic Allen-Cahn equation with finite differences, IMEX with Laplacian and cubic term implicit. 

455 

456 This class implements the two-dimensional Allen-Cahn (AC) equation with periodic boundary conditions, with the two 

457 phases at :math:`u = 0` and :math:`u = 1` 

458 

459 .. math:: 

460 \frac{\partial u}{\partial t} = \Delta u 

461 + \frac{1}{2\varepsilon^2} (2u - 1)\left(1 - (2u - 1)^\nu\right) 

462 

463 for a constant :math:`\nu`, which at the default :math:`\nu = 2` is the usual 

464 :math:`\Delta u - \frac{2}{\varepsilon^2} u (1 - u)(1 - 2u)`. 

465 

466 Initial condition are circles of the form 

467 

468 .. math:: 

469 u({\bf x}, 0) = \frac{1}{2}\left(1 + \tanh\left(\frac{r - \sqrt{x_i^2 + y_j^2}} 

470 {\sqrt{2}\varepsilon}\right)\right) 

471 

472 for :math:`i, j=0,..,N-1`, where :math:`N` is the number of spatial grid points. For time-stepping, a special AC-splitting 

473 is used to get a *semi-implicit* treatment of the problem: The term :math:`\Delta u - \frac{1}{2\varepsilon^2}(2u - 1)^3` 

474 is handled implicitly and the nonlinear system including this part will be solved by Newton. :math:`\frac{1}{2\varepsilon^2}(2u - 1)` 

475 is only evaluated at each time. 

476 """ 

477 

478 dtype_f = imex_mesh 

479 

480 def eval_f(self, u, t): 

481 """ 

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

483 

484 Parameters 

485 ---------- 

486 u : dtype_u 

487 Current values of the numerical solution. 

488 t : float 

489 Current time of the numerical solution is computed. 

490 

491 Returns 

492 ------- 

493 f : dtype_f 

494 The right-hand side of the problem. 

495 """ 

496 f = self.dtype_f(self.init) 

497 v = u.flatten() 

498 f.impl[:] = (self.A.dot(v) + self.reaction_cubic(v)).reshape(self.nvars) 

499 f.expl[:] = self.reaction_linear(v).reshape(self.nvars) 

500 

501 self.work_counters['rhs']() 

502 return f 

503 

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

505 """ 

506 Simple Newton solver. 

507 

508 Parameters 

509 ---------- 

510 rhs : dtype_f 

511 Right-hand side for the nonlinear system. 

512 factor : float 

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

514 u0 : dtype_u 

515 Initial guess for the iterative solver. 

516 t : float 

517 Current time (required here for the BC). 

518 

519 Returns 

520 ------- 

521 me : dtype_u 

522 The solution as mesh. 

523 """ 

524 

525 # plain arrays, not the datatype: its `__abs__` is a norm, which CuPy's `linalg.norm` trips over 

526 u = u0.view(self.xp.ndarray).flatten() 

527 b = rhs.view(self.xp.ndarray).flatten() 

528 z = self.xp.zeros_like(u) 

529 

530 Id = self.xsp.eye(self.nvars[0] * self.nvars[1]) 

531 

532 # start newton iteration 

533 n = 0 

534 res = 99 

535 while n < self.newton_maxiter: 

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

537 g = u - factor * (self.A.dot(u) + self.reaction_cubic(u)) - b 

538 

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

540 res = self.xp.linalg.norm(g, self.xp.inf) 

541 

542 if res < self.newton_tol: 

543 break 

544 

545 # assemble dg 

546 dg = Id - factor * (self.A + self.xsp.diags(self.reaction_cubic_prime(u), offsets=0)) 

547 

548 # newton update: u1 = u0 - g/dg 

549 # u -= spsolve(dg, g) 

550 u -= self.linalg.cg(dg, g, x0=z, rtol=self.lin_tol, atol=0)[0] 

551 # increase iteration count 

552 n += 1 

553 # print(n, res) 

554 

555 self.work_counters['newton']() 

556 

557 # if n == self.newton_maxiter: 

558 # raise ProblemError('Newton did not converge after %i iterations, error is %s' % (n, res)) 

559 

560 me = self.dtype_u(self.init) 

561 me[:] = u.reshape(self.nvars) 

562 

563 self.newton_ncalls += 1 

564 

565 return me 

566 

567 

568# noinspection PyUnusedLocal 

569class allencahn_multiimplicit(allencahn_fullyimplicit): 

570 r""" 

571 2D periodic Allen-Cahn equation with finite differences, multi-implicit: Laplacian (CG), reaction (Newton). 

572 

573 Example implementing the two-dimensional Allen-Cahn equation with periodic boundary conditions, with the two 

574 phases at :math:`u = 0` and :math:`u = 1` 

575 

576 .. math:: 

577 \frac{\partial u}{\partial t} = \Delta u 

578 + \frac{1}{2\varepsilon^2} (2u - 1)\left(1 - (2u - 1)^\nu\right) 

579 

580 for a constant :math:`\nu`, which at the default :math:`\nu = 2` is the usual 

581 :math:`\Delta u - \frac{2}{\varepsilon^2} u (1 - u)(1 - 2u)`. 

582 

583 Initial condition are circles of the form 

584 

585 .. math:: 

586 u({\bf x}, 0) = \frac{1}{2}\left(1 + \tanh\left(\frac{r - \sqrt{x_i^2 + y_j^2}} 

587 {\sqrt{2}\varepsilon}\right)\right) 

588 

589 for :math:`i, j=0,..,N-1`, where :math:`N` is the number of spatial grid points. For time-stepping, the problem is 

590 treated in *multi-implicit* fashion, i.e., the linear system containing the Laplacian is solved by the conjugate gradients 

591 method, and the system containing the rest of the right-hand side will be solved by Newton's method. 

592 """ 

593 

594 dtype_f = comp2_mesh 

595 

596 def eval_f(self, u, t): 

597 """ 

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

599 

600 Parameters 

601 ---------- 

602 u : dtype_u 

603 Current values of the numerical solution. 

604 t : float 

605 Current time of the numerical solution is computed. 

606 

607 Returns 

608 ------- 

609 f : dtype_f 

610 The right-hand side of the problem. 

611 """ 

612 f = self.dtype_f(self.init) 

613 v = u.flatten() 

614 f.comp1[:] = self.A.dot(v).reshape(self.nvars) 

615 f.comp2[:] = self.reaction(v).reshape(self.nvars) 

616 

617 self.work_counters['rhs']() 

618 return f 

619 

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

621 r""" 

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

623 

624 Parameters 

625 ---------- 

626 rhs : dtype_f 

627 Right-hand side for the linear system. 

628 factor : float 

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

630 u0 : dtype_u 

631 Initial guess for the iterative solver. 

632 t : float 

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

634 

635 Returns 

636 ------- 

637 me : dtype_u 

638 The solution as mesh. 

639 """ 

640 

641 me = self.dtype_u(self.init) 

642 

643 Id = self.xsp.eye(self.nvars[0] * self.nvars[1]) 

644 

645 me[:] = self.linalg.cg( 

646 Id - factor * self.A, 

647 rhs.flatten(), 

648 x0=u0.flatten(), 

649 rtol=self.lin_tol, 

650 maxiter=self.lin_maxiter, 

651 atol=0, 

652 callback=self.work_counters['linear'], 

653 )[0].reshape(self.nvars) 

654 

655 self.lin_ncalls += 1 

656 

657 return me 

658 

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

660 """ 

661 Simple Newton solver. 

662 

663 Parameters 

664 ---------- 

665 rhs : dtype_f 

666 Right-hand side for the nonlinear system. 

667 factor : float 

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

669 u0 : dtype_u 

670 Initial guess for the iterative solver. 

671 t : float 

672 Current time (required here for the BC). 

673 

674 Returns 

675 ------- 

676 me : dtype_u 

677 The solution as mesh. 

678 """ 

679 

680 # plain arrays, not the datatype: its `__abs__` is a norm, which CuPy's `linalg.norm` trips over 

681 u = u0.view(self.xp.ndarray).flatten() 

682 b = rhs.view(self.xp.ndarray).flatten() 

683 z = self.xp.zeros_like(u) 

684 

685 Id = self.xsp.eye(self.nvars[0] * self.nvars[1]) 

686 

687 # start newton iteration 

688 n = 0 

689 res = 99 

690 while n < self.newton_maxiter: 

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

692 g = u - factor * self.reaction(u) - b 

693 

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

695 res = self.xp.linalg.norm(g, self.xp.inf) 

696 

697 if res < self.newton_tol: 

698 break 

699 

700 # assemble dg 

701 dg = Id - factor * self.xsp.diags(self.reaction_prime(u), offsets=0) 

702 

703 # newton update: u1 = u0 - g/dg 

704 # u -= spsolve(dg, g) 

705 u -= self.linalg.cg(dg, g, x0=z, rtol=self.lin_tol, atol=0)[0] 

706 # increase iteration count 

707 n += 1 

708 # print(n, res) 

709 

710 self.work_counters['newton']() 

711 

712 # if n == self.newton_maxiter: 

713 # raise ProblemError('Newton did not converge after %i iterations, error is %s' % (n, res)) 

714 

715 me = self.dtype_u(self.init) 

716 me[:] = u.reshape(self.nvars) 

717 

718 self.newton_ncalls += 1 

719 

720 return me 

721 

722 

723# noinspection PyUnusedLocal 

724class allencahn_multiimplicit_v2(allencahn_fullyimplicit): 

725 r""" 

726 2D periodic Allen-Cahn with finite differences, multi-implicit: Laplacian plus cubic term, linear term. 

727 

728 This class implements the two-dimensional Allen-Cahn (AC) equation with periodic boundary conditions, with the two 

729 phases at :math:`u = 0` and :math:`u = 1` 

730 

731 .. math:: 

732 \frac{\partial u}{\partial t} = \Delta u 

733 + \frac{1}{2\varepsilon^2} (2u - 1)\left(1 - (2u - 1)^\nu\right) 

734 

735 for a constant :math:`\nu`, which at the default :math:`\nu = 2` is the usual 

736 :math:`\Delta u - \frac{2}{\varepsilon^2} u (1 - u)(1 - 2u)`. 

737 

738 The initial condition has the form of circles 

739 

740 .. math:: 

741 u({\bf x}, 0) = \frac{1}{2}\left(1 + \tanh\left(\frac{r - \sqrt{x_i^2 + y_j^2}} 

742 {\sqrt{2}\varepsilon}\right)\right) 

743 

744 for :math:`i, j=0,..,N-1`, where :math:`N` is the number of spatial grid points. For time-stepping, a special AC-splitting 

745 is used here to get another kind of *semi-implicit* treatment of the problem: The term :math:`\Delta u - \frac{1}{2\varepsilon^2}(2u - 1)^3` 

746 is handled implicitly and the nonlinear system including this part will be solved by Newton. The linear reaction 

747 term :math:`\frac{1}{2\varepsilon^2}(2u - 1)` acts pointwise, so its implicit system is solved by a pointwise 

748 division. 

749 """ 

750 

751 dtype_f = comp2_mesh 

752 

753 def eval_f(self, u, t): 

754 """ 

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

756 

757 Parameters 

758 ---------- 

759 u : dtype_u 

760 Current values of the numerical solution. 

761 t : float 

762 Current time of the numerical solution is computed. 

763 

764 Returns 

765 ------- 

766 f : dtype_f 

767 The right-hand side of the problem. 

768 """ 

769 f = self.dtype_f(self.init) 

770 v = u.flatten() 

771 f.comp1[:] = (self.A.dot(v) + self.reaction_cubic(v)).reshape(self.nvars) 

772 f.comp2[:] = self.reaction_linear(v).reshape(self.nvars) 

773 

774 self.work_counters['rhs']() 

775 return f 

776 

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

778 """ 

779 Simple Newton solver. 

780 

781 Parameters 

782 ---------- 

783 rhs : dtype_f 

784 Right-hand side for the nonlinear system. 

785 factor : float 

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

787 u0 : dtype_u 

788 Initial guess for the iterative solver. 

789 t : float 

790 Current time (required here for the BC). 

791 

792 Returns 

793 ------ 

794 me : dtype_u 

795 The solution as mesh. 

796 """ 

797 

798 # plain arrays, not the datatype: its `__abs__` is a norm, which CuPy's `linalg.norm` trips over 

799 u = u0.view(self.xp.ndarray).flatten() 

800 b = rhs.view(self.xp.ndarray).flatten() 

801 z = self.xp.zeros_like(u) 

802 

803 Id = self.xsp.eye(self.nvars[0] * self.nvars[1]) 

804 

805 # start newton iteration 

806 n = 0 

807 res = 99 

808 while n < self.newton_maxiter: 

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

810 g = u - factor * (self.A.dot(u) + self.reaction_cubic(u)) - b 

811 

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

813 res = self.xp.linalg.norm(g, self.xp.inf) 

814 

815 if res < self.newton_tol: 

816 break 

817 

818 # assemble dg 

819 dg = Id - factor * (self.A + self.xsp.diags(self.reaction_cubic_prime(u), offsets=0)) 

820 

821 # newton update: u1 = u0 - g/dg 

822 # u -= spsolve(dg, g) 

823 u -= self.linalg.cg( 

824 dg, 

825 g, 

826 x0=z, 

827 rtol=self.lin_tol, 

828 atol=0, 

829 )[0] 

830 # increase iteration count 

831 n += 1 

832 # print(n, res) 

833 

834 self.work_counters['newton']() 

835 

836 # if n == self.newton_maxiter: 

837 # raise ProblemError('Newton did not converge after %i iterations, error is %s' % (n, res)) 

838 

839 me = self.dtype_u(self.init) 

840 me[:] = u.reshape(self.nvars) 

841 

842 self.newton_ncalls += 1 

843 

844 return me 

845 

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

847 r""" 

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

849 

850 Parameters 

851 ---------- 

852 rhs : dtype_f 

853 Right-hand side for the linear system. 

854 factor : float 

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

856 u0 : dtype_u 

857 Initial guess for the iterative solver. 

858 t : float 

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

860 

861 Returns 

862 ------- 

863 me : dtype_u 

864 The solution as mesh. 

865 """ 

866 

867 me = self.dtype_u(self.init) 

868 

869 me[:] = ((rhs - 0.5 * factor / self.eps**2) / (1.0 - factor / self.eps**2)).reshape(self.nvars) 

870 return me