Skip to content

Split property_engine.h into separately tested units (no behaviour change) #232

Open

Description

@petlenz

Part of #237. First phase: every later phase touches the engine.

Why

core/property_engine.h grows from 609 lines on main to 1359 lines with #229. One class now holds six responsibilities:

Responsibility Today (line numbers on feature/issue-218)
Graph building compute_execution_order (~317), edges and kinds, find_cycle/describe_cycle (~487-528), upstream_of (~1023), graph()
Scheduling constant folding (find_constants ~570, refresh_constants, retire, verify), track_reads, route_to_writers, distribute
Iteration groups and stages form_groups (~897), stage running
Derivatives index_partials (~1100), derivative<R>, differentiate, accumulate (~1258), fold (~1270), plans and slots
Residual systems plan_systems (~1118), solve, step, jacobian, jacobian_by_differences, start, scatter, gather (~753-890)
History state commit, revert, checkpoint/restore, collect_history_properties

Each feature added since #173 has grown the same class. The units share members freely: m_edges, m_reverse_edges, m_upstream, the stage lists and the plans. None of them can be tested without a full material context. The review of #226 and #229 found two bugs whose cause was this sharing:

  • group membership read the non-local edges only;
  • update_property() did not know about system stages.

Design

  • One header per unit under core/engine/, each a class with an explicit narrow interface:
    • graph.h: the immutable result of build(), holding nodes, typed edges, topological order, upstream_of and the cycle report;
    • schedule.h: the active/constant callback lists and stages, built from a graph;
    • groups.h
    • systems.h
    • derivatives.h (the engine side; packing stays in core/derivatives.h)
    • history_state.h
  • property_engine becomes a facade that owns the units and forwards the public API unchanged. material_context and every host stay untouched.
  • Data flows one way: graph → schedule → (groups, systems, derivatives). A unit receives the graph by const reference, and no unit reaches into another's members.
  • Each unit gets its own test file working on a hand-built graph of fake properties, with no materials.

Tests

  • All existing tests pass unchanged; this refactor has no behaviour change.
  • Report CSVs are byte-identical to before.
  • New per-unit tests, for example:
    • graph: cycle report and upstream sets on a hand-built graph;
    • schedule: constant folding on a graph with a guarded node;
    • systems: Newton on a 2×2 residual through fake properties;
    • derivatives: accumulation order on a diamond graph.
  • A size check in CI: no header under core/engine/ over about 400 lines (a script, not a hard rule).

Depends on

#229 merged (it is the last PR adding to the monolith).

Out of scope

Any API change, and new features. Fixes found during the split go in separate commits with their own red test.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions