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

243 statements  

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

1import numpy as np 

2from petsc4py import PETSc 

3 

4from pySDC.core.problem import Problem 

5from pySDC.implementations.datatype_classes.petsc_vec import petsc_vec, petsc_vec_imex, petsc_vec_comp2 

6 

7 

8class Fisher_full(object): 

9 r""" 

10 Helper class to generate residual and Jacobian matrix for PETSc's nonlinear solver SNES. 

11 

12 Parameters 

13 ---------- 

14 da : DMDA object 

15 Object of ``PETSc``. 

16 prob : problem instance 

17 Contains problem information for ``PETSc``. 

18 factor : float 

19 Temporal factor :math:`\Delta t Q_\Delta`. 

20 dx : float 

21 Grid spacing in x direction. 

22 

23 Attributes 

24 ---------- 

25 localX : PETSc vector object 

26 Local vector for ``PETSc``. 

27 xs, xe : int 

28 Defines the ranges for spatial domain. 

29 mx : tuple 

30 Get sizes for the vector containing the spatial points. 

31 row : PETSc matrix stencil object 

32 Row for a matrix. 

33 col : PETSc matrix stencil object 

34 Column for a matrix. 

35 """ 

36 

37 def __init__(self, da, prob, factor, dx): 

38 """Initialization routine""" 

39 assert da.getDim() == 1 

40 self.da = da 

41 self.factor = factor 

42 self.dx = dx 

43 self.prob = prob 

44 self.localX = da.createLocalVec() 

45 self.xs, self.xe = self.da.getRanges()[0] 

46 self.mx = self.da.getSizes()[0] 

47 self.row = PETSc.Mat.Stencil() 

48 self.col = PETSc.Mat.Stencil() 

49 

50 def formFunction(self, snes, X, F): 

51 r""" 

52 Function to evaluate the residual for the Newton solver. This function should be equal to the RHS 

53 in the solution. 

54 

55 Parameters 

56 ---------- 

57 snes : PETSc solver object 

58 Nonlinear solver object. 

59 X : PETSc vector object 

60 Input vector. 

61 F : PETSc vector object 

62 Output vector :math:`F(X)`. 

63 

64 Returns 

65 ------- 

66 None 

67 Overwrites F. 

68 """ 

69 self.da.globalToLocal(X, self.localX) 

70 x = self.da.getVecArray(self.localX) 

71 f = self.da.getVecArray(F) 

72 

73 for i in range(self.xs, self.xe): 

74 if i == 0 or i == self.mx - 1: 

75 f[i] = x[i] 

76 else: 

77 u = x[i] # center 

78 u_e = x[i + 1] # east 

79 u_w = x[i - 1] # west 

80 u_xx = (u_e - 2 * u + u_w) / self.dx**2 

81 f[i] = x[i] - self.factor * (u_xx + self.prob.lambda0**2 * x[i] * (1 - x[i] ** self.prob.nu)) 

82 

83 def formJacobian(self, snes, X, J, P): 

84 """ 

85 Function to return the Jacobian matrix. 

86 

87 Parameters 

88 ---------- 

89 snes : PETSc solver object 

90 Nonlinear solver object. 

91 X : PETSc vector object 

92 Input vector. 

93 J : PETSc matrix object 

94 Current Jacobian matrix. 

95 P : PETSc matrix object 

96 New Jacobian matrix. 

97 

98 Returns 

99 ------- 

100 PETSc.Mat.Structure.SAME_NONZERO_PATTERN 

101 Matrix status. 

102 """ 

103 self.da.globalToLocal(X, self.localX) 

104 x = self.da.getVecArray(self.localX) 

105 P.zeroEntries() 

106 

107 for i in range(self.xs, self.xe): 

108 self.row.i = i 

109 self.row.field = 0 

110 if i == 0 or i == self.mx - 1: 

111 P.setValueStencil(self.row, self.row, 1.0) 

112 else: 

113 diag = 1.0 - self.factor * ( 

114 -2.0 / self.dx**2 + self.prob.lambda0**2 * (1.0 - (self.prob.nu + 1) * x[i] ** self.prob.nu) 

115 ) 

116 for index, value in [ 

117 (i - 1, -self.factor / self.dx**2), 

118 (i, diag), 

119 (i + 1, -self.factor / self.dx**2), 

120 ]: 

121 self.col.i = index 

122 self.col.field = 0 

123 P.setValueStencil(self.row, self.col, value) 

124 P.assemble() 

125 if J != P: 

126 J.assemble() # matrix-free operator 

127 return PETSc.Mat.Structure.SAME_NONZERO_PATTERN 

128 

129 

130class Fisher_reaction(object): 

131 r""" 

132 Helper class to generate residual and Jacobian matrix for ``PETSc``'s nonlinear solver SNES. 

133 

134 Parameters 

135 ---------- 

136 da : DMDA object 

137 Object of ``PETSc``. 

138 prob : problem instance 

139 Contains problem information for ``PETSc``. 

140 factor : float 

141 Temporal factor :math:`\Delta t Q_\Delta`. 

142 

143 Attributes 

144 ---------- 

145 localX : PETSc vector object 

146 Local vector for ``PETSc``. 

147 """ 

148 

149 def __init__(self, da, prob, factor): 

150 """Initialization routine""" 

151 assert da.getDim() == 1 

152 self.da = da 

153 self.prob = prob 

154 self.factor = factor 

155 self.localX = da.createLocalVec() 

156 

157 def formFunction(self, snes, X, F): 

158 r""" 

159 Function to evaluate the residual for the Newton solver. This function should be equal to the RHS 

160 in the solution. 

161 

162 Parameters 

163 ---------- 

164 snes : PETSc solver object 

165 Nonlinear solver object. 

166 X : PETSc vector object 

167 Input vector. 

168 F : PETSc vector object 

169 Output vector :math:`F(X)`. 

170 

171 Returns 

172 ------- 

173 None 

174 Overwrites F. 

175 """ 

176 self.da.globalToLocal(X, self.localX) 

177 x = self.da.getVecArray(self.localX) 

178 f = self.da.getVecArray(F) 

179 mx = self.da.getSizes()[0] 

180 xs, xe = self.da.getRanges()[0] 

181 for i in range(xs, xe): 

182 if i == 0 or i == mx - 1: 

183 f[i] = x[i] 

184 else: 

185 f[i] = x[i] - self.factor * self.prob.lambda0**2 * x[i] * (1 - x[i] ** self.prob.nu) 

186 

187 def formJacobian(self, snes, X, J, P): 

188 """ 

189 Function to return the Jacobian matrix. 

190 

191 Parameters 

192 ---------- 

193 snes : PETSc solver object 

194 Nonlinear solver object. 

195 X : PETSc vector object 

196 Input vector. 

197 J : PETSc matrix object 

198 Current Jacobian matrix. 

199 P : PETSc matrix object 

200 New Jacobian matrix. 

201 

202 Returns 

203 ------- 

204 PETSc.Mat.Structure.SAME_NONZERO_PATTERN 

205 Matrix status. 

206 """ 

207 self.da.globalToLocal(X, self.localX) 

208 x = self.da.getVecArray(self.localX) 

209 P.zeroEntries() 

210 row = PETSc.Mat.Stencil() 

211 mx = self.da.getSizes()[0] 

212 xs, xe = self.da.getRanges()[0] 

213 for i in range(xs, xe): 

214 row.i = i 

215 row.field = 0 

216 if i == 0 or i == mx - 1: 

217 P.setValueStencil(row, row, 1.0) 

218 else: 

219 diag = 1.0 - self.factor * self.prob.lambda0**2 * (1.0 - (self.prob.nu + 1) * x[i] ** self.prob.nu) 

220 P.setValueStencil(row, row, diag) 

221 P.assemble() 

222 if J != P: 

223 J.assemble() # matrix-free operator 

224 return PETSc.Mat.Structure.SAME_NONZERO_PATTERN 

225 

226 

227class petsc_fisher_multiimplicit(Problem): 

228 r""" 

229 1D generalized Fisher equation with PETSc finite differences, multi-implicit: diffusion (CG), reaction (SNES). 

230 

231 The following one-dimensional problem is an example of a reaction-diffusion equation with traveling waves, and can 

232 be seen as a generalized Fisher equation. This class implements a special case of the Kolmogorov-Petrovskii-Piskunov 

233 problem [1]_ on a finite interval, where the first and last grid points keep their initial values (Dirichlet 

234 boundary conditions) 

235 

236 .. math:: 

237 \frac{\partial u}{\partial t} = \Delta u + \lambda_0^2 u (1 - u^\nu) 

238 

239 with exact solution 

240 

241 .. math:: 

242 u(x, 0) = \left[ 

243 1 + \left(2^{\nu / 2} - 1\right) \exp\left(-(\nu / 2)\sigma_1 x + 2 \lambda_1 t\right) 

244 \right]^{-2 / \nu} 

245 

246 for :math:`x \in \mathbb{R}`, and 

247 

248 .. math:: 

249 \sigma_1 = \lambda_1 - \sqrt{\lambda_1^2 - \lambda_0^2}, 

250 

251 .. math:: 

252 \lambda_1 = \frac{\lambda_0}{2} \left[ 

253 \left(1 + \frac{\nu}{2}\right)^{1/2} + \left(1 + \frac{\nu}{2}\right)^{-1/2} 

254 \right]. 

255 

256 This class is implemented to be solved in spatial using ``PETSc`` [2]_, [3]_. For time-stepping, the problem will be solved 

257 *multi-implicitly*. 

258 

259 Parameters 

260 ---------- 

261 nvars : int, optional 

262 Spatial resolution. 

263 lambda0 : float, optional 

264 Problem parameter : math:`\lambda_0`. 

265 nu : float, optional 

266 Problem parameter :math:`\nu`. 

267 interval : tuple, optional 

268 Defines the spatial domain. 

269 comm : PETSc.COMM_WORLD, optional 

270 Communicator for PETSc. 

271 lsol_tol : float, optional 

272 Tolerance for linear solver to terminate. 

273 nlsol_tol : float, optional 

274 Tolerance for nonlinear solver to terminate. 

275 lsol_maxiter : int, optional 

276 Maximum number of iterations for linear solver. 

277 nlsol_maxiter : int, optional 

278 Maximum number of iterations for nonlinear solver. 

279 

280 Attributes 

281 ---------- 

282 dx : float 

283 Mesh grid width. 

284 xs, xe : int 

285 Define the ranges. 

286 A : PETSc matrix object 

287 Discretization matrix. 

288 localX : PETSc vector object 

289 Local vector for solution. 

290 ksp : PETSc solver object 

291 Linear solver object. 

292 snes : PETSc solver object 

293 Nonlinear solver object. 

294 F : PETSc vector object 

295 Global vector. 

296 J : PETSc matrix object 

297 Jacobi matrix. 

298 

299 References 

300 ---------- 

301 .. [1] Z. Feng. Traveling wave behavior for a generalized fisher equation. Chaos Solitons Fractals 38(2), 

302 481–488 (2008). 

303 .. [2] PETSc Web page. Satish Balay et al. https://petsc.org/ (2023). 

304 .. [3] Parallel distributed computing using Python. Lisandro D. Dalcin, Rodrigo R. Paz, Pablo A. Kler, 

305 Alejandro Cosimo. Advances in Water Resources (2011). 

306 """ 

307 

308 dtype_u = petsc_vec 

309 dtype_f = petsc_vec_comp2 

310 

311 def __init__( 

312 self, 

313 nvars, 

314 lambda0, 

315 nu, 

316 interval, 

317 comm=PETSc.COMM_WORLD, 

318 lsol_tol=1e-10, 

319 nlsol_tol=1e-10, 

320 lsol_maxiter=None, 

321 nlsol_maxiter=None, 

322 ): 

323 """Initialization routine""" 

324 # create DMDA object which will be used for all grid operations 

325 da = PETSc.DMDA().create([nvars], dof=1, stencil_width=1, comm=comm) 

326 

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

328 super().__init__(init=da) 

329 self._makeAttributeAndRegister( 

330 'nvars', 

331 'lambda0', 

332 'nu', 

333 'interval', 

334 'comm', 

335 'lsol_tol', 

336 'nlsol_tol', 

337 'lsol_maxiter', 

338 'nlsol_maxiter', 

339 localVars=locals(), 

340 readOnly=True, 

341 ) 

342 

343 # compute dx and get local ranges 

344 self.dx = (self.interval[1] - self.interval[0]) / (self.nvars - 1) 

345 self.xs, self.xe = self.init.getRanges()[0] 

346 

347 # compute discretization matrix A and identity 

348 self.A = self.__get_A() 

349 self.localX = self.init.createLocalVec() 

350 

351 # setup linear solver 

352 self.ksp = PETSc.KSP() 

353 self.ksp.create(comm=self.comm) 

354 self.ksp.setType('cg') 

355 pc = self.ksp.getPC() 

356 pc.setType('ilu') 

357 self.ksp.setInitialGuessNonzero(True) 

358 self.ksp.setFromOptions() 

359 self.ksp.setTolerances(rtol=self.lsol_tol, atol=self.lsol_tol, max_it=self.lsol_maxiter) 

360 self.ksp_itercount = 0 

361 self.ksp_ncalls = 0 

362 

363 # setup nonlinear solver 

364 self.snes = PETSc.SNES() 

365 self.snes.create(comm=self.comm) 

366 if self.nlsol_maxiter <= 1: 

367 self.snes.setType('ksponly') 

368 self.snes.getKSP().setType('cg') 

369 pc = self.snes.getKSP().getPC() 

370 pc.setType('ilu') 

371 # self.snes.setType('ngmres') 

372 self.snes.setFromOptions() 

373 self.snes.setTolerances( 

374 rtol=self.nlsol_tol, 

375 atol=self.nlsol_tol, 

376 stol=self.nlsol_tol, 

377 max_it=self.nlsol_maxiter, 

378 ) 

379 self.snes_itercount = 0 

380 self.snes_ncalls = 0 

381 self.F = self.init.createGlobalVec() 

382 self.J = self.init.createMatrix() 

383 

384 def __get_A(self): 

385 r""" 

386 Helper function to assemble ``PETSc`` matrix A. 

387 

388 Returns 

389 ------- 

390 A : PETSc matrix object 

391 Discretization matrix. 

392 """ 

393 # create matrix and set basic options 

394 A = self.init.createMatrix() 

395 A.setType('aij') # sparse 

396 A.setFromOptions() 

397 A.setPreallocationNNZ((3, 3)) 

398 A.setUp() 

399 

400 # fill matrix 

401 A.zeroEntries() 

402 row = PETSc.Mat.Stencil() 

403 col = PETSc.Mat.Stencil() 

404 mx = self.init.getSizes()[0] 

405 xs, xe = self.init.getRanges()[0] 

406 for i in range(xs, xe): 

407 row.i = i 

408 row.field = 0 

409 if i == 0 or i == mx - 1: 

410 A.setValueStencil(row, row, 1.0) 

411 else: 

412 diag = -2.0 / self.dx**2 

413 for index, value in [ 

414 (i - 1, 1.0 / self.dx**2), 

415 (i, diag), 

416 (i + 1, 1.0 / self.dx**2), 

417 ]: 

418 col.i = index 

419 col.field = 0 

420 A.setValueStencil(row, col, value) 

421 A.assemble() 

422 return A 

423 

424 def get_sys_mat(self, factor): 

425 """ 

426 Helper function to assemble the system matrix of the linear problem. 

427 

428 Parameters 

429 ---------- 

430 factor : float 

431 Factor to define the system matrix. 

432 

433 Returns 

434 ------- 

435 A : PETSc matrix object 

436 Matrix for the system to solve. 

437 """ 

438 

439 # create matrix and set basic options 

440 A = self.init.createMatrix() 

441 A.setType('aij') # sparse 

442 A.setFromOptions() 

443 A.setPreallocationNNZ((3, 3)) 

444 A.setUp() 

445 

446 # fill matrix 

447 A.zeroEntries() 

448 row = PETSc.Mat.Stencil() 

449 col = PETSc.Mat.Stencil() 

450 mx = self.init.getSizes()[0] 

451 xs, xe = self.init.getRanges()[0] 

452 for i in range(xs, xe): 

453 row.i = i 

454 row.field = 0 

455 if i == 0 or i == mx - 1: 

456 A.setValueStencil(row, row, 1.0) 

457 else: 

458 diag = 1.0 + factor * 2.0 / self.dx**2 

459 for index, value in [ 

460 (i - 1, -factor / self.dx**2), 

461 (i, diag), 

462 (i + 1, -factor / self.dx**2), 

463 ]: 

464 col.i = index 

465 col.field = 0 

466 A.setValueStencil(row, col, value) 

467 A.assemble() 

468 return A 

469 

470 def eval_f(self, u, t): 

471 """ 

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

473 

474 Parameters 

475 ---------- 

476 u : dtype_u 

477 Current values of the numerical solution. 

478 t : float 

479 Current time the numerical solution is computed. 

480 

481 Returns 

482 ------- 

483 f : dtype_f 

484 Right-hand side of the problem. 

485 """ 

486 

487 f = self.dtype_f(self.init) 

488 self.A.mult(u, f.comp1) 

489 fa1 = self.init.getVecArray(f.comp1) 

490 fa1[0] = 0 

491 fa1[-1] = 0 

492 

493 fa2 = self.init.getVecArray(f.comp2) 

494 xa = self.init.getVecArray(u) 

495 for i in range(self.xs, self.xe): 

496 fa2[i] = self.lambda0**2 * xa[i] * (1 - xa[i] ** self.nu) 

497 fa2[0] = 0 

498 fa2[-1] = 0 

499 

500 return f 

501 

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

503 r""" 

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

505 

506 Parameters 

507 ---------- 

508 rhs : dtype_f 

509 Right-hand side for the linear system. 

510 factor : float 

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

512 u0 : dtype_u 

513 Initial guess for the iterative solver. 

514 t : float 

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

516 

517 Returns 

518 ------- 

519 me : dtype_u 

520 Solution as mesh. 

521 """ 

522 

523 me = self.dtype_u(u0) 

524 

525 self.ksp.setOperators(self.get_sys_mat(factor)) 

526 self.ksp.solve(rhs, me) 

527 

528 self.ksp_itercount += self.ksp.getIterationNumber() 

529 self.ksp_ncalls += 1 

530 

531 return me 

532 

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

534 r""" 

535 Nonlinear solver for :math:`(I - factor \cdot F)(\vec{u}) = \vec{rhs}`. 

536 

537 Parameters 

538 ---------- 

539 rhs : dtype_f 

540 Right-hand side for the linear system. 

541 factor : float 

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

543 u0 : dtype_u 

544 Initial guess for the iterative solver. 

545 t : float 

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

547 

548 Returns 

549 ------- 

550 me : dtype_u 

551 Solution as mesh. 

552 """ 

553 

554 me = self.dtype_u(u0) 

555 target = Fisher_reaction(self.init, self, factor) 

556 

557 # assign residual function and Jacobian 

558 F = self.init.createGlobalVec() 

559 self.snes.setFunction(target.formFunction, F) 

560 J = self.init.createMatrix() 

561 self.snes.setJacobian(target.formJacobian, J) 

562 

563 self.snes.solve(rhs, me) 

564 

565 self.snes_itercount += self.snes.getIterationNumber() 

566 self.snes_ncalls += 1 

567 

568 return me 

569 

570 def u_exact(self, t): 

571 r""" 

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

573 

574 Parameters 

575 ---------- 

576 t : float 

577 Time of the exact solution. 

578 

579 Returns 

580 ------- 

581 me : dtype_u 

582 Exact solution. 

583 """ 

584 

585 lam1 = self.lambda0 / 2.0 * ((self.nu / 2.0 + 1) ** 0.5 + (self.nu / 2.0 + 1) ** (-0.5)) 

586 sig1 = lam1 - np.sqrt(lam1**2 - self.lambda0**2) 

587 me = self.dtype_u(self.init) 

588 xa = self.init.getVecArray(me) 

589 for i in range(self.xs, self.xe): 

590 xa[i] = ( 

591 1 

592 + (2 ** (self.nu / 2.0) - 1) 

593 * np.exp(-self.nu / 2.0 * sig1 * (self.interval[0] + (i + 1) * self.dx + 2 * lam1 * t)) 

594 ) ** (-2.0 / self.nu) 

595 

596 return me 

597 

598 

599class petsc_fisher_fullyimplicit(petsc_fisher_multiimplicit): 

600 r""" 

601 1D generalized Fisher equation with PETSc finite differences, fully implicit with PETSc's SNES. 

602 

603 The following one-dimensional problem is an example of a reaction-diffusion equation with traveling waves, and can 

604 be seen as a generalized Fisher equation. This class implements a special case of the Kolmogorov-Petrovskii-Piskunov 

605 problem [1]_ on a finite interval, where the first and last grid points keep their initial values (Dirichlet 

606 boundary conditions) 

607 

608 .. math:: 

609 \frac{\partial u}{\partial t} = \Delta u + \lambda_0^2 u (1 - u^\nu) 

610 

611 with exact solution 

612 

613 .. math:: 

614 u(x, 0) = \left[ 

615 1 + \left(2^{\nu / 2} - 1\right) \exp\left(-(\nu / 2)\sigma_1 x + 2 \lambda_1 t\right) 

616 \right]^{-2 / \nu} 

617 

618 for :math:`x \in \mathbb{R}`, and 

619 

620 .. math:: 

621 \sigma_1 = \lambda_1 - \sqrt{\lambda_1^2 - \lambda_0^2}, 

622 

623 .. math:: 

624 \lambda_1 = \frac{\lambda_0}{2} \left[ 

625 \left(1 + \frac{\nu}{2}\right)^{1/2} + \left(1 + \frac{\nu}{2}\right)^{-1/2} 

626 \right]. 

627 

628 This class is implemented to be solved in spatial using ``PETSc`` [2]_, [3]_. For time-stepping, the problem is treated 

629 *fully-implicitly*. 

630 """ 

631 

632 dtype_f = petsc_vec 

633 

634 def eval_f(self, u, t): 

635 """ 

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

637 

638 Parameters 

639 ---------- 

640 u : dtype_u 

641 Current values of the numerical solution. 

642 t : float 

643 Current time the numerical solution is computed. 

644 

645 Returns 

646 ------- 

647 f : dtype_f 

648 Right-hand side of the problem. 

649 """ 

650 

651 f = self.dtype_f(self.init) 

652 self.A.mult(u, f) 

653 

654 fa2 = self.init.getVecArray(f) 

655 xa = self.init.getVecArray(u) 

656 for i in range(self.xs, self.xe): 

657 fa2[i] += self.lambda0**2 * xa[i] * (1 - xa[i] ** self.nu) 

658 fa2[0] = 0 

659 fa2[-1] = 0 

660 

661 return f 

662 

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

664 r""" 

665 Nonlinear solver for :math:`(I - factor \cdot F)(\vec{u}) = \vec{rhs}`. 

666 

667 Parameters 

668 ---------- 

669 rhs : dtype_f 

670 Right-hand side for the linear system. 

671 factor : float 

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

673 u0 : dtype_u 

674 Initial guess for the iterative solver. 

675 t : float 

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

677 

678 Returns 

679 ------- 

680 me : dtype_u 

681 Solution as mesh. 

682 """ 

683 

684 me = self.dtype_u(u0) 

685 target = Fisher_full(self.init, self, factor, self.dx) 

686 

687 # assign residual function and Jacobian 

688 

689 self.snes.setFunction(target.formFunction, self.F) 

690 self.snes.setJacobian(target.formJacobian, self.J) 

691 

692 self.snes.solve(rhs, me) 

693 

694 self.snes_itercount += self.snes.getIterationNumber() 

695 self.snes_ncalls += 1 

696 

697 return me 

698 

699 

700class petsc_fisher_semiimplicit(petsc_fisher_multiimplicit): 

701 r""" 

702 1D generalized Fisher equation with PETSc finite differences, IMEX with diffusion implicit, reaction explicit. 

703 

704 The following one-dimensional problem is an example of a reaction-diffusion equation with traveling waves, and can 

705 be seen as a generalized Fisher equation. This class implements a special case of the Kolmogorov-Petrovskii-Piskunov 

706 problem [1]_ on a finite interval, where the first and last grid points keep their initial values (Dirichlet 

707 boundary conditions) 

708 

709 .. math:: 

710 \frac{\partial u}{\partial t} = \Delta u + \lambda_0^2 u (1 - u^\nu) 

711 

712 with exact solution 

713 

714 .. math:: 

715 u(x, 0) = \left[ 

716 1 + \left(2^{\nu / 2} - 1\right) \exp\left(-(\nu / 2)\sigma_1 x + 2 \lambda_1 t\right) 

717 \right]^{-2 / \nu} 

718 

719 for :math:`x \in \mathbb{R}`, and 

720 

721 .. math:: 

722 \sigma_1 = \lambda_1 - \sqrt{\lambda_1^2 - \lambda_0^2}, 

723 

724 .. math:: 

725 \lambda_1 = \frac{\lambda_0}{2} \left[ 

726 \left(1 + \frac{\nu}{2}\right)^{1/2} + \left(1 + \frac{\nu}{2}\right)^{-1/2} 

727 \right]. 

728 

729 This class is implemented to be solved in spatial using ``PETSc`` [2]_, [3]_. For time-stepping, the problem here will be 

730 solved in a *semi-implicit* way. 

731 """ 

732 

733 dtype_f = petsc_vec_imex 

734 

735 def eval_f(self, u, t): 

736 """ 

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

738 

739 Parameters 

740 ---------- 

741 u : dtype_u 

742 Current values of the numerical solution. 

743 t : float 

744 Current time the numerical solution is computed. 

745 

746 Returns 

747 ------- 

748 f : dtype_f 

749 Right-hand side of the problem. 

750 """ 

751 

752 f = self.dtype_f(self.init) 

753 self.A.mult(u, f.impl) 

754 fa1 = self.init.getVecArray(f.impl) 

755 fa1[0] = 0 

756 fa1[-1] = 0 

757 

758 fa2 = self.init.getVecArray(f.expl) 

759 xa = self.init.getVecArray(u) 

760 for i in range(self.xs, self.xe): 

761 fa2[i] = self.lambda0**2 * xa[i] * (1 - xa[i] ** self.nu) 

762 fa2[0] = 0 

763 fa2[-1] = 0 

764 

765 return f 

766 

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

768 r""" 

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

770 

771 Parameters 

772 ---------- 

773 rhs : dtype_f 

774 Right-hand side for the linear system. 

775 factor : float 

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

777 u0 : dtype_u 

778 Initial guess for the iterative solver. 

779 t : float 

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

781 

782 Returns 

783 ------- 

784 me : dtype_u 

785 Solution as mesh. 

786 """ 

787 

788 me = self.dtype_u(u0) 

789 

790 self.ksp.setOperators(self.get_sys_mat(factor)) 

791 self.ksp.solve(rhs, me) 

792 

793 self.ksp_itercount += self.ksp.getIterationNumber() 

794 self.ksp_ncalls += 1 

795 

796 return me