Example #1
0
 def write_checkpoint(self):
     """Write checkpoint file (with solution and time) to disk."""
     checkpoint_filepath = self.output_dir + "checkpoint_t" + str(self.state.time) + ".h5"
     
     self.latest_checkpoint_filepath = checkpoint_filepath
     
     phaseflow.helpers.print_once("Writing checkpoint file to " + checkpoint_filepath)
     
     with fenics.HDF5File(fenics.mpi_comm_world(), checkpoint_filepath, "w") as h5:
         
         h5.write(self.state.solution.function_space().mesh().leaf_node(), "mesh")
     
         h5.write(self.state.solution.leaf_node(), "solution")
         
         if self.second_order_time_discretization:
         
             h5.write(self.old_state.solution.leaf_node(), "old_solution")
         
     if fenics.MPI.rank(fenics.mpi_comm_world()) is 0:
     
         with h5py.File(checkpoint_filepath, "r+") as h5:
             
             h5.create_dataset("time", data = self.state.time)
             
             h5.create_dataset("timestep_size", data = self.timestep_size)
             
             if self.second_order_time_discretization:
             
                 h5.create_dataset("old_time", data = self.old_state.time)
Example #2
0
    def read_checkpoint(self, filepath):
        """Read solutions and times from a checkpoint file."""
        self._mesh = fenics.Mesh()

        print("Reading checkpoint from " + filepath)

        with fenics.HDF5File(self.mesh.mpi_comm(), filepath, "r") as h5:

            h5.read(self._mesh, "mesh", True)

            self._function_space = fenics.FunctionSpace(
                self.mesh, self._element)

            for i in range(self.time_order + 1):

                self._solutions[i] = fenics.Function(self.function_space)

                h5.read(self._solutions[i], "solution" + str(i))
                """ fenics.HDF5File doesn't implement read methods for every write method.
                Our only option here seems to be to use a fenics.Vector to store values,
                because a reader is implemented for GenericVector, which Vector inherits from.
                Furthermore, for the correct read method to be called, we must pass a boolean
                as a third argument related to distributed memory.
                """
                time = fenics.Vector(fenics.mpi_comm_world(), 1)

                h5.read(time, "time" + str(i), False)

                self._times[i] = time.get_local()[0]

        self.newton_solution = fenics.Function(self.function_space)

        self.setup_solver()
Example #3
0
def Newton_manual(F, udp, bcs, atol, rtol, max_it, lmbda, udp_res, VVQ):
    #Reset counters
    Iter = 0
    residual = 1
    rel_res = residual
    dw = TrialFunction(VVQ)
    Jac = derivative(F, udp, dw)  # Jacobi

    while rel_res > rtol and residual > atol and Iter < max_it:
        A = assemble(Jac)
        A.ident_zeros()
        b = assemble(-F)

        [bc.apply(A, b, udp.vector()) for bc in bcs]

        #solve(A, udp_res.vector(), b, "superlu_dist")

        solve(A, udp_res.vector(), b)  #, "mumps")

        udp.vector()[:] = udp.vector()[:] + lmbda * udp_res.vector()[:]
        #udp.vector().axpy(1., udp_res.vector())
        [bc.apply(udp.vector()) for bc in bcs]
        rel_res = norm(udp_res, 'l2')
        residual = b.norm('l2')

        if MPI.rank(mpi_comm_world()) == 0:
            print "Newton iteration %d: r (atol) = %.3e (tol = %.3e), r (rel) = %.3e (tol = %.3e) " \
        % (Iter, residual, atol, rel_res, rtol)
        Iter += 1

    return udp
Example #4
0
def solver_nonlinear(G, d_, w_, wd_, bcs, T, dt, action=None, **namespace):
    dis_x = []; dis_y = []; time = []
    solver_parameters = {"newton_solver": \
                          {"relative_tolerance": 1E-8,
                           "absolute_tolerance": 1E-8,
                           "maximum_iterations": 100,
                           #"krylov_solver": {"monitor_convergence": True},
                           #"linear_solver": {"monitor_convergence": True},
                           "relaxation_parameter": 0.9}}
    t = 0
    while t < (T - dt*DOLFIN_EPS):
        solve(G == 0, wd_["n"], bcs, solver_parameters=solver_parameters)

        # Update solution
        times = ["n-3", "n-2", "n-1", "n"]
        for i, t_tmp in enumerate(times[:-1]):
            wd_[t_tmp].vector().zero()
            wd_[t_tmp].vector().axpy(1, wd_[times[i+1]].vector())
            w_[t_tmp], d_[t_tmp] = wd_[t_tmp].split(True)

        # Get displacement
        if callable(action):
            action(wd_, t)

        t += dt
        if MPI.rank(mpi_comm_world()) == 0:

            print "Time: ",t #,"dis_x: ", d(coord)[0], "dis_y: ", d(coord)[1]

    return dis_x, dis_y, time
Example #5
0
def solver_nonlinear(G, d_, w_, wd_, bcs, T, dt, action=None, **namespace):
    dis_x = []; dis_y = []; time = []
    solver_parameters = {"newton_solver": \
                          {"relative_tolerance": 1E-8,
                           "absolute_tolerance": 1E-8,
                           "maximum_iterations": 100,
                           "relaxation_parameter": 1.0}}
    t = 0
    while t <= T:
        solve(G == 0, wd_["n"], bcs, solver_parameters=solver_parameters)

        # Update solution
        times = ["n-3", "n-2", "n-1", "n"]
        for i, t_tmp in enumerate(times[:-1]):
            wd_[t_tmp].vector().zero()
            wd_[t_tmp].vector().axpy(1, wd_[times[i+1]].vector())
            w_[t_tmp], d_[t_tmp] = wd_[t_tmp].split(True)

        # Get displacement
        if callable(action):
            action(wd_, t)

        t += dt
        if MPI.rank(mpi_comm_world()) == 0:
            print "Time: ", t
Example #6
0
def Newton_manual(F, vd, bcs, J, atol, rtol, max_it, lmbda\
                 , vd_res):
    #Reset counters
    Iter      = 0
    residual   = 1
    rel_res    = residual
    while rel_res > rtol and residual > atol and Iter < max_it:
        A = assemble(J, keep_diagonal = True)
        A.ident_zeros()
        b = assemble(-F)

        [bc.apply(A, b, vd.vector()) for bc in bcs]

        #solve(A, vd_res.vector(), b, "superlu_dist")
        #solve(A, vd_res.vector(), b, "mumps")
        solve(A, vd_res.vector(), b)

        vd.vector().axpy(1., vd_res.vector())
        [bc.apply(vd.vector()) for bc in bcs]
        rel_res = norm(vd_res, 'l2')
        residual = b.norm('l2')

        if MPI.rank(mpi_comm_world()) == 0:
            print "Newton iteration %d: r (atol) = %.3e (tol = %.3e), r (rel) = %.3e (tol = %.3e) " \
        % (Iter, residual, atol, rel_res, rtol)
        Iter += 1

    #Reset
    residual   = 1
    rel_res    = residual
    Iter = 0

    return vd
    def setup_coarse_mesh(self):
        """ This creates the rectangular mesh, or rectangular prism in 3D. """
        if len(self.mesh_size) == 2:

            self.mesh = fenics.RectangleMesh(
                fenics.mpi_comm_world(), fenics.Point(self.xmin, self.ymin),
                fenics.Point(self.xmax, self.ymax), self.mesh_size[0],
                self.mesh_size[1], "crossed")

        elif len(self.mesh_size) == 3:

            self.mesh = fenics.BoxMesh(
                fenics.mpi_comm_world(),
                fenics.Point(self.xmin, self.ymin, self.zmin),
                fenics.Point(self.xmax, self.ymax, self.zmax),
                self.mesh_size[0], self.mesh_size[1], self.mesh_size[2])
Example #8
0
def print_text(text, color='white', atrb=0, cls=None):
  """
  Print text <text> from calling class <cl> to the screen.
  """
  if cls is not None:
    color = cls.color()
  if MPI.rank(mpi_comm_world())==0:
    if atrb != 0:
      text = ('%s%s' + text + '%s') % (fg(color), attr(atrb), attr(0))
    else:
      text = ('%s' + text + '%s') % (fg(color), attr(0))
    print text
Example #9
0
def get_text(text, color='white', atrb=0, cls=None):
  """
  Returns text <text> from calling class <cl> for later printing.
  """
  if cls is not None:
    color = cls.color()
  if MPI.rank(mpi_comm_world())==0:
    if atrb != 0:
      text = ('%s%s' + text + '%s') % (fg(color), attr(atrb), attr(0))
    else:
      text = ('%s' + text + '%s') % (fg(color), attr(0))
    return text
Example #10
0
def print_min_max(u, title, color='97', cls=None):
  """
  Print the minimum and maximum values of <u>, a Vector, Function, or array.
  """
  #if cls is not None:
  #  color = cls.color()
  if isinstance(u, GenericVector):
    uMin = MPI.min(mpi_comm_world(), u.min())
    uMax = MPI.max(mpi_comm_world(), u.max())
    s    = title + ' <min, max> : <%.3e, %.3e>' % (uMin, uMax)
    print_text(s, color)
  elif isinstance(u, ndarray):
    if u.dtype != float64:
      u = u.astype(float64)
    uMin = MPI.min(mpi_comm_world(), u.min())
    uMax = MPI.max(mpi_comm_world(), u.max())
    s    = title + ' <min, max> : <%.3e, %.3e>' % (uMin, uMax)
    print_text(s, color)
  elif isinstance(u, Function):# \
    #   or isinstance(u, dolfin.functions.function.Function):
    uMin = MPI.min(mpi_comm_world(), u.vector().min())
    uMax = MPI.max(mpi_comm_world(), u.vector().max())
    s    = title + ' <min, max> : <%.3e, %.3e>' % (uMin, uMax)
    print_text(s, color)
  elif isinstance(u, int) or isinstance(u, float):
    s    = title + ' : %.3e' % u
    print_text(s, color)
  elif isinstance(u, Constant):
    s    = title + ' : %.3e' % u(0)
    print_text(s, color)
  else:
    er = title + ": print_min_max function requires a Vector, Function" \
         + ", array, int or float, not %s." % type(u)
    print_text(er, 'red', 1)
Example #11
0
def viz(results, runs):
    if MPI.rank(mpi_comm_world()) != 0: pass
    # TODO: Get minimum time of all to plot over consistent
    plt.figure()
    for i, r in enumerate(results):
        displacement_x = np.array(r[0])
        displacement_y = np.array(r[1])
        time = np.array(r[2])
        simulation_parameters = runs[i]
        E = simulation_parameters["E"]
        dt = simulation_parameters["dt"]
        name = simulation_parameters["name"]
        case_path = simulation_parameters["case_path"]

        plt.plot(time, displacement_y, label=name)
        plt.ylabel("Displacement y")
        plt.xlabel("Time")
        plt.legend(loc=3)
        plt.hold("on")

        plt.title("implementation: %s, dt = %g, y_displacement" % (name, dt))

        # TODO: Move this to solver and use fenicsprobe
        if MPI.rank(mpi_comm_world()) == 0:
            if not path.exists(case_path):
                makedirs(case_path)

            print case_path
            displacement_x.dump(path.join(case_path, "dis_x.np"))
            displacement_t.dump(path.join(case_path, "dis_y.np"))
            time.dump(path.join(case_path, "time.np"))

            # Store simulation parameters
            f = open(path.join(case_path, "param.dat", 'w'))
            cPickle.dump(simulation_parameters, f)
            f.close()

    # FIXME: store in a consistent manner
    plt.savefig("test.png")
Example #12
0
def print_min_max(u, title, color='97'):
  """
  Print the minimum and maximum values of ``u``, a Vector, Function, or array.

  :param u: the variable to print the min and max of
  :param title: the name of the function to print
  :param color: the color of printed text
  :type u: :class:`~fenics.GenericVector`, :class:`~numpy.ndarray`, :class:`~fenics.Function`, int, float, :class:`~fenics.Constant`
  :type title: string
  :type color: string
  """
  if isinstance(u, fe.GenericVector):
    uMin = MPI.min(fe.mpi_comm_world(), u.min())
    uMax = MPI.max(fe.mpi_comm_world(), u.max())
    s    = title + ' <min, max> : <%.3e, %.3e>' % (uMin, uMax)
    print_text(s, color)
  elif isinstance(u, np.ndarray):
    if u.dtype != np.float64:
      u = u.astype(np.float64)
    uMin = fe.MPI.min(mpi_comm_world(), u.min())
    uMax = fe.MPI.max(mpi_comm_world(), u.max())
    s    = title + ' <min, max> : <%.3e, %.3e>' % (uMin, uMax)
    print_text(s, color)
  elif isinstance(u, fe.Function):# \
    #   or isinstance(u, dolfin.functions.function.Function):
    uMin = MPI.min(fe.mpi_comm_world(), u.vector().min())
    uMax = MPI.max(fe.mpi_comm_world(), u.vector().max())
    s    = title + ' <min, max> : <%.3e, %.3e>' % (uMin, uMax)
    print_text(s, color)
  elif isinstance(u, int) or isinstance(u, float):
    s    = title + ' : %.3e' % u
    print_text(s, color)
  elif isinstance(u, fe.Constant):
    s    = title + ' : %.3e' % u(0)
    print_text(s, color)
  else:
    er = title + ": print_min_max function requires a Vector, Function" \
         + ", array, int or float, not %s." % type(u)
    print_text(er, 'red', 1)
Example #13
0
def checkpoint(solution, time, checkpoint_filepath):
    """ Write a checkpoint file (with solution and time). 
    
    The Poisson problem implemented in this module is not time dependent;
    but Phaseflow simulations are time dependent, and require h5py in addition to 
    the built-in `fenics.HDF5File` for checkpointing/restarting between time steps.
    """
    with fenics.HDF5File(fenics.mpi_comm_world(), checkpoint_filepath,
                         "w") as h5:

        h5.write(solution.function_space().mesh(), "mesh")

        h5.write(solution, "solution")

    with h5py.File(checkpoint_filepath, "r+") as h5:

        h5.create_dataset("time", data=time)
Example #14
0
def print_text(text, color='white', atrb=0, cls=None):
  """
  Print text ``text`` from calling class ``cls`` to the screen.

  :param text: the text to print
  :param color: the color of the text to print
  :param atrb: attributes to send use by ``colored`` package
  :param cls: the calling class
  :type text: string
  :type color: string
  :type atrb: int
  :type cls: object
  """
  if cls is not None:
    color = cls.color()
  if fe.MPI.rank(fe.mpi_comm_world())==0:
    if atrb != 0:
      text = ('%s%s' + text + '%s') % (fg(color), attr(atrb), attr(0))
    else:
      text = ('%s' + text + '%s') % (fg(color), attr(0))
    print text
Example #15
0
    def setup(self):
        """ Set up objects needed before the simulation can run. """
        self.validate_attributes()

        self.setup_derived_attributes()

        if not self.restarted:

            self.setup_coarse_mesh()

            self.setup_element()

            self.refine_initial_mesh()

            self.setup_function_space()

            self.setup_states()

            self.setup_initial_values()

            if self.second_order_time_discretization:

                self.old_old_state.set_from_other_state(self.old_state)

                self.old_old_state.time -= self.timestep_size

        self.setup_problem_and_solver()

        if self.prefix_output_dir_with_tempdir:

            self.output_dir = tempfile.mkdtemp() + "/" + self.output_dir

        phaseflow.helpers.mkdir_p(self.output_dir)

        if fenics.MPI.rank(fenics.mpi_comm_world()) is 0:

            with open(self.output_dir + '/simulation_vars.txt',
                      'w') as simulation_vars_file:

                pprint.pprint(vars(self), simulation_vars_file)
Example #16
0
def main_func():

    # 1. parsing
    args = get_args().parse_args()
    ifile = args.ifile
    ofile = args.o or '{}.h5'.format(os.path.splitext(args.ifile)[0])

    # 2. conversion
    comm = mpi_comm_world()
    gdim = args.geometric_dimension
    mesh, phi, markers = gmsh2dolfin(ifile,
                                     comm=comm,
                                     geometric_dimension=gdim)

    # 3. writing
    save_geometry(comm,
                  mesh,
                  phi,
                  ofile,
                  '',
                  markers=markers,
                  overwrite_file=True)
Example #17
0
def solver_linear(G, d_, w_, wd_, bcs, T, dt, action=None, **namespace):
    a = lhs(G); L = rhs(G)
    A = assemble(a)
    b = assemble(L)
    t = 0

    #d_prec = PETScPreconditioner("default")
    #d_solver = PETScKrylovSolver("gmres", d_prec)
    #d_solver.parameters["monitor_convergence"] = True
    #from IPython import embed; embed()
    #d_solver.prec = d_prec

    # Solver loop
    while t < (T - dt*DOLFIN_EPS):
        t += dt

        # Assemble
        assemble(a, tensor=A)
        assemble(L, tensor=b)

        # Apply BC
        for bc in bcs: bc.apply(A, b)

        # Solve
        #d_solver.solve(A, wd_["n"].vector(), b)
        solve(A, wd_["n"].vector(), b)

        # Update solution
        times = ["n-3", "n-2", "n-1", "n"]
        for i, t_tmp in enumerate(times[:-1]):
            wd_[t_tmp].vector().zero()
            wd_[t_tmp].vector().axpy(1, wd_[times[i+1]].vector())

        # Get displacement
        if callable(action):
            action(wd_, t)

        if MPI.rank(mpi_comm_world()) == 0:
            print "Time: ",t
Example #18
0
def main_func():

    comm = mpi_comm_world()
    args = get_args().parse_args()
    ifile = args.ifile
    ofile = args.o or '{}.h5'.format(os.path.splitext(args.ifile)[0])

    # 1. parsing
    with open(ifile, 'r') as f:
        code = f.read()

    # 2. convert
    mesh, phi, markers = geo2dolfin(code, args.topological_dimension,
                                    args.geometric_dimension, comm)

    # 3. writing
    save_geometry(comm,
                  mesh,
                  phi,
                  ofile,
                  '',
                  markers=markers,
                  overwrite_file=True)
try:
    import h5py
    has_h5py = True
except:
    has_h5py = False

from hashlib import sha1
import os
from fenics import *
import fenics
import re

__all__ = ["save_geometry", "load_geometry", "save_function", "load_function"]

# If dolfin compiled with petsc4py support we expect different types for the mpi_comm.
mpi_comm_type = type(fenics.mpi_comm_world())
str_repr_mpi_comm_type = mpi_comm_type.__name__
rst_repr_mpi_comm_type = "petsc4py.PETSc.Comm" if fenics.has_petsc4py() \
                         else "dolfin.MPI_Comm"

def save_geometry(comm,mesh,phi,h5name,h5group='',markers=None,\
                  overwrite_file=False,overwrite_group=True):

    if not isinstance(comm, mpi_comm_type):
        raise TypeError("expected  a '{}' for the first argument".format(\
            str_repr_mpi_comm_type))

    if not isinstance(mesh, Mesh):
        raise TypeError("expected  a 'ufl.Domain' for the second argument")

    if not isinstance(h5name, str):
Example #20
0
    t0 = timer.time()
    #try:
    if r["space"] == "mixedspace":
        problem_mix(**vars())
    elif r["space"] == "singlespace":
        problem_single()
    else:
        print("Problem type %s is not implemented, only mixedspace " +
              "and singlespace are valid options") % r["space"]
        sys.exit(0)
    #except Exception as e:
    #    print "Problems for solver", r["name"]
    #    print "Unexpected error:", sys.exc_info()[0]
    #    print e
    #    print "Move on the the next solver"
    r["number_of_cores"] = MPI.max(mpi_comm_world(), MPI.rank(
        mpi_comm_world())) + 1
    r["solution_time"] = MPI.sum(mpi_comm_world(),
                                 timer.time() - t0) / (r["number_of_cores"])

    displacement = probe.array()
    probe.clear()

    # Store results
    if MPI.rank(mpi_comm_world()) == 0 and not load:
        results.append(
            (displacement[0, :], displacement[1, :], np.array(time)))
        if not path.exists(r["case_path"]):
            makedirs(r["case_path"])
        results[-1][0].dump(path.join(r["case_path"], "dis_x.np"))
        results[-1][1].dump(path.join(r["case_path"], "dis_y.np"))
Example #21
0
def run(output_dir="output/wang2010_natural_convection_air",
        rayleigh_number=1.e6,
        prandtl_number=0.71,
        stefan_number=0.045,
        heat_capacity=1.,
        thermal_conductivity=1.,
        liquid_viscosity=1.,
        solid_viscosity=1.e8,
        gravity=(0., -1.),
        m_B=None,
        ddT_m_B=None,
        penalty_parameter=1.e-7,
        temperature_of_fusion=-1.e12,
        regularization_smoothing_factor=0.005,
        mesh=fenics.UnitSquareMesh(fenics.dolfin.mpi_comm_world(), 20, 20,
                                   "crossed"),
        initial_values_expression=("0.", "0.", "0.",
                                   "0.5*near(x[0],  0.) -0.5*near(x[0],  1.)"),
        boundary_conditions=[{
            "subspace": 0,
            "value_expression": ("0.", "0."),
            "degree": 3,
            "location_expression":
            "near(x[0],  0.) | near(x[0],  1.) | near(x[1], 0.) | near(x[1],  1.)",
            "method": "topological"
        }, {
            "subspace": 2,
            "value_expression": "0.5",
            "degree": 2,
            "location_expression": "near(x[0],  0.)",
            "method": "topological"
        }, {
            "subspace": 2,
            "value_expression": "-0.5",
            "degree": 2,
            "location_expression": "near(x[0],  1.)",
            "method": "topological"
        }],
        start_time=0.,
        end_time=10.,
        time_step_size=1.e-3,
        stop_when_steady=True,
        steady_relative_tolerance=1.e-4,
        adaptive=False,
        adaptive_metric="all",
        adaptive_solver_tolerance=1.e-4,
        nlp_absolute_tolerance=1.e-8,
        nlp_relative_tolerance=1.e-8,
        nlp_max_iterations=50,
        restart=False,
        restart_filepath=""):
    """Run Phaseflow.
    
    Phaseflow is configured entirely through the arguments in this run() function.
    
    See the tests and examples for demonstrations of how to use this.
    """

    # Handle default function definitions.
    if m_B is None:

        def m_B(T, Ra, Pr, Re):

            return T * Ra / (Pr * Re**2)

    if ddT_m_B is None:

        def ddT_m_B(T, Ra, Pr, Re):

            return Ra / (Pr * Re**2)

    # Report arguments.
    phaseflow.helpers.print_once(
        "Running Phaseflow with the following arguments:")

    phaseflow.helpers.print_once(phaseflow.helpers.arguments())

    phaseflow.helpers.mkdir_p(output_dir)

    if fenics.MPI.rank(fenics.mpi_comm_world()) is 0:

        arguments_file = open(output_dir + "/arguments.txt", "w")

        arguments_file.write(str(phaseflow.helpers.arguments()))

        arguments_file.close()

    # Check if 1D/2D/3D.
    dimensionality = mesh.type().dim()

    phaseflow.helpers.print_once("Running " + str(dimensionality) +
                                 "D problem")

    # Initialize time.
    if restart:

        with h5py.File(restart_filepath, "r") as h5:

            time = h5["t"].value

            assert (abs(time - start_time) < TIME_EPS)

    else:

        time = start_time

    # Define the mixed finite element and the solution function space.
    W_ele = make_mixed_fe(mesh.ufl_cell())

    W = fenics.FunctionSpace(mesh, W_ele)

    # Set the initial values.
    if restart:

        mesh = fenics.Mesh()

        with fenics.HDF5File(mesh.mpi_comm(), restart_filepath, "r") as h5:

            h5.read(mesh, "mesh", True)

        W_ele = make_mixed_fe(mesh.ufl_cell())

        W = fenics.FunctionSpace(mesh, W_ele)

        w_n = fenics.Function(W)

        with fenics.HDF5File(mesh.mpi_comm(), restart_filepath, "r") as h5:

            h5.read(w_n, "w")

    else:

        w_n = fenics.interpolate(
            fenics.Expression(initial_values_expression, element=W_ele), W)

    # Organize the boundary conditions.
    bcs = []

    for item in boundary_conditions:

        bcs.append(
            fenics.DirichletBC(W.sub(item["subspace"]),
                               item["value_expression"],
                               item["location_expression"],
                               method=item["method"]))

    # Set the variational form.
    """Set local names for math operators to improve readability."""
    inner, dot, grad, div, sym = fenics.inner, fenics.dot, fenics.grad, fenics.div, fenics.sym
    """The linear, bilinear, and trilinear forms b, a, and c, follow the common notation 
    for applying the finite element method to the incompressible Navier-Stokes equations,
    e.g. from danaila2014newton and huerta2003fefluids.
    """
    def b(u, q):
        return -div(u) * q  # Divergence

    def D(u):

        return sym(grad(u))  # Symmetric part of velocity gradient

    def a(mu, u, v):

        return 2. * mu * inner(D(u), D(v))  # Stokes stress-strain

    def c(w, z, v):

        return dot(dot(grad(z), w), v)  # Convection of the velocity field

    dt = fenics.Constant(time_step_size)

    Re = fenics.Constant(reynolds_number)

    Ra = fenics.Constant(rayleigh_number)

    Pr = fenics.Constant(prandtl_number)

    Ste = fenics.Constant(stefan_number)

    C = fenics.Constant(heat_capacity)

    K = fenics.Constant(thermal_conductivity)

    g = fenics.Constant(gravity)

    def f_B(T):

        return m_B(T=T, Ra=Ra, Pr=Pr, Re=Re) * g  # Buoyancy force, $f = ma$

    gamma = fenics.Constant(penalty_parameter)

    T_f = fenics.Constant(temperature_of_fusion)

    r = fenics.Constant(regularization_smoothing_factor)

    def P(T):

        return 0.5 * (1. - fenics.tanh(
            (T_f - T) / r))  # Regularized phase field.

    mu_l = fenics.Constant(liquid_viscosity)

    mu_s = fenics.Constant(solid_viscosity)

    def mu(T):

        return mu_s + (mu_l - mu_s) * P(T)  # Variable viscosity.

    L = C / Ste  # Latent heat

    u_n, p_n, T_n = fenics.split(w_n)

    w_w = fenics.TrialFunction(W)

    u_w, p_w, T_w = fenics.split(w_w)

    v, q, phi = fenics.TestFunctions(W)

    w_k = fenics.Function(W)

    u_k, p_k, T_k = fenics.split(w_k)

    F = (b(u_k, q) - gamma * p_k * q + dot(u_k - u_n, v) / dt + c(u_k, u_k, v)
         + b(v, p_k) + a(mu(T_k), u_k, v) + dot(f_B(T_k), v) + C / dt *
         (T_k - T_n) * phi - dot(C * T_k * u_k, grad(phi)) +
         K / Pr * dot(grad(T_k), grad(phi)) + 1. / dt * L *
         (P(T_k) - P(T_n)) * phi) * fenics.dx

    def ddT_f_B(T):

        return ddT_m_B(T=T, Ra=Ra, Pr=Pr, Re=Re) * g

    def sech(theta):

        return 1. / fenics.cosh(theta)

    def dP(T):

        return sech((T_f - T) / r)**2 / (2. * r)

    def dmu(T):

        return (mu_l - mu_s) * dP(T)

    # Set the Jacobian (formally the Gateaux derivative) in variational form.
    JF = (b(u_w, q) - gamma * p_w * q + dot(u_w, v) / dt + c(u_k, u_w, v) +
          c(u_w, u_k, v) + b(v, p_w) + a(T_w * dmu(T_k), u_k, v) +
          a(mu(T_k), u_w, v) + dot(T_w * ddT_f_B(T_k), v) +
          C / dt * T_w * phi - dot(C * T_k * u_w, grad(phi)) -
          dot(C * T_w * u_k, grad(phi)) + K / Pr * dot(grad(T_w), grad(phi)) +
          1. / dt * L * T_w * dP(T_k) * phi) * fenics.dx

    # Set the functional metric for the error estimator for adaptive mesh refinement.
    """I haven't found a good way to make this flexible yet.
    Ideally the user would be able to write the metric, but this would require giving the user
    access to much data that phaseflow is currently hiding.
    """
    M = P(T_k) * fenics.dx

    if adaptive_metric == "phase_only":

        pass

    elif adaptive_metric == "all":

        M += T_k * fenics.dx

        for i in range(dimensionality):

            M += u_k[i] * fenics.dx

    else:

        assert (False)

    # Make the problem.
    problem = fenics.NonlinearVariationalProblem(F, w_k, bcs, JF)

    # Make the solvers.
    """ For the purposes of this project, it would be better to just always use the adaptive solver; but
    unfortunately the adaptive solver encounters nan's whenever evaluating the error for problems not 
    involving phase-change. So far my attempts at writing a MWE to reproduce the  issue have failed.
    """
    adaptive_solver = fenics.AdaptiveNonlinearVariationalSolver(problem, M)

    adaptive_solver.parameters["nonlinear_variational_solver"]["newton_solver"]["maximum_iterations"]\
        = nlp_max_iterations

    adaptive_solver.parameters["nonlinear_variational_solver"]["newton_solver"]["absolute_tolerance"]\
        = nlp_absolute_tolerance

    adaptive_solver.parameters["nonlinear_variational_solver"]["newton_solver"]["relative_tolerance"]\
        = nlp_relative_tolerance

    static_solver = fenics.NonlinearVariationalSolver(problem)

    static_solver.parameters["newton_solver"][
        "maximum_iterations"] = nlp_max_iterations

    static_solver.parameters["newton_solver"][
        "absolute_tolerance"] = nlp_absolute_tolerance

    static_solver.parameters["newton_solver"][
        "relative_tolerance"] = nlp_relative_tolerance

    # Open a context manager for the output file.
    with fenics.XDMFFile(output_dir + "/solution.xdmf") as solution_file:

        # Write the initial values.
        write_solution(solution_file, w_n, time)

        if start_time >= end_time - TIME_EPS:

            phaseflow.helpers.print_once(
                "Start time is already too close to end time. Only writing initial values."
            )

            return w_n, mesh

        # Solve each time step.
        progress = fenics.Progress("Time-stepping")

        fenics.set_log_level(fenics.PROGRESS)

        for it in range(1, MAX_TIME_STEPS):

            if (time > end_time - TIME_EPS):

                break

            if adaptive:

                adaptive_solver.solve(adaptive_solver_tolerance)

            else:

                static_solver.solve()

            time = start_time + it * time_step_size

            phaseflow.helpers.print_once("Reached time t = " + str(time))

            write_solution(solution_file, w_k, time)

            # Write checkpoint/restart files.
            restart_filepath = output_dir + "/restart_t" + str(time) + ".h5"

            with fenics.HDF5File(fenics.mpi_comm_world(), restart_filepath,
                                 "w") as h5:

                h5.write(mesh.leaf_node(), "mesh")

                h5.write(w_k.leaf_node(), "w")

            if fenics.MPI.rank(fenics.mpi_comm_world()) is 0:

                with h5py.File(restart_filepath, "r+") as h5:

                    h5.create_dataset("t", data=time)

            # Check for steady state.
            if stop_when_steady and steady(W, w_k, w_n,
                                           steady_relative_tolerance):

                phaseflow.helpers.print_once(
                    "Reached steady state at time t = " + str(time))

                break

            # Set initial values for next time step.
            w_n.leaf_node().vector()[:] = w_k.leaf_node().vector()

            # Report progress.
            progress.update(time / end_time)

            if time >= (end_time - fenics.dolfin.DOLFIN_EPS):

                phaseflow.helpers.print_once("Reached end time, t = " +
                                             str(end_time))

                break

    # Return the interpolant to sample inside of Python.
    w_k.rename("w", "state")

    return w_k, mesh