Boundary conditions and loads#
Dirichlet constraints (fixed DOFs) and nodal forces / moments are
the minimum set of boundary conditions every structural analysis
needs. The native, Python-first surface is
fix() for Dirichlet and
apply_force() for loads. The legacy
APDL-dialect verbs (d / f) are still supported through the
APDL wrapper in femorph_solver.interop.mapdl for users
porting a MAPDL deck line-by-line.
DOF labels#
Every node has at most six DOFs, drawn from the enum
femorph_solver.DOFLabel:
Label |
Meaning |
|---|---|
|
Translational displacement along global x / y / z. |
|
Rotation about global x / y / z (beam / shell only). |
The effective DOF set at a node is the union of the DOFs every
adjacent element needs — a QUAD4_SHELL (SHELL181) node has all
six; a HEX8 (SOLID185) node has three (UX, UY, UZ);
a node touched by both sees all six. Rotational DOFs on a
solid-only region are silently dropped.
Loads use femorph_solver.ForceLabel — the parallel enum with
FX / FY / FZ and MX / MY / MZ.
Model.fix — Dirichlet constraints#
Model.fix takes a boolean mask
over the grid’s nodes (or an explicit node-id array) plus the DOF
label(s) to pin. Typical usage on the bundled quarter-arc example:
import numpy as np
import femorph_solver as fs
grid = fs.examples.quarter_arc_sector()
model = fs.Model.from_grid(grid)
model.assign("HEX8", {"EX": 2.1e11, "PRXY": 0.30, "DENS": 7850.0})
pts = np.asarray(grid.points)
clamped = np.isclose(pts[:, 2], pts[:, 2].min())
model.fix(where=clamped, dof=["UX", "UY", "UZ"]) # clamp the base
model.fix(where=pts[:, 0] > 0.99, dof="UZ", value=1e-3) # prescribed z-disp
The same call accepts either the raw string or a
DOFLabel member — both "UX" and
DOFLabel.UX resolve to the same DOF index.
Multiple fix calls with the same (node, label) pair
overwrite, so re-pinning a DOF updates its value.
Model.apply_force — nodal forces / moments#
Model.apply_force is
keyword-driven — one call can set several components on a single
node:
model.apply_force(101, fy=-1000.0) # 1 kN downward at node 101
model.apply_force(101, fy=-1000.0, mz=50.0) # or several components at once
As with fix(), labels pass through the
ForceLabel enum; unknown labels raise
ValueError at call time rather than silently masking a zero.
Inspecting BC state#
Model.bcs and
Model.loads are typed views
over the active constraints and loads:
>>> model.bcs
BoundaryConditions(n_pinned=12)
>>> model.bcs.to_table() # DataFrame-like summary
>>> model.loads
Loads(n_applied=1)
Both objects surface clear() / pin() / apply() helpers
so you can tweak state imperatively without reaching into the
Model’s internals.
The prescribed= argument#
Every solver entry point also accepts a prescribed dict so you
can bypass the Model’s BC records and set constraints directly
from an array-driven script:
K = model.stiffness_matrix()
M = model.mass_matrix()
# Fix DOFs for nodes 1..10 in all three translational directions.
dof_map = model.dof_map()
fixed_dofs = {
i for i, (node, d) in enumerate(dof_map)
if node <= 10 and d < 3 # 0=UX, 1=UY, 2=UZ
}
prescribed = {i: 0.0 for i in fixed_dofs}
result = solve_modal(K, M, prescribed=prescribed, n_modes=10)
Model.solve() / Model.modal_solve() /
Model.transient_solve() merge the Model’s BC records into this
dict automatically — use prescribed only when you’re calling
solve_modal / solve_static / solve_transient directly.
Cyclic-symmetry face constraints#
Cyclic-symmetry analyses add a second kind of constraint: paired
low-face / high-face DOFs relate by a phase-dependent rotation.
That’s a first-class argument to
solve_cyclic_modal() rather than
a BC record — see Cyclic-symmetry modal analysis.
Porting an APDL deck#
Users porting a MAPDL deck line-by-line can reach the APDL-dialect
verbs (D / F) through the context-managed wrapper:
from femorph_solver.interop.mapdl import APDL
with APDL(model) as apdl:
apdl.d(1, "ALL") # pin all DOFs at node 1
apdl.f(101, "FY", -1000.0) # nodal force
APDL writes into the same Model.bcs / Model.loads
state as fix() / apply_force(); the two styles are
interchangeable and can be mixed in one model. Calling d / f
directly on Model emits a DeprecationWarning that
points at these two replacements.
Limitations (today)#
Only zero-value Dirichlet is lossless. Non-zero
fix(where=..., value=...)constraints work insolve_static()but the modal / cyclic paths require zero-value BCs at prescribed DOFs (they drop the row/column entirely rather than shifting the eigenproblem).No inertia / body-force loads.
ACEL-style gravity isn’t exposed yet; build the equivalent nodal force yourself if you need a body load.No element-face pressure.
SFEpressure loads are parsed from a CDB but not converted into equivalent nodal forces.
See also#
The Model — grid construction and
Model.assign()for the element + material setup that bracket BC application.Static analysis — how
K u = Fis partitioned when Dirichlet DOFs are present.