I have changed my code and found a most interesting behavior:
```from dolfin import *
import ufl_legacy as ufl
from petsc4py import PETSc
i,j,k,l = ufl.indices(4)
import elastic_tensors
########################################################################
# Mesh #
########################################################################
# Defines the degree of the interpolation polynomial
polynomial_degree = 1
element_type = 'P'
# Defines the mesh
mesh = UnitCubeMesh(16, 16, 24)#BoxMesh(Point(0,0,0), Point(1,1,1), 1,1,1)
# Defines the solution space using polynomial interpolation functions
V = VectorFunctionSpace(mesh, element_type, polynomial_degree)
########################################################################
# Newton-Raphson settings #
########################################################################
# Sets the final time
final_time = 1.0
# Sets the number of pseudotime points for loading
n_pseudoTimePoints = 1
# Sets the step in pseudo time
delta_t = final_time/n_pseudoTimePoints
# Defines the maximum number of iterations
n_maxIter = 40
# Defines a pseudotime variable to control the incremental process
t = 0.0
# Defines a function of the pseudo time that gives the displacement a-
# long the incremental process
def u_x(t):
return t*0.0
def u_y(t):
return t*0.0
def u_z(t):
return t*0.0
# Defines the norm of the residual to be used as stopping criterion to
# the Newton-Raphson method
residual_tol = 1E-4
########################################################################
# Dirichlet boundary conditions #
########################################################################
# Defines domain subregions
below = CompiledSubDomain("near(x[2], side) && on_boundary", side=0.0)
above = CompiledSubDomain("near(x[2], side) && on_boundary", side=1.0)
# Defines the expressions for the Dirichlet boundary conditions, i.e.,
# their values for the displacement field
expression_below = Expression(("0.0", "0.0", "0.0"), degree=
polynomial_degree)
def expression_above(t):
return Expression((str(u_x(t)), str(u_y(t)), str(u_z(t))), degree=
polynomial_degree)
# Defines two objects of Dirichlet boundary conditions for the displace-
# ment field
bc_DirichletBelowDisplacement = DirichletBC(V, expression_below, below)
def bc_DirichletAboveDisplacement(t):
return DirichletBC(V, expression_above(t), above)
def BCs_dirichletDisplacement(t):
return [bc_DirichletBelowDisplacement,
bc_DirichletAboveDisplacement(t)]
# The update direction must be null in the regions where Dirichlet boun-
# dary conditions are applied to the displacement field
#bc_DirichletAboveDirection = DirichletBC(V, Constant(("0.0", "0.0",
#"0.0")), above)
bc_DirichletBelowDirection = DirichletBC(V, Constant(("0.0", "0.0",
"0.0")), below)
#BCs_dirichletDirection = [bc_DirichletBelowDirection,
#bc_DirichletAboveDirection]
BCs_dirichletDirection = [bc_DirichletBelowDirection]
########################################################################
# Neumann boundary conditions #
########################################################################
# Creates a mesh function for the boundaries to apply Neumann boundary
# conditions. The dimension of the integration over the boundary is one
# less than the domain integration. Besides, the last argument, 0, is to
# initialize the mesh with this value; so it substitutes the command
# set_all(0)
boundaries = MeshFunction("size_t", mesh, mesh.topology().dim()-1, 0)
# Tags the labels for the subregions in the boundary
above.mark(boundaries, 1)
# Creates the surface area differential for the above facet
ds = Measure('ds', domain=mesh, subdomain_data=boundaries)
# Defines the boundary traction vector in the reference configuration
T = Constant((0.0, 0.0, 1000.0))
########################################################################
# Definition of solutions and variation #
########################################################################
# Defines the function for the update direction in the Newton-Raphson
# scheme
Delta_u = TrialFunction(V)
# Defines the variation
delta_u = TestFunction(V)
# Defines the displacement field
u = Function(V)
# Defines the body forces vector in the reference configuration
B = Constant((0.0, 0.0, 0.0))
########################################################################
# Tensors definition #
########################################################################
# Recovers the problem's dimension
dimension = Delta_u.geometric_dimension()
# Initializes the second order identity tensor
I = Identity(dimension)
# Calculates the deformation gradient tensor in terms of the solution u
F = grad(u)+I
# Calculates the right Cauchy-Green tensor
C = (F.T)*F
# Calculates the first two invariants of the right Cauchy-Green tensor
I1 = tr(C)
I2 = 0.5*((tr(C)**2)-tr(C*C))
# Calculates the jacobian of the kinematic mapping from the reference to
# the deformed configurations
J = det(F)
########################################################################
# Constitutive modelling #
########################################################################
# Saint Venant-Kirchhoff model
E = Constant(200E6)
nu = Constant(0.3)
mu = E/(2*(1+nu))
lmbda = (E*nu)/((1+nu)*(1-2*nu))
# Defines the Helmholtz energy function
Psi = ((0.5*mu*(1-I2))+(0.25*mu*((I1-1)**2))+(0.125*lmbda*((I1-3)**2)))
# Defines the derivatives of the energy function w.r.t. the invariants
# of the right Cauchy-Green tensor up to the second order
dPsi_dI1 = (0.5*mu*(I1-1))+(0.25*lmbda*(I1-3))
dPsi_dI2 = -0.5*mu
ddPsi_ddI1 = (0.5*mu)+(0.25*lmbda)
ddPsi_ddI2 = 0.0
ddPsi_dI1dI2 = 0.0
# Defines the k factor for the volumetric part of the second Piola-
# Kirchhoff stress tensor and the derivative of the former w.r.t. the
# jacobian
k_vol = 0.0
dkvol_dJ = 0.0
# Calculates the second Piola-Kirchhoff stress tensor
S = (2*(((dPsi_dI1+(I1*dPsi_dI2))*I)-(dPsi_dI2*C)+(k_vol*inv(C))))
# Defines the Cauchy stress tensor by the push-forward of the second Pi-
# ola-Kirchhoff stress tensor
sigma = (1/J)*F*S*F.T
# Evaluates the deviatoric parcel of the Cauchy stress tensor
dev_sigma = sigma-((1/3)*(tr(sigma))*I)
# Defines a function to evaluate the von Mises stress
von_mises = sqrt((3/2)*inner(dev_sigma,dev_sigma))
# Defines the fourth order elastic tensor
C_elastic = elastic_tensors.C_hyperNonIsochoric(C, I1, k_vol, J,
dkvol_dJ, dPsi_dI2, ddPsi_ddI1, ddPsi_ddI2, ddPsi_dI1dI2, dimension)
########################################################################
# Variational form and solution procedure #
########################################################################
# Defines the linearized variational form in terms of the update direc-
# tion, a==L. Note that a is constituted by the directional derivative
# of the variation of the power/work; while L is formed by the residual
# itself. This scheme states the Newton-Raphson method into a variational
# form
L = (-inner((F.T)*grad(delta_u),S)*dx+(dot(B,delta_u)*dx)+(dot(T,delta_u
)*ds(1)))
# Analytical derivative of the fourth order elastic tensor
a = ((inner((F.T)*grad(delta_u),elastic_tensors.double_contraction(
C_elastic,sym((F.T)*grad(Delta_u))))*dx))+(inner(grad(delta_u)*S,grad(
Delta_u))*dx)
# Numerical derivative of the residue entire. Use it to validate the
# analytical derivative only
#a = -derivative(L, u, Delta_u)
Delta_u = Function(V)
# Iterates through the pseudotime points
for j in range(n_pseudoTimePoints):
# Updates time
t += delta_t
print("\n#########################################################"+
"\n# Initiates the", j,"pseudotime point #\n"+
"#########################################################\n")
print("t =", t)
# Applies boundary conditions to the displacement field
BCs_dirichletU = BCs_dirichletDisplacement(t)
for CC in BCs_dirichletU:
CC.apply(u.vector())
# Iterates through the loop of the Newton-Raphson method
for i in range(n_maxIter):
print("\nInitiates iteration", i+1, "of the Newton-Raphson met"+
"hod\n")
# Solves the linearized variational form
Kt, R = assemble_system(a, L, BCs_dirichletDirection)
### PETSc solver
# Gets the tangent matrix
Kt_matrix = as_backend_type(Kt).mat()
vectorDelta_u, R_vector = Kt_matrix.getVecs()
# Gets the residual vector
R_vector.setValues(range(R.size()), R)
# Sets the Krylov linear solver. Uses conjugate gradient ('cg')
krylov_linearSolver = PETSc.KSP().create()
krylov_linearSolver.setType('cg')
krylov_linearSolver.setOperators(Kt_matrix)
krylov_linearSolver.solve(R_vector, vectorDelta_u)
# Evaluates the norm of the residual
norm_residual = norm(R, "l2")
print("Residual norm is", norm_residual)
# Verifies stop criterion
if norm_residual<residual_tol:
print("Residual criterion met at iteration", i)
break
# Updates the displacement field
u.vector()[:] = u.vector()[:]+vectorDelta_u
print("\n\n")
# Saves the solution into a VTK file
file = File("displacement_t_"+str(j)+"_.pvd")
file << u
# Calculates the von Mises stress
V = FunctionSpace(mesh, element_type, polynomial_degree)
von_mises = project(von_mises, V)
file_vonMises = File("von_misesStress.pvd")
file_vonMises << von_mises
After the comment “# Analytical derivative of the fourth order elastic tensor”, I put the linearized form. My Newton scheme converged very slowly and tried almost everything to fix it until, out of a stroke of luck, I decided to put the sym() for the F.T*grad(Delta_u) bit and it worked beautifully. Nevertheless, this behavior should not appear, for the fourth order tensor C has minor simmetry and, consequently, it operates with the symmetric part only of any second order tensor regardless. Therefore, I suspect it is a numerical error or some kind of a bug. Could you enlighten me with any idea for the origin of this behavior?