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 in mapdl-archive and ansys-mapdl-reader.

  • [solvers] — pulls in the fast SPD direct backends (Pardiso, CHOLMOD, UMFPACK). Without it the solver falls through to scipy.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 sigma to the modal solver so the shift-invert factor doesn’t see a singular K:

    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 with model.solve_modal(lumped=True).

  • Element formulation — HEX8 with full integration shear- locks on bending-dominated meshes. Use the EAS variant (HEX8 with the integration="enhanced_strain" keyword) on bending-rich problems before comparing to MAPDL’s SOLID185 with the KEYOPT(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-sector Model in CyclicModel) 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 full K / M are 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, and

  • what MAPDL says about the deck (just running it through mapdl -b confirms 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#