Linear static analysis of beams and frames#
An L-shaped frame of BeamC elements, clamped at the base of the
column and loaded at the free end of the beam, in its plane and out of its
plane. The orientation vector of update_rotation_matrix puts the
element \(z\) axis of both members along the global \(Z\) axis, such that the
in-plane bending is governed by Izz and the out-of-plane bending by
Iyy, see Conventions. The displacements of the free end, with
the bending, shear, axial and torsional deformations, are exact with one
element per member, and the internal forces at the base are the support
reactions:
def frame(num_elements, H=3., L=2.):
r"""Nodes, elements, property and ``KC0`` of the L-shaped frame, with
``num_elements`` elements per member"""
E = 200.e9
nu = 0.3
b = 0.05 # width along the element z axis
h = 0.10 # height along the element y axis
prop = BeamProp()
prop.E = E
# NOTE no shear correction, A is the shear area
prop.G = E/(2*(1 + nu))
prop.A = b*h
prop.Izz = b*h**3/12
prop.Iyy = h*b**3/12
prop.J = 1.5e-6
# NOTE column from (0, 0, 0) to (0, H, 0), then beam to (L, H, 0)
s = np.linspace(0, 1, num_elements + 1)
column = np.column_stack((0*s, H*s, 0*s))
beam = np.column_stack((L*s[1:], H + 0*s[1:], 0*s[1:]))
ncoords = np.vstack((column, beam))
x = ncoords.flatten()
num_nodes = ncoords.shape[0]
N = DOF*num_nodes
data = BeamCData()
probe = BeamCProbe()
num_beams = num_nodes - 1
KC0r = np.zeros(data.KC0_SPARSE_SIZE*num_beams, dtype=INT)
KC0c = np.zeros(data.KC0_SPARSE_SIZE*num_beams, dtype=INT)
KC0v = np.zeros(data.KC0_SPARSE_SIZE*num_beams, dtype=DOUBLE)
elements = []
init_k_KC0 = 0
for pos1 in range(num_beams):
pos2 = pos1 + 1
elem = BeamC(probe)
elem.n1 = pos1 + 1
elem.n2 = pos2 + 1
elem.c1 = DOF*pos1
elem.c2 = DOF*pos2
elem.init_k_KC0 = init_k_KC0
# NOTE the element z axis is Z for both members: in-plane bending
# with Izz, out-of-plane bending with Iyy
if pos1 < num_elements:
elem.update_rotation_matrix(-1., 0., 0., x) # column
else:
elem.update_rotation_matrix(0., 1., 0., x) # beam
elem.update_probe_xe(x)
elem.update_KC0(KC0r, KC0c, KC0v, prop)
elements.append(elem)
init_k_KC0 += data.KC0_SPARSE_SIZE
KC0 = coo_matrix((KC0v, (KC0r, KC0c)), shape=(N, N)).tocsc()
return elements, prop, x, N, KC0
@pytest.mark.parametrize('num_elements', [1, 4])
def test_l_frame_tip_displacements(num_elements):
H = 3.
L = 2.
P = 1.e3
elements, prop, x, N, KC0 = frame(num_elements, H=H, L=L)
E, G, A = prop.E, prop.G, prop.A
Izz, Iyy, J = prop.Izz, prop.Iyy, prop.J
# NOTE clamped base, the first node
bk = np.zeros(N, dtype=bool)
bk[:DOF] = True
bu = ~bk
KC0uu = KC0[bu, :][:, bu]
tip = N - DOF
# in-plane load at the free end
f = np.zeros(N)
f[tip + 1] = -P
u = np.zeros(N)
u[bu] = solve(KC0uu, f[bu])
v_ref = -P*(L**3/(3*E*Izz) + L/(G*A) + L**2*H/(E*Izz) + H/(E*A))
u_ref = P*L*H**2/(2*E*Izz)
rz_ref = -P*(L*H/(E*Izz) + L**2/(2*E*Izz))
print('v', u[tip + 1], v_ref, 'u', u[tip + 0], u_ref)
assert np.isclose(u[tip + 1], v_ref, rtol=1e-8)
assert np.isclose(u[tip + 0], u_ref, rtol=1e-8)
assert np.isclose(u[tip + 5], rz_ref, rtol=1e-8)
# NOTE the internal forces at the base are the support reactions,
# balancing the load and its moment P*L about Z
fint = np.zeros(N)
for elem in elements:
elem.update_probe_ue(u)
elem.update_fint(fint, prop)
assert np.allclose(fint[:DOF], [0, P, 0, 0, 0, P*L], atol=1e-6*P*L)
assert np.allclose(fint[bu], f[bu], atol=1e-6*P)
# out-of-plane load at the free end
f = np.zeros(N)
f[tip + 2] = P
u = np.zeros(N)
u[bu] = solve(KC0uu, f[bu])
w_ref = P*((L**3 + H**3)/(3*E*Iyy) + (L + H)/(G*A) + L**2*H/(G*J))
print('w', u[tip + 2], w_ref)
assert np.isclose(u[tip + 2], w_ref, rtol=1e-8)
The tip deflection of a cantilever with shear deformation, Eq. (1.2) of Bathe
(2014), Finite Element Procedures, 2nd ed., Sec. 1.2, p. 5, exact with one
BeamC element, whereas BeamLR converges with mesh
refinement:
def cantilever_tip(name, prop, L, num_elements):
"""Tip deflections along y and z under unit tip forces"""
KC0, _ = assemble(name, prop, L, num_elements)
KC0 = KC0.toarray()
N = KC0.shape[0]
bu = np.ones(N, dtype=bool)
bu[:DOF] = False
tip = []
for direction in [1, 2]:
f = np.zeros(N)
f[N - DOF + direction] = 1.
u = np.zeros(N)
u[bu] = np.linalg.solve(KC0[np.ix_(bu, bu)], f[bu])
tip.append(u[N - DOF + direction])
return np.array(tip)
@pytest.mark.parametrize('name, num_elements, rtol', [
('BeamC', 1, 1e-10),
('BeamC', 3, 1e-10),
# NOTE BeamLR converges quadratically; error 0.02 % with 32 elements
('BeamLR', 100, 1e-4),
])
def test_bathe_cantilever_shear_deflection(name, num_elements, rtol):
L = 28. # cm, L + r_N in Fig. 1.2(b) of Bathe (2014)
W = 1000. # N
prop = bathe_prop()
delta = W*cantilever_tip(name, prop, L, num_elements)
# Eq. (1.2) of Bathe (2014)
delta_ref = W*L**3/(3*prop.E*prop.Izz) + W*L/(prop.A*prop.G)
print(name, num_elements, delta, delta_ref)
assert np.allclose(delta, delta_ref, rtol=rtol)
# NOTE published value, given with two significant digits
assert np.allclose(delta, 0.053, atol=0.0005)