What happens
src/simlin-engine/examples/clearn_discover.rs compiles C-LEARN v77 (test/xmutil_test_models/C-LEARN v77 for Vensim.mdl) with LTM in discovery mode, runs the VM, and flags link-score columns that are 0 at every saved step while both endpoints vary over the run:
CLEARN_SKIP_DISCOVERY=1 cargo run --release -p simlin-engine --example clearn_discover
The heuristic sits around lines 196-217 of that example. It was written to catch fragments that silently fail and read a constant 0, the GH #587 failure mode. On C-LEARN it reports 24 such columns:
- 7636
link_score columns in total
- 1845 with at least one finite nonzero value
- 251 saved steps
The same 24 columns appear on main at 1996601 and on branch ltm-audit-wasm-function-splitting at f9b53a7 (PR #1063), so that branch did not introduce them.
The example prints only the first 15 (suspect_zero_cols.iter().take(15), line 230). The full list below came from temporarily raising that cap.
All 24 columns
Prefix $⁚ltm⁚link_score⁚ omitted.
- co2_conc_change_at_impact_year→co2_conc_change_at_impact_year
- co2_emissions_at_y2009→annual_change_of_co2_emissions
- co2_emissions_at_y2009→co2_emissions_at_y2009
- co2eq_conc_change_at_impact_year→co2eq_conc_change_at_impact_year
- global_annual_rate_of_co2eq_2050_to_2100→global_annual_rate_of_co2eq_2050_to_2100
- global_annual_rate_of_co2eq_peak_to_2050→global_annual_rate_of_co2eq_peak_to_2050
- global_annual_rate_of_co2eq_to_target→global_annual_rate_of_co2eq_to_target
- global_co2eq_in_2050→global_annual_rate_of_co2eq_2050_to_2100
- global_co2eq_in_2050→global_co2eq_in_2050
- global_co2eq_in_2100→global_co2eq_in_2100
- global_co2eq_peak→global_annual_rate_of_co2eq_peak_to_2050
- global_co2eq_peak→global_annual_rate_of_co2eq_to_target
- global_co2eq_peak→global_co2eq_peak
- global_co2eq_target→global_annual_rate_of_co2eq_to_target
- global_co2eq_target→global_co2eq_target
- global_cum_co2eq_2000_to_2049→max_probabilities_of_exceeding_2_deg_c
- global_cum_co2eq_2000_to_2049→min_probabilities_of_exceeding_2_deg_c
- global_rs_co2eq_at_2005→global_rs_co2eq_at_2005
- global_start_emissions_for_im_7→global_emissions_with_linear_reduction
- global_total_co2_emissions→co2_emissions_at_y2009
- max_probabilities_of_exceeding_2_deg_c→max_probabilities_of_exceeding_2_deg_c
- min_probabilities_of_exceeding_2_deg_c→min_probabilities_of_exceeding_2_deg_c
- temperature_change_at_impact_year→temperature_change_at_impact_year
- user_other_forcings→other_forcings
Equation shapes
The equations are quoted from test/xmutil_test_models/C-LEARN v77 for Vensim.mdl. The MDL importer rewrites SAMPLE IF TRUE(cond, input, init) to IF cond THEN input ELSE PREVIOUS(SELF, init) (src/simlin-engine/src/mdl/xmile_compat.rs, the "sample if true" arm). That rewrite is where the self links come from: none of these equations names itself in the source text.
1. SAMPLE IF TRUE self links (14)
A sample-and-hold variable reads itself through the PREVIOUS(SELF, init) hold arm. All 14 self links in the list have this shape:
co2_conc_change_at_impact_year, co2_emissions_at_y2009, co2eq_conc_change_at_impact_year, global_annual_rate_of_co2eq_2050_to_2100, global_annual_rate_of_co2eq_peak_to_2050, global_annual_rate_of_co2eq_to_target, global_co2eq_in_2050, global_co2eq_in_2100, global_co2eq_peak, global_co2eq_target, global_rs_co2eq_at_2005, max_probabilities_of_exceeding_2_deg_c, min_probabilities_of_exceeding_2_deg_c, temperature_change_at_impact_year.
Examples:
Global CO2eq peak = SAMPLE IF TRUE(Time<=Global start year, Global CO2 Equivalent Emissions, Global CO2 Equivalent Emissions)
CO2 Emissions at y2009 = SAMPLE IF TRUE(Time=y2009, Global total CO2 emissions, :NA:)
2. Links from another variable into a SAMPLE IF TRUE target (7)
- global_co2eq_in_2050→global_annual_rate_of_co2eq_2050_to_2100
- global_co2eq_peak→global_annual_rate_of_co2eq_peak_to_2050
- global_co2eq_peak→global_annual_rate_of_co2eq_to_target
- global_co2eq_target→global_annual_rate_of_co2eq_to_target
- global_cum_co2eq_2000_to_2049→max_probabilities_of_exceeding_2_deg_c
- global_cum_co2eq_2000_to_2049→min_probabilities_of_exceeding_2_deg_c
- global_total_co2_emissions→co2_emissions_at_y2009 (sampled only at
Time=y2009)
Equations:
Global annual rate of CO2eq 2050 to 2100 = SAMPLE IF TRUE(Time<=FINAL TIME, LN(MAX(0.01, Global CO2eq in 2100)/Global CO2eq in 2050)/(FINAL TIME-y2050), :NA:)
Global annual rate of CO2eq peak to 2050 = SAMPLE IF TRUE(Time<=y2050, LN(Global CO2eq in 2050/IF THEN ELSE(Test peaks, Peak Global CO2eq, Global CO2eq peak))/(MAX(One year, y2050-Peak year)), :NA:)
Global annual rate of CO2eq to target = SAMPLE IF TRUE(Time=IF THEN ELSE(Global target year>Global start year, Global target year, y2050), LN(Global CO2eq target/IF THEN ELSE(Test peaks, Peak Global CO2eq, Global CO2eq peak))/(MAX(One year, IF THEN ELSE(Global target year>Global start year, Global target year, y2050)-Peak year)), :NA:)
Max probabilities of exceeding 2 Deg C = SAMPLE IF TRUE(Time>y2049, MIN(1, IF THEN ELSE(Global Cum CO2eq 2000 to 2049 < max prob equation cutoff, EXP(... * Global Cum CO2eq 2000 to 2049), EXP(...)/(1 + EXP(...))))*"100 percent", :NA:). Min probabilities of exceeding 2 Deg C has the same structure.
CO2 Emissions at y2009 = SAMPLE IF TRUE(Time=y2009, Global total CO2 emissions, :NA:)
In the two global_co2eq_peak→... links, the source is read only on one arm of IF THEN ELSE(Test peaks, Peak Global CO2eq, Global CO2eq peak). It is not yet known which arm is active in this run.
3. IF THEN ELSE targets whose reading arm may be inactive (3)
Annual change of CO2 emissions = IF THEN ELSE(Time>y2009 :AND: Global total CO2 emissions>Zero emissions, LN(Global total CO2 emissions/CO2 Emissions at y2009)/(Time-y2009), :NA:), for co2_emissions_at_y2009→annual_change_of_co2_emissions
Global emissions with linear reduction = IF THEN ELSE(Global percent of reference emissions=0, RS global emissions, MAX(min emissions, Global start emissions for IM 7 - DELAY3(IM 7 global reductions, MAX(One year, Years to delay)))), for global_start_emissions_for_im_7→global_emissions_with_linear_reduction
Other Forcings = IF THEN ELSE(Control other forcings=1 :AND: Time>Last historical RF year, User other forcings, IF THEN ELSE(...)), for user_other_forcings→other_forcings
That gives 14 + 7 + 3 = 24.
Unverified hypotheses
These have not been checked. Many of the 24 may be correct structural zeros rather than defects:
- Self links may be correct zeros. A sample-and-hold variable's value comes either from its input arm, which does not read itself, or from holding unchanged. A zero-change guard can therefore legitimately zero its self link.
- Sources on an inactive arm contribute nothing. A source read only on an inactive IF arm, or only at one sample instant, also contributes nothing.
- Some cases need per-link adjudication. These look less obviously structural and need checking against the partial definition:
global_co2eq_in_2050→global_annual_rate_of_co2eq_2050_to_2100: the sampling condition Time<=FINAL TIME is always true, so the input arm is always live.
global_cum_co2eq_2000_to_2049→max_probabilities_of_exceeding_2_deg_c and →min_probabilities_of_exceeding_2_deg_c.
- The heuristic cannot tell the two cases apart. The example's "both endpoints vary" test cannot distinguish a structural zero from a silently broken fragment.
What resolving this should establish
- Expected scores. For each shape, derive the expected score independently from the equation and the ceteris-paribus definition.
- Classification. Classify each of the 24 as a correct zero or a defect, and fix any defects.
- Harness suppression. Teach the harness to suppress established structural-zero classes, so the SUSPECT list reports only genuine suspects. Otherwise the list stays noisy, and a new real failure would hide among these 24.
- Truncation. Print the full suspect list, or at least note the truncation, instead of
take(15).
Related
What happens
src/simlin-engine/examples/clearn_discover.rscompiles C-LEARN v77 (test/xmutil_test_models/C-LEARN v77 for Vensim.mdl) with LTM in discovery mode, runs the VM, and flags link-score columns that are 0 at every saved step while both endpoints vary over the run:The heuristic sits around lines 196-217 of that example. It was written to catch fragments that silently fail and read a constant 0, the GH #587 failure mode. On C-LEARN it reports 24 such columns:
link_scorecolumns in totalThe same 24 columns appear on
mainat 1996601 and on branchltm-audit-wasm-function-splittingat f9b53a7 (PR #1063), so that branch did not introduce them.The example prints only the first 15 (
suspect_zero_cols.iter().take(15), line 230). The full list below came from temporarily raising that cap.All 24 columns
Prefix
$⁚ltm⁚link_score⁚omitted.Equation shapes
The equations are quoted from
test/xmutil_test_models/C-LEARN v77 for Vensim.mdl. The MDL importer rewritesSAMPLE IF TRUE(cond, input, init)toIF cond THEN input ELSE PREVIOUS(SELF, init)(src/simlin-engine/src/mdl/xmile_compat.rs, the"sample if true"arm). That rewrite is where the self links come from: none of these equations names itself in the source text.1. SAMPLE IF TRUE self links (14)
A sample-and-hold variable reads itself through the
PREVIOUS(SELF, init)hold arm. All 14 self links in the list have this shape:co2_conc_change_at_impact_year, co2_emissions_at_y2009, co2eq_conc_change_at_impact_year, global_annual_rate_of_co2eq_2050_to_2100, global_annual_rate_of_co2eq_peak_to_2050, global_annual_rate_of_co2eq_to_target, global_co2eq_in_2050, global_co2eq_in_2100, global_co2eq_peak, global_co2eq_target, global_rs_co2eq_at_2005, max_probabilities_of_exceeding_2_deg_c, min_probabilities_of_exceeding_2_deg_c, temperature_change_at_impact_year.
Examples:
Global CO2eq peak = SAMPLE IF TRUE(Time<=Global start year, Global CO2 Equivalent Emissions, Global CO2 Equivalent Emissions)CO2 Emissions at y2009 = SAMPLE IF TRUE(Time=y2009, Global total CO2 emissions, :NA:)2. Links from another variable into a SAMPLE IF TRUE target (7)
Time=y2009)Equations:
Global annual rate of CO2eq 2050 to 2100 = SAMPLE IF TRUE(Time<=FINAL TIME, LN(MAX(0.01, Global CO2eq in 2100)/Global CO2eq in 2050)/(FINAL TIME-y2050), :NA:)Global annual rate of CO2eq peak to 2050 = SAMPLE IF TRUE(Time<=y2050, LN(Global CO2eq in 2050/IF THEN ELSE(Test peaks, Peak Global CO2eq, Global CO2eq peak))/(MAX(One year, y2050-Peak year)), :NA:)Global annual rate of CO2eq to target = SAMPLE IF TRUE(Time=IF THEN ELSE(Global target year>Global start year, Global target year, y2050), LN(Global CO2eq target/IF THEN ELSE(Test peaks, Peak Global CO2eq, Global CO2eq peak))/(MAX(One year, IF THEN ELSE(Global target year>Global start year, Global target year, y2050)-Peak year)), :NA:)Max probabilities of exceeding 2 Deg C = SAMPLE IF TRUE(Time>y2049, MIN(1, IF THEN ELSE(Global Cum CO2eq 2000 to 2049 < max prob equation cutoff, EXP(... * Global Cum CO2eq 2000 to 2049), EXP(...)/(1 + EXP(...))))*"100 percent", :NA:).Min probabilities of exceeding 2 Deg Chas the same structure.CO2 Emissions at y2009 = SAMPLE IF TRUE(Time=y2009, Global total CO2 emissions, :NA:)In the two
global_co2eq_peak→...links, the source is read only on one arm ofIF THEN ELSE(Test peaks, Peak Global CO2eq, Global CO2eq peak). It is not yet known which arm is active in this run.3. IF THEN ELSE targets whose reading arm may be inactive (3)
Annual change of CO2 emissions = IF THEN ELSE(Time>y2009 :AND: Global total CO2 emissions>Zero emissions, LN(Global total CO2 emissions/CO2 Emissions at y2009)/(Time-y2009), :NA:), for co2_emissions_at_y2009→annual_change_of_co2_emissionsGlobal emissions with linear reduction = IF THEN ELSE(Global percent of reference emissions=0, RS global emissions, MAX(min emissions, Global start emissions for IM 7 - DELAY3(IM 7 global reductions, MAX(One year, Years to delay)))), for global_start_emissions_for_im_7→global_emissions_with_linear_reductionOther Forcings = IF THEN ELSE(Control other forcings=1 :AND: Time>Last historical RF year, User other forcings, IF THEN ELSE(...)), for user_other_forcings→other_forcingsThat gives 14 + 7 + 3 = 24.
Unverified hypotheses
These have not been checked. Many of the 24 may be correct structural zeros rather than defects:
global_co2eq_in_2050→global_annual_rate_of_co2eq_2050_to_2100: the sampling conditionTime<=FINAL TIMEis always true, so the input arm is always live.global_cum_co2eq_2000_to_2049→max_probabilities_of_exceeding_2_deg_cand→min_probabilities_of_exceeding_2_deg_c.What resolving this should establish
take(15).Related