Architecture
Hermit separates reusable finite-element setup from differentiable problem data.
ShellDomain owns the mesh, the mixed shell space and the ordering maps, and caches
the compiled ShellPDE on first solve; Field is the common representation for
material, load, geometry, and output fields.
The mesh must be serial: ShellDomain requires its vertex numbering to be a
permutation of the mesh-file order, and raises on an MPI-partitioned mesh. This
applies to every use, not only to shape derivatives.
domain = hm.ShellDomain(mesh, element="CG2CG1")
bcs = hm.clamp(domain, where=root)
mat = hm.isotropic(domain, E=E, nu=nu, thickness=t, density=rho)
state = hm.solve(domain, mat, hm.pressure(domain, pz), bcs)
compliance = hm.compliance(state)
The domain and boundary conditions are constructed once and reused across cases.
solve returns a ShellState that retains its domain, geometry, material, loads, and
boundary conditions, so each output requires only that state. The FEniCSx assembly and
linear/adjoint solves remain behind custom operations; CSDL sees differentiable inputs
and outputs.
Fields and ordering
Field.coeffs are in FE-dof order. hm.from_coeffs accepts that order directly, while
hm.from_nodal and hm.from_cells accept mesh-file vertex and cell order respectively.
Use domain.dof_coords(space) with hm.from_function or hm.from_coeffs when constructing
coefficients on other spaces. Input fields can use independent spaces; no domain-wide
material or load space exists.
hm.interpolate transfers between spaces on the same mesh by reference-element
collocation. hm.project is the geometry-dependent L2 alternative.
Materials, loads, and boundary conditions
hm.isotropic, hm.laminate, and hm.composite construct a Material. Composite
ABD matrices remain in laminate axes and an Orientation from hm.fiber_angle or
hm.fiber_direction is consumed inside the shell form.
hm.pressure, hm.traction, hm.moment, hm.point_load, and hm.load_vector each
return composable Loads; hm.edge_pressure, hm.edge_traction, and
hm.edge_moment apply the same loads per unit length on exterior facets selected by
a where= predicate. Every load term reaches the residual and the compliance form on
its own space, so mixing spaces costs no interpolation.
Use hm.clamp, hm.pin, hm.symmetry, and hm.gauge for boundary conditions. Each
takes value= for a non-zero prescribed displacement or rotation, and + merges
them with later terms winning on overlapping entities. A prescribed value is
interpolated once rather than carried as a solve input, so unlike a load it cannot be
a csdl.Variable. See Background.
Outputs and frames
Scalar outputs are individual functions: hm.compliance, hm.mass,
hm.center_of_gravity, hm.elastic_energy, stress aggregates, and composite
hm.failure_index. hm.strain_fields(state, space=..., method=..., frame=...)
returns membrane, bending, and shear Fields; hm.displacement_field and
hm.rotation_field return state fields.
A DG strain field defaults to an element-local frame, while a CG field defaults to a
global Cartesian frame. Use to_global() or to_frame(...) to change representation.
Local-frame components require DG because neighbouring element frames need not agree;
global-frame components can be continuous.
Geometry and surrogates
The reference geometry is used by default. Pass
geometry=hm.geometry(domain, node_disp=shape) (or nodes=...) to make geometry a
differentiable input. A surrogate can use the same arguments as hm.solve and return a
ShellState; state-based output functions can then consume that result.