Skip to main content

mpeqs

Exact answers for the arithmetic a language model should not be doing itself.

Linux macOS Windows PyPI

pip install mpeqs
import mpeqs

mpeqs.solve({"solver": "arith", "answer": "(17/100)*250"})   # Fraction(85, 2)

The model names a solver and fills its slots. This evaluates the spec exactly, or refuses by name — it never guesses, and it never returns a number it could not derive.

The refusal is the point. A solver that cannot evaluate a spec raises Refusal with a reason, and that reason is a routing signal: it tells the caller to ask the model, rather than quietly handing back a wrong number.

try:
    mpeqs.solve({"solver": "arith", "answer": "1/0"})
except mpeqs.Refusal as why:
    ...   # ask the model; the record declined this one

Shape disposes over name. A model that picks the wrong solver but fills the right slots has still described the problem correctly, so a spec carrying an answer key is arithmetic whatever it calls itself. Pass repair=False to turn that off.

What is exact and what is not

Results come back as fractions.Fraction, so nothing is lost on the way out. The exactness is in the arithmetic, not in the parsing: a ratio written as a ratio stays exact, while a decimal literal is read as a float first.

mpeqs.solve({"solver": "arith", "answer": "1/10+2/10"})   # Fraction(3, 10)  exact
mpeqs.solve({"solver": "arith", "answer": "0.1+0.2"})     # the binary expansion

Solvers

arith, geometry, iterate, modular, multisearch, polynomial, and the named families behind them — factorisation, gcd/lcm, linear systems, quadratics, remainders, combinatorics, rates, mixtures, ratios, base conversion.

mpeqs.solvers() lists what a given build dispatches to.

No dependencies

The whole library is standard library. One py3-none-any wheel serves Linux, macOS and Windows on every supported interpreter, the install is instant, and a test asserts that no third-party import creeps in.

Calculus: three derivatives that must agree

mpeqs.calculus differentiates (chain rule included), integrates expanded polynomials exactly, and solves linear and quadratic equations with exact rational roots — refusing irrational discriminants by value rather than floating them.

The derivative ships three ways on purpose: symbolic rules, dual-number autograd (the chain rule falls out of the arithmetic), and grad_pyspell — an interpreter for the PySpell subset run over dual numbers, so a function with loops and branches differentiates exactly at a point. The tests demand all three agree at scores of random rational points; a bug in one must be a matching bug in another to survive.

from mpeqs import calculus
calculus.differentiate("(3*x**2 + 5)**4")        # 4 * (3*x**2 + 5)**3 * (3 * (2*x))
calculus.integrate("x**2", lower=0, upper=1)      # Fraction(1, 3)
calculus.solve_quadratic(1, -5, 6)                # (Fraction(3), Fraction(2))
calculus.grad_pyspell("""
def f(x):
    y = 1
    n = 0
    while n < 4:
        y = y * (3*x*x + 5)
        n = n + 1
    return y
""", Fraction(1, 2))                              # Fraction(36501, 16) -- exact

PySpell: when an expression is not enough

solve() evaluates expressions, and is safe by restriction — the source is parsed to an AST, walked against a whitelist, and evaluated with __builtins__ emptied. Nothing runs that was not understood first.

What it cannot express is a procedure. There is no expression for "how many steps does the Collatz sequence take from 27" — that needs a loop, a branch and an accumulator. The usual answer is to let the model emit Python and exec it, which trades a whitelist for a sandbox and hope.

mpeqs.pyspell is the third option. PySpell is mpedb's stored-function language: a small deterministic subset of Python with no imports, no clock, no randomness, no file or network I/O, and a fixed instruction budget so a runaway loop fails identically everywhere. A model can write a loop. It still cannot open a socket.

import mpeqs.pyspell as spell

spell.call("""
def col(n):
    if n < 1:
        return 1 // 0
    c = 0
    while n != 1:
        if n % 2 == 0:
            n = n // 2
        else:
            n = 3 * n + 1
        c = c + 1
    return c
""", 27)
# 111

And when the model claims a relationship between two functions, the claim is checked against a probe corpus rather than believed:

spell.check_bijective(
    "def dbl(x):\n    return x * 2\n",
    "def hlv(x):\n    return x // 2\n")
# mpeqs.Refusal: dbl/hlv is not bijective: ... forward(Float(1e308)) = Float(inf) ...

That refusal is correct. x*2 then x//2 is genuinely not bijective over the values PySpell admits — a denormal floors to zero, and 1e308 is an integral float whose double is infinity. The verifier names the input that breaks it, every time. Guard the domain to bounded positive integers and the same pair is accepted, reporting how many probe values actually round-tripped rather than a bare "verified".

It caught the author of that example twice while it was being written. That is the argument for "declare and check" over "generate and hope".

Optional, because it is not free: mpedb ships as a compiled wheel needing CPython 3.12 or newer, while mpeqs itself runs from 3.10 with one artefact for every platform.

pip install mpeqs[pyspell]

Without it, mpeqs.pyspell.available() is False and every entry point raises Refusal naming the extra, rather than failing at import. Nothing else in mpeqs changes.

Licence

The mpedb License 1.0 — free of charge for every person and every organization, except that a group over five billion dollars in revenue or valuation owes seven US cents per device, once. Not an OSI-approved licence.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mpeqs-0.4.0.tar.gz (128.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mpeqs-0.4.0-py3-none-any.whl (59.3 kB view details)

Uploaded Python 3

File details

Details for the file mpeqs-0.4.0.tar.gz.

File metadata

  • Download URL: mpeqs-0.4.0.tar.gz
  • Upload date:
  • Size: 128.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.12

File hashes

Hashes for mpeqs-0.4.0.tar.gz
Algorithm Hash digest
SHA256 2c0c4f22da95eadb33a9d673fc7072539e88a98321a1093feeab20bcde39e94a
MD5 108778699459037693fce6cc45e9b476
BLAKE2b-256 1a0ebe7aca210956f56136bf360d124612ae884ba9efe78cd848228403d04997

See more details on using hashes here.

File details

Details for the file mpeqs-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: mpeqs-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 59.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.12.12

File hashes

Hashes for mpeqs-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1e0d477cb4699a7fce15aa8e51af71a16d743fdfa24c5b67b5f539da58b5a6a2
MD5 00e574e1cfe735289c9d8539b3328b44
BLAKE2b-256 f559ecb14799a7ac42e93e971fa9cf5693f72b5e9691ee0117bd64d44ad47cf8

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page