Hi,
I would like to compute the highest maximum principal stress outside of a sphere (specified by function in_sphere()) and not in nodes belonging to low quality elements. I have a function that can compute the element quality given the 4 tetrahedron nodes, belonging to a given node. How could I go about adding this as an output to the fea_solve() function? What I am not very clear on is how the DG0 element stress values are projected to the nodes; is this like an average of the elements around the node?
The short version of the code is posted below.
The mesh can be found here: Dropbox
Thank you,
Alex
import numpy as np
import pygalmesh
import meshio
import time
from mpi4py import MPI
import ufl
import basix.ufl as bu
from dolfinx import mesh, fem, io, default_scalar_type
r_loading = 3.0e-3
def make_dolfinx_mesh_from_pygalmesh(vol_mesh):
points = vol_mesh.points
tet_cells = None
for c in vol_mesh.cells:
if c.type in ["tetra", "tetra10", "tetrahedron"]:
tet_cells = c.data
break
if tet_cells is None:
raise RuntimeError("No tetrahedral cells found in the volume mesh.")
# Vector Lagrange P1 element for mesh
finiteElement = bu.element("Lagrange", "tetrahedron", 1, shape=(3,))
domain = mesh.create_mesh(MPI.COMM_WORLD, cells=tet_cells, x=points, e=finiteElement)
return domain
def in_sphere(x, point, r_range):
return ((x[0]-point[0])**2 + (x[1]-point[1])**2 + (x[2]-point[2])**2) < r_range**2
def mark_boundaries(domain, facet_node,grasp_node,grasp_node_normal):
dim = domain.topology.dim
fdim = dim - 1
domain.topology.create_connectivity(fdim, dim)
def left_facets(xq):
return in_sphere(xq, facet_node, r_loading)
def right_facets(xq):
return in_sphere(xq, grasp_node, r_loading)
facets_left = mesh.locate_entities(domain, fdim, left_facets)
facets_right = mesh.locate_entities(domain, fdim, right_facets)
facet_indices = np.concatenate([facets_left, facets_right])
facet_markers = np.concatenate([np.full_like(facets_left, 1, dtype=np.int32),
np.full_like(facets_right, 2, dtype=np.int32)])
# Sort by facet index
sort_perm = np.argsort(facet_indices)
facet_indices = facet_indices[sort_perm]
facet_markers = facet_markers[sort_perm]
mt = mesh.meshtags(domain, fdim, facet_indices, facet_markers)
return mt
def elasticity_parameters2(domain, point, r_range, E1=10.0e4, E2=1.0e4, nu=0.35):
"""Lamé parameters from E, nu."""
param1 = nu / ((1.0 + nu) * (1.0 - 2.0 * nu))
param2 = 0.5 / (1.0 + nu)
lambda1_ = E1 * param1
mu1 = E1 * param2
lambda2_ = E2 * param1
mu2 = E2 * param2
Lambda_ = fem.functionspace(domain, ("DG", 0))
lambda_ = fem.Function(Lambda_)
lambda_.interpolate(lambda x: np.where(in_sphere(x, point, r_range), lambda2_, lambda1_))
Mu = fem.functionspace(domain, ("DG", 0))
mu = fem.Function(Mu)
mu.interpolate(lambda x: np.where(in_sphere(x, point, r_range), mu2, mu1))
return lambda_, mu
def epsilon(u):
return ufl.sym(ufl.grad(u))
def sigma(u, lambda_, mu):
return lambda_ * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2.0 * mu * epsilon(u)
def build_primal_problem(domain, facet_tags, lambda_, mu, node_normal,
p_traction=1.0/(np.pi*r_loading**2), body_force=(0.0, 0.0, -4000.0*9.8)):
"""
BCs:
- left side (tag 1): fixed
- right side (tag 2): traction in +x direction
"""
dim = domain.topology.dim
fdim = dim - 1
ds = ufl.Measure("ds", domain=domain, subdomain_data=facet_tags)
V = fem.functionspace(domain, ("Lagrange", 1, (domain.geometry.dim,)))
u = ufl.TrialFunction(V)
v = ufl.TestFunction(V)
f_vec = fem.Constant(domain, default_scalar_type(body_force))
# Traction direction: push along +x (change sign if needed)
T = fem.Constant(domain, -p_traction * default_scalar_type((node_normal[0], node_normal[1], node_normal[2])))
a = ufl.inner(sigma(u, lambda_, mu), epsilon(v)) * ufl.dx
L = ufl.dot(f_vec, v) * ufl.dx + ufl.dot(T, v) * ds(2)
# Dirichlet BC on left jaw (tag 1)
left_facets = facet_tags.find(1)
domain.topology.create_connectivity(fdim, dim)
left_dofs = fem.locate_dofs_topological(V, fdim, left_facets)
u_D = np.array((0.0, 0.0, 0.0), dtype=default_scalar_type)
bc_left = fem.dirichletbc(u_D, left_dofs, V)
return V, a, L, [bc_left]
def principal_stress(domain, uh, lambda_, mu):
#Compute principal stresses via invariants (analytical eigenvalue solution)
sigma_uh = sigma(uh, lambda_, mu)
q = ufl.tr(sigma_uh) / 3 # mean stress (I1/3)
B = sigma_uh - q * ufl.Identity(3) # deviatoric stress tensor
j = ufl.tr(B * B) / 2 # J2 = 1/2 tr(B^2)
b = ufl.tr(B * B * B) / 3 # J3 = 1/3 tr(B^3)
p = 2 / ufl.sqrt(3) * ufl.sqrt(j + 1e-12) # p = 2/sqrt(3)*sqrt(J2) (small epsilon added)
r = 4 * b / (p**3) # r = 4 J3 / p^3
r = ufl.max_value(ufl.min_value(r,+1-1e-6),-1+1e-6) # clamp r to [-1+eps, 1-eps] to avoid acos domain error
phi = ufl.acos(r) / 3
lambda2 = q + p * ufl.cos(phi) # largest principal stress (max)
V_dg = fem.functionspace(domain, ("DG", 0))
pmax_expr = fem.Expression(lambda2, V_dg.element.interpolation_points)
pmax = fem.Function(V_dg, name="PrincipalStress")
pmax.interpolate(pmax_expr)
return pmax
def fea_solve(domain, facet_node, grasp_node, grasp_node_normal):
lambda_, mu = elasticity_parameters2(domain, facet_node, 4.0e-3)
#lambda_, mu = elasticity_parameters()
facet_tags = mark_boundaries(domain, facet_node,grasp_node,grasp_node_normal)
Vp, a_p, L_p, bcs_p = build_primal_problem(domain, facet_tags, lambda_, mu, grasp_node_normal)
problem = fem.petsc.LinearProblem(a_p, L_p, bcs=bcs_p,
petsc_options_prefix="linear_problem",
petsc_options={"ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps"})
uh = problem.solve()
# Principal stress
pmax = principal_stress(domain, uh, lambda_, mu)
return domain, uh, pmax
# Part dependent mesh for FEA
##################################################################
# min_facet_angle = 25.0
# max_radius_surface_delaunay_ball = 0.0006
# max_facet_distance = 0.0006
# max_circumradius_edge_ratio = 3.0
# t_start = time.perf_counter()
# vol_mesh = pygalmesh.generate_volume_mesh_from_surface_mesh("bracket_ver2.STL",
# min_facet_angle=min_facet_angle,
# max_radius_surface_delaunay_ball=max_radius_surface_delaunay_ball,
# max_facet_distance=max_facet_distance,
# max_circumradius_edge_ratio=max_circumradius_edge_ratio,
# odt=True, lloyd=True,
# seed=0, verbose=False)
# t_end = time.perf_counter()
# print(f"Meshing elapsed time: {t_end - t_start:.6f} seconds")
# vol_mesh.write("mesh_braket.vtk")
domain = make_dolfinx_mesh_from_pygalmesh(meshio.read("mesh_braket.vtk"))
grasp_facet_node = [0.02, 0.018, 0.008]
grasp_node2 = [0.0, 0.018, 0.008]
grasp_node_normals = [-1.0, 0.0, 0.0]
t_start = time.perf_counter()
domain_f, uh, pmax = fea_solve(domain, grasp_facet_node, grasp_node2, grasp_node_normals)
t_end = time.perf_counter()
print(f"FEA elapsed time: {t_end - t_start:.6f} seconds")
print('\n')
with io.XDMFFile(domain_f.comm, "max_p_stress_braket.xdmf", "w") as xdmf:
xdmf.write_mesh(domain_f)
uh.name = "u"
xdmf.write_function(uh)
pmax.name = "PrincipalStress"
xdmf.write_function(pmax)
max_stress_per_N = np.max(pmax.x.array)
#max_stress_per_N = np.sort(pmax.x.array)[-4]
print(max_stress_per_N)