def solve(self, interval, dt=None): """ Solve the discretization on a given time interval (t0, t1) with a given timestep dt and return generator for a tuple of the interval and the current solution. *Arguments* interval (:py:class:`tuple`) The time interval for the solve given by (t0, t1) dt (int, optional) The timestep for the solve. Defaults to length of interval *Returns* (timestep, solution_fields) via (:py:class:`genexpr`) *Example of usage*:: # Create generator solutions = solver.solve((0.0, 1.0), 0.1) # Iterate over generator (computes solutions as you go) for (interval, solution_fields) in solutions: (t0, t1) = interval v_, vur = solution_fields # do something with the solutions """ timer = Timer("PDE step") # Initial set-up # Solve on entire interval if no interval is given. (T0, T) = interval if dt is None: dt = (T - T0) t0 = T0 t1 = T0 + dt # Step through time steps until at end time while (True) : info("Solving on t = (%g, %g)" % (t0, t1)) self.step((t0, t1)) # Yield solutions yield (t0, t1), self.solution_fields() # Break if this is the last step if end_of_time(T, t0, t1, dt): break # If not: update members and move to next time # Subfunction assignment would be good here. if isinstance(self.v_, Function): self.merger.assign(self.v_, self.vur.sub(0)) else: debug("Assuming that v_ is updated elsewhere. Experimental.") t0 = t1 t1 = t0 + dt
def __init__(self, mesh, time, M_i, M_e, I_s=None, I_a=None, v_=None, params=None): # Check some input assert isinstance(mesh, Mesh), \ "Expecting mesh to be a Mesh instance, not %r" % mesh assert isinstance(time, Constant) or time is None, \ "Expecting time to be a Constant instance (or None)." assert isinstance(params, Parameters) or params is None, \ "Expecting params to be a Parameters instance (or None)" self._nullspace_basis = None # Store input self._mesh = mesh self._time = time self._M_i = M_i self._M_e = M_e self._I_s = I_s self._I_a = I_a # Initialize and update parameters if given self.parameters = self.default_parameters() if params is not None: self.parameters.update(params) # Set-up function spaces k = self.parameters["polynomial_degree"] Ve = FiniteElement("CG", self._mesh.ufl_cell(), k) V = FunctionSpace(self._mesh, "CG", k) Ue = FiniteElement("CG", self._mesh.ufl_cell(), k) U = FunctionSpace(self._mesh, "CG", k) use_R = self.parameters["use_avg_u_constraint"] if use_R: Re = FiniteElement("R", self._mesh.ufl_cell(), 0) R = FunctionSpace(self._mesh, "R", 0) self.VUR = FunctionSpace(mesh, MixedElement((Ve, Ue, Re))) else: self.VUR = FunctionSpace(mesh, MixedElement((Ve, Ue))) self.V = V # Set-up solution fields: if v_ is None: self.merger = FunctionAssigner(V, self.VUR.sub(0)) self.v_ = Function(V, name="v_") else: debug("Experimental: v_ shipped from elsewhere.") self.merger = None self.v_ = v_ self.vur = Function(self.VUR, name="vur") # Figure out whether we should annotate or not self._annotate_kwargs = annotate_kwargs(self.parameters)
def step(self, interval): """ Solve on the given time step (t0, t1). *Arguments* interval (:py:class:`tuple`) The time interval (t0, t1) for the step *Invariants* Assuming that v\_ is in the correct state for t0, gives self.vur in correct state at t1. """ timer = Timer("PDE step") solver_type = self.parameters["linear_solver_type"] # Extract interval and thus time-step (t0, t1) = interval dt = t1 - t0 theta = self.parameters["theta"] t = t0 + theta*dt self.time.assign(t) # Update matrix and linear solvers etc as needed if self._timestep is None: self._timestep = Constant(dt) (self._lhs, self._rhs) = self.variational_forms(self._timestep) # Preassemble left-hand side and initialize right-hand side vector debug("Preassembling bidomain matrix (and initializing vector)") self._lhs_matrix = assemble(self._lhs, **self._annotate_kwargs) self._rhs_vector = Vector(self._mesh.mpi_comm(), self._lhs_matrix.size(0)) self._lhs_matrix.init_vector(self._rhs_vector, 0) # Create linear solver (based on parameter choices) self._linear_solver, self._update_solver = self._create_linear_solver() else: timestep_unchanged = (abs(dt - float(self._timestep)) < 1.e-12) self._update_solver(timestep_unchanged, dt) # Assemble right-hand-side assemble(self._rhs, tensor=self._rhs_vector, **self._annotate_kwargs) # Solve problem self.linear_solver.solve(self.vur.vector(), self._rhs_vector, **self._annotate_kwargs)
def _update_krylov_solver(self, timestep_unchanged, dt): """Helper function for updating a KrylovSolver depending on whether timestep has changed.""" # Update reuse of preconditioner parameter in accordance with # changes in timestep if timestep_unchanged: debug("Timestep is unchanged, reusing preconditioner") else: debug("Timestep has changed, updating preconditioner") if dolfin_adjoint and self.parameters["enable_adjoint"]: raise ValueError("dolfin-adjoint doesn't support changing timestep (yet)") # Update stored timestep self._timestep.assign(Constant(dt))#, annotate=annotate) # Reassemble matrix assemble(self._lhs, tensor=self._lhs_matrix, **self._annotate_kwargs) # Make new Krylov solver (self._linear_solver, dummy) = self._create_linear_solver() # Set nonzero initial guess if it indeed is nonzero if (self.vur.vector().norm("l2") > 1.e-12): debug("Initial guess is non-zero.") self.linear_solver.parameters["nonzero_initial_guess"] = True
def _update_lu_solver(self, timestep_unchanged, dt): """Helper function for updating an LUSolver depending on whether timestep has changed.""" # Update reuse of factorization parameter in accordance with # changes in timestep if timestep_unchanged: debug("Timestep is unchanged, reusing LU factorization") else: debug("Timestep has changed, updating LU factorization") if dolfin_adjoint and self.parameters["enable_adjoint"]: raise ValueError("dolfin-adjoint doesn't support changing timestep (yet)") # Update stored timestep # FIXME: dolfin_adjoint still can't annotate constant assignment. self._timestep.assign(Constant(dt))#, annotate=annotate) # Reassemble matrix assemble(self._lhs, tensor=self._lhs_matrix, **self._annotate_kwargs) (self._linear_solver, dummy) = self._create_linear_solver()
def __init__(self, mesh, heart_mesh, time, M_i, M_e, M_T, I_s=None, I_a=None, v_=None, params=None): # Check some input assert isinstance(mesh, Mesh), \ "Expecting mesh to be a Mesh instance, not %r" % mesh assert isinstance(heart_mesh, Mesh), \ "Expecting heart_mesh to be a Mesh instance, not %r" % heart_mesh assert isinstance(time, Constant) or time is None, \ "Expecting time to be a Constant instance (or None)." assert isinstance(params, Parameters) or params is None, \ "Expecting params to be a Parameters instance (or None)" self._nullspace_basis = None # Store input self._mesh = mesh self._heart_mesh = heart_mesh self._time = time self._M_i = M_i self._M_e = M_e self._M_T = M_T self._I_s = I_s self._I_a = I_a # Check if the heart mesh is a submesh of the whole mesh mapping = self._heart_mesh.topology().mapping()[self._mesh.id()] cell_map = mapping.cell_map() # Cells related to heart domain are marked with 1, other cells (torso) are marked with 0 self._heart_cells = MeshFunction("size_t", self._mesh, self._mesh.topology().dim(), 0) for c in cells(self._heart_mesh): idx = int(cell_map[c.index()]) self._heart_cells[idx] = 1; # Initialize and update parameters if given self.parameters = self.default_parameters() if params is not None: self.parameters.update(params) # Set-up function spaces k = self.parameters["polynomial_degree"] V = FunctionSpace(self._heart_mesh, "CG", k) U = FunctionSpace(self._mesh, "CG", k) use_R = self.parameters["use_avg_u_constraint"] if use_R: R = FunctionSpace(self._mesh, "R", 0) self.VUR = MixedFunctionSpace(V, U, R) else: self.VUR = MixedFunctionSpace(V, U) self.V = V # Set-up solution fields: if v_ is None: self.merger = FunctionAssigner(V, self.VUR.sub_space(0)) self.v_ = Function(self.VUR.sub_space(0), name="v_") else: debug("Experimental: v_ shipped from elsewhere.") self.merger = None self.v_ = v_ self.vur = Function(self.VUR) # Figure out whether we should annotate or not self._annotate_kwargs = annotate_kwargs(self.parameters)
def _create_linear_solver(self): "Helper function for creating linear solver based on parameters." solver_type = self.parameters["linear_solver_type"] if solver_type == "direct": solver = LUSolver(self._lhs_matrix) solver.parameters.update(self.parameters["lu_solver"]) update_routine = self._update_lu_solver elif solver_type == "iterative": # Initialize KrylovSolver with matrix alg = self.parameters["algorithm"] prec = self.parameters["preconditioner"] debug("Creating PETSCKrylovSolver with %s and %s" % (alg, prec)) if prec == "fieldsplit": # Argh. DOLFIN won't let you construct a PETScKrylovSolver with fieldsplit. Sigh .. solver = PETScKrylovSolver() # FIXME: work around DOLFIN bug #583. Just deleted this when fixed. solver.parameters.update({"convergence_norm_type": "preconditioned"}) #solver.parameters["preconditioner"]["structure"] = "same" # MER this should be set by user, and is below solver.parameters.update(self.parameters["petsc_krylov_solver"]) solver.set_operator(self._lhs_matrix) # Initialize the KSP directly: ksp = solver.ksp() ksp.setType(alg) ksp.pc.setType(prec) ksp.setOptionsPrefix("bidomain_") # it's really stupid, solver.set_options_prefix() doesn't work # Set various options (by default) for the fieldsplit # approach to solving the bidomain equations. # FIXME: This needs a try from petsc4py import PETSc # Patrick believes that the fieldsplit index sets # should already be set from the assembled matrix. # Now let's set some default options for the solver. opts = PETSc.Options("bidomain_") if "pc_fieldsplit_type" not in opts: opts["pc_fieldsplit_type"] = "symmetric_multiplicative" if "fieldsplit_0_ksp_type" not in opts: opts["fieldsplit_0_ksp_type"] = "preonly" if "fieldsplit_1_ksp_type" not in opts: opts["fieldsplit_1_ksp_type"] = "preonly" if "fieldsplit_0_pc_type" not in opts: opts["fieldsplit_0_pc_type"] = "hypre" if "fieldsplit_1_pc_type" not in opts: opts["fieldsplit_1_pc_type"] = "hypre" ksp.setFromOptions() ksp.setUp() else: solver = PETScKrylovSolver(alg, prec) solver.set_operator(self._lhs_matrix) # Still waiting for that bug fix: solver.parameters.update({"convergence_norm_type": "preconditioned"}) solver.parameters.update(self.parameters["petsc_krylov_solver"]) # Set nullspace if present. We happen to know that the # transpose nullspace is the same as the nullspace (easy # to prove from matrix structure). if self.parameters["use_avg_u_constraint"]: # Nothing to do, no null space pass else: # If dolfin-adjoint is enabled and installled: set the solver nullspace if dolfin_adjoint: solver.set_nullspace(self.nullspace) solver.set_transpose_nullspace(self.nullspace) # Otherwise, set the nullspace in the operator # directly. else: A = as_backend_type(self._lhs_matrix) A.set_nullspace(self.nullspace) update_routine = self._update_krylov_solver else: error("Unknown linear_solver_type given: %s" % solver_type) return (solver, update_routine)