Skip to main content

mathsteps

A dual-interface step-by-step symbolic/numerical math solver — use as a CLI or Python library.

mathsteps takes a math problem and prints every intermediate step along the way to the final answer — like Symbolab or Mathway, but local, scripted, and open-source.

Libraries compute; you explain. NumPy / SciPy / SymPy give you correct, fast final answers. They don't naturally expose how they got there. mathsteps is the step-tracking / explanation layer on top.

Installation

pip install mathsteps

This installs:

  • ✅ mathsteps CLI command (ready to use immediately)
  • ✅ mathsteps Python package (importable in scripts/notebooks)
  • ✅ All dependencies pre-configured

For development (editable install from source):

python -m venv .venv
.venv\Scripts\python.exe -m pip install -e ".[dev]"

Quick Start

import mathsteps

# Solve Ax = b (system of 2 equations, 2 unknowns)
result = mathsteps.linear_system([[1, 2], [3, 4]], [5, 11])
print(result.answer)    # [1. 2.]        a numpy array
print(result.exact)     # [1, 2]         the exact SymPy rationals
print(result.verified)  # True           checked against numpy.linalg.solve
print(result.solver)    # gaussian_elimination

# Print step-by-step solution
for i, step in enumerate(result.steps, 1):
    print(f"Step {i}: {step.description}")
    if step.before:
        print(f"  Before: {step.before}")
    if step.after:
        print(f"  After:  {step.after}")

# Root finding: find x where cos(x) = x
result = mathsteps.root("cos(x) - x", method="newton", x0=0.0)
print(result.answer)    # 0.7390851332151607 (a float)

# Solve ODE: dy/dx = -2xy with initial condition
result = mathsteps.ivp("-2*x*y", y0=1, x_end=2, h=0.1, method="rk4")
print(result.answer)    # y(2) = 0.01832...
t, y = result.details["x"], result.details["y"]   # the whole trajectory

# Interpolation returns a callable polynomial
p = mathsteps.lagrange([[0, 1], [1, 2], [2, 5]]).answer
print(p(3))             # 10.0
print(p.expr)           # x**2 + 1   (SymPy form)

# Matrices
result = mathsteps.determinant([[6, 1, 1], [4, -2, 5], [2, 8, 7]])
print(result.answer)    # -306.0   (result.exact == -306)

values, vectors = mathsteps.eigenvalues([[4, 2], [2, 3]]).answer   # numpy.linalg.eig layout

Systems, second-order equations and BVP profiles

# A system of ODEs (predator-prey), forwards ...
r = mathsteps.ivp(["1.1*u - 0.4*u*w", "0.1*u*w - 0.4*w"],
                  variable="t", function=["u", "w"], y0=[10, 5], x_end=20, h=0.005)
r.answer              # [u(20), w(20)]
r.details["y"]        # shape (points, 2): the whole solution

# ... a second-order equation (damped spring  x'' = -4x - 0.5 x')
r = mathsteps.ivp_second_order("-4*x - 0.5*xp", variable="t", function="x",
                               y0=1, dy0=0, x_end=10, h=0.005)

# ... and backwards in x (x_end < x0)
mathsteps.ivp("y", y0=2.718281828, x0=1, x_end=0, h=0.01)    # y(0) = 1

# Boundary value problems return the whole solution, not just y(x_end)
r = mathsteps.bvp_shooting("0.01*(y-20)", y_left=100, y_right=50, x_end=10)
r.answer              # T(x) on the grid r.details["x"]
r.details["slope"]    # the converged T'(0)

Result Structure

Every function returns a mathsteps.Result object:

result = mathsteps.linear_system([[1, 2], [3, 4]], [5, 11])

result.solver      # str: solver name ("gaussian_elimination")
result.answer      # the answer as plain Python / NumPy: float, ndarray, tuple of arrays, ...
result.exact       # the exact SymPy form where one exists (rationals, matrices), else None
result.details     # extra outputs: x / y grids of ODE and BVP solutions, shooting slope, ...
result.steps       # list[Step]: step-by-step breakdown
result.verified    # True / False vs NumPy/SciPy, or None if not checked
result.converged   # iterative solvers: False if max_iter ran out; else None/True
result.problem     # dict: the original problem dict

# Inspect each step
for step in result.steps:
    step.description  # str: what happened (e.g. "Swap rows")
    step.before       # str: state before (e.g. matrix as string)
    step.after        # str: state after
    step.data         # dict: structured numerical data

Full library API

Function result.answer Problem
linear_system(A, b) ndarray x (exact: rationals) Ax = b
determinant(A) float (exact: exact value) determinant
inverse(A) ndarray (exact: sympy.Matrix) matrix inverse
lu(A) tuple of arrays (P, L, U), P @ A = L @ U LU decomposition
eigenvalues(A) (values, vectors) like numpy.linalg.eig, one column per independent eigenvector eigenvalues / eigenvectors
cramers_rule(A, b) ndarray Cramer's rule
root(f, …) float find f(x)=0
ivp(f_expr, …) y(x_end): float, or ndarray for a system; details: x, y dy/dx = f(x,y), systems, backwards
ivp_second_order(f, …) [y(x_end), y'(x_end)]; details: x, y y'' = f(x, y, y')
bvp_shooting(f, …) y on the grid details["x"]; details["slope"] two-point BVP
bvp_finite_difference(…) y on the grid details["x"] linear BVP via finite differences
lagrange(points) callable polynomial (p(x), p.expr, p.coefficients) polynomial interpolation
newton_divided_differences(points) callable polynomial Newton-form interpolation
integrate(f, a, b, …) float numerical integration
differentiate(f, x, …) float numerical derivative
solve_problem(dict) Result generic (raw problem dict)
available_solvers() list[str] every registered solver

1. Direct subcommands (easiest — flags prompt for everything)

mathsteps linear-system --A "1 2; 3 4" --b "5 11"
mathsteps root          --function "x**3 - x - 2" --method newton --x0 1.5
mathsteps root          --function "x**3 - x - 2" --method bisection --a 1 --c 2
mathsteps root          --function "x**3 - x - 2" --method secant   --x0 1 --x1 2
mathsteps ivp           --f-expr "-2*x*y" --y0 1 --x-end 2 --h 0.1 --method rk4
mathsteps bvp           --f-expr "sin(x) - y" --a 0 --c 0 --x-end 1 --s0 0 --s1 1
mathsteps determinant   --A "6 1 1; 4 -2 5; 2 8 7"
mathsteps inverse       --A "1 2 3; 0 1 4; 5 6 0"
mathsteps lu            --A "2 1 1; 4 -6 0; -2 7 2"
mathsteps eigen         --A "2 0 0; 0 3 4; 0 4 9"
mathsteps cramer        --A "2 1 -1; -3 -1 2; -2 1 2" --b "8 -11 -3"
mathsteps interp        --points "(0,1); (1,2); (2,5)"
mathsteps integrate     --function "sin(x)" --a 0 --c 3.14159 --n 100 --method simpson
mathsteps diff          --function "sin(x)" --x 1 --h 0.001

Add --pretty to any command for Rich tables + LaTeX-style math.

2. Free-form equation (auto-detects type)

mathsteps solve "x**3 - x - 2"
mathsteps solve "x**2 - 4 = 0"
mathsteps solve "y' = -2*x*y" --y0 1 --x-end 2 --h 0.1 --method rk4

The detector:

  • y' / dy/dx / d?/d? ⇒ IVP
  • exactly one = with no derivatives ⇒ root-finding (treats lhs - rhs as f(x))
  • otherwise ⇒ root-finding on the whole expression

3. JSON file (power-user / scripted)

mathsteps solve examples\linear_system_3x3.json
mathsteps verify examples\linear_system_3x3.json   # exit 0 on PASS, 1 on FAIL
mathsteps list-solvers                              # show every solver

JSON files live under examples/ and have a strict per-solver schema (see mathsteps/domains/<domain>/<solver>.py for the exact keys).

4. Interactive wizard

mathsteps ask

Walks you through a numbered menu, prompting for each input.

Supported problem types

Linear algebra

Type Solver Notes
linear_system Gaussian elimination partial pivoting + back-substitution
matrix_inverse Gauss-Jordan reduced row echelon form
determinant Cofactor expansion
lu_decomposition Doolittle partial pivoting
eigenvalues Characteristic polynomial small matrices
cramers_rule Cramer's rule

Numerical analysis

Type Solver
root_finding (method bisection) Bisection method
root_finding (method newton_raphson) Newton-Raphson
root_finding (method secant) Secant method
root_finding (method fixed_point) Fixed-point iteration x = g(x)
numerical_diff Finite differences (forward / backward / central)
numerical_integration Trapezoidal or Simpson's rule
interpolation (method lagrange) Lagrange interpolation
interpolation (method newton_divided_differences) Newton form

IVP — initial value problems

Type Solver
ivp (method euler) Forward Euler
ivp (method heun) Improved Euler / Heun
ivp (method midpoint) Midpoint method (RK2)
ivp (method rk4) Classical 4th-order Runge-Kutta
ivp (method rk45) Adaptive Dormand-Prince RK45

BVP — boundary value problems

Type Solver
bvp (method shooting) Shooting method (uses RK4 + secant internally)
bvp (method finite_difference) Finite-difference discretisation + linear algebra

See examples/ for the exact JSON schema of each solver.

Documentation

Verification and convergence

Answers are checked against a trusted reference (NumPy / SciPy / SymPy ground truth) by mathsteps.verify.verify_problem, the single source of truth shared by the Python API and the CLI. The result is three-valued:

result.verified Meaning
True Checked against an independent reference and it matches.
False Checked and it does not match.
None Not checked: you passed verify=False, or the check could not run. Never a pass.

Every problem type has an independent check: linear algebra against NumPy, roots against SciPy, ODEs (scalar and systems) against solve_ivp, integrals against scipy.integrate.quad, derivatives against the exact SymPy derivative, BVPs against scipy.integrate.solve_bvp, interpolants through their data points. Numerical methods are supposed to differ from the true answer by their truncation error, so each tolerance is derived from that error (Richardson extrapolation): a correct Euler run passes and a wrong RK4 answer does not.

The CLI prints Verification: PASS, FAIL or NOT CHECKED, and mathsteps verify file.json exits 0 / 1 / 2 respectively.

Iterative solvers (Newton, secant, bisection, fixed-point, shooting) set result.converged. If max_iter runs out before tol is met it is False, a mathsteps.ConvergenceWarning is emitted, and the last step says so; the CLI prints a warning on stderr. Raise max_iter (slowly converging problems like cos(x) fixed-point need more than the default 50) or loosen tol.

Testing

.venv\Scripts\python.exe -m pytest -v

Project layout

mathsteps/
├── core/
│   ├── step.py            # Step dataclass
│   ├── solver_base.py     # abstract Solver interface
│   ├── registry.py        # maps problem type -> solver
│   └── render.py          # pretty LaTeX / Rich output
├── domains/
│   ├── linalg/            # 6 solvers
│   ├── numerical/         # 8 solvers
│   ├── ode_ivp/           # 5 solvers
│   └── ode_bvp/           # 2 solvers
├── cli.py                 # mathsteps console script
└── verify.py              # ground-truth verification
examples/                  # one JSON file per supported problem type
tests/                     # pytest suite

License

MIT — see LICENSE.

Large problems: the detail setting

A 40x40 elimination or an ODE with 30 000 steps would produce a flood of steps nobody reads. Solvers that can flood take detail=:

detail Records
"full" every step (the default for small problems)
"summary" the first and last few steps plus a note, or one step per pivot column; the default for large problems (linear systems / inverses / LU above 10x10, ODEs above 200 steps)
"none" only the introduction and the final step

The answer, details and verified are identical in every mode:

mathsteps.linear_system(A, b)                  # 40x40: ~40 steps, not ~800
mathsteps.linear_system(A, b, detail="full")   # every row operation
mathsteps.ivp("-0.5*y", y0=1, x_end=20, h=0.001, detail="none")

The CLI has the same flag (--detail full|summary|none) on linear-system, inverse, lu and ivp.

What it does not do

  • ODE systems and second-order IVPs are solved as first-order systems; boundary value problems are scalar and second-order (shooting, or linear finite differences).
  • Eigenvalues are exact when the characteristic polynomial factors into pieces of degree <= 2 over the rationals (integers, fractions, a ± sqrt(b)) and 15-digit numerical roots otherwise.
  • integrate samples the interval's endpoints, so an integrand that is infinite or undefined there (sin(x)/x at 0) raises an error instead of returning garbage; start at a small a instead.

Metadata

Release files for mathsteps 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mathsteps 0.1.0
File Size Uploaded
mathsteps-0.1.0.tar.gz 115.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mathsteps 0.1.0
File Interpreter ABI Platform
mathsteps-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 195.9 kB

Release files / mathsteps-0.1.0.tar.gz

Download URL mathsteps-0.1.0.tar.gz
Size 115.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3aad697cee6472e5d382dee5cdd09ed1be0367a258fdcd06847673a2eaa22bbe
BLAKE2b-256 checksum
How to use checksums
58b2c5cf6ade5bd7ffd553c89327ae13e4edd4775952f462946cab09970d4a8f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / mathsteps-0.1.0-py3-none-any.whl

Download URL mathsteps-0.1.0-py3-none-any.whl
Size 80.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bd9351b5cda301f11572116e126d7abecb2583f540b624f2585742e7d7e904b8
BLAKE2b-256 checksum
How to use checksums
0dd78cd3a37b9f5ea2fa40a1e052c0927bf7335b46f2b22d147cc0f93b9aa027
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page