A minimal command-line calculator for exact arithmetic, symbolic differentiation, integration, algebraic equation solving, and ordinary differential equations.
Powered by SymPy.
phil is designed to be the first-stop calculator for quick terminal math, homework, and symbolic workflows — before reaching for WolframAlpha, a Python REPL, or a graphing calculator.
It prioritizes exactness, speed, and discoverability. When it rewrites your input, it says so.
Requires uv.
uv tool install philcalc- PyPI: https://pypi.org/project/philcalc/
- Source: https://github.com/sacchen/phil
- Tutorial: TUTORIAL.md
- Roadmap: ROADMAP.md
phil '1/3 + 1/6'
phil '10^100000 + 1 - 10^100000'
phil 'd(x^3 + 2*x, x)'
phil 'int(sin(x), x)'
phil "ode y' = y, y(0)=1"
phil '(1 - 25e^5)e^{-5t} + (25e^5 - 1)t e^{-5t} + t e^{-5t} ln(t)'Or open the REPL (phil) and try these in order:
1/3 + 1/6d(x^3 + 2*x, x)int(sin(x), x)solve(x^2 - 4, x)N(pi, 20)
If stuck, run :examples or :h.
phil '<expression>'
phil --format pretty '<expression>'
phil --format json '<expression>'
phil --no-simplify '<expression>'
phil --explain-parse '<expression>'
phil --latex '<expression>'
phil --latex-inline '<expression>'
phil --latex-block '<expression>'
phil --wa '<expression>'
phil --wa --copy-wa '<expression>'
phil --color always '<expression>' # also: auto (default), never
phil "ode y' = y"
phil "ode y' = y, y(0)=1"
phil "linalg solve A=[[2,1],[1,3]] b=[1,2]"
phil "linalg rref A=[[1,2],[2,4]]"
phil "linalg det A=[[1,2],[3,4]]"
phil "linalg inv A=[[1,2],[3,4]]"
phil "linalg eig A=[[1,2],[3,4]]"
phil "linalg nullspace A=[[1,2],[2,4]]"
phil :examples
phil :tutorial
phil :ode
phil :linalgphil
phil> <expression>REPL commands:
:h/:helpshow strict command reference?,??,???progressive feature discovery (quick start, speed shortcuts, advanced demos):examplesshow runnable expression patterns:tutorial/:t/:tourstart interactive tutorial mode:odeshow ODE cheat sheet and templates:linalg/:lashow linear algebra cheat sheet and templates:next/:repeat/:donecontrol interactive tutorial mode (Enteradvances while tutorial is active):v/:versionshow current version:update/:checkcompare current vs latest version and print update command:q/:quit/:xexit
The REPL starts with phil vX.Y.Z REPL [status] (:h help, :t tutorial) on interactive terminals.
When an update is available, startup prints uv tool upgrade philcalc on the next line.
Errors are prefixed with E: followed by a hint: line.
Most evaluation errors also include hint: try WolframAlpha: <url> (suppressed for some local guardrail failures).
Session keeps ans (last result) and supports assignment: A = Matrix([[1,2],[3,4]]).
Inline CLI options work per-expression: --latex d(x^2, x).
For readable ODE solving, prefer ode ... input: ode y' = y.
These normalizations apply in all modes (including --strict):
{}->()ln(t)->log(t)!factorialsin^2(x)accepted- Leibniz shorthand accepted (
d(sin(x))/dx,df(t)/dt) - ODE shorthand accepted (
dy/dx = y,y' = y,y'' + y = 0,y'(0)=0) - LaTeX-style ODE accepted (
\frac{dy}{dx} = y,\frac{d^2y}{dx^2} + y = 0) - Common LaTeX wrappers and commands are normalized:
$...$,\(...\),\sin,\cos,\ln,\sqrt{...},\frac{a}{b}
Relaxed parsing (default) also enables implicit multiplication:
2x->2*xsinx->sin(x)(with ahint:notice)
Use --strict to require explicit multiplication:
phil --strict '2*x'Undefined symbols raise errors.
Built-in helper names are reserved for evaluation and cannot be reassigned.
In ODE input, prefer explicit multiplication (20*y instead of 20y) for predictable parsing.
phil defaults to exact symbolic arithmetic.
Cancellable huge expressions stay fast and exact:
10^10000000000 + 1 - 10^10000000000 -> 1
2^(2^20) + 1 - 2^(2^20) -> 1
Non-cancellable explosive growth fails fast with a recovery hint rather than hanging:
10^10000000000 + 1
2^(2^(2^20))
100001!
factorial(10^10)
Ambiguous shorthand is rejected with explicit guidance:
sin x^2
Precedence note:
-2^2->-(2^2)- Use
(-2)^2for a negative base squared.
--format jsonprints a compact JSON object withinput,parsed, andresult; diagnostics stay onstderr--format prettyimproves matrix readability--explain-parseprintshint: parsed as: ...onstderr--color auto|always|nevercontrols ANSI output on diagnostic lines;NO_COLORalso respectedstdoutstays result-only, so pipes and scripts remain predictable- Complex expressions print a WolframAlpha equivalent link by default;
--waforces it,--copy-wacopies it to the clipboard
uv tool upgrade philcalcIn REPL:
- Startup prints a status badge on interactive terminals; when an update is available a second line prints the upgrade command
:versionshows your installed version:update/:checkshow current version, latest known release, and update command
For release notifications on GitHub, use "Watch" -> "Custom" -> "Releases only" on the repo page.
$ phil '1/3 + 1/6'
1/2
$ phil 'd(x^3 + 2*x, x)'
3*x**2 + 2
$ phil 'int(sin(x), x)'
-cos(x)
$ phil 'solve(x^2 - 4, x)'
[-2, 2]
$ phil 'N(pi, 30)'
3.14159265358979323846264338328
$ phil --latex 'd(x^2, x)'
2 x
$ phil --latex-inline 'd(x^2, x)'
$2 x$
$ phil --latex-block 'd(x^2, x)'
$$
2 x
$$
$ phil --format pretty 'Matrix([[1,2],[3,4]])'
[1 2]
[3 4]| Operation | Syntax |
|---|---|
| Derivative | d(expr, var) |
| Integral | int(expr, var) |
| Solve equation | solve(expr, var) |
| Solve ODE | dsolve(Eq(...), func) |
| Equation | Eq(lhs, rhs) |
| Numeric eval | N(expr, digits) |
| Integer GCD/LCM | gcd(a, b), lcm(a, b) |
| Primality / factorization | isprime(n), factorint(n) |
| Rational parts | num(expr), den(expr) |
| Matrix determinant | det(Matrix([[...]])) or linalg det A=[[...]] |
| Matrix inverse | inv(Matrix([[...]])) or linalg inv A=[[...]] |
| Matrix rank | rank(Matrix([[...]])) or linalg rank A=[[...]] |
| Matrix eigenvalues | eigvals(Matrix([[...]])) or linalg eig A=[[...]] |
| Matrix RREF | rref(Matrix([[...]])) or linalg rref A=[[...]] |
| Matrix nullspace | nullspace(Matrix([[...]])) or linalg nullspace A=[[...]] |
| Solve linear system (Ax=b) | msolve(Matrix([[...]]), Matrix([...])) or linalg solve A=[[...]] b=[...] |
| Symbolic linear solve | linsolve((Eq(...), Eq(...)), (x, y)) |
Notes:
For Ax=b, use linalg solve A=[[...]] b=[...] or msolve(A, b) instead of solve(A=..., b=...).
x, y, z, t, pi, e, f
sin, cos, tan, exp, log, sqrt, abs, gamma, atan2, binomial, limit, series, factor, expand
gcd, lcm, isprime, factorint, num, den
symbols("A B C")returns a tuple of symbolsS("A")is shorthand forSymbol("A")
Matrix, eye, zeros, ones, det, inv, rank, eigvals, rref, nullspace, msolve, linsolve
^is exponentiation (x^2)- Function exponent notation is accepted (
sin^2(x),cos^2(x)) !is factorial (5!)- Relaxed mode (default) allows implicit multiplication (
2x); use--strictto require2*x d(expr)/int(expr)infer the variable when exactly one symbol is present- Leibniz shorthand is accepted:
d(sin(x))/dx,df(t)/dt - ODE shorthand is accepted:
dy/dx = y,y' = y,y'' + y = 0,y'(0)=0 - LaTeX-style ODE shorthand is accepted:
\frac{dy}{dx} = y,\frac{d^2y}{dx^2} + y = 0 name = exprassigns in the REPL session (ansis always the last result)- Built-in helper names are reserved and cannot be reassigned
- Undefined symbols raise an error
From a local clone:
uv tool install . # install locally
uv run --group dev pytest # run tests
uv run --group dev pytest -m "not integration" # fast local loop
scripts/checks.sh # full quality gateCI runs via GitHub Actions. License is MIT. See CONTRIBUTOR.md for contribution guide and release process.