Source code for femorph_solver.validation._problems.cc_beam_central_load

"""Clamped-clamped beam under central point load.

Reference geometry
------------------

Prismatic slender beam of length :math:`L`, rectangular cross-
section, clamped rigidly at *both* ends (every DOF of the end
faces fixed) and loaded by a central transverse point load
:math:`P` at :math:`x = L/2`.

Closed-form quantities
----------------------

The fixed-end boundary conditions carry extra rotational
constraints that reduce the mid-span deflection by a factor of
4 compared to the simply-supported case:

.. math::

    \\delta_{\\text{mid}} = \\frac{P L^{3}}{192 E I}, \\qquad
    I = \\frac{w h^{3}}{12}.

The clamping moments at each end equal :math:`\\pm P L / 8`.

References
----------
* Timoshenko, S. P.  *Strength of Materials, Part I*, 3rd ed.,
  Van Nostrand, 1955, §5.7 — fixed-end beam with central load.
* Gere & Goodno (2018), *Mechanics of Materials* 9th ed., §10.3
  Table 10-1 — fixed-fixed beam deflection formulas.

Cross-references (public verification manuals — fair-use
citation of problem IDs only; no vendor content vendored):

* **Abaqus AVM 1.5.x** fixed-fixed beam 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 ClampedClampedBeamCentralLoad(BenchmarkProblem): """Clamped-clamped beam mid-span deflection under central point load.""" name: str = "cc_beam_central_load" description: str = ( "Prismatic clamped-clamped beam with central transverse " "point load — Euler-Bernoulli closed-form " "(Timoshenko 1955 §5.7)." ) analysis: str = "static" #: Beam length [m]. L: float = 1.0 #: Cross-section width [m]. width: float = 0.05 #: Cross-section height [m]. height: float = 0.05 #: Young's modulus [Pa]. E: float = 2.0e11 #: Poisson's ratio. nu: float = 0.3 rho: float = 7850.0 #: Total central transverse load [N] (acts in ``-z``). P: float = 1.0e3 @property def published_values(self) -> tuple[PublishedValue, ...]: I = self.width * self.height**3 / 12.0 # noqa: E741 delta = self.P * self.L**3 / (192.0 * self.E * I) return ( PublishedValue( name="mid_span_deflection", value=delta, unit="m", source="Timoshenko 1955 §5.7", formula="delta = P L^3 / (192 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) # Full clamp on both end faces — pinning every DOF at # x = 0 and x = L. Unlike the SS case, the fixed-fixed # configuration locks out both end rotations and end # translations, so no knife-edge idealisation is needed. both_ends = np.where((pts[:, 0] < 1e-9) | (pts[:, 0] > self.L - 1e-9))[0] m.fix(nodes=(both_ends + 1).tolist(), dof="ALL") # Central point load on the mid-span bottom line. 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_node = -self.P / len(load_nodes) for n in load_nodes: m.apply_force(int(n + 1), fz=fz_per_node) # Extract from the top-face centerline to avoid the # local-load indentation. 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
[docs] def extract(self, model: femorph_solver.Model, result: Any, name: str) -> float: u = np.asarray(result.displacement).reshape(-1, 3) if name == "mid_span_deflection": mid = model._bench_midspan_top_nodes # type: ignore[attr-defined] return float(-u[mid, 2].mean()) raise KeyError(f"unknown quantity {name!r}")