The Model#
femorph_solver.Model is the pre-processing wrapper. It holds a
pyvista.UnstructuredGrid (nodes + cells), the global material
and real-constant tables, the element-type registry, and the current
Dirichlet / force records. From it the solver family assembles the
global sparse \(K\) and \(M\).
Two ways to construct one#
- From an existing mesh
If you already have a
pyvista.UnstructuredGridor amapdl_archive.Archive:import mapdl_archive import femorph_solver archive = mapdl_archive.Archive("blade.cdb") m = femorph_solver.Model.from_grid(archive.grid)
from_gridexpects the MAPDL-style cell/point arrays (ansys_node_num,ansys_elem_num,ansys_elem_type_num,ansys_material_type,ansys_real_constant). Missing arrays are auto-filled with sequential 1-based ids, so a plain pyvista mesh also works.- By hand, native API
The first-class entry point is
Model.assign(), which declares the element kernel + material in one call:import femorph_solver as fs model = fs.Model.from_grid(grid) model.assign( fs.ELEMENTS.HEX8, {"EX": 2.0e11, "PRXY": 0.30, "DENS": 7850.0}, )
- By hand, APDL-dialect
For users porting an APDL deck command-by-command,
femorph_solver.interop.mapdl.APDLexposes theN/E/ET/MAT/MPverbs over the same Model. MAPDL element-catalogue spellings are translated to neutral kernel names at this boundary ("SOLID185"→"HEX8"etc.):from femorph_solver.interop.mapdl import APDL model = fs.Model() with APDL(model) as apdl: apdl.et(1, "SOLID185") # ET,1,SOLID185 apdl.type(1) # TYPE,1 apdl.mat(1) # MAT,1 apdl.n(1, 0, 0, 0) # N,1, 0, 0, 0 apdl.n(2, 1, 0, 0) # ... nodes 3..8 ... apdl.e(1, 2, 3, 4, 5, 6, 7, 8) # E,1,2,3,4,5,6,7,8
What’s on a Model#
The grid is the canonical mesh representation:
m.grid # pyvista.UnstructuredGrid
m.grid.point_data["ansys_node_num"]
m.grid.cell_data["ansys_elem_num"]
m.grid.cell_data["ansys_elem_type_num"]
m.grid.cell_data["ansys_material_type"]
m.grid.cell_data["ansys_real_constant"]
Shortcut accessors return typed views of the same data:
Attribute |
Returns |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Assembly#
Once pre-processing is done, materialise the global matrices:
K = m.stiffness_matrix() # scipy.sparse.csr_array
M = m.mass_matrix() # scipy.sparse.csr_array (consistent)
M_lumped = m.mass_matrix(lumped=True)
The same cached grid feeds every call, so successive assemblies reuse
the CSR sparsity pattern. The global DOF layout is exposed via
Model.dof_map() — an (N, 2) array of
(mapdl_node_num, local_dof_idx) that lets you translate between
solver vectors and physical node / component.
Solve shortcuts#
For the three most common analyses you can bypass the solver module and ask the Model directly:
static = m.solve() # linear static
modal = m.modal_solve(n_modes=10) # free-vibration modal
transient = m.transient_solve(dt=1e-4, n_steps=1000, F=load_vec)
These delegate to solve_static(),
solve_modal(), and
solve_transient() respectively,
passing the current Dirichlet / force records. For finer control
(non-auto backends, explicit thread limits, custom prescribed
dicts) call those solver functions directly with K and
M — see Solving.
See also#
Element library — which element types you can register.
Materials — the
mpcommand and the property enum.Real constants and sections — per-element real-constant layouts.
Boundary conditions and loads — Dirichlet and force commands.
femorph_solver.Model— full API reference.