engine: lookup-only tables produce no series (#606)#609
Conversation
A standalone Vensim lookup -- a graphical function with no driving input -- is a table indexed by an explicit input, not a time series. Lowering it to gf(Time) (the #590 import fix) synthesised a phantom Time-indexed series: a unit error in general, and a value genuine Vensim never saves. Such a table is now a first-class non-value-bearing static table (Variable::Var::is_table_only / db::source_var_is_table_only): excluded from the runlist and the saved output, its data reached only through LOOKUP(table, x) call sites that resolve the table by ident. A lookup-table reference is a layout reference, not a data-flow dependency, so a new BuiltinContents::LookupTable walk variant keeps it off the dependency/causal/LTM graphs while referenced_tables on VariableDeps/ImplicitVarDeps re-supplies the fragment compiler's metadata and tables map. A bare reference to a table (no argument) is now a compile error (LookupReferencedWithoutArgument); an empty equation paired with a graphical function is no longer flagged as EmptyEquation. The MDL importer emits the canonical empty-equation form instead of the "0+0" sentinel, still accepted on read for already-serialized models (full removal tracked in #608). The dead gf(Time) lowering is removed; the C-LEARN EXPECTED_VDF_RESIDUAL carve-out shrinks from 13 to 4.
Review:
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #609 +/- ##
==========================================
- Coverage 82.87% 82.86% -0.02%
==========================================
Files 261 261
Lines 69813 69836 +23
==========================================
+ Hits 57856 57867 +11
- Misses 11957 11969 +12 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Fixes #606. A standalone Vensim lookup -- a graphical function with no driving input (e.g.
Historical GDP LOOKUP[COP]((1850,...),...)) -- is a table indexed by an explicit input, not a time series. The #590 import-pipeline fix lowered such a table togf(Time), synthesising a phantom Time-indexed series: a latent unit error (benign in C-LEARN only by coincidence -- year-indexed tables, consumers callingLOOKUP(self, Time / One year),One year == 1), and a value genuine Vensim never saves.This makes a lookup-only table a first-class non-value-bearing static table (
Variable::Var::is_table_only/db::source_var_is_table_only): excluded from the runlist and from the saved output, so it produces no series of its own. Its data is reached only throughLOOKUP(table, x)call sites, which resolve the table by ident ->base_gf(independent of the runlist), so two calls at different arguments are independent intermediate expressions.Key changes
builtins::BuiltinContents::LookupTablewalk variant keeps it off the dependency / causal / LTM graphs, while areferenced_tablesset onVariableDeps/ImplicitVarDepsre-supplies the fragment compiler's metadata + tables map (so consumers still resolve the table).ErrorCode::LookupReferencedWithoutArgument. An empty equation paired with a graphical function is no longer flagged asEmptyEquation."0+0"sentinel."0+0"is still accepted on read for already-serialized models; fully removing that read-shim (gated on a data migration) is tracked in engine: remove legacy "0+0" LOOKUP_SENTINEL read-shim from lookup-only detection (gated on datamodel migration) #608.gf(Time)lowering (lookup_only_index_expr,LookupOnlyLayout); the shared lookup-only predicate moved tosrc/variable.rs.Tests
lookup_only_tests.rsrewritten to the new contract: no saved series across scalar / apply-to-all / arrayed shapes, consumers read the table, multi-call independence (gap = g(Time) - g(Time-1)), bare-reference compile error, and the legacy"0+0"form still accepted.simulates_clearn+clearn_residual_exactness,--ignored) pass; theEXPECTED_VDF_RESIDUALcarve-out shrinks from 13 to 4 (the 9 lookup-only ghost columns leave the comparison cleanly).