Custom Newton Solver problem with Dirichlet conditions

Hi, sorry for the late reply, I tried to modify your code according to best of my knowledge to convert it into displacement controlled as stated in this post above and in the post Plasticity in fenics both time by @bleyerj but faced the same issue which is NewtonSolver converges first then at 3rd or 4th iteration gives nan value.
The modified code -

from dolfin import *
import numpy as np
parameters["form_compiler"]["representation"] = 'quadrature'
parameters["form_compiler"]["representation"] = 'tsfc'
import warnings
from ffc.quadrature.deprecation import QuadratureRepresentationDeprecationWarning
warnings.simplefilter("once", QuadratureRepresentationDeprecationWarning)

# material parameters
E = Constant(70e3)
nu = Constant(0.3)
lmbda = E*nu/(1+nu)/(1-2*nu)
mu = E/2./(1+nu)
sig0 = Constant(250.)  # yield strength
Et = E/100.  # tangent modulus
H = E*Et/(E-Et)  # hardening modulus

from mshr import *
domain= Rectangle(Point(0., 0.), Point(1., 1.)) 
mesh = generate_mesh(domain, 20)

deg_u = 2
deg_stress = 2
V = VectorFunctionSpace(mesh, "CG", deg_u)
We = VectorElement("Quadrature", mesh.ufl_cell(), degree=deg_stress, dim=4, quad_scheme='default')
W = FunctionSpace(mesh, We)
W0e = FiniteElement("Quadrature", mesh.ufl_cell(), degree=deg_stress, quad_scheme='default')
W0 = FunctionSpace(mesh, W0e)

# Boundary conditions
class top(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[1], 1., 1e-8)
class bot(SubDomain):
    def inside(self, x, on_boundary):
        return on_boundary and near(x[1], 0., 1e-8)

boundaries = MeshFunction("size_t", mesh, mesh.geometry().dim() - 1)

top().mark(boundaries, 1)
bot().mark(boundaries, 2)

load = Expression("t", t = 0.0, degree=1)

bcbot= DirichletBC(V, Constant((0.0,0.0)), boundaries, 2)
bctop = DirichletBC(V.sub(1), load, boundaries, 1)
bc = [bcbot, bctop]

n = FacetNormal(mesh)
ds = Measure("ds")(subdomain_data=boundaries)

sig = Function(W)
sig_old = Function(W)
n_elas = Function(W)
beta = Function(W0)
p = Function(W0, name="Cumulative plastic strain")
u = Function(V, name="Total displacement")
du = Function(V, name="Iteration correction")
Du = Function(V, name="Current increment")
v = TrialFunction(V)
u_ = TestFunction(V)

def eps(v):
    e = sym(grad(v))
    return as_tensor([[e[0, 0], e[0, 1], 0],
                      [e[0, 1], e[1, 1], 0],
                      [0, 0, 0]])
def sigma(eps_el):
    return lmbda*tr(eps_el)*Identity(3) + 2*mu*eps_el

def as_3D_tensor(X):
    return as_tensor([[X[0], X[3], 0],
                      [X[3], X[1], 0],
                      [0, 0, X[2]]])

ppos = lambda x: (x+abs(x))/2.
def proj_sig(deps, old_sig, old_p):
    sig_n = as_3D_tensor(old_sig)
    sig_elas = sig_n + sigma(deps)
    s = dev(sig_elas)
    sig_eq = sqrt(3/2.*inner(s, s))
    f_elas = sig_eq - sig0 - H*old_p
    dp = ppos(f_elas)/(3*mu+H)
    n_elas = s/sig_eq*ppos(f_elas)/f_elas
    beta = 3*mu*dp/sig_eq
    new_sig = sig_elas-beta*s
    return as_vector([new_sig[0, 0], new_sig[1, 1], new_sig[2, 2], new_sig[0, 1]]), \
           as_vector([n_elas[0, 0], n_elas[1, 1], n_elas[2, 2], n_elas[0, 1]]), \
           beta, dp

def sigma_tang(e):
    N_elas = as_3D_tensor(n_elas)
    return sigma(e) - 3*mu*(3*mu/(3*mu+H)-beta)*inner(N_elas, e)*N_elas-2*mu*beta*dev(e)

metadata = {"quadrature_degree": deg_stress, "quadrature_scheme": "default"}
dxm = dx(metadata=metadata)

a_Newton = inner(eps(v), sigma_tang(eps(u_)))*dxm
res = -inner(eps(u_), as_3D_tensor(sig))*dxm 

def local_project(v, V, u=None):
    dv = TrialFunction(V)
    v_ = TestFunction(V)
    a_proj = inner(dv, v_)*dxm
    b_proj = inner(v, v_)*dxm
    solver = LocalSolver(a_proj, b_proj)
    solver.factorize()
    if u is None:
        u = Function(V)
        solver.solve_local_rhs(u)
        return u
    else:
        solver.solve_local_rhs(u)
        return

P0 = FunctionSpace(mesh, "DG", 0)
p_avg = Function(P0, name="Plastic strain")

u_r = 0.05
Nitermax, tol = 5, 1e-8  # parameters of the Newton-Raphson procedure
Nincr = 20
load_steps = np.linspace(0, 1.1, Nincr+1)[1:]**0.5
results = np.zeros((Nincr+1, 2))
for (i, t) in enumerate(load_steps):
    load.t = t*u_r
    A, Res = assemble_system(a_Newton, res, bc)
    nRes0 = Res.norm("l2")
    nRes = nRes0
    Du.interpolate(Constant((0, 0)))
    print("Increment:", str(i+1))
    niter = 0
    while nRes/nRes0 > tol and niter < Nitermax:
        solve(A, du.vector(), Res, "lu")
        Du.assign(Du+du)
        deps = eps(Du)
        sig_, n_elas_, beta_, dp_ = proj_sig(deps, sig_old, p)
        local_project(sig_, W, sig)
        local_project(n_elas_, W, n_elas)
        local_project(beta_, W0, beta)
        A, Res = assemble_system(a_Newton, res, bc)
        for bc_i in bc:
            bc_i.homogenize()
            
        nRes = Res.norm("l2")
        print("    Residual:", nRes)
        niter += 1
    u.assign(u+Du)
    p.assign(p+local_project(dp_, W0))
    sig_old.assign(sig)
    p_avg.assign(project(p, P0))
    results[i+1, :] = (u(0.5, 1.)[1], t)

Gives the output -

Increment: 1
    Residual: 8193.78486479144
    Residual: 12.584295265311695
    Residual: 27.48110701737934
    Residual: 2.258960298045684
    Residual: 0.7402582538956958
Increment: 2
    Residual: 0.8915496012667151
    Residual: 0.44893033099816865
    Residual: 0.07483532946744006
    Residual: 0.009844688153043523
    Residual: 0.0007199674903077572
Increment: 3
    Residual: 0.0002757595008993816
    Residual: 2.2939673222581952e-05
    Residual: 8.785003486230311e-07
    Residual: 6.257779286847474e-09
    Residual: 8.034401067089978e-14
Increment: 4
    Residual: 7.378512786780972e-14
    Residual: nan
Increment: 5
Increment: 6
Increment: 7
Increment: 8
Increment: 9
Increment: 10
Increment: 11
Increment: 12
Increment: 13
Increment: 14
Increment: 15
Increment: 16
Increment: 17
Increment: 18
Increment: 19
Increment: 20

And one more thing I am not sure what you were trying to do in these lines of code -

ST = TensorFunctionSpace(mesh, "DG", 0, shape =(3,3))
sigma_t = Function(P0)
load_vertical = Function(P0, name="vertical reaction")

sigma_tensor = as_3D_tensor(sig_)
sigma_t.assign(local_project(sig_[1], P0))
 normal = as_vector((n[0],n[1], 0))
Traction = dot(sigma_t, normal)
f_y = Traction[1] *ds(1)
file_results.write(str(assemble(f_y)), t)

Because running it yields the error -

Dot product requires non-scalar arguments, got arguments with ranks 0 and 1.
---------------------------------------------------------------------------
UFLException                              Traceback (most recent call last)
/tmp/ipykernel_170/2372490712.py in <module>
     41     sigma_t.assign(local_project(sig_[1], P0))
     42     normal = as_vector((n[0],n[1], 0))
---> 43     Traction = dot(sigma_t, normal)
     44     f_y = Traction[1] *ds(1)
     45     file_results.write(str(assemble(f_y)), t)

/usr/lib/python3/dist-packages/ufl/operators.py in dot(a, b)
    174     if a.ufl_shape == () and b.ufl_shape == ():
    175         return a * b
--> 176     return Dot(a, b)
    177 
    178 

/usr/lib/python3/dist-packages/ufl/tensoralgebra.py in __new__(cls, a, b)
    188         # Checks
    189         if not ((ar >= 1 and br >= 1) or scalar):
--> 190             error("Dot product requires non-scalar arguments, "
    191                   "got arguments with ranks %d and %d." % (ar, br))
    192         if not (scalar or ash[-1] == bsh[0]):

/usr/lib/python3/dist-packages/ufl/log.py in error(self, *message)
    156         "Write error message and raise an exception."
    157         self._log.error(*message)
--> 158         raise self._exception_type(self._format_raw(*message))
    159 
    160     def begin(self, *message):

UFLException: Dot product requires non-scalar arguments, got arguments with ranks 0 and 1.

which makes sense because sigma_t is scalar.

1 Like