297 lines
10 KiB
Rust
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);
|
|
}
|