Beispiel #1
0
def test_krylov_solver_options_prefix(pushpop_parameters):
    "Test set/get PETScKrylov solver prefix option"

    # Set backend
    parameters["linear_algebra_backend"] = "PETSc"

    # Prefix
    prefix = "test_foo_"

    # Create solver and set prefix
    solver = PETScKrylovSolver()
    solver.set_options_prefix(prefix)

    # Check prefix (pre solve)
    assert solver.get_options_prefix() == prefix

    # Solve a system of equations
    mesh = UnitSquareMesh(4, 4)
    V = FunctionSpace(mesh, "Lagrange", 1)
    u, v = TrialFunction(V), TestFunction(V)
    a, L = u*v*dx, Constant(1.0)*v*dx
    A, b = assemble(a), assemble(L)
    solver.set_operator(A)
    solver.solve(b.copy(), b)

    # Check prefix (post solve)
    assert solver.get_options_prefix() == prefix
Beispiel #2
0
def test_krylov_solver_options_prefix(pushpop_parameters):
    "Test set/get PETScKrylov solver prefix option"

    # Set backend
    parameters["linear_algebra_backend"] = "PETSc"

    # Prefix
    prefix = "test_foo_"

    # Create solver and set prefix
    solver = PETScKrylovSolver()
    solver.set_options_prefix(prefix)

    # Check prefix (pre solve)
    assert solver.get_options_prefix() == prefix

    # Solve a system of equations
    mesh = UnitSquareMesh(4, 4)
    V = FunctionSpace(mesh, "Lagrange", 1)
    u, v = TrialFunction(V), TestFunction(V)
    a, L = u*v*dx, Constant(1.0)*v*dx
    A, b = assemble(a), assemble(L)
    solver.set_operator(A)
    solver.solve(b.copy(), b)

    # Check prefix (post solve)
    assert solver.get_options_prefix() == prefix
Beispiel #3
0
def test_krylov_solver_norm_type():
    """Check setting of norm type used in testing for convergence by
    PETScKrylovSolver

    """

    norm_type = (PETScKrylovSolver.norm_type_default_norm,
                 PETScKrylovSolver.norm_type_natural,
                 PETScKrylovSolver.norm_type_preconditioned,
                 PETScKrylovSolver.norm_type_none,
                 PETScKrylovSolver.norm_type_unpreconditioned)

    for norm in norm_type:
        # Solve a system of equations
        mesh = UnitSquareMesh(4, 4)
        V = FunctionSpace(mesh, "Lagrange", 1)
        u, v = TrialFunction(V), TestFunction(V)
        a = u*v*dx
        L = Constant(1.0)*v*dx
        A, b = assemble(a), assemble(L)

        solver = PETScKrylovSolver("cg")
        solver.parameters["maximum_iterations"] = 2
        solver.parameters["error_on_nonconvergence"] = False
        solver.set_norm_type(norm)
        solver.set_operator(A)
        solver.solve(b.copy(), b)
        solver.get_norm_type()

        if norm is not PETScKrylovSolver.norm_type_default_norm:
            assert solver.get_norm_type() == norm
Beispiel #4
0
def test_krylov_solver_norm_type():
    """Check setting of norm type used in testing for convergence by
    PETScKrylovSolver

    """

    norm_type = (PETScKrylovSolver.norm_type.default_norm,
                 PETScKrylovSolver.norm_type.natural,
                 PETScKrylovSolver.norm_type.preconditioned,
                 PETScKrylovSolver.norm_type.none,
                 PETScKrylovSolver.norm_type.unpreconditioned)

    for norm in norm_type:
        # Solve a system of equations
        mesh = UnitSquareMesh(4, 4)
        V = FunctionSpace(mesh, "Lagrange", 1)
        u, v = TrialFunction(V), TestFunction(V)
        a = u*v*dx
        L = Constant(1.0)*v*dx
        A, b = assemble(a), assemble(L)

        solver = PETScKrylovSolver("cg")
        solver.parameters["maximum_iterations"] = 2
        solver.parameters["error_on_nonconvergence"] = False
        solver.set_norm_type(norm)
        solver.set_operator(A)
        solver.solve(b.copy(), b)
        solver.get_norm_type()

        if norm is not PETScKrylovSolver.norm_type.default_norm:
            assert solver.get_norm_type() == norm
    def solve(self, F, u, grad = None, H = None):
        
        if grad is None:
            print "Using Automatic Differentiation to compute the gradient"
            grad = derivative(F,u)
            
        if H is None:
            print "Using Automatic Differentiation to compute the Hessian"
            H = derivative(grad, u)
        
        rtol = self.parameters["rel_tolerance"]
        atol = self.parameters["abs_tolerance"]
        gdu_tol = self.parameters["gdu_tolerance"]
        max_iter = self.parameters["max_iter"]
        c_armijo = self.parameters["c_armijo"] 
        max_backtrack = self.parameters["max_backtracking_iter"]
        prt_level =  self.parameters["print_level"]
        cg_coarsest_tol = self.parameters["cg_coarse_tolerance"]
        
        Fn = assemble(F)
        gn = assemble(grad)
        g0_norm = gn.norm("l2")
        gn_norm = g0_norm
        tol = max(g0_norm*rtol, atol)
        du = Vector()
        
        self.converged = False
        self.reason = 0
        
        if prt_level > 0:
            print "{0:3} {1:15} {2:15} {3:15} {4:15} {5:15} {6:5}".format(
                "It", "Energy", "||g||", "(g,du)", "alpha", "tol_cg", "cg_it")
        
        for self.it in range(max_iter):
            Hn = assemble(H)
            
            Hn.init_vector(du,1)
            solver = PETScKrylovSolver("cg", "petsc_amg")
            solver.set_operator(Hn)
            solver.parameters["nonzero_initial_guess"] = False
            cg_tol = min(cg_coarsest_tol, math.sqrt( gn_norm/g0_norm) )
            solver.parameters["relative_tolerance"] = cg_tol
            lin_it = solver.solve(du,gn)
            
            self.total_cg_iter += lin_it
            

            du_gn = -du.inner(gn)
            
            if(-du_gn < gdu_tol):
                self.converged=True
                self.reason = 3
                break
             
            u_backtrack = u.copy(deepcopy=True)
            alpha = 1.   
            bk_converged = False
            
            #Backtrack
            for j in range(max_backtrack):
                u.assign(u_backtrack)
                u.vector().axpy(-alpha, du)
                Fnext = assemble(F)
                if Fnext < Fn + alpha*c_armijo*du_gn:
                    Fn = Fnext
                    bk_converged = True
                    break
                
                alpha = alpha/2.
                
            if not bk_converged:
                self.reason = 2
                break
                   
            gn = assemble(grad)
            gn_norm = gn.norm("l2")
            
            if prt_level > 0:
                print "{0:3d} {1:15f} {2:15f} {3:15f} {4:15f} {5:15f} {6:5d}".format(
                        self.it, Fn, gn_norm, du_gn, alpha, cg_tol, lin_it)
                
            if gn_norm < tol:
                self.converged = True
                self.reason = 1
                break
            
        self.final_grad_norm = gn_norm
        
        if prt_level > -1:
            print self.termination_reasons[self.reason]
            if self.converged:
                print "Inexact Newton CG converged in ", self.it, \
                "nonlinear iterations and ", self.total_cg_iter, "linear iterations."
            else:
                print "Inexact Newton CG did NOT converge after ", self.it, \
                "nonlinear iterations and ", self.total_cg_iter, "linear iterations."
            print "Final norm of the gradient", self.final_grad_norm
            print "Value of the cost functional", Fn
Beispiel #6
0
    def solve(self, F, u, grad=None, H=None):

        if grad is None:
            print("Using Symbolic Differentiation to compute the gradient")
            grad = derivative(F, u)

        if H is None:
            print("Using Symbolic Differentiation to compute the Hessian")
            H = derivative(grad, u)

        rtol = self.parameters["rel_tolerance"]
        atol = self.parameters["abs_tolerance"]
        gdu_tol = self.parameters["gdu_tolerance"]
        max_iter = self.parameters["max_iter"]
        c_armijo = self.parameters["c_armijo"]
        max_backtrack = self.parameters["max_backtracking_iter"]
        prt_level = self.parameters["print_level"]
        cg_coarsest_tol = self.parameters["cg_coarse_tolerance"]

        Fn = assemble(F)
        gn = assemble(grad)
        g0_norm = gn.norm("l2")
        gn_norm = g0_norm
        tol = max(g0_norm * rtol, atol)
        du = Vector()

        self.converged = False
        self.reason = 0

        if prt_level > 0:
            print(
                "{0:>3} {1:>15} {2:>15} {3:>15} {4:>15} {5:>15} {6:>7}".format(
                    "It", "Energy", "||g||", "(g,du)", "alpha", "tol_cg",
                    "cg_it"))

        for self.it in range(max_iter):
            Hn = assemble(H)

            Hn.init_vector(du, 1)
            solver = PETScKrylovSolver("cg", "petsc_amg")
            solver.set_operator(Hn)
            solver.parameters["nonzero_initial_guess"] = False
            cg_tol = min(cg_coarsest_tol, math.sqrt(gn_norm / g0_norm))
            solver.parameters["relative_tolerance"] = cg_tol
            lin_it = solver.solve(du, gn)

            self.total_cg_iter += lin_it

            du_gn = -du.inner(gn)

            if (-du_gn < gdu_tol):
                self.converged = True
                self.reason = 3
                break

            u_backtrack = u.copy(deepcopy=True)
            alpha = 1.
            bk_converged = False

            #Backtrack
            for j in range(max_backtrack):
                u.assign(u_backtrack)
                u.vector().axpy(-alpha, du)
                Fnext = assemble(F)
                if Fnext < Fn + alpha * c_armijo * du_gn:
                    Fn = Fnext
                    bk_converged = True
                    break

                alpha = alpha / 2.

            if not bk_converged:
                self.reason = 2
                break

            gn = assemble(grad)
            gn_norm = gn.norm("l2")

            if prt_level > 0:
                print("{0:3d} {1:15e} {2:15e} {3:15e} {4:15e} {5:15e} {6:7d}".
                      format(self.it, Fn, gn_norm, du_gn, alpha, cg_tol,
                             lin_it))

            if gn_norm < tol:
                self.converged = True
                self.reason = 1
                break

        self.final_grad_norm = gn_norm

        if prt_level > -1:
            print(self.termination_reasons[self.reason])
            if self.converged:
                print( "Inexact Newton CG converged in ", self.it, \
                "nonlinear iterations and ", self.total_cg_iter, "linear iterations." )
            else:
                print( "Inexact Newton CG did NOT converge after ", self.it, \
                "nonlinear iterations and ", self.total_cg_iter, "linear iterations.")
            print("Final norm of the gradient", self.final_grad_norm)
            print("Value of the cost functional", Fn)
Beispiel #7
0
class LinearSystem(object):
	def __init__(self, grid, split=False, tol=1.e-6, maxIteration=100):
		self.dx = grid.dx
		self.ds = grid.ds
		self.dS = grid.dS
		self.tol = tol
		self.maxIteration = maxIteration
		self.split = split
	
	def stiffnessBlock(self, properties, u, w):
		return 2*properties.G*inner(sym(grad(u)), grad(w))*self.dx + properties.lamda*div(u)*div(w)*self.dx

	def porePressureBlock(self, properties, p, w):
		return -properties.alpha*p*div(w)*self.dx

	def solidVelocityBlock(self, properties, dt, u, q):
		return (properties.alpha/dt)*div(u)*q*self.dx

	def storageBlock(self, properties, dt, p, q):
		return (1./(properties.Q*dt))*p*q*self.dx

	def solidPressureBlock(self, properties, dt, p, q, delta=1):
		return (properties.alpha**2/(delta*properties.K*dt))*p*q*self.dx

	def fluidFlowBlock(self, properties, p, q):
		return (properties.k/properties.mu)*inner(grad(p), grad(q))*self.dx

	def forceVector(self, load, w, mark):
		return inner(load, w)*self.ds(mark)

	def apply(self, entity, bcs):
		if self.split:
			for bc in bcs:
				bc.apply(entity)
		else:
			bcs.apply(entity)
		return entity

	def assembly(self, entity, bcs=[]):
		if self.split:
			entity = assemble(entity)
		else:
			entity = block_assemble(entity)
		if bcs:
			self.apply(entity, bcs)
		return entity

	def initializeLinearSystem(self, properties, dt, u, w, p, q, bcs):
		self.stiffnessBlock = self.stiffnessBlock(properties, u, w)
		self.porePressureBlock = self.porePressureBlock(properties, p, w)
		self.solidVelocityBlock = self.solidVelocityBlock(properties, dt, u, q)
		self.storageBlock = self.storageBlock(properties, dt, p, q)
		self.solidPressureBlock = self.solidPressureBlock(properties, dt, p, q)
		self.fluidFlowBlock = self.fluidFlowBlock(properties, p, q)
		self.bcs = bcs
		if self.split:
			self.geoSolver = PETScKrylovSolver("gmres", "hypre_amg")
			self.geoSolver.parameters['relative_tolerance'] = self.tol
			self.flowSolver = PETScKrylovSolver("bicgstab", "sor")
			self.flowSolver.parameters['relative_tolerance'] = self.tol

		"""
		Solvers:
		'bicgstab'	Biconjugate gradient stabilized method
		'cg'	Conjugate gradient method
		'gmres'	Generalized minimal residual method
		'minres'	Minimal residual method
		'petsc'	PETSc built in LU solver
		'richardson'	Richardson method
		'superlu_dist'	Parallel SuperLU
		'tfqmr'	Transpose-free quasi-minimal residual method
		'umfpack'	UMFPACK

		PC:
		'icc'	Incomplete Cholesky factorization
		'ilu'	Incomplete LU factorization
		'petsc_amg'	PETSc algebraic multigrid
		'hypre_amg'	HYPRE algebraic multigrid
		'sor'	Successive over-relaxation
		"""

	def assemblyCoefficientsMatrix(self):
		if self.split:
			K = self.stiffnessBlock
			M = self.storageBlock + self.solidPressureBlock + self.fluidFlowBlock
			self.geoCoefficientsMatrix = self.assembly(K, self.bcs.uDirichlet)
			self.flowCoefficientsMatrix = self.assembly(M, self.bcs.pDirichlet)
		else:
			A = [[self.stiffnessBlock, 		self.porePressureBlock],
				 [self.solidVelocityBlock,	self.storageBlock + self.fluidFlowBlock]]
			self.coefficientsMatrix = self.assembly(A, self.bcs.dirichlet)

	def assemblyVector(self, forceVector, u0, p0):
		if self.split:
			self.u0 = u0
			self.p0 = p0
			self.fixedGeoVector = self.assembly(forceVector)
			self.fixedflowVector = self.assembly(self.storageBlock*p0) + self.assembly(self.solidVelocityBlock*u0)
		else:
			f = [forceVector,
			 	 0]
			f = self.assembly(f)
			m = [0,
				 self.storageBlock*p0]
			m = self.assembly(m)
			s = [0,
				 self.solidVelocityBlock*u0]
			s = self.assembly(s)
			self.vector = self.apply(f + m + s, self.bcs.dirichlet)

	def iterateGeoVector(self, p_hk):
		self.geoVector = self.fixedGeoVector + self.assembly(-self.porePressureBlock*p_hk)

	def iterateFlowVector(self, u_hk, p_hk):
		self.flowVector = self.fixedflowVector + self.assembly(-self.solidVelocityBlock*u_hk) + self.assembly(self.solidPressureBlock*p_hk)

	def getRelativeErrorNorm(self, p_h, p_hk):
		set_log_level(40)
		error = errornorm(p_h, p_hk)/norm(p_h)
		set_log_level(20)
		return error


	def solveProblem(self, space):
		X = space.function()
		if self.split:
			(u_h, p_h) = X
			(u_hk, p_hk) = space.assignInitialCondition(Constant((0.0, 0.0, 0.0)), Constant(0.0))
			u_hk.assign(self.u0)
			p_hk.assign(self.p0)
			error = 1e34
			iteration = 1
			while error > self.tol and iteration < self.maxIteration:
				print("Iteration = {:4d}".format(iteration), end="\t")
				self.iterateFlowVector(u_hk, p_hk)
				self.apply(self.flowVector, self.bcs.pDirichlet)
				self.flowSolver.solve(self.flowCoefficientsMatrix, p_h.vector(), self.flowVector)
				p_hk.assign(p_h)
				self.iterateGeoVector(p_hk)
				self.apply(self.geoVector, self.bcs.uDirichlet)
				self.geoSolver.solve(self.geoCoefficientsMatrix, u_h.vector(), self.geoVector)
				u_hk.assign(u_h)
				self.iterateFlowVector(u_hk, p_hk)
				self.apply(self.flowVector, self.bcs.pDirichlet)
				self.flowSolver.solve(self.flowCoefficientsMatrix, p_h.vector(), self.flowVector)
				error = self.getRelativeErrorNorm(p_h, p_hk)
				print("Error = {:.2E}".format(error), end="\r")
				p_hk.assign(p_h)
				iteration += 1
			print("\033[K", end="\r")
			print("Iteration = {:4d}".format(iteration), end="\t")
			print("Error = {:.2E}".format(error), end="\t")
			self.solution = (u_h, p_h)
		else:
			block_solve(self.coefficientsMatrix, X.block_vector(), self.vector, "mumps")
			self.solution = X.block_split()
    def _pressure_poisson(self,
                          p1, p0,
                          mu, ui,
                          divu,
                          p_bcs=None,
                          p_n=None,
                          rotational_form=False,
                          tol=1.0e-10,
                          verbose=True
                          ):
        '''Solve the pressure Poisson equation

            - \Delta phi = -div(u),
            boundary conditions,

        for

            \nabla p = u.
        '''
        P = p1.function_space()
        p = TrialFunction(P)
        q = TestFunction(P)

        a2 = dot(grad(p), grad(q)) * dx
        L2 = -divu * q * dx
        if p0:
            L2 += dot(grad(p0), grad(q)) * dx
        if p_n:
            n = FacetNormal(P.mesh())
            L2 += dot(n, p_n) * q * ds

        if rotational_form:
            L2 -= mu * dot(grad(div(ui)), grad(q)) * dx

        if p_bcs:
            solve(a2 == L2, p1,
                  bcs=p_bcs,
                  solver_parameters={
                      'linear_solver': 'iterative',
                      'symmetric': True,
                      'preconditioner': 'hypre_amg',
                      'krylov_solver': {'relative_tolerance': tol,
                                        'absolute_tolerance': 0.0,
                                        'maximum_iterations': 100,
                                        'monitor_convergence': verbose}
                  })
        else:
            # If we're dealing with a pure Neumann problem here (which is the
            # default case), this doesn't hurt CG if the system is consistent,
            # cf.
            #
            #    Iterative Krylov methods for large linear systems,
            #    Henk A. van der Vorst.
            #
            # And indeed, it is consistent: Note that
            #
            #    <1, rhs> = \sum_i 1 * \int div(u) v_i
            #             = 1 * \int div(u) \sum_i v_i
            #             = \int div(u).
            #
            # With the divergence theorem, we have
            #
            #    \int div(u) = \int_\Gamma n.u.
            #
            # The latter term is 0 iff inflow and outflow are exactly the same
            # at any given point in time. This corresponds with the
            # incompressibility of the liquid.
            #
            # In turn, this hints towards penetrable boundaries to require
            # Dirichlet conditions on the pressure.
            #
            A = assemble(a2)
            b = assemble(L2)
            #
            # In principle, the ILU preconditioner isn't advised here since it
            # might destroy the semidefiniteness needed for CG.
            #
            # The system is consistent, but the matrix has an eigenvalue 0.
            # This does not harm the convergence of CG, but when
            # preconditioning one has to take care that the preconditioner
            # preserves the kernel.  ILU might destroy this (and the
            # semidefiniteness). With AMG, the coarse grid solves cannot be LU
            # then, so try Jacobi here.
            # <http://lists.mcs.anl.gov/pipermail/petsc-users/2012-February/012139.html>
            #
            prec = PETScPreconditioner('hypre_amg')
            PETScOptions.set('pc_hypre_boomeramg_relax_type_coarse', 'jacobi')
            solver = PETScKrylovSolver('cg', prec)
            solver.parameters['absolute_tolerance'] = 0.0
            solver.parameters['relative_tolerance'] = tol
            solver.parameters['maximum_iterations'] = 100
            solver.parameters['monitor_convergence'] = verbose
            # Create solver and solve system
            A_petsc = as_backend_type(A)
            b_petsc = as_backend_type(b)
            p1_petsc = as_backend_type(p1.vector())
            solver.set_operator(A_petsc)
            try:
                solver.solve(p1_petsc, b_petsc)
            except RuntimeError as error:
                info('')
                # Check if the system is indeed consistent.
                #
                # If the right hand side is flawed (e.g., by round-off errors),
                # then it may have a component b1 in the direction of the null
                # space, orthogonal the image of the operator:
                #
                #     b = b0 + b1.
                #
                # When starting with initial guess x0=0, the minimal achievable
                # relative tolerance is then
                #
                #    min_rel_tol = ||b1|| / ||b||.
                #
                # If ||b|| is very small, which is the case when ui is almost
                # divergence-free, then min_rel_to may be larger than the
                # prescribed relative tolerance tol.
                #
                # Use this as a consistency check, i.e., bail out if
                #
                #     tol < min_rel_tol = ||b1|| / ||b||.
                #
                # For computing ||b1||, we use the fact that the null space is
                # one-dimensional, i.e.,  b1 = alpha e,  and
                #
                #     e.b = e.(b0 + b1) = e.b1 = alpha ||e||^2,
                #
                # so  alpha = e.b/||e||^2  and
                #
                #     ||b1|| = |alpha| ||e|| = e.b / ||e||
                #
                e = Function(P)
                e.interpolate(Constant(1.0))
                evec = e.vector()
                evec /= norm(evec)
                alpha = b.inner(evec)
                normB = norm(b)
                info('Linear system convergence failure.')
                info(error.message)
                message = ('Linear system not consistent! '
                           '<b,e> = %g, ||b|| = %g, <b,e>/||b|| = %e, tol = %e.') \
                           % (alpha, normB, alpha/normB, tol)
                info(message)
                if tol < abs(alpha) / normB:
                    info('\int div(u)  =  %e' % assemble(divu * dx))
                    #n = FacetNormal(Q.mesh())
                    #info('\int_Gamma n.u = %e' % assemble(dot(n, u)*ds))
                    #info('\int_Gamma u[0] = %e' % assemble(u[0]*ds))
                    #info('\int_Gamma u[1] = %e' % assemble(u[1]*ds))
                    ## Now plot the faulty u on a finer mesh (to resolve the
                    ## quadratic trial functions).
                    #fine_mesh = Q.mesh()
                    #for k in range(1):
                    #    fine_mesh = refine(fine_mesh)
                    #V1 = FunctionSpace(fine_mesh, 'CG', 1)
                    #W1 = V1*V1
                    #uplot = project(u, W1)
                    ##uplot = Function(W1)
                    ##uplot.interpolate(u)
                    #plot(uplot, title='u_tentative')
                    #plot(uplot[0], title='u_tentative[0]')
                    #plot(uplot[1], title='u_tentative[1]')
                    plot(divu, title='div(u_tentative)')
                    interactive()
                    exit()
                    raise RuntimeError(message)
                else:
                    exit()
                    raise RuntimeError('Linear system failed to converge.')
            except:
                exit()
        return
def test_poisson(k):
    # Polynomial order and mesh resolution
    nx_list = [4, 8, 16]

    # Error list
    error_u_l2, error_u_h1 = [], []

    for nx in nx_list:
        mesh = UnitSquareMesh(nx, nx)

        # Define FunctionSpaces and functions
        V = FunctionSpace(mesh, "DG", k)
        Vbar = FunctionSpace(mesh,
                             FiniteElement("CG", mesh.ufl_cell(), k)["facet"])

        u_soln = Expression("sin(pi*x[0])*sin(pi*x[1])",
                            degree=k + 1,
                            domain=mesh)
        f = Expression("2*pi*pi*sin(pi*x[0])*sin(pi*x[1])", degree=k + 1)
        u, v = Function(V), TestFunction(V)
        ubar, vbar = Function(Vbar), TestFunction(Vbar)

        n = FacetNormal(mesh)
        h = CellDiameter(mesh)
        alpha = Constant(6 * k * k)
        penalty = alpha / h

        def facet_integral(integrand):
            return integrand('-') * dS + integrand('+') * dS + integrand * ds

        u_flux = ubar
        F_v_flux = grad(u) + penalty * outer(u_flux - u, n)

        residual_local = inner(grad(u), grad(v)) * dx
        residual_local += facet_integral(inner(outer(u_flux - u, n), grad(v)))
        residual_local -= facet_integral(inner(F_v_flux, outer(v, n)))
        residual_local -= f * v * dx

        residual_global = facet_integral(inner(F_v_flux, outer(vbar, n)))

        a_ll = derivative(residual_local, u)
        a_lg = derivative(residual_local, ubar)
        a_gl = derivative(residual_global, u)
        a_gg = derivative(residual_global, ubar)

        l_l = -residual_local
        l_g = -residual_global

        bcs = [DirichletBC(Vbar, u_soln, "on_boundary")]

        # Initialize static condensation assembler
        assembler = AssemblerStaticCondensation(a_ll, a_lg, a_gl, a_gg, l_l,
                                                l_g, bcs)

        A_g, b_g = PETScMatrix(), PETScVector()
        assembler.assemble_global_lhs(A_g)
        assembler.assemble_global_rhs(b_g)

        for bc in bcs:
            bc.apply(A_g, b_g)

        solver = PETScKrylovSolver()
        solver.set_operator(A_g)
        PETScOptions.set("ksp_type", "preonly")
        PETScOptions.set("pc_type", "lu")
        PETScOptions.set("pc_factor_mat_solver_type", "mumps")
        solver.set_from_options()

        solver.solve(ubar.vector(), b_g)
        assembler.backsubstitute(ubar._cpp_object, u._cpp_object)

        # Compute L2 and H1 norms
        e_u_l2 = assemble((u - u_soln)**2 * dx)**0.5
        e_u_h1 = assemble(grad(u - u_soln)**2 * dx)**0.5

        if mesh.mpi_comm().rank == 0:
            error_u_l2.append(e_u_l2)
            error_u_h1.append(e_u_h1)

    if mesh.mpi_comm().rank == 0:
        iterator_list = [1.0 / float(nx) for nx in nx_list]
        conv_u_l2 = compute_convergence(iterator_list, error_u_l2)
        conv_u_h1 = compute_convergence(iterator_list, error_u_h1)

        # Optimal rate of k + 1 - tolerance
        assert np.all(conv_u_l2 >= (k + 1.0 - 0.15))
        # Optimal rate of k - tolerance
        assert np.all(conv_u_h1 >= (k - 0.1))
Beispiel #10
0
def compute_pressure(
    P,
    p0,
    mu,
    ui,
    u,
    my_dx,
    p_bcs=None,
    rotational_form=False,
    tol=1.0e-10,
    verbose=True,
):
    """Solve the pressure Poisson equation

    .. math::

        \\begin{align}
          -\\frac{1}{r} \\div(r \\nabla (p_1-p_0)) =
              -\\frac{1}{r} \\div(r u),\\\\
          \\text{(with boundary conditions)},
        \\end{align}

    for :math:`\\nabla p = u`.

    The pressure correction is based on the update formula

    .. math::
        \\frac{\\rho}{dt} (u_{n+1}-u^*)
            + \\begin{pmatrix}
                \\text{d}\\phi/\\text{d}r\\\\
                \\text{d}\\phi/\\text{d}z\\\\
                \\frac{1}{r} \\text{d}\\phi/\\text{d}\\theta
              \\end{pmatrix}
                = 0

    with :math:`\\phi = p_{n+1} - p^*` and

    .. math::

         \\frac{1}{r} \\frac{\\text{d}}{\\text{d}r} (r u_r^{(n+1)})
       + \\frac{\\text{d}}{\\text{d}z}  (u_z^{(n+1)})
       + \\frac{1}{r} \\frac{\\text{d}}{\\text{d}\\theta} (u_{\\theta}^{(n+1)})
           = 0

    With the assumption that u does not change in the direction
    :math:`\\theta`, one derives

    .. math::

       - \\frac{1}{r}   \\div(r \\nabla \\phi) =
           \\frac{1}{r} \\frac{\\rho}{dt}   \\div(r (u_{n+1} - u^*))\\\\
       - \\frac{1}{r} \\langle n, r \\nabla \\phi\\rangle =
           \\frac{1}{r} \\frac{\\rho}{dt} \\langle n, r (u_{n+1} - u^*)\\rangle

    In its weak form, this is

    .. math::

      \\int r \\langle\\nabla\\phi, \\nabla q\\rangle \\,2 \\pi =
           - \\frac{\\rho}{dt} \\int \\div(r u^*) q \\, 2 \\pi
           - \\frac{\\rho}{dt} \\int_{\\Gamma}
                 \\langle n,  r (u_{n+1}-u^*)\\rangle q \\, 2\\pi.

    (The terms :math:`1/r` cancel with the volume elements :math:`2\\pi r`.)
    If the Dirichlet boundary conditions are applied to both :math:`u^*` and
    :math:`u_n` (the latter in the velocity correction step), the boundary
    integral vanishes.

    If no Dirichlet conditions are given (which is the default case), the
    system has no unique solution; one eigenvalue is 0. This however, does not
    hurt CG convergence if the system is consistent, cf. :cite:`vdV03`. And
    indeed it is consistent if and only if

    .. math::
        \\int_\\Gamma r \\langle n, u\\rangle = 0.

    This condition makes clear that for incompressible Navier-Stokes, one
    either needs to make sure that inflow and outflow always add up to 0, or
    one has to specify pressure boundary conditions.

    Note that, when using a multigrid preconditioner as is done here, the
    coarse solver must be chosen such that it preserves the nullspace of the
    problem.
    """
    W = ui.function_space()
    r = SpatialCoordinate(W.mesh())[0]

    p = TrialFunction(P)
    q = TestFunction(P)
    a2 = dot(r * grad(p), grad(q)) * 2 * pi * my_dx
    # The boundary conditions
    #     n.(p1-p0) = 0
    # are implicitly included.
    #
    # L2 = -div(r*u) * q * 2*pi*my_dx
    div_u = 1 / r * (r * u[0]).dx(0) + u[1].dx(1)
    L2 = -div_u * q * 2 * pi * r * my_dx
    if p0:
        L2 += r * dot(grad(p0), grad(q)) * 2 * pi * my_dx

    # In the Cartesian variant of the rotational form, one makes use of the
    # fact that
    #
    #     curl(curl(u)) = grad(div(u)) - div(grad(u)).
    #
    # The same equation holds true in cylindrical form. Hence, to get the
    # rotational form of the splitting scheme, we need to
    #
    # rotational form
    if rotational_form:
        # If there is no dependence of the angular coordinate, what is
        # div(grad(div(u))) in Cartesian coordinates becomes
        #
        #     1/r div(r * grad(1/r div(r*u)))
        #
        # in cylindrical coordinates (div and grad are in cylindrical
        # coordinates). Unfortunately, we cannot write it down that
        # compactly since u_phi is in the game.
        # When using P2 elements, this value will be 0 anyways.
        div_ui = 1 / r * (r * ui[0]).dx(0) + ui[1].dx(1)
        grad_div_ui = as_vector((div_ui.dx(0), div_ui.dx(1)))
        L2 -= r * mu * dot(grad_div_ui, grad(q)) * 2 * pi * my_dx
        # div_grad_div_ui = 1/r * (r * grad_div_ui[0]).dx(0) \
        #     + (grad_div_ui[1]).dx(1)
        # L2 += mu * div_grad_div_ui * q * 2*pi*r*dx
        # n = FacetNormal(Q.mesh())
        # L2 -= mu * (n[0] * grad_div_ui[0] + n[1] * grad_div_ui[1]) \
        #     * q * 2*pi*r*ds

    p1 = Function(P)
    if p_bcs:
        solve(
            a2 == L2,
            p1,
            bcs=p_bcs,
            solver_parameters={
                "linear_solver": "iterative",
                "symmetric": True,
                "preconditioner": "hypre_amg",
                "krylov_solver": {
                    "relative_tolerance": tol,
                    "absolute_tolerance": 0.0,
                    "maximum_iterations": 100,
                    "monitor_convergence": verbose,
                },
            },
        )
    else:
        # If we're dealing with a pure Neumann problem here (which is the
        # default case), this doesn't hurt CG if the system is consistent,
        # cf. :cite:`vdV03`. And indeed it is consistent if and only if
        #
        #   \int_\Gamma r n.u = 0.
        #
        # This makes clear that for incompressible Navier-Stokes, one
        # either needs to make sure that inflow and outflow always add up
        # to 0, or one has to specify pressure boundary conditions.
        #
        # If the right-hand side is very small, round-off errors may impair
        # the consistency of the system. Make sure the system we are
        # solving remains consistent.
        A = assemble(a2)
        b = assemble(L2)
        # Assert that the system is indeed consistent.
        e = Function(P)
        e.interpolate(Constant(1.0))
        evec = e.vector()
        evec /= norm(evec)
        alpha = b.inner(evec)
        normB = norm(b)
        # Assume that in every component of the vector, a round-off error
        # of the magnitude DOLFIN_EPS is present. This leads to the
        # criterion
        #    |<b,e>| / (||b||*||e||) < DOLFIN_EPS
        # as a check whether to consider the system consistent up to
        # round-off error.
        #
        # TODO think about condition here
        # if abs(alpha) > normB * DOLFIN_EPS:
        if abs(alpha) > normB * 1.0e-12:
            # divu = 1 / r * (r * u[0]).dx(0) + u[1].dx(1)
            adivu = assemble(((r * u[0]).dx(0) + u[1].dx(1)) * 2 * pi * my_dx)
            info("\\int 1/r * div(r*u) * 2*pi*r  =  {:e}".format(adivu))
            n = FacetNormal(P.mesh())
            boundary_integral = assemble((n[0] * u[0] + n[1] * u[1]) * 2 * pi * r * ds)
            info("\\int_Gamma n.u * 2*pi*r = {:e}".format(boundary_integral))
            message = (
                "System not consistent! "
                "<b,e> = {:g}, ||b|| = {:g}, <b,e>/||b|| = {:e}.".format(
                    alpha, normB, alpha / normB
                )
            )
            info(message)
            # # Plot the stuff, and project it to a finer mesh with linear
            # # elements for the purpose.
            # plot(divu, title='div(u_tentative)')
            # # Vp = FunctionSpace(Q.mesh(), 'CG', 2)
            # # Wp = MixedFunctionSpace([Vp, Vp])
            # # up = project(u, Wp)
            # fine_mesh = Q.mesh()
            # for k in range(1):
            #     fine_mesh = refine(fine_mesh)
            # V = FunctionSpace(fine_mesh, 'CG', 1)
            # W = V * V
            # # uplot = Function(W)
            # # uplot.interpolate(u)
            # uplot = project(u, W)
            # plot(uplot[0], title='u_tentative[0]')
            # plot(uplot[1], title='u_tentative[1]')
            # # plot(u, title='u_tentative')
            # interactive()
            # exit()
            raise RuntimeError(message)
        # Project out the roundoff error.
        b -= alpha * evec

        #
        # In principle, the ILU preconditioner isn't advised here since it
        # might destroy the semidefiniteness needed for CG.
        #
        # The system is consistent, but the matrix has an eigenvalue 0.
        # This does not harm the convergence of CG, but when
        # preconditioning one has to make sure that the preconditioner
        # preserves the kernel. ILU might destroy this (and the
        # semidefiniteness). With AMG, the coarse grid solves cannot be LU
        # then, so try Jacobi here.
        # <http://lists.mcs.anl.gov/pipermail/petsc-users/2012-February/012139.html>
        #
        prec = PETScPreconditioner("hypre_amg")
        from dolfin import PETScOptions

        PETScOptions.set("pc_hypre_boomeramg_relax_type_coarse", "jacobi")
        solver = PETScKrylovSolver("cg", prec)
        solver.parameters["absolute_tolerance"] = 0.0
        solver.parameters["relative_tolerance"] = tol
        solver.parameters["maximum_iterations"] = 100
        solver.parameters["monitor_convergence"] = verbose
        # Create solver and solve system
        A_petsc = as_backend_type(A)
        b_petsc = as_backend_type(b)
        p1_petsc = as_backend_type(p1.vector())
        solver.set_operator(A_petsc)
        solver.solve(p1_petsc, b_petsc)
    return p1
def solve(W, P,
          mu,
          u_bcs, p_bcs,
          f,
          verbose=True,
          tol=1.0e-10
          ):
    # Some initial sanity checks.
    assert mu > 0.0

    WP = MixedFunctionSpace([W, P])

    # Translate the boundary conditions into the product space.
    # This conditional loop is able to deal with conditions of the kind
    #
    #     DirichletBC(W.sub(1), 0.0, right_boundary)
    #
    new_bcs = []
    for k, bcs in enumerate([u_bcs, p_bcs]):
        for bc in bcs:
            space = bc.function_space()
            C = space.component()
            if len(C) == 0:
                new_bcs.append(DirichletBC(WP.sub(k),
                                           bc.value(),
                                           bc.domain_args[0]))
            elif len(C) == 1:
                new_bcs.append(DirichletBC(WP.sub(k).sub(int(C[0])),
                                           bc.value(),
                                           bc.domain_args[0]))
            else:
                raise RuntimeError('Illegal number of subspace components.')

    # Define variational problem
    (u, p) = TrialFunctions(WP)
    (v, q) = TestFunctions(WP)

    # Build system.
    # The sign of the div(u)-term is somewhat arbitrary since the right-hand
    # side is 0 here. We can either make the system symmetric or positive-
    # definite.
    # On a second note, we have
    #
    #    \int grad(p).v = - \int p * div(v) + \int_\Gamma p n.v.
    #
    # Since, we have either p=0 or n.v=0 on the boundary, we could as well
    # replace the term dot(grad(p), v) by -p*div(v).
    #
    a = mu * inner(grad(u), grad(v))*dx \
      - p * div(v) * dx \
      - q * div(u) * dx
    #a = mu * inner(grad(u), grad(v))*dx + dot(grad(p), v) * dx \
    #  - div(u) * q * dx
    L = dot(f, v)*dx
    A, b = assemble_system(a, L, new_bcs)

    if has_petsc():
        # For an assortment of preconditioners, see
        #
        #     Performance and analysis of saddle point preconditioners
        #     for the discrete steady-state Navier-Stokes equations;
        #     H.C. Elman, D.J. Silvester, A.J. Wathen;
        #     Numer. Math. (2002) 90: 665-688;
        #     <http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.145.3554>.
        #
        # Set up field split.
        W = SubSpace(WP, 0)
        P = SubSpace(WP, 1)
        u_dofs = W.dofmap().dofs()
        p_dofs = P.dofmap().dofs()
        prec = PETScPreconditioner()
        prec.set_fieldsplit([u_dofs, p_dofs], ['u', 'p'])

        PETScOptions.set('pc_type', 'fieldsplit')
        PETScOptions.set('pc_fieldsplit_type', 'additive')
        PETScOptions.set('fieldsplit_u_pc_type', 'lu')
        PETScOptions.set('fieldsplit_p_pc_type', 'jacobi')

        ## <http://scicomp.stackexchange.com/questions/7288/which-preconditioners-and-solver-in-petsc-for-indefinite-symmetric-systems-sho>
        #PETScOptions.set('pc_type', 'fieldsplit')
        ##PETScOptions.set('pc_fieldsplit_type', 'schur')
        ##PETScOptions.set('pc_fieldsplit_schur_fact_type', 'upper')
        #PETScOptions.set('pc_fieldsplit_detect_saddle_point')
        ##PETScOptions.set('fieldsplit_u_pc_type', 'lsc')
        ##PETScOptions.set('fieldsplit_u_ksp_type', 'preonly')

        #PETScOptions.set('pc_type', 'fieldsplit')
        #PETScOptions.set('fieldsplit_u_pc_type', 'hypre')
        #PETScOptions.set('fieldsplit_u_ksp_type', 'preonly')
        #PETScOptions.set('fieldsplit_p_pc_type', 'jacobi')
        #PETScOptions.set('fieldsplit_p_ksp_type', 'preonly')

        ## From PETSc/src/ksp/ksp/examples/tutorials/ex42-fsschur.opts:
        #PETScOptions.set('pc_type', 'fieldsplit')
        #PETScOptions.set('pc_fieldsplit_type', 'SCHUR')
        #PETScOptions.set('pc_fieldsplit_schur_fact_type', 'UPPER')
        #PETScOptions.set('fieldsplit_p_ksp_type', 'preonly')
        #PETScOptions.set('fieldsplit_u_pc_type', 'bjacobi')

        ## From
        ##
        ##     Composable Linear Solvers for Multiphysics;
        ##     J. Brown, M. Knepley, D.A. May, L.C. McInnes, B. Smith;
        ##     <http://www.computer.org/csdl/proceedings/ispdc/2012/4805/00/4805a055-abs.html>;
        ##     <http://www.mcs.anl.gov/uploads/cels/papers/P2017-0112.pdf>.
        ##
        #PETScOptions.set('pc_type', 'fieldsplit')
        #PETScOptions.set('pc_fieldsplit_type', 'schur')
        #PETScOptions.set('pc_fieldsplit_schur_factorization_type', 'upper')
        ##
        #PETScOptions.set('fieldsplit_u_ksp_type', 'cg')
        #PETScOptions.set('fieldsplit_u_ksp_rtol', 1.0e-6)
        #PETScOptions.set('fieldsplit_u_pc_type', 'bjacobi')
        #PETScOptions.set('fieldsplit_u_sub_pc_type', 'cholesky')
        ##
        #PETScOptions.set('fieldsplit_p_ksp_type', 'fgmres')
        #PETScOptions.set('fieldsplit_p_ksp_constant_null_space')
        #PETScOptions.set('fieldsplit_p_pc_type', 'lsc')
        ##
        #PETScOptions.set('fieldsplit_p_lsc_ksp_type', 'cg')
        #PETScOptions.set('fieldsplit_p_lsc_ksp_rtol', 1.0e-2)
        #PETScOptions.set('fieldsplit_p_lsc_ksp_constant_null_space')
        ##PETScOptions.set('fieldsplit_p_lsc_ksp_converged_reason')
        #PETScOptions.set('fieldsplit_p_lsc_pc_type', 'bjacobi')
        #PETScOptions.set('fieldsplit_p_lsc_sub_pc_type', 'icc')

        # Create Krylov solver with custom preconditioner.
        solver = PETScKrylovSolver('gmres', prec)
        solver.set_operator(A)
    else:
        # Use the preconditioner as recommended in
        # <http://fenicsproject.org/documentation/dolfin/dev/python/demo/pde/stokes-iterative/python/documentation.html>,
        #
        #     prec = inner(grad(u), grad(v))*dx - p*q*dx
        #
        # although it doesn't seem to be too efficient.
        # The sign on the last term doesn't matter.
        prec = mu * inner(grad(u), grad(v))*dx \
             - p*q*dx
        M, _ = assemble_system(prec, L, new_bcs)
        #solver = KrylovSolver('tfqmr', 'amg')
        solver = KrylovSolver('gmres', 'amg')
        solver.set_operators(A, M)

    solver.parameters['monitor_convergence'] = verbose
    solver.parameters['report'] = verbose
    solver.parameters['absolute_tolerance'] = 0.0
    solver.parameters['relative_tolerance'] = tol
    solver.parameters['maximum_iterations'] = 500

    # Solve
    up = Function(WP)
    solver.solve(up.vector(), b)

    # Get sub-functions
    u, p = up.split()

    return u, p
    def _pressure_poisson(self, p1, p0,
                          mu, ui,
                          u,
                          p_bcs=None,
                          rotational_form=False,
                          tol=1.0e-10,
                          verbose=True
                          ):
        '''Solve the pressure Poisson equation
            -1/r \div(r \nabla (p1-p0)) = -1/r div(r*u),
            boundary conditions,
        for
            \nabla p = u.
        '''
        r = Expression('x[0]', degree=1, domain=self.W.mesh())

        Q = p1.function_space()

        p = TrialFunction(Q)
        q = TestFunction(Q)
        a2 = dot(r * grad(p), grad(q)) * 2 * pi * dx
        # The boundary conditions
        #     n.(p1-p0) = 0
        # are implicitly included.
        #
        # L2 = -div(r*u) * q * 2*pi*dx
        div_u = 1/r * (r * u[0]).dx(0) + u[1].dx(1)
        L2 = -div_u * q * 2*pi*r*dx
        if p0:
            L2 += r * dot(grad(p0), grad(q)) * 2*pi*dx

        # In the Cartesian variant of the rotational form, one makes use of the
        # fact that
        #
        #     curl(curl(u)) = grad(div(u)) - div(grad(u)).
        #
        # The same equation holds true in cylindrical form. Hence, to get the
        # rotational form of the splitting scheme, we need to
        #
        # rotational form
        if rotational_form:
            # If there is no dependence of the angular coordinate, what is
            # div(grad(div(u))) in Cartesian coordinates becomes
            #
            #     1/r div(r * grad(1/r div(r*u)))
            #
            # in cylindrical coordinates (div and grad are in cylindrical
            # coordinates). Unfortunately, we cannot write it down that
            # compactly since u_phi is in the game.
            # When using P2 elements, this value will be 0 anyways.
            div_ui = 1/r * (r * ui[0]).dx(0) + ui[1].dx(1)
            grad_div_ui = as_vector((div_ui.dx(0), div_ui.dx(1)))
            L2 -= r * mu * dot(grad_div_ui, grad(q)) * 2*pi*dx
            #div_grad_div_ui = 1/r * (r * grad_div_ui[0]).dx(0) \
            #    + (grad_div_ui[1]).dx(1)
            #L2 += mu * div_grad_div_ui * q * 2*pi*r*dx
            #n = FacetNormal(Q.mesh())
            #L2 -= mu * (n[0] * grad_div_ui[0] + n[1] * grad_div_ui[1]) \
            #    * q * 2*pi*r*ds

        if p_bcs:
            solve(
                a2 == L2, p1,
                bcs=p_bcs,
                solver_parameters={
                    'linear_solver': 'iterative',
                    'symmetric': True,
                    'preconditioner': 'amg',
                    'krylov_solver': {'relative_tolerance': tol,
                                      'absolute_tolerance': 0.0,
                                      'maximum_iterations': 100,
                                      'monitor_convergence': verbose}
                    }
                )
        else:
            # If we're dealing with a pure Neumann problem here (which is the
            # default case), this doesn't hurt CG if the system is consistent,
            # cf. :cite:`vdV03`. And indeed it is consistent if and only if
            #
            #   \int_\Gamma r n.u = 0.
            #
            # This makes clear that for incompressible Navier-Stokes, one
            # either needs to make sure that inflow and outflow always add up
            # to 0, or one has to specify pressure boundary conditions.
            #
            # If the right-hand side is very small, round-off errors may impair
            # the consistency of the system. Make sure the system we are
            # solving remains consistent.
            A = assemble(a2)
            b = assemble(L2)
            # Assert that the system is indeed consistent.
            e = Function(Q)
            e.interpolate(Constant(1.0))
            evec = e.vector()
            evec /= norm(evec)
            alpha = b.inner(evec)
            normB = norm(b)
            # Assume that in every component of the vector, a round-off error
            # of the magnitude DOLFIN_EPS is present. This leads to the
            # criterion
            #    |<b,e>| / (||b||*||e||) < DOLFIN_EPS
            # as a check whether to consider the system consistent up to
            # round-off error.
            #
            # TODO think about condition here
            #if abs(alpha) > normB * DOLFIN_EPS:
            if abs(alpha) > normB * 1.0e-12:
                divu = 1 / r * (r * u[0]).dx(0) + u[1].dx(1)
                adivu = assemble(((r * u[0]).dx(0) + u[1].dx(1)) * 2 * pi * dx)
                info('\int 1/r * div(r*u) * 2*pi*r  =  %e' % adivu)
                n = FacetNormal(Q.mesh())
                boundary_integral = assemble((n[0] * u[0] + n[1] * u[1])
                                             * 2 * pi * r * ds)
                info('\int_Gamma n.u * 2*pi*r = %e' % boundary_integral)
                message = ('System not consistent! '
                           '<b,e> = %g, ||b|| = %g, <b,e>/||b|| = %e.') \
                           % (alpha, normB, alpha / normB)
                info(message)
                # Plot the stuff, and project it to a finer mesh with linear
                # elements for the purpose.
                plot(divu, title='div(u_tentative)')
                #Vp = FunctionSpace(Q.mesh(), 'CG', 2)
                #Wp = MixedFunctionSpace([Vp, Vp])
                #up = project(u, Wp)
                fine_mesh = Q.mesh()
                for k in range(1):
                    fine_mesh = refine(fine_mesh)
                V = FunctionSpace(fine_mesh, 'CG', 1)
                W = V * V
                #uplot = Function(W)
                #uplot.interpolate(u)
                uplot = project(u, W)
                plot(uplot[0], title='u_tentative[0]')
                plot(uplot[1], title='u_tentative[1]')
                #plot(u, title='u_tentative')
                interactive()
                exit()
                raise RuntimeError(message)
            # Project out the roundoff error.
            b -= alpha * evec

            #
            # In principle, the ILU preconditioner isn't advised here since it
            # might destroy the semidefiniteness needed for CG.
            #
            # The system is consistent, but the matrix has an eigenvalue 0.
            # This does not harm the convergence of CG, but when
            # preconditioning one has to make sure that the preconditioner
            # preserves the kernel.  ILU might destroy this (and the
            # semidefiniteness). With AMG, the coarse grid solves cannot be LU
            # then, so try Jacobi here.
            # <http://lists.mcs.anl.gov/pipermail/petsc-users/2012-February/012139.html>
            #
            prec = PETScPreconditioner('hypre_amg')
            from dolfin import PETScOptions
            PETScOptions.set('pc_hypre_boomeramg_relax_type_coarse', 'jacobi')
            solver = PETScKrylovSolver('cg', prec)
            solver.parameters['absolute_tolerance'] = 0.0
            solver.parameters['relative_tolerance'] = tol
            solver.parameters['maximum_iterations'] = 100
            solver.parameters['monitor_convergence'] = verbose
            # Create solver and solve system
            A_petsc = as_backend_type(A)
            b_petsc = as_backend_type(b)
            p1_petsc = as_backend_type(p1.vector())
            solver.set_operator(A_petsc)
            solver.solve(p1_petsc, b_petsc)
            # This would be the stump for Epetra:
            #solve(A, p.vector(), b, 'cg', 'ml_amg')
        return
class ObjectiveAcoustic(LinearOperator):
    """
    Computes data misfit, gradient and Hessian evaluation for the seismic
    inverse problem using acoustic wave data
    """
    # CONSTRUCTORS:
    def __init__(self, mpicomm_global, acousticwavePDE, sources, \
    sourcesindex, timestepsindex, \
    invparam='ab', regularization=None):
        """ 
        Input:
            acousticwavePDE should be an instantiation from class AcousticWave
        """
        self.mpicomm_global = mpicomm_global

        self.PDE = acousticwavePDE
        self.PDE.exact = None
        self.obsop = None   # Observation operator
        self.dd = None  # observations
        self.fwdsource = sources
        self.srcindex = sourcesindex
        self.tsteps = timestepsindex
        self.PDEcount = 0

        self.inverta = False
        self.invertb = False
        if 'a' in invparam:
            self.inverta = True
        if 'b' in invparam:
            self.invertb = True
        assert self.inverta + self.invertb > 0

        Vm = self.PDE.Vm
        V = self.PDE.V
        VmVm = createMixedFS(Vm, Vm)
        self.ab = Function(VmVm)   # used for conversion (Vm,Vm)->VmVm
        self.invparam = invparam
        self.MG = Function(VmVm)
        self.MGv = self.MG.vector()
        self.Grad = Function(VmVm)
        self.srchdir = Function(VmVm)
        self.delta_m = Function(VmVm)
        self.m_bkup = Function(VmVm)
        LinearOperator.__init__(self, self.MGv, self.MGv)
        self.GN = False

        if regularization == None:  
            print '[ObjectiveAcoustic] *** Warning: Using zero regularization'
            self.regularization = ZeroRegularization(Vm)
        else:   
            self.regularization = regularization
            self.PD = self.regularization.isPD()
        self.alpha_reg = 1.0

        self.p, self.q = Function(V), Function(V)
        self.phat, self.qhat = Function(V), Function(V)
        self.ahat, self.bhat = Function(Vm), Function(Vm)
        self.ptrial, self.ptest = TrialFunction(V), TestFunction(V)
        self.mtest, self.mtrial = TestFunction(Vm), TrialFunction(Vm)
        if self.PDE.parameters['lumpM']:
            self.Mprime = LumpedMassMatrixPrime(Vm, V, self.PDE.M.ratio)
            self.get_gradienta = self.get_gradienta_lumped
            self.get_hessiana = self.get_hessiana_lumped
            self.get_incra = self.get_incra_lumped
        else:
            self.wkformgrada = inner(self.mtest*self.p, self.q)*dx
            self.get_gradienta = self.get_gradienta_full
            self.wkformhessa = inner(self.phat*self.mtest, self.q)*dx \
            + inner(self.p*self.mtest, self.qhat)*dx
            self.wkformhessaGN = inner(self.p*self.mtest, self.qhat)*dx
            self.get_hessiana = self.get_hessiana_full
            self.wkformrhsincra = inner(self.ahat*self.ptrial, self.ptest)*dx
            self.get_incra = self.get_incra_full
        self.wkformgradb = inner(self.mtest*nabla_grad(self.p), nabla_grad(self.q))*dx
        self.wkformgradbout = assemble(self.wkformgradb)
        self.wkformrhsincrb = inner(self.bhat*nabla_grad(self.ptrial), nabla_grad(self.ptest))*dx
        self.wkformhessb = inner(nabla_grad(self.phat)*self.mtest, nabla_grad(self.q))*dx \
        + inner(nabla_grad(self.p)*self.mtest, nabla_grad(self.qhat))*dx
        self.wkformhessbGN = inner(nabla_grad(self.p)*self.mtest, nabla_grad(self.qhat))*dx

        # Mass matrix:
        self.mmtest, self.mmtrial = TestFunction(VmVm), TrialFunction(VmVm)
        weak_m =  inner(self.mmtrial, self.mmtest)*dx
        self.Mass = assemble(weak_m)
        self.solverM = PETScKrylovSolver("cg", "jacobi")
        self.solverM.parameters["maximum_iterations"] = 2000
        self.solverM.parameters["absolute_tolerance"] = 1e-24
        self.solverM.parameters["relative_tolerance"] = 1e-24
        self.solverM.parameters["report"] = False
        self.solverM.parameters["error_on_nonconvergence"] = True 
        self.solverM.parameters["nonzero_initial_guess"] = False # True?
        self.solverM.set_operator(self.Mass)

        # Time-integration factors
        self.factors = np.ones(self.PDE.times.size)
        self.factors[0], self.factors[-1] = 0.5, 0.5
        self.factors *= self.PDE.Dt
        self.invDt = 1./self.PDE.Dt

        # Absorbing BCs
        if self.PDE.parameters['abc']:
            assert not self.PDE.parameters['lumpD']

            self.wkformgradaABC = inner(
            self.mtest*sqrt(self.PDE.b/self.PDE.a)*self.p, 
            self.q)*self.PDE.ds(1)
            self.wkformgradbABC = inner(
            self.mtest*sqrt(self.PDE.a/self.PDE.b)*self.p, 
            self.q)*self.PDE.ds(1)
            self.wkformgradaABCout = assemble(self.wkformgradaABC)
            self.wkformgradbABCout = assemble(self.wkformgradbABC)

            self.wkformincrrhsABC = inner(
            (self.ahat*sqrt(self.PDE.b/self.PDE.a)
             + self.bhat*sqrt(self.PDE.a/self.PDE.b))*self.ptrial,
            self.ptest)*self.PDE.ds(1)

            self.wkformhessaABC = inner(
            (self.bhat/sqrt(self.PDE.a*self.PDE.b) - 
            self.ahat*sqrt(self.PDE.b/(self.PDE.a*self.PDE.a*self.PDE.a)))
            *self.p*self.mtest, self.q)*self.PDE.ds(1)
            self.wkformhessbABC = inner(
            (self.ahat/sqrt(self.PDE.a*self.PDE.b) - 
            self.bhat*sqrt(self.PDE.a/(self.PDE.b*self.PDE.b*self.PDE.b)))
            *self.p*self.mtest, self.q)*self.PDE.ds(1)


    def copy(self):
        """(hard) copy constructor"""
        newobj = self.__class__(self.PDE.copy())
        setfct(newobj.MG, self.MG)
        setfct(newobj.Grad, self.Grad)
        setfct(newobj.srchdir, self.srchdir)
        newobj.obsop = self.obsop
        newobj.dd = self.dd
        newobj.fwdsource = self.fwdsource
        newobj.srcindex = self.srcindex
        newobj.tsteps = self.tsteps
        return newobj


    # FORWARD PROBLEM + COST:
    #@profile
    def solvefwd(self, cost=False):
        self.PDE.set_fwd()
        self.solfwd, self.solpfwd, self.solppfwd = [], [], [] 
        self.Bp = []

        #TODO: make fwdsource iterable to return source term
        Ricker = self.fwdsource[0]
        srcv = self.fwdsource[2]
        for sii in self.srcindex:
            ptsrc = self.fwdsource[1][sii]
            def srcterm(tt):
                srcv.zero()
                srcv.axpy(Ricker(tt), ptsrc)
                return srcv
            self.PDE.ftime = srcterm
            solfwd, solpfwd, solppfwd,_ = self.PDE.solve()
            self.solfwd.append(solfwd)
            self.solpfwd.append(solpfwd)
            self.solppfwd.append(solppfwd)

            self.PDEcount += 1

            #TODO: come back and parallellize this too (over time steps)
            Bp = np.zeros((len(self.obsop.PtwiseObs.Points),len(solfwd)))
            for index, sol in enumerate(solfwd):
                setfct(self.p, sol[0])
                Bp[:,index] = self.obsop.obs(self.p)
            self.Bp.append(Bp)

        if cost:
            assert not self.dd == None, "Provide data observations to compute cost"
            self.cost_misfit_local = 0.0
            for Bp, dd in izip(self.Bp, self.dd):
                self.cost_misfit_local += self.obsop.costfct(\
                Bp[:,self.tsteps], dd[:,self.tsteps],\
                self.PDE.times[self.tsteps], self.factors[self.tsteps])
            self.cost_misfit = MPI.sum(self.mpicomm_global, self.cost_misfit_local)
            self.cost_misfit /= len(self.fwdsource[1])
            self.cost_reg = self.regularization.costab(self.PDE.a, self.PDE.b)
            self.cost = self.cost_misfit + self.alpha_reg*self.cost_reg
            if DEBUG:   
                print 'cost_misfit={}, cost_reg={}'.format(\
                self.cost_misfit, self.cost_reg)

    def solvefwd_cost(self):    self.solvefwd(True)


    # ADJOINT PROBLEM + GRADIENT:
    #@profile
    def solveadj(self, grad=False):
        self.PDE.set_adj()
        self.soladj, self.solpadj, self.solppadj = [], [], []

        for Bp, dd in zip(self.Bp, self.dd):
            self.obsop.assemble_rhsadj(Bp, dd, self.PDE.times, self.PDE.bc)
            self.PDE.ftime = self.obsop.ftimeadj
            soladj,solpadj,solppadj,_ = self.PDE.solve()
            self.soladj.append(soladj)
            self.solpadj.append(solpadj)
            self.solppadj.append(solppadj)

            self.PDEcount += 1

        if grad:
            self.MG.vector().zero()
            MGa_local, MGb_local = self.MG.split(deepcopy=True)
            MGav_local, MGbv_local = MGa_local.vector(), MGb_local.vector()

            t0, t1 = self.tsteps[0], self.tsteps[-1]+1

            for solfwd, solpfwd, solppfwd, soladj in \
            izip(self.solfwd, self.solpfwd, self.solppfwd, self.soladj):

                for fwd, fwdp, fwdpp, adj, fact in \
                izip(solfwd[t0:t1], solpfwd[t0:t1], solppfwd[t0:t1],\
                soladj[::-1][t0:t1], self.factors[t0:t1]):
                    setfct(self.q, adj[0])
                    if self.inverta:
                        # gradient a
                        setfct(self.p, fwdpp[0])
                        MGav_local.axpy(fact, self.get_gradienta()) 
                    if self.invertb:
                        # gradient b
                        setfct(self.p, fwd[0])
                        assemble(form=self.wkformgradb, tensor=self.wkformgradbout)
                        MGbv_local.axpy(fact, self.wkformgradbout)

                    if self.PDE.parameters['abc']:
                        setfct(self.p, fwdp[0])
                        if self.inverta:
                            assemble(form=self.wkformgradaABC, tensor=self.wkformgradaABCout)
                            MGav_local.axpy(0.5*fact, self.wkformgradaABCout)
                        if self.invertb:
                            assemble(form=self.wkformgradbABC, tensor=self.wkformgradbABCout)
                            MGbv_local.axpy(0.5*fact, self.wkformgradbABCout)

            MGa, MGb = self.MG.split(deepcopy=True)
            MPIAllReduceVector(MGav_local, MGa.vector(), self.mpicomm_global)
            MPIAllReduceVector(MGbv_local, MGb.vector(), self.mpicomm_global)
            setfct(MGa, MGa.vector()/len(self.fwdsource[1]))
            setfct(MGb, MGb.vector()/len(self.fwdsource[1]))
            self.MG.vector().zero()
            if self.inverta:
                assign(self.MG.sub(0), MGa)
            if self.invertb:
                assign(self.MG.sub(1), MGb)
            if DEBUG:
                print 'grad_misfit={}, grad_reg={}'.format(\
                self.MG.vector().norm('l2'),\
                self.regularization.gradab(self.PDE.a, self.PDE.b).norm('l2'))

            self.MG.vector().axpy(self.alpha_reg, \
            self.regularization.gradab(self.PDE.a, self.PDE.b))

            try:
                self.solverM.solve(self.Grad.vector(), self.MG.vector())
            except:
                # if |G|<<1, first residuals may diverge
                # caveat: Hope that ALL processes throw an exception
                pseudoGradnorm = np.sqrt(self.MGv.inner(self.MGv))
                if pseudoGradnorm < 1e-8:
                    print '*** Warning: Increasing divergence_limit for Mass matrix solver'
                    self.solverM.parameters["divergence_limit"] = 1e6
                    self.solverM.solve(self.Grad.vector(), self.MG.vector())
                else:
                    print '*** Error: Problem with Mass matrix solver'
                    sys.exit(1)

    def solveadj_constructgrad(self):   self.solveadj(True)

    def get_gradienta_lumped(self):
        return self.Mprime.get_gradient(self.p.vector(), self.q.vector())

    def get_gradienta_full(self):
        return assemble(self.wkformgrada)


    # HESSIAN:
    #@profile
    def ftimeincrfwd(self, tt):
        """ Compute rhs for incremental forward at time tt """
        try:
            index = int(np.where(isequal(self.PDE.times, tt, 1e-14))[0])
        except:
            print 'Error in ftimeincrfwd at time {}'.format(tt)
            print np.min(np.abs(self.PDE.times-tt))
            sys.exit(0)

        # bhat: bhat*grad(p).grad(qtilde)
#        assert isequal(tt, self.solfwdi[index][1], 1e-16)
        setfct(self.p, self.solfwdi[index][0])
        self.q.vector().zero()
        self.q.vector().axpy(1.0, self.C*self.p.vector())

        # ahat: ahat*p''*qtilde:
        setfct(self.p, self.solppfwdi[index][0])
        self.q.vector().axpy(1.0, self.get_incra(self.p.vector()))

        # ABC:
        if self.PDE.parameters['abc']:
            setfct(self.phat, self.solpfwdi[index][0])
            self.q.vector().axpy(0.5, self.Dp*self.phat.vector())

        return -1.0*self.q.vector()


    #@profile
    def ftimeincradj(self, tt):
        """ Compute rhs for incremental adjoint at time tt """
        try:
            indexf = int(np.where(isequal(self.PDE.times, tt, 1e-14))[0])
            indexa = int(np.where(isequal(self.PDE.times[::-1], tt, 1e-14))[0])
        except:
            print 'Error in ftimeincradj at time {}'.format(tt)
            print np.min(np.abs(self.PDE.times-tt))
            sys.exit(0)

        # B* B phat
#        assert isequal(tt, self.solincrfwd[indexf][1], 1e-16)
        setfct(self.phat, self.solincrfwd[indexf][0])
        self.qhat.vector().zero()
        self.qhat.vector().axpy(1.0, self.obsop.incradj(self.phat, tt))

        if not self.GN:
            # bhat: bhat*grad(ptilde).grad(v)
#            assert isequal(tt, self.soladji[indexa][1], 1e-16)
            setfct(self.q, self.soladji[indexa][0])
            self.qhat.vector().axpy(1.0, self.C*self.q.vector())

            # ahat: ahat*ptilde*q'':
            setfct(self.q, self.solppadji[indexa][0])
            self.qhat.vector().axpy(1.0, self.get_incra(self.q.vector()))

            # ABC:
            if self.PDE.parameters['abc']:
                setfct(self.phat, self.solpadji[indexa][0])
                self.qhat.vector().axpy(-0.5, self.Dp*self.phat.vector())

        return -1.0*self.qhat.vector()

    def get_incra_full(self, pvector):
        return self.E*pvector

    def get_incra_lumped(self, pvector):
        return self.Mprime.get_incremental(self.ahat.vector(), pvector)

        
    #@profile
    def mult(self, abhat, y):
        """
        mult(self, abhat, y): return y = Hessian * abhat
        inputs:
            y, abhat = Function(V).vector()
        """
        setfct(self.ab, abhat)
        ahat, bhat = self.ab.split(deepcopy=True)
        setfct(self.ahat, ahat)
        setfct(self.bhat, bhat)
        if not self.inverta:
            self.ahat.vector().zero()
        if not self.invertb:
            self.bhat.vector().zero()

        self.C = assemble(self.wkformrhsincrb)
        if not self.PDE.parameters['lumpM']:    self.E = assemble(self.wkformrhsincra)
        if self.PDE.parameters['abc']:  self.Dp = assemble(self.wkformincrrhsABC)

        t0, t1 = self.tsteps[0], self.tsteps[-1]+1

        # Compute Hessian*abhat
        self.ab.vector().zero()
        yaF_local, ybF_local = self.ab.split(deepcopy=True)
        ya_local, yb_local = yaF_local.vector(), ybF_local.vector()

        # iterate over sources:
        for self.solfwdi, self.solpfwdi, self.solppfwdi, \
        self.soladji, self.solpadji, self.solppadji \
        in izip(self.solfwd, self.solpfwd, self.solppfwd, \
        self.soladj, self.solpadj, self.solppadj):
            # incr. fwd
            self.PDE.set_fwd()
            self.PDE.ftime = self.ftimeincrfwd
            self.solincrfwd,solpincrfwd,self.solppincrfwd,_ = self.PDE.solve()
            self.PDEcount += 1

            # incr. adj
            self.PDE.set_adj()
            self.PDE.ftime = self.ftimeincradj
            solincradj,_,_,_ = self.PDE.solve()
            self.PDEcount += 1

            # assemble Hessian-vect product:
            for fwd, adj, fwdp, incrfwdp, \
            fwdpp, incrfwdpp, incrfwd, incradj, fact \
            in izip(self.solfwdi[t0:t1], self.soladji[::-1][t0:t1],\
            self.solpfwdi[t0:t1], solpincrfwd[t0:t1], \
            self.solppfwdi[t0:t1], self.solppincrfwd[t0:t1],\
            self.solincrfwd[t0:t1], solincradj[::-1][t0:t1], self.factors[t0:t1]):
#                ttf, tta, ttf2 = incrfwd[1], incradj[1], fwd[1]
#                assert isequal(ttf, tta, 1e-16), 'tfwd={}, tadj={}, reldiff={}'.\
#                format(ttf, tta, abs(ttf-tta)/ttf)
#                assert isequal(ttf, ttf2, 1e-16), 'tfwd={}, tadj={}, reldiff={}'.\
#                format(ttf, ttf2, abs(ttf-ttf2)/ttf)

                setfct(self.q, adj[0])
                setfct(self.qhat, incradj[0])
                if self.invertb:
                    # Hessian b
                    setfct(self.p, fwd[0])
                    setfct(self.phat, incrfwd[0])
                    if self.GN:
                        yb_local.axpy(fact, assemble(self.wkformhessbGN))
                    else:
                        yb_local.axpy(fact, assemble(self.wkformhessb))

                if self.inverta:
                    # Hessian a
                    setfct(self.p, fwdpp[0])
                    setfct(self.phat, incrfwdpp[0])
                    ya_local.axpy(fact, self.get_hessiana())

                if self.PDE.parameters['abc']:
                    if not self.GN:
                        setfct(self.p, incrfwdp[0])
                        if self.inverta:
                            ya_local.axpy(0.5*fact, assemble(self.wkformgradaABC))
                        if self.invertb:
                            yb_local.axpy(0.5*fact, assemble(self.wkformgradbABC))

                    setfct(self.p, fwdp[0])
                    setfct(self.q, incradj[0])
                    if self.inverta:
                        ya_local.axpy(0.5*fact, assemble(self.wkformgradaABC))
                    if self.invertb:
                        yb_local.axpy(0.5*fact, assemble(self.wkformgradbABC))

                    if not self.GN:
                        setfct(self.q, adj[0])
                        if self.inverta:
                            ya_local.axpy(0.25*fact, assemble(self.wkformhessaABC))
                        if self.invertb:
                            yb_local.axpy(0.25*fact, assemble(self.wkformhessbABC))

        yaF, ybF = self.ab.split(deepcopy=True)
        MPIAllReduceVector(ya_local, yaF.vector(), self.mpicomm_global)
        MPIAllReduceVector(yb_local, ybF.vector(), self.mpicomm_global)
        self.ab.vector().zero()
        if self.inverta:
            assign(self.ab.sub(0), yaF)
        if self.invertb:
            assign(self.ab.sub(1), ybF)
        y.zero()
        y.axpy(1.0/len(self.fwdsource[1]), self.ab.vector())
        if DEBUG:
            print 'Hess_misfit={}, Hess_reg={}'.format(\
            y.norm('l2'),\
            self.regularization.hessianab(self.ahat.vector(),\
            self.bhat.vector()).norm('l2'))

        y.axpy(self.alpha_reg, \
        self.regularization.hessianab(self.ahat.vector(), self.bhat.vector()))

    def get_hessiana_full(self):
        if self.GN:
            return assemble(self.wkformhessaGN)
        else:
            return assemble(self.wkformhessa)

    def get_hessiana_lumped(self):
        if self.GN:
            return self.Mprime.get_gradient(self.p.vector(), self.qhat.vector())
        else:
            return self.Mprime.get_gradient(self.phat.vector(), self.q.vector()) +\
            self.Mprime.get_gradient(self.p.vector(), self.qhat.vector())


    def assemble_hessian(self):
        self.regularization.assemble_hessianab(self.PDE.a, self.PDE.b)



    # SETTERS + UPDATE:
    def update_PDE(self, parameters): self.PDE.update(parameters)

    def update_m(self, medparam):
        """ medparam contains both med parameters """
        setfct(self.ab, medparam)
        a, b = self.ab.split(deepcopy=True)
        self.update_PDE({'a':a, 'b':b})

    def backup_m(self): 
        """ back-up current value of med param a and b """
        assign(self.m_bkup.sub(0), self.PDE.a)
        assign(self.m_bkup.sub(1), self.PDE.b)

    def restore_m(self):    
        """ restore backed-up values of a and b """
        a, b = self.m_bkup.split(deepcopy=True)
        self.update_PDE({'a':a, 'b':b})

    def mediummisfit(self, target_medium):
        """
        Compute medium misfit at current position
        """
        assign(self.ab.sub(0), self.PDE.a)
        assign(self.ab.sub(1), self.PDE.b)
        try:
            diff = self.ab.vector() - target_medium.vector()
        except:
            diff = self.ab.vector() - target_medium
        Md = self.Mass*diff
        self.ab.vector().zero()
        self.ab.vector().axpy(1.0, Md)
        Mda, Mdb = self.ab.split(deepcopy=True)
        self.ab.vector().zero()
        self.ab.vector().axpy(1.0, diff)
        da, db = self.ab.split(deepcopy=True)
        medmisfita = np.sqrt(da.vector().inner(Mda.vector()))
        medmisfitb = np.sqrt(db.vector().inner(Mdb.vector()))
        return medmisfita, medmisfitb 

    def compare_ab_global(self):
        """
        Check that med param (a, b) are the same across all proc
        """
        assign(self.ab.sub(0), self.PDE.a)
        assign(self.ab.sub(1), self.PDE.b)
        ab_recv = self.ab.vector().copy()
        normabloc = np.linalg.norm(self.ab.vector().array())
        MPIAllReduceVector(self.ab.vector(), ab_recv, self.mpicomm_global)
        ab_recv /= MPI.size(self.mpicomm_global)
        diff = ab_recv - self.ab.vector()
        reldiff = np.linalg.norm(diff.array())/normabloc
        assert reldiff < 2e-16, 'Diff in (a,b) across proc: {:.2e}'.format(reldiff)



    # GETTERS:
    def getmbkup(self):         return self.m_bkup.vector()
    def getMG(self):            return self.MGv
    def getprecond(self):
        if self.PC == 'prior':
            return self.regularization.getprecond()
        elif self.PC == 'bfgs':
            return self.bfgsop
        else:
            print 'Wrong keyword for choice of preconditioner'
            sys.exit(1)



    # SOLVE INVERSE PROBLEM
    #@profile
    def inversion(self, initial_medium, target_medium, parameters_in=[], \
    boundsLS=None, myplot=None):
        """ 
        Solve inverse problem with that objective function 
        parameters:
            solverNS = solver for Newton system ('steepest', 'Newton', 'BFGS')
            retolgrad = relative tolerance for stopping criterion (grad)
            abstolgrad = absolute tolerance for stopping criterion (grad)
            tolcost = tolerance for stopping criterion (cost)
            maxiterNewt = max nb of Newton iterations
            nbGNsteps = nb of Newton steps with GN Hessian
            maxtolcg = max value of the tolerance for CG solver
            checkab = nb of steps in-between check of param
            inexactCG = [bool] inexact CG solver or exact CG
            isprint = [bool] print results to screen
            avgPC = [bool] average Preconditioned step over all proc in CG
            PC = choice of preconditioner ('prior', or 'bfgs')
        """
        parameters = {}
        parameters['solverNS']          = 'Newton'
        parameters['reltolgrad']        = 1e-10
        parameters['abstolgrad']        = 1e-14
        parameters['tolcost']           = 1e-24
        parameters['maxiterNewt']       = 100
        parameters['nbGNsteps']         = 10
        parameters['maxtolcg']          = 0.5
        parameters['checkab']           = 10
        parameters['inexactCG']         = True
        parameters['isprint']           = False
        parameters['avgPC']             = True
        parameters['PC']                = 'prior'
        parameters['BFGS_damping']      = 0.2
        parameters['memory_limit']      = 50
        parameters['H0inv']             = 'Rinv'

        parameters.update(parameters_in)

        solverNS = parameters['solverNS']
        isprint = parameters['isprint']
        maxiterNewt = parameters['maxiterNewt']
        reltolgrad = parameters['reltolgrad']
        abstolgrad = parameters['abstolgrad']
        tolcost = parameters['tolcost']
        nbGNsteps = parameters['nbGNsteps']
        checkab = parameters['checkab']
        avgPC = parameters['avgPC']
        if parameters['inexactCG']:
            maxtolcg = parameters['maxtolcg']
        else:
            maxtolcg = 1e-12
        if solverNS == 'BFGS':
            maxtolcg = -1.0
        self.PC = parameters['PC']
        # BFGS (preconditioner or solver):
        if self.PC == 'bfgs' or solverNS == 'BFGS':
            self.bfgsop = BFGS_operator(parameters)
            H0inv = self.bfgsop.parameters['H0inv']
        else:
            self.bfgsop = []
        self.PDEcount = 0   # reset

        if isprint:
            print '\t{:12s} {:10s} {:12s} {:12s} {:12s} {:16s}\t\t\t    {:10s} {:12s} {:10s} {:10s}'.format(\
            'iter', 'cost', 'misfit', 'reg', '|G|', 'medmisf', 'a_ls', 'tol_cg', 'n_cg', 'PDEsolves')

        a0, b0 = initial_medium.split(deepcopy=True)
        self.update_PDE({'a':a0, 'b':b0})
        self._plotab(myplot, 'init')

        Mab = self.Mass*target_medium.vector()
        self.ab.vector().zero()
        self.ab.vector().axpy(1.0, Mab)
        Ma, Mb = self.ab.split(deepcopy=True)
        at, bt = target_medium.split(deepcopy=True)
        atnorm = np.sqrt(at.vector().inner(Ma.vector()))
        btnorm = np.sqrt(bt.vector().inner(Mb.vector()))

        alpha = -1.0    # dummy value for print outputs

        self.solvefwd_cost()
        for it in xrange(maxiterNewt):
            MGv_old = self.MGv.copy()
            self.solveadj_constructgrad()
            gradnorm = np.sqrt(self.MGv.inner(self.Grad.vector()))
            if it == 0:   gradnorm0 = gradnorm

            medmisfita, medmisfitb = self.mediummisfit(target_medium)

            self._plotab(myplot, str(it))
            self._plotgrad(myplot, str(it))

            # Stopping criterion (gradient)
            if gradnorm < gradnorm0*reltolgrad or gradnorm < abstolgrad:
                print '{:12d} {:12.4e} {:12.2e} {:12.2e} {:11.4e} {:10.2e} ({:4.1f}%) {:10.2e} ({:4.1f}%)'.\
                format(it, self.cost, self.cost_misfit, self.cost_reg, gradnorm,\
                medmisfita, 100.0*medmisfita/atnorm, medmisfitb, 100.0*medmisfitb/btnorm),
                print '{:11.3f} {:12.2} {:10} {:10d}'.format(\
                alpha, "", "", self.PDEcount)
                if isprint:
                    print '\nGradient sufficiently reduced'
                    print 'Optimization converged'
                return

            # Assemble Hessian of regularization for nonlinear regularization:
            self.assemble_hessian()

            # Update BFGS approx (s, y, H0)
            if self.PC == 'bfgs' or solverNS == 'BFGS':
                if it > 0:
                    s = self.srchdir.vector() * alpha
                    y = self.MGv - MGv_old
                    theta = self.bfgsop.update(s, y)
                else:
                    theta = 1.0

                if H0inv == 'Rinv':
                    self.bfgsop.set_H0inv(self.regularization.getprecond())
                elif H0inv == 'Minv':
                    print 'H0inv = Minv? That is not a good idea'
                    sys.exit(1)

            # Compute search direction and plot
            tolcg = min(maxtolcg, np.sqrt(gradnorm/gradnorm0))
            self.GN = (it < nbGNsteps)  # use GN or full Hessian?
            # most time spent here:
            if avgPC:
                cgiter, cgres, cgid = compute_searchdirection(self,
                {'method':solverNS, 'tolcg':tolcg,\
                'max_iter':250+1250*(self.GN==False)},\
                comm=self.mpicomm_global, BFGSop=self.bfgsop)
            else:
                cgiter, cgres, cgid = compute_searchdirection(self,
                {'method':solverNS, 'tolcg':tolcg,\
                'max_iter':250+1250*(self.GN==False)}, BFGSop=self.bfgsop)

            # addt'l safety: zero-out entries of 'srchdir' corresponding to
            # param that are not inverted for
            if not self.inverta*self.invertb:
                srcha, srchb = self.srchdir.split(deepcopy=True)
                if not self.inverta:
                    srcha.vector().zero()
                    assign(self.srchdir.sub(0), srcha)
                if not self.invertb:
                    srchb.vector().zero()
                    assign(self.srchdir.sub(1), srchb)
            self._plotsrchdir(myplot, str(it))

            if isprint:
                print '{:12d} {:12.4e} {:12.2e} {:12.2e} {:11.4e} {:10.2e} ({:4.1f}%) {:10.2e} ({:4.1f}%)'.\
                format(it, self.cost, self.cost_misfit, self.cost_reg, gradnorm,\
                medmisfita, 100.0*medmisfita/atnorm, medmisfitb, 100.0*medmisfitb/btnorm),
                print '{:11.3f} {:12.2e} {:10d} {:10d}'.format(\
                alpha, tolcg, cgiter, self.PDEcount)

            # Backtracking line search
            cost_old = self.cost
            statusLS, LScount, alpha = bcktrcklinesearch(self, parameters, boundsLS)
            cost = self.cost
            # Perform line search for dual variable (TV-PD):
            if self.PD: 
                self.regularization.update_w(self.srchdir.vector(), alpha)

            if it%checkab == 0:
                self.compare_ab_global()

            # Stopping criterion (LS)
            if not statusLS:
                if isprint:
                    print '\nLine search failed'
                    print 'Optimization aborted'
                return

            # Stopping criterion (cost)
            if np.abs(cost-cost_old)/np.abs(cost_old) < tolcost:
                if isprint:
                    print '\nCost function stagnates'
                    print 'Optimization aborted'
                return

        if isprint:
            print '\nMaximum number of Newton iterations reached'
            print 'Optimization aborted'




    # PLOTS:
    def _plotab(self, myplot, index):
        """ plot media during inversion """
        if not myplot == None:
            if self.invparam == 'a' or self.invparam == 'ab':
                myplot.set_varname('a'+index)
                myplot.plot_vtk(self.PDE.a)
            if self.invparam == 'b' or self.invparam == 'ab':
                myplot.set_varname('b'+index)
                myplot.plot_vtk(self.PDE.b)

    def _plotgrad(self, myplot, index):
        """ plot grad during inversion """
        if not myplot == None:
            if self.invparam == 'a':
                myplot.set_varname('Grad_a'+index)
                myplot.plot_vtk(self.Grad)
            elif self.invparam == 'b':
                myplot.set_varname('Grad_b'+index)
                myplot.plot_vtk(self.Grad)
            elif self.invparam == 'ab':
                Ga, Gb = self.Grad.split(deepcopy=True)
                myplot.set_varname('Grad_a'+index)
                myplot.plot_vtk(Ga)
                myplot.set_varname('Grad_b'+index)
                myplot.plot_vtk(Gb)

    def _plotsrchdir(self, myplot, index):
        """ plot srchdir during inversion """
        if not myplot == None:
            if self.invparam == 'a':
                myplot.set_varname('srchdir_a'+index)
                myplot.plot_vtk(self.srchdir)
            elif self.invparam == 'b':
                myplot.set_varname('srchdir_b'+index)
                myplot.plot_vtk(self.srchdir)
            elif self.invparam == 'ab':
                Ga, Gb = self.srchdir.split(deepcopy=True)
                myplot.set_varname('srchdir_a'+index)
                myplot.plot_vtk(Ga)
                myplot.set_varname('srchdir_b'+index)
                myplot.plot_vtk(Gb)



    # SHOULD BE REMOVED:
    def set_abc(self, mesh, class_bc_abc, lumpD):  
        self.PDE.set_abc(mesh, class_bc_abc, lumpD)
    def init_vector(self, x, dim):
        self.Mass.init_vector(x, dim)
    def getmcopyarray(self):    return self.getmcopy().array()
    def getMGarray(self):       return self.MGv.array()
    def setsrcterm(self, ftime):    self.PDE.ftime = ftime
test = TestFunction(V)
a_true = inner(mtrue*nabla_grad(trial), nabla_grad(test))*dx
A_true = assemble(a_true)
bc.apply(A_true)
solver = PETScKrylovSolver('cg')    # doesn't work with ilu preconditioner
#solver = LUSolver()    # doesn't work in parallel !?
#solver.parameters['reuse_factorization'] = True
solver.set_operator(A_true)
# Assemble rhs
L = f*test*dx
b = assemble(L)
bc.apply(b)
# Solve:
u_true = Function(V)

"""
solver.solve(u_true.vector(), b)
if myrank == 0: print 'By hand:\n'
print 'P{0}: max(u)={1}\n'.format(myrank, max(u_true.vector().array()))

MPI.barrier(mycomm)

# Same with object
ObsOp = ObsEntireDomain({'V': V})
Goal = ObjFctalElliptic(V, Vme, bc, bc, [f], ObsOp,[],[],[],False)
Goal.update_m(mtrue)
Goal.solvefwd()
if myrank == 0: print 'With ObjFctalElliptic class:\n'
print 'P{0}: max(u)={1}\n'.format(myrank, max(Goal.U[0]))
"""
Beispiel #15
0
def _compute_pressure(p0,
                      alpha,
                      rho,
                      dt,
                      mu,
                      div_ui,
                      p_bcs=None,
                      p_function_space=None,
                      rotational_form=False,
                      tol=1.0e-10,
                      verbose=True):
    '''Solve the pressure Poisson equation

        - \\Delta phi = -div(u),
        boundary conditions,

    for p with

        \\nabla p = u.
    '''
    #
    # The following is based on the update formula
    #
    #     rho/dt (u_{n+1}-u*) + \nabla phi = 0
    #
    # with
    #
    #     phi = (p_{n+1} - p*) + chi*mu*div(u*)
    #
    # and div(u_{n+1})=0. One derives
    #
    #   - \nabla^2 phi = rho/dt div(u_{n+1} - u*),
    #   - n.\nabla phi = rho/dt  n.(u_{n+1} - u*),
    #
    # In its weak form, this is
    #
    #     \int \grad(phi).\grad(q)
    #   = - rho/dt \int div(u*) q - rho/dt \int_Gamma n.(u_{n+1}-u*) q.
    #
    # If Dirichlet boundary conditions are applied to both u* and u_{n+1} (the
    # latter in the final step), the boundary integral vanishes.
    #
    # Assume that on the boundary
    #   L2 -= inner(n, rho/k (u_bcs - ui)) * q * ds
    # is zero. This requires the boundary conditions to be set for ui as well
    # as u_final.
    # This creates some problems if the boundary conditions are supposed to
    # remain 'free' for the velocity, i.e., no Dirichlet conditions in normal
    # direction. In that case, one needs to specify Dirichlet pressure
    # conditions.
    #
    if p0:
        P = p0.function_space()
    else:
        P = p_function_space

    p1 = Function(P)
    p = TrialFunction(P)
    q = TestFunction(P)

    a2 = dot(grad(p), grad(q)) * dx
    L2 = -alpha * rho / dt * div_ui * q * dx

    L2 += dot(grad(p0), grad(q)) * dx

    if rotational_form:
        L2 -= mu * dot(grad(div_ui), grad(q)) * dx

    if p_bcs:
        solve(a2 == L2,
              p1,
              bcs=p_bcs,
              solver_parameters={
                  'linear_solver': 'iterative',
                  'symmetric': True,
                  'preconditioner': 'hypre_amg',
                  'krylov_solver': {
                      'relative_tolerance': tol,
                      'absolute_tolerance': 0.0,
                      'maximum_iterations': 100,
                      'monitor_convergence': verbose,
                      'error_on_nonconvergence': True
                  }
              })
    else:
        # If we're dealing with a pure Neumann problem here (which is the
        # default case), this doesn't hurt CG if the system is consistent, cf.
        #
        #    Iterative Krylov methods for large linear systems,
        #    Henk A. van der Vorst.
        #
        # And indeed, it is consistent: Note that
        #
        #    <1, rhs> = \sum_i 1 * \int div(u) v_i
        #             = 1 * \int div(u) \sum_i v_i
        #             = \int div(u).
        #
        # With the divergence theorem, we have
        #
        #    \int div(u) = \int_\Gamma n.u.
        #
        # The latter term is 0 if and only if inflow and outflow are exactly
        # the same at any given point in time. This corresponds with the
        # incompressibility of the liquid.
        #
        # Another lesson from this:
        # If the mesh has penetration boundaries, you either have to specify
        # the normal component of the velocity such that \int(n.u) = 0, or
        # specify Dirichlet conditions for the pressure somewhere.
        #
        A = assemble(a2)
        b = assemble(L2)

        # If the right hand side is flawed (e.g., by round-off errors), then it
        # may have a component b1 in the direction of the null space,
        # orthogonal to the image of the operator:
        #
        #     b = b0 + b1.
        #
        # When starting with initial guess x0=0, the minimal achievable
        # relative tolerance is then
        #
        #    min_rel_tol = ||b1|| / ||b||.
        #
        # If ||b|| is very small, which is the case when ui is almost
        # divergence-free, then min_rel_to may be larger than the prescribed
        # relative tolerance tol. This happens, for example, when the time
        # steps is very small.
        # Sanitation of right-hand side is easy with
        #
        #     e = Function(P)
        #     e.interpolate(Constant(1.0))
        #     evec = e.vector()
        #     evec /= norm(evec)
        #     print(b.inner(evec))
        #     b -= b.inner(evec) * evec
        #
        # However it's hard to decide when the right-hand side is inconsistent
        # because of round-off errors in previous steps, or because the system
        # is actually inconsistent (insufficient boundary conditions or
        # something like that). Hence, don't do anything and rather try to
        # fight the cause for round-off.

        # In principle, the ILU preconditioner isn't advised here since it
        # might destroy the semidefiniteness needed for CG.
        #
        # The system is consistent, but the matrix has an eigenvalue 0. This
        # does not harm the convergence of CG, but with preconditioning one has
        # to make sure that the preconditioner preserves the kernel. ILU might
        # destroy this (and the semidefiniteness). With AMG, the coarse grid
        # solves cannot be LU then, so try Jacobi here.
        # <http://lists.mcs.anl.gov/pipermail/petsc-users/2012-February/012139.html>
        #

        # TODO clear everything; possible in FEniCS 2017.1
        # <https://fenicsproject.org/qa/12916/clear-petscoptions>
        # PETScOptions.clear()

        prec = PETScPreconditioner('hypre_amg')
        PETScOptions.set('pc_hypre_boomeramg_relax_type_coarse', 'jacobi')
        solver = PETScKrylovSolver('cg', prec)
        solver.parameters['absolute_tolerance'] = 0.0
        solver.parameters['relative_tolerance'] = tol
        solver.parameters['maximum_iterations'] = 1000
        solver.parameters['monitor_convergence'] = verbose
        solver.parameters['error_on_nonconvergence'] = True

        # Create solver and solve system
        A_petsc = as_backend_type(A)
        b_petsc = as_backend_type(b)
        p1_petsc = as_backend_type(p1.vector())
        solver.set_operator(A_petsc)

        solver.solve(p1_petsc, b_petsc)
    return p1