Frequently asked questions#
Distilled from the issue tracker. If a question repeatedly shows up in support channels, it gets a permanent answer here so users hit the doc instead of waiting for a reply.
For the longer-form treatment of any of these, follow the cross- references. When a question is best answered by another page, the FAQ entry just links there rather than restating it.
Installation and import#
The build fails with Python.h not found on a fresh
checkout. What’s missing?
The C++ extension needs a development install of Python (i.e. the
header files). On Debian / Ubuntu install python3-dev; on
NixOS / nix-shell drop in the python3.12 derivation; on macOS
the system Python ships with the headers but a brew-installed
Python may need brew install python@3.12. See
Installation.
Why does import femorph_solver emit a
`DeprecationWarning` about `mapdl_api`?
femorph_solver.mapdl_api is the legacy import path; the current
home is femorph_solver.interop.mapdl. The shim still works
for one release cycle. Update imports:
# Old
from femorph_solver.mapdl_api import from_cdb
# New
from femorph_solver.interop.mapdl import from_cdb
Optional extras ([solvers] , [mapdl] ) **— do I
need them?
[mapdl]— required if you read MAPDL CDB / RST / EMAT / FULL files. Pulls inmapdl-archiveandansys-mapdl-reader.[solvers]— pulls in the fast SPD direct backends (Pardiso, CHOLMOD, UMFPACK). Without it the solver falls through toscipy.linalg.splu(SuperLU) which is correct but noticeably slower on problems past ~50 k DOF.
The core package always works without any extras.
Solver behaviour#
My modal solve returns six near-zero frequencies. Is the model broken?
No — those are the six rigid-body modes of an unconstrained 3-D solid. Either:
Add Dirichlet constraints (
model.fix(...)) so the model isn’t free-floating; the solver will then return the elastic modes only.Or pass a small negative
sigmato the modal solver so the shift-invert factor doesn’t see a singularK:model.solve_modal(n_modes=10, sigma=-1.0)
See Modal eigenvalue problem and shift-invert Lanczos for the math and Troubleshooting flowchart for the diagnostic flowchart.
The first natural frequency from femorph-solver is ~1 % off the MAPDL value on the same mesh. Is the kernel wrong?
Probably not. Some sources of small differences that are not bugs:
Mass-matrix flavour — femorph-solver defaults to the consistent mass; if the comparison MAPDL solve used the lumped mass (
LUMPM,ON), the frequencies will differ. Re-run withmodel.solve_modal(lumped=True).Element formulation — HEX8 with full integration shear- locks on bending-dominated meshes. Use the EAS variant (
HEX8with theintegration="enhanced_strain"keyword) on bending-rich problems before comparing to MAPDL’s SOLID185 with theKEYOPT(2)=2(incompatible-modes) option.Mesh curvature — quadratic elements on coarse meshes introduce different geometric error than linear elements; a per-element check that the Jacobians are within bounds is the place to start.
For a 1 % difference, run the same problem on a hand-built analytical reference (e.g. the cantilever Euler-Bernoulli formula); whoever’s closer to the analytic answer is the right one.
The static solve raises factor failed: matrix is singular.
What now?
Almost always means the BCs leave a rigid-body mode unconstrained. Common causes:
Forgot to clamp the model entirely — a mesh has its translational rigid-body modes free.
Pinned only the translation DOFs on a beam / shell mesh that carries rotational DOFs too.
Coupled-DOF (
CP) chains that close into a circular constraint and produce a zero-pivot in \(K_{ff}\).
The Troubleshooting flowchart flowchart walks each case down to the fix.
Is solve_modal deterministic? I’m getting eigenvector
sign-flips between runs.
Eigenvalues are deterministic (same input → same numbers, modulo last-digit BLAS jitter). Eigenvector sign is not defined by the eigenproblem; ARPACK / LOBPCG / Pardiso may return \(+\varphi\) or \(-\varphi\) for the same mode depending on the random restart vector. Use the absolute mode shape, the mass-normalised displacement at a reference DOF, or the modal assurance criterion (MAC) for downstream comparison rather than raw element-wise sign.
Why does solve_modal on a 1 M-DOF mesh run out of RAM
even though I have 32 GB?
The factoriser doubles the in-memory peak because it has to keep
both K and the factor live during shift-invert. Two options:
Reduce the problem.
CyclicModel.solve_modal(wrap a single-sectorModelinCyclicModel) is the canonical solution for full-rotor problems; an axisymmetric solid model is the canonical solution for revolution-symmetric problems.Pass
release_inputs=True(the default) so the cached fullK/Mare dropped before the factor allocates, but otherwise the only knob left is a smaller mesh.
See Out-of-core (OOC) Pardiso — limits and tuning for the long-form discussion.
Reading foreign decks#
My CDB / BDF / INP loads but model.solve_static() complains
about missing material properties.
The reader registers the deck’s element-type / element-list /
node-list, but materials are an MAPDL MP / NASTRAN MAT1 /
Abaqus *MATERIAL card that the parser turns into a separate
table. If the deck declared materials, they are on the model
already; if not, you’ll need to assign them explicitly via
assign():
model.assign(
ELEMENTS.HEX8,
{"EX": 2.0e11, "PRXY": 0.30, "DENS": 7850.0},
)
The Vendor verification-manual coverage matrix lists which decks ship with materials and which need them assigned.
An MAPDL deck I can run in MAPDL fails to load via ``from_cdb``. Bug?
Possible, but check Vendor verification-manual coverage matrix first — some KEYOPT combinations are tracked as known gaps with linked issues. If the deck uses one of those, the reader silently skips the unrecognised card.
If the gap is unfiled, please report it at femorph/solver#issues with:
the smallest deck that reproduces the load failure,
femorph_solver.Report()output, andwhat MAPDL says about the deck (just running it through
mapdl -bconfirms the deck itself is well-formed).
Performance and threading#
``solve_modal`` runs single-threaded even on a 32-core box. How do I parallelise?
The factor and back-solve respect BLAS / OpenMP threading. Set the cap explicitly:
import femorph_solver as fs
fs.set_thread_limit(8)
or via the environment variable FEMORPH_SOLVER_NUM_THREADS=8.
On shared CI runners cap to ~4 to avoid oversubscription; on a
dedicated workstation 1× physical core count is safe.
There is no MPI / multi-process path today — see Known limitations for the long form.
Pardiso is installed but ``linear_solver=”auto”`` falls through to SuperLU. Why?
Most likely the linked MKL doesn’t expose the SPD direct path the dispatch order requires. Check:
print(femorph_solver.Report())
The Registered solvers block reports each backend’s
available() probe. A “no” next to a backend means its
available() returned False; the Notes column usually
explains why.
Forcing the backend to fail loudly rather than silently fall through:
model.solve_static(linear_solver="pardiso")
raises a clear error if Pardiso isn’t actually loadable.
Documentation and contributing#
The docs talk about a function / class but I can’t find it in the API reference.
The API reference page api/index.rst is hand-curated as of
v0.20.x. Issue
#587 tracks the
refactor to autosummary-recursive so every public symbol gets an
auto-generated page. In the meantime, the symbol exists in the
source — grep -nE 'def <name>' src/femorph_solver is the
canonical way to locate it.
A docstring example doesn’t match the current behaviour. How do I report it?
Doctest gating is on for the modules in
tests/test_doctest_modules.py’s allowlist; if the example is
on an allowlisted module, that’s a CI bug — please file an issue.
For modules not yet on the allowlist, the docstring is treated as
illustrative; please file the issue anyway with the offending
docstring + the call that produced the actual behaviour.
I’d like to add a feature. What’s the contribution flow?
Trunk-based. Open a branch off main, target main with
your PR, and follow the rules in Documentation style guide
(for documentation) and Provenance inventory (for new
numerical kernels — every kernel gets a sourcing block).
Discuss the approach on a tracker issue before writing code if
the change is non-trivial; the project admin labels approach-
agreement issues with approach-ok so the PR doesn’t risk
landing on a design choice that won’t merge.
See also#
Troubleshooting flowchart — symptom-driven flowchart.
Known limitations — capabilities the project explicitly does not have.
Changelog — what changed in each release, with PR / issue links.
Architecture and design philosophy — the layer-by-layer design rationale.