GAIA-Agent / tools /math_tools.py
DenisRz's picture
Initial upload: GAIA Agent
67d287e
"""
Mathematical computation tools for the GAIA Agent.
Includes symbolic math, matrix operations, calculator, and statistics.
"""
import json
from langchain_core.tools import tool
@tool
def symbolic_math(expression: str, operation: str = "simplify", variable: str = "x") -> str:
"""Perform symbolic mathematics operations using SymPy.
Operations available:
- simplify: Simplify an expression
- expand: Expand an expression
- factor: Factor an expression
- solve: Solve an equation (set equal to 0)
- differentiate: Compute derivative with respect to variable
- integrate: Compute indefinite integral with respect to variable
- limit: Compute limit as variable approaches 0 (use expression like "sin(x)/x")
- series: Compute Taylor series expansion
Args:
expression: Mathematical expression (use ** for power, e.g., "x**2 + 2*x + 1")
operation: One of: simplify, expand, factor, solve, differentiate, integrate, limit, series
variable: Variable to use for calculus operations (default: "x")
"""
try:
import sympy as sp
from sympy.parsing.sympy_parser import parse_expr, standard_transformations, implicit_multiplication_application
# Define common symbols
x, y, z, t, n, a, b, c = sp.symbols('x y z t n a b c')
symbol_map = {'x': x, 'y': y, 'z': z, 't': t, 'n': n, 'a': a, 'b': b, 'c': c}
# Get the main variable
var = symbol_map.get(variable, sp.Symbol(variable))
# Parse the expression with implicit multiplication support
transformations = standard_transformations + (implicit_multiplication_application,)
expr = parse_expr(expression, local_dict=symbol_map, transformations=transformations)
operation = operation.lower().strip()
if operation == "simplify":
result = sp.simplify(expr)
elif operation == "expand":
result = sp.expand(expr)
elif operation == "factor":
result = sp.factor(expr)
elif operation == "solve":
result = sp.solve(expr, var)
elif operation in ("differentiate", "diff", "derivative"):
result = sp.diff(expr, var)
elif operation in ("integrate", "integral"):
result = sp.integrate(expr, var)
elif operation == "limit":
result = sp.limit(expr, var, 0)
elif operation == "series":
result = sp.series(expr, var, 0, 6)
else:
return f"Unknown operation: {operation}. Available: simplify, expand, factor, solve, differentiate, integrate, limit, series"
return f"Input: {expression}\nOperation: {operation}\nResult: {result}"
except ImportError:
return "Error: SymPy is not installed. Please install it with: pip install sympy"
except Exception as e:
return f"Symbolic math error: {str(e)}"
@tool
def matrix_operations(matrix_a: str, operation: str = "determinant", matrix_b: str = "") -> str:
"""Perform matrix operations using NumPy.
Operations available:
- determinant: Compute determinant of matrix_a
- inverse: Compute inverse of matrix_a
- transpose: Compute transpose of matrix_a
- eigenvalues: Compute eigenvalues of matrix_a
- eigenvectors: Compute eigenvectors of matrix_a
- rank: Compute rank of matrix_a
- trace: Compute trace of matrix_a
- multiply: Matrix multiplication of matrix_a @ matrix_b
- add: Element-wise addition of matrix_a + matrix_b
- solve: Solve linear system Ax = b (matrix_a is A, matrix_b is b vector)
Args:
matrix_a: Matrix as JSON array, e.g., "[[1,2],[3,4]]"
operation: One of: determinant, inverse, transpose, eigenvalues, eigenvectors, rank, trace, multiply, add, solve
matrix_b: Second matrix for binary operations (as JSON array)
"""
try:
import numpy as np
# Parse matrix_a
try:
a = np.array(json.loads(matrix_a), dtype=float)
except json.JSONDecodeError:
return f"Error parsing matrix_a: {matrix_a}. Use JSON format like [[1,2],[3,4]]"
operation = operation.lower().strip()
# Parse matrix_b if needed
b = None
if matrix_b and operation in ("multiply", "add", "solve"):
try:
b = np.array(json.loads(matrix_b), dtype=float)
except json.JSONDecodeError:
return f"Error parsing matrix_b: {matrix_b}. Use JSON format like [[1,2],[3,4]]"
if operation == "determinant":
if a.shape[0] != a.shape[1]:
return "Error: Determinant requires a square matrix"
result = np.linalg.det(a)
elif operation == "inverse":
if a.shape[0] != a.shape[1]:
return "Error: Inverse requires a square matrix"
result = np.linalg.inv(a)
elif operation == "transpose":
result = a.T
elif operation == "eigenvalues":
if a.shape[0] != a.shape[1]:
return "Error: Eigenvalues require a square matrix"
result = np.linalg.eigvals(a)
elif operation == "eigenvectors":
if a.shape[0] != a.shape[1]:
return "Error: Eigenvectors require a square matrix"
eigenvalues, eigenvectors = np.linalg.eig(a)
result = {"eigenvalues": eigenvalues.tolist(), "eigenvectors": eigenvectors.tolist()}
elif operation == "rank":
result = np.linalg.matrix_rank(a)
elif operation == "trace":
result = np.trace(a)
elif operation == "multiply":
if b is None:
return "Error: multiply operation requires matrix_b"
result = a @ b
elif operation == "add":
if b is None:
return "Error: add operation requires matrix_b"
result = a + b
elif operation == "solve":
if b is None:
return "Error: solve operation requires matrix_b (the b vector)"
result = np.linalg.solve(a, b)
else:
return f"Unknown operation: {operation}. Available: determinant, inverse, transpose, eigenvalues, eigenvectors, rank, trace, multiply, add, solve"
# Format result
if isinstance(result, np.ndarray):
result_str = np.array2string(result, precision=6, suppress_small=True)
elif isinstance(result, dict):
result_str = json.dumps(result, indent=2)
else:
result_str = str(result)
return f"Matrix A:\n{np.array2string(a)}\nOperation: {operation}\nResult:\n{result_str}"
except ImportError:
return "Error: NumPy is not installed. Please install it with: pip install numpy"
except np.linalg.LinAlgError as e:
return f"Linear algebra error: {str(e)}"
except Exception as e:
return f"Matrix operation error: {str(e)}"
@tool
def calculator(expression: str) -> str:
"""Evaluate a mathematical expression with high precision.
Supports standard math operations: +, -, *, /, ** (power), % (modulo)
Also supports functions: sqrt, sin, cos, tan, log, log10, exp, abs, ceil, floor, round
Constants: pi, e
Args:
expression: Mathematical expression to evaluate, e.g., "sqrt(2) * pi" or "2**10 + 5"
"""
try:
import math
from decimal import Decimal, getcontext
# Set high precision
getcontext().prec = 50
# Safe evaluation context with math functions
safe_dict = {
'sqrt': math.sqrt,
'sin': math.sin,
'cos': math.cos,
'tan': math.tan,
'asin': math.asin,
'acos': math.acos,
'atan': math.atan,
'atan2': math.atan2,
'log': math.log,
'log10': math.log10,
'log2': math.log2,
'exp': math.exp,
'pow': pow,
'abs': abs,
'ceil': math.ceil,
'floor': math.floor,
'round': round,
'pi': math.pi,
'e': math.e,
'inf': math.inf,
'factorial': math.factorial,
'gcd': math.gcd,
'lcm': math.lcm,
'degrees': math.degrees,
'radians': math.radians,
}
# Basic security check - only allow safe characters
allowed_chars = set('0123456789+-*/.()%, abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_')
if not all(c in allowed_chars for c in expression):
return f"Error: Expression contains invalid characters. Only math operations and functions are allowed."
# Evaluate the expression
result = eval(expression, {"__builtins__": {}}, safe_dict)
# Format the result
if isinstance(result, float):
# Check if it's close to an integer
if result == int(result):
result_str = str(int(result))
else:
result_str = f"{result:.15g}" # High precision but remove trailing zeros
else:
result_str = str(result)
return f"Expression: {expression}\nResult: {result_str}"
except ZeroDivisionError:
return "Error: Division by zero"
except ValueError as e:
return f"Math error: {str(e)}"
except Exception as e:
return f"Calculator error: {str(e)}"
@tool
def statistical_analysis(data: str, operation: str = "describe") -> str:
"""Perform statistical analysis on numerical data.
Operations available:
- describe: Full statistical summary (mean, median, std, min, max, quartiles)
- mean: Arithmetic mean
- median: Median value
- mode: Most frequent value
- std: Standard deviation
- var: Variance
- correlation: Correlation coefficient (requires 2D data like [[x1,y1],[x2,y2],...])
- regression: Linear regression (requires 2D data)
- percentile: Compute 25th, 50th, 75th percentiles
- zscore: Compute z-scores for each value
Args:
data: Numerical data as JSON array, e.g., "[1, 2, 3, 4, 5]" or "[[1,2],[3,4]]" for 2D
operation: One of: describe, mean, median, mode, std, var, correlation, regression, percentile, zscore
"""
try:
import numpy as np
from scipy import stats as sp_stats
# Parse data
try:
arr = np.array(json.loads(data), dtype=float)
except json.JSONDecodeError:
return f"Error parsing data: {data}. Use JSON format like [1, 2, 3, 4, 5]"
operation = operation.lower().strip()
if operation == "describe":
if arr.ndim == 1:
result = {
"count": len(arr),
"mean": float(np.mean(arr)),
"median": float(np.median(arr)),
"std": float(np.std(arr)),
"variance": float(np.var(arr)),
"min": float(np.min(arr)),
"max": float(np.max(arr)),
"25th_percentile": float(np.percentile(arr, 25)),
"50th_percentile": float(np.percentile(arr, 50)),
"75th_percentile": float(np.percentile(arr, 75)),
"sum": float(np.sum(arr)),
}
else:
result = {
"shape": arr.shape,
"mean_per_column": np.mean(arr, axis=0).tolist(),
"std_per_column": np.std(arr, axis=0).tolist(),
}
elif operation == "mean":
result = float(np.mean(arr))
elif operation == "median":
result = float(np.median(arr))
elif operation == "mode":
mode_result = sp_stats.mode(arr.flatten(), keepdims=False)
result = {"mode": float(mode_result.mode), "count": int(mode_result.count)}
elif operation == "std":
result = float(np.std(arr))
elif operation == "var":
result = float(np.var(arr))
elif operation == "correlation":
if arr.ndim != 2 or arr.shape[1] != 2:
return "Error: correlation requires 2D data with 2 columns, e.g., [[x1,y1],[x2,y2],...]"
result = float(np.corrcoef(arr[:, 0], arr[:, 1])[0, 1])
elif operation == "regression":
if arr.ndim != 2 or arr.shape[1] != 2:
return "Error: regression requires 2D data with 2 columns, e.g., [[x1,y1],[x2,y2],...]"
slope, intercept, r_value, p_value, std_err = sp_stats.linregress(arr[:, 0], arr[:, 1])
result = {
"slope": float(slope),
"intercept": float(intercept),
"r_squared": float(r_value**2),
"p_value": float(p_value),
"std_error": float(std_err),
"equation": f"y = {slope:.6f}x + {intercept:.6f}"
}
elif operation == "percentile":
result = {
"25th": float(np.percentile(arr, 25)),
"50th": float(np.percentile(arr, 50)),
"75th": float(np.percentile(arr, 75)),
"90th": float(np.percentile(arr, 90)),
"95th": float(np.percentile(arr, 95)),
"99th": float(np.percentile(arr, 99)),
}
elif operation == "zscore":
zscores = sp_stats.zscore(arr.flatten())
result = zscores.tolist()
else:
return f"Unknown operation: {operation}. Available: describe, mean, median, mode, std, var, correlation, regression, percentile, zscore"
# Format result
if isinstance(result, dict):
result_str = json.dumps(result, indent=2)
elif isinstance(result, list):
result_str = json.dumps(result)
else:
result_str = str(result)
return f"Data: {len(arr.flatten())} values\nOperation: {operation}\nResult:\n{result_str}"
except ImportError as e:
missing = "scipy" if "scipy" in str(e) else "numpy"
return f"Error: {missing} is not installed. Please install it with: pip install {missing}"
except Exception as e:
return f"Statistical analysis error: {str(e)}"