Exemplo n.º 1
0
    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()
Exemplo n.º 6
0
    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)