use std::time::{Duration, Instant};
use symplex::prelude::*;
const ITERATIONS: u32 = 100;
fn bench<F: FnMut()>(mut f: F) -> (Duration, Duration) {
for _ in 0..5 {
f();
}
let start = Instant::now();
for _ in 0..ITERATIONS {
f();
}
let total = start.elapsed();
let avg = total / ITERATIONS;
(total, avg)
}
fn report(label: &str, avg: Duration) {
if avg.as_micros() > 1000 {
println!(" {label:<45} {:>8.2} ms", avg.as_secs_f64() * 1000.0);
} else {
println!(" {label:<45} {:>8.2} µs", avg.as_nanos() as f64 / 1000.0);
}
}
fn section(title: &str) {
println!();
println!("╔══════════════════════════════════════════════════════════════╗");
println!("║ {title:<60} ║");
println!("╚══════════════════════════════════════════════════════════════╝");
}
#[test]
#[ignore = "benchmark: prints timings only (~10-30 s in debug); run with --ignored --nocapture --release"]
fn perf_simple_polynomial() {
section("1. Simple polynomial: x³ + 2x + 1");
let ctx = Context::new();
let x = ctx.symbol("x");
let build = || x.powi(3) + &x * 2 + 1;
let expr = build();
println!(" Input: {expr}");
println!(" simplify() → {}", expr.simplify());
println!(" smart_simplify() → {}", expr.simplify());
println!(" full_simplify() → {}", expr.simplify());
println!(" simplify_trace() → {}", expr.simplify());
println!();
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("smart_simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("full_simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("simplify_trace() [pattern-rules only]", avg);
}
#[test]
#[ignore = "benchmark: prints timings only (~10-30 s in debug); run with --ignored --nocapture --release"]
fn perf_trig_identity() {
section("2. Trig identity: sin²(x) + cos²(x)");
let ctx = Context::new();
let x = ctx.symbol("x");
let expr = x.sin().powi(2) + x.cos().powi(2);
println!(" Input: {expr}");
println!(" simplify() → {}", expr.simplify());
println!(" smart_simplify() → {}", expr.simplify());
println!(" full_simplify() → {}", expr.simplify());
println!(" simplify_trace() → {}", expr.simplify());
println!();
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("smart_simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("full_simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("simplify_trace() [pattern-rules only]", avg);
}
#[test]
#[ignore = "benchmark: prints timings only (~10-30 s in debug); run with --ignored --nocapture --release"]
fn perf_large_polynomial() {
section("3. Large polynomial (20 terms)");
let ctx = Context::new();
let x = ctx.symbol("x");
let mut expr = ctx.int(21);
for i in 1..=20i64 {
let coeff = ctx.int(i);
expr += coeff * x.powi(21 - i);
}
println!(" Input: {expr}");
println!(
" (expression has {} characters in display form)",
format!("{expr}").len()
);
let simplified = expr.simplify();
println!(" simplify() → {simplified}");
let smart = expr.simplify();
println!(" smart_simplify() → {smart}");
println!();
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("smart_simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("full_simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("simplify_trace() [pattern-rules only]", avg);
}
#[test]
#[ignore = "benchmark: prints timings only (~10-30 s in debug); run with --ignored --nocapture --release"]
fn perf_already_simple() {
section("4. Already simple: x (no-op case for tight loops)");
let ctx = Context::new();
let x = ctx.symbol("x");
println!(" Input: {x}");
println!(" simplify() → {}", x.simplify());
println!(" smart_simplify() → {}", x.simplify());
println!(" full_simplify() → {}", x.simplify());
println!();
let (_, avg) = bench(|| {
let _ = x.simplify();
});
report("simplify()", avg);
let (_, avg) = bench(|| {
let _ = x.simplify();
});
report("smart_simplify()", avg);
let (_, avg) = bench(|| {
let _ = x.simplify();
});
report("full_simplify()", avg);
let (_, avg) = bench(|| {
let _ = x.simplify();
});
report("simplify_trace() [pattern-rules only]", avg);
println!();
let five = ctx.int(5);
println!(" Input: {five} (numeric literal)");
let (_, avg) = bench(|| {
let _ = five.simplify();
});
report("simplify() [numeric literal 5]", avg);
let (_, avg) = bench(|| {
let _ = five.simplify();
});
report("smart_simplify() [numeric literal 5]", avg);
let (_, avg) = bench(|| {
let _ = five.simplify();
});
report("full_simplify() [numeric literal 5]", avg);
}
#[test]
#[ignore = "benchmark: prints timings only (~10-30 s in debug); run with --ignored --nocapture --release"]
fn perf_deep_nesting() {
section("5. Deep nesting: sin(sin(sin(... x ...))) depth=10");
let ctx = Context::new();
let x = ctx.symbol("x");
let mut expr = x.clone();
for _ in 0..10 {
expr = expr.sin();
}
println!(" Input: {expr}");
println!(" (display length: {} chars)", format!("{expr}").len());
let simplified = expr.simplify();
println!(" simplify() → {simplified}");
let smart = expr.simplify();
println!(" smart_simplify() → {smart}");
println!();
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("smart_simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("full_simplify()", avg);
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report("simplify_trace() [pattern-rules only]", avg);
println!();
println!(" --- depth=20 ---");
let mut deep = x.clone();
for _ in 0..20 {
deep = deep.sin();
}
println!(" (display length: {} chars)", format!("{deep}").len());
let (_, avg) = bench(|| {
let _ = deep.simplify();
});
report("simplify() [depth=20]", avg);
let (_, avg) = bench(|| {
let _ = deep.simplify();
});
report("smart_simplify() [depth=20]", avg);
let (_, avg) = bench(|| {
let _ = deep.simplify();
});
report("full_simplify() [depth=20]", avg);
}
#[test]
#[ignore = "benchmark: prints timings only (~10-30 s in debug); run with --ignored --nocapture --release"]
fn perf_fu_bailout_on_non_trig() {
section("6. fu() bail-out cost on non-trig expressions");
println!(" Measuring: does fu()/has_trig check add measurable overhead");
println!(" to smart_simplify for purely algebraic expressions?");
println!();
let ctx = Context::new();
let x = ctx.symbol("x");
let poly = x.powi(3) + &x * 2 + 1;
println!(" Non-trig expr: {poly}");
let (_, avg_smart) = bench(|| {
let _ = poly.simplify();
});
report("smart_simplify() [non-trig poly]", avg_smart);
let (_, avg_trace) = bench(|| {
let _ = poly.simplify();
});
report("simplify_trace() [non-trig poly, no fu]", avg_trace);
let overhead_ns = if avg_smart > avg_trace {
(avg_smart - avg_trace).as_nanos()
} else {
0
};
println!();
println!(
" → Overhead of smart_simplify over pattern-rules-only: {:.2} µs",
overhead_ns as f64 / 1000.0
);
println!(" (includes flag computation, eval, expand, factor_terms, fu bail-out)");
println!();
let trig = x.sin().powi(2) + x.cos().powi(2);
println!(" Trig expr: {trig}");
let (_, avg_trig_smart) = bench(|| {
let _ = trig.simplify();
});
report("smart_simplify() [with trig]", avg_trig_smart);
let (_, avg_trig_trace) = bench(|| {
let _ = trig.simplify();
});
report("simplify_trace() [with trig]", avg_trig_trace);
let fu_cost_ns = if avg_trig_smart > avg_smart {
(avg_trig_smart - avg_smart).as_nanos()
} else {
0
};
println!();
println!(
" → Extra cost when trig IS present (fu + trig_expand + factor+fu): ~{:.2} µs",
fu_cost_ns as f64 / 1000.0
);
}
#[test]
#[ignore = "benchmark: prints timings only (~10-30 s in debug); run with --ignored --nocapture --release"]
fn perf_consolidated_approach() {
section("7. Consolidated approach: simplify() = smart_simplify always");
println!(" Simulating: .simplify() always runs smart_simplify (12+ strategies)");
println!(" .simplify() iterates smart_simplify up to 10×");
println!();
println!(" Current .simplify() = simplify_trace() + smart_simplify(), pick best");
println!(" Proposed .simplify() = smart_simplify() only");
println!();
let ctx = Context::new();
let x = ctx.symbol("x");
let cases: Vec<(&str, Ex)> = vec![
("x (atom)", x.clone()),
("x³ + 2x + 1", x.powi(3) + &x * 2 + 1),
("sin²(x) + cos²(x)", x.sin().powi(2) + x.cos().powi(2)),
("(x+1)² - x² - 2x", {
let xp1 = &x + 1;
&xp1.powi(2) - &x.powi(2) - &x * 2
}),
("sin(sin(sin(x)))", x.sin().sin().sin()),
];
println!(
" {:30} {:>14} {:>14} {:>14}",
"Expression", "current .s()", "proposed .s()", "ratio"
);
println!(" {:-<30} {:-<14} {:-<14} {:-<14}", "", "", "", "");
for (name, expr) in &cases {
let (_, avg_current) = bench(|| {
let _ = expr.simplify();
});
let (_, avg_proposed) = bench(|| {
let _ = expr.simplify();
});
let ratio = if avg_proposed.as_nanos() > 0 && avg_current.as_nanos() > 0 {
avg_proposed.as_nanos() as f64 / avg_current.as_nanos() as f64
} else {
f64::NAN
};
println!(
" {name:30} {:>11.2} µs {:>11.2} µs {:>11.2}×",
avg_current.as_nanos() as f64 / 1000.0,
avg_proposed.as_nanos() as f64 / 1000.0,
ratio,
);
}
println!();
println!(" full_simplify() timing (iterates up to 10× with cancel+expand+radical):");
for (name, expr) in &cases {
let (_, avg) = bench(|| {
let _ = expr.simplify();
});
report(&format!("full_simplify() [{name}]"), avg);
}
}
#[test]
#[ignore = "benchmark: prints timings only (~10-30 s in debug); run with --ignored --nocapture --release"]
fn perf_summary() {
section("8. Summary — absolute cost of smart_simplify on atoms");
println!(" The critical question: is smart_simplify too expensive for");
println!(" expressions that are already simple (the no-op hot path)?");
println!();
let ctx = Context::new();
let x = ctx.symbol("x");
let (_, avg_atom_smart) = bench(|| {
let _ = x.simplify();
});
report("smart_simplify(x) [atom]", avg_atom_smart);
let (_, avg_atom_simp) = bench(|| {
let _ = x.simplify();
});
report("simplify(x) [current]", avg_atom_simp);
let (_, avg_atom_full) = bench(|| {
let _ = x.simplify();
});
report("full_simplify(x)", avg_atom_full);
let small = &x + 1;
let (_, avg_small_smart) = bench(|| {
let _ = small.simplify();
});
report("smart_simplify(x + 1) [small]", avg_small_smart);
let (_, avg_small_full) = bench(|| {
let _ = small.simplify();
});
report("full_simplify(x + 1)", avg_small_full);
println!();
println!(" ┌─────────────────────────────────────────────────────────┐");
println!(" │ RECOMMENDATION │");
println!(" │ │");
println!(" │ If smart_simplify(atom) < 5 µs: │");
println!(" │ ✅ Consolidation is fine. The flag-based gating │");
println!(" │ makes the no-op path extremely cheap. │");
println!(" │ │");
println!(" │ If smart_simplify(atom) is 5–50 µs: │");
println!(" │ ⚠️ Acceptable for most uses, but add a fast path │");
println!(" │ that skips strategy evaluation for atoms. │");
println!(" │ │");
println!(" │ If smart_simplify(atom) > 50 µs: │");
println!(" │ ❌ Too slow for tight loops. Keep the split API. │");
println!(" │ │");
println!(" │ The actual numbers are printed above — check them! │");
println!(" └─────────────────────────────────────────────────────────┘");
let atom_us = avg_atom_smart.as_nanos() as f64 / 1000.0;
println!();
if atom_us < 5.0 {
println!(
" ✅ VERDICT: smart_simplify(atom) = {atom_us:.2} µs — consolidation is FAST ENOUGH."
);
println!(" The early-exit for atoms in smart_simplify makes it essentially free.");
} else if atom_us < 50.0 {
println!(
" ⚠️ VERDICT: smart_simplify(atom) = {atom_us:.2} µs — acceptable with caveats."
);
println!(" Consider keeping the atom early-exit and monitoring in benchmarks.");
} else {
println!(
" ❌ VERDICT: smart_simplify(atom) = {atom_us:.2} µs — too slow for consolidation."
);
println!(" Keep .simplify() as pattern-rules-only for hot paths.");
}
}
#[test]
fn perf_inputs_simplify_correctly() {
let ctx = Context::new();
let x = ctx.symbol("x");
assert_eq!((x.sin().powi(2) + x.cos().powi(2)).simplify(), ctx.int(1));
assert_eq!(x.simplify(), x);
assert_eq!(ctx.int(5).simplify(), ctx.int(5));
let mut nested = x.clone();
for _ in 0..10 {
nested = nested.sin();
}
let simplified = nested.simplify();
let at_half = |e: &Ex| e.subs(&x, &ctx.rational(1, 2)).eval_f64().unwrap();
assert!((at_half(&simplified) - at_half(&nested)).abs() < 1e-14);
let mut want = 0.5f64;
for _ in 0..10 {
want = want.sin();
}
assert!((at_half(&nested) - want).abs() < 1e-14);
let p = (&x + 1).powi(2) - (x.powi(2) + &x * 2 + 1);
assert!(p.simplify().is_zero_structural(), "{}", p.simplify());
}