use crate::engine::{CycleConfig, Engine, EvalConfig};
use crate::test_workbook::TestWorkbook;
use formualizer_common::LiteralValue;
use formualizer_parse::parser::parse;
fn iterate_engine(max_iterations: u32, max_change: f64) -> Engine<TestWorkbook> {
Engine::new(
TestWorkbook::new(),
EvalConfig::default().with_cycle(CycleConfig::iterate(max_iterations, max_change)),
)
}
fn set_formula(engine: &mut Engine<TestWorkbook>, sheet: &str, row: u32, col: u32, f: &str) {
engine
.set_cell_formula(sheet, row, col, parse(f).expect("parse"))
.expect("set formula");
}
fn num(engine: &Engine<TestWorkbook>, sheet: &str, row: u32, col: u32) -> f64 {
match engine.get_cell_value(sheet, row, col) {
Some(LiteralValue::Number(n)) => n,
Some(LiteralValue::Int(i)) => i as f64,
other => panic!("expected number at {sheet} r{row}c{col}, got {other:?}"),
}
}
const TOL: f64 = 0.01;
#[test]
fn scc_feeds_scc_converges_both_and_telemetry_counts_two() {
let mut engine = iterate_engine(100, 0.001);
set_formula(&mut engine, "Sheet1", 1, 1, "=0.5*10+0.5*A2");
set_formula(&mut engine, "Sheet1", 2, 1, "=0.5*A1");
set_formula(&mut engine, "Sheet1", 1, 2, "=0.5*A1+0.5*B2");
set_formula(&mut engine, "Sheet1", 2, 2, "=0.5*B1");
engine.evaluate_all().unwrap();
assert!((num(&engine, "Sheet1", 1, 1) - 20.0 / 3.0).abs() < TOL);
assert!((num(&engine, "Sheet1", 2, 1) - 10.0 / 3.0).abs() < TOL);
assert!(
(num(&engine, "Sheet1", 1, 2) - 40.0 / 9.0).abs() < TOL,
"B1 must be built from SCC₁'s CONVERGED output, got {}",
num(&engine, "Sheet1", 1, 2)
);
assert!((num(&engine, "Sheet1", 2, 2) - 20.0 / 9.0).abs() < TOL);
let t = engine.last_cycle_telemetry();
assert_eq!(t.static_sccs, 2);
assert_eq!(t.iterated_sccs, 2, "both SCCs iterate");
assert_eq!(t.converged_sccs, 2);
assert_eq!(t.capped_sccs, 0);
assert_eq!(t.live_cycles_witnessed, 2);
assert!(t.max_passes_single_scc < 100);
}
#[test]
fn diamond_of_four_sccs_converges_in_condensation_order() {
let mut engine = iterate_engine(100, 0.001);
set_formula(&mut engine, "Sheet1", 1, 1, "=0.5*10+0.5*A2");
set_formula(&mut engine, "Sheet1", 2, 1, "=0.5*A1");
set_formula(&mut engine, "Sheet1", 1, 2, "=0.5*A1+0.5*B2");
set_formula(&mut engine, "Sheet1", 2, 2, "=0.5*B1");
set_formula(&mut engine, "Sheet1", 1, 3, "=0.5*A1+0.5*C2");
set_formula(&mut engine, "Sheet1", 2, 3, "=0.5*C1");
set_formula(&mut engine, "Sheet1", 1, 4, "=0.5*(B1+C1)+0.5*D2");
set_formula(&mut engine, "Sheet1", 2, 4, "=0.5*D1");
engine.evaluate_all().unwrap();
assert!((num(&engine, "Sheet1", 1, 1) - 20.0 / 3.0).abs() < TOL);
assert!((num(&engine, "Sheet1", 1, 2) - 40.0 / 9.0).abs() < TOL);
assert!((num(&engine, "Sheet1", 1, 3) - 40.0 / 9.0).abs() < TOL);
assert!(
(num(&engine, "Sheet1", 1, 4) - 160.0 / 27.0).abs() < TOL,
"D1 needs BOTH parallel SCC outputs converged first, got {}",
num(&engine, "Sheet1", 1, 4)
);
let t = engine.last_cycle_telemetry();
assert_eq!(t.static_sccs, 4);
assert_eq!(t.iterated_sccs, 4);
assert_eq!(t.converged_sccs, 4);
assert_eq!(t.capped_sccs, 0);
}
#[test]
fn capped_scc_feeding_converging_scc_uses_the_capped_value() {
let mut engine = iterate_engine(5, 0.001);
set_formula(&mut engine, "Sheet1", 1, 1, "=A1+1"); set_formula(&mut engine, "Sheet1", 1, 2, "=0.5*A1+0.02*B2");
set_formula(&mut engine, "Sheet1", 2, 2, "=0.5*B1");
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 5.0);
assert!(
(num(&engine, "Sheet1", 1, 2) - 0.5 * 5.0 / 0.99).abs() < TOL,
"B1 from A1's capped value, got {}",
num(&engine, "Sheet1", 1, 2)
);
let t = engine.last_cycle_telemetry();
assert_eq!(t.iterated_sccs, 2);
assert_eq!(t.converged_sccs, 1);
assert_eq!(t.capped_sccs, 1);
engine.evaluate_all().unwrap();
assert_eq!(num(&engine, "Sheet1", 1, 1), 10.0);
assert!((num(&engine, "Sheet1", 1, 2) - 0.5 * 10.0 / 0.99).abs() < TOL);
}
#[test]
fn scc_chain_through_plain_acyclic_middleman_stays_ordered() {
let mut engine = iterate_engine(100, 0.001);
set_formula(&mut engine, "Sheet1", 1, 1, "=0.5*10+0.5*A2");
set_formula(&mut engine, "Sheet1", 2, 1, "=0.5*A1");
set_formula(&mut engine, "Sheet1", 1, 13, "=A1*3"); set_formula(&mut engine, "Sheet1", 1, 2, "=0.5*M1+0.5*B2");
set_formula(&mut engine, "Sheet1", 2, 2, "=0.5*B1");
engine.evaluate_all().unwrap();
let a1 = 20.0 / 3.0;
assert!((num(&engine, "Sheet1", 1, 13) - a1 * 3.0).abs() < 3.0 * TOL);
assert!(
(num(&engine, "Sheet1", 1, 2) - (2.0 / 3.0) * a1 * 3.0).abs() < 3.0 * TOL,
"got {}",
num(&engine, "Sheet1", 1, 2)
);
let t = engine.last_cycle_telemetry();
assert_eq!(t.iterated_sccs, 2);
assert_eq!(t.converged_sccs, 2);
}