Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
645
vendor/ruvector/examples/scipix/tests/math_tests.rs
vendored
Normal file
645
vendor/ruvector/examples/scipix/tests/math_tests.rs
vendored
Normal file
@@ -0,0 +1,645 @@
|
||||
//! Comprehensive integration tests for mathematical expression parsing and conversion
|
||||
//!
|
||||
//! These tests cover complex mathematical expressions including:
|
||||
//! - Fractions and nested fractions
|
||||
//! - Radicals (square roots, nth roots)
|
||||
//! - Powers and exponents
|
||||
//! - Matrices and vectors
|
||||
//! - Integrals and summations
|
||||
//! - Trigonometric functions
|
||||
//! - Greek letters and special symbols
|
||||
//! - Complex nested expressions
|
||||
//!
|
||||
//! NOTE: These tests require the `math` feature to be enabled.
|
||||
//! Run with: cargo test --features math
|
||||
|
||||
#![cfg(feature = "math")]
|
||||
|
||||
use ruvector_scipix::math::{
|
||||
parse_expression, to_asciimath, to_latex, to_mathml, AsciiMathGenerator, BinaryOp, BracketType,
|
||||
LaTeXConfig, LaTeXGenerator, LargeOpType, MathExpr, MathNode,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_quadratic_formula() {
|
||||
// The famous quadratic formula: x = (-b ± √(b² - 4ac)) / 2a
|
||||
let latex_input = r"\frac{-b + \sqrt{b^2 - 4*a*c}}{2*a}";
|
||||
let expr = parse_expression(latex_input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\frac"));
|
||||
assert!(latex.contains(r"\sqrt"));
|
||||
assert!(latex.contains("b"));
|
||||
assert!(latex.contains("a"));
|
||||
assert!(latex.contains("c"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<mfrac>"));
|
||||
assert!(mathml.contains("<msqrt>"));
|
||||
|
||||
let asciimath = to_asciimath(&expr);
|
||||
assert!(asciimath.contains("sqrt"));
|
||||
assert!(asciimath.contains("/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pythagorean_theorem() {
|
||||
// a² + b² = c²
|
||||
let input = "a^2 + b^2 = c^2";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains("a^{2}"));
|
||||
assert!(latex.contains("b^{2}"));
|
||||
assert!(latex.contains("c^{2}"));
|
||||
assert!(latex.contains("="));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<msup>"));
|
||||
assert!(mathml.contains("<mo>=</mo>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_euler_identity() {
|
||||
// e^(iπ) + 1 = 0
|
||||
let input = "e^{i*\\pi} + 1 = 0";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains("e^"));
|
||||
assert!(latex.contains("\\pi"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_fractions() {
|
||||
// Complex continued fraction
|
||||
let input = r"\frac{1}{\frac{2}{\frac{3}{4}}}";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
// Should contain multiple \frac commands
|
||||
assert!(latex.matches(r"\frac").count() >= 3);
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.matches("<mfrac>").count() >= 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matrix_2x2() {
|
||||
// 2x2 matrix
|
||||
let expr = MathExpr::new(
|
||||
MathNode::Matrix {
|
||||
rows: vec![
|
||||
vec![
|
||||
MathNode::Number {
|
||||
value: "1".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
MathNode::Number {
|
||||
value: "2".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
],
|
||||
vec![
|
||||
MathNode::Number {
|
||||
value: "3".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
MathNode::Number {
|
||||
value: "4".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
bracket_type: BracketType::Brackets,
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\begin{bmatrix}"));
|
||||
assert!(latex.contains(r"\end{bmatrix}"));
|
||||
assert!(latex.contains("&"));
|
||||
assert!(latex.contains(r"\\"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<mtable>"));
|
||||
assert!(mathml.contains("<mtr>"));
|
||||
assert!(mathml.contains("<mtd>"));
|
||||
|
||||
let asciimath = to_asciimath(&expr);
|
||||
assert!(asciimath.contains("["));
|
||||
assert!(asciimath.contains(";"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matrix_3x3() {
|
||||
// 3x3 identity matrix
|
||||
let expr = MathExpr::new(
|
||||
MathNode::Matrix {
|
||||
rows: vec![
|
||||
vec![
|
||||
MathNode::Number {
|
||||
value: "1".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
MathNode::Number {
|
||||
value: "0".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
MathNode::Number {
|
||||
value: "0".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
],
|
||||
vec![
|
||||
MathNode::Number {
|
||||
value: "0".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
MathNode::Number {
|
||||
value: "1".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
MathNode::Number {
|
||||
value: "0".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
],
|
||||
vec![
|
||||
MathNode::Number {
|
||||
value: "0".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
MathNode::Number {
|
||||
value: "0".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
MathNode::Number {
|
||||
value: "1".to_string(),
|
||||
is_decimal: false,
|
||||
},
|
||||
],
|
||||
],
|
||||
bracket_type: BracketType::Parentheses,
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\begin{pmatrix}"));
|
||||
assert!(latex.matches(r"\\").count() >= 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_definite_integral() {
|
||||
// ∫₀¹ x² dx
|
||||
let expr = MathExpr::new(
|
||||
MathNode::LargeOp {
|
||||
op_type: LargeOpType::Integral,
|
||||
lower: Some(Box::new(MathNode::Number {
|
||||
value: "0".to_string(),
|
||||
is_decimal: false,
|
||||
})),
|
||||
upper: Some(Box::new(MathNode::Number {
|
||||
value: "1".to_string(),
|
||||
is_decimal: false,
|
||||
})),
|
||||
content: Box::new(MathNode::Binary {
|
||||
op: BinaryOp::Power,
|
||||
left: Box::new(MathNode::Symbol {
|
||||
value: "x".to_string(),
|
||||
unicode: Some('x'),
|
||||
}),
|
||||
right: Box::new(MathNode::Number {
|
||||
value: "2".to_string(),
|
||||
is_decimal: false,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\int"));
|
||||
assert!(latex.contains("_{0}"));
|
||||
assert!(latex.contains("^{1}"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<munderover>"));
|
||||
assert!(mathml.contains("∫"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_summation() {
|
||||
// ∑_{i=1}^{n} i²
|
||||
let expr = MathExpr::new(
|
||||
MathNode::LargeOp {
|
||||
op_type: LargeOpType::Sum,
|
||||
lower: Some(Box::new(MathNode::Binary {
|
||||
op: BinaryOp::Equal,
|
||||
left: Box::new(MathNode::Symbol {
|
||||
value: "i".to_string(),
|
||||
unicode: Some('i'),
|
||||
}),
|
||||
right: Box::new(MathNode::Number {
|
||||
value: "1".to_string(),
|
||||
is_decimal: false,
|
||||
}),
|
||||
})),
|
||||
upper: Some(Box::new(MathNode::Symbol {
|
||||
value: "n".to_string(),
|
||||
unicode: Some('n'),
|
||||
})),
|
||||
content: Box::new(MathNode::Binary {
|
||||
op: BinaryOp::Power,
|
||||
left: Box::new(MathNode::Symbol {
|
||||
value: "i".to_string(),
|
||||
unicode: Some('i'),
|
||||
}),
|
||||
right: Box::new(MathNode::Number {
|
||||
value: "2".to_string(),
|
||||
is_decimal: false,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\sum"));
|
||||
assert!(latex.contains("_{i"));
|
||||
assert!(latex.contains("^{n}"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("∑"));
|
||||
assert!(mathml.contains("<munderover>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_product_notation() {
|
||||
// ∏_{k=1}^{n} k
|
||||
let expr = MathExpr::new(
|
||||
MathNode::LargeOp {
|
||||
op_type: LargeOpType::Product,
|
||||
lower: Some(Box::new(MathNode::Binary {
|
||||
op: BinaryOp::Equal,
|
||||
left: Box::new(MathNode::Symbol {
|
||||
value: "k".to_string(),
|
||||
unicode: Some('k'),
|
||||
}),
|
||||
right: Box::new(MathNode::Number {
|
||||
value: "1".to_string(),
|
||||
is_decimal: false,
|
||||
}),
|
||||
})),
|
||||
upper: Some(Box::new(MathNode::Symbol {
|
||||
value: "n".to_string(),
|
||||
unicode: Some('n'),
|
||||
})),
|
||||
content: Box::new(MathNode::Symbol {
|
||||
value: "k".to_string(),
|
||||
unicode: Some('k'),
|
||||
}),
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\prod"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("∏"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nth_root() {
|
||||
// ∛8 (cube root)
|
||||
let input = r"\sqrt[3]{8}";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\sqrt[3]"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<mroot>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_radical() {
|
||||
// √(a² + b²)
|
||||
let input = r"\sqrt{a^2 + b^2}";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\sqrt"));
|
||||
assert!(latex.contains("a^{2}"));
|
||||
assert!(latex.contains("b^{2}"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binomial_coefficient() {
|
||||
// (n choose k) represented as fraction
|
||||
let input = r"\frac{n}{k}";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\frac{n}{k}"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<mfrac>"));
|
||||
assert!(mathml.contains("<mi>n</mi>"));
|
||||
assert!(mathml.contains("<mi>k</mi>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trigonometric_functions() {
|
||||
// sin²(x) + cos²(x) = 1
|
||||
let input = r"\sin{x}^2 + \cos{x}^2 = 1";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\sin"));
|
||||
assert!(latex.contains(r"\cos"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_limits() {
|
||||
// lim_{x→∞} (1 + 1/x)^x = e
|
||||
// Simplified: testing basic limit structure
|
||||
let input = r"\sum_{x=1}^{10} x";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\sum"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_greek_letters() {
|
||||
// α + β + γ = δ
|
||||
let input = r"\alpha + \beta + \gamma = \delta";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\alpha"));
|
||||
assert!(latex.contains(r"\beta"));
|
||||
assert!(latex.contains(r"\gamma"));
|
||||
assert!(latex.contains(r"\delta"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subscript_and_superscript() {
|
||||
// a₁² + a₂² = a₃²
|
||||
let input = "a_1^2 + a_2^2 = a_3^2";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains("a_{1}^{2}"));
|
||||
assert!(latex.contains("a_{2}^{2}"));
|
||||
assert!(latex.contains("a_{3}^{2}"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<msubsup>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_operator_precedence() {
|
||||
// 1 + 2 * 3 should parse as 1 + (2 * 3)
|
||||
let input = "1 + 2 * 3";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
match expr.root {
|
||||
MathNode::Binary {
|
||||
op: BinaryOp::Add,
|
||||
right,
|
||||
..
|
||||
} => {
|
||||
assert!(matches!(
|
||||
*right,
|
||||
MathNode::Binary {
|
||||
op: BinaryOp::Multiply,
|
||||
..
|
||||
}
|
||||
));
|
||||
}
|
||||
_ => panic!("Expected addition with multiplication on right"),
|
||||
}
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
// Should not have unnecessary parentheses around 2 * 3
|
||||
assert!(!latex.contains("(2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parentheses_grouping() {
|
||||
// (1 + 2) * 3 should parse as (1 + 2) * 3
|
||||
let input = "(1 + 2) * 3";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
// The addition should be grouped
|
||||
assert!(latex.contains("1 + 2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_nested_expression() {
|
||||
// Complex expression with multiple levels of nesting
|
||||
let input = r"\frac{\sqrt{a + b}}{c^2 - d^2}";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\frac"));
|
||||
assert!(latex.contains(r"\sqrt"));
|
||||
assert!(latex.contains("c^{2}"));
|
||||
assert!(latex.contains("d^{2}"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<mfrac>"));
|
||||
assert!(mathml.contains("<msqrt>"));
|
||||
assert!(mathml.contains("<msup>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_latex_config_display_style() {
|
||||
let expr = parse_expression(r"\frac{1}{2}").unwrap();
|
||||
|
||||
let config_inline = LaTeXConfig {
|
||||
display_style: false,
|
||||
auto_size_delimiters: true,
|
||||
spacing: true,
|
||||
};
|
||||
|
||||
let config_display = LaTeXConfig {
|
||||
display_style: true,
|
||||
auto_size_delimiters: true,
|
||||
spacing: true,
|
||||
};
|
||||
|
||||
let gen_inline = LaTeXGenerator::with_config(config_inline);
|
||||
let gen_display = LaTeXGenerator::with_config(config_display);
|
||||
|
||||
let latex_inline = gen_inline.generate(&expr);
|
||||
let latex_display = gen_display.generate(&expr);
|
||||
|
||||
// Both should contain \frac
|
||||
assert!(latex_inline.contains(r"\frac"));
|
||||
assert!(latex_display.contains(r"\frac"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_latex_config_no_auto_size() {
|
||||
let expr = MathExpr::new(
|
||||
MathNode::Group {
|
||||
content: Box::new(MathNode::Number {
|
||||
value: "42".to_string(),
|
||||
is_decimal: false,
|
||||
}),
|
||||
bracket_type: BracketType::Parentheses,
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let config_auto = LaTeXConfig {
|
||||
display_style: false,
|
||||
auto_size_delimiters: true,
|
||||
spacing: true,
|
||||
};
|
||||
|
||||
let config_no_auto = LaTeXConfig {
|
||||
display_style: false,
|
||||
auto_size_delimiters: false,
|
||||
spacing: true,
|
||||
};
|
||||
|
||||
let gen_auto = LaTeXGenerator::with_config(config_auto);
|
||||
let gen_no_auto = LaTeXGenerator::with_config(config_no_auto);
|
||||
|
||||
let latex_auto = gen_auto.generate(&expr);
|
||||
let latex_no_auto = gen_no_auto.generate(&expr);
|
||||
|
||||
assert!(latex_auto.contains(r"\left("));
|
||||
assert!(!latex_no_auto.contains(r"\left("));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_asciimath_unicode_vs_ascii() {
|
||||
let expr = parse_expression("2 * 3").unwrap();
|
||||
|
||||
let gen_unicode = AsciiMathGenerator::new();
|
||||
let gen_ascii = AsciiMathGenerator::ascii_only();
|
||||
|
||||
let unicode_output = gen_unicode.generate(&expr);
|
||||
let ascii_output = gen_ascii.generate(&expr);
|
||||
|
||||
assert!(unicode_output.contains("×") || unicode_output.contains("*"));
|
||||
assert!(ascii_output.contains("*"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_double_integral() {
|
||||
let expr = MathExpr::new(
|
||||
MathNode::LargeOp {
|
||||
op_type: LargeOpType::DoubleIntegral,
|
||||
lower: None,
|
||||
upper: None,
|
||||
content: Box::new(MathNode::Symbol {
|
||||
value: "f".to_string(),
|
||||
unicode: Some('f'),
|
||||
}),
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\iint"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("∬"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_triple_integral() {
|
||||
let expr = MathExpr::new(
|
||||
MathNode::LargeOp {
|
||||
op_type: LargeOpType::TripleIntegral,
|
||||
lower: None,
|
||||
upper: None,
|
||||
content: Box::new(MathNode::Symbol {
|
||||
value: "f".to_string(),
|
||||
unicode: Some('f'),
|
||||
}),
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\iiint"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("∭"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decimal_numbers() {
|
||||
let input = "3.14159";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
match expr.root {
|
||||
MathNode::Number { value, is_decimal } => {
|
||||
assert_eq!(value, "3.14159");
|
||||
assert!(is_decimal);
|
||||
}
|
||||
_ => panic!("Expected decimal number"),
|
||||
}
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains("3.14159"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_expression() {
|
||||
// Test a realistically complex expression
|
||||
let input = r"\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}";
|
||||
let expr = parse_expression(input).unwrap();
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
assert!(latex.contains(r"\frac"));
|
||||
assert!(latex.contains(r"\sqrt"));
|
||||
assert!(latex.contains("b^{2}"));
|
||||
|
||||
let mathml = to_mathml(&expr);
|
||||
assert!(mathml.contains("<mfrac>"));
|
||||
assert!(mathml.contains("<msqrt>"));
|
||||
|
||||
let asciimath = to_asciimath(&expr);
|
||||
assert!(asciimath.contains("sqrt"));
|
||||
assert!(asciimath.contains("/"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_bracket_types() {
|
||||
for bracket_type in &[
|
||||
BracketType::Parentheses,
|
||||
BracketType::Brackets,
|
||||
BracketType::Braces,
|
||||
BracketType::Vertical,
|
||||
] {
|
||||
let expr = MathExpr::new(
|
||||
MathNode::Group {
|
||||
content: Box::new(MathNode::Symbol {
|
||||
value: "x".to_string(),
|
||||
unicode: Some('x'),
|
||||
}),
|
||||
bracket_type: *bracket_type,
|
||||
},
|
||||
1.0,
|
||||
);
|
||||
|
||||
let latex = to_latex(&expr);
|
||||
let mathml = to_mathml(&expr);
|
||||
|
||||
// All should produce valid output
|
||||
assert!(!latex.is_empty());
|
||||
assert!(!mathml.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user