Common Patterns
Topic:
getting-started.common-patterns
Common patterns and best practices when using MathHook, including macro usage guidelines, polynomial construction, substitution patterns, function composition, matrix operations, error handling, performance patterns, and educational features. Includes detailed pitfalls to avoid.
Common Patterns
This chapter covers common patterns and best practices when using MathHook.
Macro Usage Guidelines
ALWAYS use macros for:
- Symbol creation:
symbol!(x)notSymbol::new("x") - Simple expressions:
expr!(x + y) - Function calls:
expr!(sin(x))
Use explicit API for:
- Runtime/loop variables (macros see token 'i', not value)
- Programmatic construction with runtime data
- Dynamic polynomial building
Building Polynomials
Fixed Degree: Use macros with add: helper
Dynamic Degree: Use explicit API with loops
Substitution Patterns
Single or multiple variable substitution using HashMap.
Working with Functions
Create with expr! macro, function! macro, or Expression::function() for
runtime function names. Compose functions by nesting.
Matrix Patterns
Create from vectors, identity matrices, zero matrices. Perform symbolic matrix operations.
Error Handling
Handle parsing errors with match on Result. Handle solver errors by checking
result type.
Performance Patterns
- Bulk operations: Use iterators and collect
- Caching results: Store in HashMap keyed by string representation
- Reuse expressions (immutable, cheap to clone)
Educational Patterns
Use step-by-step explanations and derivative explanations for teaching.
Common Pitfalls
- Runtime variables in macros: Use explicit API for loop variables
- Nested macro calls: Use intermediate variables
- Float equality: Use epsilon comparison, not ==
Examples
Macro Usage - Correct Patterns
When to use macros vs explicit API
Rust
#![allow(unused)] fn main() { use mathhook::prelude::*; // ALWAYS use macros for symbols let x = symbol!(x); // NOT Symbol::new("x") // Simple expressions - use macros let expr = expr!(x + y); let expr = expr!(2 * x); let expr = expr!(x ^ 2); // Function calls - use macros let expr = expr!(sin(x)); }
Python
from mathhook import Expression
# Python uses method chaining
x = Expression.symbol('x')
y = Expression.symbol('y')
expr = x.add(y)
expr = Expression.integer(2).mul(x)
expr = x.pow(2)
JavaScript
import { Expression } from 'mathhook-node';
// Node.js uses method chaining
const x = Expression.symbol('x');
const y = Expression.symbol('y');
const expr = x.add(y);
const expr2 = Expression.integer(2).mul(x);
const expr3 = x.pow(2);
Runtime Variables - Explicit API Required
Why macros don't work with loop variables
Rust
#![allow(unused)] fn main() { use mathhook::prelude::*; // WRONG - creates Symbol("i"), not integer value for i in 0..10 { let expr = expr!(i); // BAD! } // CORRECT - use explicit API for runtime variables for i in 0..10 { let term = Expression::integer(i); // GOOD! } // CORRECT - programmatic construction let x = symbol!(x); let coefficients = vec![1, 2, 3]; let mut terms = Vec::new(); for i in 0..coefficients.len() { let coeff = Expression::integer(coefficients[i]); let x_expr = Expression::from(x.clone()); let power = Expression::integer(i as i64); terms.push(Expression::mul(vec![coeff, Expression::pow(x_expr, power)])); } let polynomial = Expression::add(terms); }
Python
from mathhook import Expression
# Python doesn't have compile-time macros
# Always use explicit API (which is fine)
x = Expression.symbol('x')
coefficients = [1, 2, 3]
terms = []
for i, coeff in enumerate(coefficients):
coeff_expr = Expression.integer(coeff)
power_expr = Expression.integer(i)
term = coeff_expr.mul(x.pow(power_expr))
terms.append(term)
polynomial = Expression.add(terms)
JavaScript
import { Expression } from 'mathhook-node';
// Node.js doesn't have compile-time macros
// Always use explicit API
const x = Expression.symbol('x');
const coefficients = [1, 2, 3];
const terms = [];
for (let i = 0; i < coefficients.length; i++) {
const coeffExpr = Expression.integer(coefficients[i]);
const powerExpr = Expression.integer(i);
const term = coeffExpr.mul(x.pow(powerExpr));
terms.push(term);
}
const polynomial = Expression.add(terms);
Building Polynomials - Dynamic Degree
Construct polynomials with runtime coefficients
Rust
#![allow(unused)] fn main() { use mathhook::prelude::*; fn build_polynomial(coefficients: &[i64], x: &Symbol) -> Expression { let mut terms = Vec::new(); for (i, &coeff) in coefficients.iter().enumerate() { let coeff_expr = Expression::integer(coeff); let x_expr = Expression::from(x.clone()); let power = Expression::integer(i as i64); let term = Expression::mul(vec![coeff_expr, Expression::pow(x_expr, power)]); terms.push(term); } Expression::add(terms) } let x = symbol!(x); let poly = build_polynomial(&[1, -5, 6], &x); // x^2 - 5x + 6 }
Python
from mathhook import Expression
def build_polynomial(coefficients, x):
terms = []
for i, coeff in enumerate(coefficients):
coeff_expr = Expression.integer(coeff)
power = Expression.integer(i)
term = coeff_expr.mul(x.pow(power))
terms.append(term)
return Expression.add(terms)
x = Expression.symbol('x')
poly = build_polynomial([1, -5, 6], x) # x^2 - 5x + 6
JavaScript
import { Expression } from 'mathhook-node';
function buildPolynomial(coefficients: number[], x: Expression): Expression {
const terms = [];
for (let i = 0; i < coefficients.length; i++) {
const coeffExpr = Expression.integer(coefficients[i]);
const power = Expression.integer(i);
const term = coeffExpr.mul(x.pow(power));
terms.push(term);
}
return Expression.add(terms);
}
const x = Expression.symbol('x');
const poly = buildPolynomial([1, -5, 6], x); // x^2 - 5x + 6
Substitution - Single and Multiple
Replace symbols with values
Rust
#![allow(unused)] fn main() { use mathhook::prelude::*; use std::collections::HashMap; let x = symbol!(x); let y = symbol!(y); let expr = expr!(add: (x * y), x, y); // Single substitution let mut vars = HashMap::new(); vars.insert("x".to_string(), Expression::integer(3)); let result = expr.substitute(&vars); // Multiple substitutions let mut vars = HashMap::new(); vars.insert("x".to_string(), Expression::integer(2)); vars.insert("y".to_string(), Expression::integer(3)); let result = expr.substitute(&vars); }
Python
from mathhook import Expression
x = Expression.symbol('x')
y = Expression.symbol('y')
expr = x.mul(y).add(x).add(y)
# Single substitution
vars = {'x': Expression.integer(3)}
result = expr.substitute(vars)
# Multiple substitutions
vars = {'x': Expression.integer(2), 'y': Expression.integer(3)}
result = expr.substitute(vars)
JavaScript
import { Expression } from 'mathhook-node';
const x = Expression.symbol('x');
const y = Expression.symbol('y');
const expr = x.mul(y).add(x).add(y);
// Single substitution
const vars1 = new Map([['x', Expression.integer(3)]]);
const result1 = expr.substitute(vars1);
// Multiple substitutions
const vars2 = new Map([
['x', Expression.integer(2)],
['y', Expression.integer(3)]
]);
const result2 = expr.substitute(vars2);
Function Composition
Compose functions by nesting
Rust
#![allow(unused)] fn main() { use mathhook::prelude::*; let x = symbol!(x); // sin(cos(x)) - direct nesting let composed = expr!(sin(cos(x))); // Or build step by step let inner = expr!(cos(x)); let composed_alt = function!(sin, inner); println!("Composed function: {}", composed); }
Python
from mathhook import Expression
x = Expression.symbol('x')
# Build step by step
inner = Expression.function('cos', [x])
composed = Expression.function('sin', [inner])
print(f"Composed function: {composed}")
JavaScript
import { Expression } from 'mathhook-node';
const x = Expression.symbol('x');
// Build step by step
const inner = Expression.function('cos', [x]);
const composed = Expression.function('sin', [inner]);
console.log(`Composed function: ${composed.toString()}`);
Performance - Bulk Operations
Efficient batch processing
Rust
#![allow(unused)] fn main() { use mathhook::prelude::*; let x = symbol!(x); // Simplify many expressions efficiently let expressions = vec![ expr!(x + x), expr!(x * 1), expr!(add: (x ^ 2), (-(x ^ 2))), ]; let simplified: Vec<_> = expressions .iter() .map(|e| e.simplify()) .collect(); }
Python
from mathhook import Expression
x = Expression.symbol('x')
# Simplify many expressions
expressions = [
x.add(x),
x.mul(Expression.integer(1)),
x.pow(2).add(x.pow(2).neg())
]
simplified = [e.simplify() for e in expressions]
JavaScript
import { Expression } from 'mathhook-node';
const x = Expression.symbol('x');
// Simplify many expressions
const expressions = [
x.add(x),
x.mul(Expression.integer(1)),
x.pow(2).add(x.pow(2).neg())
];
const simplified = expressions.map(e => e.simplify());
Performance - Caching Results
Cache frequently computed expressions
Rust
#![allow(unused)] fn main() { use mathhook::prelude::*; use std::collections::HashMap; let x = symbol!(x); let mut cache: HashMap<String, Expression> = HashMap::new(); let expr = expr!(x ^ 2); let key = format!("{}", expr); if let Some(cached) = cache.get(&key) { println!("Using cached result"); } else { let result = expr.simplify(); cache.insert(key, result.clone()); } }
Python
from mathhook import Expression
x = Expression.symbol('x')
cache = {}
expr = x.pow(2)
key = str(expr)
if key in cache:
print("Using cached result")
else:
result = expr.simplify()
cache[key] = result
JavaScript
import { Expression } from 'mathhook-node';
const x = Expression.symbol('x');
const cache = new Map<string, Expression>();
const expr = x.pow(2);
const key = expr.toString();
if (cache.has(key)) {
console.log("Using cached result");
} else {
const result = expr.simplify();
cache.set(key, result);
}
Common Pitfall - Float Equality
Never use == for approximate values
Rust
#![allow(unused)] fn main() { use mathhook::prelude::*; // WRONG - comparing floats directly let val1: f64 = 3.14; let val2: f64 = 3.14000000001; // if val1 == val2 { } // BAD! // CORRECT - use epsilon comparison let tolerance: f64 = 1e-10; if (val1 - val2).abs() < tolerance { println!("Values are approximately equal"); } // OR use exact rationals for symbolic computation let exact = Expression::rational(314, 100); // Exact 3.14 }
Python
from mathhook import Expression
# WRONG - comparing floats directly
val1 = 3.14
val2 = 3.14000000001
# if val1 == val2: # BAD!
# CORRECT - use epsilon comparison
tolerance = 1e-10
if abs(val1 - val2) < tolerance:
print("Values are approximately equal")
# OR use exact rationals
exact = Expression.rational(314, 100) # Exact 3.14
JavaScript
import { Expression } from 'mathhook-node';
// WRONG - comparing floats directly
const val1 = 3.14;
const val2 = 3.14000000001;
// if (val1 === val2) { } // BAD!
// CORRECT - use epsilon comparison
const tolerance = 1e-10;
if (Math.abs(val1 - val2) < tolerance) {
console.log("Values are approximately equal");
}
// OR use exact rationals
const exact = Expression.rational(314, 100); // Exact 3.14
API Reference
- Rust:
mathhook::prelude - Python:
mathhook.Expression - JavaScript:
mathhook-node.Expression