Skip to content

engine: arrayed VECTOR SORT ORDER returns flat global indices instead of per-iterated-slice 0-based ranks #585

@bpowers

Description

@bpowers

Summary

For an arrayed (multi-dimensional) source, Opcode::VectorSortOrder returns global flat indices into the entire array instead of per-iterated-slice 0-based ranks. When the resulting "order" is fed to VECTOR ELM MAP, the out-of-range global indices index a single-row slice -> out-of-bounds -> NaN. This is a runtime-numeric correctness bug (the model compiles and runs to FINAL TIME).

Concrete C-LEARN manifestation

C-LEARN (test/xmutil_test_models/C-LEARN v77 for Vensim.mdl):

Target Order[COP,Target]   = VECTOR SORT ORDER(Effective Target Year[COP,Target], ASCENDING)   ; line ~19565
sorted target year[COP,Target] = VECTOR ELM MAP(Effective Target Year[COP,t1], Target Order[COP,Target])  ; line ~19486

Target has 3 elements; COP has 7 rows. Opcode::VectorSortOrder (src/simlin-engine/src/vm.rs:~2334-2377) iterates the whole input view (for i in 0..size, size = input_view.size()) and writes orig_idx -- the global flat index -- into the result. So for the 7th COP row it emits [18,19,20] instead of the per-row 0-based ranks [0,1,2].

VECTOR ELM MAP (src/simlin-engine/src/vm_vector_elm_map.rs:~107, the flat_i = base_i + offset_val path with the [0, full_len) -> NaN guard) then uses [18,19,20] to index the 7-element single-column slice -> out of bounds -> NaN. (The ELM MAP OOB->NaN behavior is the correct genuine-Vensim semantics from the Phase 5 work; the bug is the bad indices coming from VectorSortOrder, not the guard.)

Observed: target_order[cop_developing_b] = [18,19,20] (Simlin) vs [0,1,2] (Ref.vdf); sorted_target_year[cop_developing_b] = NaN vs 4000.0 (Ref.vdf). This is one of the two root causes of C-LEARN's core series being entirely NaN at AC7.3.

Root cause

Opcode::VectorSortOrder sorts and ranks over the flattened whole-array view and emits absolute flat indices. This is the multi-row / apply-to-all (A2A) case. Phase 4 (AC5) corrected the 1-based->0-based output for the single-row case but did not cover per-iterated-slice ranks for a multi-dimensional source: a sort order for [COP,Target] must produce, for each iterated COP row, a 0-based permutation within that row ([0..Target.len())), not flat indices into the whole [COP,Target] array.

Why it matters

  • Correctness: any arrayed VECTOR SORT ORDER whose result is consumed positionally (e.g. as the offset argument to VECTOR ELM MAP, the canonical Vensim idiom) produces out-of-range indices -> NaN. C-LEARN's sorted target year series is entirely NaN as a direct consequence.

Components affected

  • src/simlin-engine/src/vm.rs:~2334-2377 -- Opcode::VectorSortOrder (emits global flat indices over the whole view)
  • src/simlin-engine/src/vm_vector_elm_map.rs:~107 -- the consumer whose OOB->NaN guard (correctly) trips on the bad indices

Possible approach for resolution

VectorSortOrder must rank within the currently-iterated source slice (per-row), 0-based, producing a valid per-row index permutation rather than absolute flat indices into the whole array. The fix must keep the single-row AC5 case byte-identical (it is already genuine-Vensim 0-based for an effectively-1-D source).

Context / lineage

Freshly root-caused during Phase 6 of the element-level cycle resolution work (branch clearn-hero-model, HEAD b0910842). Being fixed now as Phase 6 Task 9; filing for traceability. Relate to the AC5 / Phase 4 VECTOR SORT ORDER work (commit a82dff29, which fixed the single-row 1-based->0-based output).

This is the Cluster B spurious-NaN bug. Relationship to existing issues:

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingengineIssues with the rust-based simulation engine

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions