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