Issue with the implementation of the three network Model (nonlinear viscoelasticity)

Dear FEniCS community,

I’m currently working on the implementation of the Three Network Model for nonlinear viscoelasticity.

My code is structured following two similar FEniCS examples:

  1. Linear Viscoelasticity – COMET-FEniCS documentation
  2. Discussion on a viscoelastic problem – FEniCS Discourse

I’ve formulated a three-field variational problem to solve for the displacement field u and the two internal deformation gradient fields FvA and FvB.

However, I suspect there’s a problem with how I’m initializing or updating FvA and FvB, because when I print their values for debugging, both appear as zero matrices throughout the domain.

Has anyone encountered a similar issue or could suggest how to correctly initialize and evolve internal tensor fields like FvA and FvB in a mixed function space?

Thanks in advance for your help!
Here is the code:

from dolfinx import *
from matplotlib import pyplot as plt
import meshio
from dolfinx.io import gmshio
import pyvista
import numpy as np
from mpi4py import MPI
import math
import os
import sys
from dolfinx.fem.petsc import NonlinearProblem
from dolfinx.nls.petsc import NewtonSolver
import pyvista
from dolfinx import mesh, fem, plot, default_scalar_type, log
from basix.ufl import element, mixed_element
import ufl
from dolfinx.fem import dirichletbc, locate_dofs_topological, Function
from dolfinx.mesh import locate_entities_boundary
length = 10
width = 4
depth = 2
num_ele_along_depth = 4
size_ele = depth/num_ele_along_depth
n = [int(length/size_ele), int(width/size_ele), int(depth/size_ele)]
domain = mesh.create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([length, width, depth])], n, cell_type=mesh.CellType.hexahedron)
tdim = domain.topology.dim # Topological dimension
dim = domain.topology.dim - 1 # Facets are one dimension lower than the domain
######### Define the function space #########
#############################################
element_u = element("CG", domain.ufl_cell().cellname(), 2, shape=(tdim,)) # Displacement field 
element_FvA = element("DG", domain.ufl_cell().cellname(), 0, shape=(tdim,tdim)) # FvA field
element_FvB = element("DG", domain.ufl_cell().cellname(), 0, shape=(tdim,tdim)) # FvB field
mixedelement = mixed_element([element_u, element_FvA, element_FvB]) # Mixed element for the function space
M = fem.functionspace(domain, mixedelement)

V, _ = M.sub(0).collapse() #Displacement
print("Number of DoFs in displacement:", V.dofmap.index_map.size_global)  #DoFs 9328
V1, V1_to_M1 = M.sub(1).collapse() #Fv field A
print("Number of DoFs in Fv field A:", V1.dofmap.index_map.size_global)
V2, V2_to_M1 = M.sub(2).collapse() #Fv field B
print("Number of DoFs in Fv field B:", V2.dofmap.index_map.size_global)

# Coordinate X = 0
def boundary_x0(x):
    return np.isclose(x[0], 0.0)

facets_x0 = locate_entities_boundary(domain, dim, boundary_x0)
dofs_x0 = locate_dofs_topological((M.sub(0), V), dim, facets_x0)
zero_displacement = Function(V)
zero_displacement.x.array[:] = 0.0
bc_x0 = dirichletbc(zero_displacement, dofs_x0, M.sub(0))
bcs = [bc_x0]

####### VARIATIONAL FORMULATION #######
#######################################
v = ufl.TestFunctions(M)
δu, δFvA, δFvB = v
m = fem.Function(M)
u, FvA, FvB = ufl.split(m)
print(f"u: {u.ufl_shape}")
print(f"FvA: {FvA.ufl_shape}")
print(f"FvB: {FvB.ufl_shape}")
# Saving the function at the previous time step
m_old = fem.Function(M)  # to store all previous fields
u_old, Fv_oldA, Fv_oldB= ufl.split(m_old)  # to access individual components
print(f"u_old: {u_old.ufl_shape}")
print(f"FvA_old: {Fv_oldA.ufl_shape}")
print(f"FvB_old: {Fv_oldB.ufl_shape}")

# ####### VARIATIONAL FORMULATION #######
# #######################################
#Traction vector
T = fem.Constant(domain, default_scalar_type((0, 0, 0)))
#Body force vector
B = fem.Constant(domain, default_scalar_type((0, -1, 0)))

#Material parameters
Bulk_Modulus=default_scalar_type(1000) #Considered as compressible materials
K=fem.Constant(domain, Bulk_Modulus)
#Material parameters for network A
MUA= fem.Constant(domain, default_scalar_type(15.58)) #Shear modulus network A
LAMBDAL= fem.Constant(domain, default_scalar_type(6.28)) #Lambda lock -- the same for all networks
TAUHATA= fem.Constant(domain, default_scalar_type(11.57)) #tauhat
MA=fem.Constant(domain, default_scalar_type(9.51))
#Material parameters for network B
MUBI=fem.Constant(domain, default_scalar_type(47.25)) #Initial Shear modulus network B
MUBF=fem.Constant(domain, default_scalar_type(1.32)) #Final Shear modulus network B
BETA=fem.Constant(domain, default_scalar_type(4.19)) #BETA
TAUHATB=fem.Constant(domain, default_scalar_type(39.56))
MB=fem.Constant(domain, default_scalar_type(14.67))
#Material parameters for network C
MUC=fem.Constant(domain, default_scalar_type(6.27))
State_value_n= fem.functionspace(domain, ("Lagrange", 1))
State_value_d= fem.functionspace(domain, ("Lagrange", 1))
V_viss = fem.functionspace(domain, ("Lagrange", 1))
def inv_Langevin(ED_chainstretch):
    # Padè approximation for the inverse Langevin function
    ED_limited = ufl.min_value(ED_chainstretch, 0.99)
    L_inv = (ED_limited*(3-ED_limited**2))/(1-ED_limited**2)
    chainstretch_expr = fem.Expression(ED_limited, V_viss.element.interpolation_points())
    chainstretch_fn = fem.Function(V_viss)
    chainstretch_fn.interpolate(chainstretch_expr)
    print("Max chain stretch:", np.max(np.abs(chainstretch_fn.x.array)))
    return L_inv

# Cauchy stress tensor for the Eight Chain model
def cauchy_stress_EC(FF_local, shear_modulus, lambdalock, K_local):
    # Identity tensor
    I_local = ufl.variable(ufl.Identity(3))
    C_local = ufl.variable(ufl.transpose(FF_local) * FF_local)
    J_local = ufl.max_value(ufl.variable(ufl.det(FF_local)), 1e-8)
    b_local=ufl.variable(FF_local*ufl.transpose(FF_local))
    b_local_dist = ufl.variable((J_local**(-2/3))*b_local)
    Ib1_local = ufl.variable(ufl.tr(b_local))
    Ib1_dist_local = ufl.variable(ufl.tr(b_local_dist))
    # Invariants of deformation tensors
    Ic1_local = ufl.variable(ufl.tr(C_local))
    Ic1_dist_local = ufl.variable((J_local**(-2/3))*ufl.tr(C_local))
    eff_dist_chainstrech= ufl.max_value(ufl.variable(ufl.sqrt(Ic1_dist_local/3)), 1e-8)
    chainstretch_expr2 = fem.Expression(eff_dist_chainstrech, V_viss.element.interpolation_points())
    chainstretch_fn2 = fem.Function(V_viss)
    chainstretch_fn2.interpolate(chainstretch_expr2)
    print("Max chain stretch 2:", np.max(np.abs(chainstretch_fn2.x.array)))
    ###########################################
    # Calcola l'argomento di inv_Langevin
    langevin_arg_n = ufl.variable(eff_dist_chainstrech / lambdalock)
    # Calcola anche il valore della funzione inv_Langevin sull'argomento
    langevin_val_n = ufl.variable(inv_Langevin(langevin_arg_n))
    # Interpola entrambi in funzioni FEM per il debug numerico
    langevin_arg_expr_n = fem.Expression(langevin_arg_n, State_value_n.element.interpolation_points(), domain.comm)
    langevin_val_expr_n = fem.Expression(langevin_val_n, State_value_n.element.interpolation_points(), domain.comm)
    langevin_arg_fn_n = fem.Function(State_value_n)
    langevin_val_fn_n = fem.Function(State_value_n)
    langevin_arg_fn_n.interpolate(langevin_arg_expr_n)
    langevin_val_fn_n.interpolate(langevin_val_expr_n)
    # Stampa i valori estremi
    print("[DEBUG] Langevin argument NUMERATORE (eff_dist_chainstrech / lambdalock):")
    print("  min =", np.min(langevin_arg_fn_n.x.array), "max =", np.max(langevin_arg_fn_n.x.array))
    print("[DEBUG] NUMERATORE inv_Langevin(argument):")
    print("  min =", np.min(langevin_val_fn_n.x.array), "max =", np.max(langevin_val_fn_n.x.array))
    #################################
    #################################
    # Calcola l'argomento di inv_Langevin
    langevin_arg_d = ufl.variable(1/ lambdalock)
    # Calcola anche il valore della funzione inv_Langevin sull'argomento
    langevin_val_d = ufl.variable(inv_Langevin(langevin_arg_d))
    # Interpola entrambi in funzioni FEM per il debug numerico
    langevin_arg_expr_d = fem.Expression(langevin_arg_d, State_value_d.element.interpolation_points(), domain.comm)
    langevin_val_expr_d = fem.Expression(langevin_val_d, State_value_d.element.interpolation_points(), domain.comm)
    langevin_arg_fn_d = fem.Function(State_value_d)
    langevin_val_fn_d = fem.Function(State_value_d)
    langevin_arg_fn_d.interpolate(langevin_arg_expr_d)
    langevin_val_fn_d.interpolate(langevin_val_expr_d)
    # Stampa i valori estremi
    print("[DEBUG] Langevin argument DENOMINATORE (eff_dist_chainstrech / lambdalock):")
    print("  min =", np.min(langevin_arg_fn_d.x.array), "max =", np.max(langevin_arg_fn_d.x.array))
    print("[DEBUG] DENOMINATORE inv_Langevin(argument):")
    print("  min =", np.min(langevin_val_fn_d.x.array), "max =", np.max(langevin_val_fn_d.x.array))
    #################################
    numeratore = inv_Langevin(eff_dist_chainstrech/lambdalock)
    denominatore = (inv_Langevin(1/lambdalock))
    part1=(shear_modulus/(J_local*eff_dist_chainstrech))*((numeratore)/(denominatore))
    part2=part1*(b_local_dist-((1/3)*Ib1_dist_local*I_local))
    return part2 + K_local*(J_local-1)*I_local

State_value = fem.functionspace(domain, ("Lagrange", 1))
State_tensor= fem.functionspace(domain, ("Lagrange", 1, (tdim,tdim)))
State_vector = fem.functionspace(domain, ("Lagrange", 1, (tdim,)))

#Inizializzazione FvA
initial_stateFviA = ufl.Identity(3)
initial_stateFviA_exp = fem.Expression(initial_stateFviA, V1.element.interpolation_points(), domain.comm)
initial_stateFviA_fn = fem.Function(V1)
initial_stateFviA_fn.interpolate(initial_stateFviA_exp)
m_old.x.array[V1_to_M1] = initial_stateFviA_fn.x.array
#m.x.array[V1_to_M1] = initial_stateFviA_fn.x.array
#Inizializzazione FvA
initial_stateFviB = ufl.Identity(3) # V2: spazio per FvB
initial_stateFviB_exp = fem.Expression(initial_stateFviB, V2.element.interpolation_points(), domain.comm)
initial_stateFviB_fn = fem.Function(V2)
initial_stateFviB_fn.interpolate(initial_stateFviB_exp)
m_old.x.array[V2_to_M1] = initial_stateFviB_fn.x.array
print(np.isnan(m_old.x.array).any())
#m.x.array[V2_to_M1] = initial_stateFviB_fn.x.array
state_muB_in = MUBI #Initial shear modulus of network B
state_muB_current = fem.Constant(domain, default_scalar_type(0)) #Current state variable for the shear modulus of network B 
tau_C=fem.Constant(domain, default_scalar_type(0.001))

freq = 0.05 # Time step size
deltaT = freq #time step size

d = len(u) # Spatial dimension
I = ufl.variable(ufl.Identity(d)) # Identity tensor
F_new = ufl.variable(I + ufl.grad(u))
##F=F_el*F_vi
FelasticA_new = ufl.variable(F_new*ufl.inv(FvA))
FelasticB_new = ufl.variable(F_new*ufl.inv(FvB))
Cv_Anew = ufl.variable(ufl.transpose(FvA)*(FvA))
Cv_Aold = ufl.variable(ufl.transpose(Fv_oldA)*(Fv_oldA)) #CvA at the previous time step
Cv_Bnew = ufl.variable(ufl.transpose(FvB)*(FvB))
Cv_Bold = ufl.variable(ufl.transpose(Fv_oldB)*(Fv_oldB)) #CvB at the previous time step
#Network A
Cauchy_stress_A = cauchy_stress_EC(FelasticA_new, MUA, LAMBDAL, K)
dev_stressA =(Cauchy_stress_A-((1/3)*ufl.tr(Cauchy_stress_A)*I))
eps = 1e-8
tau_A = ufl.sqrt(ufl.tr(ufl.dot(dev_stressA,dev_stressA))) #Frobenius norm
hydr_pressure_A = -ufl.tr(Cauchy_stress_A)/3
RampA = (hydr_pressure_A+ufl.sqrt(hydr_pressure_A**2))/2
argA= ufl.conditional(ufl.gt(tau_A/(TAUHATA + RampA) - tau_C, 0.0),
                     tau_A/(TAUHATA + RampA) - tau_C,
                     0.0)
deltagammaA =(argA)**MA #Delta gamma A
f_A = 2*ufl.transpose(Fv_oldA)*deltagammaA*(dev_stressA/tau_A) * Fv_oldA
#Network B
state_muB_current = - BETA*(state_muB_in-MUBF)*(deltagammaA)*deltaT + state_muB_in
Cauchy_stress_B= cauchy_stress_EC(FelasticB_new, state_muB_current, LAMBDAL, K)
dev_stressB=(Cauchy_stress_B-((1/3)*ufl.tr(Cauchy_stress_B)*I))
tau_B=ufl.sqrt(ufl.tr(ufl.dot(dev_stressB,dev_stressB))) #Frobenius norm
hydr_pressure_B=-ufl.tr(Cauchy_stress_B)/3 #Ho levato qui il meno
RampB=(hydr_pressure_B+ufl.sqrt(hydr_pressure_A**2))/2
argB= ufl.conditional(ufl.gt(tau_B/(TAUHATB + RampB) - tau_C, 0.0),
                        tau_B/(TAUHATB + RampB) - tau_C,
                        0.0)
deltagammaB =(argB)**MB #Delta gamma B
f_B = 2*ufl.transpose(Fv_oldB)*deltagammaB*(dev_stressB/tau_B) * Fv_oldB
# Network C
CS_NetworkC = cauchy_stress_EC(F_new, MUC, LAMBDAL, K)
deltaCvA=(Cv_Anew-Cv_Aold)
deltaCvB=(Cv_Bnew-Cv_Bold)

def δe(test_function_local):
    return ufl.sym(ufl.grad(test_function_local)) #Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)

metadata = {"quadrature_degree": 4}
ds = ufl.Measure('ds', domain=domain, metadata=metadata)
dx = ufl.Measure("dx", domain=domain, metadata=metadata)  # Elementarily volume

F_int_A= ufl.inner(Cauchy_stress_A, δe(δu))*dx + ufl.inner(deltaCvA,δFvA)*dx - deltaT*ufl.inner(f_A, δFvA)*dx
F_int_B= ufl.inner(Cauchy_stress_B, δe(δu))*dx + ufl.inner(deltaCvB,δFvB)*dx - deltaT*ufl.inner(f_B, δFvB)*dx
F_int_C= ufl.inner(CS_NetworkC, δe(δu))*dx
F_ext= - ufl.inner(δu, B) * dx - ufl.inner(δu, T) * ds
Variational= F_int_A + F_int_C + F_ext

log.set_log_level(log.LogLevel.INFO)

def bc_vec_moving(x, rate, time):
    x = np.asarray(x)
    dim = x.shape[0]
    num_points = x.shape[1]
    disp = np.zeros((dim, num_points), dtype=PETSc.ScalarType)
    disp[0, :] = rate * time  # Spostamento lungo x
    return disp

def boundary_xL(x):
    return np.isclose(x[0], length)

facets_xL = locate_entities_boundary(domain, dim, boundary_xL)
dofs_xL = locate_dofs_topological((M.sub(0), V), dim, facets_xL)
######## VISUALIZATION ########
#Function space for visualization
V_vis = fem.functionspace(domain, ("Lagrange", 1, (3,)))
TS = fem.functionspace(domain, ("DG", 0,(3, 3))) #True stress tensor
print(f"Number of DoFs in True Stress Tensor: {TS.dofmap.index_map.size_global}")
uh_vis_list = []
true_stress_vis_list =[]

with io.XDMFFile(domain.comm, "Viscoplasticity/results_1.xdmf", "w") as outfile:
    outfile.write_mesh(domain)  # write mesh once
    t_end = 0.20 # Total simulation time
    t = 0.10
    step = 1
    list_bc_m = [] 
    while t <= t_end:
        print(f"Time step {step}, time: {t:.2f}")
        
        ####
        #### DEBUG NUMERICO u
        arg_expr_n = fem.Expression(u, State_vector.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_vector)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] u:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print("u new at first cell:", arg_fn_n.x.array[:3].reshape(3))
        ####
        #### DEBUG NUMERICO F_new
        arg_expr_n = fem.Expression(F_new, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] F_new:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print("F new tensor at first cell:", arg_fn_n.x.array[:9].reshape(3, 3))
        ####
        #### DEBUG NUMERICO det(F_new)
        detF_expr = fem.Expression(ufl.det(F_new), State_value.element.interpolation_points(), domain.comm)
        detF_fn = fem.Function(State_value)
        detF_fn.interpolate(detF_expr)
        print(f"[t = {t:.2f}] det(F): min={np.min(detF_fn.x.array)}, max={np.max(detF_fn.x.array)}")
        ####
        #### DEBUG NUMERICO FelasticA_new
        arg_expr_n = fem.Expression(FelasticA_new, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] FelasticA_new:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print("Felastic new tensor at first cell:", arg_fn_n.x.array[:9].reshape(3, 3))
        ####
        #### DEBUG NUMERICO FvA
        arg_expr_n = fem.Expression(FvA, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] FvA:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print("FvA tensor at first cell:", arg_fn_n.x.array[:9].reshape(3, 3))
        ####
        #### DEBUG NUMERICO FvA_old
        arg_expr_n = fem.Expression(Fv_oldA, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] FvA_old:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print("FvA_old tensor at first cell:", arg_fn_n.x.array[:9].reshape(3, 3))
        ####
        #### DEBUG NUMERICO Cv_Anew
        arg_expr_n = fem.Expression(Cv_Anew, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] Cv_Anew :")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print("Cv_Anew tensor at first cell:", arg_fn_n.x.array[:9].reshape(3, 3))
        ####
        #### DEBUG NUMERICO Cv_Aold
        arg_expr_n = fem.Expression(Cv_Aold, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] Cv_Aold:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print("Cv_Aold tensor at first cell:", arg_fn_n.x.array[:9].reshape(3, 3))
        ####
        #### DEBUG NUMERICO Cauchy_stress_A
        arg_expr_n = fem.Expression(Cauchy_stress_A, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] Cauchy_stress_A:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print("Cauchy_stress_A tensor at first cell:", arg_fn_n.x.array[:9].reshape(3, 3))
        ####
        #### DEBUG NUMERICO tau_A
        arg_expr_n = fem.Expression(tau_A, State_value.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_value)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] tau_A:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        ####
        #### DEBUG NUMERICO hydr_pressure_A
        arg_expr_n = fem.Expression(hydr_pressure_A, State_value.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_value)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] hydr_pressure_A:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        ####
        #### DEBUG NUMERICO RampA
        arg_expr_n = fem.Expression(RampA, State_value.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_value)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] RampA:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        ####
        #### DEBUG NUMERICO argA
        arg_expr_n = fem.Expression(argA, State_value.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_value)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] argA:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        ####
        #### DEBUG NUMERICO deltagammaA
        arg_expr_n = fem.Expression(deltagammaA, State_value.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_value)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] deltagammaA:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        print(ufl.shape(deltagammaA))
        ####
        #### DEBUG NUMERICO f_A
        arg_expr_n2 = fem.Expression(f_A, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n2 = fem.Function(State_tensor)
        arg_fn_n2.interpolate(arg_expr_n2)
        print("[DEBUG] f_A:")
        print("  min =", np.min(arg_fn_n2.x.array), "max =", np.max(arg_fn_n2.x.array))
        ####
        #### DEBUG NUMERICO CS_NetworkC
        arg_expr_n = fem.Expression(CS_NetworkC, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] CS_NetworkC:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        CS_TNM = Cauchy_stress_A + Cauchy_stress_B + CS_NetworkC
        #####
        # DEBUG NUMERICO deltaCvA
        arg_expr_n = fem.Expression(deltaCvA, State_tensor.element.interpolation_points(), domain.comm)
        arg_fn_n = fem.Function(State_tensor)
        arg_fn_n.interpolate(arg_expr_n)
        print("[DEBUG] deltaCvA:")
        print("  min =", np.min(arg_fn_n.x.array), "max =", np.max(arg_fn_n.x.array))
        
        ##RISOLUZIONE
        problem = NonlinearProblem(Variational, m, bcs)
        solver = NewtonSolver(domain.comm, problem)
        # Set Newton solver options
        solver.atol = 1e-8 #Sets the absolute tolerance (stopping criterion when the residual is small)
        solver.rtol = 1e-8 #Sets the relative tolerance (stopping criterion based on relative change)
        solver.max_it = 500 #Sets the maximum number of iterations
        solver.convergence_criterion = "incremental" #Sets the convergence criterion to incremental
        num_its, converged = solver.solve(m) 
        #num_its stores the number of Newton iterations required for convergence.
        #converged is True if the solver successfully found a solution, otherwise False
        if not converged:
            print(f"Solver failed at step {step}. Iterations: {num_its}")
        assert (converged) #Test if a condition returns True, if not, it raises an AssertionError
        print(f"Time step {step}, Number of iterations {num_its}, Displacement: {displacement_rate*t}")
        m.x.scatter_forward()
        # Update the state variables
        #initial_stateFviA = StateFviA_new
        initial_stateFviA_expr = fem.Expression(FvA, State_tensor.element.interpolation_points(), domain.comm)
        initialstateFviA = fem.Function(State_tensor)
        initialstateFviA.interpolate(initial_stateFviA_expr)
        Fv_oldA.x.array[:] = initialstateFviA.x.array[:]
        assert not np.isnan(initial_stateFviA.x.array).any(), "NaN in StateFviA"
        #initial_stateFviB = StateFviB_new
        initial_stateFviB_expr = fem.Expression(FvB, State_tensor.element.interpolation_points(), domain.comm)
        initialstateFviB = fem.Function(State_tensor)
        initialstateFviB.interpolate(initial_stateFviB_expr)
        Fv_oldB.x.array[:] = initialstateFviB.x.array[:]
        assert not np.isnan(initial_stateFviB.x.array).any(), "NaN in StateFviB"
        #state_muB_in = Stateshear_modulusB_T
        state_muB_in_expr = fem.Expression(state_muB_current, State_value.element.interpolation_points(), domain.comm)
        state_muB_in = fem.Function(State_value)
        state_muB_in.interpolate(state_muB_in_expr)
        assert not np.isnan(state_muB_in.x.array).any(), "NaN in muB"
        #
        TrueStress_expr = fem.Expression(CS_TNM, TS.element.interpolation_points())
        stress_tensor_fn = fem.Function(TS)
        stress_tensor_fn.interpolate(TrueStress_expr)
        print("True stress tensor at first cell:", stress_tensor_fn.x.array[:9].reshape(3, 3))
        true_stress_copy = fem.Function(TS)
        true_stress_copy.x.array[:] = stress_tensor_fn.x.array[:]
        true_stress_vis_list.append(true_stress_copy)
        cs_array = stress_tensor_fn.x.array[:]

        assert not np.isnan(cs_array).any(), "NaN in CS_TNM"
        max_stress = np.max(np.abs(cs_array))
        if max_stress > 1e6:
            print(f"⚠️ Warning: High stress detected: {max_stress:.2e}")
        print("Saved true stress solution")

        # Update the previous solution
        # Save a copy of the solution to keep history
        u, FvA, FvB = m.split()
        uh = m.sub(0).collapse()
        uh_vis = fem.Function(V_vis)
        uh_vis.interpolate(uh)
        uh_copy = fem.Function(V_vis)
        uh_copy.x.array[:] = uh_vis.x.array[:]  # copia dei dati
        uh_vis_list.append(uh_copy)
        print("Saved displacement solution")
        print("Max displacement norm:", np.max(np.abs(uh_vis.x.array)))

        time_step = step
        t += deltaT
        step += 1
        # WRITE XDMFFile
        
        uh.name = "Displacement"
        stress_tensor_fn.name = "TrueStress"
        
        outfile.write_function(uh_vis, time_step)
        outfile.write_function(stress_tensor_fn, time_step)
    outfile.close()
print("Simulation completed")

Here is the output from my debug prints:

[DEBUG] u:
  min = 0.0 max = 0.0
u new at first cell: [0. 0. 0.]
[DEBUG] F_new:
  min = 0.0 max = 1.0
F new tensor at first cell: [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
[t = 0.10] det(F): min=1.0, max=1.0
[DEBUG] FelasticA_new:
  min = nan max = nan
Felastic new tensor at first cell: [[nan nan nan]
 [nan nan nan]
 [nan nan nan]]
[DEBUG] FvA:
  min = 0.0 max = 0.0
FvA tensor at first cell: [[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
[DEBUG] FvA_old:
  min = 0.0 max = 1.0
FvA_old tensor at first cell: [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
[DEBUG] Cv_Anew :
  min = 0.0 max = 0.0
Cv_Anew tensor at first cell: [[0. 0. 0.]
 [0. 0. 0.]
 [0. 0. 0.]]
[DEBUG] Cv_Aold:
  min = 0.0 max = 1.0
Cv_Aold tensor at first cell: [[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]
[DEBUG] Cauchy_stress_A:
  min = nan max = nan
Cauchy_stress_A tensor at first cell: [[nan nan nan]
 [nan nan nan]
 [nan nan nan]]
[DEBUG] tau_A:
  min = nan max = nan
[DEBUG] hydr_pressure_A:
  min = nan max = nan
[DEBUG] RampA:
  min = nan max = nan
[DEBUG] argA:
  min = 0.0 max = 0.0
[DEBUG] deltagammaA:
  min = 0.0 max = 0.0
()
[DEBUG] f_A:
  min = nan max = nan
[DEBUG] CS_NetworkC:
  min = 0.0 max = 0.0
[DEBUG] deltaCvA:
  min = -1.0 max = 0.0
[2025-05-30 18:09:48.376] [info] Requesting connectivity (2, 0) - (3, 0)
[2025-05-30 18:09:48.377] [info] Requesting connectivity (3, 0) - (2, 0)
[2025-05-30 18:09:52.047] [info] Column ghost size increased from 0 to 0
[2025-05-30 18:09:52.310] [info] PETSc Krylov solver starting to solve system.
[2025-05-30 18:09:54.668] [info] PETSc Krylov solver starting to solve system.
[2025-05-30 18:09:56.433] [info] Newton iteration 2: r (abs) = inf (tol = 1e-08), r (rel) = -nan (tol = 1e-08)
[2025-05-30 18:09:56.700] [info] PETSc Krylov solver starting to solve system.
1 Like

Hi,

Your code is far from a minimal working example and contains so many things that it’s not easy to see the issue, but it looks like your Fv_oldA is correctly set to identity, right?
And the line to initialize your main variable m is commented out?

Note that I think you can interpolate directly with m.sub(1).interpolate(...) for instance without resorting to an auxiliary function and setting the values at the dofs manually.

Hi,
Thanks for replying to my problem.
I tried the code on my PC and it works, but the calculation output is inf.
I initialised only m_old to identity, leaving m not initialised. Is it correct? Thanks for your advice, do you refer to the initialisation of FvA and FvB, right?

I reviewed my code. Here is a clear and short version of the code.

from dolfinx import *
from matplotlib import pyplot as plt
import meshio
from dolfinx.io import gmshio
import pyvista
import numpy as np
from mpi4py import MPI
import math
import os
import sys
from dolfinx.fem.petsc import NonlinearProblem
from dolfinx.nls.petsc import NewtonSolver
import pyvista
from dolfinx import mesh, fem, plot, default_scalar_type, log
from basix.ufl import element, mixed_element
import ufl
from dolfinx.fem import dirichletbc, locate_dofs_topological, Function
from dolfinx.mesh import locate_entities_boundary
from petsc4py import PETSc
length = 10
width = 4
depth = 2
num_ele = 4
n = [int(lengthnum_ele), int(widthnum_ele), int(depth*num_ele)]
domain = mesh.create_box(MPI.COMM_WORLD, [np.array([0, 0, 0]), np.array([length, width, depth])], n, cell_type=mesh.CellType.hexahedron)
tdim = domain.topology.dim # Topological dimension
dim = domain.topology.dim - 1 # Facets are one dimension lower than the domain
element_u = element(“CG”, domain.ufl_cell().cellname(), 1, shape=(tdim,)) # Displacement field
element_FvA = element(“DG”, domain.ufl_cell().cellname(), 0, shape=(tdim,tdim)) # FvA field
element_FvB = element(“DG”, domain.ufl_cell().cellname(), 0, shape=(tdim,tdim)) # FvB field
element_muB = element(“DG”, domain.ufl_cell().cellname(), 0, 0) # shear modulus B field
mixedelement = mixed_element([element_u, element_FvA, element_FvB, element_muB]) # Mixed element for the function space
M = fem.functionspace(domain, mixedelement)

V, _ = M.sub(0).collapse() #Displacement
V1, V1_to_M1 = M.sub(1).collapse() #Fv field A
V2, V2_to_M1 = M.sub(2).collapse() #Fv field B
V3, V3_to_M1 = M.sub(3).collapse() #shear modulus field B

def left(x):
return np.isclose(x[0], 0)
def right(x):
return np.isclose(x[0], length)

fdim = domain.topology.dim - 1
left_facets = mesh.locate_entities_boundary(domain, fdim, left)
right_facets = mesh.locate_entities_boundary(domain, fdim, right)
marked_facets = np.hstack([left_facets, right_facets])
marked_values = np.hstack([np.full_like(left_facets, 2), np.full_like(right_facets, 1)])
sorted_facets = np.argsort(marked_facets)
facet_tag = mesh.meshtags(domain, fdim, marked_facets[sorted_facets], marked_values[sorted_facets])
list_bc = #initialisation of a list
list_bc_m = #initialisation of a list
bcs = #initialisation of a list
#Displacement Boundary
u_bc = fem.Function(V)
u_bc.x.array[:] = 0.0
u_bc_m = fem.Function(V)
#fixed in x
dofs_x = fem.locate_dofs_topological((M.sub(0).sub(0), V.sub(0)), fdim, facet_tag.find(2))
list_bc.append(fem.dirichletbc(u_bc, dofs_x, M.sub(0).sub(0)))
#fixed in y
dofs_y = fem.locate_dofs_topological((M.sub(0).sub(1), V.sub(1)), fdim, facet_tag.find(2))
list_bc.append(fem.dirichletbc(u_bc, dofs_y, M.sub(0).sub(1)))
#fixed in z
dofs_z = fem.locate_dofs_topological((M.sub(0).sub(2), V.sub(2)), fdim, facet_tag.find(2))
list_bc.append(fem.dirichletbc(u_bc, dofs_z, M.sub(0).sub(2)))
####### VARIATIONAL FORMULATION #######
#######################################
v = ufl.TestFunctions(M)
δu, δFvA, δFvB, δmuB = v
m = fem.Function(M)
u, FvA, FvB, muB = ufl.split(m)
#Saving the function at the previous time step
m_old = fem.Function(M) # to store all previous fields
u_old, Fv_oldA, Fv_oldB, muB_old= ufl.split(m_old) # to access individual components

######## VARIATIONAL FORMULATION #######
#Traction vector
T = fem.Constant(domain, default_scalar_type((0, 0, 0)))
#Body force vector
B = fem.Constant(domain, default_scalar_type((0, 0, 0)))
#Material parameters
Bulk_Modulus=default_scalar_type(1000) #Considered as compressible materials
K=fem.Constant(domain, Bulk_Modulus)
#Material parameters for network A
MUA= fem.Constant(domain, default_scalar_type(15.58)) #Shear modulus network A
LAMBDAL= fem.Constant(domain, default_scalar_type(6.28)) #Lambda lock – the same for all networks
TAUHATA= fem.Constant(domain, default_scalar_type(11.57)) #tauhat
MA=fem.Constant(domain, default_scalar_type(9.51))
#Material parameters for network B
MUBI=fem.Constant(domain, default_scalar_type(47.25)) #Initial Shear modulus network B
MUBF=fem.Constant(domain, default_scalar_type(1.32)) #Final Shear modulus network B
BETA=fem.Constant(domain, default_scalar_type(4.19)) #BETA
TAUHATB=fem.Constant(domain, default_scalar_type(39.56))
MB=fem.Constant(domain, default_scalar_type(14.67))
#Material parameters for network C
MUC=fem.Constant(domain, default_scalar_type(6.27))

tau_C=fem.Constant(domain, default_scalar_type(0.01))
deltaT = 2 #time step size in second

def inv_Langevin(ED_chainstretch):
# Padè approximation for the inverse Langevin function
ED_limited = ufl.min_value(ED_chainstretch, 0.99)
L_inv = (ED_limited*(3-ED_limited2))/(1-ED_limited2)
return L_inv

#Cauchy stress tensor for the Eight Chain model
def cauchy_stress_EC(FF_local, shear_modulus, lambdalock, K_local):
I_local = ufl.variable(ufl.Identity(3))
J_local = ufl.variable(ufl.det(FF_local))
b_local=ufl.variable(FF_localufl.transpose(FF_local))
b_local_dist = ufl.variable((J_local**(-2/3))b_local)
Ib1_dist_local = ufl.variable(ufl.tr(b_local_dist))
eff_dist_chainstrech= ufl.variable(ufl.sqrt(Ib1_dist_local/3))
numerator = inv_Langevin(eff_dist_chainstrech/lambdalock)
denominator = (inv_Langevin(1/lambdalock))
part1=(shear_modulus/(J_local
eff_dist_chainstrech))
((numerator)/(denominator))
part2=part1*(b_local_dist-((1/3)Ib1_dist_localI_local))
return part2 + K_local*(J_local-1)*I_local

initial_stateFv = ufl.Identity(3)
initial_stateFvA_exp = fem.Expression(initial_stateFv, V1.element.interpolation_points(), domain.comm)
initial_stateFvB_exp = fem.Expression(initial_stateFv, V2.element.interpolation_points(), domain.comm)
initial_statemuB_exp = fem.Expression(MUBI, V3.element.interpolation_points(), domain.comm)
m.sub(1).interpolate(initial_stateFvA_exp) #Inizialization FvA
m.sub(2).interpolate(initial_stateFvB_exp) #Inizialization FvB
m.sub(3).interpolate(initial_statemuB_exp) ##Inizialization muB
initial_stateFvA_exp_old = fem.Expression(initial_stateFv, V1.element.interpolation_points(), domain.comm)
initial_stateFvB_exp_old = fem.Expression(initial_stateFv, V2.element.interpolation_points(), domain.comm)
initial_statemuB_exp_old = fem.Expression(MUBI, V3.element.interpolation_points(), domain.comm)
m_old.sub(1).interpolate(initial_stateFvA_exp_old) #Inizialization FvA_old
m_old.sub(2).interpolate(initial_stateFvB_exp_old) #Inizialization FvB_old
m_old.sub(3).interpolate(initial_statemuB_exp_old) ##Inizialization muB_old

d = len(u) # Spatial dimension
I = ufl.variable(ufl.Identity(d)) # Identity tensor
F_new = ufl.variable(I + ufl.grad(u))
##F=F_elF_vi
FelasticA_new = ufl.variable(F_new
ufl.inv(FvA))
FelasticB_new = ufl.variable(F_new*ufl.inv(FvB))

#Network A
Cauchy_stress_A = cauchy_stress_EC(FelasticA_new, MUA, LAMBDAL, K)
dev_stressA =(Cauchy_stress_A-((1/3)*ufl.tr(Cauchy_stress_A)*I))
tau_A = ufl.sqrt(ufl.tr(ufl.dot(dev_stressA, dev_stressA))) #Frobenius norm
hydr_pressure_A = -ufl.tr(Cauchy_stress_A)/3
RampA = (hydr_pressure_A + ufl.sqrt(hydr_pressure_A2))/2
argA = ufl.conditional(tau_A > 0, tau_A/(TAUHATA + RampA) - tau_C, 0.0)
delta_gammaA = argA
MA
zero_tensor = ufl.as_tensor([[0.0]3]3)
argA2 = ufl.conditional(tau_A > 0.0, dev_stressA / tau_A, zero_tensor) #check if tau_A is greater than zero to avoid division by zero
f_A = delta_gammaA
argA2
FvA

#Network B
f_muB = - BETA*(muB-MUBF)*(delta_gammaA)
Cauchy_stress_B= cauchy_stress_EC(FelasticB_new, muB, LAMBDAL, K)
dev_stressB=(Cauchy_stress_B-((1/3)ufl.tr(Cauchy_stress_B)I))
tau_B = ufl.sqrt(ufl.tr(ufl.dot(dev_stressB, dev_stressB))) #Frobenius norm
hydr_pressure_B = -ufl.tr(Cauchy_stress_B)/3
RampB = (hydr_pressure_B + ufl.sqrt(hydr_pressure_B2))/2
argB = ufl.conditional(tau_B > 0, tau_B/(TAUHATB + RampB) - tau_C, 0.0)
delta_gammaB = argB
MB
argB2 = ufl.conditional(tau_B > 0.0, dev_stressB / tau_B, zero_tensor) #check if tau_A is greater than zero to avoid division by zero
f_B = delta_gammaB
argB2
FvB
#Network C
CS_NetworkC = cauchy_stress_EC(F_new, MUC, LAMBDAL, K)

def δe(test_function_local):
return ufl.sym(ufl.grad(test_function_local)) #Equivalent to 0.5*(ufl.nabla_grad(u) + ufl.nabla_grad(u).T)

metadata = {“quadrature_degree”: 4}
ds = ufl.Measure(‘ds’, domain=domain, metadata=metadata)
dx = ufl.Measure(“dx”, domain=domain, metadata=metadata) # Elementarily volume

F_int_A= ufl.inner(Cauchy_stress_A, δe(δu))*dx + ufl.inner(FvA-Fv_oldA,δFvA)dx - deltaTufl.inner(f_A, δFvA)*dx
F_int_B= ufl.inner(Cauchy_stress_B, δe(δu))*dx + ufl.inner(FvB-Fv_oldB,δFvB)dx - deltaTufl.inner(f_B, δFvB)*dx + ufl.inner(muB-muB_old,δmuB)dx - deltaTufl.inner(f_muB, δmuB)*dx
F_int_C= ufl.inner(CS_NetworkC, δe(δu))*dx
F_ext= - ufl.inner(δu, B) * dx - ufl.inner(δu, T) * ds
Variational= F_int_A + F_int_B + F_int_C + F_ext

log.set_log_level(log.LogLevel.INFO)

######## VISUALIZATION ########
#Function space for visualization
V_vis = fem.functionspace(domain, (“Lagrange”, 1, (3,)))
TS = fem.functionspace(domain, (“DG”, 0,(3, 3))) #True stress tensor
print(f"Number of DoFs in True Stress Tensor: {TS.dofmap.index_map.size_global}“)
uh_vis_list = #initialisation of a list
true_stress_vis_list = [ ] #initialisation of a list
State_value = fem.functionspace(domain, (“Lagrange”, 1))
State_tensor= fem.functionspace(domain, (“Lagrange”, 1, (tdim,tdim)))
State_vector = fem.functionspace(domain, (“Lagrange”, 1, (tdim,)))
with io.XDMFFile(domain.comm, “Viscoplasticity/results_1.xdmf”, “w”) as outfile:
outfile.write_mesh(domain) # write mesh once
t_end = 200 #1200.00 # Total simulation time
t = 2 # Initial time
step = 1
displacement_rate = 0.083
while t <= t_end:
print(f"Time step {step}, time: {t:.2f}”)
dofs_x_m = fem.locate_dofs_topological((M.sub(0).sub(0), V.sub(0)), fdim, facet_tag.find(1))
u_bc_m.x.array[:] = displacement_rate*t
list_bc_m.append(fem.dirichletbc(u_bc_m, dofs_x_m, M.sub(0).sub(0)))
bcs = list_bc + list_bc_m
CS_TNM = Cauchy_stress_A + Cauchy_stress_B + CS_NetworkC
##RISOLUZIONE
problem = NonlinearProblem(Variational, m, bcs)
solver = NewtonSolver(domain.comm, problem)
# Set Newton solver options
solver.atol = 1e-8 #Sets the absolute tolerance (stopping criterion when the residual is small)
solver.rtol = 1e-8 #Sets the relative tolerance (stopping criterion based on relative change)
solver.max_it = 500 #Sets the maximum number of iterations
solver.convergence_criterion = “incremental” #Sets the convergence criterion to incremental
num_its, converged = solver.solve(m)
#num_its stores the number of Newton iterations required for convergence.
#converged is True if the solver successfully found a solution, otherwise False
if not converged:
print(f"Solver failed at step {step}. Iterations: {num_its}")
assert (converged) #Test if a condition returns True, if not, it raises an AssertionError
m.x.scatter_forward()
m_old.x.scatter_forward()
u, FvA, FvB, muB = m.split()
u_old, Fv_oldA, Fv_oldB, muB_old = m_old.split()
Fv_oldA = m.sub(1)
Fv_oldB = m.sub(2)
muB_old = m.sub(3)
#
TrueStress_expr = fem.Expression(CS_TNM, TS.element.interpolation_points())
stress_tensor_fn = fem.Function(TS)
stress_tensor_fn.interpolate(TrueStress_expr)
print(“True stress tensor at first cell:”, stress_tensor_fn.x.array[:9].reshape(3, 3))
true_stress_copy = fem.Function(TS)
true_stress_copy.x.array[:] = stress_tensor_fn.x.array[:]
true_stress_vis_list.append(true_stress_copy)
cs_array = stress_tensor_fn.x.array[:]
#Save a copy of the solution to keep history
uh = m.sub(0).collapse()
uh_vis = fem.Function(V_vis)
uh_vis.interpolate(uh)
uh_copy = fem.Function(V_vis)
uh_copy.x.array[:] = uh_vis.x.array[:] # copia dei dati
uh_vis_list.append(uh_copy)
print(“Saved displacement solution”)
print(“Max displacement norm:”, np.max(np.abs(uh_vis.x.array)))
time_step = step
t += deltaT
step += 1
# WRITE XDMFFile
uh.name = “Displacement”
stress_tensor_fn.name = “TrueStress”
outfile.write_function(uh_vis, time_step)
outfile.write_function(stress_tensor_fn, time_step)
outfile.close()
print(“Simulation completed”)