calculator / app.py
meraj12's picture
Update app.py
462ac63 verified
import streamlit as st
import sympy as sp
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from mpmath import mp
from io import BytesIO
import base64
import ast
st.set_page_config(page_title="Advanced Math Calculator", layout="wide")
# --------------------------
# Utilities
# --------------------------
def parse_sympy(expr_str):
"""Parse a user's expression into a SymPy expression, with safe locals."""
try:
# Add common symbols/functions
local_dict = {name: getattr(sp, name) for name in ['sin','cos','tan','asin','acos','atan','log','ln','exp','sqrt','pi','E'] if hasattr(sp, name)}
# allow 'x' as default symbol
x = sp.symbols('x')
local_dict['x'] = x
expr = sp.sympify(expr_str, locals=local_dict)
return expr, None
except Exception as e:
return None, str(e)
def pretty(e):
try:
return sp.pretty(e)
except Exception:
return str(e)
def download_link(content: str, filename: str, mode='text'):
b = content.encode('utf-8')
b64 = base64.b64encode(b).decode()
href = f"data:application/octet-stream;base64,{b64}"
return href
# --------------------------
# App UI
# --------------------------
st.title("🧮 Advanced Math Calculator — Streamlit")
st.write("Solve symbolic & numeric math: algebra, calculus, matrices, plotting, and more.")
col1, col2 = st.columns([2,1])
with col2:
st.header("Options")
mode = st.selectbox("Operation type", [
"Evaluate Expression",
"Solve Equation (single)",
"Solve System of Equations",
"Derivative",
"Integral",
"Limit",
"Series Expansion",
"Simplify / Factor / Expand",
"Matrix Operations",
"Numerical Solve (nsolve)",
"Plot Function"
])
show_steps = st.checkbox("Show symbolic steps / details (when available)", value=True)
extra_precision = st.number_input("Numeric precision (mpmath) (digits)", min_value=15, max_value=80, value=30)
with col1:
st.header(mode)
user_input = st.text_area("Enter expression / equation / matrix (use Python-like syntax)", height=140)
if st.button("Compute"):
mp.mp.dps = int(extra_precision)
if mode == "Evaluate Expression":
expr, err = parse_sympy(user_input)
if err:
st.error(f"Parse error: {err}")
else:
st.subheader("Symbolic Result")
st.code(pretty(expr))
try:
numeric = sp.N(expr)
st.subheader("Numeric Approximation")
st.write(numeric)
except Exception as e:
st.write("Numeric eval error:", e)
elif mode == "Solve Equation (single)":
# Expecting something like '2*x+1=0' or 'sin(x)-1/2=0'
if '=' not in user_input:
st.error("Please provide an equation with '=' (for example: 2*x+1=0)")
else:
left, right = user_input.split('=',1)
L, errL = parse_sympy(left)
R, errR = parse_sympy(right)
if errL or errR:
st.error(errL or errR)
else:
eq = sp.Eq(L, R)
x = list(eq.free_symbols)
try:
if len(x) == 0:
st.write("No variables found in equation. Result:")
st.write(sp.solve(eq))
elif len(x) == 1:
sol = sp.solve(eq, x[0])
st.subheader("Solutions")
st.write(sol)
if show_steps:
st.subheader("SymPy representation")
st.code(sp.srepr(sol))
else:
st.info("Multiple variables detected; consider using 'Solve System of Equations'")
except Exception as e:
st.error(f"Solve error: {e}")
elif mode == "Solve System of Equations":
# Expect lines of equations separated by newline
lines = [ln.strip() for ln in user_input.splitlines() if ln.strip()]
eqs = []
errors = []
for ln in lines:
if '=' not in ln:
errors.append(f"Line missing '=': {ln}")
continue
Ls, Rs = ln.split('=',1)
L, e1 = parse_sympy(Ls)
R, e2 = parse_sympy(Rs)
if e1 or e2:
errors.append(e1 or e2)
else:
eqs.append(sp.Eq(L, R))
if errors:
st.error('
'.join(errors))
else:
vars = sorted(list({s for eq in eqs for s in eq.free_symbols}), key=lambda s: str(s))
try:
sol = sp.solve(eqs, vars, dict=True)
st.subheader("Solutions")
st.write(sol)
except Exception as e:
st.error(f"System solve error: {e}")
elif mode == "Derivative":
# Expect 'd/dx: expr' or 'expr' with variable selection
var = st.text_input("Variable for differentiation (single) — default 'x'", value='x')
expr, err = parse_sympy(user_input)
if err:
st.error(err)
else:
try:
dv = sp.diff(expr, sp.symbols(var))
st.subheader("Derivative")
st.code(pretty(dv))
if show_steps:
st.write("SymPy diff repr:")
st.code(sp.srepr(dv))
except Exception as e:
st.error(str(e))
elif mode == "Integral":
var = st.text_input("Variable for integration (single) — default 'x'", value='x')
definite = st.checkbox("Definite integral?", value=False)
if definite:
bounds = st.text_input("Bounds as two values separated by comma (a,b)")
expr, err = parse_sympy(user_input)
if err:
st.error(err)
else:
try:
a_str,b_str = bounds.split(',')
a, _ = parse_sympy(a_str.strip())
b, _ = parse_sympy(b_str.strip())
res = sp.integrate(expr, (sp.symbols(var), a, b))
st.subheader("Integral result")
st.code(pretty(res))
except Exception as e:
st.error(f"Integration error: {e}")
else:
expr, err = parse_sympy(user_input)
if err:
st.error(err)
else:
try:
res = sp.integrate(expr, sp.symbols(var))
st.subheader("Indefinite Integral")
st.code(pretty(res))
except Exception as e:
st.error(f"Integration error: {e}")
elif mode == "Limit":
# format: expression, point [, dir]
text = user_input
try:
parts = [p.strip() for p in text.split(',')]
expr, err = parse_sympy(parts[0])
if err: raise Exception(err)
pt = parse_sympy(parts[1])[0] if len(parts) > 1 else sp.oo
dir = parts[2] if len(parts) > 2 else None
if dir:
res = sp.limit(expr, sp.symbols('x'), pt, dir)
else:
res = sp.limit(expr, sp.symbols('x'), pt)
st.subheader("Limit")
st.code(pretty(res))
except Exception as e:
st.error(f"Limit parse/eval error: {e}")
elif mode == "Series Expansion":
# format: expr, x, x0, n
try:
parts = [p.strip() for p in user_input.split(',')]
expr, err = parse_sympy(parts[0])
if err: raise Exception(err)
var = sp.symbols(parts[1]) if len(parts) > 1 else sp.symbols('x')
x0 = parse_sympy(parts[2])[0] if len(parts) > 2 else 0
n = int(parts[3]) if len(parts) > 3 else 6
ser = sp.series(expr, var, x0, n).removeO()
st.subheader("Series Expansion")
st.code(pretty(ser))
except Exception as e:
st.error(f"Series parse/eval error: {e}")
elif mode == "Simplify / Factor / Expand":
option = st.selectbox("Choose", ["simplify","factor","expand","trigsimp","ratsimp"], index=0)
expr, err = parse_sympy(user_input)
if err:
st.error(err)
else:
try:
if option == 'simplify': res = sp.simplify(expr)
elif option == 'factor': res = sp.factor(expr)
elif option == 'expand': res = sp.expand(expr)
elif option == 'trigsimp': res = sp.trigsimp(expr)
elif option == 'ratsimp': res = sp.ratsimp(expr)
st.subheader(option.capitalize())
st.code(pretty(res))
except Exception as e:
st.error(e)
elif mode == "Matrix Operations":
st.info("Matrix format: use Python-like lists, e.g. [[1,2],[3,4]]")
try:
# Use ast.literal_eval for safe parsing of Python literals
parsed = ast.literal_eval(user_input)
M = sp.Matrix(parsed)
except Exception as e:
st.error(f"Matrix parse error: {e}")
M = None
if M is not None:
st.subheader("Matrix")
st.write(M)
op = st.selectbox("Matrix op", ['determinant','inv','eigen','rank','rref','transpose'])
try:
if op == 'determinant': st.write(M.det())
elif op == 'inv': st.write(M.inv())
elif op == 'eigen': st.write(M.eigenvects())
elif op == 'rank': st.write(M.rank())
elif op == 'rref': st.write(M.rref())
elif op == 'transpose': st.write(M.T)
except Exception as e:
st.error(f"Matrix operation error: {e}")
elif mode == "Numerical Solve (nsolve)":
# Expect equation and initial guess(s)
try:
# format: equation , guess
eq_part, guess_part = [p.strip() for p in user_input.split(',')]
if '=' in eq_part:
L, R = eq_part.split('=',1)
expr = parse_sympy(L)[0] - parse_sympy(R)[0]
else:
expr = parse_sympy(eq_part)[0]
# guess can be single number or comma-separated for multivariate
guesses = [float(g) for g in guess_part.replace(';',',').split(',') if g.strip()]
if len(guesses) == 0:
st.error('Please provide at least one initial guess after a comma')
else:
if len(guesses) == 1:
sol = sp.nsolve(expr, guesses[0])
else:
sol = sp.nsolve(expr, guesses)
st.write(sol)
except Exception as e:
st.error(f"nsolve error: {e}")
elif mode == "Plot Function":
# allow plotting single-variable functions in variable x
expr, err = parse_sympy(user_input)
if err:
st.error(err)
else:
try:
var = sp.symbols('x')
f = sp.lambdify(var, expr, modules=['numpy'])
rng = st.text_input('Enter x-range as a,b', value='-10,10')
a_str,b_str = [s.strip() for s in rng.split(',')]
a = float(a_str); b = float(b_str)
xs = np.linspace(a,b,1000)
ys = f(xs)
fig, ax = plt.subplots()
ax.plot(xs, ys)
ax.set_xlabel('x')
ax.set_ylabel('f(x)')
ax.grid(True)
st.pyplot(fig)
# allow download as png
buf = BytesIO()
fig.savefig(buf, format='png', bbox_inches='tight')
buf.seek(0)
st.download_button('Download plot PNG', data=buf, file_name='plot.png')
except Exception as e:
st.error(f"Plot error: {e}")
# Footer / helpers
st.sidebar.header('Examples')
st.sidebar.markdown("""
**Evaluate:** `sin(pi/4) + log(10)`
**Solve:** `x**2 - 5*x + 6 = 0`
**System:**
`x + y = 3`
`x - y = 1`
**Derivative:** `sin(x)*exp(x)`
**Integral:** `x**2` (use definite checkbox + bounds `0,2`)
**Series:** `exp(x), x, 0, 6`
**Matrix:** `[[1,2],[3,4]]`
**Plot:** `sin(x)/x`
""")
st.sidebar.markdown('---')
st.sidebar.markdown('Built with SymPy + Streamlit. For deployment, add `app.py` and `requirements.txt` to a Hugging Face Space (Streamlit).')
# End of app.py