Source code for femorph_solver.elements.point_mass
"""PointMass — concentrated point mass.
femorph-solver implements the *translational-only* variant: a
single-node element carrying three translational DOFs ``UX, UY, UZ``.
The rotary-inertia variant requires 6 DOFs/node and will be added
alongside Beam2 once mixed-DOF assembly is in place.
Real constants
--------------
real[0]: MASSX — translational mass along x (mandatory)
real[1]: MASSY — translational mass along y (defaults to MASSX)
real[2]: MASSZ — translational mass along z (defaults to MASSX)
Matrices
--------
K_e = 0₃×₃ (a point mass has no stiffness contribution)
M_e = diag(MASSX, MASSY, MASSZ)
Lumped and consistent formulations coincide for a one-node element.
References
----------
* Concentrated / point-mass element (lumped nodal mass, no stiffness
contribution): Cook, Malkus, Plesha, Witt, *Concepts and
Applications of Finite Element Analysis*, 4th ed., Wiley, 2002,
§16.3 (discussion of lumped nodal masses). Bathe, K.J., *Finite
Element Procedures*, 2nd ed., Prentice Hall, 2014, §4.2.4.
* Anisotropic diagonal mass tensor (``diag(MASSX, MASSY, MASSZ)``,
supports different masses on each translational axis): standard
generalisation of the isotropic ``m·I₃`` lumped mass.
"""
from __future__ import annotations
import numpy as np
from femorph_solver.elements._base import ElementBase
from femorph_solver.elements._registry import register
def _mass_vector(real: np.ndarray) -> np.ndarray:
r = np.asarray(real, dtype=np.float64)
if r.size == 0:
raise ValueError("PointMass requires REAL[0]=MASSX (point mass); got empty real set")
mx = float(r[0])
my = float(r[1]) if r.size > 1 else mx
mz = float(r[2]) if r.size > 2 else mx
return np.array([mx, my, mz], dtype=np.float64)
[docs]
@register
class PointMass(ElementBase):
name = "POINT_MASS"
n_nodes = 1
n_dof_per_node = 3 # UX, UY, UZ — translational-only (no rotary inertia)
vtk_cell_type = 1 # VTK_VERTEX
[docs]
@staticmethod
def ke(
coords: np.ndarray,
material: dict[str, float],
real: np.ndarray,
) -> np.ndarray:
return np.zeros((3, 3), dtype=np.float64)
[docs]
@staticmethod
def me(
coords: np.ndarray,
material: dict[str, float],
real: np.ndarray,
lumped: bool = False,
) -> np.ndarray:
return np.diag(_mass_vector(real))