Skip to content

ltm: 24 C-LEARN link scores are identically zero in discovery mode although both endpoints vary #1064

Description

@bpowers

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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