mechanicalLaw: the base class
mechanicalLaw is the abstract base class for every constitutive law in solids4foam. A law's job is narrow: given the current deformation, return the stress, plus the handful of scalars the solid models need in order to build and solve the momentum equation. Everything else — reading mechanicalProperties, partitioning the mesh by material, mapping fields between sub-meshes — is done by mechanicalModel and is invisible to the law.
Source: mechanicalLaw.H, mechanicalLaw.C and newMechanicalLaw.C.
User Guide
This is a developer-facing page. As a user you never select mechanicalLaw itself; you select one of its derived types with the type keyword inside a mechanical sub-dictionary. The only part of this class you interact with directly is the small set of entries it reads for every law:
| Entry | Default | Description |
|---|---|---|
rho | none | Density, as a dimensionedScalar |
solvePressureEqn | no | Smooth the hydrostatic stress with a Laplacian |
pressureSmoothingScaleFactor | 100 | Scale factor for that smoothing |
regionName | see below | Name of the base solid mesh region |
rho is read only when the law uses the base-class rho()/rhoScalar(), which nearly all do. solvePressureEqn and pressureSmoothingScaleFactor are read with lookupOrAddDefault, so their values are echoed into constant/mechanicalProperties.withDefaultValues. regionName defaults to solid if a solid region exists, otherwise region0; set it only in unusual multi-region set-ups, such as a law attached to a mesh-motion mesh.
Enabling solvePressureEqn solves an additional Laplacian equation for the hydrostatic stress. It can quell checkerboarding in the hydrostatic stress at the cost of an extra linear solve per outer iteration; it is off by default.
Developer Notes
The interface a law must implement
Only two functions are pure virtual:
| Function | Contract |
|---|---|
correct(volSymmTensorField& sigma) | Set sigma from the deformation |
impK() | Return the implicit stiffness field |
Everything else has a default implementation, which is either a sensible fallback or a notImplemented that fires only if a solid model actually requests it.
correct(volSymmTensorField& sigma) is the constitutive update. It must overwrite sigma on the mesh the law was constructed on — which, in a multi-material case, is the material's sub-mesh, not the base mesh. Whether sigma is the Cauchy stress or a work-conjugate measure is fixed by the solid model the law is registered for: linear-geometry and total-Lagrangian laws in solids4foam return the Cauchy stress, and it is the law's responsibility to perform any push-forward internally. correct() is called every outer iteration, so it must be idempotent in the sense that calling it twice with the same deformation gives the same answer; any path-dependent state (plastic strain, viscous internal variables) must be advanced in updateTotalFields(), not here.
impK() returns the diffusivity used for the implicit Laplacian term in the segregated momentum equation. It is not a physical property: it is a numerical stiffness that only has to make the outer iterations converge, since the discretisation is written as an implicit Laplacian plus an explicit deferred correction that cancels it at convergence. In practice laws return something of the order of 2*mu + lambda, and it is legitimate to return a larger value to damp the iterations at the cost of slower convergence. Two things do depend on impK() beyond convergence rate, so it cannot be arbitrary:
- the traction boundary conditions use it to convert a traction into a surface-normal gradient;
- the bi-material interface correction in
solidSubMeshesuses the base-meshimpKfield to weight the two sides of an interface, so a badly scaledimpKdegrades the interface treatment in multi-material cases.
impKf() is the surface-field equivalent. The base class implements it as fvc::interpolate(impK()); override it only if the law can produce a genuinely better face value.
rho() returns a volScalarField built from rhoScalar(), which reads the rho entry. The field is created with READ_IF_PRESENT, so a rho field in the time directory takes precedence over the dictionary value, and with zeroGradient boundaries which are corrected before the field is returned. Override rho() for a genuinely non-uniform density, or rhoScalar() alone if the law computes a uniform density from other entries.
bulkModulus() and shearModulus() default to notImplemented. They are not needed by every solid model, but the ones that do call them will abort if the law has not overridden them, so implement them for any new law intended for general use. bulkModulus() is what the compressibility-related machinery uses; a nearly incompressible law must return the correct value here or the mixed pressure-displacement solid models cannot be used with it.
Optional interface
| Function | Default | Purpose |
|---|---|---|
correct(surfaceSymmTensorField&) | notImplemented | The uns models |
correct(pointSymmTensorField&, ...) | notImplemented | Vertex-centred |
correct(CompactListList<...>) | notImplemented | Face quadrature |
materialTangent() | notImplemented | Newton-Raphson (PETSc SNES) |
materialTangentField(...) | uniform | Per-face material tangent |
residual() | 0 | Material convergence measure |
newDeltaT() | endTime | Law-requested time-step limit |
updateTotalFields() | no-op | End-of-time-step state update |
setRestart() | no-op | Adjust field write options on restart |
A few notes on these. The surface-field correct() must be implemented for a law to work with the uns family of solid models, and the point-field variant for the vertex-centred ones; the face-quadrature variant is not available on foam-extend at all. materialTangentField() defaults to filling mesh().nFaces() entries with the single value from materialTangent(), so overriding just materialTangent() is enough for a homogeneous law. residual() returning a non-zero value makes the solid model keep iterating until the material has converged as well as the momentum equation — this is what plasticity laws use — and mechanicalModel::residual() takes the maximum over all laws. newDeltaT() is likewise reduced to the minimum over all laws.
Total- versus updated-Lagrangian variants
A law is registered in exactly one of the two run-time selection tables: linGeomMechLaw or nonLinGeomMechLaw. Both tables have the same constructor signature, and the nonLinearGeometry::nonLinearType enumerator passed to it is what tells a nonlinear law which formulation it is being used in. It is available to the law through the protected nonLinGeom() accessor and takes one of LINEAR_GEOMETRY, TOTAL_LAGRANGIAN or UPDATED_LAGRANGIAN.
The base class provides both deformation gradients so that a single law class can serve both formulations:
F()andFf(): the total deformation gradient, relative to the initial configuration;relF()andrelFf(): the relative deformation gradient, relative to the configuration at the end of the previous time step.
The updateF(...) family of protected functions is the intended entry point. It updates the appropriate gradient for the current formulation from the registered grad(D)/grad(DD) fields, and it also implements the enforce-linear escape hatch: if the solid model has tripped its enforceLinear switch — typically because the outer iterations are diverging — updateF overwrites sigma with the linear elastic response built from the linearised mu and K passed in, and returns true. A law should check that return value and skip its own constitutive update:
if (updateF(sigma, mu_, K_))
{
return;
}
This is why laws must call mu(...) and K(...) to register their linearised moduli even when the constitutive model itself has no such constants: those fields are the fallback.
Whether the law is used incrementally is a separate question from the formulation, and is answered by the protected incremental() accessor, which asks the solid model whether it solves for DD rather than D. The base class uses this to decide which gradient field to look up, so a law that goes through updateF() does not need to care.
Other base-class services
mechanicalLaw also manages a number of demand-driven fields so that derived laws do not each reimplement them: mu/muf and K/Kf (linearised shear and bulk moduli), epsilon/epsilonf (small strain, via updateEpsilon()), sigma0/sigma0f (initial or residual stress), and sigmaHyd/gradSigmaHyd with the updateSigmaHyd(...) overloads that implement the optional pressure smoothing equation. The accessors are protected and each field is only allocated when first used.
planeStress() returns the planeStress switch from mechanicalProperties, searching the law's own region first and then the region0 or solid sub-registry, so it works both for standalone solid cases and for the solid region of a fluid-solid interaction case.
Adding a new mechanical law
-
Create the directory and class. Place it under
src/solids4FoamModels/materialModels/mechanicalModel/mechanicalLaws/, inlinearGeometryLaws/ornonLinearGeometryLaws/according to which selection table it belongs to. CopyinglinearElasticorneoHookeanElasticis the fastest start. -
Derive from
mechanicalLawand declare the run-time type name in the header:TypeName("myElasticLaw");The string is what users write as
typeinmechanicalProperties; it does not have to match the class name, though by convention it does. The one deliberate exception in the toolbox is the thermal lawconstantThermal, whose type name isconstant. -
Provide the standard constructor, taking
(const word& name, const fvMesh& mesh, const dictionary& dict, const nonLinearGeometry::nonLinearType& nonLinGeom), and forward all four arguments to the base class. Read the law's own entries fromdictin the initialiser list, usinglookupfor required entries andlookupOrDefault/lookupOrAddDefaultfor optional ones. Disallow copy construction and assignment, as the base class does. -
Implement
correct(volSymmTensorField&)andimpK(), and overridebulkModulus()andshearModulus()unless the law is only ever going to be used with solid models that do not need them. -
Register the law in the
.Cfile:#include "addToRunTimeSelectionTable.H" namespace Foam { defineTypeNameAndDebug(myElasticLaw, 0); addToRunTimeSelectionTable ( mechanicalLaw, myElasticLaw, linGeomMechLaw ); }Use
nonLinGeomMechLawinstead for a finite-strain law. Registering in the wrong table is the common mistake; the selector's error message will then tell users the law "can only be used with a linear geometry solid model". -
Add the source to the build. There is a single
Make/filesper build flavour insrc/solids4FoamModels/Make/: add one line to bothfiles.openfoamandfiles.foamextend, next to the sibling laws, e.g.$(linGeomLaws)/myElasticLaw/myElasticLaw.CHeader files are not listed. If the law cannot compile on foam-extend, omit it from
files.foamextendrather than guarding the whole file. -
Rebuild with
./Allwmakein the repository root. Because the laws are pulled in by the run-time selection table rather than by any explicit reference, a law that is missing fromMake/fileswill compile in a unity build but never appear in the table — the symptom is "Unknown mechanicalLaw type" listing every law except yours. -
Add a tutorial and a
README.mdin the law's directory, following the pattern of the existing law pages.