I would like to implement a arc-length solver.
For didactic reasons, I thought I would start reproducing a basic Newton-Raphson solver.
Say I have this setting, a 2D hyperelastic problem
import numpy as np
import ufl
import dolfinx
from petsc4py import PETSc
from mpi4py import MPI
from dolfinx import fem, mesh, plot
from dolfinx.plot import create_vtk_mesh
import pyvista
pyvista.set_jupyter_backend("panel")
L = 5.; H = 1;
cell_size = 0.1;
nx = 10
ny = 5
domain = mesh.create_rectangle(MPI.COMM_WORLD, [np.array([0,0]), np.array([L, H])],
[nx, ny], mesh.CellType.quadrilateral)
ndim = domain.geometry.dim
V = fem.VectorFunctionSpace(domain, ("CG", 2))
def left(x):
return np.isclose(x[0], 0)
def right(x):
return np.isclose(x[0], L)
fdim = domain.topology.dim -1
left_facets = mesh.locate_entities_boundary(domain, fdim, left)
right_facets = mesh.locate_entities_boundary(domain, fdim, right)
# Concatenate and sort the arrays based on facet indices. Left facets marked with 1, right facets with two
marked_facets = np.hstack([left_facets, right_facets])
marked_values = np.hstack([np.full(len(left_facets), 1, dtype=np.int32), np.full(len(right_facets), 2, dtype=np.int32)])
sorted_facets = np.argsort(marked_facets)
facet_tag = mesh.meshtags(domain, fdim, marked_facets[sorted_facets], marked_values[sorted_facets])
u_bc = np.array((0,) * domain.geometry.dim, dtype=PETSc.ScalarType)
left_dofs = fem.locate_dofs_topological(V, facet_tag.dim, facet_tag.indices[facet_tag.values==1])
bcs = [fem.dirichletbc(u_bc, left_dofs, V)]
B = fem.Constant(domain, PETSc.ScalarType((0, 0)))
T = fem.Constant(domain, PETSc.ScalarType((0, 0)))
v = ufl.TestFunction(V)
u = fem.Function(V)
# Spatial dimension
d = len(u)
# Identity tensor
I = ufl.variable(ufl.Identity(d))
# Deformation gradient
F = ufl.variable(I + ufl.grad(u))
# Right Cauchy-Green tensor
C = ufl.variable(F.T * F)
# Invariants of deformation tensors
Ic = ufl.variable(ufl.tr(C))
J = ufl.variable(ufl.det(F))
# Elasticity parameters
E = PETSc.ScalarType(1.0e4)
nu = PETSc.ScalarType(0.3)
mu = fem.Constant(domain, E/(2*(1 + nu)))
lmbda = fem.Constant(domain, E*nu/((1 + nu)*(1 - 2*nu)))
# Stored strain energy density (compressible neo-Hookean model)
psi = (mu / 2) * (Ic - 3) - mu * ufl.ln(J) + (lmbda / 2) * (ufl.ln(J))**2
# Stress
# Hyper-elasticity
P = ufl.diff(psi, F)
metadata = {"quadrature_degree": 4}
ds = ufl.Measure('ds', domain=domain, subdomain_data=facet_tag, metadata=metadata)
dx = ufl.Measure("dx", domain=domain, metadata=metadata)
# Define form F (we want to find u such that F(u) = 0)
F = ufl.inner(ufl.grad(v), P)*dx - ufl.inner(v, B)*dx - ufl.inner(v, T)*ds(2)
problem = fem.petsc.NonlinearProblem(F, u, bcs)
from dolfinx import nls
solver = nls.petsc.NewtonSolver(domain.comm, problem)
# Set Newton solver options
solver.atol = 1e-8
solver.rtol = 1e-8
solver.convergence_criterion = "incremental"
but now I want to write my own basic NR solver, to work on a local mesh.
To show where I get stuck, one load step is sufficient.
So I thought I would go on like this
from scipy import linalg
from scipy.linalg import norm
MAX_ITER = 10
MAX_ERROR = 10**-5
## function to get the Jacobian as numpy array
def ufl_form_2_numpy(F,u):
J = ufl.derivative(F, u)
z = dolfinx.fem.petsc.assemble_matrix(dolfinx.fem.form(J),bcs=bcs)
z.assemble()
z.convert("dense")
return z.getDenseArray()
iter = 0
disp = 0
err = 10**5
tval0 = -1
while (err > MAX_ERROR) and iter <= MAX_ITER:
print(5*"*"+f"iteration number: {iter}" + 3*"*"+ f"the error norm is {err}")
# set the traction to the vaue at the end of the first load step
T.value[1] = 1 * tval0
# compute RHS and LHS from forms directly
my_RHS = dolfinx.fem.petsc.assemble_vector(dolfinx.fem.form(F)).getArray()
my_Jacobian = ufl_form_2_numpy(F,u)
# solve linearised system
x = linalg.solve(my_Jacobian, -my_RHS)
# update displacement
disp =+ x
err = norm(my_RHS)
iter +=1
### For this to work, I need to update the solution field u, for the new forms to be computed correctly.
I was unable to see how I could update the femFunction
u, so that the forms are computed correctly based on the updated displacement for new iterations.
Thank you for your help and patience on such menial matters. Any other advice is of course greatly welcome. The approach above might be stone-age like, but it would make implementation of an arc-length procedure trivial, as all the necessary quantities are comfily handed out ready to be used.