hermit ====== .. py:module:: hermit .. autoapi-nested-parse:: Hermit -- Reissner-Mindlin shell analysis in FEniCSx, connected to CSDL. The public API is a ``ShellDomain`` (mesh + state space + index maps) plus free functions that build its inputs -- ``Field`` builders, ``BoundaryConditions``, ``Geometry``, ``Material`` / ``Orientation``, ``Loads`` -- fed to :func:`solve`, whose ``ShellState`` the postprocess functions (``compliance`` / ``mass`` / ``stress_field`` / ...) consume. The FEniCSx custom operations live in ``hermit.fenics.ops`` and are called only from ``hermit._solve`` / ``hermit.outputs`` / ``hermit.transfer``. Note on module names: several implementation modules are underscore-prefixed (``_solve`` / ``_field`` / ``_geometry`` / ``_laminate``) precisely so they cannot shadow the public function of the same name. ``import hermit.solve`` would otherwise rebind ``hermit.solve`` from the function to the module -- silently, with no error. .. !! processed by numpydoc !! Submodules ---------- .. toctree:: :maxdepth: 1 /src/autoapi/hermit/bcs/index /src/autoapi/hermit/csdl_helpers/index /src/autoapi/hermit/domain/index /src/autoapi/hermit/failure/index /src/autoapi/hermit/fenics/index /src/autoapi/hermit/loads/index /src/autoapi/hermit/material/index /src/autoapi/hermit/outputs/index /src/autoapi/hermit/transfer/index Classes ------- .. autoapisummary:: hermit.BoundaryConditions hermit.Field hermit.Geometry hermit.Layup hermit.Loads hermit.Material hermit.Orientation hermit.ShellDomain hermit.ShellState Functions --------- .. autoapisummary:: hermit.aggregated_stress hermit.as_field hermit.center_of_gravity hermit.clamp hermit.compliance hermit.composite hermit.constant hermit.displacement_field hermit.edge_moment hermit.edge_pressure hermit.edge_traction hermit.elastic_energy hermit.failure_field hermit.failure_index hermit.fiber_angle hermit.fiber_direction hermit.field_fn hermit.from_cells hermit.from_coeffs hermit.from_function hermit.from_nodal hermit.gauge hermit.geometry hermit.interpolate hermit.isotropic hermit.laminate hermit.load_vector hermit.mass hermit.moment hermit.near hermit.nodal_displacement hermit.nodal_rotation hermit.on_plane hermit.pin hermit.pnorm_stress hermit.point_load hermit.pressure hermit.project hermit.read_mesh hermit.rotation_field hermit.solve hermit.strain_fields hermit.stress_field hermit.stress_scaling hermit.symmetry hermit.thickness_only hermit.traction Package 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:class:: Field(domain, space, coeffs, *, kind='scalar', frame=None, global_frame=False) A finite-element field on a :class:`~hermit.ShellDomain`. The input/output primitive of the API: every material, load, geometry and output field is a ``Field``. Build one with :func:`constant`, :func:`from_nodal`, :func:`from_cells`, :func:`from_function` or :func:`from_coeffs` rather than calling this constructor directly. :Parameters: **domain** : ShellDomain The domain the field lives on. **space** : tuple FE space descriptor, ``(family, degree)`` or ``(family, degree, value_shape)``, e.g. ``("Lagrange", 1)`` or ``("DG", 0, (3,))``. **coeffs** : csdl.Variable or array_like Coefficients in **FE dof order**, either flat ``(n_scalar_dofs * bs,)`` or ``(n_scalar_dofs, *value_shape)``. Stored flat, in the interleaved block layout ``Function(V).x.array`` uses. **kind** : {'scalar', 'vector3', 'strain2', 'shear2', 'tensor3'}, optional Component layout, which fixes how the components transform under a change of frame. Default ``'scalar'`` (frame-invariant). **frame** : ndarray, optional The frame ``coeffs`` are expressed in: ``(3, 3)`` when ``global_frame`` is True, otherwise ``(n_cells, 3, 3)`` in FE cell order. ``None`` means frame-free. **global_frame** : bool, optional True for a single Cartesian frame shared by every dof, False for a per-cell local frame. Default False. :Attributes: **coeffs** : csdl.Variable Flat coefficients, FE dof order. **space** : tuple Normalized ``(family, degree, value_shape)``. **values** : ndarray Centroid value per cell, file cell order (see :attr:`values`). .. seealso:: :obj:`hermit.interpolate`, :obj:`hermit.project` move a field between spaces. .. rubric:: Notes Two fields combine elementwise (``+ - * /``) only when they already share a space; a plain scalar also works, but a ``csdl.Variable`` scalar must go on the right (``fld * var``, not ``var * fld``). .. !! processed by numpydoc !! .. py:method:: cell_values() Centroid value of every cell. :Returns: csdl.Variable Shape ``(n_cells, bs)``, in **file** cell order. .. !! processed by numpydoc !! .. py:method:: eval(cells, ref_points) Evaluate the field at reference points inside given cells. :Parameters: **cells** : array_like of int Cell ids in **file** order, one per query point. **ref_points** : array_like Reference-cell coordinates: one point broadcast to every query, or one point per query. :Returns: csdl.Variable Shape ``(n_query, bs)``, or ``(n_query,)`` for a scalar field. :Raises: ValueError If the field is on a Quadrature space, which has no basis to tabulate. .. !! processed by numpydoc !! .. py:method:: to_frame(target) -> Field Re-express a frame-relative field in another frame. :Parameters: **target** : array_like For a global-frame field, a full ``(3, 3)`` Cartesian frame whose rows are the new basis axes in world coordinates; every dof is rotated by the same transform. For a per-cell field, the direction the in-plane ``e0`` is aligned to: a ``(3,)`` global direction, a ``(3, 3)`` frame (row 0 is the direction), or a per-cell ``(n_cells, 3)`` / ``(n_cells, 3, 3)`` array in **file** cell order. The tangent plane (``e2``) is unchanged. :Returns: Field The same field in the new frame. A scalar or frame-free field is returned unchanged. :Raises: ValueError If a per-cell field is on a continuous (CG) space, where per-cell frames are ambiguous at shared nodes, or if a global-frame field is given anything but a full ``(3, 3)`` frame. .. seealso:: :obj:`to_global` re-express in world x/y/z. .. !! processed by numpydoc !! .. py:method:: to_global() -> Field Re-express the field in the world x/y/z frame. :Returns: Field Equivalent to ``to_frame(np.eye(3))``. A scalar or frame-free field is returned unchanged. .. !! processed by numpydoc !! .. py:property:: V .. py:property:: block_size .. py:attribute:: coeffs .. py:attribute:: domain .. py:attribute:: global_frame :value: False .. py:property:: is_dg .. py:attribute:: kind :value: 'scalar' .. py:property:: n_dofs .. py:property:: n_scalar_dofs .. py:attribute:: space .. py:property:: value_shape .. py:property:: values :type: numpy.ndarray ndarray: :meth:`cell_values` as plain numpy -- for inspection and plotting. .. !! processed by numpydoc !! .. py:class:: Geometry(domain, *, node_disp=None, nodes=None) The mesh coordinate field, in any configuration :func:`~hermit.solve` accepts. Build one with :func:`geometry`. Giving neither ``node_disp`` nor ``nodes`` selects the reference configuration and no mesh-derivative forms are built, so leaving it out costs nothing. :Parameters: **domain** : ShellDomain .. **node_disp** : csdl.Variable or array_like, optional ``(n_nodes, 3)`` displacement added to the reference coordinates, in **file** vertex order. The usual shape design variable. **nodes** : csdl.Variable or array_like, optional ``(n_nodes, 3)`` absolute coordinates, in **file** vertex order. :Attributes: **field** : Field The coordinates as a ``("Lagrange", 1, (3,))`` field, for uniformity with every other input. **nodes** : csdl.Variable ``(n_nodes, 3)`` file-order coordinates, the layout the FEniCSx operations consume. **is_differentiable** : bool True when ``node_disp`` or ``nodes`` was supplied. :Raises: ValueError If both ``node_disp`` and ``nodes`` are given, or if the mesh has a higher-order (curved) coordinate element. .. !! processed by numpydoc !! .. py:attribute:: domain .. py:attribute:: field .. py:class:: Layup Bases: :py:obj:`csdl_alpha.VariableGroup` A laminate layup. :Parameters: **materials** : Material | list[Material] The ply materials. A single ``Material`` is broadcast to every ply. **angles** : csdl.Variable | np.ndarray | float Ply orientation angles in radians. A scalar is broadcast to every ply. **heights** : csdl.Variable | np.ndarray | float Ply thicknesses. A scalar is broadcast to every ply. **num_plies** : int, optional Number of plies. Inferred from the other arguments when omitted. .. !! processed by numpydoc !! .. py:attribute:: angles :type: Union[csdl_alpha.Variable, numpy.ndarray, float] .. py:attribute:: heights :type: Union[csdl_alpha.Variable, numpy.ndarray, float] .. py:attribute:: materials :type: Union[caddee_materials.Material, list] .. py:attribute:: num_plies :type: int :value: None .. py:class:: Loads(domain, *, traction_terms=(), moment_terms=(), pressure_terms=(), edge_traction_terms=(), edge_moment_terms=(), edge_pressure_terms=(), direct=None) A composable bundle of load terms. Build one with :func:`pressure`, :func:`traction`, :func:`moment`, :func:`edge_pressure`, :func:`edge_traction`, :func:`edge_moment`, :func:`point_load` or :func:`load_vector` rather than calling this constructor directly. ``a + b`` merges two ``Loads`` on the same domain, concatenating the term lists and summing the direct right-hand sides. :Parameters: **domain** : ShellDomain .. **traction_terms, moment_terms** : sequence of Field, optional Distributed ``(3,)`` vector fields. **pressure_terms** : sequence of Field, optional Distributed scalar fields, acting along the shell normal. **edge_traction_terms, edge_moment_terms, edge_pressure_terms** : sequence, optional Edge terms, each pairing a field with the exterior-facet measure it acts on. **direct** : csdl.Variable, optional A generalised right-hand side in ``domain.W`` dof ordering. .. rubric:: Examples >>> loads = hm.pressure(domain, 2.0) + hm.point_load(domain, at=tip, force=[0, 0, -1]) .. !! processed by numpydoc !! .. py:method:: combined_moment(space=('Lagrange', 1, (3, ))) -> hermit._field.Field | None Sum the distributed moment terms onto one space. :Parameters: **space** : tuple, optional Target space. Exact when every term is already on it. :Returns: Field or None ``None`` when there are no moment terms. .. !! processed by numpydoc !! .. py:method:: combined_traction(space=('Lagrange', 1, (3, ))) -> hermit._field.Field | None Sum the traction and pressure terms onto one space. Pressure is converted to a traction with a numpy snapshot of the reference normal. :func:`~hermit.solve` does not use this -- it takes each term directly, with the live normal. :Parameters: **space** : tuple, optional Target space. Exact when every term is already on it. :Returns: Field or None ``None`` when there are no such terms. .. !! processed by numpydoc !! .. py:method:: direct_vector(ndof=None) -> csdl_alpha.Variable The combined point-load and load-vector right-hand side. :Parameters: **ndof** : int, optional Length to use when there is no direct term. Defaults to the number of ``domain.W`` dofs. :Returns: csdl.Variable Shape ``(ndof,)`` in ``domain.W`` dof order; zeros when there is no point load or load vector. .. !! processed by numpydoc !! .. py:attribute:: direct :value: None .. py:attribute:: domain .. py:attribute:: edge_moment_terms :value: [] .. py:attribute:: edge_pressure_terms :value: [] .. py:attribute:: edge_traction_terms :value: [] .. py:attribute:: moment_terms :value: [] .. py:attribute:: pressure_terms :value: [] .. py:attribute:: traction_terms :value: [] .. py:class:: Material(A, B, D, As, thickness, density, *, orientation=None, E=None, nu=None, layup=None) The constitutive input bundle consumed by the solve and the outputs. Build one with :func:`isotropic`, :func:`laminate`, :func:`composite` or :func:`thickness_only` rather than calling this constructor directly. :Parameters: **A, B, D** : Field or None Membrane, coupling and bending stiffness, each a ``(3, 3)`` field in the laminate's own material axes, **unrotated**. ``None`` only for a :func:`thickness_only` material. **As** : Field or None Transverse-shear stiffness, a ``(2, 2)`` field in the ``(xz, yz)`` ordering the shell form uses. **thickness, density** : Field Scalar fields, on any space. **orientation** : Orientation, optional Applied to ``A``/``B``/``D``/``As`` inside the shell form. ``None`` means no rotation, which is correct for an isotropic material. **E, nu** : Field, optional Isotropic constants, needed by the von Mises stress outputs. **layup** : Layup, optional The ply stack, needed by the Tsai-Wu failure outputs. :Attributes: **domain** : ShellDomain Taken from ``thickness``. .. !! processed by numpydoc !! .. py:attribute:: E :value: None .. py:attribute:: density .. py:property:: domain .. py:attribute:: layup :value: None .. py:attribute:: nu :value: None .. py:attribute:: orientation :value: None .. py:attribute:: thickness .. py:class:: Orientation(domain, *, kind, value) The laminate fibre orientation carried on a :class:`Material`. Build one with :func:`fiber_angle` or :func:`fiber_direction`. It does not rotate anything at construction time: the rotation into each element's frame happens inside the shell form during :func:`~hermit.solve`, and again in the Tsai-Wu failure recovery. :Parameters: **domain** : ShellDomain .. **kind** : {'angle', 'direction'} Whether ``value`` is an in-plane angle or a global direction vector. **value** : Field Scalar field of angles (rad) for ``kind='angle'``, or a ``(3,)`` vector field for ``kind='direction'``. .. !! processed by numpydoc !! .. py:attribute:: domain .. py:attribute:: kind .. py:attribute:: value .. py:class:: ShellDomain(mesh, *, element='CG2CG1', cell_tags=None, regions=None, quadrature_degree=4) Mesh, shell state space, index maps and region tags, built once. The first object in every Hermit workflow. It carries no CSDL and no per-solve cost, so build it once and reuse it across every load case, sweep or optimizer iteration. :Parameters: **mesh** : dolfinx.mesh.Mesh A **serial**, straight-sided (P1 geometry) surface mesh of triangles or quadrilaterals. **element** : {'CG2CG1', 'CG1CG1', 'CG2CR1'}, optional Displacement x rotation element pair. Default ``'CG2CG1'``. ``'CG2CR1'`` uses a Crouzeix-Raviart rotation that resists shear locking on thin shells, and is **triangle meshes only**. **cell_tags** : dolfinx.mesh.MeshTags, optional Cell tags, for per-region output integrals. **regions** : dict, optional Maps region names to tag values, so outputs can take ``region="skin"``. **quadrature_degree** : int, optional Degree for every integral this package pins by hand: the penalty-BC facet measures, the stress measures, the DG0 ``'average'`` field method and the oriented elastic energy. Default 4, which is deep in the converged regime for the shell integrand. :Attributes: **W** : dolfinx.fem.FunctionSpace **The** mixed state space for this domain, shared by everything downstream. **n_nodes, n_cells** : int .. **node_coords** : ndarray ``(n_nodes, 3)`` vertex coordinates in **file** order. **cell_centroids** : ndarray ``(n_cells, 3)`` centroids in **file** order. **node_input_idx, cell_input_idx** : ndarray File-to-FE gather indices, ``fe_order = file_order[node_input_idx]``. **reverse_node_idx, reverse_cell_idx** : ndarray Their inverses, ``file_order = fe_order[reverse_node_idx]``. :Raises: ValueError If the mesh is not serial (its ``input_global_indices`` must form a node permutation), or if CG1 / DG0 dof order does not coincide with the mesh's own vertex / cell order. .. rubric:: Notes **Serial meshes only.** This is a requirement of every use, not only of the shape-derivative path: an MPI-partitioned mesh raises here, before any solve. **Three coefficient orderings** coexist on any mesh: *file* (the mesh-file vertex and cell numbering, which external arrays use), *local* (DOLFINx's post-read reordering) and *FE dof* (per function space, indexing ``Function.x.array``). :func:`~hermit.from_nodal` and :func:`~hermit.from_cells` take file order and permute; :func:`~hermit.from_coeffs` takes FE dof order directly. **One state space per domain.** DOLFINx does not treat two independently built but structurally identical function spaces as interchangeable for boundary conditions: a strong BC located against a second instance is silently dropped, with no exception and a wrong answer. Everything downstream therefore shares ``domain.W``. .. rubric:: Examples >>> domain = hm.ShellDomain(hm.read_mesh("plate.xdmf"), element="CG2CG1") .. !! processed by numpydoc !! .. py:method:: check_cell_orientation_consistency(tol=0.0) Verify that cell normals do not flip across shared interior facets. Called by :func:`~hermit.pressure` and :func:`~hermit.edge_pressure`, whose sign convention is only well defined on a consistently wound mesh. :Parameters: **tol** : float, optional Two adjacent normals are inconsistent when their dot product is at or below this. Default 0. :Raises: ValueError Naming the offending cell pair. Fix the mesh winding upstream, or use :func:`~hermit.traction`, which takes an explicit global vector. .. rubric:: Notes Cheap and memoized; a no-op after the first successful call. .. !! processed by numpydoc !! .. py:method:: dof_coords(space) Coordinates of a space's scalar dofs, in FE dof order. :Parameters: **space** : tuple ``(family, degree)`` or ``(family, degree, value_shape)``. :Returns: ndarray Shape ``(n_scalar_dofs, 3)``. For a blocked (vector or tensor) space these are per scalar dof, not per component: all components of one dof share a coordinate. .. seealso:: :obj:`hermit.from_coeffs`, :obj:`hermit.from_function` build coefficients on this space. .. !! processed by numpydoc !! .. py:method:: function_space(space) The DOLFINx function space for a space descriptor. :Parameters: **space** : tuple ``(family, degree)`` or ``(family, degree, value_shape)``. :Returns: dolfinx.fem.FunctionSpace Memoized, one per normalized descriptor. .. !! processed by numpydoc !! .. py:method:: local_frames() The orthonormal local shell frame of every cell. :Returns: ndarray Shape ``(n_cells, 3, 3)``, rows ``e0``, ``e1`` in-plane and ``e2`` the element normal, at the reference geometry, in **FE** cell order. Memoized. .. !! processed by numpydoc !! .. py:method:: local_frames_at(points, cells) The local shell frame at chosen points inside chosen cells. :Parameters: **points** : array_like Reference-cell coordinates: one point broadcast to every query, or one point per query. **cells** : array_like of int Cell ids in **file** order. :Returns: ndarray Shape ``(n, 3, 3)``; see :meth:`local_frames` for the row convention. .. !! processed by numpydoc !! .. py:attribute:: W .. py:attribute:: cell_centroids .. py:attribute:: cell_input_idx .. py:attribute:: cell_tags :value: None .. py:attribute:: element :value: 'CG2CG1' .. py:attribute:: mesh .. py:attribute:: n_cells .. py:attribute:: n_nodes .. py:attribute:: node_coords .. py:attribute:: node_input_idx .. py:attribute:: quadrature_degree :value: 4 .. py:attribute:: regions .. py:attribute:: reverse_cell_idx .. py:attribute:: reverse_node_idx .. py:class:: ShellState(domain, geometry, material, loads, bcs, *, disp_solid) The solved shell state, and back-references to everything that produced it. Returned by :func:`solve`. Because it carries its own inputs, every output function in :mod:`hermit.outputs` needs only the state. A surrogate solve should return one of these to be a drop-in replacement. :Parameters: **domain** : ShellDomain .. **geometry** : Geometry or None ``None`` is normalised to the reference configuration, so ``state.geometry`` is never ``None``. **material** : Material .. **loads** : Loads .. **bcs** : BoundaryConditions .. **disp_solid** : csdl.Variable The raw mixed-space dof vector, in ``domain.W`` dof order. .. !! processed by numpydoc !! .. py:attribute:: bcs .. py:attribute:: disp_solid .. py:attribute:: domain .. py:attribute:: geometry .. py:attribute:: loads .. py:attribute:: material .. py:function:: aggregated_stress(state, *, rho=100, m=1e-06, region=None, surface='top', quadrature_degree=None) Smooth aggregate of isotropic von Mises stress, ``(1/m) * pnorm**(1/rho)``. A differentiable stand-in for the peak stress, suitable as an optimizer constraint. :Parameters: **state** : ShellState .. **rho** : float, optional Aggregation exponent. Default 100. **m** : float, optional Stress scaling; see :func:`pnorm_stress`. Default 1e-6. **region** : str or int, optional Restrict to a mesh-tagged region. **surface** : {'top', 'bottom', 'mid'}, optional Through-thickness station. Default ``'top'``. **quadrature_degree** : int, optional Overrides the domain's degree for this integral. :Returns: csdl.Variable Scalar, in stress units. :Warns: UserWarning If ``m`` is degenerate for this problem, which would otherwise return a number that barely depends on the solution. .. rubric:: Notes Approaches ``max(vm)`` **from below** as ``rho`` grows: this is the area-averaged integral p-norm, bounded above by the pointwise maximum, unlike the discrete log-sum-exp aggregate :func:`failure_index` uses. .. rubric:: Examples >>> m = hm.stress_scaling(state) >>> peak = hm.aggregated_stress(state, m=m) .. !! processed by numpydoc !! .. py:function:: as_field(domain, value, *, space=None) -> Field Coerce a scalar, array or field to a :class:`Field`. This is how the rest of the API accepts a bare number or array wherever a ``Field`` is expected. :Parameters: **domain** : ShellDomain .. **value** : Field, float, array_like or csdl.Variable The value to resolve. **space** : tuple, optional Space to use for a broadcast scalar, and to test an array's leading axis against before falling back to the per-vertex / per-cell orderings. :Returns: Field .. :Raises: ValueError If an array's leading axis matches neither ``domain.n_nodes``, ``domain.n_cells``, nor the given space's scalar-dof count. .. rubric:: Notes Resolution order: 1. A ``Field`` is returned unchanged (``space`` is ignored -- reconcile a mismatch yourself with :func:`~hermit.interpolate` or :func:`~hermit.project`). 2. A scalar, or a length-1 array / ``csdl.Variable``, is broadcast with :func:`constant` on ``space`` (default ``("Lagrange", 1)``). 3. An array with a leading axis longer than 1 is read as FE dof order when it matches ``space``'s scalar-dof count, else as file order via :func:`from_nodal` (``n_nodes``) or :func:`from_cells` (``n_cells``). .. !! processed by numpydoc !! .. py:function:: center_of_gravity(state) Mass-weighted centre of gravity, in global Cartesian coordinates. :Parameters: **state** : ShellState .. :Returns: csdl.Variable Shape ``(3,)``. .. !! processed by numpydoc !! .. 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:: compliance(state) Work done by the applied loads on the solution. :Parameters: **state** : ShellState .. :Returns: csdl.Variable Scalar. Conjugate to the exact ``Loads`` object that was solved, including edge and point terms. .. !! processed by numpydoc !! .. py:function:: composite(domain, *, A, B, D, As, thickness, density, orientation=None, E=None, nu=None) -> Material Material from pre-computed ABD stiffness fields. Nothing is assembled here, so there is no ``constitutive_space`` to choose: each field keeps whatever space it was built on. :Parameters: **domain** : ShellDomain .. **A, B, D** : Field or array_like ``(3, 3)`` membrane, coupling and bending stiffness, in laminate axes. **As** : Field or array_like ``(2, 2)`` transverse-shear stiffness, ``(xz, yz)`` ordering. **thickness, density** : Field, float or array_like .. **orientation** : Orientation, optional Rotates the supplied ABD into each element's frame inside the shell form. **E, nu** : Field, float or array_like, optional Supply these to enable the isotropic von Mises stress outputs. :Returns: Material .. .. rubric:: Notes The result carries no ``layup``, so the Tsai-Wu outputs (:func:`~hermit.failure_index`, :func:`~hermit.failure_field`) do not work on it. Those need :func:`laminate`, which knows the ply stack the criterion is evaluated over. .. !! processed by numpydoc !! .. py:function:: constant(domain, space, value) -> Field Build a uniform field by broadcasting one value to every dof. :Parameters: **domain** : ShellDomain .. **space** : tuple ``(family, degree)`` or ``(family, degree, value_shape)``. **value** : float, array_like or csdl.Variable A scalar, or a value of the space's ``value_shape``. Stays differentiable when it is a ``csdl.Variable``. :Returns: Field .. .. !! processed by numpydoc !! .. py:function:: displacement_field(state) Mid-surface displacement as a field. :Parameters: **state** : ShellState .. :Returns: Field ``(3,)`` vector field on the displacement subspace of the element, in global Cartesian components. .. seealso:: :obj:`nodal_displacement` the same quantity at mesh vertices, in file order. .. !! processed by numpydoc !! .. py:function:: edge_moment(domain, m, *, where, space=('Lagrange', 1)) -> Loads Moment per unit length on selected exterior facets. :Parameters: **domain** : ShellDomain .. **m** : Field or array_like A ``(3,)`` global vector, or a ``Field`` of ``(3,)`` vectors. **where** : callable Coordinate predicate selecting the loaded edge; see :func:`edge_traction`. **space** : tuple, optional Space for a non-``Field`` value. Default ``("Lagrange", 1)``. :Returns: Loads .. :Raises: ValueError If ``m`` does not resolve to a ``(3,)`` vector field. .. !! processed by numpydoc !! .. py:function:: edge_pressure(domain, p, *, where, space=('DG', 0)) -> Loads Pressure per unit length along the shell normal, on selected exterior facets. :Parameters: **domain** : ShellDomain .. **p** : Field, float or array_like Scalar pressure; positive acts along ``+n``. **where** : callable Coordinate predicate selecting the loaded edge; see :func:`edge_traction`. **space** : tuple, optional Space for a non-``Field`` value. Default ``("DG", 0)``. :Returns: Loads .. :Raises: ValueError If ``space`` is not scalar, or if the domain's cell normals are not consistently oriented. .. !! processed by numpydoc !! .. py:function:: edge_traction(domain, t, *, where, space=('Lagrange', 1)) -> Loads Force per unit length on selected exterior facets. :Parameters: **domain** : ShellDomain .. **t** : Field or array_like A ``(3,)`` global vector, or a ``Field`` of ``(3,)`` vectors. **where** : callable Coordinate predicate selecting the loaded edge, with the same contract as :func:`~hermit.clamp`: it receives a ``(3, N)`` coordinate array and returns an ``(N,)`` boolean mask. Only exterior facets are selected. **space** : tuple, optional Space for a non-``Field`` value. Default ``("Lagrange", 1)``. :Returns: Loads .. :Raises: ValueError If ``t`` does not resolve to a ``(3,)`` vector field. .. rubric:: Examples >>> tip = hm.edge_traction(domain, [0.0, 0.0, -1.0], where=hm.near("x", 10.0)) .. !! processed by numpydoc !! .. py:function:: elastic_energy(state) Stored shell strain energy. :Parameters: **state** : ShellState .. :Returns: csdl.Variable Scalar. For a linear solve this is half the compliance, up to the round-off of the direct solve. :Raises: ValueError If the material carries no ``A``/``B``/``D``/``As`` (a :func:`~hermit.thickness_only` material). .. !! processed by numpydoc !! .. py:function:: failure_field(state, *, sampling='average') Per-cell, per-ply-face Tsai-Wu failure indices. :Parameters: **state** : ShellState .. **sampling** : {'average', 'midpoint'}, optional How the DG0 strain measures are sampled per cell. Default ``'average'``. :Returns: Field DG0 field with one component per ply face. :Raises: ValueError If the material carries no layup. Only :func:`~hermit.laminate` sets one. .. !! processed by numpydoc !! .. py:function:: failure_index(state, *, rho=100, sampling='average') KS-aggregated Tsai-Wu failure index over the whole laminate. :Parameters: **state** : ShellState .. **rho** : float, optional KS aggregation exponent. Default 100. **sampling** : {'average', 'midpoint'}, optional How the DG0 strain measures are sampled per cell. Default ``'average'``. :Returns: csdl.Variable Scalar. Below 1 means no ply face has failed; the log-sum-exp KS aggregate bounds the true maximum from **above**. :Raises: ValueError If the material carries no layup. Only :func:`~hermit.laminate` sets one. .. !! processed by numpydoc !! .. py:function:: fiber_angle(domain, angle) -> Orientation Orientation given as an angle from each element's own in-plane axis. :Parameters: **domain** : ShellDomain .. **angle** : Field, float or array_like Angle in radians from the element's ``e0`` to the fibre direction, on any space. A ``csdl.Variable`` makes it a differentiable design field. :Returns: Orientation .. :Raises: ValueError If ``angle`` is not a scalar field. .. seealso:: :obj:`fiber_direction` orientation given as a global direction. .. rubric:: Notes The angle is measured from ``e0``, which follows the mesh parametrisation. On a mesh where ``e0`` is not uniform, a continuous (CG) angle field makes the physical fibre direction kink slightly at element interfaces -- fine on a flat or structured mesh, otherwise prefer :func:`fiber_direction`. .. !! processed by numpydoc !! .. py:function:: fiber_direction(domain, direction) -> Orientation Orientation given as a global fibre direction. :Parameters: **domain** : ShellDomain .. **direction** : Field or array_like A ``(3,)`` global vector, or a ``Field`` of ``(3,)`` vectors on any space (e.g. a per-cell curvilinear fibre path). :Returns: Orientation .. :Raises: ValueError If ``direction`` is not a ``(3,)`` vector field. .. seealso:: :obj:`fiber_angle` orientation given as an element-relative angle. .. rubric:: Notes The shell form projects the direction into each element's tangent plane and never forms the angle explicitly, so there is no branch cut. The projection is degenerate only where ``direction`` is parallel to the shell normal, which is detected when concrete values reach the form. .. !! processed by numpydoc !! .. py:function:: field_fn(*args, fn=None) -> Field Apply a function to the coefficients of one or more fields. :Parameters: **\*args** : Field Operands, all on the same domain and the same space. ``fn`` may be passed as the last positional argument instead of as a keyword. **fn** : callable Applied to the operands' flat ``coeffs``, one argument per field. :Returns: Field On the shared space. ``kind`` / ``frame`` / ``global_frame`` carry through only when every operand agrees, otherwise the result is scalar and frame-free. :Raises: ValueError If no operand is given, or if the operands are on different domains or different spaces. Use :func:`~hermit.interpolate` or :func:`~hermit.project` to reconcile spaces first; there is no implicit coercion. TypeError If an operand is not a ``Field``. .. rubric:: Examples >>> total = field_fn(a, b, fn=lambda x, y: x + y) .. !! processed by numpydoc !! .. py:function:: from_cells(domain, values) -> Field Build a DG0 field from per-cell values in mesh-file order. :Parameters: **domain** : ShellDomain .. **values** : csdl.Variable or array_like Shape ``(n_cells,)`` or ``(n_cells, k)``, in **file** cell order. :Returns: Field On ``("DG", 0)`` or ``("DG", 0, (k,))``. The file-to-FE permutation is applied as a CSDL gather, so a ``csdl.Variable`` input stays differentiable. :Raises: ValueError If the leading axis is not ``domain.n_cells``. .. seealso:: :obj:`from_nodal` the per-vertex (CG1) counterpart. .. !! processed by numpydoc !! .. py:function:: from_coeffs(domain, space, coeffs, *, kind='scalar', frame=None, global_frame=False) -> Field Build a field from raw coefficients in FE dof order. The explicit way to say "these are FE dof coefficients"; pair it with :meth:`~hermit.ShellDomain.dof_coords` to know where each dof lives. :Parameters: **domain** : ShellDomain .. **space** : tuple ``(family, degree)`` or ``(family, degree, value_shape)``. **coeffs** : csdl.Variable or array_like FE dof order, flat or ``(n_scalar_dofs, *value_shape)``. **kind, frame, global_frame** Orientation bookkeeping; see :class:`Field`. :Returns: Field .. .. !! processed by numpydoc !! .. py:function:: from_function(domain, space, fn) -> Field Build a field by evaluating a function at the space's dof coordinates. :Parameters: **domain** : ShellDomain .. **space** : tuple ``(family, degree)`` or ``(family, degree, value_shape)``. **fn** : callable Called once with the ``(n_scalar_dofs, 3)`` dof coordinates and returning the coefficients. Returning numpy gives a constant field; returning a ``csdl.Variable`` (e.g. closing over a design variable) is passed through undisturbed and stays differentiable. :Returns: Field .. .. rubric:: Notes The sample points are the *reference* dof coordinates, so the result is not differentiable with respect to the geometry. .. rubric:: Examples >>> taper = from_function(domain, ("Lagrange", 1), lambda x: 0.2 - 0.01 * x[:, 0]) .. !! processed by numpydoc !! .. py:function:: from_nodal(domain, values) -> Field Build a CG1 field from per-vertex values in mesh-file order. :Parameters: **domain** : ShellDomain .. **values** : csdl.Variable or array_like Shape ``(n_nodes,)`` or ``(n_nodes, k)``, in **file** vertex order (the ordering an external geometry or CADDEE-side array uses). :Returns: Field On ``("Lagrange", 1)`` or ``("Lagrange", 1, (k,))``. The file-to-FE permutation is applied as a CSDL gather, so a ``csdl.Variable`` input stays differentiable. :Raises: ValueError If the leading axis is not ``domain.n_nodes``. .. seealso:: :obj:`from_cells` the per-cell (DG0) counterpart. :obj:`from_coeffs` when the array is already in FE dof order. .. !! 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:: geometry(domain, *, node_disp=None, nodes=None) -> Geometry Build the geometry input for a solve. :Parameters: **domain** : ShellDomain .. **node_disp** : csdl.Variable or array_like, optional ``(n_nodes, 3)`` displacement from the reference coordinates, file order. **nodes** : csdl.Variable or array_like, optional ``(n_nodes, 3)`` absolute coordinates, file order. :Returns: Geometry The reference configuration when neither argument is given. .. rubric:: Examples >>> nd = csdl.Variable(value=np.zeros((domain.n_nodes, 3)), name="node_disp") >>> state = hm.solve(domain, mat, loads, bcs, ... geometry=hm.geometry(domain, node_disp=nd)) .. !! processed by numpydoc !! .. py:function:: interpolate(src_field, space) -> hermit._field.Field Collocate a field at another space's dof points -- the default transfer. :Parameters: **src_field** : Field .. **space** : tuple Target space; its value shape must match the source's. :Returns: Field On the target space, or ``src_field`` itself if already there. :Raises: ValueError If the value shapes differ, or if either space is not point-evaluation (Lagrange, DG, DQ or Quadrature). Piola-mapped families such as RT and Nedelec drag in the geometric Jacobian and are rejected, as does Crouzeix-Raviart. A Quadrature *source* has no basis to tabulate -- use :func:`project`. .. seealso:: :obj:`project` the geometry-dependent L2 alternative. .. rubric:: Notes A constant sparse matrix, memoized per source/target pair, so this is a plain ``csdl.sparse.matvec``: fully differentiable in the coefficients and independent of the geometry, because the target dofs' reference coordinates never move. .. !! processed by numpydoc !! .. py:function:: isotropic(domain, *, E, nu, thickness, density, constitutive_space=None, orientation=None) -> Material Isotropic single-layer material. ``A``/``B``/``D``/``As`` are assembled from the closed form in plain CSDL, with a 0.833 shear-correction factor. :Parameters: **domain** : ShellDomain .. **E, nu, thickness, density** : Field, float or array_like Young's modulus, Poisson's ratio, shell thickness and mass density. Each may be a scalar (broadcast), a per-vertex / per-cell array, or a ``Field`` on its own space. A ``csdl.Variable`` makes it a design variable. **constitutive_space** : tuple, optional Space the ABD fields are evaluated on. Defaults to ``("DG", d)`` where ``d`` is the highest degree among ``E``, ``nu`` and ``thickness``. **orientation** : Orientation, optional Accepted and ignored -- an isotropic in-plane stiffness has no preferred axis, so rotating it is a no-op. The parameter exists so that one orientation object can be passed uniformly to any material constructor. :Returns: Material .. .. rubric:: Notes The ABD fields are an *interpolant* of the closed form at the ``constitutive_space`` dofs, not an exact representation of it: ``E*t`` and ``t**3`` have higher polynomial degree than any fixed space carries. Raise ``constitutive_space`` if that matters for a strongly graded thickness. .. rubric:: Examples >>> mat = hm.isotropic(domain, E=4.32e8, nu=0.0, thickness=0.2, density=1.0) .. !! processed by numpydoc !! .. py:function:: laminate(domain, *, layup, density, orientation=None, shear_correction=0.833, constitutive_space=None) -> Material Composite material from a ply stack, via classical lamination theory. :Parameters: **domain** : ShellDomain .. **layup** : Layup The ply stack (materials, angles, heights). Its angles and heights may be ``csdl.Variable``\s, making the ABD differentiable in the layup. **density** : Field, float or array_like Mass density. **orientation** : Orientation, optional Rotates the laminate axes into each element's frame inside the shell form. Without it the laminate 0-degree axis is taken to be the element ``e0``. **shear_correction** : float, optional Multiplies the CLT transverse-shear stiffness. Default 0.833, the same factor :func:`isotropic` applies. **constitutive_space** : tuple, optional Space the ABD fields are broadcast onto. Defaults to ``("DG", d)`` where ``d`` is the highest degree among ``density`` and ``orientation``. :Returns: Material Carrying ``layup``, so :func:`~hermit.failure_index` and :func:`~hermit.failure_field` work on the resulting state. .. rubric:: Notes One layup is broadcast uniformly to every cell; spatial variation of the stack itself is not part of this API. Use :func:`composite` with per-point ABD fields for that. .. rubric:: Examples >>> layup = hm.Layup(ud, np.radians([0.0, 90.0, 0.0]), np.full(3, 0.002)) >>> mat = hm.laminate(domain, layup=layup, density=1.6e3, ... orientation=hm.fiber_direction(domain, [1.0, 0.0, 0.0])) .. !! processed by numpydoc !! .. py:function:: load_vector(domain, vec) -> Loads A generalised right-hand side given directly in state-space dof order. :Parameters: **domain** : ShellDomain .. **vec** : csdl.Variable or array_like One entry per ``domain.W`` dof, in that space's dof ordering. :Returns: Loads .. :Raises: ValueError If ``vec`` does not have exactly ``domain.W``'s dof count. .. !! processed by numpydoc !! .. py:function:: mass(state) Structural mass, ``int density * thickness dx``. :Parameters: **state** : ShellState .. :Returns: csdl.Variable Scalar. .. !! processed by numpydoc !! .. py:function:: moment(domain, m, *, space=('Lagrange', 1)) -> Loads Distributed moment per unit area, in global components. Contributes ``int m . dtheta dx`` to the residual. :Parameters: **domain** : ShellDomain .. **m** : Field or array_like A ``(3,)`` global vector, or a ``Field`` of ``(3,)`` vectors. **space** : tuple, optional Space for a non-``Field`` value. Default ``("Lagrange", 1)``. :Returns: Loads .. :Raises: ValueError If ``m`` does not resolve to a ``(3,)`` vector field. .. !! 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:: nodal_displacement(state) Displacements at the mesh vertices. :Parameters: **state** : ShellState .. :Returns: csdl.Variable Shape ``(n_nodes, 3)``, in **file** vertex order -- the ordering an external geometry pipeline uses. .. !! processed by numpydoc !! .. py:function:: nodal_rotation(state) Director rotations at the mesh vertices. :Parameters: **state** : ShellState .. :Returns: csdl.Variable Shape ``(n_nodes, 3)``, in **file** vertex order. .. !! 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:: pnorm_stress(state, *, rho=100, m=1e-06, region=None, surface='top', quadrature_degree=None) Area-normalized p-norm stress integral, ``(1/A) int (m*vm)**rho dx``. :Parameters: **state** : ShellState .. **rho** : float, optional Aggregation exponent. Default 100. **m** : float, optional Stress scaling; see Notes. Default 1e-6. **region** : str or int, optional Restrict the integral to a mesh-tagged region. Needs a domain built with ``cell_tags=`` and ``regions=``. **surface** : {'top', 'bottom', 'mid'}, optional Through-thickness station the stress is recovered at. Default ``'top'``. **quadrature_degree** : int, optional Overrides the domain's degree for this integral. :Returns: csdl.Variable Scalar. :Raises: ValueError If the material carries no ``E`` and ``nu`` (stress recovery is isotropic-only -- use :func:`failure_index` for laminates), or if ``region=`` is given without mesh tags. KeyError If ``region`` is not among the mesh tags. .. seealso:: :obj:`aggregated_stress` the same quantity reduced back to stress units. .. rubric:: Notes ``m`` is a problem-specific scaling, not a smoothing knob. It enters as ``(m*vm)**rho``, so it must put ``m*max(vm)`` near 1 or the integrand runs off the end of double precision: with the default ``m`` and a peak von Mises of 1.4e4, ``(m*vm)**100`` is 1e-186. Use :func:`stress_scaling` to compute one. ``m`` must also be **constant** with respect to the design variables. It is not differentiated and cancels out of the aggregate exactly; recomputing it inside the recorded graph each optimizer iteration silently makes the reported derivative wrong. .. !! processed by numpydoc !! .. py:function:: point_load(domain, *, at, force=None, moment=None) -> Loads A consistent point force and/or moment at a physical coordinate. The cell containing ``at`` is located, the state-space basis is evaluated there, and the result is scattered into the direct right-hand side -- the weak form of a Dirac delta, ``int F . delta(x - at) . du dx = F . du(at)``. The point need not be a mesh vertex. :Parameters: **domain** : ShellDomain .. **at** : array_like Physical coordinate, shape ``(3,)``. **force** : csdl.Variable or array_like, optional ``(3,)`` point force. **moment** : csdl.Variable or array_like, optional ``(3,)`` point moment. :Returns: Loads .. :Raises: ValueError If neither ``force`` nor ``moment`` is given, or if ``at`` lies in no cell. .. rubric:: Notes Differentiable in ``force`` and ``moment``, but **not** in ``at`` or the mesh coordinates: the containing cell is fixed at construction. .. !! processed by numpydoc !! .. py:function:: pressure(domain, p, *, space=('DG', 0)) -> Loads Scalar pressure acting along the shell normal. Positive ``p`` acts along ``+n``. The residual uses the live ``ufl.CellNormal``, so the load is differentiable with respect to the mesh coordinates. :Parameters: **domain** : ShellDomain .. **p** : Field, float or array_like Scalar pressure. **space** : tuple, optional Space for a non-``Field`` value. Default ``("DG", 0)``. :Returns: Loads .. :Raises: ValueError If ``space`` is not scalar, or if the domain's cell normals are not consistently oriented. On a badly-wound imported mesh a uniform pressure would otherwise become a sign-alternating load with no other symptom. .. seealso:: :obj:`traction` an explicit global vector, independent of the normal. .. !! processed by numpydoc !! .. py:function:: project(src_field, space, *, geometry=None) -> hermit._field.Field L2-project a field onto another space, ``M c = int(phi_target . src) dx``. :Parameters: **src_field** : Field .. **space** : tuple Target space; its value shape must match the source's. **geometry** : Geometry, ndarray or csdl.Variable, optional Defaults to the domain's reference coordinates, which lets the mass-matrix factorization be cached across calls. Passing a live geometry enables the shape-derivative branch. :Returns: Field On the target space. Projecting onto the same space is the identity up to solver round-off. :Raises: ValueError If the value shapes differ, if ``geometry`` belongs to another domain, or if the source and target Quadrature degrees disagree. .. rubric:: Notes Both sides carry ``dx``, so unlike :func:`interpolate` this depends on the geometry. It is a custom operation with a mass-matrix quotient-rule VJP, reverse mode only: one cotangent solve per call, never a dense Jacobian. .. !! processed by numpydoc !! .. py:function:: read_mesh(path, name='Grid') Read a mesh from an XDMF file. :Parameters: **path** : str or pathlib.Path .. **name** : str, optional Grid name inside the file. Default ``"Grid"``. :Returns: dolfinx.mesh.Mesh Read on ``MPI.COMM_WORLD``. Run serially -- :class:`ShellDomain` requires a serial mesh. .. rubric:: Examples >>> domain = hm.ShellDomain(hm.read_mesh("plate.xdmf")) .. !! processed by numpydoc !! .. py:function:: rotation_field(state) Director rotation as a field. :Parameters: **state** : ShellState .. :Returns: Field ``(3,)`` vector field on the rotation subspace of the element, in global Cartesian components. .. !! processed by numpydoc !! .. py:function:: solve(domain, material, loads, bcs, *, geometry=None) -> ShellState Solve the linear shell problem. :Parameters: **domain** : ShellDomain .. **material** : Material Must carry ``A``/``B``/``D``/``As``; a :func:`~hermit.thickness_only` material is surrogate-only. **loads** : Loads .. **bcs** : BoundaryConditions .. **geometry** : Geometry, optional Defaults to the reference configuration. Pass ``hm.geometry(domain, node_disp=...)`` to make the mesh coordinates a differentiable input. :Returns: ShellState .. :Raises: ValueError If ``material``, ``loads`` or ``bcs`` was built against a different ``ShellDomain``, or if the material has no ABD stiffness. .. rubric:: Notes The formulation is linear and the solve is a direct MUMPS factorization, wrapped as a CSDL implicit custom operation. Because the shell tangent is symmetric, one factorization serves both the forward solve and the adjoint. Every input must be built against this exact ``domain``. DOLFINx silently drops a boundary condition located against a structurally identical but distinct function space, so this is checked rather than assumed. ``solve`` is swappable: any ``Callable[(domain, material, loads, bcs), ShellState]`` is a drop-in surrogate, and the output functions consume its result unchanged. .. rubric:: Examples >>> state = hm.solve(domain, material, hm.pressure(domain, 2.0), ... hm.clamp(domain, where=hm.near("x", 0.0))) .. !! processed by numpydoc !! .. py:function:: strain_fields(state, *, space=('DG', 2), method='project', frame=None) Membrane strain, bending curvature and transverse shear. :Parameters: **state** : ShellState .. **space** : tuple, optional Target space for all three fields. Default ``("DG", 2)``. **method** : {'project', 'interpolate', 'average'}, optional Recovery method. ``'average'`` is the DG0 cell average. Default ``'project'``. **frame** : {'local', 'global'}, optional Component frame. Defaults to the element-local in-plane frame on a DG space, and to global Cartesian on a CG space, where per-cell frames would be ambiguous at shared nodes. :Returns: tuple of Field ``(mid_strain, curvature, shear_strain)``. The component layout follows the frame: in the element-local frame the first two are in-plane engineering Voigt ``[xx, yy, 2xy]`` and the third is ``[xz, yz]``; in the global frame they become full symmetric tensors, ``[xx, yy, zz, 2yz, 2xz, 2xy]`` and a 3-vector. :Raises: ValueError If ``method`` or ``frame`` is not one of the accepted values, or if a local frame is requested on a continuous (CG) space. .. seealso:: :obj:`hermit.Field.to_frame`, :obj:`hermit.Field.to_global` re-express the components. .. rubric:: Examples >>> eps, kappa, gamma = hm.strain_fields(state) >>> kappa_global = kappa.to_global().values .. !! processed by numpydoc !! .. py:function:: stress_field(state, *, space=('DG', 2), method='project', surface='top') Isotropic von Mises stress field. :Parameters: **state** : ShellState .. **space** : tuple, optional Target space. Default ``("DG", 2)``. **method** : {'project', 'interpolate', 'average'}, optional Recovery method. Default ``'project'``. **surface** : {'top', 'bottom', 'mid'}, optional Through-thickness station. Default ``'top'``. :Returns: Field Scalar field. :Raises: ValueError If the material carries no ``E`` and ``nu``; von Mises recovery is isotropic-only. :func:`~hermit.composite` accepts ``E=`` / ``nu=`` for this. .. !! processed by numpydoc !! .. py:function:: stress_scaling(state, *, surface='top', space=('DG', 2), method='project') Suggest the stress scaling ``m = 1 / max|von Mises|`` for the aggregates. :Parameters: **state** : ShellState .. **surface** : {'top', 'bottom', 'mid'}, optional Through-thickness station. Default ``'top'``. **space** : tuple, optional Space the stress field is recovered on. Default ``("DG", 2)``. **method** : {'project', 'interpolate', 'average'}, optional Field recovery method. Default ``'project'``. :Returns: float A plain Python float, deliberately: it is a snapshot taken outside the differentiated path, so it cannot contaminate the derivative of :func:`aggregated_stress`. :Raises: ValueError If the stress field has no value (run under an inline ``csdl.Recorder``), or if the peak is not finite and positive. .. rubric:: Notes Costs one extra stress-field assembly. Call it once when setting a problem up, not inside an optimizer loop -- a design-dependent ``m`` makes the reported gradient wrong even though the value looks better scaled. .. !! 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 !! .. py:function:: thickness_only(domain, *, thickness, density) -> Material Thickness and density only, for a surrogate solve. ``A`` through ``As`` stay ``None``, so :func:`~hermit.solve` rejects this material: it is for a surrogate that carries its own constitutive model but still needs :func:`~hermit.mass` and :func:`~hermit.center_of_gravity`. :Parameters: **domain** : ShellDomain .. **thickness, density** : Field, float or array_like .. :Returns: Material .. .. !! processed by numpydoc !! .. py:function:: traction(domain, t, *, space=('Lagrange', 1)) -> Loads Distributed force per unit area, in global components. Contributes ``int t . du dx`` to the residual. Unlike :func:`pressure` this never touches the shell normal, so it is the right choice for a body force such as self weight on a curved surface. :Parameters: **domain** : ShellDomain .. **t** : Field or array_like A ``(3,)`` global vector, or a ``Field`` of ``(3,)`` vectors. **space** : tuple, optional Space for a non-``Field`` value. Default ``("Lagrange", 1)``. :Returns: Loads .. :Raises: ValueError If ``t`` does not resolve to a ``(3,)`` vector field. .. seealso:: :obj:`pressure` a scalar load following the shell normal. :obj:`edge_traction` the same load on selected exterior facets. .. !! processed by numpydoc !!