class CHProblem(NonlinearProblem): """Class for interfacing with :py:class:`dolfin.NewtonSolver`.""" def __init__(self, F, bcs, J): super(SemiDecoupled.CHProblem, self).__init__() # Assembler for Newton system self.assembler = SystemAssembler(J, F, bcs) # Store forms for later self.forms = { "F": F, "J": J, } def function_space(self): return self.forms["F"].arguments()[0].function_space() def form(self, A, P, b, x): if A.empty(): matA = as_backend_type(A).mat() assert not matA.isAssembled() bs = self.function_space().dofmap().block_size() matA.setBlockSize(bs) # matP = as_backend_type(P).mat() # assert not matP.isAssembled() # matP.setBlockSize(bs) def F(self, b, x): self.assembler.assemble(b, x) def J(self, A, x): # matA = as_backend_type(A).mat() # bs = self.function_space().dofmap().block_size() # assert matA.getBlockSize() == bs self.assembler.assemble(A)
class Problem(NonlinearProblem): """ Class for interfacing with :py:class:`NewtonSolver`. """ def __init__(self, F, bcs, J, null_space=None): super(Monolithic.Problem, self).__init__() self.assembler = SystemAssembler(J, F, bcs) self.null_space = null_space def F(self, b, x): self.assembler.assemble(b, x) if self.null_space: # Orthogonalize RHS vector b with respect to the null space self.null_space.orthogonalize(b) def J(self, A, x): self.assembler.assemble(A) if self.null_space: # Attach null space to PETSc matrix as_backend_type(A).set_nullspace(self.null_space)
class CHNonlinearProblem(NonlinearProblem): """Class for interfacing with :py:class:`CHNewtonSolver`.""" def __init__(self, F, bcs, J, J_pc=None): """Return subclass of :py:class:`dolfin.NonlinearProblem` suitable for :py:class:`NewtonSolver` in combination with :py:class:`muflon.solving.solvers.SemiDecoupled`. """ super(CHNonlinearProblem, self).__init__() # Assembler for Newton system self.assembler = SystemAssembler(J, F, bcs) # Assembler for preconditioner if J_pc is not None: self.assembler_pc = SystemAssembler(J_pc, F, bcs) else: self.assembler_pc = None # Store bcs self._bcs = bcs # Store forms for later self.forms = { "F": F, "J": J, "J_pc": J_pc, } def get_form(self, key): form = self.forms.get(key) if form is None: raise AttributeError("Form '%s' not available" % key) return form def function_space(self): return self.forms["F"].arguments()[0].function_space() def form(self, A, P, b, x): if A.empty(): matA = as_backend_type(A).mat() assert not matA.isAssembled() bs = self.function_space().dofmap().block_size() matA.setBlockSize(bs) if self.assembler_pc is not None and P.empty(): matP = as_backend_type(P).mat() assert not matP.isAssembled() matP.setBlockSize(bs) def F(self, b, x): self.assembler.assemble(b, x) def J(self, A, x): self.assembler.assemble(A) def J_pc(self, P, x): if self.assembler_pc is not None: self.assembler_pc.assemble(P)
class rmtursAssembler(object): def __init__(self, a, L, bcs, a_pc=None): self.assembler = SystemAssembler(a, L, bcs) if a_pc is not None: self.assembler_pc = SystemAssembler(a_pc, L, bcs) else: self.assembler_pc = None self._bcs = bcs def rhs_vector(self, b, x=None): if x is not None: self.assembler.assemble(b, x) else: self.assembler.assemble(b) def system_matrix(self, A): self.assembler.assemble(A) def pc_matrix(self, P): if self.assembler_pc is not None: self.assembler_pc.assemble(P)
class DamageSolverSNES: """docstring for DamageSolverSNES""" def __init__(self, problem, parameters={}, lb=None): super(DamageSolverSNES, self).__init__() self.problem = problem self.energy = problem.energy self.state = problem.state self.alpha = problem.state['alpha'] self.alpha_dvec = as_backend_type(self.alpha.vector()) self.alpha_pvec = self.alpha_dvec.vec() self.bcs = problem.bcs self.parameters = parameters comm = self.alpha.function_space().mesh().mpi_comm() self.comm = comm V = self.alpha.function_space() self.V = V self.Ealpha = derivative( self.energy, self.alpha, dolfin.TestFunction(self.alpha.ufl_function_space())) self.dm = self.alpha.function_space().dofmap() solver = PETScSNESSolver() snes = solver.snes() if lb == None: lb = interpolate(Constant(0.), V) ub = interpolate(Constant(1.), V) prefix = "damage_" snes.setOptionsPrefix(prefix) for option, value in self.parameters["snes"].items(): PETScOptions.set(prefix + option, value) log(LogLevel.DEBUG, "DEBUG: Set: {} = {}".format(prefix + option, value)) snes.setFromOptions() (J, F, bcs_alpha) = (problem.J, problem.F, problem.bcs) self.ass = SystemAssembler(J, F, bcs_alpha) self.b = self.init_residual() snes.setFunction(self.residual, self.b.vec()) self.A = self.init_jacobian() snes.setJacobian(self.jacobian, self.A.mat()) snes.ksp.setOperators(self.A.mat()) snes.setVariableBounds(self.problem.lb.vec(), self.problem.ub.vec()) # # snes.solve(None, Function(V).vector().vec()) self.solver = snes def update_x(self, x): """ Given a PETSc Vec x, update the storage of our solution function alpha. """ x.copy(self.alpha_pvec) self.alpha_dvec.update_ghost_values() def init_residual(self): alpha = self.problem.state["alpha"] b = as_backend_type(Function(alpha.function_space()).vector()) return b def init_jacobian(self): A = PETScMatrix(self.comm) self.ass.init_global_tensor(A, Form(self.problem.J)) return A def residual(self, snes, x, b): self.update_x(x) b_wrap = PETScVector(b) self.ass.assemble(b_wrap, self.alpha_dvec) def jacobian(self, snes, x, A, P): self.update_x(x) A_wrap = PETScMatrix(A) self.ass.assemble(A_wrap) def solve(self): alpha = self.problem.state["alpha"] # Need a copy for line searches etc. to work correctly. x = alpha.copy(deepcopy=True) xv = as_backend_type(x.vector()).vec() # Solve the problem self.solver.solve(None, xv) def inactive_set_indicator(self, tol=1.0e-5): Ealpha = assemble(self.Ealpha) mask_grad = Ealpha[:] < tol mask_ub = self.alpha.vector()[:] < 1. - tol mask_lb = self.alpha.vector()[:] > self.problem.lb[:] + tol local_inactive_set_alpha = set(np.where(mask_ub == True)[0]) \ & set(np.where(mask_grad == True)[0]) \ & set(np.where(mask_lb == True)[0]) _set_alpha = [ self.dm.local_to_global_index(k) for k in local_inactive_set_alpha ] inactive_set_alpha = set(_set_alpha) | set(self.dm.dofs()) index_set = petsc4py.PETSc.IS() index_set.createGeneral(list(inactive_set_alpha)) return index_set
class PCDAssembler(object): """Base class for creating linear problems to be solved by application of the PCD preconditioning strategy. Users are encouraged to use this class for interfacing with :py:class:`fenapack.field_split.PCDKrylovSolver`. On request it assembles not only the individual PCD operators but also the system matrix and the right hand side vector defining the linear problem. """ def __init__(self, a, L, bcs, a_pc=None, mp=None, mu=None, ap=None, fp=None, kp=None, gp=None, bcs_pcd=[]): """Collect individual variational forms and boundary conditions defining a linear problem (system matrix + RHS vector) on the one side and preconditioning operators on the other side. *Arguments* a (:py:class:`dolfin.Form` or :py:class:`ufl.Form`) Bilinear form representing a system matrix. L (:py:class:`dolfin.Form` or :py:class:`ufl.Form`) Linear form representing a right hand side vector. bcs (:py:class:`list` of :py:class:`dolfin.DirichletBC`) Boundary conditions applied to ``a``, ``L``, and ``a_pc``. a_pc (:py:class:`dolfin.Form` or :py:class:`ufl.Form`) Bilinear form representing a matrix optionally passed to preconditioner instead of ``a``. In case of PCD, stabilized 00-block can be passed to 00-KSP solver. mp, mu, ap, fp, kp, gp (:py:class:`dolfin.Form` or :py:class:`ufl.Form`) Bilinear forms which (some of them) might be used by a particular PCD(R) preconditioner. Typically they represent "mass matrix" on pressure, "mass matrix" on velocity, minus Laplacian operator on pressure, pressure convection-diffusion operator, pressure convection operator and pressure gradient respectively. bcs_pcd (:py:class:`list` of :py:class:`dolfin.DirichletBC`) Artificial boundary conditions used by PCD preconditioner. All the arguments should be given on the common mixed function space. All the forms are wrapped using :py:class:`PCDForm` so that each of them can be endowed with additional set of properties. By default, ``mp``, ``mu``, ``ap`` and ``gp`` are assumed to be constant if the preconditioner is used repeatedly in some outer iterative process (e.g Newton-Raphson method, time-stepping). As such, the corresponding operators are assembled only once. On the other hand, ``fp`` and ``kp`` are updated in every outer iteration. Also note that ``gp`` is the only form that is by default in a *phantom mode*. It means that the corresponding operator (if needed) is not obtained by assembling the form, but it is extracted as the 01-block of the system matrix. The default setting can be modified by accessing a :py:class:`PCDForm` instance via :py:meth:`PCDAssembler.get_pcd_form` and changing the properties directly. """ # Assembler for the linear system of algebraic equations self.assembler = SystemAssembler(a, L, bcs) # Assembler for preconditioner if a_pc is not None: self.assembler_pc = SystemAssembler(a_pc, L, bcs) else: self.assembler_pc = None # Store bcs self._bcs = bcs self._bcs_pcd = bcs_pcd # Store and initialize forms self._forms = { "L": PCDForm(L), "ap": PCDForm(ap, const=True), "mp": PCDForm(mp, const=True), "mu": PCDForm(mu, const=True), "fp": PCDForm(fp), "kp": PCDForm(kp), "gp": PCDForm(gp, const=True, phantom=True), } def get_pcd_form(self, key): """Return form wrapped in :py:class:`PCDForm`.""" form = self._forms.get(key) if form is None: raise AttributeError("Form '%s' requested by PCD not available" % key) assert isinstance(form, PCDForm) return form def get_dolfin_form(self, key): """Return form as :py:class:`dolfin.Form` or :py:class:`ufl.Form`.""" return self.get_pcd_form(key).dolfin_form() def function_space(self): return self.get_dolfin_form("L").arguments()[0].function_space() def rhs_vector(self, b, x=None): """Assemble right hand side vector ``b``. The version with ``x`` is suitable for use inside a (quasi)-Newton solver. """ if x is not None: self.assembler.assemble(b, x) else: self.assembler.assemble(b) def system_matrix(self, A): """Assemble system matrix ``A``.""" self.assembler.assemble(A) def pc_matrix(self, P): """Assemble preconditioning matrix ``P`` whose relevant blocks can be passed to actual parts of the ``KSP`` solver. """ if self.assembler_pc is not None: self.assembler_pc.assemble(P) def ap(self, Ap): assembler = SystemAssembler(self.get_dolfin_form("ap"), self.get_dolfin_form("L"), self.pcd_bcs()) assembler.assemble(Ap) def mp(self, Mp): assemble(self.get_dolfin_form("mp"), tensor=Mp) def mu(self, Mu): assemble(self.get_dolfin_form("mu"), tensor=Mu) def fp(self, Fp): assemble(self.get_dolfin_form("fp"), tensor=Fp) def kp(self, Kp): assemble(self.get_dolfin_form("kp"), tensor=Kp) def gp(self, Bt): """Assemble discrete pressure gradient. It is crucial to respect any constraints placed on the velocity test space by Dirichlet boundary conditions.""" assemble(self.get_dolfin_form("gp"), tensor=Bt) for bc in self._bcs: bc.apply(Bt) # FIXME: Naming def pcd_bcs(self): try: assert self._bcs_pcd is not None except (AttributeError, AssertionError): raise AttributeError("BCs requested by PCD not available") return self._bcs_pcd
def ap(self, Ap): assembler = SystemAssembler(self.get_dolfin_form("ap"), self.get_dolfin_form("L"), self.pcd_bcs()) assembler.assemble(Ap)