Skip to content

ltm: should a ceteris-paribus partial freeze TIME? (today it does not, and the target's autonomous time evolution is attributed to the link) #1016

Description

@bpowers

The question

Should a ceteris-paribus partial hold TIME at its previous value?

Today it does not. db/ltm/compile.rs (three sites, each
|| matches!(effective, "time" | "dt" | "initial_time" | "final_time")) skips those idents during
dependency processing, and the ceteris-paribus wrap freezes dependencies. So for a target whose
equation reads time() — directly or through STEP/RAMP/PULSE — the partial advances the clock
while holding every model input back, and the target's autonomous time evolution is attributed to
the link
.

Why the reference implies TIME should be frozen

docs/reference/ltm--loops-that-matter.md:231-233 defines the quantity as

Delta_x(z) = f(x_current, y_previous) - z_previous: the partial change in z due to x alone,
computed by re-evaluating f with the current value of x but the previous values of all other
inputs (ceteris paribus)

The document never mentions TIME, so this is an inference from the formula rather than a quotation.
But the formula only closes if "all other inputs" covers everything f reads that varies. Take a
source x that does not appear in f: then f(x_current, y_previous) reduces to f(y_previous),
and the anchor z_previous is f at the previous step's inputs, so Delta_x(z) = 0 identically.
Exclude TIME and that identity fails — a source with no influence whatsoever scores nonzero, which
also contradicts the reference's own edge case at :249 ("If x does not change, the link score is
0").

Evidence that this is what is happening

Measured on C-LEARN v77 with LTM enabled, by zeroing the arms whose model dependencies are all
frozen and diffing all 30,123 result slots × 251 saved steps:

  • 35 of 35 link-score variables whose values move carry a live time-dependent term in their
    partial. No exceptions.
  • 26 of them sit at exactly ±1.0 — which for a fully-frozen arm can only mean |N| = |Δz|, i.e.
    the entire change in the target came from something the freeze did not reach. In a fully-frozen
    partial the only such thing is the clock.
  • The competing explanation for unit magnitude does not apply: the single-live-input case
    (docs/design/ltm--loops-that-matter.md:391, "for a single-input black box z = F(x) all of Δz
    is attributable to x") requires a live input, and these arms have none.

Two independent derivations converge: the algebra above, and the runtime measurement.

Blast radius

This is a scoring-semantics change, not a refactor. On C-LEARN, 6,975 of 16,967 link-score
arms (41.1%)
retain a live time-dependent term (time 7,812 occurrences, step 330, time_step
74, final_time 12, ramp 4). Freezing TIME would move roughly two in five link scores: the
fully-frozen ones to exactly 0, and the rest to a different, clock-free magnitude.

The guard template contributes none of these — its TIME/INITIAL_TIME are bare globals, not calls
— so every occurrence is inside a partial.

Link scores feed loop scores and dominance ranking, and relative loop scores are normalized over a
partition, so a changed magnitude moves the denominator every sibling score is divided by. Validation
therefore has to compare dominance rankings, not epsilon-compare series.

What is unmeasured

  • Nobody has implemented TIME-freezing or measured the resulting scores. What exists is the arm
    count it unblocks (measured) and the partial count it touches (measured) — not the new values,
    and not whether any dominance ranking flips.
  • No claim is made about what Vensim or Stella do here. That is an external-tool claim and it is
    unverified. If it should be settled against the tools rather than against the formula, the
    ground-truth runs under test/ are the instrument, and per this repo's standing rule an
    unverified claim about another tool must not carry the design decision.
  • The live-time census is a PREVIOUS-depth-tracking text scan of generated arms, consistent with
    the AST-level evidence but not itself an AST walk.

Why it also gates a large performance win

#977 (arm materialization on arrayed targets) can only omit arms whose partial is provably
PREVIOUS(target). On C-LEARN that is 4,036 arms today; 5,035 further arms are blocked solely by a
live time()
. Under a TIME-freezing convention those become zero by construction and omission
stops being an approximation — taking that fix from −17.4% of the flow program to roughly −42%.

The two questions are the same question: those 5,035 arms are either scoring the clock (and are
wrong today) or they are not (and must keep their values). The performance consequence follows from
the correctness answer, not the other way round, and must not be allowed to motivate it.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    ltmLoops that Matter (LTM) analysis subsystem

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions