MathSolver / solver.py
Taizun's picture
Update solver.py
2bad1d3 verified
import sympy as sp
from sympy.parsing.sympy_parser import parse_expr, standard_transformations, implicit_multiplication_application
from sympy.solvers import solve
from sympy import integrate, diff, latex,simplify, expand,sqrt, log, exp, sin, cos, tan, asin, acos, atan, Symbol, factorial, laplace_transform
import re
def format_expression(expr):
latex_expr = latex(expr)
replacements = {
'**': '^', # Power notation
'*x': 'x', # Remove unnecessary multiplication signs
'*(': '(', # Remove multiplication before parentheses
'exp': 'e^', # Exponential notation
'sqrt': '√', # Square root
'factorial': '!', # Factorial symbol
'gamma': 'Γ', # Gamma function
'Gamma': 'Γ', # Sometimes SymPy capitalizes it
'fresnels': 'S', # Fresnel S integral
'fresnelc': 'C', # Fresnel C integral
'hyper': '₁F₂', # Generalized hypergeometric function
'log': 'ln', # Natural logarithm
'oo': '∞', # Infinity symbol
'pi': 'π', # Pi symbol
'E': 'ℯ', # Euler's constant
'I': '𝒊', # Imaginary unit
'Abs': '|', # Absolute value
'Integral': '∫', # Integral symbol
'Derivative': 'd/dx', # Differentiation
'Sum': 'Σ', # Summation symbol
'Product': '∏', # Product symbol
'sin': 'sin', 'cos': 'cos', 'tan': 'tan', # Trig functions (unchanged)
'asin': 'sin⁻¹', 'acos': 'cos⁻¹', 'atan': 'tan⁻¹', # Inverse trig
'sinh': 'sinh', 'cosh': 'cosh', 'tanh': 'tanh', # Hyperbolic trig
'asinh': 'sinh⁻¹', 'acosh': 'cosh⁻¹', 'atanh': 'tanh⁻¹', # Inverse hyperbolic trig
'diff': 'd/dx', # Derivative notation
'integrate': '∫', # Integral notation
'Limit': 'lim', # Limit notation
'floor': '⌊', # Floor function
'ceiling': '⌈', # Ceiling function
'mod': 'mod', # Modulus (unchanged)
'Re': 'ℜ', # Real part
'Im': 'ℑ' # Imaginary part
}
for old, new in replacements.items():
latex_expr = latex_expr.replace(old, new)
return f"$$ {latex_expr} $$"
def preprocess_equation(equation_str):
"""Convert user-friendly equation format to SymPy format."""
try:
# Replace common mathematical notations
replacements = {
'^': '**',
'sin⁻¹': 'asin',
'cos⁻¹': 'acos',
'tan⁻¹': 'atan',
'e^': 'exp',
'ln': 'log', # Convert ln to log (SymPy default)
'√': 'sqrt', # Convert square root symbol to sqrt()
'!': '.factorial()', # Convert factorial to function call
}
for old, new in replacements.items():
equation_str = equation_str.replace(old, new)
equation_str = re.sub(r'(\d+)!', r'factorial(\1)', equation_str)
# Handle exponential expressions
if 'exp' in equation_str:
parts = equation_str.split('exp')
for i in range(1, len(parts)):
if parts[i] and parts[i][0] != '(':
parts[i] = '(' + parts[i]
if '=' in parts[i]:
exp_part, rest = parts[i].split('=', 1)
parts[i] = exp_part + ')=' + rest
else:
parts[i] = parts[i] + ')'
equation_str = 'exp'.join(parts)
# Add multiplication symbol where needed
processed = ''
i = 0
while i < len(equation_str):
if i + 1 < len(equation_str):
if equation_str[i].isdigit() and equation_str[i+1] == 'x':
processed += equation_str[i] + '*'
i += 1
continue
processed += equation_str[i]
i += 1
return processed
except Exception as e:
raise Exception(f"Error in equation format: {str(e)}")
def process_expression(expr_str):
"""Process mathematical expressions without equations."""
try:
processed_expr = preprocess_equation(expr_str)
x = Symbol('x')
if expr_str.startswith('∫'): # Integration
expr_to_integrate = processed_expr[1:].strip()
expr = parse_expr(expr_to_integrate, transformations=(standard_transformations + (implicit_multiplication_application,)))
result = integrate(expr, x)
return f"∫{format_expression(expr)} = {format_expression(result)}"
elif expr_str.startswith('d/dx'): # Differentiation
expr_to_diff = processed_expr[4:].strip()
if expr_to_diff.startswith('(') and expr_to_diff.endswith(')'):
expr_to_diff = expr_to_diff[1:-1]
expr = parse_expr(expr_to_diff, transformations=(standard_transformations + (implicit_multiplication_application,)))
result = diff(expr, x)
return f"d/dx({format_expression(expr)}) = {format_expression(result)}"
elif 'sqrt' in processed_expr.lower():
try:
transformations = standard_transformations + (implicit_multiplication_application,)
# Remove "sqrt" and parse the expression inside
expr = sp.parse_expr(processed_expr.replace("sqrt", ""), transformations=transformations)
sqrt_result = sp.sqrt(expr)
# If it's sqrt(x^2), simplify it to |x|
simplified_result = sp.simplify(sqrt_result)
steps = []
steps.append(f"**Step 1:** Original expression: \n{to_latex(expr)}")
# Case 1: Perfect Squares → Show exact value (e.g., sqrt(9) = ±3)
if sqrt_result.is_Integer:
steps.append(f"**Step 2:** √{to_latex(expr)} is a perfect square")
steps.append(f"**Step 3:** Solution: \n±{to_latex(sqrt_result)}")
solution = "\n".join(steps)
# Case 2: Non-Perfect Squares → Show decimal value (e.g., sqrt(2) ≈ 1.41)
elif sqrt_result.is_real and not sqrt_result.is_rational:
decimal_value = float(sqrt_result.evalf())
steps.append(f"**Step 2:** √{to_latex(expr)} is not a perfect square")
steps.append(f"**Step 3:** Approximate value: \n{decimal_value}")
solution = "\n".join(steps)
# Case 3: Expressions like √x² → |x|
elif simplified_result != sqrt_result:
steps.append(f"**Step 2:** Simplification using identity: \n{to_latex(simplified_result)}")
solution = "\n".join(steps)
# Case 4: General Expression → Return as-is
else:
steps.append(f"**Step 2:** Taking square root: \n{to_latex(sqrt_result)}")
steps.append(f"**Step 3:** Considering both positive and negative roots: \n±{to_latex(sqrt_result)}")
solution = "\n".join(steps)
except Exception as e:
solution = f"Error: {str(e)}"
elif 'factorial' in processed_expr: # Factorial case
expr = parse_expr(processed_expr, transformations=(standard_transformations + (implicit_multiplication_application,)))
result = expr.doit() # Compute the factorial correctly
return f"{format_expression(expr)} = {result}"
elif '/' in expr_str: # Handle fractions and return decimal
expr = parse_expr(processed_expr, transformations=(standard_transformations + (implicit_multiplication_application,)))
simplified = simplify(expr)
decimal_value = float(simplified)
return f"Simplified: {format_expression(simplified)}\nDecimal: {decimal_value}"
else: # Regular expression simplification
expr = parse_expr(processed_expr, transformations=(standard_transformations + (implicit_multiplication_application,)))
simplified = simplify(expr)
expanded = expand(simplified)
return f"Simplified: {format_expression(simplified)}\nExpanded: {format_expression(expanded)}"
except Exception as e:
raise Exception(f"Error processing expression: {str(e)}")
except Exception as e:
raise Exception(f"Error processing expression: {str(e)}")
def solve_equation(equation_str):
"""Solve the given equation and return the solution."""
try:
if '=' not in equation_str:
return process_expression(equation_str)
# Preprocess equation
equation_str = preprocess_equation(equation_str)
# Split equation into left and right parts
left_side, right_side = [side.strip() for side in equation_str.split('=')]
# Parse both sides with implicit multiplication
transformations = standard_transformations + (implicit_multiplication_application,)
left_expr = parse_expr(left_side, transformations=transformations)
right_expr = parse_expr(right_side, transformations=transformations)
equation = left_expr - right_expr
# Solve the equation
x = Symbol('x')
solution = solve(equation, x)
# Format solution
if len(solution) == 0:
return "No solution exists"
elif len(solution) == 1:
return f"x = {format_expression(solution[0])}"
else:
return "x = " + ", ".join([format_expression(sol) for sol in solution])
except Exception as e:
raise Exception(f"Invalid equation format: {str(e)}")
def generate_steps(equation_str):
"""Generate step-by-step solution for the equation or expression."""
steps = []
try:
if '=' not in equation_str:
steps.append(f"1. Original expression: {equation_str}")
result = process_expression(equation_str)
steps.append(f"2. Result: {result}")
return steps
# Preprocess equation
processed_eq = preprocess_equation(equation_str)
# Split equation into left and right parts
left_side, right_side = [side.strip() for side in processed_eq.split('=')]
# Parse expressions with implicit multiplication
transformations = standard_transformations + (implicit_multiplication_application,)
left_expr = parse_expr(left_side, transformations=transformations)
right_expr = parse_expr(right_side, transformations=transformations)
# Step 1: Show original equation
steps.append(f"1. Original equation: {format_expression(left_expr)} = {format_expression(right_expr)}")
# Step 2: Move all terms to left side
equation = left_expr - right_expr
steps.append(f"2. Move all terms to left side: {format_expression(equation)} = 0")
# Step 3: Factor if possible
factored = sp.factor(equation)
if factored != equation:
steps.append(f"3. Factor the equation: {format_expression(factored)} = 0")
# Step 4: Solve
x = Symbol('x')
solution = solve(equation, x)
steps.append(f"4. Solve for x: x = {', '.join([format_expression(sol) for sol in solution])}")
# Step 5: Verify solutions
steps.append("5. Verify solutions:")
for sol in solution:
result = equation.subs(x, sol)
steps.append(f" When x = {format_expression(sol)}, equation equals {format_expression(result)}")
return steps
except Exception as e:
raise Exception(f"Error generating steps: {str(e)}")