hermit.bcs ========== .. py:module:: hermit.bcs .. autoapi-nested-parse:: ``BoundaryConditions`` -- the BC builders ``clamp`` / ``pin`` / ``symmetry`` / ``gauge``, compiled once against a :class:`hermit.ShellDomain`. No CSDL is involved, so a compiled ``BoundaryConditions`` survives across every ``solve`` for its domain. Two enforcement paths are available: a penalty term over the selected facets (the default; it eliminates no dofs and supports several independently masked regions in one form) and strong Dirichlet constraints (``method="strong"``). The penalty scale is large, so under a large geometry perturbation the strong path conditions better -- see the shape-derivative docs. Selecting a region: ``where`` ----------------------------- ``where`` is the raw DOLFINx entity locator, ``Callable[[ndarray], ndarray[bool]]``, receiving coordinates of shape ``(3, N)`` and returning an ``(N,)`` boolean mask. Two helpers build the common cases: - :func:`near` -- ``x[axis] == value`` within an absolute tolerance. - :func:`on_plane` -- points on a plane through a point with a given normal, which need not be axis-aligned. Prescribed values ----------------- Every builder takes ``value=`` -- a scalar, a ``(6,)`` vector over ``(ux, uy, uz, rx, ry, rz)``, or a callable returning ``(N, 6)`` for its ``(N, 3)`` coordinates. It defaults to zero. A ``csdl.Variable`` is deliberately **not** accepted: an enforced displacement is interpolated once, not carried as a differentiable solve input, so it cannot be a design variable. Loads and material properties can. The ``symmetry`` mask --------------------- On a symmetry plane with unit normal ``n``, the constrained dofs are the displacement *along* ``n`` and the two rotations *about the in-plane axes*; the rotation about ``n`` itself is left free. For an axis-aligned plane with normal index ``k`` that is ``mask = (m0, m1, m2, r0, r1, r2)`` with ``m_k = 1`` and ``r_i = 1`` for ``i != k`` -- normal ``+x`` gives ``(1, 0, 0, 0, 1, 1)``, the standard "SPC on trans-x / rot-y / rot-z" convention. The penalty machinery supports only an axis-aligned 6-component mask, so ``symmetry`` raises on a rotated normal. Merging: later terms win on overlap ----------------------------------- ``a + b`` merges two ``BoundaryConditions`` on the same domain. Walking the merged term list from last-added to first, each term's located entities are removed from every earlier term before that term's measures are recompiled. So on a facet claimed by two regions only the later region's mask governs, rather than the sum of both masks; a term that loses all of its entities is dropped. Strong constraints are concatenated in ``+`` order, and DOLFINx applies them in order, giving the same "later wins" rule. ``penalty_beta`` ---------------- Stored on the whole compiled object (default ``1e15``); the effective penalty is ``beta / h`` through the form's own ``CellDiameter``. Merging two objects that both carry penalty terms with different explicit ``penalty_beta`` raises rather than silently picking one. One state space per domain -------------------------- A ``BoundaryConditions`` is valid only for a solve sharing its ``domain.W`` object. DOLFINx does not treat two independently built but structurally identical ``FunctionSpace``\s as interchangeable for ``DirichletBC`` application: it does not raise, it silently solves the wrong problem. See :class:`hermit.ShellDomain`. .. !! processed by numpydoc !! Classes ------- .. autoapisummary:: hermit.bcs.BoundaryConditions Functions --------- .. autoapisummary:: hermit.bcs.clamp hermit.bcs.gauge hermit.bcs.near hermit.bcs.on_plane hermit.bcs.pin hermit.bcs.symmetry Module Contents --------------- .. py:class:: BoundaryConditions(domain, *, penalty_terms=(), strong=(), penalty_beta=None) Located facets, dofs and measures, compiled once against a domain. Build one with :func:`clamp`, :func:`pin`, :func:`symmetry` or :func:`gauge` rather than calling this constructor directly. ``a + b`` merges two objects on the same domain, with later terms winning on overlapping entities. :Parameters: **domain** : ShellDomain .. **penalty_terms** : sequence, optional Compiled penalty regions, each carrying a dof mask, a prescribed target and its facet measures. **strong** : sequence of dolfinx.fem.DirichletBC, optional Strong constraints. **penalty_beta** : float, optional Penalty scale for the whole object; ``None`` selects the default 1e15. :Attributes: **strong_dofs** : ndarray Sorted unique dof indices held by the strong constraints. .. rubric:: Notes Valid only for a solve that shares this ``domain``'s state space; see the module docstring. .. !! processed by numpydoc !! .. py:method:: to_bc_data() Pack this object for the FEniCSx solve operation. :Returns: hermit.fenics.bcs.BCData Carrying a single masked penalty region when there is at most one, and a list of independently masked regions when several survive a merge. .. !! processed by numpydoc !! .. py:attribute:: domain .. py:attribute:: penalty_beta :value: None .. py:attribute:: penalty_terms :value: [] .. py:attribute:: strong :value: [] .. py:property:: strong_dofs .. py:function:: clamp(domain, *, where, method='penalty', penalty_beta=None, value=0.0) Fix all six generalized dofs on a region. :Parameters: **domain** : ShellDomain .. **where** : callable Coordinate predicate; receives a ``(3, N)`` array and returns an ``(N,)`` boolean mask. :func:`near` and :func:`on_plane` build the common cases. **method** : {'penalty', 'strong'}, optional Enforcement path. Default ``'penalty'``. **penalty_beta** : float, optional Penalty scale, ``'penalty'`` only. Default 1e15. **value** : float, array_like or callable, optional Prescribed value: a scalar, a ``(6,)`` vector over ``(ux, uy, uz, rx, ry, rz)``, or a callable returning ``(N, 6)`` for its ``(N, 3)`` coordinates. Default 0. Not differentiable -- see the module docstring. :Returns: BoundaryConditions .. :Raises: ValueError If ``method`` is not recognised, or if ``penalty_beta`` is given with ``method='strong'``. .. seealso:: :obj:`pin` constrain a subset of the six dofs. .. rubric:: Examples >>> bcs = hm.clamp(domain, where=hm.near("x", 0.0)) .. !! processed by numpydoc !! .. py:function:: gauge(domain, *, at, dofs, value=0.0) Pin selected dofs at a single point, to remove rigid-body modes. Always enforced strongly. Use it to fix the residual null space of an otherwise softly supported model. :Parameters: **domain** : ShellDomain .. **at** : array_like A ``(3,)`` point; the nearest dof of each selected component is used. **dofs** : sequence of str Any subset of ``('ux', 'uy', 'uz', 'rx', 'ry', 'rz')``. **value** : float, array_like or callable, optional Prescribed value; see :func:`clamp`. Default 0. :Returns: BoundaryConditions .. :Raises: ValueError If a dof name is not one of the six. .. rubric:: Examples >>> bcs = supports + hm.gauge(domain, at=[0.0, 0.0, 0.0], dofs=("ux", "uy")) .. !! processed by numpydoc !! .. py:function:: near(axis, value, *, tol=1e-12) Build a ``where`` predicate selecting an axis-aligned plane. :Parameters: **axis** : {0, 1, 2, 'x', 'y', 'z'} Coordinate axis to test. **value** : float Coordinate value the plane sits at. **tol** : float, optional Absolute tolerance. Default 1e-12. :Returns: callable Suitable as ``where=`` for any BC or edge-load builder. :Raises: ValueError If ``axis`` is not one of the accepted names. .. rubric:: Notes The comparison is purely absolute. A relative tolerance would swamp ``tol`` far from the origin -- ``near("x", 10.0)`` would select everything within 1e-4 of the plane. .. rubric:: Examples >>> bcs = hm.clamp(domain, where=hm.near("x", 0.0)) .. !! processed by numpydoc !! .. py:function:: on_plane(point, normal, *, tol=1e-12) Build a ``where`` predicate selecting an arbitrary plane. :Parameters: **point** : array_like A ``(3,)`` point on the plane. **normal** : array_like A ``(3,)`` plane normal; need not be unit or axis-aligned. **tol** : float, optional Absolute distance tolerance. Default 1e-12. :Returns: callable Suitable as ``where=`` for any BC or edge-load builder. .. seealso:: :obj:`near` the axis-aligned shorthand. .. !! processed by numpydoc !! .. py:function:: pin(domain, *, where, dofs, method='penalty', penalty_beta=None, value=0.0) Fix a chosen subset of the six generalized dofs on a region. :Parameters: **domain** : ShellDomain .. **where** : callable Coordinate predicate; see :func:`clamp`. **dofs** : sequence of str Any subset of ``('ux', 'uy', 'uz', 'rx', 'ry', 'rz')``. **method** : {'penalty', 'strong'}, optional Enforcement path. Default ``'penalty'``. **penalty_beta** : float, optional Penalty scale, ``'penalty'`` only. **value** : float, array_like or callable, optional Prescribed value; see :func:`clamp`. Default 0. :Returns: BoundaryConditions .. :Raises: ValueError If a dof name is unknown, if ``method`` is not recognised, or if ``penalty_beta`` is given with ``method='strong'``. .. rubric:: Examples >>> diaphragm = hm.pin(domain, where=hm.near("x", 0.0), dofs=("uy", "uz")) .. !! processed by numpydoc !! .. py:function:: symmetry(domain, *, where, normal, penalty_beta=None, value=0.0) Apply a symmetry-plane constraint (penalty enforcement). Constrains the displacement along ``normal`` and the two rotations about the in-plane axes, leaving the rotation about ``normal`` free. :Parameters: **domain** : ShellDomain .. **where** : callable Coordinate predicate selecting the plane; see :func:`clamp`. **normal** : array_like A ``(3,)`` plane normal, which must be (close to) axis-aligned. **penalty_beta** : float, optional Penalty scale. Default 1e15. **value** : float, array_like or callable, optional Prescribed value; see :func:`clamp`. Default 0. :Returns: BoundaryConditions .. :Raises: ValueError If ``normal`` is zero or not axis-aligned. The penalty form carries a 6-component mask in global x/y/z, with no rotated-frame projection. .. rubric:: Examples >>> half = hm.symmetry(domain, where=hm.near("y", 0.0), normal=[0, 1, 0]) .. !! processed by numpydoc !!