Hi Nate. Thanks for your comment. Here is a minimal working example code:
import numpy as np
import ufl
from dolfinx import log, plot
from dolfinx.fem import Function, FunctionSpace
from dolfinx.fem.petsc import NonlinearProblem
from dolfinx.io import XDMFFile
from dolfinx.mesh import create_rectangle, CellType
from dolfinx.nls.petsc import NewtonSolver
from ufl import dx, grad, inner, div
from mpi4py import MPI
from petsc4py import PETSc
import pyvista as pv
log.set_output_file("log.txt")
kappa = 0.02
eta = 1.5
dt = 3.0e-02# time step
theta = 0.5 # time stepping family, e.g. theta=1 -> backward Euler, theta=0.5 -> Crank-Nicholson
msh = create_rectangle(comm=MPI.COMM_WORLD,
points=((0.0, 0.0), (30.0, 30.0)), n=(80, 80),
cell_type=CellType.triangle,)
P1 = ufl.FiniteElement("Lagrange", msh.ufl_cell(), 1)
P2 = ufl.VectorElement("Lagrange", msh.ufl_cell(), 2)
Mixed_Element = ufl.MixedElement([P1, P1, P1, P2])
ME = FunctionSpace(msh, Mixed_Element)
q, v, m, n = ufl.TestFunctions(ME)
u = Function(ME) # current solution
u0 = Function(ME) # solution from previous converged step
# Split mixed functions
c, mu, p, a = ufl.split(u)
c0, mu0, p0, a0 = ufl.split(u0)
# Zero u
u.x.array[:] = 0.0
# Interpolate initial condition
u.sub(0).interpolate(lambda x: 0.50 + 0.02 * (0.5 - np.random.rand(x.shape[1])))
u.x.scatter_forward()
c = ufl.variable(c)
y = 0.1
f = y * (-2 * (c-1/2)**2 + 4 * (c-1/2)**4)
dfdc = ufl.diff(f, c)
mu_mid = (1.0 - theta) * mu0 + theta * mu
# Weak statement of the equations
F0 = inner(c, q) * dx - inner(c0, q) * dx + dt * inner(grad(mu_mid), grad(q)) * dx
F1 = inner(mu, v) * dx - inner(dfdc, v) * dx - kappa * inner(grad(c), grad(v)) * dx
F3 = -eta * inner(grad(a), grad(n)) * dx - inner(grad(p), n) * dx - inner(c, inner(grad(mu), n)) * dx
F4 = inner(div(a), m) * dx
F = F0 + F1 + F3 + F4
problem = NonlinearProblem(F, u)
solver = NewtonSolver(MPI.COMM_WORLD, problem)
solver.convergence_criterion = "incremental"
solver.rtol = 1e+3
solver.atol = 1e+3
solver.max_it = 1000
# We can customize the linear solver used inside the NewtonSolver by
# modifying the PETSc options
ksp = solver.krylov_solver
opts = PETSc.Options()
option_prefix = ksp.getOptionsPrefix()
opts[f"{option_prefix}ksp_type"] = "preonly"
opts[f"{option_prefix}pc_type"] = "lu"
opts[f"{option_prefix}pc_factor_mat_solver_type"] = "mumps"
opts[f"{option_prefix}ksp_monitor"] = None
ksp.setFromOptions()
# Output file
file = XDMFFile(MPI.COMM_WORLD, "demo_ch/output.xdmf", "w")
file.write_mesh(msh)
# Step in time
t = 0.0
T = 400 * dt
# vector
V0, dofs = ME.sub(0).collapse()
# Prepare viewer for plotting the solution during the computation
topology, cell_types, x = plot.create_vtk_mesh(V0)
grid = pv.UnstructuredGrid(topology, cell_types, x)
# Set output data
grid.point_data["c"] = u.x.array[dofs].real
grid.set_active_scalars("c")
c = u.sub(0)
u0.x.array[:] = u.x.array
time_data = []
while (t < T):
t += dt
r = solver.solve(u)
print(f"Step {int(t/dt)}: num iterations: {r[0]}")
u0.x.array[:] = u.x.array
file.write_function(c, t)
time_data.append(u.x.array[dofs].real.copy())
# Create a plotter object
plotter = pv.Plotter(off_screen=True)
# Add the initial mesh to the plotter
grid.point_data["c"] = time_data[0]
plotter.add_mesh(grid, scalars="c", clim=[0, 1])
# Set the view to 2D
plotter.view_xy()
# Open a mp4 file to store the animation
plotter.open_movie("Model-H.mp4")
# Iterate over each time step
for i in range(len(time_data)):
# Update the scalar data
grid.point_data["c"] = time_data[i]
plotter.add_text(f"Time: {((i + 1)*dt):.4f}", name='time-label')
plotter.write_frame()
plotter.close()