Custom Newton Solver problem with Dirichlet conditions

Hello everyone,

For some further applications, I need to access to the assembled system at each Newton iteration of a nonlinear hyperelasticity problem. Therefore I build my own Newton Solver using petsc4py:

def NewtonSolver(G, u, bcs, dG):
    tol = 1e-10
    error = 1.
    it = 0
    while error > tol and it < 10:
        # Assemble system
        dg, g = d.assemble_system(dG, G, bcs)
        
        # Manual BC
        #g = d.assemble(G)
        #dg = d.assemble(dG)
        
        # Apply BC
        #for bc in bcs:
            #bc.apply(g)
            #bc.apply(dg)

        error = g.norm('l2')
        print('Iteration:', str(it), '; Error: %3.3e' % error)
        if error < tol:
            break
        
        # PETSc solver
        dGp = d.as_backend_type(dg).mat()
        x, b = dGp.getVecs()
        b.setValues(range(g.size()), g)
        ksp = PETSc.KSP().create()
        ksp.setType('cg')
        ksp.setOperators(dGp)
        ksp.solve(b, x)
        
        
        # Update
        u.vector()[:] = u.vector()[:] - x
        it += 1

And I used it to solve a simple example of a square with zero displacement at the left boundary and a traction or a prescribed displacement at the right boundary (the following code is for the second example, but it can be easily changed to the first one):

#%% Mesh
E = 250.
nu = 0.3

mu = E/(2.0*(1.0 + nu))
lmbda = E*nu/((1.0 + nu)*(1.0 - 2.0*nu))

mesh = d.UnitSquareMesh(3,3)

class C2(d.SubDomain):
    def inside(self,x,on_boundary):
        return d.near(x[0],1)
class C4(d.SubDomain):
    def inside(self,x,on_boundary):
        return d.near(x[0],0)
    
c2 = C2()
c4 = C4()

#%% Function Spaces and Functions
V = d.VectorFunctionSpace(mesh, 'CG', 1)

u = d.Function(V)
du = d.TrialFunction(V)
delta_u = d.TestFunction(V)


#%% Boundary Conditions
boundary_markers = d.MeshFunction("size_t", mesh, mesh.topology().dim() - 1)

c2.mark(boundary_markers, 2)
c4.mark(boundary_markers, 4)

d.File('bc.pvd') << boundary_markers

u0 = d.Constant((0., 0.))
u1 = d.Constant((0.1, 0.))
bc4 =  d.DirichletBC(V, u0, boundary_markers, 4)
bc2 =  d.DirichletBC(V, u1, boundary_markers, 2)

ds = d.Measure("ds", domain = V.mesh(), subdomain_data=boundary_markers)

bcs = [bc2, bc4]

#%% NL problem
# Volume
dim = len(u)

I = d.Identity(dim)             # Identity tensor
F = I + d.grad(u)             # Deformation gradient
J  = d.det(F)
FiT = d.inv(F.T)

PK1 = mu*F + (lmbda*d.ln(J) - mu)*FiT

gradu = d.grad(delta_u)
T = d.Constant((30.,0.))
G = d.inner(PK1, gradu)*d.dx #- d.dot(T, delta_u)*ds(2)

dG = d.derivative(G, u)

I am comparing the results with the NonlinearVariationalSolver:

#%% Solve
u.rename('u','u')
# Fenics solver
problem = d.NonlinearVariationalProblem(G, u, bcs, dG)
solver = d.NonlinearVariationalSolver(problem)
solver.solve()
d.File('u0.pvd') << u

# Custom solver
NewtonSolver(G, u, bcs, dG)
d.File('u1.pvd') << u

The problem with traction at the right end works very well, although the residual norm for the last steps is not exactly the same as in the Fenics solver:

NonlinearVariationalSolver:

      Solving nonlinear variational problem.
        Newton iteration 0: r (abs) = 1.581e+01 (tol = 1.000e-10) r (rel) = 1.000e+00 (tol = 1.000e-09)
        Newton iteration 1: r (abs) = 1.563e+00 (tol = 1.000e-10) r (rel) = 9.883e-02 (tol = 1.000e-09)
        Newton iteration 2: r (abs) = 1.169e-02 (tol = 1.000e-10) r (rel) = 7.395e-04 (tol = 1.000e-09)
        Newton iteration 3: r (abs) = 1.215e-06 (tol = 1.000e-10) r (rel) = 7.685e-08 (tol = 1.000e-09)
        Newton iteration 4: r (abs) = 7.951e-14 (tol = 1.000e-10) r (rel) = 5.028e-15 (tol = 1.000e-09)
        Newton solver finished in 4 iterations and 4 linear solver iterations.

Custom Solver:

Iteration: 0 ; Error: 1.581e+01
Iteration: 1 ; Error: 1.563e+00
Iteration: 2 ; Error: 1.169e-02
Iteration: 3 ; Error: 1.235e-06
Iteration: 4 ; Error: 6.880e-12

Now, my problem is when I put a non-zero Dirichlet condition at the right end. The default fenics solver works perfect:

      Solving nonlinear variational problem.
        Newton iteration 0: r (abs) = 5.418e+01 (tol = 1.000e-10) r (rel) = 1.000e+00 (tol = 1.000e-09)
        Newton iteration 1: r (abs) = 4.638e-01 (tol = 1.000e-10) r (rel) = 8.560e-03 (tol = 1.000e-09)
        Newton iteration 2: r (abs) = 5.118e-04 (tol = 1.000e-10) r (rel) = 9.445e-06 (tol = 1.000e-09)
        Newton iteration 3: r (abs) = 7.032e-10 (tol = 1.000e-10) r (rel) = 1.298e-11 (tol = 1.000e-09)
        Newton solver finished in 3 iterations and 3 linear solver iterations.

But my custom solver fails:

Iteration: 0 ; Error: 5.418e+01
Iteration: 1 ; Error: 6.535e+01
Iteration: 2 ; Error: 8.172e+01
Iteration: 3 ; Error: 1.067e+02
Iteration: 4 ; Error: 1.474e+02
Iteration: 5 ; Error: 2.192e+02

I think my solver fails because I am missing something on how boundary conditions should be handled. In fact, in the update is something wrong because at each iteration the vector x is 0.1 at the prescribed dofs of the right end which makes that u gets bigger and bigger at each iteration. Before I was using

        g = d.assemble(G)
        dg = d.assemble(dG)
        
        # Apply BC
        for bc in bcs:
            bc.apply(g)
            bc.apply(dg)

instead of dg, g = d.assemble_system(dG, G, bcs), but that is even worst:

Iteration: 0 ; Error: 2.000e-01
Iteration: 1 ; Error: nan

So, I had two questions, the first one is if someone has an idea of what I am doing wrong and how to fix it.

The second one is why is different to use assemble_system instead of assemble and then apply in this case. I have been doing some digging in this last issue and assemble_system maintains the assembled matrix symmetric. Can someone explain to me how assemble_system does that?

Thank you in advance and sorry for the long post, but there were a lot of things.

Hello,
when using a Newton method with non-zero prescribed DirichletBC you should compute the first iterate with your bcs and for the next ones compute them with the same DirichletBC but with zero value, so that you will not accumulate the imposed value at each iteration.

Also, you might want to take a look here https://fenicsproject.org/qa/536/newton-method-programmed-manually/ for getting a correct residual with inhomogeneous boundary conditions

Or use this with a PETScSNESSolver where you can get access to the underlying SNES (which I assume is what you want).

Thanks for the answers! I finally solved by doing two things before entering the Newton loop:

  1. Initialize the vector u with the boundary conditions.
  2. Homogenize the boundary conditions.

With these changes, I get the same residuals than the NonlinearVariationalSolver (although to get the same values, I need to initialize u before using the default solver). The code looks like this:

def NewtonSolver(G, u, bcs, dG):
    tol = 1e-10
    error = 1.
    it = 0
    
    for bc in bcs:
        bc.apply(u.vector())
        bc.homogenize()
    
    while error > tol and it < 10:
        # Assemble
        dg, g = d.assemble_system(dG, G, bcs)
        
        # PETSc solver
        dGp = d.as_backend_type(dg).mat()
        x, b = dGp.getVecs()
        b.setValues(range(g.size()), g)
        ksp = PETSc.KSP().create()
        ksp.setType('cg')
        ksp.setOperators(dGp)
        ksp.solve(b, x)
        

        error = g.norm('l2')
        print('Iteration:', str(it), '; Error: %3.3e' % error)
        if error < tol:
            break
        
        # Update
        u.vector()[:] = u.vector()[:] - x
        it += 1

I still have the question about the difference between assemble_system and assemble and apply and how assemble_system diagonalizes the matrix.

@nate what I need is to sum certain values to the assembled tangent and residual matrix, so the example you mention does not work for me. I already achieve this using petsc4py. I don’t know what is the underlying SNES, could you explain that? Maybe there is a better way of doing what I am doing.

The underlying SNES is the underlying PETSc nonlinear solver.

Hi bleyerjthanks for your demo and the suggestion about the case of displacement control. Following your idea, my code works well.

Hi, I am also trying to achieve the same thing with boundary conditions as -

# 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)

bc_top = DirichletBC(V.sub(1), Constant(1.), boundaries, 1)
bc_bottom = DirichletBC(V, Constant((0., 0.)), boundaries, 2)

bc_u = [bc_bottom, bc_top]

And I modified the elasto-plastic code with 2 conditions stated by @bleyerj as -

Nitermax, tol = 200, 1e-6 # parameters of the Newton-Raphson procedure
Nincr = 20
load_steps = [1., 2., 3.]
results = np.zeros((Nincr+1, 2))

for (i,t) in enumerate(load_steps):
    A, Res = assemble_system(a, L, bc_u)     
    err0 = Res.norm("l2")
    err1 = err0
    print(err1/ err0)
    print("Increment:", str(i + 1))
    
    ## 1st iteration with nonzero DirichletBC conditions
    Du.interpolate(Constant((0, 0.1)))
    niter = 0
    while err1/err0 > tol and niter < Nitermax:
        solve(A, du.vector(), Res, "umfpack")
        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, L, bc_u)
        err1 = Res.norm("l2")  
        print(err1/err0)
        
        ## 2nd iteration onwards => zero DirichletBC conditions
        for bc in bc_u:
            bc.homogenize()
            
        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)

But still the newton solver doesn’t converge and give nan values from 3rd iteration. Any guidance would be highly appreciable.
Thanks.
PS: Kindly have a look at my post Iterative solver diverging to get a better understanding of what I’m trying to do.

1 Like

Hi, I am trying to achieve the same thing. Here is my code,
Newton solver not converging is one issue.
I have been trying to add some lines to calculate the vertical reaction force. Do you have any suggestions?


from dolfin import *
import numpy as np
parameters["form_compiler"]["representation"] = 'quadrature'
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

mesh = Mesh("mesh.xml")

# Boundary conditions
# normal AND
# ds, load:user expression
top = CompiledSubDomain("near(x[1], 0.5) && on_boundary")

# bottom boundary
bot = CompiledSubDomain("near(x[1], -0.5) && on_boundary")




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)

#load, defined using user expression, t
load = Expression("t", t = 0.0, degree=1)

# bc, bottom
bcbot= DirichletBC(V, Constant((0.0,0.0)), bot)

# load applied on top u_y
# boundary condition on top
bctop = DirichletBC(V.sub(1), load, top)

# grouping disp bc together
bc = [bcbot, bctop]
n = FacetNormal(mesh)



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

# XDMF : eXtensible Data Model and Format
# method to exchange scientific data
# XDMF will not handle quadrature space
# we define Functionspace P0
file_results = XDMFFile("plasticity_results.xdmf")
file_results.parameters["flush_output"] = True
file_results.parameters["functions_share_mesh"] = True
P0 = FunctionSpace(mesh, "DG", 0)
ST = TensorFunctionSpace(mesh, "DG", 0, shape =(3,3))
p_avg = Function(P0, name="Plastic strain")
sigma_t = Function(P0)
load_vertical = Function(P0, name="vertical reaction")

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, "mumps")
        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)
        nRes = Res.norm("l2")
        print("    Residual:", nRes)
        niter += 1
    u.assign(u+Du)
    p.assign(p+local_project(dp_, W0))
    sig_old.assign(sig)
    file_results.write(u, t)
    p_avg.assign(project(p, P0))
    sigma_tensor = as_3D_tensor(sig_)
    sigma_t.assign(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)

    results[i+1, :] = (t*u_r, t)


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

Hi,
Thank you for the reply. I have figured out the Newton solver issue. There were two issues.

  1. Homogenize the boundary conditions
  2. Take care of your mesh size and time step; it should work if you provide a reasonable size and time step.
    Thank you.
1 Like

I would suggest to post your method so that it can helps others who seek to do the same thing.

Here is my code; it is practically the same. The only thing you need to take care of is that if your mesh is too fine, try to give a time step that makes sense. I guess you need to satisfy CFL condition for convergence.

from dolfin import *
from matplotlib import pyplot
import numpy as np
parameters["form_compiler"]["representation"] = 'quadrature'
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

mesh = Mesh("mesh.xml")
#mesh = UnitSquareMesh(10,10)
plot(mesh, title="Finite element mesh")
pyplot.savefig('my_mesh.png')

# Boundary conditions
# normal AND
# ds, load:user expression
top = CompiledSubDomain("near(x[1], 0.5) && on_boundary")

# bottom boundary
bot = CompiledSubDomain("near(x[1], -0.5) && on_boundary")


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)
#load, defined using user expression, t
load = Expression("t", t = 0.0, degree=1)

# bc, bottom
bcbot= DirichletBC(V, Constant((0.0,0.0)), bot)

# load applied on top u_y
# boundary condition on top
bctop = DirichletBC(V.sub(1), load, top)

# grouping disp bc together
bc_u = [bcbot,bctop]

# MeshFunction: identify the boundaries of the domain
boundaries = MeshFunction("size_t", mesh, mesh.topology().dim() - 1)

# initialize boundaries before marking
boundaries.set_all(0)

# mark the top boundary
top.mark(boundaries, 1)

# to integrate over boundari 
ds = Measure("ds")(subdomain_data= boundaries)

# define normal to the surface/ boundaries
n = FacetNormal(mesh)




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)


# -----------------------------
# Constitutive relation update
# -----------------------------

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)

# --------------------------------------------
# Global problem and Newton-Raphson procedure
# --------------------------------------------

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)
Vs = FunctionSpace(mesh, "DG", 1)
p_avg = Function(P0, name="Plastic strain")

# Create a folder to store cumulative plastic strain
conc_f = File("./plastic_strain/p_avg.pvd")
fname = open('ForcevsDisp.txt', 'w')



u_r = 0.007
Nitermax, tol = 20, 1e-8  # parameters of the Newton-Raphson procedure
Nincr = 200
load_steps = np.linspace(0, 1.1, Nincr+1)[1:]**0.5
for (i, t) in enumerate(load_steps):
    load.t = t*u_r
    A, Res = assemble_system(a_Newton, res, bc_u)
    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_u)
        nRes = Res.norm("l2")
        print("    Residual:", nRes)
        for bc in bc_u:
            bc.homogenize()
        niter += 1
    u.assign(u+Du)
    p.assign(p+local_project(dp_, W0)) 
    sig_old.assign(sig)    
          
1 Like

Hi, violetus! sorry for the late reply. According to suggestion of bleyerj, I solve this problem. My code is same as anupama’s code (the main idea is same, but there is a little difference.). If you need the code, I can send you in e-mail.

Hello yanwei, thanks for the response. Yes please if you can e-mail me your code which addresses displacement controlled plasticity model, it would be great help.
E-mail id : ratansahu05102001@gmail.com

Hello yanwei,
Sorry, I noticed this reply after a long time. Could you please help me understand what is wrong with my code? If possible could you share your code?
Thanking you
Anupama

I guass the problem in this line "res = -inner(eps(u_), as_3D_tensor(sig))*dxm ".
you can replace ‘sig’ with ‘sig_old’.