"""Propped cantilever — fixed end + simple support under central load.
A propped cantilever is a beam clamped at one end
(``x = 0``) and simply supported at the other (``x = L``) —
a common statically-indeterminate case that needs
compatibility to resolve the reactions.
Under a central point load :math:`P` at ``x = L/2``:
* Mid-span deflection:
.. math::
\\delta_{\\text{mid}} = \\frac{7 P L^{3}}{768 E I}.
* Reactions:
.. math::
R_A &= \\frac{11 P}{16} \\text{ (fixed end)}, \\\\
R_B &= \\frac{5 P}{16} \\text{ (simple end)}.
* Fixed-end moment:
.. math::
M_A = -\\frac{3 P L}{16}.
References
----------
* Gere, J. M. and Goodno, B. J. *Mechanics of Materials*,
9th ed., Cengage, 2018, §10.3 Table 10-1 Case 6 —
propped cantilever deflection formulas.
* Timoshenko, S. P. *Strength of Materials, Part I*, 3rd ed.,
Van Nostrand, 1955, §5.8 — statically-indeterminate beams.
Cross-references (public verification manuals — fair-use
citation of problem IDs only; no vendor content vendored):
* **Abaqus AVM 1.5.x** propped-cantilever family.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import numpy as np
import pyvista as pv
import femorph_solver
from femorph_solver import ELEMENTS
from femorph_solver.validation._benchmark import (
BenchmarkProblem,
PublishedValue,
)
[docs]
@dataclass
class ProppedCantileverCentralLoad(BenchmarkProblem):
"""Propped cantilever mid-span deflection under central point load."""
name: str = "propped_cantilever_central_load"
description: str = (
"Fixed-pinned beam with central transverse point load — "
"Euler-Bernoulli closed-form "
"(Gere & Goodno 2018 §10.3 Table 10-1 Case 6)."
)
analysis: str = "static"
L: float = 1.0
width: float = 0.05
height: float = 0.05
E: float = 2.0e11
nu: float = 0.3
rho: float = 7850.0
P: float = 1.0e3
@property
def published_values(self) -> tuple[PublishedValue, ...]:
I = self.width * self.height**3 / 12.0 # noqa: E741
delta = 7.0 * self.P * self.L**3 / (768.0 * self.E * I)
return (
PublishedValue(
name="mid_span_deflection",
value=delta,
unit="m",
source="Gere & Goodno 2018 §10.3 Table 10-1 Case 6",
formula="delta_mid = 7 P L^3 / (768 E I)",
tolerance=0.05,
),
)
[docs]
def build_model(self, **mesh_params: Any) -> femorph_solver.Model:
nx = int(mesh_params.get("nx", 40))
if nx % 2 != 0:
nx += 1
ny = int(mesh_params.get("ny", 3))
nz = int(mesh_params.get("nz", 3))
xs = np.linspace(0.0, self.L, nx + 1)
ys = np.linspace(0.0, self.width, ny + 1)
zs = np.linspace(0.0, self.height, nz + 1)
grid = pv.StructuredGrid(
*np.meshgrid(xs, ys, zs, indexing="ij")
).cast_to_unstructured_grid()
m = femorph_solver.Model.from_grid(grid)
m.assign(
ELEMENTS.HEX8(integration="enhanced_strain"),
material={"EX": self.E, "PRXY": self.nu, "DENS": self.rho},
)
pts = np.asarray(m.grid.points)
# Fixed end: full clamp on the left face x = 0.
left_face = np.where(pts[:, 0] < 1e-9)[0]
m.fix(nodes=(left_face + 1).tolist(), dof="ALL")
# Simple support at the right end (roller, UZ pinned along
# the bottom-line knife-edge); axial UX left free to avoid
# locking out axial strain. UY pinned at a single corner
# node to kill the lateral rigid-body mode.
right_line = np.where((pts[:, 0] > self.L - 1e-9) & (pts[:, 2] < 1e-9))[0]
for n in right_line:
m.fix(nodes=int(n + 1), dof="UZ", value=0.0)
right_y_pin = np.where(
(pts[:, 0] > self.L - 1e-9) & (pts[:, 1] < 1e-9) & (pts[:, 2] < 1e-9)
)[0]
for n in right_y_pin:
m.fix(nodes=int(n + 1), dof="UY", value=0.0)
# Central point load: mid-span bottom-line nodes.
x_mid = 0.5 * self.L
load_nodes = np.where((np.abs(pts[:, 0] - x_mid) < 1e-9) & (pts[:, 2] < 1e-9))[0]
fz_per = -self.P / len(load_nodes)
for n in load_nodes:
m.apply_force(int(n + 1), fz=fz_per)
# Extract from the top-face centerline to sidestep the
# local-load stress concentration.
m._bench_midspan_top_nodes = np.where(
(np.abs(pts[:, 0] - x_mid) < 1e-9) & (pts[:, 2] > self.height - 1e-9)
)[0] # type: ignore[attr-defined]
return m