Source code for femorph_solver.elements.combin14

"""COMBIN14 — 3D 2-node spring-damper (longitudinal mode only).

Default KEYOPT(2) = 0: longitudinal spring oriented along the I→J axis.
Real constants:

    real[0] : K    — spring stiffness (force / length)
    real[1] : CV1  — linear damping  (force · time / length, unused for K_e)
    real[2] : CV2  — non-linear (cubic) damping (unused here)
    real[3] : IL   — initial length (unused; reference length is the nodal distance)

Stiffness (Theory Ref § 14.14)::

    K_e = K · [[ C, -C],
               [-C,  C]]        with C_ij = d_i · d_j  (3 × 3)

where ``(d_x, d_y, d_z)`` is the unit vector along I→J.

COMBIN14 is massless by convention (modal codes treat it as a stiffness-only
contribution); ``me`` returns a 6×6 zero matrix so that assembly loops stay
uniform.

Only the longitudinal KEYOPT(2)=0 mode is implemented. Torsional (3) and
2-D planar (4, 5, 6) variants need separate code paths and will be added
alongside their datasets.

References
----------
* Discrete spring / lumped connector element (3-D longitudinal
  mode): Cook, Malkus, Plesha, Witt, *Concepts and Applications
  of Finite Element Analysis*, 4th ed., Wiley, 2002, §2.3 and
  §2.10 (direction-cosine rotation of a scalar spring between
  two nodes in 3-D).  Stiffness ``K · (d ⊗ d)`` on the I→J
  axis is the standard result.
* Direction-cosine transformation for a bar / spring aligned
  with an arbitrary 3-D axis: Zienkiewicz, O.C. and Taylor, R.L.,
  *The Finite Element Method*, 7th ed., 2013, §2.4.4.

MAPDL compatibility — specification source
------------------------------------------
* Ansys, Inc., *Ansys Mechanical APDL Element Reference*,
  Release 2022R2, section "COMBIN14 — Spring-Damper".

Short factual summary (paraphrased): 2-node spring-damper;
``KEYOPT(2)=0`` is the longitudinal (axial) mode, 3 translational
DOFs per node; spring stiffness from ``REAL[0]``; linear and
non-linear damping real constants are unused by femorph-solver's
linear-elastic path.  Torsional (``KEYOPT(2)=3``) and 2-D planar
modes are not yet implemented.  Ansys Element Reference is the
compat spec only.
"""

from __future__ import annotations

import numpy as np

from femorph_solver.elements._base import ElementBase
from femorph_solver.elements._registry import register


def _axial_frame(coords: np.ndarray) -> tuple[float, np.ndarray]:
    d = coords[1] - coords[0]
    L = float(np.linalg.norm(d))
    if L == 0.0:
        raise ValueError("COMBIN14: coincident nodes, element length is zero")
    return L, d / L


[docs] @register class COMBIN14(ElementBase): name = "COMBIN14" n_nodes = 2 n_dof_per_node = 3 # UX, UY, UZ — longitudinal KEYOPT(2)=0 vtk_cell_type = 3 # VTK_LINE
[docs] @staticmethod def ke( coords: np.ndarray, material: dict[str, float], real: np.ndarray, ) -> np.ndarray: _, d = _axial_frame(np.asarray(coords, dtype=np.float64)) real = np.asarray(real, dtype=np.float64) if real.size == 0: raise ValueError("COMBIN14 requires REAL[0]=K (spring constant); got empty real set") k_spring = float(real[0]) C = np.outer(d, d) k = k_spring * np.block([[C, -C], [-C, C]]) return np.ascontiguousarray(k)
[docs] @staticmethod def me( coords: np.ndarray, material: dict[str, float], real: np.ndarray, lumped: bool = False, ) -> np.ndarray: return np.zeros((6, 6), dtype=np.float64)