Skip to content

Design: build the property graph the library is named for (property-granular edges, one dataflow) #78

Description

@petlenz

The library's central abstraction is a property graph. The graph it actually builds and sorts is a material graph, and there are three unrelated ways for one material to read another's value. This issue is the umbrella for fixing that, plus the five design changes that become cheap once it is fixed.

Everything here keeps run-time assembly. None of it proposes compile-time composition, and none of it is motivated by performance: the graph costs ~5% of a J2 plastic UMAT call (13 of 287 ns), and std::function dispatch measures within 0.2 ns of a raw function pointer (#77 has the numbers).

Work is additive wherever possible: new mechanisms land alongside the old ones, materials port one at a time behind their existing tests, and nothing is removed until nothing uses it.


1. Property-granular edges, and one dataflow

The problem. property_engine.h:174 iterates prop->traits().input_dependencies to add edges. Nothing in the tree ever writes to that field. Ordering is therefore per material, which

  • reports cycles that do not exist at property level (A.x -> B.y and B.z -> A.w reads as A <-> B);
  • gives a cycle error that names no material and no property;
  • means EdgeKind::Local edges create no ordering at all -- only Global edges are added.

And there are three access paths that do not agree:

path ordered by the graph? null-checked?
engine topological push yes, per material n/a
update_source() pull, inside a material no yes
material_ref no assert only, and Release is -O3 -DNDEBUG

The ordering that makes the kinematic-hardening rig in #55 work is a BFS-level accident: solver::g has indegree 0 and kin::stress has indegree 1, and the sort is FIFO BFS. A model taking its strain over a Local edge gets no such protection.

The change.

  • Populate input_dependencies when an input_property is wired. The field is already there and already read.
  • Make cycle and ordering errors print the property path.
  • Make pull the primary mechanism and push a convenience. The return maps genuinely need pull -- an inner Newton re-evaluates hardening at trial states the topological order knows nothing about -- so ctx.update() becomes "pull each sink" rather than a second, separate mechanism. This also removes the dead hardening evaluation the performance work counted: 5 hardening.compute() calls per plastic step, exactly 1 of them scheduled by the graph and immediately overwritten.
  • Fold material_ref into the edge mechanism, or remove it.

Subsumes #68. Related: #59 (the material_ref null check), #69.

2. Split the physics model from the graph adapter

The cleanest code in the tree is j2_kinematic_system in #55: a plain struct with compute_residual / compute_jacobian, no graph, no registry, testable against a closed form and a finite difference.

The shipped materials are the opposite. Each publishes properties, owns parameters, embeds a solver, writes history and implements physics in one class -- which is why the three plasticity materials share roughly half their code by copy and J2 is written twice. That duplication has already cost a real bug: the explicit RK stage passed G where the others pass effective_modulus(G), a 3x error in the multiplier.

The change. Define a residual-system concept (residual, jacobian, unknowns) in the shape #55 already has; write one generic return_map_material adapter that wires any such system into the graph; port materials one at a time behind their existing tests.

New physics then needs no graph knowledge at all. Starts with #75.

3. Parameter descriptors instead of std::any + type_index

Dynamic assembly needs type erasure at construction, not at access -- and the materials already resolve parameters to typed references in their constructors. What is left is a type_index-keyed reader registry that cannot distinguish vector_newton's zero_blocks from weighted_sum's terms (both are vector<pair<string,string>>), so a malformed weighted_sum term reports a zero_blocks error.

The change. One descriptor per parameter, declared once: name, type, required, range, unit, doc string. Read by the C++ path, the JSON path, the error messages, and a generated documentation table.

That this one seam covers #65, #66, #67, #74 and the type table in #72 is the evidence it is the right seam.

4. finalize() as the single validation gate

Validation is currently scattered and mostly absent. finalize() is the only point that sees the whole model, so it should reject, each naming the material and property:

Once it does, the hot path can assume a valid graph. commit()/revert() also need the check_finalized their siblings have (#60).

5. Stop using history properties as Newton mailboxes

m_kappa.new_value() = trial; m_H.update_source(); uses committed state as a scratch channel, conflating "state the driver commits" with "an iterate I am trying". It is also why the STATEV shear-factor problem (#63) is latent rather than visible.

The change. Give the pull path an explicit "evaluate at this value" argument, so a trial state never has to be written into history to be seen.

Keeps the existing rule that materials and solvers never call commit()/revert() -- the driver does.

6. "Solver" as a first-class concept

Solvers being materials is elegant and stays. But converged() is a convention rather than a contract, which is how rk_integrator shipped both unregistered and without one (#61). Make it an interface the conformance test checks, and extend that test to solvers/ (#35).


Not changing

Order

1 -> 4 -> 3 -> 2 -> 6 -> 5. Item 1 is the prerequisite: the library is named for a graph it does not build, and 2, 4 and 6 are all cleaner once it does.

All references are to main at 91bd26f.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions