Derivative wrt a tensor (Rank issue)

Hello,

I try to derive a scalar function wrt a tensor funtion, typically this one
image
I defined the psi_plus function:

def psi_plus(eps, lamda, mu):
    delta = tr(eps)**2 - 4 * det(eps)
    sqrt_delta = conditional(gt(delta, 0), sqrt(tr(eps)**2 - 4 * det(eps)), 0)
    eigen_value_1 = (tr(eps) + sqrt_delta) / 2
    eigen_value_2 = (tr(eps) - sqrt_delta) / 2
    tr_epsilon_plus = conditional(gt(tr(eps), 0.), tr(eps), 0.)
    eigen_value_1_plus = conditional(gt(eigen_value_1, 0.), eigen_value_1, 0.)
    eigen_value_2_plus = conditional(gt(eigen_value_2, 0.), eigen_value_2, 0.)
    return lamda / 2 * tr_epsilon_plus**2 + mu * (eigen_value_1_plus**2 + eigen_value_2_plus**2)

then I defined the derivative :

def cauchy_stress_plus(eps, psi_plus):
    eps = variable(eps)
    energy_plus = psi_plus(eps)
    sigma_plus = diff(energy_plus, eps)
    return sigma_plus

But when I ran this simple code :

V     = VectorFunctionSpace(mesh,'Lagrange', 1)
u_ =   Function(V), du = TrialFunction(V)
epsilon = epsilon(u_)
psi_plus = partial(psi_plus, lmbda, mu)
sigma_plus = cauchy_stress_plus(epsilon, psi_plus)
f = inner( sigma, epsilon(v) ) * dx + dot( Constant((0., 0.)), v ) * ds
J = derivative(f,  u_, du)

I got this error :

Trace of tensor with rank != 2 is undefined.
Traceback (most recent call last):
  File "/scratchm/lmersel/FEniCS/PFmodel/PF_fenics_Lamia/CubeLinElas/MainCubeLinElas_Miehe.py", line 110, in <module>
    sigma_minus = cauchy_stress_minus(u_, psi_minus)
  File "/scratchm/lmersel/FEniCS/PFmodel/PF_fenics_Lamia/CubeLinElas/Function_utils.py", line 69, in cauchy_stress_minus
    energy_minus = psi_minus(eps)
  File "/scratchm/lmersel/FEniCS/PFmodel/PF_fenics_Lamia/CubeLinElas/Function_utils.py", line 47, in psi_minus_linear_elasticity_model_C
    delta = tr(eps)**2 - 4 * det(eps)
  File "/stck/lmersel/anaconda/envs/fenics/lib/python3.9/site-packages/ufl/operators.py", line 254, in tr
    return Trace(A)
  File "/stck/lmersel/anaconda/envs/fenics/lib/python3.9/site-packages/ufl/tensoralgebra.py", line 276, in __new__
    error("Trace of tensor with rank != 2 is undefined.")
  File "/stck/lmersel/anaconda/envs/fenics/lib/python3.9/site-packages/ufl/log.py", line 172, in error
    raise self._exception_type(self._format_raw(*message))
ufl.log.UFLException: Trace of tensor with rank != 2 is undefined.

I check the rank of epsilon (ufl.rank(epsilon) = 2) but I don’t know what I did wrong in here.

Thanks for the help!

You are missing several key definitions in the code above for anyone to be able to reproduce a minimal example and help you. Please modify your post and make sure that the code posted can be copy-pasted and in turn executed and reproduce your error message.

I am sorry, this is the complete code. The functions are inspired from the open source code implemented by Tianju Xue.

from fenics import *
import numpy as np
import matplotlib.pyplot as plt
from functools import partial
import ufl

import sys
import time

# ==================================
# Util function
# ===================================

def epsilon(u):
    strain = as_tensor([[ u[0].dx(0), (1./2.)*(u[0].dx(1) + u[1].dx(0))  ],
                      [ (1./2.)*(u[0].dx(1) + u[1].dx(0)) , u[1].dx(1) ]])
    return strain

# ----------------------------------------------------------------
# Model : Miehe 
#

def psi_plus_elasticity(eps, lamda, mu):
    delta = tr(eps)**2 - 4 * det(eps)
    sqrt_delta = conditional(gt(delta, 0), sqrt(tr(eps)**2 - 4 * det(eps)), 0)
    eigen_value_1 = (tr(eps) + sqrt_delta) / 2
    eigen_value_2 = (tr(eps) - sqrt_delta) / 2
    tr_epsilon_plus = conditional(gt(tr(eps), 0.), tr(eps), 0.)
    eigen_value_1_plus = conditional(gt(eigen_value_1, 0.), eigen_value_1, 0.)
    eigen_value_2_plus = conditional(gt(eigen_value_2, 0.), eigen_value_2, 0.)
    return lamda / 2 * tr_epsilon_plus**2 + mu * (eigen_value_1_plus**2 + eigen_value_2_plus**2)


def psi_minus_elasticity(eps, lamda, mu):
    #eps = epsilon(u)
    delta = tr(eps)**2 - 4 * det(eps)
    sqrt_delta = conditional(gt(delta, 0), sqrt(tr(eps)**2 - 4 * det(eps)), 0)
    eigen_value_1 = (tr(eps) + sqrt_delta) / 2
    eigen_value_2 = (tr(eps) - sqrt_delta) / 2
    tr_epsilon_minus = conditional(lt(tr(eps), 0.), tr(eps), 0.)
    eigen_value_1_minus = conditional(lt(eigen_value_1, 0.), eigen_value_1, 0.)
    eigen_value_2_minus = conditional(lt(eigen_value_2, 0.), eigen_value_2, 0.)
    return lamda / 2 * tr_epsilon_minus**2 + mu * (eigen_value_1_minus**2 + eigen_value_2_minus**2)


# ---------------------------------------------------------------- 
# Cauchy stress
def cauchy_stress_plus(eps, psi_plus):
    eps = variable(eps)
    energy_plus = psi_plus(eps)
    sigma_plus = diff(energy_plus, eps)
    return sigma_plus

    
def cauchy_stress_minus(eps, psi_minus):
    eps = variable(eps)
    energy_minus = psi_minus(eps)
    sigma_minus = diff(energy_minus, eps)
    return sigma_minus

# ==================================
# Linear elastic problem - pure traction test
# ===================================

ERROR = 40
set_log_level(ERROR) # log level
PROGRESS = 16
#set_log_level(PROGRESS) # log level

parameters.parse()   # read paramaters from command line
# set some dolfin specific parameters
parameters["form_compiler"]["optimize"]     = True
parameters["form_compiler"]["cpp_optimize"] = True
parameters["form_compiler"]["representation"] = "uflacs" # quadrature is deprecated


# ------------------
# folder name : save file Create VTK file for saving solution
# ------------------
savedir        = "results_CubeLinElas_Miehe_XUE_nx3/"
vtkfileU       = File(savedir+'displacement.pvd')
vtkfileSigma   = File(savedir+'stress.pvd')


E, nu     = 210000, 0.3 #[MPa]
G         = E / (2.0*(1.0 + nu)) # [MPa]
lmbda     = E * nu / ((1.0 + nu)* (1.0 - 2.0*nu))
K0, Gc    = lmbda+2./3.*G, 2.7

T            = 1.           # final time
num_steps    = 10    # number of time steps
time_step    = np.linspace(0, T, num_steps)


n = 5
mesh = UnitSquareMesh(n, n,'crossed')
Nnode = len(mesh.coordinates())
coord = mesh.coordinates()
ndim = mesh.topology().dim()

V         = VectorFunctionSpace(mesh,'Lagrange', 1)
Tens      = TensorFunctionSpace(mesh, 'Lagrange', 1)


Disp       = Expression( 't', t = 0.0, degree = 1 )
# boolean boundary function
boundary_bottom  = 'near(x[1], 0)'
boundary_up      = 'near(x[1], 1)'
boundary_left    = 'near(x[0], 0)'
boundary_right   = 'near(x[0], 1)'

bottom     = CompiledSubDomain(boundary_bottom)
up         = CompiledSubDomain(boundary_up)
left       = CompiledSubDomain(boundary_left)
right      = CompiledSubDomain(boundary_right)
boundaries = MeshFunction("size_t", mesh,1)
boundaries.set_all(0)
bottom.mark(boundaries, 1) # mark left as 1
up.mark(boundaries, 2) # mark right as 2
left.mark(boundaries, 3) # mark right as 3
right.mark(boundaries, 4) # mark right as 4
ds = Measure("ds",subdomain_data=boundaries) # left: ds(1), right: ds(2), crack : ds(4), ds(0) all the edges


bcu_bottom = DirichletBC( V.sub(1) , Constant((0.)), boundary_bottom)
bcu_right  = DirichletBC( V.sub(0) , Constant(0.), boundary_right)
bcu_left   = DirichletBC( V.sub(0) , Constant(0.), boundary_left)
bcu_up     = DirichletBC( V.sub(1) , Disp, boundary_up )
bcu        = [bcu_bottom, bcu_up, bcu_right, bcu_left]


du,v,u_        = TrialFunction(V), TestFunction(V), Function(V)
sig_funct      = Function(Tens, name="Stress")
u_old = Function(V)

"""
=========================================================
                        Define Variational problem
=========================================================
"""

epsilon = epsilon(u_)
psi_plus = partial(psi_plus_linear_elasticity, lmbda, G)
psi_minus = partial(psi_minus_linear_elasticity, lmbda, G)
sigma_minus = cauchy_stress_minus(epsilon, psi_minus)
sigma_plus = cauchy_stress_plus(epsilon, psi_plus)
sigma = sigma_minus + sigma_plus
  
f = inner( sigma, epsilon(v) ) * dx + dot( Constant((0., 0.)), v ) * ds
J = derivative(f,  u_, du)

#u_guess = interpolate(Expression( ('0.', 'x[0]*x[0]'),  degree = 2 ),V)
#u_.assign(u_guess)

height = 1.
Uref = 2.
#x, y = x[0],x[1]
def nonzero_initial_guess():
    x1 = x[0]
    x2 = x[1]
    u1 = Constant(0.)
    u2 = Uref * x2 / height
    u_initial = as_vector([u1, u2])
    return u_initial

u_initial = nonzero_initial_guess()
u_.assign(project(u_initial, V))

problem = NonlinearVariationalProblem(f, u_, bcu, J)
solver  = NonlinearVariationalSolver(problem)
newton_prm = solver.parameters['newton_solver']
newton_prm['relative_tolerance'] = 1e-8
newton_prm['absolute_tolerance'] = 1e-8
newton_prm['maximum_iterations'] = 100
newton_prm['krylov_solver']['nonzero_initial_guess'] = True


it = 0
for t in time_step[1:]:

    it +=1
    Disp.t = t
    print('\n -> Time iteration : %d, Time [s] : %.3g, Ux_given [mm] :  %.3g mm ' %(it, t, Disp.t))

    #---------------- RESOLUTION
    iter_u = solver.solve()
    

    #---------------- PostProcess
    sig_funct.assign(project(sigma, Tens))
    elastic_energy         = 0.5*inner(sigma, epsilon(u_))*dx
    elastic_energy_density = project(0.5*inner(sigma, epsilon(u_)), V_alpha)
    elastic_energy_value   = assemble(elastic_energy)

    Fn = assemble(sigma[0,0]*ds(2))
    Ft = assemble(sigma[1,1]*ds(2))
 

    vtkfileU      << u_
    vtkfileSigma  << sig_funct

    print( "Eel_density_max: %.8g" % (elastic_energy_density.vector().max()))
    print("(Fn, Ft): (%.8g,%.8g)"%(Fn,Ft))
    print("\nElastic energies : (%g)"%(elastic_energy_value)) 
    print("-------------------------------------------------------------")