Quickstart ========== A complete **pre-process → solve → post-process** cycle in ~20 lines of native Python. No foreign-deck file, no APDL / Abaqus / NASTRAN input 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 (:func:`femorph_solver.examples.quarter_arc_sector`). .. code-block:: python 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 (:class:`femorph_solver.ElementType.HEX8`), and stamped a steel material in a single :meth:`~femorph_solver.Model.assign` call. :meth:`~femorph_solver.Model.fix` pinned every grid point on the lowest-z face using a boolean predicate — no per-node loop. 2. **Solve.** :meth:`~femorph_solver.Model.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 --------------------- :class:`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): .. list-table:: :widths: 32 68 :header-rows: 1 * - Call - What it does * - ``Model.from_grid(grid)`` - Build a model directly on a :class:`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 :math:`K\phi = \omega^2 M\phi` modal sweep with shift-invert Lanczos. Returns a :class:`~femorph_solver.result.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. :class:`~femorph_solver.Model` also plugs into the materials library via :mod:`femorph_solver.materials`:: from femorph_solver.materials import fetch, UnitSystem steel = fetch("SS304", units=UnitSystem.SI) model.assign("HEX8", steel) Next steps ---------- - :doc:`/gallery/quickstart/index` — more runnable examples. - :doc:`/user-guide/index` — conceptual walk-through of each phase. - :doc:`/gallery/analyses/index` — deeper demos per analysis type (static / modal / cyclic / transient). - :doc:`/gallery/elements/index` — per-element kernel demos. - :doc:`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.