Files
symclaw/crates/symclaw-core/tests/integration.rs
T

297 lines
10 KiB
Rust

//! Cross-module integration tests for SymClaw.
use std::collections::HashMap;
use std::sync::Arc;
use symclaw_core::ast::Expr;
use symclaw_core::interner::Symbol;
use symclaw_core::{
codegen, differentiate, eval, integrate, limits, linalg, number_theory, ode, parser, pattern,
poly, precision, proof, series, simplify, streaming, tensor, transforms, units,
};
fn parse(s: &str) -> Arc<Expr> {
parser::parse(s).unwrap()
}
// ── 1. Parse → Simplify → Differentiate → Integrate roundtrip ──
#[test]
fn parse_simplify_differentiate_integrate_roundtrip() {
let expr = parse("x^3 + 2*x");
let simplified = simplify::simplify(&expr);
let deriv = differentiate::differentiate(&simplified, Symbol::new("x"));
// d/dx(x^3 + 2x) = 3x^2 + 2
let integral = integrate::integrate(&deriv, Symbol::new("x"));
assert!(integral.is_some());
}
// ── 2. Parse → to_polynomial → back to expr ────────────────────
#[test]
fn parse_to_polynomial_roundtrip() {
let expr = parse("x^2 + 2*x + 1");
let p = poly::Polynomial::from_expr(&expr).expect("should convert to polynomial");
let back = p.to_expr();
// Simplify both and compare
let s1 = simplify::simplify(&expr);
let s2 = simplify::simplify(&Arc::new(back));
assert_eq!(format!("{s1}"), format!("{s2}"));
}
// ── 3. Parse → limit → verify ──────────────────────────────────
#[test]
fn limit_sinx_over_x() {
let expr = parse("sin(x)/x");
let result = limits::limit_at(&expr, "x", 0.0);
match result {
limits::LimitResult::Finite(e) => {
let s = format!("{e}");
assert!(s == "1" || s.contains("1"), "expected 1, got {s}");
}
other => panic!("expected finite limit, got {other:?}"),
}
}
// ── 4. Laplace transform ────────────────────────────────────────
#[test]
fn laplace_of_exp() {
// L{e^(a*t)} = 1/(s-a)
let expr = parse("exp(a*t)");
let result = transforms::laplace(&expr, "t", "s");
assert!(result.is_some(), "Laplace of exp(a*t) should exist");
}
// ── 5. Codegen to all 7 languages ──────────────────────────────
#[test]
fn codegen_all_languages() {
let expr = parse("x^2 + sin(x)");
let langs = [
codegen::Language::Python,
codegen::Language::C,
codegen::Language::Rust,
codegen::Language::Julia,
codegen::Language::JavaScript,
codegen::Language::GLSL,
codegen::Language::WGSL,
];
for lang in &langs {
let opts = codegen::CodegenOptions::new(*lang);
let code = codegen::generate(&expr, &opts);
assert!(
!code.is_empty(),
"codegen for {lang:?} produced empty output"
);
}
}
// ── 6. Proof trace ──────────────────────────────────────────────
#[test]
fn proof_trace_simplification() {
let expr = parse("x + 0");
let trace = proof::trace_simplify(&expr);
assert!(!trace.original.is_empty());
assert!(!trace.result.is_empty());
}
// ── 7. Units check correct ─────────────────────────────────────
#[test]
fn units_check_correct() {
let expr = parse("F - m*a");
let ctx = units::parse_dim_context("F:force,m:mass,a:acceleration").unwrap();
let result = units::infer_dimension(&expr, &ctx);
assert!(result.is_ok(), "F - m*a should be dimensionally consistent");
}
// ── 8. Units check incorrect ────────────────────────────────────
#[test]
fn units_check_incorrect() {
let expr = parse("F + m");
let ctx = units::parse_dim_context("F:force,m:mass").unwrap();
let result = units::infer_dimension(&expr, &ctx);
assert!(
result.is_err(),
"F + m should be dimensionally inconsistent"
);
}
// ── 9. Pattern match → verify ───────────────────────────────────
#[test]
fn pattern_match_trig_identity() {
let expr = parse("sin(x)^2 + cos(x)^2");
let pat = pattern::parse_pattern("sin(?x)^2 + cos(?x)^2").unwrap();
let bindings = pattern::match_pattern(&pat, &expr);
assert!(bindings.is_some(), "should match trig identity pattern");
let b = bindings.unwrap();
assert!(b.contains_key("x"), "should capture x");
}
// ── 10. Save → load roundtrip ───────────────────────────────────
#[test]
fn save_load_roundtrip() {
let expr = parse("x^2 + sin(x)");
let path = "/tmp/symclaw_test_integration.bin";
let config = streaming::StreamConfig::default();
streaming::save_to_file(&expr, path, &config).unwrap();
let loaded = streaming::load_from_file(path).unwrap();
assert_eq!(format!("{expr}"), format!("{}", Arc::new(loaded)));
let _ = std::fs::remove_file(path);
}
// ── 11. Matrix det ──────────────────────────────────────────────
#[test]
fn matrix_determinant() {
let mat = linalg::from_rows(vec![
vec![Expr::int(1), Expr::int(2)],
vec![Expr::int(3), Expr::int(4)],
]);
let det = linalg::determinant(&mat).unwrap();
let simplified = simplify::simplify(&det);
// det = 1*4 - 2*3 = -2
let s = format!("{simplified}");
assert!(s == "-2" || s == "(-2)", "expected -2, got {s}");
}
// ── 12. Number theory: factorize → verify product ───────────────
#[test]
fn factorize_verify_product() {
let n = 360u64;
let factors = number_theory::factorize(n);
let product: u64 = factors.iter().map(|&(p, e)| p.pow(e)).product();
assert_eq!(product, n);
}
// ── 13. Number theory: is_prime ─────────────────────────────────
#[test]
fn is_prime_basic() {
assert!(number_theory::is_prime(97));
assert!(!number_theory::is_prime(100));
assert!(number_theory::is_prime(2));
assert!(!number_theory::is_prime(1));
}
// ── 14. Tensor parse → contracted pairs ─────────────────────────
#[test]
fn tensor_contract() {
let te = tensor::parse_tensor_expr("T^mu_nu * S^nu").unwrap();
let contracted = tensor::contracted_pairs(&te);
assert!(contracted.contains(&"nu".to_owned()));
}
// ── 15. BigFloat pi computation ─────────────────────────────────
#[test]
fn bigfloat_pi() {
let pi = precision::BigFloat::pi(50);
let val = pi.to_f64();
assert!((val - std::f64::consts::PI).abs() < 1e-10);
}
// ── 16. ErrorFloat propagation ──────────────────────────────────
#[test]
fn errorfloat_propagation() {
let a = precision::ErrorFloat::new(10.0, 0.1);
let b = precision::ErrorFloat::new(5.0, 0.2);
let c = a.add(&b);
assert!((c.value - 15.0).abs() < 1e-10);
assert!(c.uncertainty > 0.0);
}
// ── 17. Taylor → eval at point ──────────────────────────────────
#[test]
fn taylor_exp_at_zero() {
let expr = parse("exp(x)");
let point = parse("0");
let expansion = series::taylor(&expr, Symbol::new("x"), &point, 5);
// Evaluate at x=0.1, compare with e^0.1
let mut vars = HashMap::new();
vars.insert(Symbol::new("x"), 0.1);
let val = eval::eval(&expansion, &vars).unwrap();
assert!((val - 0.1_f64.exp()).abs() < 1e-4);
}
// ── 18. ODE solve ───────────────────────────────────────────────
#[test]
fn ode_first_order_linear() {
// y' - y = 0 → y = C*exp(x)
// Construct Derivative node manually since parser doesn't handle y'
let y_prime = Arc::new(Expr::Derivative {
expr: Arc::new(Expr::Sym(Symbol::new("y"))),
var: Symbol::new("x"),
order: 1,
});
let y = Arc::new(Expr::Sym(Symbol::new("y")));
let expr = Arc::new(Expr::Add(vec![y_prime, Arc::new(Expr::Neg(y))]));
let sol = ode::solve_ode1(&expr, Symbol::new("y"), Symbol::new("x"));
assert!(sol.is_some(), "should solve y' - y = 0");
}
// ── 19. GCD number theory ───────────────────────────────────────
#[test]
fn gcd_basic() {
assert_eq!(number_theory::gcd(12, 18), 6);
assert_eq!(number_theory::gcd(100, 75), 25);
}
// ── 20. Limit at infinity ───────────────────────────────────────
#[test]
fn limit_at_infinity() {
let expr = parse("1/x");
let result = limits::limit_inf(&expr, "x");
match result {
limits::LimitResult::Finite(e) => {
let s = format!("{e}");
assert!(s == "0" || s.contains("0"), "expected 0, got {s}");
}
other => panic!("expected finite limit 0, got {other:?}"),
}
}
// ── 21. Differentiate chain rule ────────────────────────────────
#[test]
fn differentiate_chain_rule() {
let expr = parse("sin(x^2)");
let deriv = differentiate::differentiate(&expr, Symbol::new("x"));
let simplified = simplify::simplify(&deriv);
// Should contain cos(x^2) and 2*x
let s = format!("{simplified}");
assert!(
s.contains("cos") || s.contains("2"),
"chain rule result: {s}"
);
}
// ── 22. Polynomial GCD ─────────────────────────────────────────
#[test]
fn polynomial_from_expr_and_back() {
let expr = parse("x^3 + 3*x^2 + 3*x + 1");
let p = poly::Polynomial::from_expr(&expr).unwrap();
assert!(p.degree() >= 3);
}
// ── 23. Euler totient ───────────────────────────────────────────
#[test]
fn euler_totient() {
assert_eq!(number_theory::euler_totient(12), 4);
assert_eq!(number_theory::euler_totient(1), 1);
}