Models
FEModel - A classical finite element model
- class edelweissfe.models.femodel.FEModel(dimension)[source]
This is is a standard finite element model tree. It takes care of the correct number of variables, for nodes and scalar degrees of freedem, and it manages the fields.
- Parameters:
dimension (int) – The dimension of the model.
Methods
The constraints that take part in the equation system: all constraints, in their order, that are active, i.e. switched on and within reach (see
active).advanceToTime(time)Accept the current state of the model and sub instances, and set the new time.
advanceVelocity(timeIncrement)Advance the velocity of the model by its acceleration over a time increment, \(\boldsymbol{V} \leftarrow \boldsymbol{V} + \Delta t \, \boldsymbol{A}\), in every node field, on every node.
buildEquationSystem([withSystemMatrix])Lay out the global equation system of the model as it is now: number its degrees of freedom, and say which elements contribute to it, see
EquationSystemLayout.createElement(element)Add a freshly created element to the model.
createNode(node)Add a freshly created node to the model.
prepareYourself(journal)Prepare the model for a simulation.
readRestart(restartFile[, journal])Read the state of the model from a restart checkpoint written by
writeRestart().removeElement(elNumber)Remove an element from the model.
Bring the coupling of the degrees of freedom up to date with the converged state, before an increment: which nodes the entities of the equation system couple.
writeRestart(restartFile)Write the current (converged) state of the model to a restart checkpoint.
- activeConstraints()[source]
The constraints that take part in the equation system: all constraints, in their order, that are active, i.e. switched on and within reach (see
active).- Returns:
The active constraints.
- Return type:
list[ConstraintBase]
- advanceToTime(time)[source]
Accept the current state of the model and sub instances, and set the new time.
Of the constraints, only the active ones accept their state (see
activeConstraints()): a constraint that took no part in the increment has no new state, and keeps its history as it was, e.g. the multipliers and the frictional history of a contact switched off for a step.- Parameters:
time (float) – The new time.
- advanceVelocity(timeIncrement)[source]
Advance the velocity of the model by its acceleration over a time increment, \(\boldsymbol{V} \leftarrow \boldsymbol{V} + \Delta t \, \boldsymbol{A}\), in every node field, on every node.
An explicit solver integrates the velocity staggered half an increment behind the solution within its step, and publishes the acceleration at the end of every increment. At the end of its step, it advances the velocity by the half increment it lags behind, so that the step leaves the velocity at its end time, as every solver does (see the documentation page Node field results). Where the acceleration is zero, the velocity stays as it is. The model does this itself, without an equation system: a step resumed at its very end has built none.
- Parameters:
timeIncrement (float) – The time increment.
- buildEquationSystem(withSystemMatrix=True)[source]
Lay out the global equation system of the model as it is now: number its degrees of freedom, and say which elements contribute to it, see
EquationSystemLayout. Called by a solver whenever it (re)builds its equation system: at the start of a step, and after the topology update reported a change of the mesh or of the coupling (updateConnectivity()).The equation system of a finite element model holds the node fields, the elements, and the active constraints with their scalar variables (e.g. Lagrange multipliers). A constraint that is not active (see
active) is left out together with its scalar variables, which would otherwise be unknowns without an equation.- Parameters:
withSystemMatrix (bool) – False for an explicit solver, which assembles no system matrix: then neither the pattern of the system matrix is built, nor the map from the degrees of freedom back to their nodes, by which the Newton iterations report the nodes with the largest residuals.
- Returns:
The layout of the equation system.
- Return type:
- createElement(element)[source]
Add a freshly created element to the model.
- Parameters:
element – The element, already carrying a number obtained from
reserveElementNumbers().
- createNode(node)[source]
Add a freshly created node to the model.
- Parameters:
node – The node, already carrying a label obtained from
reserveNodeNumbers().
- prepareYourself(journal)[source]
Prepare the model for a simulation. Creates the variables, bundles the fields, and initializes elements.
- Parameters:
journal (Journal) – The journal instance.
- readRestart(restartFile, journal=None)[source]
Read the state of the model from a restart checkpoint written by
writeRestart().Either the model has been rebuilt from the original
.inpfile andprepareYourself()called – a resumed run – or it is the very model the checkpoint was written from, going back to it within the run (seefallBackToCheckpoint()). Of the recorded topology decisions, those the model has not applied yet are replayed: all of them for a rebuilt model, none for a model going back. A model whose mesh has changed since the checkpoint cannot go back to it, since a mesh change cannot be undone.- Parameters:
restartFile (File) – An open, readable
h5py.File(or group) to read the checkpoint from.journal (Journal) – Optional Journal for the progress messages of the topology replay.
- Raises:
RestartError – If the model is not the one the checkpoint was written from, before anything is restored, or if the checkpoint does not hold every entry of a node field (see
ENTRIES).MeshChangedSinceCheckpoint – If the model’s mesh has changed since the checkpoint was written, before anything is restored.
- removeElement(elNumber)[source]
Remove an element from the model. Its number is retired, never reissued.
- Parameters:
elNumber (int) – The number of the element to remove.
- updateConnectivity()[source]
Bring the coupling of the degrees of freedom up to date with the converged state, before an increment: which nodes the entities of the equation system couple. Called by the solvers’ topology update at the start of every increment and of every retry after a cutback, before the increment is proposed (see
updateTopologyAndConnectivity()). If it reports a change, an implicit solver rebuilds its equation system (buildEquationSystem()).In a finite element model, the elements keep their nodes, and only a constraint may change the nodes it couples: a contact constraint searches for the surface it touches. A meshfree model of EdelweissMeshfree moves its material points through a background grid, and its particles carry their kernel functions along: it reassigns them here first, then lets its constraints follow.
Only the constraints that are switched on are located anew (
switchedOn), those out of reach included, so that they can find themselves within reach again. A constraint switched off from outside rests until it is switched on again, and is located anew at the start of the next increment then.A change of the coupling is not a change of the mesh: it neither moves nodes nor creates them, so a solver rebuilds its equation system, but does not re-equilibrate (
equilibrateAfterModelChange).- Returns:
True if a coupling changed, so that the equation system has to be rebuilt.
- Return type:
bool
- writeRestart(restartFile)[source]
Write the current (converged) state of the model to a restart checkpoint.
Does not serialize model topology (nodes/elements/sets/sections/materials) – only state that a rebuild from the original
.inpfile cannot reproduce: node field values, element quadrature-point history, scalar variables (e.g. Lagrange multipliers), stateful constraints’ internal history (e.g. frictional contact), and the additional parameters (e.g. the load parameter of the arc length method).- Parameters:
restartFile (File) – An open, writable
h5py.File(or group) to write the checkpoint into.
- additionalParameters
Additional information.
- analyticalFields
AnalyticalFields in the model.
- constraintSets
ConstraintsSets in the model.
- constraints
Constraints in the model.
- contactFacetRecipes
facet elSet name -> (surfaceName, prefix, triangulation, nodalWeights).
- domainSize
Spatial dimension of the model
- elementProperties
Element properties.
- elementSets
ElementSets in the model.
- elements
Elements in the model.
- fieldOutputController
Set once by the driver; lets in-model entities (e.g. AMR markers) look up a named *fieldOutput by value, not just by declaration.
- materials
Materials in the model.
- modelModifiers
Model modifiers (dynamic topology / mesh mutation entities) in the model.
- multiPointConstraints
Multi-point (DOF-elimination) constraints in the model.
- nodeFields
NodeFields in the model.
- nodeSets
NodeSets in the model.
- nodes
Nodes in the model.
- rigidBodies
RigidBodies in the model.
- scalarVariables
ScalarVariables in the model.
- sections
Sections in the model.
- surfaces
Surface definitions in the model.
- time
Current time of the model.
- topology
How the mesh may change during a run; see
TopologyPipeline.
EquationSystemLayout - What the equation system of a model is made of
What a model answers a solver that (re)builds its equation system: the numbering of the degrees of freedom, the elements to compute, and how the solution passes between the model and the solver’s vectors. See What the solver asks the model.
What the global equation system of a model is made of: the answer a model gives a solver that (re)builds its
equation system, see buildEquationSystem() and
What the solver asks the model.
- class edelweissfe.models.equationsystemlayout.EquationSystemLayout(model, dofManager, elements)[source]
The layout of the global equation system of a model: which degrees of freedom it has, which elements contribute to it, and how the solution passes between the model and the vectors a solver iterates on.
A solver asks the model for it whenever it (re)builds its equation system (
buildEquationSystem()), and keeps it as long as the numbering of the degrees of freedom it holds: until the next rebuild. The solver itself only solves; what the model is made of, the layout says.This class is the layout of a finite element model. A model made of other entities – EdelweissMeshfree’s material point and RKPM models, with their background cells and particles – returns a layout derived from it, which changes what its methods do; the solver calls them the same way.
- Parameters:
model (FEModel) – The model.
dofManager (DofManager) – The numbering of the degrees of freedom.
elements (list) – The elements, in the order in which the solver computes them.
Methods
readStateFromModel(U[, V, A, dynamicFields])Read the state the model carries from increment to increment into the vectors of a (re)built equation system, from which the next increment starts (see the documentation page Node field results): the solution
U, and, for a solver that integrates in time, the velocityVand the accelerationA.updateMaterialPoints(dU, timeStep, forEachChunk)Bring the material points that belong to no element up to date with the trial solution increment, right before the solver computes the elements.
writeEntryOfNodeField(dofVector, nodeField, ...)Write a vector of the equation system to one entry of one node field of the model; every write of
writeResultsToModel()andwriteVelocityAndAccelerationToModel()goes through here.writeResultsToModel(U, dU, P[, V, A, ...])Write the results of an accepted increment to the model, once (see the documentation page Node field results): the solution
U, its incrementdUand the forcePto the entries of the same names of the node fields, the solution to the scalar variables, and, for a solver that integrates in time, the velocityVand the accelerationA(seewriteVelocityAndAccelerationToModel()).writeVelocityAndAccelerationToModel(V, A[, ...])Write the velocity and the acceleration to the entries
VandAof the node fields: at acceptance, as part ofwriteResultsToModel(), and when a solver completes the state an increment starts from, e.g. the acceleration the implicit dynamic solver computes from equilibrium at the start of a step.Set the degrees of freedom of the nodes that carry no total solution to zero in the solution vector of a converged increment, before its results are written to the model: these incremental degrees of freedom are the coefficients of one increment, and every increment starts them at zero (see the documentation page Node field results).
- readStateFromModel(U, V=None, A=None, dynamicFields=None)[source]
Read the state the model carries from increment to increment into the vectors of a (re)built equation system, from which the next increment starts (see the documentation page Node field results): the solution
U, and, for a solver that integrates in time, the velocityVand the accelerationA.In a finite element model, every node carries its total solution: the entry
Uof every node field is read intoU, and the values of the scalar variables (e.g. the Lagrange multipliers of constraints). The entriesVandAof the node fields are read intoVandA, if given.- Parameters:
U (DofVector) – The solution vector of the equation system, zero, overwritten.
V (DofVector) – The velocity vector of the equation system, zero, overwritten; None if the solver has none.
A (DofVector) – The acceleration vector of the equation system, zero, overwritten; None if the solver has none.
dynamicFields (list) – The names of the fields whose velocity and acceleration are read, e.g. those the solver integrates in time; None for every field.
- updateMaterialPoints(dU, timeStep, forEachChunk)[source]
Bring the material points that belong to no element up to date with the trial solution increment, right before the solver computes the elements. Called whenever
NIST, or a solver derived from it (NEST,NID), evaluates the elements (seeassembleInternalForces()), e.g. in every Newton iteration and for every trial state of a line search. The explicit dynamic solverNEDevaluates the elements of a finite element model itself, and does not call it.The loops over the entities belong to the solver, as the loop over the elements does:
forEachChunk(entities, work)callswork(chunk)for chunks that together hold allentities, in the calling thread or on the threads of a parallel solver (seeforEachChunk()). A layout loops through it over entities that are independent of each other.A finite element model has no such material points: every element evaluates the material at its own quadrature points, in
computeKernels(), so there is nothing to do here. In the material point method, the material points move through a fixed background grid, and belong to the cell they currently lie in: here, each cell interpolates the solution increment to its material points, and the material points evaluate their material, before the solver computes the cells, which integrate the result.- Parameters:
dU (DofVector) – The trial solution increment.
timeStep (TimeStep) – The time step.
forEachChunk (Callable[[list, Callable], None]) – The solver’s loop over chunks of independent entities.
- writeEntryOfNodeField(dofVector, nodeField, entry)[source]
Write a vector of the equation system to one entry of one node field of the model; every write of
writeResultsToModel()andwriteVelocityAndAccelerationToModel()goes through here.In a finite element model, the equation system numbers every node of the field, so the whole entry is written. A model whose equation system covers a part of its nodes writes the entry on these nodes, and sets it to zero on the others.
- Parameters:
dofVector (DofVector) – The vector of the equation system.
nodeField (NodeField) – The node field of the model.
entry (str) – The entry, one of
NodeField.ENTRIES.
- writeResultsToModel(U, dU, P, V=None, A=None, dynamicFields=None)[source]
Write the results of an accepted increment to the model, once (see the documentation page Node field results): the solution
U, its incrementdUand the forcePto the entries of the same names of the node fields, the solution to the scalar variables, and, for a solver that integrates in time, the velocityVand the accelerationA(seewriteVelocityAndAccelerationToModel()).A solver whose increment ends on another equation system than it started on writes in two parts, each through the layout of the system that holds the values: EdelweissMeshfree’s explicit solver takes the increment on the kernel functions the particles reached at its start, and evaluates the force on those they reach at its end. It passes None for the entries the other part writes.
- Parameters:
U (DofVector) – The solution.
dU (DofVector | None) – The increment of the solution; None to leave the entry
dUof the node fields as it is.P (DofVector | None) – The internal force, the sum of the elements’ residual contributions, with the inertial and damping forces of a solver that integrates in time; None to leave the entry
Pof the node fields as it is.V (DofVector) – The velocity; None to leave the entry
Vof the node fields as it is.A (DofVector) – The acceleration; None to leave the entry
Aof the node fields as it is.dynamicFields (list) – The names of the fields whose velocity and acceleration are written; None for every field.
- writeVelocityAndAccelerationToModel(V, A, dynamicFields=None)[source]
Write the velocity and the acceleration to the entries
VandAof the node fields: at acceptance, as part ofwriteResultsToModel(), and when a solver completes the state an increment starts from, e.g. the acceleration the implicit dynamic solver computes from equilibrium at the start of a step.
- zeroIncrementalCoefficients(U)[source]
Set the degrees of freedom of the nodes that carry no total solution to zero in the solution vector of a converged increment, before its results are written to the model: these incremental degrees of freedom are the coefficients of one increment, and every increment starts them at zero (see the documentation page Node field results).
In a finite element model, every node carries its total solution, so there is nothing to do here. A meshfree model, whose background grid and kernel functions are incremental, sets their degrees of freedom to zero.
- Parameters:
U (DofVector) – The solution vector of the converged increment, modified in place.
- dofManager
The numbering of the degrees of freedom of the equation system; see
DofManager.
- elements
Everything the solver computes like an element in every evaluation of the model, see
computeElements(): the finite elements of the model, and in a meshfree model its cells and particles as well. A list, which a parallel solver hands out in slices to its threads.
- model
The model this equation system belongs to.
- modelForLinearSolver
The model the linear solver is set up for (see
setModel()), whose node fields are the ones the DOF manager numbers: here, the model itself.
TopologyPipeline - How the mesh may change during a run
Held by every model as model.topology. The model is the mesh and its data; the pipeline owns the
topology window, the number allocators, the fixed-point rounds of the model modifiers, the change log
and mesh-dependent consumers, and the recorded history that a restart replays. See
The topology pipeline: how a model changes itself for the design.
The topology pipeline: how, and only how, the mesh of a model may change during a run.
See TopologyPipeline.
- class edelweissfe.models.topologypipeline.TopologyPipeline(model)[source]
Everything about changing the mesh of a
FEModelduring a run, held by the model asmodel.topology.The model itself is the mesh and its data. This class is the machinery around changing it:
the topology window (
changes()), the only scope in which elements and nodes may be created or removed;the number allocators (
reserveElementNumbers(),reserveNodeNumbers()), which make numbering a pure function of the creation order;the fixed-point rounds (
update()), in which every model modifier (e.g. AMR) plans and applies until none has anything left to do;the change log (
version,changesSince()) and the registry of mesh dependents (refreshMeshDependents()), from which ties and contact catch up;the history of every applied decision, its fingerprint, and the replay of that history on restart (
replayHistory()).
- Parameters:
model – The model whose mesh this pipeline changes.
Methods
Raise the allocator above every element number setup has already handed out.
Raise the node allocator above every label setup has already handed out; the node-side counterpart of
adoptSetupElementNumbers(), called alongside it.boundTo(model)A shallow copy of this pipeline that acts on
model, for a shallow copy of the model.changes([deferFieldBookkeeping])The only scope in which elements may be created or deleted.
changesSince(version)The
ModelChangecoalesced across every mutation recorded afterversion, orNoneif the model hasn't changed since.Refuse a model in which two modifiers claim the same element.
deferFieldVariableLink(nodes)Note a field-variable relink requested inside a deferring window; it runs when the window closes.
deferNodeFieldResize(journal)Note a node-field resize requested inside a deferring window; it runs when the window closes.
ensureSurfaceFacetModifier(journal)Create the implicit facet-regeneration modifier, if any facet recipe was declared.
A short digest of the model's topology and its numbering, for verifying that a restart replay reproduced the original run.
recordChange(roundNumber, name, modifier, ...)Register everything an applied decision produced: the replay record and the changeset.
Record the
fingerprint()of the mesh as the input file built it, once the model is set up and before any model modifier acts; seesetupFingerprint().Let every registered mesh-dependent consumer catch up, once, after the topology update.
registerMeshDependent(consumer)Register a
MeshDependentto be refreshed after every topology update.replayHistory(records[, journal])Reconstruct the topology by re-applying recorded decisions, in order.
reserveElementNumbers([count])Reserve
countfresh element numbers.reserveNodeNumbers([count])Reserve
countfresh node labels.The
fingerprint()of the mesh as the input file built it.update([step])Run every model modifier to a fixed point, inside one topology window.
- adoptSetupElementNumbers()[source]
Raise the allocator above every element number setup has already handed out.
Called once, at the end of model setup. The base mesh (input file and every mesh generator) numbers its elements as a pure function of the input file, is re-run identically by a resumed run before the checkpoint is read, and is never renumbered afterwards – so those numbers need no allocator. This just makes sure nothing minted later can collide with them.
- adoptSetupNodeNumbers()[source]
Raise the node allocator above every label setup has already handed out; the node-side counterpart of
adoptSetupElementNumbers(), called alongside it.
- boundTo(model)[source]
A shallow copy of this pipeline that acts on
model, for a shallow copy of the model.As for the model itself, the containers (history, change log, mesh dependents) are shared with this pipeline, and the counters, the version and the window state are the copy’s own.
- Parameters:
model – The model the copy acts on.
- Return type:
- changes(deferFieldBookkeeping=False)[source]
The only scope in which elements may be created or deleted.
Opened once around model setup, and once per increment around the model modifiers. Outside it,
createElement()andremoveElement()raise – which is what makes “only model modifiers mutate the topology” an enforced property rather than a convention, and what letsreserveElementNumbers()guarantee that element numbering is a pure function of the ordered creation sequence.Nesting is permitted and is a no-op for the inner scope: setup-time helpers may open a window without knowing whether their caller already did.
- Parameters:
deferFieldBookkeeping (bool) – Postpone the node-field bookkeeping a mesh mutator requests through
_resizeNodeFieldsForNodes()and_linkFieldVariableObjects()until this window closes, and run it exactly once then. Both are idempotent recomputations from the model’s current nodes and elements – neither one touches numbering, connectivity or anything thefingerprint()covers – so the state after one flush at the end equals the state after a flush per mutation; only the intermediate, immediately overwritten field layouts are skipped. That is whatreplayHistory()wants: no increment is solved between two replayed records, so nothing consumes those layouts, and a flush per record makes a long replay O(records x mesh) instead of O(mesh). The live per-increment window does not defer – the solver runs on the fields right after each update. Honoured by the outermost window only, like the window itself.
- changesSince(version)[source]
The
ModelChangecoalesced across every mutation recorded afterversion, orNoneif the model hasn’t changed since. A pull-based consumer compares its own last-seen version againstversionand, on a mismatch, reconciles from this, then adopts the newversionas its own last-seen version.- Parameters:
version (int)
- Return type:
ModelChange
- checkModelModifierDomains()[source]
Refuse a model in which two modifiers claim the same element.
Run once, at the end of setup. Each modifier declares what it owns via
declaredDomain(); an overlap means both will mutate the same element and each will end up holding stale references to the other’s work. Failing here costs a clear message at startup; failing later costs a corrupted element set or a node that is both Dirichlet-prescribed and an MPC slave, discovered mid-solve.
- deferFieldVariableLink(nodes)[source]
Note a field-variable relink requested inside a deferring window; it runs when the window closes.
- deferNodeFieldResize(journal)[source]
Note a node-field resize requested inside a deferring window; it runs when the window closes.
- Parameters:
journal (Journal)
- ensureSurfaceFacetModifier(journal)[source]
Create the implicit facet-regeneration modifier, if any facet recipe was declared.
Retiling a contact/tie surface creates and deletes elements, so it is a topology change and belongs in the topology-update phase – not in a consumer’s refresh, which is where it used to live and which is what made every tie a mutating consumer. Users never declare this modifier: they already declared the
*surfacerecipe it acts on.Ordered last, so that within a round it reacts to whatever the primary modifiers (a refinement, a deposition) just did.
- Parameters:
journal (Journal)
- fingerprint()[source]
A short digest of the model’s topology and its numbering, for verifying that a restart replay reproduced the original run.
Covers exactly what a replay must get right and nothing else: every element’s number, type and connectivity (in order – a rotated connectivity is a real difference), and every node’s label and reference coordinates. The surface nodes of a discrete rigid body hold its current position instead; the body reports their reference coordinates (
referenceCoordinatesOfMovedNodes()), so that the motion of a rigid body is not mistaken for a change of the mesh. A resumed run replays the topology before it restores the displacement, i.e. with the body still at its reference position. Deliberately excludes solution state, so a mismatch means the mesh diverged, not that the solver took a different path.Recorded per round in the topology history, this turns “the resumed run diverged somewhere” into “increment 471, round 2, modifier amr” – a divergence you can bisect rather than hunt.
Not cheap: it walks the whole mesh, measured at 0.188 s on 64k elements / 69k nodes. A live run pays it once per applied modifier decision, in
recordChange(). A replay pays it once for the whole history (replayHistory()carries the recorded digests forward and checks the final one), unlessverifyFingerprintsPerRecordasks for the per-record walk to locate a divergence.Uses blake2b rather than
hash(), whose string hashing is randomised per process and would make the digest differ between two runs of the same code.- Returns:
A 32-character hex digest.
- Return type:
str
- recordChange(roundNumber, name, modifier, plan, modelChange, time=None, fingerprint=None)[source]
Register everything an applied decision produced: the replay record and the changeset.
Two things are recorded here, deliberately in one place:
an entry in
history, holding the plan in the modifier’s own serializable form plus the resulting topology fingerprint – which is what lets a resumed run be checked round by round instead of only at the end;the
modelChangeitself, stamped with the nextversion, sochangesSince()andrefreshMeshDependents()can see it.
A model modifier therefore reports what it did in exactly one way: by returning a
ModelChangefromapply()– there is no second channel to keep in sync, and so no way to update one and forget the other.Both callers of
apply(the liveupdate()loop andreplayHistory()) route through here, so a replayed run records the same changesets in the same order as the run it replays.Cost is one
fingerprint()per applied decision – not per iteration, but not free either: 0.188 s measured on 64k elements / 69k nodes – unless the caller supplies the fingerprint, which a replay does (seereplayHistory()).- Parameters:
time (float) – Model time to stamp the record with. Defaults to the current
time; a replay passes the recorded time instead, so a resumed run’s history carries the times the decisions were originally made rather than the time it was resumed at.fingerprint (str) – The digest to record. Defaults to
fingerprint()of the model as it is now, which is what a live run wants. A replay passes the digest the original run recorded, so the replayed history carries the digests that were verified rather than fresh ones that would launder a divergence into the next checkpoint.roundNumber (int)
name (str)
- Return type:
TopologyRecord
- recordSetupFingerprint()[source]
Record the
fingerprint()of the mesh as the input file built it, once the model is set up and before any model modifier acts; seesetupFingerprint().
- refreshMeshDependents()[source]
Let every registered mesh-dependent consumer catch up, once, after the topology update.
Phase 2 of the increment (see
update()for phase 1). Consumers are pure readers here – the topology window is closed – so their order does not matter and no fixed-point iteration is needed: none of them can invalidate another’s work.Each consumer sees the net change across every round of the topology update, which is why this is pull and not push: a push fires per mutation, i.e. at moments that are by construction mid-pipeline, handing a consumer a state that no longer exists by the time the solve begins.
- Returns:
True if any consumer reported that its DOF footprint changed.
- Return type:
bool
- registerMeshDependent(consumer)[source]
Register a
MeshDependentto be refreshed after every topology update.Registration is the freshness guarantee: a consumer that is not in this list is never told the mesh changed. That matters most for the consumers the solver does not otherwise tick – multi-point constraints live in
model.multiPointConstraints, which no per-increment sweep iterates, so a tie could only ever learn about refinement this way.
- replayHistory(records, journal=None)[source]
Reconstruct the topology by re-applying recorded decisions, in order.
This is the whole point of the plan/apply split: the modifier’s
apply()runs here exactly as it did live, fed a decoded plan instead of a freshly evaluated one. There is no replay-specific code path to drift from the live one – which is what the previous design had, and why a resumed run silently renumbered its elements.What a replay does not repeat per record is the whole-mesh work around
applywhose result is a pure function of the final mesh: thefingerprint()walk (the recorded digests are carried forward and the final one is checked) and the node-field bookkeeping (deferred to the end of the window, seechanges()). A history of a few hundred refinements on a mesh of tens of thousands of elements replayed in minutes otherwise, all of it spent re-deriving state that the next record, orreadRestart(), overwrote right away.- Parameters:
records – The
TopologyRecordsequence to replay.journal (Journal) – Optional Journal for progress messages.
- Raises:
TopologyError – If the replayed topology’s fingerprint differs from the one recorded with the last record (when
verifyFingerprints). WithverifyFingerprintsPerRecordevery record is checked as it is replayed and the error names the first diverging one – so a divergence is located rather than merely detected.
- reserveElementNumbers(count=1)[source]
Reserve
countfresh element numbers.The allocator is monotonic: numbers are never recycled, and are never derived from
max(self._model.elements). Both properties matter beyond tidiness.Deriving the next number from
max(self._model.elements)makes numbering a function of the deletion history as well as the creation history – a contact facet set that is deleted and rebuilt (the common case between two refinements) hands the freed numbers straight back out – so a restart replay would have to reproduce creations, deletions and their interleaving to renumber identically. With one monotonic counter it only has to reproduce the ordered creation sequence, which is exactly what the recorded topology history holds.Never recycling additionally means a number refers to one element for the model’s entire lifetime, so
mergedWith()’s documented no-reuse assumption holds, and a reference cached by number cannot silently alias a different element.- Parameters:
count (int) – How many consecutive numbers to reserve.
- Returns:
The reserved numbers, in ascending order.
- Return type:
range
- reserveNodeNumbers(count=1)[source]
Reserve
countfresh node labels. The node-side counterpart ofreserveElementNumbers(), monotonic for the same reasons.The pattern this replaces is
len(model.nodes) + 1, which is a positional guess, not an allocator: once anything is deleted the dict has gaps, and the “next” label lands on a live node and silently overwrites it.- Parameters:
count (int) – How many consecutive labels to reserve.
- Returns:
The reserved labels, in ascending order.
- Return type:
range
- setupFingerprint()[source]
The
fingerprint()of the mesh as the input file built it. A checkpoint carries it, so that a resumed run can verify that it was rebuilt from the same mesh – the recorded topology changes are verified by the replay, but they say nothing about a mesh no modifier ever touched.- Returns:
The digest.
- Return type:
str
- Raises:
RuntimeError – If the model was never set up (
prepareYourself()).
- update(step=None)[source]
Run every model modifier to a fixed point, inside one topology window.
Modifiers depend on each other – refinement invalidates a tied surface’s facets, a deposition modifier creates elements refinement may then want to split, and a 2:1 balance may need to refine what another modifier just activated. Rather than asking the user to declare a dependency order (which cannot express mutual dependence anyway), each round offers every modifier the net change since that modifier last planned. A round in which nobody plans anything is the fixed point.
Determinism comes from the round structure, not from luck: within a round, modifiers run in
self._model.modelModifiersorder, which is input-file order.- Returns:
True if the topology changed, so the solver rebuilds its equation system.
- Return type:
bool
- Raises:
TopologyError – If the rounds do not settle within
maxRounds, which means some modifier keeps planning in response to its own output. The message names the offenders.
- history
the restart history, replayed on resume.
- Type:
Ordered record of every applied model-modifier decision
- maxRounds
Guard against a model modifier that keeps planning in response to its own output.
- meshDependents
Consumers that cache mesh-derived state; see
refreshMeshDependents().
- verifyFingerprints
Compare the replayed topology’s fingerprint against the recorded one, once, after the whole history has been replayed. See
replayHistory().
- verifyFingerprintsPerRecord
Additionally compare a fingerprint after every replayed record, to locate a divergence. Off by default: it costs one whole-mesh walk per record.
- version
Bumped on every structural mutation; drives pull-based reconcile.