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 :meth:`~femorph_solver.Model.fix` for Dirichlet and :meth:`~femorph_solver.Model.apply_force` for loads. The legacy APDL-dialect verbs (``d`` / ``f``) are still supported through the ``APDL`` wrapper in :mod:`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 :class:`femorph_solver.DOFLabel`: .. list-table:: :widths: 15 85 :header-rows: 1 * - Label - Meaning * - ``UX``, ``UY``, ``UZ`` - Translational displacement along global x / y / z. * - ``ROTX``, ``ROTY``, ``ROTZ`` - 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 :class:`femorph_solver.ForceLabel` — the parallel enum with ``FX`` / ``FY`` / ``FZ`` and ``MX`` / ``MY`` / ``MZ``. Model.fix — Dirichlet constraints --------------------------------- :meth:`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: .. code-block:: python 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 :class:`~femorph_solver.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 ------------------------------------------ :meth:`Model.apply_force ` is keyword-driven — one call can set several components on a single node: .. code-block:: python 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 :meth:`fix`, labels pass through the :class:`~femorph_solver.ForceLabel` enum; unknown labels raise ``ValueError`` at call time rather than silently masking a zero. Inspecting BC state ------------------- :attr:`Model.bcs ` and :attr:`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) :meth:`Model.solve` / :meth:`Model.modal_solve` / :meth:`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 :func:`~femorph_solver.solvers.cyclic.solve_cyclic_modal` rather than a BC record — see :doc:`/user-guide/solving/cyclic`. 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 :attr:`Model.bcs` / :attr:`Model.loads` state as :meth:`fix` / :meth:`apply_force`; the two styles are interchangeable and can be mixed in one model. Calling ``d`` / ``f`` directly on :class:`Model` emits a :class:`DeprecationWarning` that points at these two replacements. Limitations (today) ------------------- - **Only zero-value Dirichlet is lossless.** Non-zero ``fix(where=..., value=...)`` constraints work in :func:`solve_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.** ``SFE`` pressure loads are parsed from a CDB but not converted into equivalent nodal forces. See also -------- - :doc:`model` — grid construction and :meth:`Model.assign` for the element + material setup that bracket BC application. - :doc:`/user-guide/solving/static` — how ``K u = F`` is partitioned when Dirichlet DOFs are present.