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.inertial_load, 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. hm.inertia_relief returns
the inertial_load that balances a free structure’s Loads; it adds nothing to the
solve itself.
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.