User Guide
07.7 · Formula Engine

Dependency Tracking and Circular References

TX1 tracks every reference between cells, defines and aggregates. The dependency graph lets the engine:

  • Recalculate in the correct order when an input changes.
  • Reject circular references before they become a problem.
  • Report every formula that references a given define (useful before rename / delete).

How It Works

When a formula is saved, FormulaDependencyService.RegisterFormula(cellId, formula) parses the expression and extracts references:

TypeRegistered as
L5, #lq5Line reference (line 5)
#var_nameVariable (define) reference
#cc.ColCustom column

Each dependency becomes an edge in a directed graph.

Detecting Cycles

Before the save commits, WouldCreateCircularReference(cellId, formula) runs a depth-first walk. If it finds a cycle (A → B → C → A), the save is rejected and the offending path is shown to the user.

Evaluation Order

GetEvaluationOrder() returns a topological sort of the graph. The engine evaluates cells in that order so upstream values are available when dependents run.

Finding What Depends on a Cell

GetAffectedCells(cellId) returns the list of cells that would need recalculation if the target changed. The engine calls this after any edit and quietly re-evaluates only the affected set — not the whole project.

User-Visible Cycle Detection

SymptomCause
#CIRC in a cellThat cell is part of a cycle; the chain is shown on hover.
Save rejected with "circular reference" toastA new formula would close a cycle.
Inability to delete a defineOther formulas still depend on it.

Breaking a Cycle

Typical causes and fixes:

SituationFix
L5 = L10 + 1, L10 = L5 * 2Pick one to hold the independent value.
#a = #b, #b = #aReplace one with a literal.
Self-referencing aggregate (L5 = SUM(L1:L10)) — L5 is inside the rangeRestrict the range or exclude L5.

Global and Scoped Scope Interactions

The dependency graph is aware of scope. A scoped parametric's dependencies do not leak into Global scope. This means you can safely name two scoped parametrics the same way and the cycle detector won't confuse them.

Performance

Dependency graph maintenance is O(edges) per edit. Even in large projects (thousands of items, hundreds of defines) updates are instantaneous.

Usage Report

GetParametricUsageAsync(defineId) returns a List<ParametricUsageInfo>:

  • The formula text.
  • The entity referencing it (item / overhead / breakdown / custom column).
  • The full path for user navigation.

Run this before renaming or deleting a define.