I’m solving a classical problem with clamped cantilever beam 3D and a force on its end. The beam has a circular section. I have this code:
import os
from typing import NamedTuple
import gmsh
import numpy as np
import ufl
from dolfinx import fem, io, mesh
from dolfinx.fem.petsc import LinearProblem
from mpi4py import MPI
from petsc4py import PETSc
# enforce 1 thread per core
nthreads = 1
os.environ["OMP_NUM_THREADS"] = str(nthreads)
os.environ["OPENBLAS_NUM_THREADS"] = str(nthreads)
os.environ["MKL_NUM_THREADS"] = str(nthreads)
comm = MPI.COMM_WORLD
# -------------------#
# Problem properties #
# -------------------#
class Material(NamedTuple):
name: str
E: PETSc.ScalarType
nu: PETSc.ScalarType
rho: PETSc.ScalarType
material = Material("Aluminum 6061", 68.9e9, 0.33, 2700)
# beam size
height = 150 # m
diameter = 6.5 # m
# loads
load = (10, 0, 0)
# --------------------#
# ------- #
# MESHING #
# ------- #
FIXED_TAG = 1000
TIP_SURFACE_TAG = 3000
VOLUME_TAG = 4000
ELEMENTS_ORDER = 2
def gmsh_tower(model: gmsh.model, name: str, h: float, d: float) -> gmsh.model:
model.add(name)
model.setCurrent(name)
# Recombine tetrahedra to hexahedra
gmsh.option.setNumber("Mesh.RecombinationAlgorithm", 2)
gmsh.option.setNumber("Mesh.RecombineAll", 2)
gmsh.option.setNumber("Mesh.CharacteristicLengthFactor", 0.1)
circle = model.occ.addDisk(0, 0, 0, d / 2, d / 2)
extruded_geometry = model.occ.extrude([(2, circle)], 0, 0, h, numElements=[h], recombine=True)
model.occ.synchronize()
fixed_sf = model.addPhysicalGroup(2, [circle], tag=FIXED_TAG)
model.setPhysicalName(2, fixed_sf, "FIXED SURFACE")
boundary_entities = model.getEntities(2)
other_boundary_entities = []
for entity in boundary_entities:
if entity != circle:
other_boundary_entities.append(entity[1])
tip_sf = model.addPhysicalGroup(2, [other_boundary_entities[-1]], tag=TIP_SURFACE_TAG)
model.setPhysicalName(2, tip_sf, "TIP SURFACE")
model.mesh.generate(3)
model.mesh.setOrder(ELEMENTS_ORDER)
volume_entities = []
for entity in extruded_geometry:
if entity[0] == 3:
volume_entities.append(entity[1])
vol = model.addPhysicalGroup(3, volume_entities, tag=VOLUME_TAG)
model.setPhysicalName(3, vol, "Mesh volume")
return model
def create_mesh(comm: MPI.Comm, model: gmsh.model, name: str, filename: str, mode: str):
msh, ct, ft = io.gmshio.model_to_mesh(model, comm, rank=0)
msh.name = name
ct.name = f"{msh.name}_cells"
ft.name = f"{msh.name}_facets"
with io.XDMFFile(msh.comm, filename, mode) as file:
msh.topology.create_connectivity(2, 3)
file.write_mesh(msh)
file.write_meshtags(
ct, msh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{msh.name}']/Geometry"
)
file.write_meshtags(
ft, msh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{msh.name}']/Geometry"
)
return (msh, ct, ft)
gmsh.initialize()
gmsh.option.setNumber("General.Terminal", 0)
# Create model
model_name = "Tower"
model = gmsh.model()
model = gmsh_tower(model=model, name=model_name, h=height, d=diameter)
model.setCurrent(model_name)
mesh_file = f"out_gmsh/mesh_rank_{MPI.COMM_WORLD.rank}.xdmf"
domain, cell_markers, facet_markers = create_mesh(
comm=MPI.COMM_SELF,
model=model,
name=model_name,
filename=mesh_file,
mode="w",
)
# -------------- #
# Function Space #
# -------------- #
dim = domain.geometry.dim
V = fem.functionspace(domain, ("Lagrange", ELEMENTS_ORDER, (dim,)))
load_facets = facet_markers.find(TIP_SURFACE_TAG)
mt = mesh.meshtags(domain, 2, load_facets, 1)
metadata = {"quadrature_degree": 4}
ds = ufl.Measure("ds", subdomain_data=mt, subdomain_id=1, metadata=metadata)
x = ufl.SpatialCoordinate(domain)
E = fem.Constant(domain, float(material.E))
nu = fem.Constant(domain, float(material.nu))
rho = fem.Constant(domain, float(material.rho))
# --------------------#
# Boundary conditions #
# --------------------#
f = ufl.as_vector(load)
# Fixed BC
fixed_facets = facet_markers.find(FIXED_TAG)
fixed_surface_dofs = fem.locate_dofs_topological(V, 2, fixed_facets)
u_bc = fem.Function(V)
fixed_bc = fem.dirichletbc(u_bc, fixed_surface_dofs)
# -------------------------#
# linear elastic equations #
# -------------------------#
lmbda = E * nu / (1 + nu) / (1 - 2 * nu)
mu = E / 2 / (1 + nu)
def epsilon(u):
return ufl.sym(ufl.grad(u))
def sigma(u):
return lmbda * ufl.nabla_div(u) * ufl.Identity(len(u)) + 2 * mu * epsilon(u)
du = ufl.TrialFunction(V)
u_ = ufl.TestFunction(V)
a = ufl.inner(sigma(du), epsilon(u_)) * ufl.dx
l = ufl.inner(f, u_) * ds(1)
u = fem.Function(V, name="Displacement")
bcs = [fixed_bc]
problem = LinearProblem(
a, l, u=u, bcs=bcs, petsc_options={"ksp_type": "cg", "pc_type": "gamg", "ksp_rtol": 1e-8}
)
uh = problem.solve()
R = diameter / 2
L = height
Fx, Fy, Fz = load
print("====================================================================")
print(" DEFORMATION ")
print("====================================================================")
Ix = np.pi / 4 * R**4
print(f" Analytic: {(Fx * L**3) / (3 * material.E * Ix)}")
print(f" Computed: {u.x.array.max()}")
print("====================================================================")
print(" REACTIONS ")
print("====================================================================")
Rx = -np.pi * R**2 * Fx
Ry = -np.pi * R**2 * Fy
Rz = -np.pi * R**2 * Fz
print(f" Analytic: Rx = {Rx:.3f}, Ry = {Ry:.3f}, Rz = {Rz:.3f}")
# ====================================================================
Rx = fem.assemble_scalar(fem.form(-sigma(u)[0, 0] * ds))
Ry = fem.assemble_scalar(fem.form(-sigma(u)[0, 1] * ds))
Rz = fem.assemble_scalar(fem.form(-sigma(u)[0, 2] * ds))
print(f" Direct: Rx = {Rx:.3f}, Ry = {Ry:.3f}, Rz = {Rz:.3f}")
# ====================================================================
residual = ufl.action(a, u) - l
v_reac = fem.Function(V)
virtual_work_form = fem.form(ufl.action(residual, v_reac))
def one(x):
values = np.zeros((1, x.shape[1]))
values[0] = 1.0
return values
u_bc.sub(0).interpolate(one)
fem.set_bc(v_reac.vector, bcs)
Rx = fem.assemble_scalar(virtual_work_form)
u_bc.vector.set(0.0)
v_reac.vector.set(0.0)
u_bc.sub(1).interpolate(one)
fem.set_bc(v_reac.vector, bcs)
Ry = fem.assemble_scalar(virtual_work_form)
u_bc.vector.set(0.0)
v_reac.vector.set(0.0)
u_bc.sub(2).interpolate(one)
fem.set_bc(v_reac.vector, bcs)
Rz = fem.assemble_scalar(virtual_work_form)
print(f"Virtual Work: Rx = {Rx:.3f}, Ry = {Ry:.3f}, Rz = {Rz:.3f}")
with the solution:
====================================================================
DEFORMATION
====================================================================
Analytic: 1.8634166653601339e-06
Computed: 6.168997972153805e-05
====================================================================
REACTIONS
====================================================================
Analytic: Rx = -331.831, Ry = -0.000, Rz = -0.000
Direct: Rx = 0.022, Ry = 0.000, Rz = -335.679
Virtual Work: Rx = -331.815, Ry = -0.000, Rz = -0.000
Soo seems that there is something wrong in the maximum displacement computation, as well as the “Direct” method to compute the reaction forces on the beam base, because its giving an approximate good results but in the wrong axis.
Also I want to compute the reaction bending moments, but I remove from here due to is really wrong results.