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 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
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 getprecond(self): Prec = PETScKrylovSolver("richardson", "amg") Prec.parameters["maximum_iterations"] = 1 Prec.parameters["error_on_nonconvergence"] = False Prec.parameters["nonzero_initial_guess"] = False Prec.set_operator(self.Regul.get_precond()) return Prec
def getprecond(self): """ precondition by TV + small fraction of mass matrix """ solver = PETScKrylovSolver('cg', self.amgprecond) solver.parameters["maximum_iterations"] = 2000 solver.parameters["relative_tolerance"] = 1e-24 solver.parameters["absolute_tolerance"] = 1e-24 solver.parameters["error_on_nonconvergence"] = True solver.parameters["nonzero_initial_guess"] = False solver.set_operator(self.H + self.sMass) return solver
def getprecond(self): """ precondition by TV + small fraction of mass matrix """ #TODO: does not appear to be a great way to apply preconditioner (DIVERGED_ITS) solver = PETScKrylovSolver('cg', self.amgprecond) solver.parameters["maximum_iterations"] = 3000 solver.parameters["relative_tolerance"] = 1e-24 solver.parameters["absolute_tolerance"] = 1e-24 solver.parameters["error_on_nonconvergence"] = True solver.parameters["nonzero_initial_guess"] = False self.precond = self.H + self.sMass solver.set_operator(self.precond) return solver
def getprecond(self): # if self.coeff_cg + self.coeff_ncg > 0.0: # solver = PETScKrylovSolver('gmres', self.amgprecond) # solver.parameters["maximum_iterations"] = 10000 # else: solver = PETScKrylovSolver('cg', self.amgprecond) solver.parameters["maximum_iterations"] = 2000 solver.parameters["absolute_tolerance"] = 1e-24 solver.parameters["relative_tolerance"] = 1e-24 solver.parameters["error_on_nonconvergence"] = True solver.parameters["nonzero_initial_guess"] = False solver.set_operator(self.precond) return solver
def getprecond(self): """ solver = PETScKrylovSolver("richardson", "amg") solver.parameters["maximum_iterations"] = 1 solver.parameters["error_on_nonconvergence"] = False solver.parameters["nonzero_initial_guess"] = False """ solver = PETScKrylovSolver('cg', self.amgsolver) solver.parameters["maximum_iterations"] = 1000 solver.parameters["relative_tolerance"] = 1e-24 solver.parameters["absolute_tolerance"] = 1e-24 solver.parameters["error_on_nonconvergence"] = True solver.parameters["nonzero_initial_guess"] = False solver.set_operator(self.precond) return solver
class LaplacianPrior(GaussianPrior): """Gaussian prior Parameters must be a dictionary containing: gamma = multiplicative factor applied to <Grad u, Grad v> term beta = multiplicative factor applied to <u,v> term (default=0.0) m0 = mean (or reference parameter when used as regularization) Vm = function space for parameter cost = 1/2 * (m-m0)^T.R.(m-m0)""" def _assemble(self): # Get input: self.gamma = self.Parameters['gamma'] if self.Parameters.has_key('beta'): self.beta = self.Parameters['beta'] else: self.beta = 0.0 self.Vm = self.Parameters['Vm'] self.m0 = Function(self.Vm) if self.Parameters.has_key('m0'): setfct(self.m0, self.Parameters['m0']) self.mtrial = TrialFunction(self.Vm) self.mtest = TestFunction(self.Vm) self.mysample = Function(self.Vm) self.draw = Function(self.Vm) # Assemble: self.R = assemble(inner(nabla_grad(self.mtrial), \ nabla_grad(self.mtest))*dx) self.M = assemble(inner(self.mtrial, self.mtest) * dx) self.Msolver = PETScKrylovSolver('cg', 'jacobi') self.Msolver.parameters["maximum_iterations"] = 2000 self.Msolver.parameters["relative_tolerance"] = 1e-24 self.Msolver.parameters["absolute_tolerance"] = 1e-24 self.Msolver.parameters["error_on_nonconvergence"] = True self.Msolver.parameters["nonzero_initial_guess"] = False self.Msolver.set_operator(self.M) # preconditioner is Gamma^{-1}: if self.beta > 1e-10: self.precond = self.gamma * self.R + self.beta * self.M else: self.precond = self.gamma * self.R + (1e-10) * self.M # Minvprior is M.A^2 (if you use M inner-product): self.Minvprior = self.gamma * self.R + self.beta * self.M # L is used to sample def Minvpriordot(self, vect): return self.Minvprior * vect def init_vector(self, u, dim): self.R.init_vector(u, dim)
def getprecond(self): """ Precondition by inverting the TV Hessian """ solver = PETScKrylovSolver('cg', self.amgprecond) solver.parameters["maximum_iterations"] = 2000 solver.parameters["relative_tolerance"] = 1e-24 solver.parameters["absolute_tolerance"] = 1e-24 solver.parameters["error_on_nonconvergence"] = True solver.parameters["nonzero_initial_guess"] = False # used to compare iterative application of preconditioner # with exact application of preconditioner: #solver = PETScLUSolver("petsc") #solver.parameters['symmetric'] = True #solver.parameters['reuse_factorization'] = True solver.set_operator(self.precond) return solver
class TV(): """ Define Total Variation regularization """ def __init__(self, parameters=[]): """ TV regularization in primal form: |f|_TV = int k(x) sqrt{|grad f|^2 + eps} dx Input parameters: * k = regularization parameter * eps = regularization constant (see above) * GNhessian = use GN format (aka, lagged diffusivity) (bool) * PCGN = use GN Hessian to precondition (bool); only used if 'GNhessian = False' * print (bool) """ self.parameters = {} self.parameters['k'] = 1.0 self.parameters['eps'] = 1e-2 self.parameters['GNhessian'] = False self.parameters['PCGN'] = False self.parameters['print'] = False self.parameters['correctcost'] = True self.parameters['amg'] = 'default' assert parameters.has_key('Vm') self.parameters.update(parameters) GN = self.parameters['GNhessian'] self.Vm = self.parameters['Vm'] eps = self.parameters['eps'] k = self.parameters['k'] isprint = self.parameters['print'] amg = self.parameters['amg'] self.m = Function(self.Vm) test, trial = TestFunction(self.Vm), TrialFunction(self.Vm) factM = 1e-2 * k M = assemble(inner(test, trial) * dx) self.sMass = M * factM self.Msolver = PETScKrylovSolver('cg', 'jacobi') self.Msolver.parameters["maximum_iterations"] = 2000 self.Msolver.parameters["relative_tolerance"] = 1e-24 self.Msolver.parameters["absolute_tolerance"] = 1e-24 self.Msolver.parameters["error_on_nonconvergence"] = True self.Msolver.parameters["nonzero_initial_guess"] = False self.Msolver.set_operator(M) self.fTV = inner(nabla_grad(self.m), nabla_grad( self.m)) + Constant(eps) self.kovsq = Constant(k) / sqrt(self.fTV) if self.parameters['correctcost']: meshtmp = UnitSquareMesh(self.Vm.mesh().mpi_comm(), 10, 10) Vtmp = FunctionSpace(meshtmp, 'CG', 1) x = SpatialCoordinate(meshtmp) correctioncost = 1. / assemble(sqrt(4.0 * x[0] * x[0]) * dx) else: correctioncost = 1.0 self.wkformcost = Constant(k * correctioncost) * sqrt(self.fTV) * dx self.wkformgrad = self.kovsq * inner(nabla_grad(self.m), nabla_grad(test)) * dx self.wkformGNhess = self.kovsq * inner(nabla_grad(trial), nabla_grad(test)) * dx self.wkformFhess = self.kovsq*( \ inner(nabla_grad(trial), nabla_grad(test)) - \ inner(nabla_grad(self.m), nabla_grad(test))*\ inner(nabla_grad(trial), nabla_grad(self.m))/self.fTV )*dx if GN: self.wkformhess = self.wkformGNhess else: self.wkformhess = self.wkformFhess if amg == 'default': self.amgprecond = amg_solver() else: self.amgprecond = amg if isprint: print '[TV] TV regularization', if GN: print ' -- GN Hessian', else: print ' -- full Hessian', if self.parameters['PCGN']: print ' -- PCGN', print ' -- k={}, eps={}'.format(k, eps) print '[TV] preconditioner = {}'.format(self.amgprecond) print '[TV] correction cost with factor={}'.format(correctioncost) def isTV(self): return True def isPD(self): return False def cost(self, m_in): """ returns the cost functional for self.m=m_in """ setfct(self.m, m_in) return assemble(self.wkformcost) def costvect(self, m_in): return self.cost(m_in) def grad(self, m_in): """ returns the gradient (in vector format) evaluated at self.m = m_in """ setfct(self.m, m_in) return assemble(self.wkformgrad) def gradvect(self, m_in): return self.grad(m_in) def assemble_hessian(self, m_in): """ Assemble the Hessian of TV at m_in """ setfct(self.m, m_in) self.H = assemble(self.wkformhess) PCGN = self.parameters['PCGN'] if PCGN: HGN = assemble(self.wkformGNhess) self.precond = HGN + self.sMass else: self.precond = self.H + self.sMass def assemble_GNhessian(self, m_in): """ Assemble the Gauss-Newton Hessian at m_in Not used anymore (wkformhess selects GN Hessian if needed) Left here for back-compatibility """ setfct(self.m, m_in) self.H = assemble(self.wkformGNhess) self.precond = self.H + self.sMass def hessian(self, mhat): """ returns the Hessian applied along a direction mhat """ isVector(mhat) return self.H * mhat def getprecond(self): """ Precondition by inverting the TV Hessian """ solver = PETScKrylovSolver('cg', self.amgprecond) solver.parameters["maximum_iterations"] = 2000 solver.parameters["relative_tolerance"] = 1e-24 solver.parameters["absolute_tolerance"] = 1e-24 solver.parameters["error_on_nonconvergence"] = True solver.parameters["nonzero_initial_guess"] = False # used to compare iterative application of preconditioner # with exact application of preconditioner: #solver = PETScLUSolver("petsc") #solver.parameters['symmetric'] = True #solver.parameters['reuse_factorization'] = True solver.set_operator(self.precond) return solver def init_vector(self, u, dim): self.sMass.init_vector(u, dim)
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
mtrue_exp = Expression('1 + 7*(pow(pow(x[0] - 0.5,2) +' + \ ' pow(x[1] - 0.5,2),0.5) > 0.2)') #mtrue = interpolate(mtrue_exp, Vme) mtrue = interpolate(mtrue_exp, Vm) f = Expression("1.0") # Assemble weak form trial = TrialFunction(V) 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
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
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)
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 _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 TVPD(): """ Total variation using primal-dual Newton """ def __init__(self, parameters): """ TV regularization in primal-dual format Input parameters: * k = regularization parameter * eps = regularization constant (see above) * rescaledradiusdual = radius of dual set * exact = use full TV (bool) * PCGN = use GN Hessian to precondition (bool); not recommended for performance but can help avoid num.instability * print (bool) """ self.parameters = {} self.parameters['k'] = 1.0 self.parameters['eps'] = 1e-2 self.parameters['rescaledradiusdual'] = 1.0 self.parameters['exact'] = False self.parameters['PCGN'] = False self.parameters['print'] = False self.parameters['correctcost'] = True self.parameters['amg'] = 'default' assert parameters.has_key('Vm') self.parameters.update(parameters) self.Vm = self.parameters['Vm'] k = self.parameters['k'] eps = self.parameters['eps'] exact = self.parameters['exact'] amg = self.parameters['amg'] self.m = Function(self.Vm) testm = TestFunction(self.Vm) trialm = TrialFunction(self.Vm) # WARNING: should not be changed. # As it is, code only works with DG0 if self.parameters.has_key('Vw'): Vw = self.parameters['Vw'] else: Vw = FunctionSpace(self.Vm.mesh(), 'DG', 0) self.wx = Function(Vw) self.wxrs = Function(Vw) # re-scaled dual variable self.wxhat = Function(Vw) self.gwx = Function(Vw) self.wy = Function(Vw) self.wyrs = Function(Vw) # re-scaled dual variable self.wyhat = Function(Vw) self.gwy = Function(Vw) self.wxsq = Vector() self.wysq = Vector() self.normw = Vector() self.factorw = Vector() testw = TestFunction(Vw) trialw = TrialFunction(Vw) normm = inner(nabla_grad(self.m), nabla_grad(self.m)) TVnormsq = normm + Constant(eps) TVnorm = sqrt(TVnormsq) if self.parameters['correctcost']: meshtmp = UnitSquareMesh(self.Vm.mesh().mpi_comm(), 10, 10) Vtmp = FunctionSpace(meshtmp, 'CG', 1) x = SpatialCoordinate(meshtmp) correctioncost = 1. / assemble(sqrt(4.0 * x[0] * x[0]) * dx) else: correctioncost = 1.0 self.wkformcost = Constant(k * correctioncost) * TVnorm * dx if exact: sys.exit(1) # self.w = nabla_grad(self.m)/TVnorm # full Hessian # self.Htvw = inner(Constant(k) * nabla_grad(testm), self.w) * dx self.misfitwx = inner(testw, self.wx * TVnorm - self.m.dx(0)) * dx self.misfitwy = inner(testw, self.wy * TVnorm - self.m.dx(1)) * dx self.Htvx = assemble(inner(Constant(k) * testm.dx(0), trialw) * dx) self.Htvy = assemble(inner(Constant(k) * testm.dx(1), trialw) * dx) self.massw = inner(TVnorm * testw, trialw) * dx mpicomm = self.Vm.mesh().mpi_comm() invMwMat, VDM, VDM = setupPETScmatrix(Vw, Vw, 'aij', mpicomm) for ii in VDM.dofs(): invMwMat[ii, ii] = 1.0 invMwMat.assemblyBegin() invMwMat.assemblyEnd() self.invMwMat = PETScMatrix(invMwMat) self.invMwd = Vector() self.invMwMat.init_vector(self.invMwd, 0) self.invMwMat.init_vector(self.wxsq, 0) self.invMwMat.init_vector(self.wysq, 0) self.invMwMat.init_vector(self.normw, 0) self.invMwMat.init_vector(self.factorw, 0) u = Function(Vw) uflrank = len(u.ufl_shape) if uflrank == 0: ones = ("1.0") elif uflrank == 1: ones = (("1.0", "1.0")) else: sys.exit(1) u = interpolate(Constant(ones), Vw) self.one = u.vector() self.wkformAx = inner(testw, trialm.dx(0) - \ self.wx * inner(nabla_grad(self.m), nabla_grad(trialm)) / TVnorm) * dx self.wkformAxrs = inner(testw, trialm.dx(0) - \ self.wxrs * inner(nabla_grad(self.m), nabla_grad(trialm)) / TVnorm) * dx self.wkformAy = inner(testw, trialm.dx(1) - \ self.wy * inner(nabla_grad(self.m), nabla_grad(trialm)) / TVnorm) * dx self.wkformAyrs = inner(testw, trialm.dx(1) - \ self.wyrs * inner(nabla_grad(self.m), nabla_grad(trialm)) / TVnorm) * dx kovsq = Constant(k) / TVnorm self.wkformGNhess = kovsq * inner(nabla_grad(trialm), nabla_grad(testm)) * dx factM = 1e-2 * k M = assemble(inner(testm, trialm) * dx) self.sMass = M * factM self.Msolver = PETScKrylovSolver('cg', 'jacobi') self.Msolver.parameters["maximum_iterations"] = 2000 self.Msolver.parameters["relative_tolerance"] = 1e-24 self.Msolver.parameters["absolute_tolerance"] = 1e-24 self.Msolver.parameters["error_on_nonconvergence"] = True self.Msolver.parameters["nonzero_initial_guess"] = False self.Msolver.set_operator(M) if amg == 'default': self.amgprecond = amg_solver() else: self.amgprecond = amg if self.parameters['print']: print '[TVPD] TV regularization -- primal-dual method', if self.parameters['PCGN']: print ' -- PCGN', print ' -- k={}, eps={}'.format(k, eps) print '[TVPD] preconditioner = {}'.format(self.amgprecond) print '[TVPD] correction cost with factor={}'.format( correctioncost) def isTV(self): return True def isPD(self): return True def cost(self, m): """ evaluate the cost functional at m """ setfct(self.m, m) return assemble(self.wkformcost) def costvect(self, m_in): return self.cost(m_in) def _assemble_invMw(self): """ Assemble inverse of matrix Mw, weighted mass matrix in dual space """ # WARNING: only works if Mw is diagonal (e.g, DG0) Mw = assemble(self.massw) Mwd = get_diagonal(Mw) as_backend_type(self.invMwd).vec().pointwiseDivide(\ as_backend_type(self.one).vec(),\ as_backend_type(Mwd).vec()) self.invMwMat.set_diagonal(self.invMwd) def grad(self, m): """ compute the gradient at m """ setfct(self.m, m) self._assemble_invMw() self.gwx.vector().zero() self.gwx.vector().axpy(1.0, assemble(self.misfitwx)) normgwx = norm(self.gwx.vector()) self.gwy.vector().zero() self.gwy.vector().axpy(1.0, assemble(self.misfitwy)) normgwy = norm(self.gwy.vector()) if self.parameters['print']: print '[TVPD] |gw|={}'.format(np.sqrt(normgwx**2 + normgwy**2)) return self.Htvx*(self.wx.vector() - self.invMwd*self.gwx.vector()) \ + self.Htvy*(self.wy.vector() - self.invMwd*self.gwy.vector()) #return assemble(self.Htvw) - self.Htv*(self.invMwd*self.gw.vector()) def gradvect(self, m_in): return self.grad(m_in) def assemble_hessian(self, m): """ build Hessian matrix at given point m """ setfct(self.m, m) self._assemble_invMw() self.Ax = assemble(self.wkformAx) Hxasym = MatMatMult(self.Htvx, MatMatMult(self.invMwMat, self.Ax)) Hx = (Hxasym + Transpose(Hxasym)) * 0.5 Axrs = assemble(self.wkformAxrs) Hxrsasym = MatMatMult(self.Htvx, MatMatMult(self.invMwMat, Axrs)) Hxrs = (Hxrsasym + Transpose(Hxrsasym)) * 0.5 self.Ay = assemble(self.wkformAy) Hyasym = MatMatMult(self.Htvy, MatMatMult(self.invMwMat, self.Ay)) Hy = (Hyasym + Transpose(Hyasym)) * 0.5 Ayrs = assemble(self.wkformAyrs) Hyrsasym = MatMatMult(self.Htvy, MatMatMult(self.invMwMat, Ayrs)) Hyrs = (Hyrsasym + Transpose(Hyrsasym)) * 0.5 self.H = Hx + Hy self.Hrs = Hxrs + Hyrs PCGN = self.parameters['PCGN'] if PCGN: HGN = assemble(self.wkformGNhess) self.precond = HGN + self.sMass else: self.precond = self.Hrs + self.sMass def hessian(self, mhat): return self.Hrs * mhat def compute_what(self, mhat): """ Compute update direction for what, given mhat """ self.wxhat.vector().zero() self.wxhat.vector().axpy( 1.0, self.invMwd * (self.Ax * mhat - self.gwx.vector())) normwxhat = norm(self.wxhat.vector()) self.wyhat.vector().zero() self.wyhat.vector().axpy( 1.0, self.invMwd * (self.Ay * mhat - self.gwy.vector())) normwyhat = norm(self.wyhat.vector()) if self.parameters['print']: print '[TVPD] |what|={}'.format( np.sqrt(normwxhat**2 + normwyhat**2)) def update_w(self, mhat, alphaLS, compute_what=True): """ update dual variable in direction what and update re-scaled version """ if compute_what: self.compute_what(mhat) self.wx.vector().axpy(alphaLS, self.wxhat.vector()) self.wy.vector().axpy(alphaLS, self.wyhat.vector()) # rescaledradiusdual=1.0: checked empirically to be max radius acceptable rescaledradiusdual = self.parameters['rescaledradiusdual'] # wx**2 as_backend_type(self.wxsq).vec().pointwiseMult(\ as_backend_type(self.wx.vector()).vec(),\ as_backend_type(self.wx.vector()).vec()) # wy**2 as_backend_type(self.wysq).vec().pointwiseMult(\ as_backend_type(self.wy.vector()).vec(),\ as_backend_type(self.wy.vector()).vec()) # |w| self.normw = self.wxsq + self.wysq as_backend_type(self.normw).vec().sqrtabs() # |w|/r as_backend_type(self.normw).vec().pointwiseDivide(\ as_backend_type(self.normw).vec(),\ as_backend_type(self.one*rescaledradiusdual).vec()) # max(1.0, |w|/r) # as_backend_type(self.factorw).vec().pointwiseMax(\ # as_backend_type(self.one).vec(),\ # as_backend_type(self.normw).vec()) count = pointwiseMaxCount(self.factorw, self.normw, 1.0) # rescale wx and wy as_backend_type(self.wxrs.vector()).vec().pointwiseDivide(\ as_backend_type(self.wx.vector()).vec(),\ as_backend_type(self.factorw).vec()) as_backend_type(self.wyrs.vector()).vec().pointwiseDivide(\ as_backend_type(self.wy.vector()).vec(),\ as_backend_type(self.factorw).vec()) minf = self.factorw.min() maxf = self.factorw.max() if self.parameters['print']: # print 'min(factorw)={}, max(factorw)={}'.format(minf, maxf) print '[TVPD] perc. dual entries rescaled={:.2f} %, min(factorw)={}, max(factorw)={}'.format(\ 100.*float(count)/self.factorw.size(), minf, maxf) def getprecond(self): """ Precondition by inverting the TV Hessian """ solver = PETScKrylovSolver('cg', self.amgprecond) solver.parameters["maximum_iterations"] = 2000 solver.parameters["relative_tolerance"] = 1e-24 solver.parameters["absolute_tolerance"] = 1e-24 solver.parameters["error_on_nonconvergence"] = True solver.parameters["nonzero_initial_guess"] = False # used to compare iterative application of preconditioner # with exact application of preconditioner: #solver = PETScLUSolver("petsc") #solver.parameters['symmetric'] = True #solver.parameters['reuse_factorization'] = True solver.set_operator(self.precond) return solver def init_vector(self, u, dim): self.sMass.init_vector(u, dim)
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
class ObjectiveFunctional(LinearOperator): """ Provides data misfit, gradient and Hessian information for the data misfit part of a time-independent symmetric inverse problem. """ __metaclass__ = abc.ABCMeta # Instantiation def __init__(self, V, Vm, bc, bcadj, \ RHSinput=[], ObsOp=[], UD=[], Regul=[], Data=[], plot=False, \ mycomm=None): # Define test, trial and all other functions self.trial = TrialFunction(V) self.test = TestFunction(V) self.mtrial = TrialFunction(Vm) self.mtest = TestFunction(Vm) self.rhs = Function(V) self.m = Function(Vm) self.mcopy = Function(Vm) self.srchdir = Function(Vm) self.delta_m = Function(Vm) self.MG = Function(Vm) self.MGv = self.MG.vector() self.Grad = Function(Vm) self.Gradnorm = 0.0 self.lenm = len(self.m.vector().array()) self.u = Function(V) self.ud = Function(V) self.diff = Function(V) self.p = Function(V) # Store other info: self.ObsOp = ObsOp self.UD = UD self.reset() # Initialize U, C and E to [] self.Data = Data self.GN = 1.0 # GN = 0.0 => GN Hessian; = 1.0 => full Hessian # Define weak forms to assemble A, C and E self._wkforma() self._wkformc() self._wkforme() # Operators and bc LinearOperator.__init__(self, self.delta_m.vector(), \ self.delta_m.vector()) self.bc = bc self.bcadj = bcadj self._assemble_solverM(Vm) self.assemble_A() self.assemble_RHS(RHSinput) self.Regul = Regul self.regparam = 1.0 if Regul != []: self.PD = self.Regul.isPD() # Counters, tolerances and others self.nbPDEsolves = 0 # Updated when solve_A called self.nbfwdsolves = 0 # Counter for plots self.nbadjsolves = 0 # Counter for plots # MPI: self.mycomm = mycomm def copy(self): """Define a copy method""" V = self.trial.function_space() Vm = self.mtrial.function_space() newobj = self.__class__(V, Vm, self.bc, self.bcadj, [], self.ObsOp, \ self.UD, self.Regul, self.Data, False) newobj.RHS = self.RHS newobj.update_m(self.m) return newobj def mult(self, mhat, y): """mult(self, mhat, y): do y = Hessian * mhat member self.GN sets full Hessian (=1.0) or GN Hessian (=0.0)""" N = self.Nbsrc # Number of sources y[:] = np.zeros(self.lenm) for C, E in zip(self.C, self.E): C.transpmult(mhat, self.rhs.vector()) if self.bcadj is not None: self.bcadj.apply(self.rhs.vector()) self.solve_A(self.u.vector(), -self.rhs.vector()) E.transpmult(mhat, self.rhs.vector()) Etmhat = self.rhs.vector().array() self.rhs.vector().axpy(1.0, self.ObsOp.incradj(self.u)) if self.bcadj is not None: self.bcadj.apply(self.rhs.vector()) self.solve_A(self.p.vector(), -self.rhs.vector()) y.axpy(1.0 / N, C * self.p.vector()) y.axpy(self.GN / N, E * self.u.vector()) y.axpy(self.regparam, self.Regul.hessian(mhat)) # Getters def getm(self): return self.m def getmarray(self): return self.m.vector().array() def getmcopyarray(self): return self.mcopy.vector().array() def getVm(self): return self.mtrial.function_space() def getMGarray(self): return self.MG.vector().array() def getMGvec(self): return self.MGv def getGradarray(self): return self.Grad.vector().array() def getGradnorm(self): return self.Gradnorm def getsrchdirarray(self): return self.srchdir.vector().array() def getsrchdirvec(self): return self.srchdir.vector() def getsrchdirnorm(self): return np.sqrt( (self.MM * self.getsrchdirvec()).inner(self.getsrchdirvec())) def getgradxdir(self): return self.gradxdir def getcost(self): return self.cost, self.misfit, self.regul def getprecond(self): return self.Regul.getprecond() # Prec = PETScKrylovSolver("richardson", "amg") # Prec.parameters["maximum_iterations"] = 1 # Prec.parameters["error_on_nonconvergence"] = False # Prec.parameters["nonzero_initial_guess"] = False # Prec.set_operator(self.Regul.get_precond()) # return Prec def getMass(self): return self.MM # Setters def setsrchdir(self, arr): self.srchdir.vector()[:] = arr def setgradxdir(self, valueloc): """Sum all local results for Grad . Srch_dir""" try: valueglob = MPI.sum(self.mycomm, valueloc) except: valueglob = valueloc self.gradxdir = valueglob # Solve def solvefwd(self, cost=False): """Solve fwd operators for given RHS""" self.nbfwdsolves += 1 if cost: self.misfit = 0.0 self.U = [] self.C = [] for ii, rhs in enumerate(self.RHS): self.solve_A(self.u.vector(), rhs) u_obs, noiselevel = self.ObsOp.obs(self.u) self.U.append(u_obs) if cost: self.misfit += self.ObsOp.costfct(u_obs, self.UD[ii]) self.C.append(assemble(self.c)) if cost: self.misfit /= len(self.U) self.regul = self.Regul.cost(self.m) self.cost = self.misfit + self.regparam * self.regul def solvefwd_cost(self): """Solve fwd operators for given RHS and compute cost fct""" self.solvefwd(True) def solveadj(self, grad=False): """Solve adj operators""" self.nbadjsolves += 1 self.Nbsrc = len(self.UD) if grad: self.MG.vector().zero() self.E = [] for ii, C in enumerate(self.C): self.ObsOp.assemble_rhsadj(self.U[ii], self.UD[ii], \ self.rhs, self.bcadj) self.solve_A(self.p.vector(), self.rhs.vector()) self.E.append(assemble(self.e)) if grad: self.MG.vector().axpy(1.0 / self.Nbsrc, C * self.p.vector()) if grad: self.MG.vector().axpy(self.regparam, self.Regul.grad(self.m)) self.solverM.solve(self.Grad.vector(), self.MG.vector()) self.Gradnorm = np.sqrt(self.Grad.vector().inner(self.MG.vector())) def solveadj_constructgrad(self): """Solve adj operators and assemble gradient""" self.solveadj(True) # Assembler def assemble_A(self): """Assemble operator A(m)""" self.A = assemble(self.a) if self.bc is not None: self.bc.apply(self.A) compute_eigfenics(self.A, 'eigA.txt') self.set_solver() def solve_A(self, b, f): """Solve system of the form A.b = f, with b and f in form to be used in solver.""" self.solver.solve(b, f) self.nbPDEsolves += 1 def assemble_RHS(self, RHSin): """Assemble RHS for fwd solve""" if RHSin == []: self.RHS = None else: self.RHS = [] for rhs in RHSin: if isinstance(rhs, Expression): L = rhs * self.test * dx b = assemble(L) if self.bc is not None: self.bc.apply(b) self.RHS.append(b) elif isinstance(rhs, GenericVector): self.RHS.append(rhs) else: raise WrongInstanceError( "rhs should be an Expression or a GenericVector") def _assemble_solverM(self, Vm): self.MM = assemble(inner(self.mtrial, self.mtest) * dx) self.solverM = PETScKrylovSolver('cg', 'jacobi') self.solverM.parameters["maximum_iterations"] = 1000 self.solverM.parameters["relative_tolerance"] = 1e-12 self.solverM.parameters["error_on_nonconvergence"] = True self.solverM.parameters["nonzero_initial_guess"] = False # self.solverM = LUSolver() # self.solverM.parameters['reuse_factorization'] = True # self.solverM.parameters['symmetric'] = True self.solverM.set_operator(self.MM) # Update param def update_Data(self, Data): """Update Data member""" self.Data = Data self.assemble_A() self.reset() def update_m(self, m): """Update values of parameter m""" if isinstance(m, np.ndarray): self.m.vector()[:] = m elif isinstance(m, Function): self.m.assign(m) elif isinstance(m, float): self.m.vector()[:] = m elif isinstance(m, int): self.m.vector()[:] = float(m) else: raise WrongInstanceError('Format for m not accepted') self.assemble_A() self.reset() def backup_m(self): self.mcopy.assign(self.m) def restore_m(self): self.update_m(self.mcopy) def reset(self): """Reset U, C and E""" self.U = [] self.C = [] self.E = [] def set_solver(self): """Reset solver for fwd operator""" #self.solver = LUSolver() #self.solver.parameters['reuse_factorization'] = True self.solver = PETScKrylovSolver("cg", "amg") self.solver.parameters["maximum_iterations"] = 1000 self.solver.parameters["relative_tolerance"] = 1e-12 self.solver.parameters["error_on_nonconvergence"] = True self.solver.parameters["nonzero_initial_guess"] = False self.solver.set_operator(self.A) def addPDEcount(self, increment=1): """Increase 'nbPDEsolves' by 'increment'""" self.nbPDEsolves += increment def resetPDEsolves(self): self.nbPDEsolves = 0 # Additional methods for compatibility with CG solver: def init_vector(self, x, dim): """Initialize vector x to be compatible with parameter Does not work in dolfin 1.3.0""" self.MM.init_vector(x, 0) def init_vector130(self): """Initialize vector x to be compatible with parameter""" return Vector(Function(self.mcopy.function_space()).vector()) # Abstract methods @abc.abstractmethod def _wkforma(self): self.a = [] @abc.abstractmethod def _wkformc(self): self.c = [] @abc.abstractmethod def _wkforme(self): self.e = [] def inversion(self, initial_medium, target_medium, mpicomm, \ parameters_in=[], myplot=None): """ solve inverse problem with that objective function """ parameters = {'tolgrad':1e-10, 'tolcost':1e-14, 'maxnbNewtiter':50, \ 'maxtolcg':0.5} parameters.update(parameters_in) maxnbNewtiter = parameters['maxnbNewtiter'] tolgrad = parameters['tolgrad'] tolcost = parameters['tolcost'] tolcg = parameters['maxtolcg'] mpirank = MPI.rank(mpicomm) self.update_m(initial_medium) self._plotm(myplot, 'init') if mpirank == 0: print '\t{:12s} {:10s} {:12s} {:12s} {:12s} {:10s} \t{:10s} {:12s} {:12s}'.format(\ 'iter', 'cost', 'misfit', 'reg', '|G|', 'medmisf', 'a_ls', 'tol_cg', 'n_cg') dtruenorm = np.sqrt(target_medium.vector().\ inner(self.MM*target_medium.vector())) self.solvefwd_cost() for it in xrange(maxnbNewtiter): self.solveadj_constructgrad() # compute gradient if it == 0: gradnorm0 = self.Gradnorm diff = self.m.vector() - target_medium.vector() medmisfit = np.sqrt(diff.inner(self.MM * diff)) if mpirank == 0: print '{:12d} {:12.4e} {:12.2e} {:12.2e} {:11.4e} {:10.2e} ({:4.2f})'.\ format(it, self.cost, self.misfit, self.regul, \ self.Gradnorm, medmisfit, medmisfit/dtruenorm), self._plotm(myplot, str(it)) self._plotgrad(myplot, str(it)) if self.Gradnorm < gradnorm0 * tolgrad or self.Gradnorm < 1e-12: if mpirank == 0: print '\nGradient sufficiently reduced -- optimization stopped' break # Compute search direction: tolcg = min(tolcg, np.sqrt(self.Gradnorm / gradnorm0)) self.assemble_hessian() # for regularization cgiter, cgres, cgid, tolcg = compute_searchdirection( self, 'Newt', tolcg) self._plotsrchdir(myplot, str(it)) # Line search: cost_old = self.cost statusLS, LScount, alpha = bcktrcklinesearch(self, 12) if mpirank == 0: print '{:11.3f} {:12.2e} {:10d}'.format(alpha, tolcg, cgiter) if self.PD: self.Regul.update_w(self.srchdir.vector(), alpha) if np.abs(self.cost - cost_old) / np.abs(cost_old) < tolcost: if mpirank == 0: if tolcg < 1e-14: print 'Cost function stagnates -- optimization aborted' break tolcg = 0.001 * tolcg def assemble_hessian(self): self.Regul.assemble_hessian(self.m) def _plotm(self, myplot, index): """ plot media during inversion """ if not myplot == None: myplot.set_varname('m' + index) myplot.plot_vtk(self.m) def _plotgrad(self, myplot, index): """ plot grad during inversion """ if not myplot == None: myplot.set_varname('Grad_m' + index) myplot.plot_vtk(self.Grad) def _plotsrchdir(self, myplot, index): """ plot srchdir during inversion """ if not myplot == None: myplot.set_varname('srchdir_m' + index) myplot.plot_vtk(self.srchdir)
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 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))
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