MASS21 — single-DOF spring-mass oscillator#

Two nodes connected by a COMBIN14 spring. A MASS21 element sits on the free node. The first modal frequency is compared to the textbook SDOF result ω = √(k / m).

from __future__ import annotations

import numpy as np
import pyvista as pv

import femorph_solver

Problem data#

k = 1 kN/m, m = 0.25 kgω = √(4000) 63.2456 rad/s (f 10.065 Hz).

k = 1000.0
mass = 0.25
omega_expected = np.sqrt(k / mass)
f_expected = omega_expected / (2.0 * np.pi)

Build the model#

Two element types: COMBIN14 (spring, TYPE 1) and MASS21 (point mass, TYPE 2). Each gets its own REAL set. The type / real verbs set the stamps that subsequent e calls inherit — the same mechanism MAPDL uses.

m = femorph_solver.Model()

m.et(1, "COMBIN14")
m.r(1, k)

m.et(2, "MASS21")
m.r(2, mass)

m.n(1, 0.0, 0.0, 0.0)
m.n(2, 1.0, 0.0, 0.0)

m.type(1)
m.real(1)
m.e(1, 2)  # spring from 1 to 2

m.type(2)
m.real(2)
m.e(2)  # point mass at node 2 (single-node element)

m.d(1, "ALL")  # clamp the spring base
m.d(2, "UY")  # kill transverse rigid-body modes
m.d(2, "UZ")
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:38: DeprecationWarning: Model.et(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).et(et_id, name)` for line-by-line APDL deck porting, or the native `Model.assign("HEX8", material)` for new code.
  m.et(1, "COMBIN14")
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:39: DeprecationWarning: Model.r(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).r(real_id, *values)` for line-by-line APDL deck porting, or the native `Model.assign(element, material, real=[...])` for new code.
  m.r(1, k)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:41: DeprecationWarning: Model.et(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).et(et_id, name)` for line-by-line APDL deck porting, or the native `Model.assign("HEX8", material)` for new code.
  m.et(2, "MASS21")
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:42: DeprecationWarning: Model.r(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).r(real_id, *values)` for line-by-line APDL deck porting, or the native `Model.assign(element, material, real=[...])` for new code.
  m.r(2, mass)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:44: DeprecationWarning: Model.n(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).n(num, x, y, z)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.n(1, 0.0, 0.0, 0.0)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:45: DeprecationWarning: Model.n(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).n(num, x, y, z)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.n(2, 1.0, 0.0, 0.0)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:47: DeprecationWarning: Model.type(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).type(et_id)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.type(1)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:48: DeprecationWarning: Model.real(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).real(real_id)` for line-by-line APDL deck porting, or the native `Model.assign("HEX8", material, real=[...])` for new code.
  m.real(1)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:49: DeprecationWarning: Model.e(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).e(*node_nums)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.e(1, 2)  # spring from 1 to 2
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:51: DeprecationWarning: Model.type(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).type(et_id)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.type(2)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:52: DeprecationWarning: Model.real(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).real(real_id)` for line-by-line APDL deck porting, or the native `Model.assign("HEX8", material, real=[...])` for new code.
  m.real(2)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:53: DeprecationWarning: Model.e(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).e(*node_nums)` for line-by-line APDL deck porting, or the native `Model.from_grid(pv_grid)` for new code.
  m.e(2)  # point mass at node 2 (single-node element)
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:55: DeprecationWarning: Model.d(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).d(node, label, value)` for line-by-line APDL deck porting, or the native `Model.fix(nodes=..., where=..., dof=...)` for new code.
  m.d(1, "ALL")  # clamp the spring base
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:56: DeprecationWarning: Model.d(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).d(node, label, value)` for line-by-line APDL deck porting, or the native `Model.fix(nodes=..., where=..., dof=...)` for new code.
  m.d(2, "UY")  # kill transverse rigid-body modes
/home/runner/_work/solver/solver/examples/elements/mass21/example_mass21.py:57: DeprecationWarning: Model.d(...) is a MAPDL-dialect shortcut and has moved off the Model public surface.  Use `APDL(model).d(node, label, value)` for line-by-line APDL deck porting, or the native `Model.fix(nodes=..., where=..., dof=...)` for new code.
  m.d(2, "UZ")

Plot the mode shape#

Dilate the mode shape for visualisation. With MASS21 being a single-node element the mesh contains a VTK_LINE (the spring) and a VTK_VERTEX (the mass) — pyvista happily draws the combined unstructured grid.

dof = m.dof_map()
mode = res.mode_shapes[:, 0]
grid = m.grid.copy()
displacement = np.zeros((grid.n_points, 3), dtype=np.float64)
for i, nn in enumerate(grid.point_data["ansys_node_num"]):
    rows = np.where(dof[:, 0] == int(nn))[0]
    for r in rows:
        displacement[i, int(dof[r, 1])] = mode[r]
# Scale so the peak is 0.3 m for clarity.
peak = float(np.max(np.abs(displacement))) or 1.0
displacement *= 0.3 / peak
grid.point_data["mode1"] = displacement

warped = grid.warp_by_vector("mode1", factor=1.0)
plotter = pv.Plotter(off_screen=True)
plotter.add_mesh(grid, style="wireframe", color="gray", line_width=3)
plotter.add_mesh(
    warped,
    color="tomato",
    line_width=6,
    render_points_as_spheres=True,
    point_size=18,
)
plotter.add_text(f"f1 = {f_computed:.3f} Hz", font_size=12)
plotter.add_axes()
plotter.show()
example mass21

Total running time of the script: (0 minutes 0.542 seconds)

Gallery generated by Sphinx-Gallery