Non linear problem for time dependent problem

Hello ,
I am working on a time-dependent nonlinear problem for the first time, and I’m having some difficulty assembling and solving it. The problem involves nonlinear heat generation, requiring iterative methods within each time step.

Could you please provide some guidance or resources on:

  1. Assembling and solving nonlinear time-dependent problems.
    
  2. Strategies for ensuring convergence in such problems.
    
  3. Any relevant tutorials or documentation for time-dependent nonlinear problems in FEniCSx.
    

I would greatly appreciate any help or links you could share!

Thank you for your time

It is not so different from a steady state nonlinear problem or a linear time dependent problem. Here is a 1D heat equation with phase change very inspired from this tutorial with a custom NR solver:

import numpy as np
from scipy.optimize import fsolve
from scipy.special import erf, erfc
from dolfinx import fem, mesh, io
import dolfinx.fem.petsc
import ufl
from mpi4py import MPI
from petsc4py import PETSc
plot = False
writepos = False
if plot:
    import matplotlib.pyplot as plt

comm = MPI.COMM_WORLD
rank = comm.Get_rank()

def transcendental_fun(x):
    return np.sqrt(np.pi)*x - \
        (stl / np.exp(np.pow(x,2)) / erf(x) ) + \
        (sts / np.exp(np.pow(x,2)) / erfc(x) )

def locate_phase_change_interface(t):
    return 2*lamma*np.sqrt(diffusivity*t);

def exact_sol(x,t):
    interface_pos = locate_phase_change_interface(t);
    sol = np.zeros(x.shape[1],dtype=x.dtype)
    liquid_indices = x[0]<=interface_pos
    solid_indices  = x[0]>interface_pos
    sol[liquid_indices] = T_l - (T_l - T_m)*erf(x[0,liquid_indices]/(2*np.sqrt(diffusivity*t)))/erf(lamma)
    sol[solid_indices] = T_s + (T_m - T_s)*erfc(x[0,solid_indices]/(2*np.sqrt(diffusivity*t)))/erfc(lamma)
    return sol

## PARAMETERS
x_left = 0.0
x_right = 0.1
cross_section = 1.0
rho = 4510*cross_section
c_p = 520
k   = 16*cross_section
diffusivity = k / rho / c_p
l = 325000
T_m = 1750
T_l = 2000
T_s = 1500
T_0 = T_s
stl = c_p*(T_l - T_m) / l
sts = c_p*(T_m - T_s) / l
lamma = fsolve(transcendental_fun, 0.388150542167233)
time = 0.0
S   = 40
max_nr_iters = 20
dt = 1.0
num_time_steps = 20

Tsl_av = (T_s + T_l)/2.0;
cte = S*2/(T_l - T_s);
# Liquid fraction and derivatives
fl_ufl   = lambda tem : 1/2*(ufl.tanh(cte*(tem - Tsl_av))+1.0)
flp_ufl  = lambda tem : cte/2.0*(1 - ufl.tanh(cte*(tem - Tsl_av))**2)
flpp_ufl = lambda tem : -cte**2 * ufl.tanh(cte*(tem-Tsl_av)) / (ufl.cosh(cte*(tem-Tsl_av)))**2
nelems = 1000

def main():
    domain = mesh.create_interval(comm,nelems,(x_left,x_right))
    ufl_coeffs = dict()
    for var in ["rho", "c_p", "k", "l", "dt"]:
        ufl_coeffs[var] = fem.Constant(domain,float(globals()[var]))
    tdim = 1
    V = fem.functionspace(domain, ("Lagrange", 1))
    f_exact = fem.Function(V, name="exact")
    u_np1 = fem.Function(V, name="uh")# Solution
    u_n = fem.Function(V, name="uh_n")# Previous time iter
    u_i = fem.Function(V, name="uh_i")# Previous non-linear iter
    du  = fem.Function(V, name="delta_u")# Non-linear increment

    # IC
    u_np1.x.array[:] = T_s

    # Dirichlet BC
    bfacets = mesh.locate_entities_boundary(domain, tdim-1, lambda x: np.full(x.shape[1], True, dtype=bool))
    bc = fem.dirichletbc(f_exact, fem.locate_dofs_topological(V, tdim-1, bfacets))

    # Forms
    (delta_u, v) = (ufl.TrialFunction(V), ufl.TestFunction(V))
    dx = ufl.dx(metadata={"quadrature_degree": 5})
    F = ufl_coeffs["rho"]*ufl_coeffs["c_p"]/ufl_coeffs["dt"]*u_n*v*dx
    F += ufl_coeffs["rho"]*ufl_coeffs["l"]*flp_ufl(u_np1)/ufl_coeffs["dt"]*u_n*v*dx
    F += -ufl_coeffs["rho"]*ufl_coeffs["c_p"]/ufl_coeffs["dt"]*u_np1*v*dx
    F += -ufl_coeffs["rho"]*ufl_coeffs["l"]*flp_ufl(u_np1)/ufl_coeffs["dt"]*u_np1*v*dx
    F += -ufl_coeffs["k"]*ufl.dot(ufl.grad(u_np1),ufl.grad(v))*dx
    J = ufl.derivative(F,u_np1)
    mresidual = fem.form(-F)# minus residual
    jacobian = fem.form(J)

    A = fem.petsc.create_matrix(jacobian)
    L = fem.petsc.create_vector(mresidual)
    solver = PETSc.KSP().create(domain.comm)
    solver.setOperators(A)

    # TIME-LOOP
    time = 0.0
    for titer in range(num_time_steps):
        time += dt
        u_n.x.array[:] = u_np1.x.array[:]
        lamma_exact_sol = lambda x : exact_sol(x,time)
        f_exact.interpolate(lamma_exact_sol)

        nriter = 0
        while nriter < max_nr_iters:
            nriter += 1
            u_i.x.array[:] = u_np1.x.array[:]
            with L.localForm() as loc_L:
                loc_L.set(0)
            A.zeroEntries()
            fem.petsc.assemble_matrix(A,jacobian, bcs=[bc])
            A.assemble()
            fem.petsc.assemble_vector(L, mresidual)

            # Compute b - J(u_D-u_(i-1))
            fem.petsc.apply_lifting(L, [jacobian], [[bc]], x0=[u_i.x.petsc_vec])
            L.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
            fem.petsc.set_bc(L, [bc], u_i.x.petsc_vec)

            # Solve
            solver.solve(L, du.x.petsc_vec)
            du.x.scatter_forward()

            u_np1.x.array[:] += du.x.array[:]

            correction_norm = du.x.petsc_vec.norm(0)
            print(f"Time-step {titer}, NR iter {nriter}: correction norm {correction_norm}")

            if correction_norm < 1e-10:
                break

        if plot:
            # PLOT
            plt.figure(figsize=(8, 6))
            plt.plot(domain.geometry.x[:,0], f_exact.x.array, label='exact_sol', color='r')
            plt.plot(domain.geometry.x[:,0], u_np1.x.array, label='uh', color='b')
            plt.title('T field')
            plt.xlabel('x')
            plt.ylabel('T')
            plt.legend()
            plt.savefig(f"time_iter{titer}.png",dpi=300)
            plt.close()

        if writepos:
            with io.VTKFile(domain.comm, "post/demo_phase_change.pvd", "w") as pf:
                pf.write_function([u_np1, f_exact], t=time)

if __name__=="__main__":
    main()

This runs with the latest dolfinx version. There is a plot flag at the beginning of the script that will write PNGs if set to true. It will only work properly in serial. There is a writepos flag that will write pvtu files if set to true.

Regarding 2, I am no expert but I would recommend relying on PETSc non-linear solvers (SNES) or the dolfinx NonLinearProblem class.