Skip to content

Repository files navigation

Math Engine

A comprehensive Java mathematical library featuring an advanced expression parser with support for custom functions, vectors, matrices, symbolic differentiation, numerical integration, equation solving, unit conversions, probability distributions, and more.

Features

  • Advanced Expression Parser - Natural mathematical syntax with exact rational arithmetic, lambdas, and comprehensions
  • Rich Data Types - Vectors, matrices, ranges, strings, functions
  • Functional Programming - Lambda functions, list comprehensions, higher-order functions (map, filter, reduce)
  • Unit Conversions - Physical units with automatic conversion integrated into parser
  • Mathematical Analysis - Symbolic differentiation, numerical integration, root finding
  • Probability Distributions - Normal, Beta, Binomial, Exponential, F, Logistic, Student T
  • Linear Algebra - Vector and matrix operations with QR/LU decomposition
  • GUI Applications - Interactive expression evaluator, unit converter, and function grapher

Requirements

  • Java 25+
  • Gradle (wrapper included)

Build and Run

# Build the project
./gradlew build
# Run tests
./gradlew test# Run GUI applications
./gradlew run # Interactive expression evaluator (MainFrame)

For complete grammar documentation, see docs/GRAMMAR.md.


Quick Start - Expression Parser (MathEngine)

The MathEngine class is the main entry point for parsing and evaluating mathematical expressions. It provides a modern, immutable API with comprehensive configuration options.

Basic Usage

// Create default engineMathEngineengine = MathEngine.create();
// Simple arithmeticNodeConstantresult = engine.evaluate("2 + 3 * 4");
System.out.println(result); // 14// Exact rational arithmetic (fractions preserved)result = engine.evaluate("1/3 + 1/6");
System.out.println(result); // 1/2// Power and factorialresult = engine.evaluate("2^10 + 5!");
System.out.println(result); // 1144

Variables and Functions

MathEngineengine = MathEngine.create();
// Define variablesengine.evaluate("x := 10");
engine.evaluate("y := 20");
NodeConstantresult = engine.evaluate("x + y"); // 30// Define custom functionsengine.evaluate("square(n) := n^2");
result = engine.evaluate("square(5)"); // 25// Multi-parameter functionsengine.evaluate("add(a, b) := a + b");
result = engine.evaluate("add(3, 7)"); // 10

Vectors and Matrices

MathEngineengine = MathEngine.create();
// Vector operationsengine.evaluate("v1 := {1, 2, 3}");
engine.evaluate("v2 := {4, 5, 6}");
NodeConstantresult = engine.evaluate("v1 + v2"); // {5, 7, 9}// Matrix operationsengine.evaluate("m1 := [[1, 2], [3, 4]]");
engine.evaluate("m2 := [[5, 6], [7, 8]]");
result = engine.evaluate("m1 + m2"); // [[6, 8], [10, 12]]// Matrix multiplication (@ operator)result = engine.evaluate("m1 @ m2"); // [[19, 22], [43, 50]]// Subscript accessresult = engine.evaluate("v1[0]"); // 1result = engine.evaluate("m1[0, 1]"); // 2result = engine.evaluate("v1[1:3]"); // {2, 3} (slice)

Lambdas and Higher-Order Functions

MathEngineengine = MathEngine.create();
// Lambda functionsNodeConstantresult = engine.evaluate("map(x -> x + 3, {1, 2, 3, 4, 5})");
// {4, 5, 6, 7, 8}// Filter with lambdaresult = engine.evaluate("filter(x -> x > 3, {1, 2, 3, 4, 5})");
// {4, 5}// Reduce (fold)result = engine.evaluate("reduce((a, b) -> a + b, {1, 2, 3, 4}, 10)");
// 10// Inline lambda callresult = engine.evaluate("(x -> x * 2)(5)");
// 10

List Comprehensions

MathEngineengine = MathEngine.create();
// Basic comprehensionNodeConstantresult = engine.evaluate("{x^2 for x in 1..5}");
// {1, 4, 9, 16, 25}// With filter conditionresult = engine.evaluate("{x for x in 1..20 if x mod 2 == 0}");
// {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}// Nested comprehensionsresult = engine.evaluate("{x*y for x in 1..3 for y in 1..3}");
// {1, 2, 3, 2, 4, 6, 3, 6, 9}

Ranges

MathEngineengine = MathEngine.create();
// Simple rangeNodeConstantresult = engine.evaluate("1..5");
// {1, 2, 3, 4, 5}// Range with stepresult = engine.evaluate("0..1 step 0.2");
// {0.0, 0.2, 0.4, 0.6, 0.8, 1.0}// Descending rangeresult = engine.evaluate("10..1 step -2");
// {10, 8, 6, 4, 2}// Sum of rangeresult = engine.evaluate("sum(1..100)");
// 5050

Recursion and Advanced Functions

MathEngineengine = MathEngine.create();
// Recursive factorialengine.evaluate("fact(n) := if(n <= 1, 1, n * fact(n-1))");
NodeConstantresult = engine.evaluate("fact(5)"); // 120// Recursive fibonacciengine.evaluate("fib(n) := if(n <= 1, n, fib(n-1) + fib(n-2))");
result = engine.evaluate("fib(10)"); // 55// Mutual recursionengine.evaluate("even(n) := if(n == 0, true, odd(n-1))");
engine.evaluate("odd(n) := if(n == 0, false, even(n-1))");
result = engine.evaluate("even(4)"); // true (as 1.0)// Higher-order functions (functions returning functions)engine.evaluate("makeAdder(n) := (x -> n + x)");
engine.evaluate("add10 := makeAdder(10)");
result = engine.evaluate("add10(5)"); // 15// Function compositionengine.evaluate("compose(f, g) := (x -> f(g(x)))");
engine.evaluate("double(x) := x * 2");
engine.evaluate("square(x) := x^2");
engine.evaluate("doubleSquare := compose(double, square)");
result = engine.evaluate("doubleSquare(5)"); // 50 (2 * 5^2)

Built-in Functions

MathEngineengine = MathEngine.create();
// Trigonometry (angle unit aware)NodeConstantresult = engine.evaluate("sin(pi/2)"); // 1.0result = engine.evaluate("cos(0)"); // 1.0// Logarithms and exponentialsresult = engine.evaluate("ln(e)"); // 1.0result = engine.evaluate("log10(100)"); // 2.0result = engine.evaluate("exp(1)"); // e// Roundingresult = engine.evaluate("floor(3.7)"); // 3result = engine.evaluate("ceil(3.2)"); // 4result = engine.evaluate("round(3.5)"); // 4result = engine.evaluate("roundn(3.14159, 2)"); // 3.14// Vector operationsresult = engine.evaluate("sum({1, 2, 3, 4})"); // 10result = engine.evaluate("mean({1, 2, 3, 4})"); // 2.5result = engine.evaluate("max({5, 2, 8, 1})"); // 8result = engine.evaluate("sort({3, 1, 4, 2})"); // {1, 2, 3, 4}result = engine.evaluate("reverse({1, 2, 3})"); // {3, 2, 1}

Unit Conversions

MathEngineengine = MathEngine.create();
// Length conversionsNodeConstantresult = engine.evaluate("100 meters in feet");
// 328.084 feet// Temperatureresult = engine.evaluate("32 fahrenheit to celsius");
// 0 celsius// Speedresult = engine.evaluate("60 mph in kph");
// 96.56064 kph// Combined expressionsresult = engine.evaluate("(50 + 50) meters in centimeters");
// 10000 cm

Configuration

// Configure angle unitsMathEngineConfigconfig = MathEngineConfig.builder()
.angleUnit(AngleUnit.DEGREES)
.build();
MathEngineengine = MathEngine.create(config);
NodeConstantresult = engine.evaluate("sin(90)"); // 1.0 (degrees mode)// Configure decimal places for displayconfig = MathEngineConfig.builder()
.decimalPlaces(4)
.build();
engine = MathEngine.create(config);
// Disable specific featuresconfig = MathEngineConfig.builder()
.vectorsEnabled(false)
.matricesEnabled(false)
.build();
engine = MathEngine.create(config);

Function Class - Single Variable Equations

The Function class provides a simple interface for working with single-variable equations. It's used by differential, integral, solvers, and plotting packages.

Basic Usage

importuk.co.ryanharrison.mathengine.core.Function;
importuk.co.ryanharrison.mathengine.parser.AngleUnit;
// Create function (defaults to variable 'x' and radians)Functionf = newFunction("x^2 + 8*x + 12");
// Evaluate at a pointdoubleresult = f.evaluateAt(3.5);
System.out.println(result); // 268.25// Specify variable and angle unitFunctiong = newFunction("t^2 - 4*t + 3", "t", AngleUnit.DEGREES);
result = g.evaluateAt(2.0); // -1.0

The Function class internally uses the parser and caches the compiled expression tree for performance.


Differential - Symbolic and Numeric Differentiation

Symbolic Differentiation

Returns exact derivative as a new Function:

importuk.co.ryanharrison.mathengine.differential.symbolic.Differentiator;
importuk.co.ryanharrison.mathengine.core.Function;
Functionf = newFunction("x^2 + 8*x + 12");
Differentiatordiff = newDifferentiator();
// Get derivative functionFunctionderivative = diff.differentiate(f, true);
System.out.println(derivative); // f(x) = 2*x+8// Evaluate derivative at pointdoubleresult = derivative.evaluateAt(3.5);
System.out.println(result); // 15.0 (exact)

Numeric Differentiation

Estimates derivative at a specific point:

importuk.co.ryanharrison.mathengine.differential.DividedDifferenceMethod;
importuk.co.ryanharrison.mathengine.differential.DifferencesDirection;
importuk.co.ryanharrison.mathengine.core.Function;
Functionf = newFunction("x^2 + 8*x + 12");
// Central differences (most accurate for smooth functions)DividedDifferenceMethodmethod = DividedDifferenceMethod.builder()
.targetFunction(f)
.targetPoint(3.5)
.direction(DifferencesDirection.Central)
.build();
doublederivative = method.deriveFirst();
System.out.println(derivative); // ~15.0 (with small error)// Can also use ExtendedCentralDifferenceMethod or RichardsonExtrapolationMethod// for higher accuracy

Integral - Numerical Integration

Estimates definite integrals using various numerical methods:

importuk.co.ryanharrison.mathengine.integral.TrapeziumIntegrator;
importuk.co.ryanharrison.mathengine.integral.SimpsonIntegrator;
importuk.co.ryanharrison.mathengine.core.Function;
Functionf = newFunction("x^2 + 8*x + 12");
// Trapezium ruleTrapeziumIntegratorintegrator = TrapeziumIntegrator.builder()
.function(f)
.lowerBound(0.5)
.upperBound(5.0)
.iterations(100)
.build();
doubleresult = integrator.integrate();
System.out.println(result); // ~194.626 (exact: 194.625)// Simpson's rule (generally more accurate)SimpsonIntegratorsimpson = SimpsonIntegrator.builder()
.function(f)
.lowerBound(0.5)
.upperBound(5.0)
.iterations(100)
.build();
result = simpson.integrate();
// More accurate than trapezium for same iterations

Other available methods:

  • RectangularIntegrator - Simple rectangle approximation

Solvers - Root Finding Algorithms

Find roots (zeros) of functions numerically:

importuk.co.ryanharrison.mathengine.solvers.BrentSolver;
importuk.co.ryanharrison.mathengine.core.Function;
importjava.util.List;
Functionf = newFunction("x^2 + 8*x + 12");
// Find single rootBrentSolversolver = BrentSolver.builder()
.targetFunction(f)
.lowerBound(-10)
.upperBound(-5)
.iterations(25)
.build();
doubleroot = solver.solve();
System.out.println(root); // ~-6.0// Find all roots in rangeList<Double> roots = solver.solveAll(-8, 2);
System.out.println(roots); // [-6.0, -2.0]

Other available solvers:

  • BisectionSolver - Simple but reliable bisection method
  • NewtonRaphsonSolver - Fast convergence (requires derivative)
  • NewtonBisectionSolver - Hybrid approach

Configure convergence criteria:

importuk.co.ryanharrison.mathengine.solvers.ConvergenceCriteria;
BrentSolverpreciseSolver = BrentSolver.builder()
.targetFunction(f)
.lowerBound(-10)
.upperBound(-5)
.convergenceCriteria(ConvergenceCriteria.WithinTolerance)
.tolerance(1e-10)
.build();

Distributions - Probability Distributions

Immutable implementations of probability distributions with factory methods and builders.

Normal Distribution

importuk.co.ryanharrison.mathengine.distributions.NormalDistribution;
// Standard normal (mean=0, stddev=1)NormalDistributionstandard = NormalDistribution.standard();
doubledensity = standard.density(0.0); // 0.3989 (peak at mean)doublecumulative = standard.cumulative(1.0); // 0.8413// Custom distribution using factory methodNormalDistributioncustom = NormalDistribution.of(15, 2.6);
density = custom.density(15.7); // 0.1480cumulative = custom.cumulative(15.7); // 0.6061// Using builderNormalDistributiondist = NormalDistribution.builder()
.mean(100)
.standardDeviation(15)
.build();
// Inverse CDF (quantile function)doublequantile = dist.inverseCumulative(0.95); // ~124.67

Other Distributions

importuk.co.ryanharrison.mathengine.distributions.*;
// Beta distributionBetaDistributionbeta = BetaDistribution.of(2.0, 5.0);
doubledensity = beta.density(0.3);
// Exponential distributionExponentialDistributionexp = ExponentialDistribution.of(1.5);
doublecumulative = exp.cumulative(2.0);
// F distributionFDistributionf = FDistribution.of(5, 10); // degrees of freedomdoublepValue = f.cumulative(2.5);
// Student's T distributionStudentTDistributiont = StudentTDistribution.of(20); // dfdoublecriticalValue = t.inverseCumulative(0.975);
// Binomial distribution (discrete)BinomialDistributionbinomial = BinomialDistribution.of(10, 0.5);
doubleprobability = binomial.density(5); // P(X = 5)cumulative = binomial.cumulative(7); // P(X <= 7)// Logistic distributionLogisticDistributionlogistic = LogisticDistribution.of(0.0, 1.0);
density = logistic.density(0.0);

All distributions provide:

  • density(x) - Probability density/mass function
  • cumulative(x) - Cumulative distribution function
  • inverseCumulative(p) - Quantile function (continuous only)
  • getMean(), getVariance(), getStandardDeviation() - Distribution properties

Linear Algebra - Vectors and Matrices

Vectors

importuk.co.ryanharrison.mathengine.linearalgebra.Vector;
// Create vectorsVectorv1 = Vector.of(1, 2, 3, 4);
Vectorv2 = Vector.parse("{5, 6, 7, 8}"); // Parse from string// Basic operationsVectorsum = v1.add(v2); // {6, 8, 10, 12}Vectordiff = v1.subtract(v2); // {-4, -4, -4, -4}Vectorscaled = v1.multiply(2); // {2, 4, 6, 8}// Vector operationsdoubledotProduct = v1.dotProduct(v2); // 70doublemagnitude = v1.getNorm(); // 5.477Vectornormalized = v1.getUnitVector();
// Cross product (3D vectors)Vectora = Vector.of(1, 0, 0);
Vectorb = Vector.of(0, 1, 0);
Vectorcross = a.crossProduct(b); // {0, 0, 1}

Matrices

importuk.co.ryanharrison.mathengine.linearalgebra.Matrix;
// Create matricesMatrixm1 = Matrix.of(newdouble[][]{{1, 2}, {3, 4}});
Matrixm2 = Matrix.ofSize(2, 2); // 2x2 zero matrix// Basic operationsMatrixsum = m1.add(m2);
Matrixproduct = m1.multiply(m2);
Matrixscaled = m1.multiply(2.0);
// Matrix propertiesdoubledeterminant = m1.determinant(); // -2Matrixtranspose = m1.transpose();
Matrixinverse = m1.inverse();
// Verify: A * A^-1 = IMatrixidentity = m1.multiply(inverse);
// Solve linear system: A*X = BMatrixA = Matrix.of(newdouble[][]{{3, 7}, {4, 12}});
MatrixB = Matrix.of(newdouble[][]{{-4, 5}, {8, 1}});
MatrixX = A.solve(B);
// Advanced decompositionsimportuk.co.ryanharrison.mathengine.linearalgebra.QRDecomposition;
importuk.co.ryanharrison.mathengine.linearalgebra.LUDecomposition;
QRDecompositionqr = newQRDecomposition(A);
MatrixQ = qr.getQ();
MatrixR = qr.getR();
LUDecompositionlu = newLUDecomposition(A);
MatrixL = lu.getL();
MatrixU = lu.getU();

Unit Conversion

Flexible unit conversion with string-based matching:

importuk.co.ryanharrison.mathengine.unitconversion.ConversionEngine;
ConversionEngineengine = ConversionEngine.loadDefaults();
// Basic conversionStringresult = engine.convertToFormattedString(12, "mph", "kph", 2);
// "12.0 miles per hour = 19.31 kilometres per hour"// Flexible unit aliasesresult = engine.convertToFormattedString(12, "miles per hour", "kilometer per hour", 2);
// Same result// Get numeric value onlydoublevalue = engine.convert(100, "meters", "feet").result().toDouble(); // 328.084// Currency conversion (requires internet)engine.updateCurrencies();
result = engine.convertToFormattedString(100, "usd", "eur", 2);
// Timezone conversionengine.updateTimeZones();
result = engine.convertToFormattedString(14, "london", "new york", 1);
// "14.0 London = 9.0 New York" (2pm in London = 9am in NYC)

The engine supports hundreds of units including:

  • Length: meters, feet, miles, kilometers, etc.
  • Mass: grams, kilograms, pounds, ounces, etc.
  • Temperature: celsius, fahrenheit, kelvin
  • Speed: mph, kph, m/s, knots, etc.
  • Area, volume, pressure, energy, power, and more

Unit definitions are stored in unit.xml and can be extended.


Special Functions

Mathematical special functions and number theory utilities:

importuk.co.ryanharrison.mathengine.special.*;
// Gamma functiondoublegamma = Gamma.gamma(5.0); // 24.0 (= 4!)doublelogGamma = Gamma.logGamma(100.0); // Avoid overflow// Beta functiondoublebeta = Beta.beta(2.0, 3.0); // 0.0833// Error function (used in normal distribution)doubleerf = Erf.erf(1.0); // 0.8427doubleerfc = Erf.erfc(1.0); // 0.1573 (= 1 - erf)doubleerfInv = Erf.erfInv(0.5); // ~0.4769// Prime numbersbooleanisPrime = Primes.isPrime(97); // truelongnextPrime = Primes.nextPrime(97); // 101List<Long> factors = Primes.primeFactors(84); // [2, 2, 3, 7]

Regression Models

Fit mathematical models to data:

importuk.co.ryanharrison.mathengine.regression.LinearRegressionModel;
// Sample datadouble[] x = {1, 2, 3, 4, 5};
double[] y = {2.1, 3.9, 6.2, 8.1, 9.9};
// Fit linear model: y = a + bxLinearRegressionModelmodel = LinearRegressionModel.of(x, y);
doubleintercept = model.getIntercept(); // a ≈ 0.08doubleslope = model.getSlope(); // b ≈ 1.98// Use fitted model for predictionsdoublepredicted = model.evaluateAt(6.0); // ~12.0// Get coefficientsdouble[] coeffs = model.getCoefficients(); // {intercept, slope}

Plotting and GUI

GUI Applications

MainFrame - Interactive expression evaluator with history:

Features:

  • Expression input with history (arrow keys)
  • Live variable/function tracking
  • Enhanced error messages with source context
  • Syntax reference panel

Converter - Unit conversion tool:

  • Dropdown selection for units
  • String-based conversion (e.g., "12 mph in kph")
  • Currency and timezone support

Grapher - Function plotter:

  • Interactive pan and zoom
  • Plot any Function object
  • Real-time rendering

Utility Classes

MathUtils

Extended mathematical functions:

importuk.co.ryanharrison.mathengine.util.MathUtils;
// Hyperbolic functionsdoublesinh = MathUtils.sinh(1.5);
doublecosh = MathUtils.cosh(1.5);
doubletanh = MathUtils.tanh(1.5);
// Inverse hyperbolicdoubleasinh = MathUtils.asinh(1.0);
doubleacosh = MathUtils.acosh(2.0);
doubleatanh = MathUtils.atanh(0.5);
// Combinatoricslongfactorial = MathUtils.factorial(10); // 3628800doublechoose = MathUtils.combination(10, 3); // 120doublepermute = MathUtils.permutation(10, 3); // 720// Number utilitiesintgcd = MathUtils.gcd(48, 18); // 6intlcm = MathUtils.lcm(12, 18); // 36doubleround = MathUtils.round(3.14159, 2); // 3.14

StatUtils

Statistical functions:

importuk.co.ryanharrison.mathengine.util.StatUtils;
double[] data = {2.1, 3.5, 2.8, 4.2, 3.9, 3.1, 2.9};
doublemean = StatUtils.mean(data); // 3.21doublemedian = StatUtils.median(data); // 3.1doublestddev = StatUtils.standardDeviation(data); // 0.73doublevariance = StatUtils.variance(data); // 0.54doublemin = StatUtils.min(data); // 2.1doublemax = StatUtils.max(data); // 4.2// Percentilesdoubleq1 = StatUtils.percentile(data, 0.25); // First quartiledoubleq3 = StatUtils.percentile(data, 0.75); // Third quartile// Distribution propertiesdoubleskewness = StatUtils.skewness(data);
doublekurtosis = StatUtils.kurtosis(data);

BigRational

Exact fraction arithmetic (used internally by parser):

importuk.co.ryanharrison.mathengine.core.BigRational;
BigRationala = newBigRational(1, 3); // 1/3BigRationalb = newBigRational(1, 6); // 1/6BigRationalsum = a.add(b); // 1/2 (exact)BigRationalproduct = a.multiply(b); // 1/18// Automatic simplificationBigRationalc = newBigRational(6, 8); // Stored as 3/4// Convert to decimal when neededdoubledecimal = c.toDouble(); // 0.75

Advanced Topics

Custom Built-in Functions

Add your own functions to the parser:

importuk.co.ryanharrison.mathengine.parser.function.MathFunction;
importuk.co.ryanharrison.mathengine.parser.parser.nodes.*;
// Define custom functionMathFunctionmyFunc = newMathFunction() {
@OverridepublicStringgetName() {
return"double";
}
@OverridepublicintgetArity() {
return1; // Number of parameters
}
@OverridepublicNodeConstantexecute(NodeConstant[] args) {
doublevalue = args[0].getTransformer().toNodeNumber().doubleValue();
returnnewNodeDouble(value * 2);
}
};
// Register with engineMathEngineConfigconfig = MathEngineConfig.builder()
.additionalFunction(myFunc)
.build();
MathEngineengine = MathEngine.create(config);
NodeConstantresult = engine.evaluate("double(21)"); // 42

Error Handling

All parser exceptions extend MathEngineException and provide detailed error messages:

importuk.co.ryanharrison.mathengine.parser.MathEngineException;
importuk.co.ryanharrison.mathengine.parser.parser.ParseException;
importuk.co.ryanharrison.mathengine.parser.evaluator.UndefinedVariableException;
MathEngineengine = MathEngine.create();
try {
engine.evaluate("2 + * 3"); // Syntax error
} catch (ParseExceptione) {
System.out.println(e.formatMessage());
// Parse error at line 1, column 5: Unexpected token '*'// 1 | 2 + * 3// | ^
}
try {
engine.evaluate("unknownVar + 5");
} catch (UndefinedVariableExceptione) {
System.out.println(e.formatMessage());
System.out.println(e.getVariableName()); // "unknownVar"
}

Documentation

  • Grammar Reference - Complete parser grammar and built-in function reference (150+ functions)
  • Source Code Javadoc - Comprehensive inline documentation

License

See LICENSE file for details.

Contributing

Contributions welcome. Please follow the code quality standards documented in docs/.

About

A mathematical library complete with a complex expression parser

Resources

Stars

27 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages