A maths tutor that runs entirely on your own machine. SymPy computes the exact symbolic answer instantly; a local LLM writes the explanation, the alternative methods, the hints and the common mistakes around it.
Every problem runs through two systems at once, and this separation is the idea worth taking from the project:
SymPy is the source of truth for the answer. The model supplies the teaching. If a small local model hallucinates a wrong root, the SymPy panel still shows the correct one beside it.
That separation is what makes a 1.9 GB model usable for mathematics at all. Asking a small model to both compute and explain gets you confident wrong arithmetic; asking it only to explain a result it was handed is a task it can actually do.
sympy_solve() is keyword-dispatched — it looks for derivative, integrate, factor, simplify, expand, or an = sign, and routes to the matching SymPy call.
| Version | Lines | Notes |
|---|---|---|
| math_platform | 685 | Earliest build. No SymPy parsing layer |
| math_platform_FREE | 592 | Trimmed free edition |
| mathgenius_pro | 1,016 | SymPy parsing added; 6 themes |
| mathgenius_pro_v3 | 1,290 | Solver refactored into sympy_solve() |
| mathgenius_v4 | 1,182 | Current. 4 themes, quiz, ELI5, Manim hooks |
Verification found a real bug — one that had been silently degrading the app for the whole life of the project.
x^2 - 7x + 12 = 0 returned nothingThat is the first example problem in the project's own README, and it is how most people naturally type a power. The instant-answer panel simply showed nothing — no error, no message.
The cause: sympy_solve() built its parser transformations as
TF = standard_transformations + (implicit_multiplication_application,)
SymPy's parser treats ^ as bitwise XOR, not exponentiation. So the expression raised:
TypeError: unsupported operand type(s) for ^: 'Symbol' and 'Add'
That exception was swallowed by a bare except: pass at the end of the function. sympy_solve() returned an empty dict, and the UI rendered an empty panel — indistinguishable from "this problem type isn't supported".
Add SymPy's convert_xor transformation. ** continues to work identically. Applied to all three affected versions.
6/7 → 7/7 problem forms now produce a symbolic result.
mathgenius_v4/app.py still contains 13 bare except: pass blocks. One of them hid this defect for the project's entire lifetime. Narrowing them is the obvious next cleanup and has deliberately not been done yet — it is listed as outstanding rather than quietly performed, because it is a behaviour change across code paths that have no test coverage.
| Check | Result |
|---|---|
ollama_ok() | Pass — True |
get_models() | ['llama3.2:latest'] |
ai_solve("x^2 - 7x + 12 = 0") | 46.1 s, structured dict, 10 keys |
→ short_answer | x=3,4 — mathematically correct |
| → classification | Algebra · ['quadratic'] · Beginner |
sympy_solve(), 7 problem forms | 7 / 7 after fix |
| Streamlit boots | HTTP 200, /_stcore/health → ok |
x^2 - 7x + 12 = 0 -> solutions ['3', '4'] Find the derivative of sin(x)*exp(x) -> derivative exp(x)*sin(x) + exp(x)*cos(x) integrate x**3 -> integral x**4/4 + C factor x**3 - 8 -> factored (x - 2)*(x**2 + 2*x + 4) simplify (x**2 - 1)/(x - 1) -> simplified x + 1 expand (x + 2)**3 -> expanded x**3 + 6*x**2 + 12*x + 8 2*x + 6 = 0 -> solutions ['-3']
ollama serve ollama pull llama3.2 python -m venv .venv && .venv\Scripts\activate pip install streamlit requests sympy matplotlib plotly numpy pandas cd mathgenius_v4 streamlit run app.py # http://localhost:8501
manim is listed for the optional animated-video feature. It is invoked via subprocess, not imported, so the app runs fine without it — and without ffmpeg or LaTeX.
Plotted by the application's own graph builder, with the roots SymPy solved exactly marked on the curve. This is also the input that exposed the caret-parsing defect — before the fix it silently returned nothing.
A symbolic engine for the answer, a language model for the explanation. Neither is asked to do the other's work, which is why a small offline model is enough.