Spaces:
Runtime error
Runtime error
| import sympy as sp | |
| import numpy as np | |
| import matplotlib.pyplot as plt | |
| import io | |
| import base64 | |
| from typing import Any, Dict, List, Optional, Union | |
| import logging | |
| import re | |
| import math | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class CalculatorTool: | |
| def __init__(self): | |
| self.variables = {} | |
| self.last_result = None | |
| def evaluate_expression(self, expression: str) -> Dict[str, Any]: | |
| """ | |
| Safely evaluate mathematical expressions | |
| """ | |
| try: | |
| # Clean the expression | |
| expression = self._clean_expression(expression) | |
| # Try sympy first for symbolic computation | |
| try: | |
| result = sp.sympify(expression).evalf() | |
| self.last_result = float(result) | |
| return { | |
| 'result': float(result), | |
| 'expression': expression, | |
| 'type': 'symbolic', | |
| 'formatted': str(result) | |
| } | |
| except: | |
| # Fall back to basic evaluation | |
| result = eval(expression, {"__builtins__": {}}, self._get_safe_namespace()) | |
| self.last_result = result | |
| return { | |
| 'result': result, | |
| 'expression': expression, | |
| 'type': 'numeric', | |
| 'formatted': str(result) | |
| } | |
| except Exception as e: | |
| logger.error(f"Error evaluating expression: {e}") | |
| return { | |
| 'error': str(e), | |
| 'expression': expression, | |
| 'result': None | |
| } | |
| def _clean_expression(self, expression: str) -> str: | |
| """ | |
| Clean and prepare expression for evaluation | |
| """ | |
| # Replace common math notation | |
| replacements = { | |
| '^': '**', | |
| '×': '*', | |
| '÷': '/', | |
| 'π': 'pi', | |
| 'e': 'E' | |
| } | |
| for old, new in replacements.items(): | |
| expression = expression.replace(old, new) | |
| return expression | |
| def _get_safe_namespace(self) -> Dict[str, Any]: | |
| """ | |
| Get safe namespace for expression evaluation | |
| """ | |
| safe_dict = { | |
| 'abs': abs, 'round': round, 'min': min, 'max': max, | |
| 'sum': sum, 'pow': pow, 'divmod': divmod, | |
| 'sin': math.sin, 'cos': math.cos, 'tan': math.tan, | |
| 'asin': math.asin, 'acos': math.acos, 'atan': math.atan, | |
| 'sinh': math.sinh, 'cosh': math.cosh, 'tanh': math.tanh, | |
| 'log': math.log, 'log10': math.log10, 'log2': math.log2, | |
| 'exp': math.exp, 'sqrt': math.sqrt, 'factorial': math.factorial, | |
| 'pi': math.pi, 'e': math.e, 'inf': math.inf, 'nan': math.nan, | |
| 'degrees': math.degrees, 'radians': math.radians, | |
| 'ceil': math.ceil, 'floor': math.floor, | |
| } | |
| safe_dict.update(self.variables) | |
| return safe_dict | |
| def solve_equation(self, equation: str, variable: str = 'x') -> Dict[str, Any]: | |
| """ | |
| Solve equations symbolically | |
| """ | |
| try: | |
| # Parse equation | |
| if '=' in equation: | |
| left, right = equation.split('=', 1) | |
| eq = sp.Eq(sp.sympify(left), sp.sympify(right)) | |
| else: | |
| eq = sp.sympify(equation) | |
| # Solve | |
| var = sp.Symbol(variable) | |
| solutions = sp.solve(eq, var) | |
| return { | |
| 'equation': equation, | |
| 'variable': variable, | |
| 'solutions': [str(sol) for sol in solutions], | |
| 'numeric_solutions': [float(sol.evalf()) if sol.is_real else complex(sol.evalf()) for sol in solutions] | |
| } | |
| except Exception as e: | |
| logger.error(f"Error solving equation: {e}") | |
| return { | |
| 'error': str(e), | |
| 'equation': equation, | |
| 'solutions': [] | |
| } | |
| def plot_function(self, expression: str, x_range: tuple = (-10, 10), | |
| points: int = 1000) -> str: | |
| """ | |
| Plot a mathematical function and return base64 encoded image | |
| """ | |
| try: | |
| x = sp.Symbol('x') | |
| expr = sp.sympify(expression) | |
| # Convert to numpy function | |
| f = sp.lambdify(x, expr, 'numpy') | |
| # Generate points | |
| x_vals = np.linspace(x_range[0], x_range[1], points) | |
| y_vals = f(x_vals) | |
| # Create plot | |
| plt.figure(figsize=(10, 6)) | |
| plt.plot(x_vals, y_vals, 'b-', linewidth=2) | |
| plt.grid(True, alpha=0.3) | |
| plt.xlabel('x') | |
| plt.ylabel('f(x)') | |
| plt.title(f'Plot of f(x) = {expression}') | |
| # Convert to base64 | |
| buffer = io.BytesIO() | |
| plt.savefig(buffer, format='png', dpi=150, bbox_inches='tight') | |
| buffer.seek(0) | |
| plot_data = base64.b64encode(buffer.getvalue()).decode() | |
| plt.close() | |
| return plot_data | |
| except Exception as e: | |
| logger.error(f"Error plotting function: {e}") | |
| return "" | |
| def calculate_derivative(self, expression: str, variable: str = 'x', | |
| order: int = 1) -> Dict[str, Any]: | |
| """ | |
| Calculate derivative of an expression | |
| """ | |
| try: | |
| var = sp.Symbol(variable) | |
| expr = sp.sympify(expression) | |
| derivative = sp.diff(expr, var, order) | |
| return { | |
| 'original': expression, | |
| 'derivative': str(derivative), | |
| 'order': order, | |
| 'variable': variable, | |
| 'simplified': str(sp.simplify(derivative)) | |
| } | |
| except Exception as e: | |
| logger.error(f"Error calculating derivative: {e}") | |
| return { | |
| 'error': str(e), | |
| 'original': expression | |
| } | |
| def calculate_integral(self, expression: str, variable: str = 'x', | |
| limits: Optional[tuple] = None) -> Dict[str, Any]: | |
| """ | |
| Calculate integral of an expression | |
| """ | |
| try: | |
| var = sp.Symbol(variable) | |
| expr = sp.sympify(expression) | |
| if limits: | |
| # Definite integral | |
| result = sp.integrate(expr, (var, limits[0], limits[1])) | |
| integral_type = 'definite' | |
| else: | |
| # Indefinite integral | |
| result = sp.integrate(expr, var) | |
| integral_type = 'indefinite' | |
| return { | |
| 'original': expression, | |
| 'integral': str(result), | |
| 'type': integral_type, | |
| 'variable': variable, | |
| 'limits': limits, | |
| 'numeric_value': float(result.evalf()) if result.is_number else None | |
| } | |
| except Exception as e: | |
| logger.error(f"Error calculating integral: {e}") | |
| return { | |
| 'error': str(e), | |
| 'original': expression | |
| } | |
| def matrix_operations(self, operation: str, *matrices) -> Dict[str, Any]: | |
| """ | |
| Perform matrix operations | |
| """ | |
| try: | |
| # Convert input to sympy matrices | |
| sp_matrices = [] | |
| for matrix in matrices: | |
| if isinstance(matrix, list): | |
| sp_matrices.append(sp.Matrix(matrix)) | |
| else: | |
| sp_matrices.append(sp.sympify(matrix)) | |
| result = None | |
| if operation == 'add' and len(sp_matrices) >= 2: | |
| result = sp_matrices[0] + sp_matrices[1] | |
| elif operation == 'multiply' and len(sp_matrices) >= 2: | |
| result = sp_matrices[0] * sp_matrices[1] | |
| elif operation == 'inverse' and len(sp_matrices) >= 1: | |
| result = sp_matrices[0].inv() | |
| elif operation == 'determinant' and len(sp_matrices) >= 1: | |
| result = sp_matrices[0].det() | |
| elif operation == 'transpose' and len(sp_matrices) >= 1: | |
| result = sp_matrices[0].T | |
| elif operation == 'eigenvalues' and len(sp_matrices) >= 1: | |
| result = sp_matrices[0].eigenvals() | |
| return { | |
| 'operation': operation, | |
| 'result': str(result) if result is not None else None, | |
| 'matrices_count': len(sp_matrices) | |
| } | |
| except Exception as e: | |
| logger.error(f"Error in matrix operation: {e}") | |
| return { | |
| 'error': str(e), | |
| 'operation': operation | |
| } | |
| def statistics_calculations(self, data: List[float], operation: str) -> Dict[str, Any]: | |
| """ | |
| Perform statistical calculations | |
| """ | |
| try: | |
| data = np.array(data) | |
| result = None | |
| if operation == 'mean': | |
| result = np.mean(data) | |
| elif operation == 'median': | |
| result = np.median(data) | |
| elif operation == 'std': | |
| result = np.std(data) | |
| elif operation == 'var': | |
| result = np.var(data) | |
| elif operation == 'min': | |
| result = np.min(data) | |
| elif operation == 'max': | |
| result = np.max(data) | |
| elif operation == 'sum': | |
| result = np.sum(data) | |
| elif operation == 'range': | |
| result = np.max(data) - np.min(data) | |
| return { | |
| 'operation': operation, | |
| 'result': float(result) if result is not None else None, | |
| 'data_size': len(data), | |
| 'data_preview': data[:5].tolist() if len(data) > 5 else data.tolist() | |
| } | |
| except Exception as e: | |
| logger.error(f"Error in statistics calculation: {e}") | |
| return { | |
| 'error': str(e), | |
| 'operation': operation | |
| } | |
| def unit_conversion(self, value: float, from_unit: str, to_unit: str) -> Dict[str, Any]: | |
| """ | |
| Convert between different units | |
| """ | |
| # Basic unit conversion factors (could be expanded) | |
| conversions = { | |
| # Length | |
| ('m', 'cm'): 100, | |
| ('m', 'mm'): 1000, | |
| ('m', 'km'): 0.001, | |
| ('cm', 'm'): 0.01, | |
| ('mm', 'm'): 0.001, | |
| ('km', 'm'): 1000, | |
| ('ft', 'm'): 0.3048, | |
| ('in', 'cm'): 2.54, | |
| # Weight | |
| ('kg', 'g'): 1000, | |
| ('g', 'kg'): 0.001, | |
| ('lb', 'kg'): 0.453592, | |
| ('kg', 'lb'): 2.20462, | |
| # Temperature (special handling needed) | |
| # Time | |
| ('h', 'min'): 60, | |
| ('min', 's'): 60, | |
| ('h', 's'): 3600, | |
| ('day', 'h'): 24, | |
| } | |
| try: | |
| if (from_unit, to_unit) in conversions: | |
| result = value * conversions[(from_unit, to_unit)] | |
| elif (to_unit, from_unit) in conversions: | |
| result = value / conversions[(to_unit, from_unit)] | |
| else: | |
| return { | |
| 'error': f"Conversion from {from_unit} to {to_unit} not supported", | |
| 'value': value | |
| } | |
| return { | |
| 'original_value': value, | |
| 'original_unit': from_unit, | |
| 'converted_value': result, | |
| 'converted_unit': to_unit, | |
| 'conversion_factor': result / value if value != 0 else None | |
| } | |
| except Exception as e: | |
| logger.error(f"Error in unit conversion: {e}") | |
| return { | |
| 'error': str(e), | |
| 'value': value | |
| } | |
| def set_variable(self, name: str, value: Any) -> bool: | |
| """ | |
| Set a variable for use in calculations | |
| """ | |
| try: | |
| self.variables[name] = value | |
| logger.info(f"Set variable {name} = {value}") | |
| return True | |
| except Exception as e: | |
| logger.error(f"Error setting variable: {e}") | |
| return False | |
| def get_variables(self) -> Dict[str, Any]: | |
| """ | |
| Get all stored variables | |
| """ | |
| return self.variables.copy() | |
| def clear_variables(self) -> bool: | |
| """ | |
| Clear all stored variables | |
| """ | |
| try: | |
| self.variables.clear() | |
| logger.info("Cleared all variables") | |
| return True | |
| except Exception as e: | |
| logger.error(f"Error clearing variables: {e}") | |
| return False | |
| def format_result_for_llm(self, result: Dict[str, Any]) -> str: | |
| """ | |
| Format calculation results for LLM consumption | |
| """ | |
| if 'error' in result: | |
| return f"Error: {result['error']}" | |
| if 'result' in result: | |
| return f"Result: {result['result']}\nExpression: {result.get('expression', 'N/A')}" | |
| # Handle other result types | |
| formatted_parts = [] | |
| for key, value in result.items(): | |
| if key not in ['error'] and value is not None: | |
| formatted_parts.append(f"{key.title()}: {value}") | |
| return "\n".join(formatted_parts) if formatted_parts else "No result to display" | |