ex8.py: Nonlinear eigenproblem with split form ============================================== This example solves a nonlinear eigenvalue problem where the nonlinear function is expressed in split form. We want to solve the following parabolic partial differential equation with time delay :math:`\tau` .. math:: u_t &= u_{xx} + a u(t) + b u(t-\tau) \\ u(0,t) &= u(\pi,t) = 0 with :math:`a = 20` and :math:`b(x) = -4.1+x (1-e^{x-\pi})`. Discretization leads to a DDE of dimension :math:`n` .. math:: -u' = A u(t) + B u(t-\tau) which results in the nonlinear eigenproblem .. math:: (-\lambda I + A + e^{-\tau\lambda}B)u = 0. The full source code for this demo can be `downloaded here <../_static/ex8.py>`__. Initialization is similar to previous examples. In this case we also need to import some math symbols. :: import sys, slepc4py slepc4py.init(sys.argv) from petsc4py import PETSc from slepc4py import SLEPc from numpy import exp from math import pi Print = PETSc.Sys.Print This script has two command-line options: the discretization size ``n`` and the time delay ``tau``. :: opts = PETSc.Options() n = opts.getInt('n', 128) tau = opts.getReal('tau', 0.001) a = 20 h = pi/(n+1) Next we have to set up the solver. In this case, we are going to represent the nonlinear problem in split form, i.e., as a sum of terms made of a constant matrix multiplied by a scalar nonlinear function. :: nep = SLEPc.NEP().create() The first term involves the identity matrix. :: Id = PETSc.Mat().createConstantDiagonal([n, n], 1.0) The second term has a tridiagonal matrix obtained from the discretization, :math:`A = \frac{1}{h^2}\operatorname{tridiag}(1,-2,1) + a I`. :: A = PETSc.Mat().create() A.setSizes([n, n]) A.setFromOptions() rstart, rend = A.getOwnershipRange() vd = -2.0/(h*h)+a vo = 1.0/(h*h) if rstart == 0: A[0, :2] = [vd, vo] rstart += 1 if rend == n: A[n-1, -2:] = [vo, vd] rend -= 1 for i in range(rstart, rend): A[i, i-1:i+2] = [vo, vd, vo] A.assemble() The third term includes a diagonal matrix :math:`B = \operatorname{diag}(b(x_i))`. :: B = PETSc.Mat().create() B.setSizes([n, n]) B.setFromOptions() rstart, rend = B.getOwnershipRange() for i in range(rstart, rend): xi = (i+1)*h B[i, i] = -4.1+xi*(1.0-exp(xi-pi)); B.assemble() B.setOption(PETSc.Mat.Option.HERMITIAN, True) Apart from the matrices, we have to create the functions, represented with `FN` objects: :math:`f_1=-\lambda, f_2=1, f_3=\exp(-\tau\lambda)`. :: f1 = SLEPc.FN().create() f1.setType(SLEPc.FN.Type.RATIONAL) f1.setRationalNumerator([-1, 0]) f2 = SLEPc.FN().create() f2.setType(SLEPc.FN.Type.RATIONAL) f2.setRationalNumerator([1]) f3 = SLEPc.FN().create() f3.setType(SLEPc.FN.Type.EXP) f3.setScale(-tau) Put all the information together to define the split operator. Note that ``A`` is passed first so that `SUBSET ` nonzero_pattern can be used. :: nep.setSplitOperator([A, Id, B], [f2, f1, f3], PETSc.Mat.Structure.SUBSET) Now we can set some options and call the solver. :: nep.setTolerances(tol=1e-9) nep.setDimensions(1) nep.setFromOptions() nep.solve() Once the solver has finished, we print some information together with the computed solution. For each computed eigenpair, we print the eigenvalue and the residual norm. :: its = nep.getIterationNumber() Print("Number of iterations of the method: %i" % its) sol_type = nep.getType() Print("Solution method: %s" % sol_type) nev, ncv, mpd = nep.getDimensions() Print("") Print("Subspace dimension: %i" % ncv) tol, maxit = nep.getTolerances() Print("Stopping condition: tol=%.4g" % tol) Print("") nconv = nep.getConverged() Print( "Number of converged eigenpairs %d" % nconv ) if nconv > 0: x = Id.createVecs('right') x.set(1.0) Print() Print(" k ||T(k)x||") Print("----------------- ------------------") for i in range(nconv): k = nep.getEigenpair(i, x) res = nep.computeError(i) if k.imag != 0.0: Print( " %9f%+9f j %12g" % (k.real, k.imag, res) ) else: Print( " %12f %12g" % (k.real, res) ) Print()