Quickstart#

A complete pre-process → solve → post-process cycle in ~20 lines of native Python. No MAPDL deck, no APDL syntax — femorph-solver drives its own mesh, material, and boundary-condition primitives, powered by a C++ assembly core.

The mesh is a quarter-arc sector of an annular disk that ships bundled with the wheel (femorph_solver.examples.quarter_arc_sector()).

import pyvista as pv
import femorph_solver as fs

# --- Pre-process ------------------------------------------------
grid = fs.examples.quarter_arc_sector()        # bundled HEX8 mesh
model = fs.Model.from_grid(grid)
model.assign("HEX8", {"EX": 2.1e11, "PRXY": 0.30, "DENS": 7850.0})

# Pin every grid point on the bottom face (z = min).
pts = grid.points
model.fix(where=pts[:, 2] <= pts[:, 2].min() + 1e-9)

# --- Solve ------------------------------------------------------
result = model.modal_solve(n_modes=10)

# --- Post-process ----------------------------------------------
print(f"First 5 modal frequencies (Hz): {result.frequency[:5]}")

deformed = grid.copy()
deformed["u"] = result.mode_shapes[:, 0].reshape(-1, 3)
deformed.warp_by_vector("u", factor=0.2, inplace=True)
pv.Plotter().add_mesh(deformed, color="lightblue").show()

What the three phases did#

  1. Pre-process. Loaded a bundled mesh, declared an 8-node hexahedral element kernel (femorph_solver.ElementType.HEX8), and stamped a steel material in a single assign() call. fix() pinned every grid point on the lowest-z face using a boolean predicate — no per-node loop.

  2. Solve. modal_solve() assembled sparse K / M via the C++ kernel, then handed off to the registered linear backend (Pardiso → CHOLMOD → SuperLU auto-chain) for shift-invert Lanczos.

  3. Post-process. Pulled the first mode shape off the result, reshaped it into a vector field, warped the grid, and rendered.

The native primitives#

femorph_solver.Model exposes a small, composable set of verbs. Each is keyword-driven and accepts Python-native inputs (boolean masks, scalar keyword components, element names from the neutral topology-first taxonomy):

Call

What it does

Model.from_grid(grid)

Build a model directly on a pyvista.UnstructuredGrid.

model.assign(element, material)

Declare an element kernel ("HEX8", "HEX20", "TET10", "BEAM2", "QUAD4_SHELL", …) and a material dict in one call.

model.fix(where=mask)

Pin every grid point selected by a boolean mask. Also accepts nodes= (scalar or array of node numbers) and dof= ("UX" / "UY" / "UZ" / "ALL" / …).

model.apply_force(node, fz=-100.0)

Keyword-driven nodal force or moment vector.

model.modal_solve(n_modes=10)

Full \(K\phi = \omega^2 M\phi\) modal sweep with shift-invert Lanczos. Returns a ModalResult with .frequency, .mode_shapes, .plot().

model.solve()

Linear static K u = f. Returns a StaticResult.

model.cyclic_modal_solve(...)

Cyclic-symmetry modal sweep per harmonic index.

Model also plugs into the materials library via femorph_solver.materials:

from femorph_solver.materials import fetch, UnitSystem
steel = fetch("SS304", units=UnitSystem.SI)
model.assign("HEX8", steel)

Next steps#

  • Quickstart — more runnable examples.

  • User guide — conceptual walk-through of each phase.

  • Analyses — deeper demos per analysis type (static / modal / cyclic / transient).

  • Elements — per-element kernel demos.

  • MAPDL interop — if your starting point is a MAPDL CDB deck, start there. The compatibility layer is first-class but secondary to the native API above.