Hi,
I am trying to solve a 3D elasticity problem with several additional field variables for eigenstrains (a simpler MWE enclosed at the end). Geometry is hemisphere with tetrahedral meshing, compressed uniaxially against a rigid wall.
Results of MWE when form is compiled in the loop (CONSISTENT)
Results of MWE when form is compiled outside the loop and boundary conditions updated after each iteration (ERROR)
I am enclosing MWE with just two eigenstrains. I want to draw your attention to the latter part of code which I am modifying to compile the forms outside of loop.
Common block of code:
from numpy import infty, iinfo, int32, argwhere, min, max, array
from dolfin import *
# Form compiler options
parameters["form_compiler"]["optimize"] = True
parameters["form_compiler"]["cpp_optimize"] = True
parameters.parse()
## Geometrical parameters
R = 100 # nm
mesh_size = 8
compression = R/50.0
time_step = 5e-2
## material parameters
AM_surface_energy = 0.20 # J/m^2 = Pa.m = Pa'.nm (no change in magnitude in new scaling)
AM_length_scale = 15.0 # nm
MM_surface_energy = 0.00 # J/m^2 = Pa.m = Pa'.nm (no change in magnitude in new scaling)
MM_length_scale = 0.00 # nm
phi = 5.0e-3 # 1 MPa = 1e-3 * Pa'
contact_penalty = 100
phase_penalty = 100
mob = 10e-3
mesh_folder = "./meshes/" + "tetrahedral/"
mesh_file = "mesh3D_R_" + str(int(R)) + "_size_" + str(int(mesh_size)) + ".xdmf"
mesh = Mesh()
#db_mesh_folder = "/Users/dhariwal/Library/CloudStorage/OneDrive-VirginiaTech/Zirconia/Particle_SIMULATION/Code/single_variant/meshes/"
#mesh_file = "mesh3D_R_" + str(int(self.R)) + "_size_" + str(int(mesh_size)) + ".xdmf"
with XDMFFile(mesh_folder + mesh_file) as infile:
infile.read(mesh)
meshSIZE = 0.5*(mesh.hmax() + mesh.hmin())
# basis for 'vector' displacement
P1 = VectorElement("Lagrange", mesh.ufl_cell(), 1)
# basis for 'scalar' monovariant phase field
P2 = FiniteElement("Lagrange", mesh.ufl_cell(), 1)
W1 = FunctionSpace(mesh, P1)
W2 = FunctionSpace(mesh, P2)
# lattice parameters
alpha = 1.0243
beta = -0.0247
gamma = 0.9563
delta = 0.058
e_v1 = array([[gamma - 1, beta, beta],
[beta, alpha - 1, delta],
[beta, delta, alpha - 1]
])
e_v2 = array([[gamma - 1, - beta, - beta],
[- beta, alpha - 1, delta],
[- beta, delta, alpha - 1]
])
shape_strains_list = [as_tensor(e_v1), as_tensor(e_v2)]
num_variants = 2
# single variant
element = MixedElement([P1, P2, P2])
# create the space of solutions over the domain
W = FunctionSpace(mesh, element)
# a generic function from W
w = Function(W)
dw = TrialFunction(W)
wx = TestFunction(W)
compliance_cubic = array([
[169.0, 138.0, 138.0, 0.0, 0.0, 0.0],
[138.0, 169.0, 138.0, 0.0, 0.0, 0.0],
[138.0, 138.0, 169.0, 0.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0, 40.0, 0.0, 0.0],
[ 0.0, 0.0, 0.0, 0.0, 40.0, 0.0],
[ 0.0, 0.0, 0.0, 0.0, 0.0, 40.0]
])
compliance_ortho = array([
[223.0, 129.0, 99.0, 0.0, 0.0, 27.0],
[129.0, 241.0, 125.0, 0.0, 0.0, -9.0],
[ 99.0, 125.0, 200.0, 0.0, 0.0, 4.0],
[ 0.0, 0.0, 0.0, 76.0, -4.0, 0.0],
[ 0.0, 0.0, 0.0, -4.0, 45.0, 0.0],
[ 27.0, -9.0, 4.0, 0.0, 0.0, 90.0]
])
Compliance_cubic = as_tensor(compliance_cubic)
Compliance_ortho = as_tensor(compliance_ortho)
# Definition of normal vectors to the elastic body and to the rigid half-space
nRIGID = Constant((0.0, 0.0, 1.0))
n = FacetNormal(mesh)
##################################
# Phase field definitions
##################################
def trans_strain(c):
strain = [c[i] * shape_strains_list[i] for i in range(num_variants)]
return sum(strain)
def mixture_compliance(c):
return ((1 - sum(c)) * Compliance_cubic) + (sum(c) * Compliance_ortho)
##################################
# Displacement field definitions
##################################
# matrix-to-vector transform
def strain3voigt(e):
return as_vector(
[e[0, 0],
e[1, 1],
e[2, 2],
e[1, 2] * 2,
e[0, 2] * 2,
e[0, 1] * 2]
)
# vector-to-matrix transform
def voigt3stress(s):
return as_tensor(
[[s[0], s[5], s[4]],
[s[5], s[1], s[3]],
[s[4], s[3], s[2]]]
)
def epsilon(u, strain_approx=None):
if strain_approx is None:
e = sym(grad(u))
return e
elif strain_approx =='lagrangian':
I = Identity(3)
Fgrad = I + grad(u)
C = Fgrad.T * Fgrad
E = 0.5*(C - I)
return E
elif strain_approx =='hencky_order_22':
I = Identity(3)
Fgrad = I + grad(u)
C = Fgrad.T * Fgrad
a = C*C - I
b = (C*C) + 4*C + I
H = 1.5 * a * inv(b)
return H
def epsilon_elastic(u, c):
return epsilon(u) - trans_strain(c)
def Vstrain(u, c):
return strain3voigt(epsilon_elastic(u, c))
def Vsigma(u, c):
return dot(mixture_compliance(c), Vstrain(u, c))
def sigma(u, c):
return voigt3stress(Vsigma(u, c))
def dilatation(c):
return det(trans_strain(c))
def gap(u): # Definition of gap function
x = SpatialCoordinate(mesh)
return x[2] + u[2]
def maculay(x): # Definition of Maculay bracket
return (x+abs(x))/2
def pN(u, c):
return dot(dot(nRIGID, sigma(u, c)), nRIGID)
def convert_to_integer(a):
"Convert to a 32-bit int. Raise exception if this will cause an overflow"
if abs(a) <= iinfo(int32).max:
return int32(a)
else:
raise OverflowError("Cannot safely convert to a 32-bit int.")
def psi(w):
u = split(w)[0]
c = split(w)[1:]
def elastic(u, c):
# elastic_energy = 0.5 * dilatation(c) * inner(sigma(u, c), epsilon_elastic(u, c))
# approximate energy without volume dilatation (det F = 1)
elastic_energy = 0.5 * inner(sigma(u, c), epsilon_elastic(u, c))
return elastic_energy
def interface(c):
energy_prefactor = (4.0/DOLFIN_PI) * (AM_surface_energy/AM_length_scale)
AM_interface = energy_prefactor * inner(1 - sum(c), sum(c))
energy_cost = AM_interface
if MM_length_scale > 0.0:
MM_interface = (4.0/DOLFIN_PI) * (MM_surface_energy/MM_length_scale)
energy_cost = AM_interface + MM_interface
return energy_cost, energy_prefactor
def chemical(c):
chemical_energy = phi*sum(c)
return chemical_energy
def contact_pen(u):
pen = (0.5 * contact_penalty *inner(maculay(-gap(u)),maculay(-gap(u))))
return pen
def phase_pen(c):
penp1 = [inner(maculay(c[i] - 1), maculay(c[i] - 1)) for i in range(num_variants)]
penp0 = [inner(maculay(-c[i]), maculay(-c[i])) for i in range(num_variants)]
penp = sum(penp0) + sum(penp1)
return 0.5 * phase_penalty * penp
def dissipation(u, c):
prefactor = interface(c)[1]
diss = 0.0
for i in range(num_variants):
X_AM = inner(sigma(u, c), shape_strains_list[i]) \
- 0.5 * inner(voigt3stress(dot((Compliance_ortho - Compliance_cubic), Vstrain(u, c))), epsilon_elastic(u, c)) \
- phi - prefactor*(1.0 - 2*sum(c))
cdot = mob * X_AM
diss = diss + (X_AM*cdot)
return diss
# return elastic(u, c), contact_pen(u), phase_pen(c)
return elastic(u, c) + interface(c)[0] + chemical(c), contact_pen(u), phase_pen(c), dissipation(u, c)
def sphere_boundaries(step):
# Spatial coordinates of mesh
x = SpatialCoordinate(mesh)
# Definition of tolerance
tol = DOLFIN_EPS # FEniCS tolerance - necessary for "boundary_markers" and the correct definition of the boundaries
bT = CompiledSubDomain('near(x[2], R, tol) && on_boundary', R = R, tol = tol)
bP = CompiledSubDomain('sqrt(x[0]*x[0] + x[1]*x[1]) < meshSIZE \
&& near(x[2], R, tol) \
&& on_boundary', meshSIZE = meshSIZE, R=R, tol = tol)
# Definition of contact zones
bC = CompiledSubDomain('on_boundary && x[2] < compression + tol', R = R, tol=tol, compression = step) # Contact search - contact part of the boundary
boundaries = MeshFunction("size_t", mesh, mesh.topology().dim()-1)
boundaries.set_all(0)
bT.mark(boundaries, 2) # top
bP.mark(boundaries, 4) # lateral_penalty (for stability)
bC.mark(boundaries, 6) # contact_zone
print("Nodes at contact zone: ", boundaries.array()[boundaries.array() == 6])
dom_markers = MeshFunction("size_t", mesh, mesh.topology().dim())
dom_markers.set_all(1)
# Displacement boundary conditions
bc1 = DirichletBC(W.sub(0).sub(2), Constant((-step)), boundaries, 2)
bc2 = DirichletBC(W.sub(0).sub(0), Constant((0)), boundaries, 4)
bc3 = DirichletBC(W.sub(0).sub(1), Constant((0)), boundaries, 4)
bcs = [bc1,bc2,bc3]
dx = Measure('dx', subdomain_data=dom_markers)
ds = Measure('ds', subdomain_data=boundaries)
return dx, ds, boundaries, bcs
When form is compiled inside time loop:
while t_curr <= time_end:
dx, ds, boundaries, bcs = sphere_boundaries(d_step)
print("Current displacement at boundary = ", d_step, " nm and time_step = ", time_step)
# Total potential energy at the current step (linear elasticity, gradient-interface)
PiT = psi(w)[0]*dx + psi(w)[1]*ds(6) + psi(w)[2]*dx + psi(w)[3]*dx
# Total potential energy of previous time-step (from known solution)
Pi0 = psi(w0)[0]*dx
# Incremental energy (increment potential energy + increment in dissiaption)
Pi = PiT - Pi0
# Compute first variation of Pi (directional derivative about u in the direction of v)
F = derivative(Pi, w, wx)
# Compute Jacobian of F for iterative "newton_solver"
J = derivative(F, w, dw)
for bc in bcs:
bc.apply(w0.vector())
w.vector()[:] = w0.vector()[:]
problem = NonlinearVariationalProblem(F, w, bcs, J=J)
solver = NonlinearVariationalSolver(problem)
snes_solver_parameters = {"nonlinear_solver": "snes",
"snes_solver": {"linear_solver": 'lu', #"lu",
"preconditioner": "pcmg",
"line_search": "bt",
"maximum_iterations": 50,
"absolute_tolerance": 1E-3,
"relative_tolerance": 1E-3,
"report": True,
#"method":'vinewtonrsls',
"error_on_nonconvergence": False,
}}
solver.parameters.update(snes_solver_parameters)
iter, converged = solver.solve()
if converged:
#time_step = 1.5 * time_step
t_curr = t_curr + time_step
#u_converged = _u
w0.assign(w)
else:
# new timestamp is set, go back to the previous
t_curr = t_curr - time_step
# update the step-size
time_step = time_step*(0.5)
# modified current time
t_curr = t_curr + time_step
d_step = compression * t_curr
_c = w.split(deepcopy=True)[1:]
mart = project(sum(_c), W2)
from vedo.dolfin import plot
plot(mart)
When form is compiled outside time loop and BCs updated after each iteration:
dx, ds, boundaries, bcs = sphere_boundaries(d_step)
for bc in bcs:
bc.apply(w0.vector())
w.vector()[:] = w0.vector()[:]
# Total potential energy at the current step (linear elasticity, gradient-interface)
PiT = psi(w)[0]*dx + psi(w)[1]*ds(6) + psi(w)[2]*dx + psi(w)[3]*dx
# Total potential energy of previous time-step (from known solution)
Pi0 = psi(w0)[0]*dx
# Incremental energy (increment potential energy + increment in dissiaption)
Pi = PiT - Pi0
# Compute first variation of Pi (directional derivative about u in the direction of v)
F = derivative(Pi, w, wx)
# Compute Jacobian of F for iterative "newton_solver"
J = derivative(F, w, dw)
while t_curr <= time_end:
print("Current displacement at boundary = ", d_step, " nm and time_step = ", time_step)
problem = NonlinearVariationalProblem(F, w, bcs, J=J)
solver = NonlinearVariationalSolver(problem)
solver.parameters.update(snes_solver_parameters)
iter, converged = solver.solve()
if converged:
#time_step = 1.5 * time_step
t_curr = t_curr + time_step
#u_converged = _u
w0.assign(w)
else:
# new timestamp is set, go back to the previous
t_curr = t_curr - time_step
# update the step-size
time_step = time_step*(0.5)
# modified current time
t_curr = t_curr + time_step
d_step = compression * t_curr
dx, ds, boundaries, bcs = sphere_boundaries(d_step)
_c = w.split(deepcopy=True)[1:]
mart = project(sum(_c), W2)
from vedo.dolfin import plot
plot(mart)
I am not able to understand from where such discrepancy in results might be arising?
Thanks,
Deepak